ooo it really do be workin

This commit is contained in:
asonix 2021-05-04 20:19:02 -05:00
parent 00de0a88b5
commit 6c628552bb
11 changed files with 1870 additions and 257 deletions

779
Cargo.lock generated
View file

@ -53,12 +53,33 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.0.1"
@ -83,6 +104,140 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "cpuid-bool"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "crc32fast"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
]
[[package]]
name = "darling"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "dbus"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f597e08dfa79b593f23bbfc7840b23b2c5aa2e3a98d8e68b67b5b9ff800dc0db"
dependencies = [
"futures-channel",
"futures-util",
"libc",
"libdbus-sys",
]
[[package]]
name = "dbus-crossroads"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a816e8ae3382c7b1bccfa6f2778346ee5b13f80e0eccf80cf8f2912af73995a"
dependencies = [
"dbus",
]
[[package]]
name = "dbus-tokio"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b4083ad3ad374032aaacf18c4cce2c65c3ba5d4e576a037339a8b6cd0b4509c"
dependencies = [
"dbus",
"libc",
"tokio",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
dependencies = [
"serde",
]
[[package]]
name = "env_logger"
version = "0.8.3"
@ -96,6 +251,126 @@ dependencies = [
"termcolor",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "futures-channel"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
[[package]]
name = "futures-io"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
[[package]]
name = "futures-macro"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23"
[[package]]
name = "futures-task"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
[[package]]
name = "futures-util"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
dependencies = [
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"proc-macro-hack",
"proc-macro-nested",
"slab",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]]
name = "hermit-abi"
version = "0.1.18"
@ -105,12 +380,55 @@ dependencies = [
"libc",
]
[[package]]
name = "http"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "httparse"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "input_buffer"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413"
dependencies = [
"bytes",
]
[[package]]
name = "instant"
version = "0.1.9"
@ -120,12 +438,33 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]]
name = "libdbus-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0"
dependencies = [
"pkg-config",
]
[[package]]
name = "libudev"
version = "0.2.0"
@ -182,12 +521,27 @@ dependencies = [
"libc",
]
[[package]]
name = "matches"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "memoffset"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
dependencies = [
"autocfg",
]
[[package]]
name = "mio"
version = "0.7.11"
@ -232,6 +586,25 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
@ -242,12 +615,39 @@ dependencies = [
"libc",
]
[[package]]
name = "obws"
version = "0.7.0"
dependencies = [
"base64",
"bitflags",
"chrono",
"either",
"futures-util",
"log",
"rgb",
"semver",
"serde",
"serde_json",
"serde_with",
"sha2",
"thiserror",
"tokio",
"tokio-tungstenite",
]
[[package]]
name = "once_cell"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "parking_lot"
version = "0.11.1"
@ -273,18 +673,77 @@ dependencies = [
"winapi",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pin-project"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
[[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.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro-nested"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
[[package]]
name = "proc-macro2"
version = "1.0.26"
@ -303,6 +762,46 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.2.6"
@ -329,12 +828,103 @@ version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]]
name = "rgb"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fddb3b23626145d1776addfc307e1a1851f60ef6ca64f376bcb889697144cf0"
[[package]]
name = "rustversion"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd"
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser",
"serde",
]
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]]
name = "serde"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_with"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b0b98f61935da47683bf5c46b965ce1642ef1db78860b8a1defb68bf1b5b43"
dependencies = [
"rustversion",
"serde",
"serde_with_macros",
]
[[package]]
name = "serde_with_macros"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48b35457e9d855d3dc05ef32a73e0df1e2c0fd72c38796a4ee909160c8eeec2"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serialport"
version = "4.0.1"
@ -352,6 +942,32 @@ dependencies = [
"winapi",
]
[[package]]
name = "sha-1"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
"cpuid-bool",
"digest",
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
"cpuid-bool",
"digest",
"opaque-debug",
]
[[package]]
name = "signal-hook-registry"
version = "1.3.0"
@ -361,6 +977,28 @@ dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
[[package]]
name = "sled"
version = "0.34.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot",
]
[[package]]
name = "smallvec"
version = "1.6.1"
@ -372,12 +1010,25 @@ name = "streamdeck"
version = "0.1.0"
dependencies = [
"anyhow",
"dbus",
"dbus-crossroads",
"dbus-tokio",
"env_logger",
"log",
"obws",
"serde",
"serde_json",
"serialport",
"sled",
"tokio",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.69"
@ -398,6 +1049,41 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tinyvec"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.5.0"
@ -429,18 +1115,111 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-tungstenite"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e96bb520beab540ab664bd5a9cfeaa1fcd846fa68c830b42e2c8963071251d2"
dependencies = [
"futures-util",
"log",
"pin-project",
"tokio",
"tungstenite",
]
[[package]]
name = "tungstenite"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093"
dependencies = [
"base64",
"byteorder",
"bytes",
"http",
"httparse",
"input_buffer",
"log",
"rand",
"sha-1",
"thiserror",
"url",
"utf-8",
]
[[package]]
name = "typenum"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-bidi"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
dependencies = [
"matches",
]
[[package]]
name = "unicode-normalization"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "url"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -6,9 +6,20 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["ipc-dbus"]
ipc-dbus = ["dbus", "dbus-tokio", "dbus-crossroads"]
[dependencies]
anyhow = "1"
log = "0.4.0"
env_logger = "0.8.0"
obws = { version = "0.7.0", path = "../obws" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serialport = "4.0.1"
sled = "0.34.6"
tokio = { version = "1.5", features = ["full"] }
dbus = { version = "0.9", optional = true }
dbus-tokio = { version = "0.7", optional = true }
dbus-crossroads = { version = "0.3.0", optional = true }

346
src/dbus.rs Normal file
View file

@ -0,0 +1,346 @@
use crate::{
message::{Command, InputMessage, ManagerMessage, ObsMessage, Query},
store::Store,
};
use dbus::{channel::MatchingReceiver, message::MatchRule, nonblock::SyncConnection};
use dbus_crossroads::{Crossroads, MethodErr};
use std::sync::Arc;
use tokio::sync::{mpsc::Sender, Notify};
pub(crate) struct Dbus {
connection: Arc<SyncConnection>,
store: Store,
input: Sender<InputMessage>,
obs: Sender<ObsMessage>,
manager: Sender<ManagerMessage>,
}
struct DbusState {
store: Store,
input: Sender<InputMessage>,
obs: Sender<ObsMessage>,
manager: Sender<ManagerMessage>,
}
impl Dbus {
pub(crate) async fn build(
shutdown: Arc<Notify>,
store: Store,
input: Sender<InputMessage>,
obs: Sender<ObsMessage>,
manager: Sender<ManagerMessage>,
) -> Result<Self, anyhow::Error> {
let (resource, connection) =
tokio::task::spawn_blocking(move || dbus_tokio::connection::new_session_sync())
.await??;
tokio::spawn(async move {
resource.await;
shutdown.notify_one();
});
connection
.request_name("dog.asonix.git.asonix.Streamdeck", false, true, false)
.await?;
Ok(Dbus {
connection,
store,
input,
obs,
manager,
})
}
pub(crate) async fn run(self) {
let Dbus {
connection,
store,
input,
obs,
manager,
} = self;
let state = DbusState {
store,
input,
obs,
manager,
};
let mut cr = Crossroads::new();
cr.set_async_support(Some((
connection.clone(),
Box::new(|x| {
tokio::spawn(x);
}),
)));
let iface_token = cr.register("dog.asonix.git.asonix.Streamdeck", |b| {
b.method_with_cr_async("GetScenes", (), ("scenes",), |mut ctx, cr, ()| {
log::info!("GetScenes");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let obs = state.obs.clone();
async move {
let (tx, rx) = tokio::sync::oneshot::channel();
if obs
.send(ObsMessage::Query(Query::GetScenes(tx)))
.await
.is_ok()
{
if let Ok(scenes) = rx.await {
return ctx.reply(Ok((scenes,)));
}
}
ctx.reply(Err(MethodErr::failed("Failed to get scenes")))
}
});
b.method_with_cr_async("EnableDiscovery", (), (), |mut ctx, cr, ()| {
log::info!("EnableDiscovery");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let manager = state.manager.clone();
async move {
if manager.send(ManagerMessage::EnableDiscovery).await.is_ok() {
return ctx.reply(Ok(()));
}
ctx.reply(Err(MethodErr::failed("Failed to enable discovery")))
}
});
b.method_with_cr_async("DisableDiscovery", (), (), |mut ctx, cr, ()| {
log::info!("DisableDiscovery");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let manager = state.manager.clone();
async move {
if manager.send(ManagerMessage::DisableDiscovery).await.is_ok() {
return ctx.reply(Ok(()));
}
ctx.reply(Err(MethodErr::failed("Failed to disable discovery")))
}
});
b.method_with_cr_async("GetDecks", (), ("decks",), |mut ctx, cr, ()| {
log::info!("GetDecks");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let manager = state.manager.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
async move {
if manager.send(ManagerMessage::List(tx)).await.is_ok() {
if let Ok(decks) = rx.await {
return ctx.reply(Ok((decks
.into_iter()
.map(|d| (d.serial_number, d.product_name, d.port_name))
.collect::<Vec<_>>(),)));
}
}
ctx.reply(Err(MethodErr::failed("Failed to fetch decks")))
}
});
b.method_with_cr_async(
"Connect",
("host", "port"),
("state",),
|mut ctx, cr, (host, port): (String, u16)| {
log::info!("Connect");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let obs = state.obs.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
async move {
if obs.send(ObsMessage::Connect(host, port)).await.is_ok() {
if obs.send(ObsMessage::State(tx)).await.is_ok() {
if let Ok(state) = rx.await {
return ctx.reply(Ok((state.to_string(),)));
}
}
}
ctx.reply(Err(MethodErr::failed("Failed to start connection")))
}
},
);
b.method_with_cr_async("Disconnect", (), ("state",), |mut ctx, cr, ()| {
log::info!("Disconnect");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let obs = state.obs.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
async move {
if obs.send(ObsMessage::Disconnect).await.is_ok() {
if obs.send(ObsMessage::State(tx)).await.is_ok() {
if let Ok(state) = rx.await {
return ctx.reply(Ok((state.to_string(),)));
}
}
}
ctx.reply(Err(MethodErr::failed("Failed to start disconnection")))
}
});
b.method_with_cr_async("GetState", (), ("state",), |mut ctx, cr, ()| {
log::info!("GetState");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let obs = state.obs.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
async move {
if obs.send(ObsMessage::State(tx)).await.is_ok() {
if let Ok(state) = rx.await {
return ctx.reply(Ok((state.to_string(),)));
}
}
ctx.reply(Err(MethodErr::failed("Failed to get state")))
}
});
b.method_with_cr_async(
"Login",
("password",),
("state",),
|mut ctx, cr, (password,): (String,)| {
log::info!("Login");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let obs = state.obs.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
async move {
if obs.send(ObsMessage::Authenticate(password)).await.is_ok() {
if obs.send(ObsMessage::State(tx)).await.is_ok() {
if let Ok(state) = rx.await {
return ctx.reply(Ok((state.to_string(),)));
}
}
}
ctx.reply(Err(MethodErr::failed("Failed to start login")))
}
},
);
b.method_with_cr_async(
"GetCommands",
("serial_number",),
("commands",),
|mut ctx, cr, (serial_number,): (String,)| {
log::info!("GetCommands");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let store = state.store.clone();
async move {
if let Ok(vec) = store.get_commands(&&serial_number).await {
let vec: Vec<_> = vec
.into_iter()
.filter_map(|(key, cmd)| {
Some((key, serde_json::to_string(&cmd).ok()?))
})
.collect();
return ctx.reply(Ok((vec,)));
}
ctx.reply(Err(MethodErr::failed("Failed to get commands")))
}
},
);
b.method_with_cr_async("ReadInput", (), ("input",), |mut ctx, cr, ()| {
log::info!("ReadInput");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let input = state.input.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
async move {
if input.send(InputMessage::ReadInput(tx)).await.is_ok() {
if let Ok((serial_number, key)) = rx.await {
return ctx.reply(Ok((vec![(key, serial_number)],)));
}
}
ctx.reply(Err(MethodErr::failed("Failed to fetch key")))
}
});
b.method_with_cr_async(
"SetInput",
("serial_number", "key", "command"),
(),
|mut ctx, cr, (serial_number, key, command): (String, u8, String)| {
log::info!("SetInput");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let store = state.store.clone();
async move {
if let Ok(command) = serde_json::from_str::<Command>(&command) {
if store.store(&serial_number, key, &command).await.is_ok() {
return ctx.reply(Ok(()));
}
}
ctx.reply(Err(MethodErr::failed("Failed to set mapping")))
}
},
);
b.method_with_cr_async(
"UnsetInput",
("serial_number", "key"),
(),
|mut ctx, cr, (serial_number, key): (String, u8)| {
log::info!("UnsetInput");
let state: &mut DbusState = cr.data_mut(ctx.path()).unwrap();
let store = state.store.clone();
async move {
if store.unset(&serial_number, key).await.is_ok() {
return ctx.reply(Ok(()));
}
ctx.reply(Err(MethodErr::failed("Failed to set mapping")))
}
},
);
});
cr.insert("/dog/asonix/git/asonix/Streamdeck", &[iface_token], state);
connection.start_receive(
MatchRule::new_method_call(),
Box::new(move |msg, conn| {
cr.handle_message(msg, conn).unwrap();
true
}),
);
}
}

View file

@ -1,95 +1,67 @@
use crate::{ManagerMessage, Port};
use crate::{
message::{DeckConfig, InputMessage, ManagerMessage},
port::Port,
};
use std::{future::Future, pin::Pin};
use tokio::{sync::mpsc::Sender, time::Duration};
pub(crate) async fn task(manager: Sender<ManagerMessage>, name: String, port: Port) {
let _ = do_task(port).await;
log::info!("{} disconnected", name);
while manager
.send(ManagerMessage::Closed(name.clone()))
.await
.is_err()
{
if manager.is_closed() {
return;
}
tokio::time::sleep(Duration::from_secs(5)).await;
}
#[derive(Debug)]
pub(crate) struct Deck {
pub(crate) manager: Sender<ManagerMessage>,
pub(crate) input: Sender<InputMessage>,
pub(crate) port: Port,
pub(crate) config: DeckConfig,
}
async fn do_task(port: Port) -> Result<(), anyhow::Error> {
let mut read_port = port.try_clone()?.lines();
let mut write_port = port;
impl Deck {
pub(crate) async fn run(self) {
let res = self.read_task();
if let Ok(fut) = res {
let _ = tokio::spawn(fut).await;
}
let (read_tx, mut read_rx) = tokio::sync::mpsc::channel(16);
let (write_tx, mut write_rx) = tokio::sync::mpsc::channel(16);
log::info!("{} disconnected", self.config.product_name);
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
while self
.manager
.send(ManagerMessage::Closed(self.config.port_name.clone()))
.await
.is_err()
{
if self.manager.is_closed() {
return;
}
let read_handle = tokio::spawn(async move {
loop {
log::trace!("read loop");
match read_port.read_line().await {
Ok(line) => {
let _ = read_tx.send(line).await;
}
Err(e) => {
log::debug!("Error reading: {}", e);
if let Ok(e) = e.downcast::<std::io::Error>() {
if matches!(e.kind(), std::io::ErrorKind::BrokenPipe) {
break;
tokio::time::sleep(Duration::from_secs(5)).await;
}
}
fn read_task(
&self,
) -> Result<Pin<Box<dyn Future<Output = ()> + Send + 'static>>, anyhow::Error> {
let mut read_port = self.port.try_clone()?.bytes();
let input = self.input.clone();
let serial_number = self.config.serial_number.clone();
Ok(Box::pin(async move {
loop {
log::trace!("read loop");
match read_port.read_byte().await {
Ok(byte) => {
let _ = input
.send(InputMessage::Press(serial_number.clone(), byte))
.await;
}
Err(e) => {
log::debug!("Error reading: {}", e);
if let Ok(e) = e.downcast::<std::io::Error>() {
if matches!(e.kind(), std::io::ErrorKind::BrokenPipe) {
break;
}
}
}
}
}
}
let _ = shutdown_tx.send(());
});
let write_handle = tokio::spawn(async move {
while let Some(bytes) = write_rx.recv().await {
log::trace!("write loop");
if let Err(e) = write_port.write(bytes).await {
if let Ok(e) = e.downcast::<std::io::Error>() {
if matches!(e.kind(), std::io::ErrorKind::BrokenPipe) {
break;
}
}
if let Err(e) = write_port.bytes_to_write().await {
if let Ok(e) = e.downcast::<serialport::Error>() {
if matches!(e.kind, serialport::ErrorKind::NoDevice) {
break;
}
}
}
}
}
});
tokio::pin!(shutdown_rx);
loop {
log::trace!("loop");
tokio::select! {
Some(line) = read_rx.recv() => {
log::info!("{}", line.trim());
}
_ = &mut shutdown_rx => {
break;
}
}
}))
}
log::debug!("Shutting down");
read_handle.abort();
write_handle.abort();
let _ = read_handle.await;
let _ = write_handle.await;
Ok(())
}

29
src/input.rs Normal file
View file

@ -0,0 +1,29 @@
use crate::{
message::{InputMessage, ObsMessage},
store::Store,
};
use tokio::sync::{
mpsc::{Receiver, Sender},
oneshot,
};
pub(crate) async fn task(store: Store, mut rx: Receiver<InputMessage>, obs_tx: Sender<ObsMessage>) {
let mut sender_opt: Option<oneshot::Sender<(String, u8)>> = None;
while let Some(msg) = rx.recv().await {
match msg {
InputMessage::Press(serial_number, key) => {
if let Some(sender) = sender_opt.take() {
let _ = sender.send((serial_number, key));
} else {
if let Ok(Some(command)) = store.pressed(&serial_number, key).await {
let _ = obs_tx.send(ObsMessage::Command(command)).await;
}
}
}
InputMessage::ReadInput(sender) => {
sender_opt = Some(sender);
}
}
}
}

View file

@ -1,147 +1,17 @@
use serialport::{SerialPortType, UsbPortInfo};
use std::{
collections::{HashMap, HashSet},
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tokio::{
sync::mpsc::{Receiver, Sender},
task::JoinHandle,
time::Duration,
};
use std::sync::Arc;
use tokio::sync::Notify;
#[cfg(feature = "ipc-dbus")]
mod dbus;
mod deck;
mod input;
mod manager;
mod message;
mod obs;
mod port;
mod store;
use port::Port;
#[derive(Debug)]
enum ManagerMessage {
Tick,
Found(HashSet<String>),
Opened(String, Port),
Closed(String),
}
struct Manager {
tx: Sender<ManagerMessage>,
ports: HashMap<String, JoinHandle<()>>,
opening: HashMap<String, JoinHandle<()>>,
finding: Option<JoinHandle<()>>,
}
impl Manager {
fn new(tx: Sender<ManagerMessage>) -> Self {
Manager {
tx,
finding: None,
ports: HashMap::default(),
opening: HashMap::default(),
}
}
fn turn<'a>(&'a mut self, msg: ManagerMessage) -> Turn<'a> {
Turn(self, Some(msg))
}
fn do_turn(&mut self, msg: ManagerMessage, cx: &mut Context<'_>) {
log::debug!("msg: {:?}", msg);
log::debug!("state: {:?}", self);
match msg {
ManagerMessage::Tick => {
if let Some(mut handle) = self.finding.take() {
if matches!(Pin::new(&mut handle).poll(cx), Poll::Pending) {
self.finding = Some(handle);
return;
}
}
let handle = tokio::spawn(find_task(self.tx.clone()));
self.finding = Some(handle);
}
ManagerMessage::Found(found_decks) => {
let known = self.ports.keys().cloned().collect::<HashSet<_>>();
for deck in found_decks.difference(&known) {
log::info!("New deck: {}", deck);
if let Some(mut handle) = self.opening.remove(deck) {
if matches!(Pin::new(&mut handle).poll(cx), Poll::Pending) {
self.opening.insert(deck.clone(), handle);
continue;
}
}
let handle = tokio::spawn(open_task(deck.clone(), self.tx.clone()));
self.opening.insert(deck.clone(), handle);
}
for deck in known.difference(&found_decks) {
log::info!("Removed deck: {}", deck);
if let Some(handle) = self.ports.remove(deck) {
handle.abort();
}
}
}
ManagerMessage::Opened(deck, port) => {
let handle = tokio::spawn(crate::deck::task(self.tx.clone(), deck.clone(), port));
self.ports.insert(deck, handle);
}
ManagerMessage::Closed(deck) => {
if let Some(handle) = self.ports.remove(&deck) {
handle.abort();
}
}
}
}
}
async fn state_task(tx: Sender<ManagerMessage>, mut rx: Receiver<ManagerMessage>) {
let mut state = Manager::new(tx);
while let Some(msg) = rx.recv().await {
state.turn(msg).await;
}
}
struct Turn<'a>(&'a mut Manager, Option<ManagerMessage>);
impl<'a> Future for Turn<'a> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let msg = self.1.take().unwrap();
self.0.do_turn(msg, cx);
Poll::Ready(())
}
}
async fn tick_task(tx: Sender<ManagerMessage>) {
let mut interval = tokio::time::interval(Duration::from_secs(5));
loop {
let _ = tx.send(ManagerMessage::Tick).await;
interval.tick().await;
}
}
async fn find_task(tx: Sender<ManagerMessage>) {
match find_streamdecks().await {
Ok(streamdecks) => {
let _ = tx.send(ManagerMessage::Found(streamdecks)).await;
}
Err(e) => log::error!("Error finding streamdecks: {}", e),
}
}
async fn open_task(name: String, tx: Sender<ManagerMessage>) {
match Port::open(name.clone()).await {
Ok(port) => {
let _ = tx.send(ManagerMessage::Opened(name, port)).await;
}
Err(e) => log::error!("Error opening streamdeck {}: {}", name, e),
}
}
use store::Store;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
@ -150,13 +20,43 @@ async fn main() -> Result<(), anyhow::Error> {
}
env_logger::init();
let (tx, rx) = tokio::sync::mpsc::channel(16);
let (manager_tx, manager_rx) = tokio::sync::mpsc::channel(16);
let (input_tx, input_rx) = tokio::sync::mpsc::channel(16);
let (obs_tx, obs_rx) = tokio::sync::mpsc::channel(16);
let db = sled::Config::new().temporary(true).open()?;
let store = Store::build(db).await?;
let shutdown = Arc::new(Notify::new());
let mut handles = vec![];
handles.push(tokio::spawn(tick_task(tx.clone())));
handles.push(tokio::spawn(state_task(tx, rx)));
#[cfg(feature = "ipc-dbus")]
{
dbus::Dbus::build(
shutdown.clone(),
store.clone(),
input_tx.clone(),
obs_tx.clone(),
manager_tx.clone(),
)
.await?
.run()
.await;
}
tokio::signal::ctrl_c().await?;
handles.push(tokio::spawn(input::task(store, input_rx, obs_tx)));
handles.push(tokio::spawn(obs::task(obs_rx)));
handles.push(tokio::spawn(manager::task(
input_tx, manager_tx, manager_rx,
)));
tokio::select! {
_ = shutdown.notified() => {},
_ = tokio::signal::ctrl_c() => {},
};
log::info!("Application closing");
for hdnl in &handles {
hdnl.abort();
@ -168,34 +68,3 @@ async fn main() -> Result<(), anyhow::Error> {
Ok(())
}
async fn find_streamdecks() -> Result<HashSet<String>, anyhow::Error> {
tokio::task::spawn_blocking(|| {
Ok(serialport::available_ports()?
.into_iter()
.filter_map(|port| {
if let SerialPortType::UsbPort(info) = port.port_type {
if is_valid_port(info) {
return Some(port.port_name);
}
}
None
})
.collect())
})
.await?
}
fn is_valid_port(_: UsbPortInfo) -> bool {
// TODO: Check for BasedOtt pid/vid
true
}
impl std::fmt::Debug for Manager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ports = self.ports.keys().map(|s| s.as_str()).collect::<Vec<_>>();
f.debug_struct("Manager").field("ports", &ports).finish()
}
}

218
src/manager.rs Normal file
View file

@ -0,0 +1,218 @@
use crate::{
deck::Deck,
message::{DeckConfig, InputMessage, ManagerMessage},
port::Port,
};
use serialport::{SerialPortType, UsbPortInfo};
use std::{
collections::{HashMap, HashSet},
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tokio::{
sync::mpsc::{Receiver, Sender},
task::JoinHandle,
time::Duration,
};
struct Manager {
input_tx: Sender<InputMessage>,
tx: Sender<ManagerMessage>,
decks: HashMap<String, DeckConfig>,
ports: HashMap<String, JoinHandle<()>>,
opening: HashMap<String, JoinHandle<()>>,
finding: Option<JoinHandle<()>>,
discovery: Option<JoinHandle<()>>,
}
impl Manager {
fn new(input_tx: Sender<InputMessage>, tx: Sender<ManagerMessage>) -> Self {
let discovery = tokio::spawn(tick_task(tx.clone()));
Manager {
input_tx,
tx,
decks: HashMap::default(),
ports: HashMap::default(),
opening: HashMap::default(),
finding: None,
discovery: Some(discovery),
}
}
fn turn<'a>(&'a mut self, msg: ManagerMessage) -> Turn<'a> {
Turn(self, Some(msg))
}
fn do_turn(&mut self, msg: ManagerMessage, cx: &mut Context<'_>) {
log::debug!("msg: {:?}", msg);
log::debug!("state: {:?}", self);
match msg {
ManagerMessage::EnableDiscovery => {
if let Some(mut handle) = self.discovery.take() {
if matches!(Pin::new(&mut handle).poll(cx), Poll::Pending) {
self.discovery = Some(handle);
return;
}
}
let handle = tokio::spawn(tick_task(self.tx.clone()));
self.discovery = Some(handle);
}
ManagerMessage::DisableDiscovery => {
if let Some(handle) = self.discovery.take() {
handle.abort();
}
}
ManagerMessage::Tick => {
if let Some(mut handle) = self.finding.take() {
if matches!(Pin::new(&mut handle).poll(cx), Poll::Pending) {
self.finding = Some(handle);
return;
}
}
let handle = tokio::spawn(find_task(self.tx.clone()));
self.finding = Some(handle);
}
ManagerMessage::List(sender) => {
let _ = sender.send(self.decks.values().cloned().collect());
}
ManagerMessage::Found(mut found_decks) => {
let known = self.ports.keys().cloned().collect::<HashSet<_>>();
let found = found_decks.keys().cloned().collect::<HashSet<_>>();
for port_name in found.difference(&known) {
if let Some(mut handle) = self.opening.remove(port_name) {
if matches!(Pin::new(&mut handle).poll(cx), Poll::Pending) {
self.opening.insert(port_name.clone(), handle);
continue;
}
}
if let Some(config) = found_decks.remove(port_name) {
log::info!("New deck: {}", config.port_name);
let handle = tokio::spawn(open_task(config, self.tx.clone()));
self.opening.insert(port_name.clone(), handle);
}
}
for port_name in known.difference(&found) {
log::info!("Removed deck: {}", port_name);
if let Some(handle) = self.ports.remove(port_name) {
handle.abort();
}
self.decks.remove(port_name);
}
}
ManagerMessage::Opened(deck, port) => {
let handle = tokio::spawn(
Deck {
manager: self.tx.clone(),
input: self.input_tx.clone(),
port,
config: deck.clone(),
}
.run(),
);
self.ports.insert(deck.port_name.clone(), handle);
self.decks.insert(deck.port_name.clone(), deck);
}
ManagerMessage::Closed(port_name) => {
if let Some(handle) = self.ports.remove(&port_name) {
handle.abort();
}
self.decks.remove(&port_name);
}
}
}
}
pub(crate) async fn task(
input_tx: Sender<InputMessage>,
tx: Sender<ManagerMessage>,
mut rx: Receiver<ManagerMessage>,
) {
let mut state = Manager::new(input_tx, tx);
while let Some(msg) = rx.recv().await {
state.turn(msg).await;
}
}
struct Turn<'a>(&'a mut Manager, Option<ManagerMessage>);
impl<'a> Future for Turn<'a> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let msg = self.1.take().unwrap();
self.0.do_turn(msg, cx);
Poll::Ready(())
}
}
async fn tick_task(tx: Sender<ManagerMessage>) {
let mut interval = tokio::time::interval(Duration::from_secs(5));
loop {
let _ = tx.send(ManagerMessage::Tick).await;
interval.tick().await;
}
}
async fn find_task(tx: Sender<ManagerMessage>) {
match find_streamdecks().await {
Ok(streamdecks) => {
let _ = tx.send(ManagerMessage::Found(streamdecks)).await;
}
Err(e) => log::error!("Error finding streamdecks: {}", e),
}
}
async fn open_task(deck: DeckConfig, tx: Sender<ManagerMessage>) {
match Port::open(deck.port_name.clone()).await {
Ok(port) => {
let _ = tx.send(ManagerMessage::Opened(deck, port)).await;
}
Err(e) => log::error!("Error opening streamdeck {}: {}", deck.port_name, e),
}
}
async fn find_streamdecks() -> Result<HashMap<String, DeckConfig>, anyhow::Error> {
tokio::task::spawn_blocking(|| {
Ok(serialport::available_ports()?
.into_iter()
.filter_map(|port| {
if let SerialPortType::UsbPort(info) = port.port_type {
if is_valid_port(&info) {
return Some((
port.port_name.clone(),
DeckConfig {
port_name: port.port_name,
product_name: info.product?,
serial_number: info.serial_number?,
},
));
}
}
None
})
.collect())
})
.await?
}
fn is_valid_port(_: &UsbPortInfo) -> bool {
// TODO: Check for BasedOtt pid/vid
true
}
impl std::fmt::Debug for Manager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ports = self.ports.keys().map(|s| s.as_str()).collect::<Vec<_>>();
f.debug_struct("Manager").field("ports", &ports).finish()
}
}

64
src/message.rs Normal file
View file

@ -0,0 +1,64 @@
use crate::port::Port;
use std::collections::HashMap;
use tokio::sync::oneshot::Sender;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
pub(crate) enum Command {
SwitchScene { name: String },
}
#[derive(Debug)]
pub(crate) enum Query {
GetScenes(Sender<Vec<String>>),
}
#[derive(Debug)]
pub(crate) enum State {
Disconnected,
Unauthenticated,
Connected,
}
#[derive(Debug)]
pub(crate) enum ObsMessage {
Query(Query),
Command(Command),
Connect(String, u16),
Authenticate(String),
State(Sender<State>),
Disconnect,
}
pub(crate) enum InputMessage {
Press(String, u8),
ReadInput(Sender<(String, u8)>),
}
#[derive(Clone, Debug)]
pub(crate) struct DeckConfig {
pub(crate) port_name: String,
pub(crate) product_name: String,
pub(crate) serial_number: String,
}
#[derive(Debug)]
pub(crate) enum ManagerMessage {
EnableDiscovery,
DisableDiscovery,
Tick,
List(Sender<Vec<DeckConfig>>),
Found(HashMap<String, DeckConfig>),
Opened(DeckConfig, Port),
Closed(String),
}
impl std::fmt::Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
State::Disconnected => write!(f, "Disconnected"),
State::Unauthenticated => write!(f, "Unauthenticated"),
State::Connected => write!(f, "Connected"),
}
}
}

197
src/obs.rs Normal file
View file

@ -0,0 +1,197 @@
use crate::message::{Command, ObsMessage, Query, State};
use obws::Client;
use tokio::sync::mpsc::Receiver;
pub(crate) struct Obs {
inner: Inner,
}
enum Inner {
Disconnected(Disconnected),
Unauthenticated(Unauthenticated),
Connected(Connected),
}
#[derive(Debug)]
struct Disconnected;
struct Unauthenticated {
client: Option<Client>,
challenge: Option<String>,
salt: Option<String>,
}
struct Connected {
client: Option<Client>,
}
pub(crate) async fn task(rx: Receiver<ObsMessage>) {
Obs::new().run(rx).await
}
impl Obs {
fn new() -> Self {
Obs {
inner: Inner::Disconnected(Disconnected),
}
}
async fn run(mut self, mut rx: Receiver<ObsMessage>) {
while let Some(msg) = rx.recv().await {
self.handle(msg).await
}
}
async fn handle(&mut self, message: ObsMessage) {
match message {
ObsMessage::State(sender) => {
let state = match &self.inner {
Inner::Disconnected(_) => State::Disconnected,
Inner::Unauthenticated(_) => State::Unauthenticated,
Inner::Connected(_) => State::Connected,
};
let _ = sender.send(state);
}
message => {
self.inner.handle(message).await;
}
}
}
}
impl Inner {
async fn handle(&mut self, message: ObsMessage) {
let new_state = match self {
Inner::Disconnected(disconnected) => disconnected.handle(message).await,
Inner::Unauthenticated(unauthenticated) => unauthenticated.handle(message).await,
Inner::Connected(connected) => connected.handle(message).await,
};
if let Some(state) = new_state {
let _ = std::mem::replace(self, state);
}
}
}
impl Disconnected {
async fn handle(&mut self, message: ObsMessage) -> Option<Inner> {
match message {
ObsMessage::Connect(host, port) => self
.connect(host, port)
.await
.map_err(|e| log::error!("{}", e))
.ok(),
_ => None,
}
}
async fn connect(&mut self, host: String, port: u16) -> Result<Inner, anyhow::Error> {
log::info!("Connecting to {}:{}", host, port);
let client = Client::connect(host, port).await?;
let auth_required = client.general().get_auth_required().await?;
if auth_required.auth_required {
return Ok(Inner::Unauthenticated(Unauthenticated {
client: Some(client),
challenge: auth_required.challenge,
salt: auth_required.salt,
}));
}
Ok(Inner::Connected(Connected {
client: Some(client),
}))
}
}
impl Unauthenticated {
async fn handle(&mut self, message: ObsMessage) -> Option<Inner> {
match message {
ObsMessage::Authenticate(password) => self.authenticate(password).await.ok(),
_ => None,
}
}
async fn authenticate(&mut self, password: String) -> Result<Inner, anyhow::Error> {
if let Some(client) = self.client.as_ref() {
if let Err(e) = client.login(Some(password)).await {
if client.general().get_version().await.is_ok() {
return Err(e.into());
} else {
return Ok(Inner::Disconnected(Disconnected));
}
}
}
Ok(Inner::Connected(Connected {
client: self.client.take(),
}))
}
}
impl Connected {
async fn handle(&mut self, message: ObsMessage) -> Option<Inner> {
match message {
ObsMessage::Command(command) => {
let res = self.command(command).await;
self.check_err(res).await
}
ObsMessage::Query(query) => {
let res = self.query(query).await;
self.check_err(res).await
}
ObsMessage::Disconnect => {
let _ = self.disconnect().await;
Some(Inner::Disconnected(Disconnected))
}
_ => None,
}
}
async fn check_err(&mut self, res: Result<(), anyhow::Error>) -> Option<Inner> {
match res {
Ok(()) => None,
Err(_) => {
if let Some(client) = self.client.as_ref() {
if client.general().get_version().await.is_ok() {
return None;
}
}
Some(Inner::Disconnected(Disconnected))
}
}
}
async fn command(&self, command: Command) -> Result<(), anyhow::Error> {
match command {
Command::SwitchScene { name } => {
if let Some(client) = self.client.as_ref() {
client.scenes().set_current_scene(&name).await?;
}
Ok(())
}
}
}
async fn query(&self, query: Query) -> Result<(), anyhow::Error> {
match query {
Query::GetScenes(sender) => {
if let Some(client) = self.client.as_ref() {
let scene_list = client.scenes().get_scene_list().await?;
let _ = sender.send(scene_list.scenes.into_iter().map(|s| s.name).collect());
}
Ok(())
}
}
}
async fn disconnect(&mut self) -> Result<(), anyhow::Error> {
if let Some(client) = self.client.as_mut() {
client.disconnect().await;
}
Ok(())
}
}

View file

@ -6,6 +6,7 @@ use std::{
pub(crate) struct Port(Inner<Box<dyn SerialPort>>);
pub(crate) struct LinesReader(Inner<BufReader<Box<dyn SerialPort>>>);
pub(crate) struct BytesReader(Inner<Box<dyn SerialPort>>);
struct Inner<I>(Option<I>);
@ -74,6 +75,10 @@ impl Port {
LinesReader(Inner(self.0.take().map(BufReader::new)))
}
pub(crate) fn bytes(mut self) -> BytesReader {
BytesReader(Inner(self.0.take()))
}
pub(crate) async fn bytes_to_write(&mut self) -> Result<u32, anyhow::Error> {
self.0
.with_inner(move |port| {
@ -85,6 +90,18 @@ impl Port {
}
}
impl BytesReader {
pub(crate) async fn read_byte(&mut self) -> Result<u8, anyhow::Error> {
self.0
.with_inner(move |mut port| {
let mut bytes = [0u8; 1];
let res = port.read_exact(&mut bytes);
(port, res.map(|_| bytes[0]))
})
.await
}
}
impl LinesReader {
pub(crate) async fn read_line(&mut self) -> Result<String, anyhow::Error> {
self.0

111
src/store.rs Normal file
View file

@ -0,0 +1,111 @@
use crate::message::Command;
use sled::{Db, IVec, Tree};
#[derive(Clone, Debug)]
pub(crate) struct Store {
commands: Tree,
db: Db,
}
impl Store {
pub(crate) async fn build(db: Db) -> Result<Self, anyhow::Error> {
let db2 = db.clone();
Ok(Store {
commands: tokio::task::spawn_blocking(move || {
db2.open_tree("dog.asonix.git.asonix.streamdeck/commands")
})
.await??,
db,
})
}
pub(crate) async fn unset(&self, deck: &str, input: u8) -> Result<(), anyhow::Error> {
let key = self.cmd_key(deck, input);
let commands = self.commands.clone();
tokio::task::spawn_blocking(move || commands.remove(key)).await??;
Ok(())
}
pub(crate) async fn store(
&self,
deck: &str,
input: u8,
command: &Command,
) -> Result<(), anyhow::Error> {
let key = self.cmd_key(deck, input);
let value = self.cmd_value(command)?;
let commands = self.commands.clone();
tokio::task::spawn_blocking(move || commands.insert(key, value)).await??;
Ok(())
}
pub(crate) async fn pressed(
&self,
deck: &str,
input: u8,
) -> Result<Option<Command>, anyhow::Error> {
let key = self.cmd_key(deck, input);
let commands = self.commands.clone();
let opt = tokio::task::spawn_blocking(move || commands.get(key)).await??;
if let Some(ivec) = opt {
return Ok(Some(serde_json::from_slice(&ivec)?));
}
Ok(None)
}
pub(crate) async fn get_commands(
&self,
deck: &str,
) -> Result<Vec<(u8, Command)>, anyhow::Error> {
let prefix = self.cmd_prefix(deck);
let commands = self.commands.clone();
let vec = tokio::task::spawn_blocking(move || {
let prefix_len = prefix.len();
commands
.scan_prefix(prefix)
.filter_map(|res| {
let (key, value) = res.ok()?;
if key.len() != prefix_len + 1 {
return None;
}
let key = key[prefix_len];
let command = serde_json::from_slice(&value).ok()?;
Some((key, command))
})
.collect()
})
.await?;
Ok(vec)
}
fn cmd_prefix(&self, deck: &str) -> IVec {
let mut prefix = deck.as_bytes().to_vec();
prefix.push(b'/');
IVec::from(prefix)
}
fn cmd_key(&self, deck: &str, input: u8) -> IVec {
let mut key = deck.as_bytes().to_vec();
key.push(b'/');
key.push(input);
IVec::from(key)
}
fn cmd_value(&self, command: &Command) -> Result<IVec, anyhow::Error> {
Ok(IVec::from(serde_json::to_vec(&command)?))
}
}