diff --git a/.vscode/settings.json b/.vscode/settings.json index c386cdc..7e34b09 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "rust.target": "i686-pc-windows-msvc" + "rust.target": "i686-pc-windows-msvc", + "[html]": { + "editor.formatOnSave": false + } } \ No newline at end of file diff --git a/Build-Plugin.ps1 b/Build-Plugin.ps1 index f545a0e..c0c5fd9 100644 --- a/Build-Plugin.ps1 +++ b/Build-Plugin.ps1 @@ -1,13 +1,14 @@ $dist = New-Item -ItemType 'Directory' -Path (Join-Path $PSScriptRoot 'dist/io.github.mdonoughe.sbzdeck.sdPlugin') -Force -cargo build --release --manifest-path (Join-Path $PSScriptRoot 'Cargo.toml' | Resolve-Path).ProviderPath -Join-Path $PSScriptRoot 'target\i686-pc-windows-msvc\release\sbzdeck.exe' | Copy-Item -Destination $dist.PSPath - -# temporary -Join-Path $PSScriptRoot 'examples\sbzdeck.json' | Copy-Item -Destination $dist.PSPath +Push-Location $PSScriptRoot +cargo build --release -p plugin +Get-Item target/i686-pc-windows-msvc/release/plugin.exe | Copy-Item -Destination (Join-Path $dist.PSPath 'sbzdeck.exe') +cargo web deploy --release -o dist/io.github.mdonoughe.sbzdeck.sdPlugin/inspector -p inspector +Pop-Location $manifest = Join-Path $PSScriptRoot 'manifest.json' | Get-Item | Get-Content | ConvertFrom-Json $manifest.CodePathWin = 'sbzdeck.exe' +$manifest.PropertyInspectorPath = 'inspector/index.html' $manifest | ConvertTo-Json -Depth 100 | Set-Content -LiteralPath ($dist | Join-Path -ChildPath 'manifest.json') $images = @( diff --git a/Cargo.lock b/Cargo.lock index 8adce0a..2d466f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,11 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "anymap" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "arrayvec" version = "0.4.10" @@ -64,6 +69,11 @@ dependencies = [ "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base-x" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "base64" version = "0.10.0" @@ -72,6 +82,15 @@ dependencies = [ "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bincode" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bitflags" version = "1.0.4" @@ -157,6 +176,16 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crc32fast" version = "1.1.2" @@ -234,6 +263,11 @@ dependencies = [ "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "dtoa" version = "0.4.3" @@ -264,6 +298,11 @@ name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -300,6 +339,16 @@ dependencies = [ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "http" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "httparse" version = "1.3.3" @@ -319,6 +368,9 @@ dependencies = [ name = "indexmap" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "input_buffer" @@ -328,6 +380,19 @@ dependencies = [ "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "inspector" +version = "0.1.0" +dependencies = [ + "common 0.1.0", + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", + "stdweb 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "yew 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "iovec" version = "0.1.2" @@ -551,6 +616,22 @@ name = "percent-encoding" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "plugin" +version = "0.1.0" +dependencies = [ + "common 0.1.0", + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "sbz-switch 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "streamdeck-rs 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro2" version = "0.4.24" @@ -742,22 +823,6 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "sbzdeck" -version = "0.1.0" -dependencies = [ - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "sbz-switch 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", - "slog 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "sloggers 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "streamdeck-rs 0.1.0 (git+https://github.com/mdonoughe/streamdeck-rs?branch=sdk2)", - "tokio 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "scopeguard" version = "0.3.3" @@ -823,6 +888,11 @@ dependencies = [ "opaque-debug 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "slab" version = "0.4.2" @@ -917,16 +987,63 @@ name = "stable_deref_trait" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "stdweb" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "discard 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", + "stdweb-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "stdweb-internal-macros 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "stdweb-internal-runtime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base-x 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", + "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "streamdeck-rs" -version = "0.1.0" -source = "git+https://github.com/mdonoughe/streamdeck-rs?branch=sdk2#f305467bd9b4321a2c2e7a2e491a9a44a1e847a3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-dns-unofficial 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tungstenite 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1352,16 +1469,36 @@ dependencies = [ "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "yew" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "anymap 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "stdweb 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum anymap 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727" "checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5" "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum base-x 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d55aa264e822dbafa12db4d54767aff17c6ba55ea2d8559b3e17392c7d000e5d" "checksum base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "621fc7ecb8008f86d7fb9b95356cd692ce9514b80a86d85b397f32a22da7b9e2" +"checksum bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f2fb9e29e72fd6bc12071533d5dc7664cb01480c59406f656d7ac25c7bd8ff7" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49665c62e0e700857531fa5d3763e91b539ff1abeebd56808d378b495870d60d" "checksum block-padding 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc4358306e344bf9775d0197fd00d2603e5afb0771bb353538630f022068ea3" @@ -1381,15 +1518,18 @@ dependencies = [ "checksum crossbeam-epoch 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f10a4f8f409aaac4b16a5474fb233624238fcdeefb9ba50d5ea059aab63ba31c" "checksum crossbeam-utils 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "41ee4864f4797060e52044376f7d107429ce1fb43460021b126424b7180ee21a" "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" +"checksum discard 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" +"checksum http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "fe67e3678f2827030e89cc4b9e7ecd16d52f132c0b940ab5005f88e821500f6a" "checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" @@ -1451,6 +1591,7 @@ dependencies = [ "checksum serde_json 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb1277d4d0563e4593e0b8b5d23d744d277b55d2bc0bf1c38d0d8a6589d38aa" "checksum serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0887a8e097a69559b56aa2526bf7aff7c3048cf627dff781f0b56a6001534593" "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" +"checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum slog 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e1a2eec401952cd7b12a84ea120e2d57281329940c3f93c2bf04f462539508e" "checksum slog-async 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e544d16c6b230d84c866662fe55e31aacfca6ae71e6fc49ae9a311cb379bfc2f" @@ -1461,7 +1602,11 @@ dependencies = [ "checksum sloggers 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca1a15430a70a22ce2b066263dc55580a90b6920b837769332d2d0844a445549" "checksum smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b73ea3738b47563803ef814925e69be00799a8c07420be8b996f8e98fb2336db" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" -"checksum streamdeck-rs 0.1.0 (git+https://github.com/mdonoughe/streamdeck-rs?branch=sdk2)" = "" +"checksum stdweb 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)" = "5eafd45550bc406cfa179ea263ba0935da87adf220dab40e2c25c4675edc49d8" +"checksum stdweb-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e21ebd9179de08f2300a65454268a17ea3de204627458588c84319c4def3930" +"checksum stdweb-internal-macros 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1635afd059cbfac7d5b1274f0c44cec110c1e013c48e8bbc22e07e52696cf887" +"checksum stdweb-internal-runtime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a2a2f4a2eb556337b2d1a302630bbddf989ae383c70393e89b48152b9896cbda" +"checksum streamdeck-rs 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49144cc2a71cb2b8aa8dea187f84488442016fe66a23967ece16c2c6329b9891" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)" = "734ecc29cd36e8123850d9bf21dfd62ef8300aaa8f879aabaa899721808be37c" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" @@ -1509,3 +1654,4 @@ dependencies = [ "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "95acf0db5515d07da9965ec0e0ba6cc2d825e2caeb7303b66ca441729801254e" +"checksum yew 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca078a19e3459f7d73b135015477bc8cdcc92c3f4a058ae949f4d957a47dfd55" diff --git a/Cargo.toml b/Cargo.toml index ee4520a..c38a176 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,25 +1,7 @@ -[package] -name = "sbzdeck" -version = "0.1.0" -authors = ["Matthew Donoughe "] -description = "Steam Deck plugin for Sound Blaster devices" -repository = "https://github.com/mdonoughe/sbzdeck/" -readme = "README.md" -keywords = ["stream-deck", "sound-blaster"] -license = "MIT/Apache-2.0" -edition = "2018" +[workspace] -[badges] -travis-ci = { repository = "mdonoughe/sbzdeck" } - -[dependencies] -futures = "0.1.25" -indexmap = "1.0" -sbz-switch = "3.1" -serde = "1.0" -serde_derive = "1.0" -serde_json = "1.0" -slog = { version = "2.4", features = ["max_level_trace", "release_max_level_info"] } -sloggers = "0.3" -streamdeck-rs = { git = "https://github.com/mdonoughe/streamdeck-rs", branch = "sdk2" } -tokio = "0.1.14" +members = [ + "common", + "inspector", + "plugin" +] diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000..9d1a5be --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "common" +version = "0.1.0" +authors = ["Matthew Donoughe "] +edition = "2018" + +[dependencies] +indexmap = { version = "1", features = ["serde-1"] } +serde = "1" +serde_derive = "1" +serde_json = "1" diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000..58097e6 --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,48 @@ +use indexmap::{IndexMap, IndexSet}; +use serde_derive::{Deserialize, Serialize}; +use std::collections::{BTreeMap, BTreeSet}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(tag = "event", rename_all = "camelCase")] +pub enum FromInspector { + GetFeatures, + #[serde(rename_all = "camelCase")] + SetFeatures { + selected_parameters: BTreeMap>, + }, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(tag = "event", rename_all = "camelCase")] +pub enum ToInspector { + #[serde(rename_all = "camelCase")] + SetFeatures { + selected_parameters: IndexMap>, + }, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct SerdeProfile { + pub volume: Option, + #[serde(default)] + pub parameters: BTreeMap>, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct SerdeProfiles { + #[serde(default)] + pub headphones: SerdeProfile, + #[serde(default)] + pub speakers: SerdeProfile, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct SerdeCardSettings { + #[serde(default)] + pub selected_parameters: IndexMap>, + #[serde(default)] + pub profiles: SerdeProfiles, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct Empty {} diff --git a/inspector/Cargo.toml b/inspector/Cargo.toml new file mode 100644 index 0000000..2cf71b0 --- /dev/null +++ b/inspector/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "inspector" +version = "0.1.0" +authors = ["Matthew Donoughe "] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +common = { path = "../common" } +indexmap = "1.0" +serde = "1" +serde_derive = "1" +serde_json = "1" +stdweb = "0.4" +yew = "0.6" diff --git a/inspector/src/feature.rs b/inspector/src/feature.rs new file mode 100644 index 0000000..f49b293 --- /dev/null +++ b/inspector/src/feature.rs @@ -0,0 +1,97 @@ +use super::parameter::Parameter; +use indexmap::IndexMap; +use stdweb::traits::IEvent; +use yew::prelude::*; +use yew::virtual_dom::VNode; + +#[derive(Clone, Default, PartialEq)] +pub struct Properties { + pub name: String, + pub is_expanded: bool, + pub parameters: IndexMap, + pub onexpandchange: Option>, + pub onchange: Option>, +} + +pub struct Feature { + name: String, + is_expanded: bool, + parameters: IndexMap, + onexpandchange: Option>, + onchange: Option>, +} + +pub enum Message { + Toggle, + SetParameter { name: String, is_selected: bool }, +} + +impl Component for Feature { + type Message = Message; + type Properties = Properties; + + fn create(properties: Self::Properties, _link: ComponentLink) -> Self { + Self { + name: properties.name, + is_expanded: properties.is_expanded, + parameters: properties.parameters, + onexpandchange: properties.onexpandchange, + onchange: properties.onchange, + } + } + + fn update(&mut self, message: Self::Message) -> ShouldRender { + match message { + Message::Toggle => { + if let Some(ref mut callback) = self.onexpandchange { + callback.emit(!self.is_expanded); + } + } + Message::SetParameter { name, is_selected } => { + if let Some(ref mut callback) = self.onchange { + callback.emit((name, is_selected)); + } + } + } + false + } + + fn change(&mut self, properties: Self::Properties) -> ShouldRender { + let changed = self.name != properties.name + || self.is_expanded != properties.is_expanded + || self.parameters != properties.parameters; + self.name = properties.name; + self.is_expanded = properties.is_expanded; + self.parameters = properties.parameters; + self.onchange = properties.onchange; + self.onexpandchange = properties.onexpandchange; + changed + } +} + +impl Renderable for Feature { + fn view(&self) -> Html { + let mut tag = html! { +
+ { &self.name } + // capitalize `type` because otherwise yew eats it +
+
+ { for self.parameters.iter().map(|(name, is_selected)| { + let cb_name = name.clone(); + html! { + + } + }) } +
+
+
+ }; + if self.is_expanded { + if let VNode::VTag(ref mut vtag) = tag { + vtag.add_attribute("open", &""); + } + } + tag + } +} diff --git a/inspector/src/lib.rs b/inspector/src/lib.rs new file mode 100644 index 0000000..b051525 --- /dev/null +++ b/inspector/src/lib.rs @@ -0,0 +1,252 @@ +extern crate indexmap; +extern crate serde; +extern crate serde_derive; +extern crate serde_json; +extern crate stdweb; +#[macro_use] +extern crate yew; + +mod feature; +mod message; +mod parameter; +mod registration; +mod socket; + +use feature::Feature; +use indexmap::IndexMap; +use socket::{StreamDeckSocketService, StreamDeckSocketTask}; +use std::collections::BTreeSet; +use stdweb::js_export; +use stdweb::web::{document, INode}; +use yew::prelude::*; +use yew::services::websocket::WebSocketStatus; + +type Message = message::Message; +type RegistrationParams = registration::InspectorRegistrationParams; + +#[js_export] +fn connect_elgato_stream_deck_socket( + port: &str, + property_inspector_uuid: String, + register_event: String, + info: &str, + action_info: &str, +) { + let url = format!("ws://localhost:{}", port); + + let info = serde_json::from_str(info).unwrap(); + let action_info = serde_json::from_str(action_info).unwrap(); + + let params = RegistrationParams { + url, + property_inspector_uuid, + register_event, + info, + action_info, + }; + + let body = document().body().unwrap(); + let target = document().create_element("div").unwrap(); + body.append_child(&target); + + yew::initialize(); + let mut scope = App::::new().mount(target); + yew::run_loop(); + + scope.send_message(ComponentMessage::Connect(params)); +} + +#[derive(Clone, Default, Eq, PartialEq)] +pub struct FeatureInfo { + pub is_expanded: bool, + pub parameters: IndexMap, +} + +pub struct Model { + link: ComponentLink, + socket: StreamDeckSocketService< + common::SerdeCardSettings, + common::Empty, + common::ToInspector, + common::FromInspector, + >, + task: Option< + StreamDeckSocketTask, + >, + registration_params: Option, + selected_params: IndexMap, +} + +pub enum ComponentMessage { + Connect(RegistrationParams), + Message(Message), + Status(WebSocketStatus), + SetParameter { + feature: String, + parameter: String, + is_selected: bool, + }, + SetFeatureExpanded { + feature: String, + is_expanded: bool, + }, +} + +impl Component for Model { + type Message = ComponentMessage; + type Properties = (); + + fn create(_properties: Self::Properties, link: ComponentLink) -> Self { + Self { + link, + socket: StreamDeckSocketService::new(), + task: None, + selected_params: IndexMap::new(), + registration_params: None, + } + } + + fn update(&mut self, message: Self::Message) -> ShouldRender { + match message { + ComponentMessage::Connect(message) => { + self.task = Some(self.socket.connect( + &message.url, + self.link.send_back(ComponentMessage::Message), + self.link.send_back(ComponentMessage::Status), + )); + self.registration_params = Some(message); + false + } + ComponentMessage::Message(message) => { + match message { + message::Message::SendToPropertyInspector { payload, .. } => match payload { + common::ToInspector::SetFeatures { + selected_parameters, + } => { + let expanded = self + .selected_params + .iter() + .filter(|(_, f)| f.is_expanded) + .map(|(n, _)| n) + .collect::>(); + self.selected_params = selected_parameters + .into_iter() + .map(|(name, params)| { + let is_expanded = expanded.contains(&name); + ( + name, + FeatureInfo { + is_expanded, + parameters: params, + }, + ) + }) + .collect() + } + }, + _ => {} + } + true + } + ComponentMessage::Status(status) => { + if let WebSocketStatus::Opened = status { + let task = self.task.as_mut().unwrap(); + let registration_params = self.registration_params.as_ref().unwrap(); + task.register( + ®istration_params.register_event, + ®istration_params.property_inspector_uuid, + ); + task.send(&message::MessageOut::SendToPlugin { + action: registration_params.action_info.action.to_string(), + context: registration_params.property_inspector_uuid.to_string(), + payload: common::FromInspector::GetFeatures, + }); + } + false + } + ComponentMessage::SetFeatureExpanded { + feature, + is_expanded, + } => { + let feature = self.selected_params.get_mut(&feature).unwrap(); + if feature.is_expanded == is_expanded { + false + } else { + feature.is_expanded = is_expanded; + true + } + } + ComponentMessage::SetParameter { + feature, + parameter, + is_selected, + } => { + let changed = { + let feature = self.selected_params.get_mut(&feature).unwrap(); + let parameter = feature.parameters.get_mut(¶meter).unwrap(); + if *parameter == is_selected { + false + } else { + *parameter = is_selected; + true + } + }; + if changed { + let task = self.task.as_mut().unwrap(); + let registration_params = self.registration_params.as_ref().unwrap(); + task.send(&message::MessageOut::SendToPlugin { + action: registration_params.action_info.action.to_string(), + context: registration_params.property_inspector_uuid.to_string(), + payload: common::FromInspector::SetFeatures { + selected_parameters: self + .selected_params + .iter() + .filter(|(_, info)| { + info.parameters.iter().any(|(_, is_selected)| *is_selected) + }) + .map(|(name, info)| { + ( + name.to_string(), + info.parameters + .iter() + .filter(|(_, is_selected)| **is_selected) + .map(|(name, _)| name.to_string()) + .collect(), + ) + }) + .collect(), + }, + }); + } + changed + } + } + } +} + +impl Renderable for Model { + fn view(&self) -> Html { + html! { +
+ { for self.selected_params.iter().map(|(name, info)| { + let cb1_name = name.to_string(); + let cb2_name = name.to_string(); + html! { + + } + }) } +
+ } + } +} diff --git a/inspector/src/message.rs b/inspector/src/message.rs new file mode 100644 index 0000000..b512bd8 --- /dev/null +++ b/inspector/src/message.rs @@ -0,0 +1,296 @@ +use serde::de; +use serde_derive::{Deserialize, Serialize}; +use std::fmt; + +/// A message received from the Stream Deck software. +/// +/// - `G` represents the global settings that are persisted within the Stream Deck software. +/// - `S` represents the settings that are persisted within the Stream Deck software. +/// - `M` represents the messages that are received from the property inspector. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-received/) +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +#[serde(tag = "event", rename_all = "camelCase")] +pub enum Message { + /// The property inspector has sent data. + /// + /// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-received/#sendtoplugin) + #[serde(rename_all = "camelCase")] + SendToPropertyInspector { + /// The uuid of the action. + action: String, + /// The instance of the action (key or part of a multiaction). + context: String, + /// Information sent from the property inspector. + payload: M, + }, + /// The application has sent settings for an action. + /// + /// This message is sent in response to GetSettings, but also after the + /// property inspector changes the settings. + /// + /// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-received/#didreceivesettings) + #[serde(rename_all = "camelCase")] + DidReceiveSettings { + /// The uuid of the action. + action: String, + /// The instance of the action (key or part of a multiaction). + context: String, + /// The device where the action exists. + device: String, + /// The current settings for the action. + payload: KeyPayload, + }, + /// The application has sent settings for an action. + /// + /// This message is sent in response to GetGlobalSettings, but also after + /// the property inspector changes the settings. + /// + /// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-received/#didreceiveglobalsettings) + #[serde(rename_all = "camelCase")] + DidReceiveGlobalSettings { + /// The current settings for the action. + payload: GlobalSettingsPayload, + }, +} + +/// A message to be sent to the Stream Deck software. +/// +/// - `G` represents the global settings that are persisted within the Stream Deck software. +/// - `S` represents the action settings that are persisted within the Stream Deck software. +/// - `M` represents the messages that are sent to the property inspector. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/) +#[allow(dead_code)] +#[derive(Debug, Serialize)] +#[serde(tag = "event", rename_all = "camelCase")] +pub enum MessageOut { + /// Retrieve settings for an instance of an action via DidReceiveSettings. + /// + /// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/#getsettings) + #[serde(rename_all = "camelCase")] + GetSettings { + /// The instance of the action (key or part of a multiaction). + context: String, + }, + /// Store settings for an instance of an action. + /// + /// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/#setsettings) + #[serde(rename_all = "camelCase")] + SetSettings { + /// The instance of the action (key or part of a multiaction). + context: String, + /// The settings to save. + payload: S, + }, + /// Send data to the plugin. + /// + /// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/#sendtoplugin) + #[serde(rename_all = "camelCase")] + SendToPlugin { + /// The uuid of the action. + action: String, + /// The instance of the action (key or part of a multiaction). + context: String, + /// The message to send. + payload: M, + }, + /// Open a URL in the default browser. + /// + /// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/#openurl) + #[serde(rename_all = "camelCase")] + OpenUrl { + /// The url to open. + payload: UrlPayload, + }, + /// Retrieve plugin settings for via DidReceiveGlobalSettings. + /// + /// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/#getglobalsettings) + #[serde(rename_all = "camelCase")] + GetGlobalSettings { + /// The instance of the action (key or part of a multiaction). + context: String, + }, + /// Store plugin settings. + /// + /// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/#setglobalsettings) + #[serde(rename_all = "camelCase")] + SetGlobalSettings { + /// The instance of the action (key or part of a multiaction). + context: String, + /// The settings to save. + payload: G, + }, + /// Write to the log. + /// + /// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/#logmessage) + #[serde(rename_all = "camelCase")] + LogMessage { + /// The message to log. + payload: LogMessagePayload, + }, +} + +/// The URL to launch as part of a [OpenUrl](enum.MessageOut.html#variant.OpenUrl) message. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/#openurl) +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UrlPayload { + /// The URL to launch. + pub url: String, +} + +/// Additional information about the key pressed. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct KeyPayload { + /// The stored settings for the action instance. + pub settings: S, + /// The location of the key that was pressed, or None if this action instance is part of a multi action. + pub coordinates: Option, + /// The current state of the action instance. + pub state: u8, + /// The desired state of the action instance (if this instance is part of a multi action). + pub user_desired_state: Option, + //TODO: is_in_multi_action ignored. replace coordinates with enum Location { Coordinates, MultiAction }. +} + +/// The new global settings. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GlobalSettingsPayload { + /// The stored settings for the plugin. + pub settings: G, +} + +/// A log message. +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LogMessagePayload { + /// The log message text. + pub message: String, +} + +/// Information about a hardware device. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/registration-procedure/#Info-parameter) +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeviceInfo { + /// The size of the device. + pub size: DeviceSize, + /// The type of the device, or None if the Stream Deck software is running with no device attached. + #[serde(rename = "type")] + pub _type: Option, +} + +/// Information about a monitored application that has launched or terminated. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplicationPayload { + /// The name of the application. + pub application: String, +} + +/// The location of a key on a device. +/// +/// Locations are specified using zero-indexed values starting from the top left corner of the device. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Coordinates { + /// The x coordinate of the key. + pub column: u8, + /// The y-coordinate of the key. + pub row: u8, +} + +/// The vertical alignment of a title. +/// +/// Titles are always centered horizontally. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Alignment { + /// The title should appear at the top of the key. + Top, + /// The title should appear in the middle of the key. + Middle, + /// The title should appear at the bottom of the key. + Bottom, +} + +/// Style information for a title. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/events-received/#titleparametersdidchange) +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TitleParameters { + /// The name of the font family. + pub font_family: String, + /// The font size. + pub font_size: u8, + /// Whether the font is bold and/or italic. + pub font_style: String, + /// Whether the font is underlined. + pub font_underline: bool, + /// Whether the title is displayed. + pub show_title: bool, + /// The vertical alignment of the title. + pub title_alignment: Alignment, + /// The color of the title. + pub title_color: String, +} + +/// The size of a device in keys. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeviceSize { + /// The number of key columns on the device. + pub columns: u8, + /// The number of key rows on the device. + pub rows: u8, +} + +/// The type of connected hardware device. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/overview/#stream-deck-hardware) +#[derive(Debug)] +pub enum DeviceType { + /// The [Stream Deck](https://www.elgato.com/en/gaming/stream-deck). + StreamDeck, + /// The [Stream Deck Mini](https://www.elgato.com/en/gaming/stream-deck-mini). + StreamDeckMini, + /// A device not documented in the 4.0.0 SDK. + Unknown(u64), +} + +impl<'de> de::Deserialize<'de> for DeviceType { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = DeviceType; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an integer") + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(match value { + 0 => DeviceType::StreamDeck, + 1 => DeviceType::StreamDeckMini, + value => DeviceType::Unknown(value), + }) + } + } + + deserializer.deserialize_u64(Visitor) + } +} diff --git a/inspector/src/parameter.rs b/inspector/src/parameter.rs new file mode 100644 index 0000000..de5c2b8 --- /dev/null +++ b/inspector/src/parameter.rs @@ -0,0 +1,62 @@ +use stdweb::traits::*; +use yew::prelude::*; + +#[derive(Clone, Default, PartialEq)] +pub struct Properties { + pub name: String, + pub is_selected: bool, + pub onchange: Option>, +} + +pub struct Parameter { + name: String, + is_selected: bool, + onchange: Option>, +} + +pub enum Message { + Toggle, +} + +impl Component for Parameter { + type Message = Message; + type Properties = Properties; + + fn create(properties: Self::Properties, _link: ComponentLink) -> Self { + Self { + name: properties.name, + is_selected: properties.is_selected, + onchange: properties.onchange, + } + } + + fn update(&mut self, message: Self::Message) -> ShouldRender { + match message { + Message::Toggle => { + if let Some(ref mut callback) = self.onchange { + callback.emit(!self.is_selected) + } + } + } + false + } + + fn change(&mut self, properties: Self::Properties) -> ShouldRender { + let changed = &self.name != &properties.name || self.is_selected != properties.is_selected; + self.name = properties.name; + self.is_selected = properties.is_selected; + self.onchange = properties.onchange; + changed + } +} + +impl Renderable for Parameter { + fn view(&self) -> Html { + html! { +
+ + +
+ } + } +} diff --git a/inspector/src/registration.rs b/inspector/src/registration.rs new file mode 100644 index 0000000..4c1fa15 --- /dev/null +++ b/inspector/src/registration.rs @@ -0,0 +1,221 @@ +use serde::de; +use serde_derive::Deserialize; +use std::fmt; + +/// Information about a connected device. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/registration-procedure/#info-parameter) +#[derive(Debug, Deserialize)] +pub struct RegistrationInfoDevice { + /// The ID of the specific device. + pub id: String, + /// The size of the device. + pub size: DeviceSize, + /// The type of the device. + #[serde(rename = "type")] + pub _type: Option, +} + +/// The language the Stream Deck software is running in. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/registration-procedure/#Info-parameter) +#[derive(Debug)] +pub enum Language { + English, + French, + German, + Spanish, + Japanese, + /// Unlike the other lanuages which are not specifically localized to a country, Chinese is specifically zh-CN. + ChineseChina, + /// A language that was not documented in the 4.0.0 SDK. + Unknown(String), +} + +impl<'de> de::Deserialize<'de> for Language { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = Language; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(match value { + "en" => Language::English, + "fr" => Language::French, + "de" => Language::German, + "es" => Language::Spanish, + "ja" => Language::Japanese, + "zh_cn" => Language::ChineseChina, + value => Language::Unknown(value.to_string()), + }) + } + } + + deserializer.deserialize_str(Visitor) + } +} + +/// The platform on which the Stream Deck software is running. +#[derive(Debug)] +pub enum Platform { + /// Mac OS X + Mac, + /// Windows + Windows, + /// A platform not documented in the 4.0.0 SDK. + Unknown(String), +} + +impl<'de: 'a, 'a> de::Deserialize<'de> for Platform { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = Platform; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(match value { + "mac" => Platform::Mac, + "windows" => Platform::Windows, + value => Platform::Unknown(value.to_string()), + }) + } + } + + deserializer.deserialize_str(Visitor) + } +} + +/// Information about the Stream Deck software. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/registration-procedure/#info-parameter) +#[derive(Debug, Deserialize)] +pub struct RegistrationInfoApplication { + pub language: Language, + pub platform: Platform, + pub version: String, +} + +/// Information about the environment the plugin is being loaded into. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/registration-procedure/#info-parameter) +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RegistrationInfo { + pub application: RegistrationInfoApplication, + pub device_pixel_ratio: u8, + pub devices: Vec, +} + +/// Information about the action for this property inspector. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/registration-procedure/#inactioninfo-parameter) +#[derive(Debug, Deserialize)] +pub struct ActionInfo { + pub action: String, + pub context: String, + pub device: String, + pub payload: ActionInfoPayload, +} + +/// Additional information about the action. +#[derive(Debug, Deserialize)] +pub struct ActionInfoPayload { + /// The stored settings for the action instance. + pub settings: S, + /// The location of the key that was pressed, or None if this action instance is part of a multi action. + pub coordinates: Option, +} + +/// The location of a key on a device. +/// +/// Locations are specified using zero-indexed values starting from the top left corner of the device. +#[derive(Debug, Deserialize)] +pub struct Coordinates { + /// The x coordinate of the key. + pub column: u8, + /// The y-coordinate of the key. + pub row: u8, +} + +/// The size of a device in keys. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeviceSize { + /// The number of key columns on the device. + pub columns: u8, + /// The number of key rows on the device. + pub rows: u8, +} + +/// The type of connected hardware device. +/// +/// [Official Documentation](https://developer.elgato.com/documentation/stream-deck/sdk/overview/#stream-deck-hardware) +#[derive(Debug)] +pub enum DeviceType { + /// The [Stream Deck](https://www.elgato.com/en/gaming/stream-deck). + StreamDeck, + /// The [Stream Deck Mini](https://www.elgato.com/en/gaming/stream-deck-mini). + StreamDeckMini, + /// A device not documented in the 4.0.0 SDK. + Unknown(u64), +} + +impl<'de> de::Deserialize<'de> for DeviceType { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = DeviceType; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an integer") + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(match value { + 0 => DeviceType::StreamDeck, + 1 => DeviceType::StreamDeckMini, + value => DeviceType::Unknown(value), + }) + } + } + + deserializer.deserialize_u64(Visitor) + } +} + +pub struct InspectorRegistrationParams { + pub url: String, + pub property_inspector_uuid: String, + pub register_event: String, + pub info: RegistrationInfo, + pub action_info: ActionInfo, +} diff --git a/inspector/src/socket.rs b/inspector/src/socket.rs new file mode 100644 index 0000000..86205ac --- /dev/null +++ b/inspector/src/socket.rs @@ -0,0 +1,95 @@ +use super::message::{Message, MessageOut}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_derive::Serialize; +use std::marker::PhantomData; +use yew::callback::Callback; +use yew::format::{Binary, Text}; +use yew::services::websocket::{WebSocketService, WebSocketStatus, WebSocketTask}; + +pub struct StreamDeckSocketService { + inner: WebSocketService, + _phantom: PhantomData<(G, S, MI, MO)>, +} + +pub struct StreamDeckSocketTask { + inner: WebSocketTask, + _phantom: PhantomData<(G, S, MO)>, +} + +impl StreamDeckSocketTask +where + G: Serialize, + S: Serialize, + MO: Serialize, +{ + pub fn send(&mut self, data: &MessageOut) { + let message = serde_json::to_string(data).unwrap(); + self.inner.send(Ok(message)); + } + + pub fn register(&mut self, event: &str, uuid: &str) { + let registration = serde_json::to_string(&Registration { event, uuid }).unwrap(); + self.inner.send(Ok(registration)); + } +} + +struct WsMessage { + pub message: Message, +} + +impl From for WsMessage { + fn from(_input: Binary) -> Self { + panic!("unexpected binary data") + } +} + +impl From for WsMessage +where + G: DeserializeOwned, + S: DeserializeOwned, + MI: DeserializeOwned, +{ + fn from(input: Text) -> Self { + WsMessage { + message: serde_json::from_str(&input.unwrap()).unwrap(), + } + } +} + +#[derive(Serialize)] +struct Registration<'a> { + event: &'a str, + uuid: &'a str, +} + +impl StreamDeckSocketService +where + G: 'static + DeserializeOwned, + S: 'static + DeserializeOwned, + MI: 'static + DeserializeOwned, +{ + pub fn new() -> Self { + Self { + inner: WebSocketService::new(), + _phantom: PhantomData, + } + } + + pub fn connect( + &mut self, + address: &str, + callback: Callback>, + notification: Callback, + ) -> StreamDeckSocketTask { + let task = self.inner.connect( + address, + Callback::from(move |message: WsMessage| callback.emit(message.message)), + Callback::from(move |status| notification.emit(status)), + ); + StreamDeckSocketTask { + inner: task, + _phantom: PhantomData, + } + } +} diff --git a/inspector/static/css/buttons.png b/inspector/static/css/buttons.png new file mode 100644 index 0000000..c60438e Binary files /dev/null and b/inspector/static/css/buttons.png differ diff --git a/inspector/static/css/buttons@2x.png b/inspector/static/css/buttons@2x.png new file mode 100644 index 0000000..16445ed Binary files /dev/null and b/inspector/static/css/buttons@2x.png differ diff --git a/inspector/static/css/caret.svg b/inspector/static/css/caret.svg new file mode 100644 index 0000000..b69162a --- /dev/null +++ b/inspector/static/css/caret.svg @@ -0,0 +1,3 @@ + + + diff --git a/inspector/static/css/check.png b/inspector/static/css/check.png new file mode 100644 index 0000000..b5e9e62 Binary files /dev/null and b/inspector/static/css/check.png differ diff --git a/inspector/static/css/check.svg b/inspector/static/css/check.svg new file mode 100644 index 0000000..5b96af0 --- /dev/null +++ b/inspector/static/css/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/inspector/static/css/elg_calendar.svg b/inspector/static/css/elg_calendar.svg new file mode 100644 index 0000000..157e01b --- /dev/null +++ b/inspector/static/css/elg_calendar.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inspector/static/css/elg_calendar_inv.svg b/inspector/static/css/elg_calendar_inv.svg new file mode 100644 index 0000000..4f8af68 --- /dev/null +++ b/inspector/static/css/elg_calendar_inv.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/inspector/static/css/elg_calendar_inv_13.svg b/inspector/static/css/elg_calendar_inv_13.svg new file mode 100644 index 0000000..8927f47 --- /dev/null +++ b/inspector/static/css/elg_calendar_inv_13.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/inspector/static/css/pi_required.svg b/inspector/static/css/pi_required.svg new file mode 100644 index 0000000..53479b7 --- /dev/null +++ b/inspector/static/css/pi_required.svg @@ -0,0 +1,3 @@ + + + diff --git a/inspector/static/css/pi_required_ok.svg b/inspector/static/css/pi_required_ok.svg new file mode 100644 index 0000000..1a0784d --- /dev/null +++ b/inspector/static/css/pi_required_ok.svg @@ -0,0 +1,3 @@ + + + diff --git a/inspector/static/css/rcheck.svg b/inspector/static/css/rcheck.svg new file mode 100644 index 0000000..af478ee --- /dev/null +++ b/inspector/static/css/rcheck.svg @@ -0,0 +1,3 @@ + + + diff --git a/inspector/static/css/sdpi.css b/inspector/static/css/sdpi.css new file mode 100644 index 0000000..d06f67b --- /dev/null +++ b/inspector/static/css/sdpi.css @@ -0,0 +1,1483 @@ +html { + --sdpi-bgcolor: #2D2D2D; + --sdpi-background: #3D3D3D; + --sdpi-color: #d8d8d8; + --sdpi-bordercolor: #3a3a3a; + --sdpi-buttonbordercolor: #969696; + --sdpi-borderradius: 0px; + --sdpi-width: 224px; + --sdpi-fontweight: 600; + --sdpi-letterspacing: -0.25pt; + height: 100%; + width: 100%; + overflow: hidden; + touch-action:none; +} + +html, body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 9pt; + background-color: var(--sdpi-bgcolor); + color: #9a9a9a; +} + +body { + height: 100%; + padding: 0; + overflow-x: hidden; + overflow-y: auto; + margin: 0; + -webkit-overflow-scrolling: touch; + -webkit-text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; +} + +mark { + background-color: var(--sdpi-bgcolor); + color: var(--sdpi-color); +} + +hr, hr2 { + -webkit-margin-before: 1em; + -webkit-margin-after: 1em; + border-style: none; + background: var(--sdpi-background); + height: 1px; +} + +hr2, +.sdpi-heading { + display: flex; + flex-basis: 100%; + align-items: center; + color: inherit; + font-size: 9pt; + margin: 8px 0px; +} + +.sdpi-heading::before, +.sdpi-heading::after { + content: ""; + flex-grow: 1; + background: var(--sdpi-background); + height: 1px; + font-size: 0px; + line-height: 0px; + margin: 0px 16px; +} + +hr2 { + height: 2px; +} + +hr, hr2 { + margin-left:16px; + margin-right:16px; +} + +.sdpi-item-value, +option, +input, +select, +button { + font-size: 10pt; + font-weight: var(--sdpi-fontweight); + letter-spacing: var(--sdpi-letterspacing); +} + + + +.win .sdpi-item-value, +.win option, +.win input, +.win select, +.win button { + font-size: 11px; + font-style: normal; + letter-spacing: inherit; + font-weight: 100; +} + +.win button { + font-size: 12px; +} + +::-webkit-progress-value, +meter::-webkit-meter-optimum-value { + border-radius: 2px; + /* background: linear-gradient(#ccf, #99f 20%, #77f 45%, #77f 55%, #cdf); */ +} + +::-webkit-progress-bar, +meter::-webkit-meter-bar { + border-radius: 3px; + background: var(--sdpi-background); +} + +::-webkit-progress-bar:active, +meter::-webkit-meter-bar:active { + border-radius: 3px; + background: #222222; +} +::-webkit-progress-value:active, +meter::-webkit-meter-optimum-value:active { + background: #99f; +} + +progress, +progress.sdpi-item-value { + min-height: 5px !important; + height: 5px; + background-color: #303030; +} + +progress { + margin-top: 8px !important; + margin-bottom: 8px !important; +} + +.full progress, +progress.full { + margin-top: 3px !important; +} + +::-webkit-progress-inner-element { + background-color: transparent; +} + + +.sdpi-item[type="progress"] { + margin-top: 4px !important; + margin-bottom: 12px; + min-height: 15px; +} + +.sdpi-item-child.full:last-child { + margin-bottom: 4px; +} + +.tabs { + /** + * Setting display to flex makes this container lay + * out its children using flexbox, the exact same + * as in the above "Stepper input" example. + */ + display: flex; + + border-bottom: 1px solid #D7DBDD; +} + +.tab { + cursor: pointer; + padding: 5px 30px; + color: #16a2d7; + font-size: 9pt; + border-bottom: 2px solid transparent; +} + +.tab.is-tab-selected { + border-bottom-color: #4ebbe4; +} + +select { + -webkit-appearance: none; + -moz-appearance: none; + -o-appearance: none; + appearance: none; + background: url(caret.svg) no-repeat 97% center; +} + +label.sdpi-file-label, +input[type="button"], +input[type="submit"], +input[type="reset"], +input[type="file"], +input[type=file]::-webkit-file-upload-button, +button, +select { + color: var(--sdpi-color); + border: 1pt solid #303030; + font-size: 8pt; + background-color: var(--sdpi-background); + border-radius: var(--sdpi-borderradius); +} + +label.sdpi-file-label, +input[type="button"], +input[type="submit"], +input[type="reset"], +input[type="file"], +input[type=file]::-webkit-file-upload-button, +button { + border: 1pt solid var(--sdpi-buttonbordercolor); + border-radius: var(--sdpi-borderradius); + border-color: var(--sdpi-buttonbordercolor); + min-height: 23px !important; + height: 23px !important; + margin-right: 8px; +} + +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type="file"] { + border-radius: var(--sdpi-borderradius); + max-width: 220px; +} + +option { + height: 1.5em; + padding: 4px; +} + +/* SDPI */ + +.sdpi-wrapper { + overflow-x: hidden; + height: 100%; +} + +.sdpi-item { + display: flex; + flex-direction: row; + min-height: 32px; + align-items: center; + margin-top: 2px; + max-width: 344px; +} + +.sdpi-item:first-child { + margin-top:-1px; +} + +.sdpi-item:last-child { + margin-bottom: 0px; +} + +.sdpi-item > *:not(.sdpi-item-label):not(meter):not(details):not(canvas) { + min-height: 26px; + padding: 0px 4px 0px 4px; +} + +.sdpi-item > *:not(.sdpi-item-label.empty):not(meter) { + min-height: 26px; + padding: 0px 4px 0px 4px; +} + + +.sdpi-item-group { + padding: 0 !important; +} + +meter.sdpi-item-value { + margin-left: 6px; +} + +.sdpi-item[type="group"] { + display: block; + margin-top: 12px; + margin-bottom: 12px; + /* border: 1px solid white; */ + flex-direction: unset; + text-align: left; +} + +.sdpi-item[type="group"] > .sdpi-item-label, +.sdpi-item[type="group"].sdpi-item-label { + width: 96%; + text-align: left; + font-weight: 700; + margin-bottom: 4px; + padding-left: 4px; +} + +dl, +ul, +ol { + -webkit-margin-before: 0px; + -webkit-margin-after: 4px; + -webkit-padding-start: 1em; + max-height: 90px; + overflow-y: scroll; + cursor: pointer; + user-select: none; +} + +table.sdpi-item-value, +dl.sdpi-item-value, +ul.sdpi-item-value, +ol.sdpi-item-value { + -webkit-margin-before: 4px; + -webkit-margin-after: 8px; + -webkit-padding-start: 1em; + width: var(--sdpi-width); + text-align: center; +} + +table > caption { + margin: 2px; +} + +.list, +.sdpi-item[type="list"] { + align-items: baseline; +} + +.sdpi-item-label { + text-align: right; + flex: none; + width: 94px; + padding-right: 4px; + font-weight: 600; + -webkit-user-select: none; +} + +.win .sdpi-item-label, +.sdpi-item-label > small{ + font-weight: normal; +} + +.sdpi-item-label:after { + content: ": "; +} + +.sdpi-item-label.empty:after { + content: ""; +} + +.sdpi-test, +.sdpi-item-value { + flex: 1 0 0; + /* flex-grow: 1; + flex-shrink: 0; */ + margin-right: 14px; + margin-left: 4px; + justify-content: space-evenly; +} + +canvas.sdpi-item-value { + max-width: 144px; + max-height: 144px; + width: 144px; + height: 144px; + margin: 0 auto; + cursor: pointer; +} + +input.sdpi-item-value { + margin-left: 5px; +} + +.sdpi-item-value button, +button.sdpi-item-value { + margin-left: 6px; + margin-right: 14px; +} + +.sdpi-item-value.range { + margin-left: 0px; +} + +table, +dl.sdpi-item-value, +ul.sdpi-item-value, +ol.sdpi-item-value, +.sdpi-item-value > dl, +.sdpi-item-value > ul, +.sdpi-item-value > ol +{ + list-style-type: none; + list-style-position: outside; + margin-left: -4px; + margin-right: -4px; + padding: 4px; + border: 1px solid var(--sdpi-bordercolor); +} + +dl.sdpi-item-value, +ul.sdpi-item-value, +ol.sdpi-item-value, +.sdpi-item-value > ol { + list-style-type: none; + list-style-position: inside; + margin-left: 5px; + margin-right: 12px; + padding: 4px !important; +} + +ol.sdpi-item-value, +.sdpi-item-value > ol[listtype="none"] { + list-style-type: none; +} +ol.sdpi-item-value[type="decimal"], +.sdpi-item-value > ol[type="decimal"] { + list-style-type: decimal; +} + +ol.sdpi-item-value[type="decimal-leading-zero"], +.sdpi-item-value > ol[type="decimal-leading-zero"] { + list-style-type: decimal-leading-zero; +} + +ol.sdpi-item-value[type="lower-alpha"], +.sdpi-item-value > ol[type="lower-alpha"] { + list-style-type: lower-alpha; +} + +ol.sdpi-item-value[type="upper-alpha"], +.sdpi-item-value > ol[type="upper-alpha"] { + list-style-type: upper-alpha; +} + +ol.sdpi-item-value[type="upper-roman"], +.sdpi-item-value > ol[type="upper-roman"] { + list-style-type: upper-roman; +} + +ol.sdpi-item-value[type="lower-roman"], +.sdpi-item-value > ol[type="lower-roman"] { + list-style-type: upper-roman; +} + +tr:nth-child(even), +.sdpi-item-value > ul > li:nth-child(even), +.sdpi-item-value > ol > li:nth-child(even), +li:nth-child(even) { + background-color: rgba(0,0,0,.2) +} + +td:hover, +.sdpi-item-value > ul > li:hover:nth-child(even), +.sdpi-item-value > ol > li:hover:nth-child(even), +li:hover:nth-child(even), +li:hover { + background-color: rgba(255,255,255,.1); +} + +td.selected, +td.selected:hover, +li.selected:hover, +li.selected { + color: white; + background-color: #77f; +} + +tr { + border: 1px solid var(--sdpi-bordercolor); +} + +td { + border-right: 1px solid var(--sdpi-bordercolor); + -webkit-user-select: none; +} + +tr:last-child, +td:last-child { + border: none; +} + +.sdpi-item-value.select, +.sdpi-item-value > select { + margin-right: 13px; + margin-left: 4px; +} + +.sdpi-item-child, +.sdpi-item-group > .sdpi-item > input[type="color"] { + margin-top: 0.4em; + margin-right: 4px; +} + +.full, +.full *, +.sdpi-item-value.full, +.sdpi-item-child > full > *, +.sdpi-item-child.full, +.sdpi-item-child.full > *, +.full > .sdpi-item-child, +.full > .sdpi-item-child > *{ + display: flex; + flex: 1 1 0; + margin-bottom: 4px; + margin-left: 0px; + width: 100%; + + justify-content: space-evenly; +} + +.sdpi-item-group > .sdpi-item > input[type="color"] { + margin-top: 0px; +} + +::-webkit-calendar-picker-indicator:focus, +input[type=file]::-webkit-file-upload-button:focus, +button:focus, +textarea:focus, +input:focus, +select:focus, +option:focus, +details:focus, +summary:focus, +.custom-select select { + outline: none; +} + +summary { + cursor: default; + -webkit-user-select: none; +} + +.pointer, +summary .pointer { + cursor: pointer; +} + +details.message { + padding: 4px 18px 4px 12px; +} + +details.message summary { + font-size: 10pt; + font-weight: 600; + min-height: 18px; +} + +details.message:first-child { + margin-top: 4px; + margin-left: 0; + padding-left: 102px; +} + +details.message h1 { + text-align: left; +} + +.message > summary::-webkit-details-marker { + display: none; +} + +.info20, +.question, +.caution, +.info { + background-repeat: no-repeat; + background-position: 72px center; +} + +.info20 { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 Z M10,8 C8.8954305,8 8,8.84275812 8,9.88235294 L8,16.1176471 C8,17.1572419 8.8954305,18 10,18 C11.1045695,18 12,17.1572419 12,16.1176471 L12,9.88235294 C12,8.84275812 11.1045695,8 10,8 Z M10,3 C8.8954305,3 8,3.88165465 8,4.96923077 L8,5.03076923 C8,6.11834535 8.8954305,7 10,7 C11.1045695,7 12,6.11834535 12,5.03076923 L12,4.96923077 C12,3.88165465 11.1045695,3 10,3 Z'/%3E%3C/svg%3E%0A"); +} + +.info { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M10,8 C9.44771525,8 9,8.42137906 9,8.94117647 L9,14.0588235 C9,14.5786209 9.44771525,15 10,15 C10.5522847,15 11,14.5786209 11,14.0588235 L11,8.94117647 C11,8.42137906 10.5522847,8 10,8 Z M10,5 C9.44771525,5 9,5.44082732 9,5.98461538 L9,6.01538462 C9,6.55917268 9.44771525,7 10,7 C10.5522847,7 11,6.55917268 11,6.01538462 L11,5.98461538 C11,5.44082732 10.5522847,5 10,5 Z'/%3E%3C/svg%3E%0A"); +} + +.info2 { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cpath fill='%23999' d='M7.5,15 C3.35786438,15 0,11.6421356 0,7.5 C0,3.35786438 3.35786438,0 7.5,0 C11.6421356,0 15,3.35786438 15,7.5 C15,11.6421356 11.6421356,15 7.5,15 Z M7.5,2 C6.67157287,2 6,2.66124098 6,3.47692307 L6,3.52307693 C6,4.33875902 6.67157287,5 7.5,5 C8.32842705,5 9,4.33875902 9,3.52307693 L9,3.47692307 C9,2.66124098 8.32842705,2 7.5,2 Z M5,6 L5,7.02155172 L6,7 L6,12 L5,12.0076778 L5,13 L10,13 L10,12 L9,12.0076778 L9,6 L5,6 Z'/%3E%3C/svg%3E%0A"); +} + +.sdpi-more-info { + background-image: linear-gradient(to right, #00000000 0%,#00000040 80%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpolygon fill='%23999' points='4 7 8 7 8 5 12 8 8 11 8 9 4 9'/%3E%3C/svg%3E%0A"); +} +.caution { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M9.03952676,0.746646542 C9.57068894,-0.245797319 10.4285735,-0.25196227 10.9630352,0.746646542 L19.7705903,17.2030214 C20.3017525,18.1954653 19.8777595,19 18.8371387,19 L1.16542323,19 C0.118729947,19 -0.302490098,18.2016302 0.231971607,17.2030214 L9.03952676,0.746646542 Z M10,2.25584053 L1.9601405,17.3478261 L18.04099,17.3478261 L10,2.25584053 Z M10,5.9375 C10.531043,5.9375 10.9615385,6.37373537 10.9615385,6.91185897 L10.9615385,11.6923077 C10.9615385,12.2304313 10.531043,12.6666667 10,12.6666667 C9.46895697,12.6666667 9.03846154,12.2304313 9.03846154,11.6923077 L9.03846154,6.91185897 C9.03846154,6.37373537 9.46895697,5.9375 10,5.9375 Z M10,13.4583333 C10.6372516,13.4583333 11.1538462,13.9818158 11.1538462,14.6275641 L11.1538462,14.6641026 C11.1538462,15.3098509 10.6372516,15.8333333 10,15.8333333 C9.36274837,15.8333333 8.84615385,15.3098509 8.84615385,14.6641026 L8.84615385,14.6275641 C8.84615385,13.9818158 9.36274837,13.4583333 10,13.4583333 Z'/%3E%3C/svg%3E%0A"); +} + +.question { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M6.77783203,7.65332031 C6.77783203,7.84798274 6.85929281,8.02888914 7.0222168,8.19604492 C7.18514079,8.36320071 7.38508996,8.44677734 7.62207031,8.44677734 C8.02409055,8.44677734 8.29703704,8.20768468 8.44091797,7.72949219 C8.59326248,7.27245865 8.77945854,6.92651485 8.99951172,6.69165039 C9.2195649,6.45678594 9.56233491,6.33935547 10.027832,6.33935547 C10.4256205,6.33935547 10.7006836,6.37695313 11.0021973,6.68847656 C11.652832,7.53271484 10.942627,8.472229 10.3750916,9.1321106 C9.80755615,9.79199219 8.29492188,11.9897461 10.027832,12.1347656 C10.4498423,12.1700818 10.7027991,11.9147157 10.7832031,11.4746094 C11.0021973,9.59857178 13.1254883,8.82415771 13.1254883,7.53271484 C13.1254883,7.07568131 12.9974785,6.65250846 12.7414551,6.26318359 C12.4854317,5.87385873 12.1225609,5.56600048 11.652832,5.33959961 C11.1831031,5.11319874 10.6414419,5 10.027832,5 C9.36767248,5 8.79004154,5.13541531 8.29492187,5.40625 C7.79980221,5.67708469 7.42317837,6.01879677 7.16503906,6.43139648 C6.90689975,6.8439962 6.77783203,7.25130007 6.77783203,7.65332031 Z M10.0099668,15 C10.2713191,15 10.5016601,14.9108147 10.7009967,14.7324415 C10.9003332,14.5540682 11,14.3088087 11,13.9966555 C11,13.7157177 10.9047629,13.4793767 10.7142857,13.2876254 C10.5238086,13.0958742 10.2890379,13 10.0099668,13 C9.72646591,13 9.48726565,13.0958742 9.2923588,13.2876254 C9.09745196,13.4793767 9,13.7157177 9,13.9966555 C9,14.313268 9.10077419,14.5596424 9.30232558,14.735786 C9.50387698,14.9119295 9.73975502,15 10.0099668,15 Z'/%3E%3C/svg%3E%0A"); +} + + +.sdpi-more-info { + position: fixed; + left: 0px; + right: 0px; + bottom: 0px; + min-height:16px; + padding-right: 16px; + text-align: right; + -webkit-touch-callout: none; + cursor: pointer; + user-select: none; + background-position: right center; + background-repeat: no-repeat; + border-radius: var(--sdpi-borderradius); + text-decoration: none; + color: var(--sdpi-color); +} + +.sdpi-more-info-button { + display: flex; + align-self: right; + margin-left: auto; + position: fixed; + right: 17px; + bottom: 0px; + user-select: none; +} + +details a { + background-position: right !important; + min-height: 24px; + display: inline-block; + line-height: 24px; + padding-right: 28px; +} + + +input:not([type="range"]), +textarea { + -webkit-appearance: none; + background: var(--sdpi-background); + color: var(--sdpi-color); + font-weight: normal; + font-size: 9pt; + border: none; + margin-top: 2px; + margin-bottom: 2px; + min-width: 219px; +} + +textarea + label { + display: flex; + justify-content: flex-end +} +input[type="radio"], +input[type="checkbox"] { + display: none; +} +input[type="radio"] + label, +input[type="checkbox"] + label { + font-size: 9pt; + color: var(--sdpi-color); + font-weight: normal; + margin-right: 8px; + -webkit-user-select: none; +} + +input[type="radio"] + label:after, +input[type="checkbox"] + label:after { + content: " " !important; +} + +.sdpi-item[type="radio"] > .sdpi-item-value, +.sdpi-item[type="checkbox"] > .sdpi-item-value { + padding-top: 2px; +} + +.sdpi-item[type="checkbox"] > .sdpi-item-value > * { + margin-top: 4px; +} + +.sdpi-item[type="checkbox"] .sdpi-item-child, +.sdpi-item[type="radio"] .sdpi-item-child { + display: inline-block; +} + +.sdpi-item[type="range"] .sdpi-item-value, +.sdpi-item[type="meter"] .sdpi-item-child, +.sdpi-item[type="progress"] .sdpi-item-child { + display: flex; +} + +.sdpi-item[type="range"] .sdpi-item-value { + min-height: 26px; +} + +.sdpi-item[type="range"] .sdpi-item-value span, +.sdpi-item[type="meter"] .sdpi-item-child span, +.sdpi-item[type="progress"] .sdpi-item-child span { + margin-top: -2px; + min-width: 8px; + text-align: right; + user-select: none; + cursor: pointer; +} + +.sdpi-item[type="range"] .sdpi-item-value span { + margin-top: 7px; + text-align: right; +} + +span + input[type="range"] { + display: flex; + max-width: 168px; + +} + +.sdpi-item[type="range"] .sdpi-item-value span:first-child, +.sdpi-item[type="meter"] .sdpi-item-child span:first-child, +.sdpi-item[type="progress"] .sdpi-item-child span:first-child { + margin-right: 4px; +} + +.sdpi-item[type="range"] .sdpi-item-value span:last-child, +.sdpi-item[type="meter"] .sdpi-item-child span:last-child, +.sdpi-item[type="progress"] .sdpi-item-child span:last-child { + margin-left: 4px; +} + +.reverse { + transform: rotate(180deg); +} + +.sdpi-item[type="meter"] .sdpi-item-child meter + span:last-child { + margin-left: -10px; +} + +.sdpi-item[type="progress"] .sdpi-item-child meter + span:last-child { + margin-left: -14px; +} + +.sdpi-item[type="radio"] > .sdpi-item-value > * { + margin-top: 2px; +} + +details { + padding: 8px 18px 8px 12px; + min-width: 86px; +} + +details > h4 { + border-bottom: 1px solid var(--sdpi-bordercolor); +} + +legend { + display: none; +} +.sdpi-item-value > textarea { + padding: 0px; + width: 219px; + margin-left: 1px; + margin-top: 3px; + padding: 4px; +} + +input[type="radio"] + label span, +input[type="checkbox"] + label span { + display: inline-block; + width: 16px; + height: 16px; + margin: 2px 4px 2px 0; + border-radius: 3px; + vertical-align: middle; + background: var(--sdpi-background); + cursor: pointer; + border: 1px solid rgb(0,0,0,.2); +} + +input[type="radio"] + label span { + border-radius: 100%; +} + +input[type="radio"]:checked + label span, +input[type="checkbox"]:checked + label span { + background-color: #77f; + background-image: url(check.svg); + background-repeat: no-repeat; + background-position: center center; + border: 1px solid rgb(0,0,0,.4); +} + +input[type="radio"]:active:checked + label span, +input[type="radio"]:active + label span, +input[type="checkbox"]:active:checked + label span, +input[type="checkbox"]:active + label span { + background-color: #303030; +} + +input[type="radio"]:checked + label span { + background-image: url(rcheck.svg); +} + +/* +input[type="radio"] + label span { + background: url(buttons.png) -38px top no-repeat; +} + +input[type="radio"]:checked + label span { + background: url(buttons.png) -57px top no-repeat; +} +*/ + +input[type="range"] { + width: var(--sdpi-width); + height: 30px; + overflow: hidden; + cursor: pointer; + background: transparent !important; +} + +.sdpi-item > input[type="range"] { + margin-left: 2px; + max-width: var(--sdpi-width); + width: var(--sdpi-width); + padding: 0px; + margin-top: 4px; +} + +/* +input[type="range"], +input[type="range"]::-webkit-slider-runnable-track, +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; +} +*/ + +input[type="range"]::-webkit-slider-runnable-track { + height: 5px; + background: #979797; + border-radius: 3px; + padding:0px !important; + border: 1px solid var(--sdpi-background); +} + +input[type="range"]::-webkit-slider-thumb { + position: relative; + -webkit-appearance: none; + background-color: var(--sdpi-color); + width: 12px; + height: 12px; + border-radius: 20px; + margin-top: -5px; + border: none; +} +input[type="range" i]{ + margin: 0; +} + +input[type="range"]::-webkit-slider-thumb::before { + position: absolute; + content: ""; + height: 5px; /* equal to height of runnable track or 1 less */ + width: 500px; /* make this bigger than the widest range input element */ + left: -502px; /* this should be -2px - width */ + top: 8px; /* don't change this */ + background: #77f; +} + +input[type="color"] { + min-width: 32px; + min-height: 32px; + width: 32px; + height: 32px; + padding: 0; + background-color: var(--sdpi-bgcolor); + flex: none; +} + +::-webkit-color-swatch { + min-width: 24px; +} + +textarea { + height: 3em; + word-break: break-word; + line-height: 1.5em; +} + +.textarea { + padding: 0px !important; +} + +textarea { + width: 219px; /*98%;*/ + height: 96%; + min-height: 6em; + resize: none; + border-radius: var(--sdpi-borderradius); +} + +/* CAROUSEL */ + +.sdpi-item[type="carousel"]{ + +} + +.sdpi-item.card-carousel-wrapper, +.sdpi-item > .card-carousel-wrapper { + padding: 0; +} + + +.card-carousel-wrapper { + display: flex; + align-items: center; + justify-content: center; + margin: 12px auto; + color: #666a73; +} + +.card-carousel { + display: flex; + justify-content: center; + width: 278px; +} +.card-carousel--overflow-container { + overflow: hidden; +} +.card-carousel--nav__left, +.card-carousel--nav__right { + /* display: inline-block; */ + width: 12px; + height: 12px; + border-top: 2px solid #42b883; + border-right: 2px solid #42b883; + cursor: pointer; + margin: 0 4px; + transition: transform 150ms linear; +} +.card-carousel--nav__left[disabled], +.card-carousel--nav__right[disabled] { + opacity: 0.2; + border-color: black; +} +.card-carousel--nav__left { + transform: rotate(-135deg); +} +.card-carousel--nav__left:active { + transform: rotate(-135deg) scale(0.85); +} +.card-carousel--nav__right { + transform: rotate(45deg); +} +.card-carousel--nav__right:active { + transform: rotate(45deg) scale(0.85); +} +.card-carousel-cards { + display: flex; + transition: transform 150ms ease-out; + transform: translatex(0px); +} +.card-carousel-cards .card-carousel--card { + margin: 0 5px; + cursor: pointer; + /* box-shadow: 0 4px 15px 0 rgba(40, 44, 53, 0.06), 0 2px 2px 0 rgba(40, 44, 53, 0.08); */ + background-color: #fff; + border-radius: 4px; + z-index: 3; +} +.xxcard-carousel-cards .card-carousel--card:first-child { + margin-left: 0; +} +.xxcard-carousel-cards .card-carousel--card:last-child { + margin-right: 0; +} +.card-carousel-cards .card-carousel--card img { + vertical-align: bottom; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + transition: opacity 150ms linear; + width: 60px; +} +.card-carousel-cards .card-carousel--card img:hover { + opacity: 0.5; +} +.card-carousel-cards .card-carousel--card--footer { + border-top: 0; + max-width: 80px; + overflow: hidden; + display: flex; + height: 100%; + flex-direction: column; +} +.card-carousel-cards .card-carousel--card--footer p { + padding: 3px 0; + margin: 0; + margin-bottom: 2px; + font-size: 15px; + font-weight: 500; + color: #2c3e50; +} +.card-carousel-cards .card-carousel--card--footer p:nth-of-type(2) { + font-size: 12px; + font-weight: 300; + padding: 6px; + color: #666a73; +} + + +h1 { + font-size: 1.3em; + font-weight: 500; + text-align: center; + margin-bottom: 12px; +} + +::-webkit-datetime-edit { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + background: url(elg_calendar_inv.svg) no-repeat left center; + padding-right: 1em; + padding-left: 25px; + background-position: 4px 0px; + } +::-webkit-datetime-edit-fields-wrapper { + + } +::-webkit-datetime-edit-text { padding: 0 0.3em; } +::-webkit-datetime-edit-month-field { } +::-webkit-datetime-edit-day-field {} +::-webkit-datetime-edit-year-field {} +::-webkit-inner-spin-button { + + /* display: none; */ + } +::-webkit-calendar-picker-indicator { + background: transparent; + font-size: 17px; +} + +::-webkit-calendar-picker-indicator:focus { + background-color: rgba(0,0,0,0.2); +} + +input[type="date"] { + -webkit-align-items: center; + display: -webkit-inline-flex; + font-family: monospace; + overflow: hidden; + padding: 0; + -webkit-padding-start: 1px; +} + +input::-webkit-datetime-edit { + -webkit-flex: 1; + -webkit-user-modify: read-only !important; + display: inline-block; + min-width: 0; + overflow: hidden; +} + +/* +input::-webkit-datetime-edit-fields-wrapper { + -webkit-user-modify: read-only !important; + display: inline-block; + padding: 1px 0; + white-space: pre; + +} +*/ + +/* +input[type="date"] { + background-color: red; + outline: none; +} + +input[type="date"]::-webkit-clear-button { + font-size: 18px; + height: 30px; + position: relative; +} + +input[type="date"]::-webkit-inner-spin-button { + height: 28px; +} + +input[type="date"]::-webkit-calendar-picker-indicator { + font-size: 15px; +} */ + +input[type="file"] { + opacity: 0; + display: none; +} + +.sdpi-item > input[type="file"] { + opacity: 1; + display: flex; +} + +input[type="file"] + span { + display: flex; + flex: 0 1 auto; + background-color: #0000ff50; +} + +label.sdpi-file-label { + cursor: pointer; + user-select: none; + display: inline-block; + min-height: 21px !important; + height: 21px !important; + line-height: 20px; + padding: 0px 4px; + margin: auto; + margin-right: 0px; + float:right; +} + +.sdpi-file-label > label:active, +.sdpi-file-label.file:active, +label.sdpi-file-label:active, +label.sdpi-file-info:active, +input[type="file"]::-webkit-file-upload-button:active, +button:active { + background-color: var(--sdpi-color); + color:#303030; +} + +input:required:invalid, input:focus:invalid { + background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPgogICAgPHBhdGggZmlsbD0iI0Q4RDhEOCIgZD0iTTQuNSwwIEM2Ljk4NTI4MTM3LC00LjU2NTM4NzgyZS0xNiA5LDIuMDE0NzE4NjMgOSw0LjUgQzksNi45ODUyODEzNyA2Ljk4NTI4MTM3LDkgNC41LDkgQzIuMDE0NzE4NjMsOSAzLjA0MzU5MTg4ZS0xNiw2Ljk4NTI4MTM3IDAsNC41IEMtMy4wNDM1OTE4OGUtMTYsMi4wMTQ3MTg2MyAyLjAxNDcxODYzLDQuNTY1Mzg3ODJlLTE2IDQuNSwwIFogTTQsMSBMNCw2IEw1LDYgTDUsMSBMNCwxIFogTTQuNSw4IEM0Ljc3NjE0MjM3LDggNSw3Ljc3NjE0MjM3IDUsNy41IEM1LDcuMjIzODU3NjMgNC43NzYxNDIzNyw3IDQuNSw3IEM0LjIyMzg1NzYzLDcgNCw3LjIyMzg1NzYzIDQsNy41IEM0LDcuNzc2MTQyMzcgNC4yMjM4NTc2Myw4IDQuNSw4IFoiLz4KICA8L3N2Zz4) no-repeat 98% center; +} + +input:required:valid { + background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPjxwb2x5Z29uIGZpbGw9IiNEOEQ4RDgiIHBvaW50cz0iNS4yIDEgNi4yIDEgNi4yIDcgMy4yIDcgMy4yIDYgNS4yIDYiIHRyYW5zZm9ybT0icm90YXRlKDQwIDQuNjc3IDQpIi8+PC9zdmc+) no-repeat 98% center; +} + +.tooltip, +:tooltip, +:title { + color: yellow; +} + +[title]:hover { + display: flex; + align-items: center; + justify-content: center; +} + +[title]:hover::after { + content: ''; + position: absolute; + bottom: -1000px; + left: 8px; + display: none; + color: #fff; + border: 8px solid transparent; + border-bottom: 8px solid #000; +} +[title]:hover::before { +content: attr(title); + display: flex; + justify-content: center; + align-self: center; + padding: 6px 12px; + border-radius: 5px; + background: rgba(0,0,0,0.8); + color: var(--sdpi-color); + font-size: 9pt; + font-family: sans-serif; + opacity: 1; + position: absolute; + height: auto; + /* width: 50%; + left: 35%; */ + text-align: center; + bottom: 2px; + z-index: 100; + box-shadow: 0px 3px 6px rgba(0, 0, 0, .5); + /* box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); */ +} + +.sdpi-item-group.file { + width: 232px; + display: flex; + align-items: center; +} + +.sdpi-file-info { + overflow-wrap: break-word; + word-wrap: break-word; + hyphens: auto; + + min-width: 132px; + max-width: 144px; + max-height: 32px; + margin-top: 0px; + margin-left: 5px; + display: inline-block; + overflow: hidden; + padding: 6px 4px; + background-color: var(--sdpi-background); +} + + +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); +} + +::-webkit-scrollbar-thumb { + background-color: #999999; + outline: 1px solid slategrey; + border-radius: 8px; +} + +a { + color: #7397d2; +} + +.testcontainer { + display: flex; + background-color: #0000ff20; + max-width: 400px; + height: 200px; + align-content: space-evenly; +} + +input[type=range] { + -webkit-appearance: none; + /* background-color: green; */ + height:6px; + margin-top: 12px; + z-index: 0; + overflow: visible; +} + +/* +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + background-color: var(--sdpi-color); + width: 12px; + height: 12px; + border-radius: 20px; + margin-top: -6px; + border: none; +} */ + +:-webkit-slider-thumb { + -webkit-appearance: none; + background-color: var(--sdpi-color); + width: 16px; + height: 16px; + border-radius: 20px; + margin-top: -6px; + border: 1px solid #999999; +} + +.sdpi-item[type="range"] .sdpi-item-group { + display: flex; + flex-direction: column; +} + +.xxsdpi-item[type="range"] .sdpi-item-group input { + max-width: 204px; +} + +.sdpi-item[type="range"] .sdpi-item-group span { + margin-left: 0px !important; +} + +.sdpi-item[type="range"] .sdpi-item-group > .sdpi-item-child { + display: flex; + flex-direction: row; +} + +.rangeLabel { + position:absolute; + font-weight:normal; + margin-top:22px; +} + +:disabled { + color: #993333; +} + +select, +select option { + color: var(--sdpi-color); +} + +select.disabled, +select option:disabled { + color: #fd9494; + font-style: italic; +} + +.runningAppsContainer { + display: none; +} + +/* debug +div { + background-color: rgba(64,128,255,0.2); +} +*/ + +.one-line { + min-height: 1.5em; +} + +.two-lines { + min-height: 3em; +} + +.three-lines { + min-height: 4.5em; +} + +.four-lines { + min-height: 6em; +} + +.min80 > .sdpi-item-child { + min-width: 80px; +} + +.min100 > .sdpi-item-child { + min-width: 100px; +} + +.min120 > .sdpi-item-child { + min-width: 120px; +} + +.min140 > .sdpi-item-child { + min-width: 140px; +} + +.min160 > .sdpi-item-child { + min-width: 160px; +} + +.min200 > .sdpi-item-child { + min-width: 200px; +} + +.max40 { + flex-basis: 40%; + flex-grow: 0; +} + +.max30 { + flex-basis: 30%; + flex-grow: 0; +} + +.max20 { + flex-basis: 20%; + flex-grow: 0; +} + +.up20 { + margin-top: -20px; +} + +.alignCenter { + align-items: center; +} + +.alignTop { + align-items: flex-start; +} + +.alignBaseline { + align-items: baseline; +} + +.noMargins, +.noMargins *, +.noInnerMargins * { + margin: 0; + padding: 0; +} + +.hidden { + display: none; +} + +.icon-brighter, +.icon-darker, +.icon-warmer, +.icon-cooler { + margin-top: 5px !important; + min-width: 20px; + width: 20px; + background-repeat: no-repeat; +} + +.icon-brighter { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='4'/%3E%3Cpath d='M14.8532861,7.77530426 C14.7173255,7.4682615 14.5540843,7.17599221 14.3666368,6.90157083 L16.6782032,5.5669873 L17.1782032,6.4330127 L14.8532861,7.77530426 Z M10.5,4.5414007 C10.2777625,4.51407201 10.051423,4.5 9.82179677,4.5 C9.71377555,4.5 9.60648167,4.50311409 9.5,4.50925739 L9.5,2 L10.5,2 L10.5,4.5414007 Z M5.38028092,6.75545367 C5.18389364,7.02383457 5.01124349,7.31068015 4.86542112,7.61289977 L2.82179677,6.4330127 L3.32179677,5.5669873 L5.38028092,6.75545367 Z M4.86542112,12.3871002 C5.01124349,12.6893198 5.18389364,12.9761654 5.38028092,13.2445463 L3.32179677,14.4330127 L2.82179677,13.5669873 L4.86542112,12.3871002 Z M9.5,15.4907426 C9.60648167,15.4968859 9.71377555,15.5 9.82179677,15.5 C10.051423,15.5 10.2777625,15.485928 10.5,15.4585993 L10.5,18 L9.5,18 L9.5,15.4907426 Z M14.3666368,13.0984292 C14.5540843,12.8240078 14.7173255,12.5317385 14.8532861,12.2246957 L17.1782032,13.5669873 L16.6782032,14.4330127 L14.3666368,13.0984292 Z'/%3E%3C/g%3E%3C/svg%3E"); +} +.icon-darker { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 14C7.790861 14 6 12.209139 6 10 6 7.790861 7.790861 6 10 6 12.209139 6 14 7.790861 14 10 14 12.209139 12.209139 14 10 14zM10 13C11.6568542 13 13 11.6568542 13 10 13 8.34314575 11.6568542 7 10 7 8.34314575 7 7 8.34314575 7 10 7 11.6568542 8.34314575 13 10 13zM14.8532861 7.77530426C14.7173255 7.4682615 14.5540843 7.17599221 14.3666368 6.90157083L16.6782032 5.5669873 17.1782032 6.4330127 14.8532861 7.77530426zM10.5 4.5414007C10.2777625 4.51407201 10.051423 4.5 9.82179677 4.5 9.71377555 4.5 9.60648167 4.50311409 9.5 4.50925739L9.5 2 10.5 2 10.5 4.5414007zM5.38028092 6.75545367C5.18389364 7.02383457 5.01124349 7.31068015 4.86542112 7.61289977L2.82179677 6.4330127 3.32179677 5.5669873 5.38028092 6.75545367zM4.86542112 12.3871002C5.01124349 12.6893198 5.18389364 12.9761654 5.38028092 13.2445463L3.32179677 14.4330127 2.82179677 13.5669873 4.86542112 12.3871002zM9.5 15.4907426C9.60648167 15.4968859 9.71377555 15.5 9.82179677 15.5 10.051423 15.5 10.2777625 15.485928 10.5 15.4585993L10.5 18 9.5 18 9.5 15.4907426zM14.3666368 13.0984292C14.5540843 12.8240078 14.7173255 12.5317385 14.8532861 12.2246957L17.1782032 13.5669873 16.6782032 14.4330127 14.3666368 13.0984292z'/%3E%3C/g%3E%3C/svg%3E"); +} +.icon-warmer { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M12.3247275 11.4890349C12.0406216 11.0007637 11.6761954 10.5649925 11.2495475 10.1998198 11.0890394 9.83238991 11 9.42659309 11 9 11 7.34314575 12.3431458 6 14 6 15.6568542 6 17 7.34314575 17 9 17 10.6568542 15.6568542 12 14 12 13.3795687 12 12.8031265 11.8116603 12.3247275 11.4890349zM17.6232392 11.6692284C17.8205899 11.4017892 17.9890383 11.1117186 18.123974 10.8036272L20.3121778 12.0669873 19.8121778 12.9330127 17.6232392 11.6692284zM18.123974 7.19637279C17.9890383 6.88828142 17.8205899 6.5982108 17.6232392 6.33077158L19.8121778 5.0669873 20.3121778 5.9330127 18.123974 7.19637279zM14.5 4.52746439C14.3358331 4.50931666 14.1690045 4.5 14 4.5 13.8309955 4.5 13.6641669 4.50931666 13.5 4.52746439L13.5 2 14.5 2 14.5 4.52746439zM13.5 13.4725356C13.6641669 13.4906833 13.8309955 13.5 14 13.5 14.1690045 13.5 14.3358331 13.4906833 14.5 13.4725356L14.5 16 13.5 16 13.5 13.4725356zM14 11C15.1045695 11 16 10.1045695 16 9 16 7.8954305 15.1045695 7 14 7 12.8954305 7 12 7.8954305 12 9 12 10.1045695 12.8954305 11 14 11zM9.5 11C10.6651924 11.4118364 11.5 12.5 11.5 14 11.5 16 10 17.5 8 17.5 6 17.5 4.5 16 4.5 14 4.5 12.6937812 5 11.5 6.5 11L6.5 7 9.5 7 9.5 11z'/%3E%3Cpath d='M12,14 C12,16.209139 10.209139,18 8,18 C5.790861,18 4,16.209139 4,14 C4,12.5194353 4.80439726,11.2267476 6,10.5351288 L6,4 C6,2.8954305 6.8954305,2 8,2 C9.1045695,2 10,2.8954305 10,4 L10,10.5351288 C11.1956027,11.2267476 12,12.5194353 12,14 Z M11,14 C11,12.6937812 10.1651924,11.5825421 9,11.1707057 L9,4 C9,3.44771525 8.55228475,3 8,3 C7.44771525,3 7,3.44771525 7,4 L7,11.1707057 C5.83480763,11.5825421 5,12.6937812 5,14 C5,15.6568542 6.34314575,17 8,17 C9.65685425,17 11,15.6568542 11,14 Z'/%3E%3C/g%3E%3C/svg%3E"); +} + +.icon-cooler { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10.4004569 11.6239517C10.0554735 10.9863849 9.57597206 10.4322632 9 9.99963381L9 9.7450467 9.53471338 9.7450467 10.8155381 8.46422201C10.7766941 8.39376637 10.7419749 8.32071759 10.7117062 8.2454012L9 8.2454012 9 6.96057868 10.6417702 6.96057868C10.6677696 6.86753378 10.7003289 6.77722682 10.7389179 6.69018783L9.44918707 5.40045694 9 5.40045694 9 4.34532219 9.32816127 4.34532219 9.34532219 2.91912025 10.4004569 2.91912025 10.4004569 4.53471338 11.6098599 5.74411634C11.7208059 5.68343597 11.8381332 5.63296451 11.9605787 5.59396526L11.9605787 3.8884898 10.8181818 2.74609294 11.5642748 2 12.5727518 3.00847706 13.5812289 2 14.3273218 2.74609294 13.2454012 3.82801356 13.2454012 5.61756719C13.3449693 5.65339299 13.4408747 5.69689391 13.5324038 5.74735625L14.7450467 4.53471338 14.7450467 2.91912025 15.8001815 2.91912025 15.8001815 4.34532219 17.2263834 4.34532219 17.2263834 5.40045694 15.6963166 5.40045694 14.4002441 6.69652946C14.437611 6.78161093 14.4692249 6.86979146 14.4945934 6.96057868L16.2570138 6.96057868 17.3994107 5.81818182 18.1455036 6.56427476 17.1370266 7.57275182 18.1455036 8.58122888 17.3994107 9.32732182 16.3174901 8.2454012 14.4246574 8.2454012C14.3952328 8.31861737 14.3616024 8.38969062 14.3240655 8.45832192L15.6107903 9.7450467 17.2263834 9.7450467 17.2263834 10.8001815 15.8001815 10.8001815 15.8001815 12.2263834 14.7450467 12.2263834 14.7450467 10.6963166 13.377994 9.32926387C13.3345872 9.34850842 13.2903677 9.36625331 13.2454012 9.38243281L13.2454012 11.3174901 14.3273218 12.3994107 13.5812289 13.1455036 12.5848864 12.1491612 11.5642748 13.1455036 10.8181818 12.3994107 11.9605787 11.2570138 11.9605787 9.40603474C11.8936938 9.38473169 11.828336 9.36000556 11.7647113 9.33206224L10.4004569 10.6963166 10.4004569 11.6239517zM12.75 8.5C13.3022847 8.5 13.75 8.05228475 13.75 7.5 13.75 6.94771525 13.3022847 6.5 12.75 6.5 12.1977153 6.5 11.75 6.94771525 11.75 7.5 11.75 8.05228475 12.1977153 8.5 12.75 8.5zM9.5 14C8.5 16.3333333 7.33333333 17.5 6 17.5 4.66666667 17.5 3.5 16.3333333 2.5 14L9.5 14z'/%3E%3Cpath d='M10,14 C10,16.209139 8.209139,18 6,18 C3.790861,18 2,16.209139 2,14 C2,12.5194353 2.80439726,11.2267476 4,10.5351288 L4,4 C4,2.8954305 4.8954305,2 6,2 C7.1045695,2 8,2.8954305 8,4 L8,10.5351288 C9.19560274,11.2267476 10,12.5194353 10,14 Z M9,14 C9,12.6937812 8.16519237,11.5825421 7,11.1707057 L7,4 C7,3.44771525 6.55228475,3 6,3 C5.44771525,3 5,3.44771525 5,4 L5,11.1707057 C3.83480763,11.5825421 3,12.6937812 3,14 C3,15.6568542 4.34314575,17 6,17 C7.65685425,17 9,15.6568542 9,14 Z'/%3E%3C/g%3E%3C/svg%3E"); +} + +.kelvin::after { + content: "K"; +} + +.mired::after { + content: " Mired"; +} + +.percent::after { + content: "%"; +} + +.sdpi-item-value + .icon-cooler, +.sdpi-item-value + .icon-warmer { + margin-left: 0px !important; + margin-top: 15px !important; +} + +/** + CONTROL-CENTER STYLES +*/ +input[type="range"].colorbrightness::-webkit-slider-runnable-track, +input[type="range"].colortemperature::-webkit-slider-runnable-track { + height: 8px; + background: #979797; + border-radius: 4px; + background-image: linear-gradient(to right,#94d0ec, #ffb165); +} + +input[type="range"].colorbrightness::-webkit-slider-runnable-track { + background-color: #efefef; + background-image: linear-gradient(to right, black , rgba(0,0,0,0)); +} + + +input[type="range"].colorbrightness::-webkit-slider-thumb, +input[type="range"].colortemperature::-webkit-slider-thumb { + width: 16px; + height: 16px; + border-radius: 20px; + margin-top: -5px; + background-color: #86c6e8; + box-shadow: 0px 0px 1px #000000; + border: 1px solid #d8d8d8; +} +.sdpi-info-label { + display: inline-block; + user-select: none; + position: absolute; + height: 15px; + width: auto; + text-align: center; + border-radius: 4px; + min-width: 44px; + max-width: 80px; + background: white; + font-size: 11px; + color: black; + z-index: 1000; + box-shadow: 0px 0px 12px rgba(0,0,0,.8); + padding: 2px; + +} + +.sdpi-info-label.hidden { + opacity: 0; + transition: opacity 0.25s linear; +} + +.sdpi-info-label.shown { + position: absolute; + opacity: 1; + transition: opacity 0.25s ease-out; +} diff --git a/inspector/static/elgato.js b/inspector/static/elgato.js new file mode 100644 index 0000000..62b20f2 --- /dev/null +++ b/inspector/static/elgato.js @@ -0,0 +1,72 @@ +/** Stream Deck software passes system-highlight color information + * to Property Inspector. Here we 'inject' the CSS styles into the DOM + * when we receive this information. */ + + +function addDynamicStyles(clrs, fromWhere) { + const node = document.getElementById('#sdpi-dynamic-styles') || document.createElement('style'); + if (!clrs.mouseDownColor) clrs.mouseDownColor = fadeColor(clrs.highlightColor, -100); + const clr = clrs.highlightColor.slice(0, 7); + const clr1 = fadeColor(clr, 100); + const clr2 = fadeColor(clr, 60); + const metersActiveColor = fadeColor(clr, -60); + + node.setAttribute('id', 'sdpi-dynamic-styles'); + node.innerHTML = ` + input[type="radio"]:checked + label span, + input[type="checkbox"]:checked + label span { + background-color: ${clrs.highlightColor}; + } + input[type="radio"]:active:checked + label span, + input[type="radio"]:active + label span, + input[type="checkbox"]:active:checked + label span, + input[type="checkbox"]:active + label span { + background-color: ${clrs.mouseDownColor}; + } + input[type="radio"]:active + label span, + input[type="checkbox"]:active + label span { + background-color: ${clrs.buttonPressedBorderColor}; + } + td.selected, + td.selected:hover, + li.selected:hover, + li.selected { + color: white; + background-color: ${clrs.highlightColor}; + } + .sdpi-file-label > label:active, + .sdpi-file-label.file:active, + label.sdpi-file-label:active, + label.sdpi-file-info:active, + input[type="file"]::-webkit-file-upload-button:active, + button:active { + background-color: ${clrs.buttonPressedBackgroundColor}; + color: ${clrs.buttonPressedTextColor}; + border-color: ${clrs.buttonPressedBorderColor}; + } + ::-webkit-progress-value, + meter::-webkit-meter-optimum-value { + background: linear-gradient(${clr2}, ${clr1} 20%, ${clr} 45%, ${clr} 55%, ${clr2}) + } + ::-webkit-progress-value:active, + meter::-webkit-meter-optimum-value:active { + background: linear-gradient(${clr}, ${clr2} 20%, ${metersActiveColor} 45%, ${metersActiveColor} 55%, ${clr}) + } + `; + document.body.appendChild(node); +}; + +/* + Quick utility to lighten or darken a color (doesn't take color-drifting, etc. into account) + Usage: + fadeColor('#061261', 100); // will lighten the color + fadeColor('#200867'), -100); // will darken the color +*/ +function fadeColor(col, amt) { + const min = Math.min, max = Math.max; + const num = parseInt(col.replace(/#/g, ''), 16); + const r = min(255, max((num >> 16) + amt, 0)); + const g = min(255, max((num & 0x0000FF) + amt, 0)); + const b = min(255, max(((num >> 8) & 0x00FF) + amt, 0)); + return '#' + (g | (b << 8) | (r << 16)).toString(16).padStart(6, 0); +} \ No newline at end of file diff --git a/inspector/static/index.html b/inspector/static/index.html new file mode 100644 index 0000000..c5f947e --- /dev/null +++ b/inspector/static/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/manifest.json b/manifest.json index 3b2fca5..3ba6c1d 100644 --- a/manifest.json +++ b/manifest.json @@ -22,7 +22,8 @@ "Author": "Matthew Donoughe ", "Category": "sbzdeck", "CategoryIcon": "categoryIcon", - "CodePathWin": "target\\i686-pc-windows-msvc\\debug\\sbzdeck.exe", + "CodePathWin": "target\\i686-pc-windows-msvc\\debug\\plugin.exe", + "PropertyInspectorPath": "target/deploy/index.html", "Description": "Control Creative Sound Blaster devices", "Name": "sbzdeck", "Icon": "actionSpeaker", diff --git a/plugin/Cargo.toml b/plugin/Cargo.toml new file mode 100644 index 0000000..016e58b --- /dev/null +++ b/plugin/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "plugin" +version = "0.1.0" +authors = ["Matthew Donoughe "] +description = "Steam Deck plugin for Sound Blaster devices" +repository = "https://github.com/mdonoughe/sbzdeck/" +readme = "README.md" +keywords = ["stream-deck", "sound-blaster"] +license = "MIT/Apache-2.0" +edition = "2018" + +[badges] +travis-ci = { repository = "mdonoughe/sbzdeck" } + +[dependencies] +common = { path = "../common" } +futures = "0.1.25" +indexmap = { version = "1.0", features = ["serde-1"] } +sbz-switch = "3.1" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" +slog = { version = "2.4", features = ["max_level_trace", "release_max_level_info"] } +streamdeck-rs = "0.2.0" +tokio = "0.1.14" diff --git a/src/main.rs b/plugin/src/main.rs similarity index 76% rename from src/main.rs rename to plugin/src/main.rs index 2b2500a..68b4576 100644 --- a/src/main.rs +++ b/plugin/src/main.rs @@ -2,31 +2,31 @@ compile_error!("This crate must be built for x86 for compatibility with sound drivers." + "(build for i686-pc-windows-msvc or suppress this error using feature ctsndcr_ignore_arch)"); +extern crate common; extern crate indexmap; extern crate sbz_switch; extern crate serde; extern crate serde_derive; extern crate serde_json; extern crate slog; -extern crate sloggers; extern crate streamdeck_rs; extern crate tokio; -mod logger; mod sb; mod settings; mod types; -use crate::settings::SerdeCardSettings; use crate::types::*; +use common::SerdeCardSettings; use futures::prelude::*; use futures::sync::mpsc; use sb::ChangeEvent; -use slog::{crit, debug, error, info, warn, Logger}; +use slog::{crit, debug, error, info, o, warn, Drain, Logger}; use std::collections::BTreeSet; use std::env; use std::sync::{Arc, Mutex}; use std::time::Duration; +use streamdeck_rs::logging::StreamDeckDrain; use streamdeck_rs::registration::RegistrationParams; use streamdeck_rs::socket::{ConnectError, StreamDeckSocket}; use streamdeck_rs::{KeyPayload, Message, MessageOut, StatePayload}; @@ -36,9 +36,11 @@ const ACTION_SELECT_OUTPUT: &str = "io.github.mdonoughe.sbzdeck.selectOutput"; fn connect( params: &RegistrationParams, -) -> impl Future, Error = ConnectError> -{ - StreamDeckSocket::::connect( +) -> impl Future< + Item = StreamDeckSocket, + Error = ConnectError, +> { + StreamDeckSocket::::connect( params.port, params.event.to_string(), params.uuid.to_string(), @@ -181,7 +183,7 @@ fn handle_press( fn handle_message( logger: &Logger, - message: Message, + message: Message, state: &State, trigger_save: &mut mpsc::Sender<()>, ) -> Result<(), ()> { @@ -219,14 +221,86 @@ fn handle_message( } } } + Message::SendToPlugin { + action, + context, + payload, + .. + } => match payload { + FromInspector::GetFeatures => { + let available = sbz_switch::dump(&logger, None) + .ok() + .and_then(|s| s.creative) + .unwrap_or_default(); + let state = state.lock().unwrap(); + let response = available + .into_iter() + .map(|(k, v)| { + let feature_selection = state.settings.selected_parameters.get(&k); + ( + k, + v.into_iter() + .map(|(k, _)| { + let selected = feature_selection + .map(|s| s.contains(&k)) + .unwrap_or_default(); + (k, selected) + }) + .collect(), + ) + }) + .collect(); + let logger_e = logger.clone(); + tokio::spawn( + state + .out + .clone() + .send(MessageOut::SendToPropertyInspector { + action: action, + context: context, + payload: ToInspector::SetFeatures { + selected_parameters: response, + }, + }) + .map_err(move |e| error!(logger_e, "failed to queue message: {:?}", e)) + .map(|_| ()), + ); + } + FromInspector::SetFeatures { + selected_parameters, + } => { + let available = sbz_switch::dump(&logger, None) + .ok() + .and_then(|s| s.creative) + .unwrap_or_default(); + let mut state = state.lock().unwrap(); + state.settings.selected_parameters = available + .into_iter() + .filter_map(|(k, v)| { + selected_parameters.get(&k).map(|feature_selection| { + ( + k, + v.into_iter() + .filter(|(k, _)| feature_selection.contains(k)) + .map(|(k, _)| k) + .collect(), + ) + }) + }) + .collect(); + info!( + logger, + "selecting features are now {:?}", state.settings.selected_parameters + ); + let _ = trigger_save.try_send(()); + } + }, _ => {} } Ok(()) } fn main() { - let logger = logger::create(); - info!(logger, "launched {:?}", env::args().collect::>()); let params = RegistrationParams::from_args(env::args()).unwrap(); let (out_sink, out_stream) = mpsc::channel(1); @@ -237,6 +311,15 @@ fn main() { settings: CardSettings::default(), }; + let (log_sink, log_stream) = mpsc::unbounded(); + let log_task = log_stream.forward( + out_sink + .clone() + .sink_map_err(|error| panic!("failed to forward log: {:?}", error)), + ); + + let logger = slog::Logger::root(StreamDeckDrain::new(log_sink).fuse(), o!()); + match sb::get_current_profile(&logger) { Ok(Some((output, profile))) => { info!(logger, "detected current output to be {:?}", output); @@ -348,10 +431,9 @@ fn main() { Ok(()) }); - let logger_e = logger.clone(); let get_settings_context = params.uuid.clone(); let test = connect(¶ms) - .map_err(move |e| crit!(logger_e, "connection failed {:?}", e)) + .map_err(move |e| panic!("connection failed {:?}", e)) .and_then(move |s| { info!(logger, "connected!"); let (sink, stream) = s.split(); @@ -381,7 +463,12 @@ fn main() { Future::select(save.map_err(|e| panic!("{:?}", e)), events) .map(|_| ()) .map_err(|_| ()), - test.map(|_| ()).map_err(|e| panic!("{:?}", e)), + Future::select( + log_task.map(|_| ()), + test.map(|_| ()).map_err(|e| panic!("{:?}", e)), + ) + .map(|_| ()) + .map_err(|_| ()), ) .map(|_| ()) .map_err(|_| ()), diff --git a/src/sb.rs b/plugin/src/sb.rs similarity index 100% rename from src/sb.rs rename to plugin/src/sb.rs diff --git a/plugin/src/settings.rs b/plugin/src/settings.rs new file mode 100644 index 0000000..1d384ce --- /dev/null +++ b/plugin/src/settings.rs @@ -0,0 +1,100 @@ +use crate::types::*; +use common::{SerdeCardSettings, SerdeProfile, SerdeProfiles}; +use indexmap::IndexMap; +use sbz_switch::soundcore::SoundCoreParamValue; +use std::collections::BTreeMap; + +fn convert_to_soundcore( + value: BTreeMap>, +) -> IndexMap> { + value + .into_iter() + .map(|(name, params)| { + ( + name, + params + .into_iter() + .filter_map(|(name, value)| match value { + serde_json::Value::Number(n) => match n.as_i64() { + Some(n) if n < i64::from(i32::min_value()) => None, + Some(n) if n <= i64::from(i32::max_value()) => { + Some((name, SoundCoreParamValue::I32(n as i32))) + } + Some(n) if n <= i64::from(u32::max_value()) => { + Some((name, SoundCoreParamValue::U32(n as u32))) + } + Some(_) => None, + None => { + Some((name, SoundCoreParamValue::Float(n.as_f64().unwrap() as f32))) + } + }, + serde_json::Value::Bool(b) => Some((name, SoundCoreParamValue::Bool(b))), + _ => None, + }) + .collect(), + ) + }) + .collect() +} + +pub fn load(de: SerdeCardSettings) -> Result { + Ok(CardSettings { + selected_parameters: de.selected_parameters, + profiles: Profiles { + headphones: Profile { + volume: de.profiles.headphones.volume, + parameters: convert_to_soundcore(de.profiles.headphones.parameters), + }, + speakers: Profile { + volume: de.profiles.speakers.volume, + parameters: convert_to_soundcore(de.profiles.speakers.parameters), + }, + }, + }) +} + +fn convert_from_soundcore( + value: &IndexMap>, +) -> BTreeMap> { + value + .into_iter() + .map(|(name, params)| { + ( + name.to_string(), + params + .into_iter() + .filter_map(|(name, value)| match value { + SoundCoreParamValue::I32(n) => { + Some((name.to_string(), serde_json::Value::Number((*n).into()))) + } + SoundCoreParamValue::U32(n) => { + Some((name.to_string(), serde_json::Value::Number((*n).into()))) + } + SoundCoreParamValue::Float(n) => serde_json::Number::from_f64((*n).into()) + .map(|v| (name.to_string(), serde_json::Value::Number(v))), + SoundCoreParamValue::Bool(b) => { + Some((name.to_string(), serde_json::Value::Bool(*b))) + } + _ => None, + }) + .collect(), + ) + }) + .collect() +} + +pub fn prepare_for_save(settings: &CardSettings) -> SerdeCardSettings { + SerdeCardSettings { + selected_parameters: settings.selected_parameters.clone(), + profiles: SerdeProfiles { + headphones: SerdeProfile { + volume: settings.profiles.headphones.volume, + parameters: convert_from_soundcore(&settings.profiles.headphones.parameters), + }, + speakers: SerdeProfile { + volume: settings.profiles.speakers.volume, + parameters: convert_from_soundcore(&settings.profiles.speakers.parameters), + }, + }, + } +} diff --git a/src/types.rs b/plugin/src/types.rs similarity index 92% rename from src/types.rs rename to plugin/src/types.rs index d7abfdb..0a9b434 100644 --- a/src/types.rs +++ b/plugin/src/types.rs @@ -1,4 +1,5 @@ -use crate::settings::SerdeCardSettings; +use common::SerdeCardSettings; +pub use common::{FromInspector, ToInspector}; use futures::sync::mpsc; use indexmap::{IndexMap, IndexSet}; use sbz_switch::soundcore::SoundCoreParamValue; @@ -71,7 +72,7 @@ impl IndexMut for Profiles { pub struct RawState { pub output: Option, pub contexts: BTreeSet, - pub out: mpsc::Sender>, + pub out: mpsc::Sender>, pub settings: CardSettings, } diff --git a/src/logger.rs b/src/logger.rs deleted file mode 100644 index b29b769..0000000 --- a/src/logger.rs +++ /dev/null @@ -1,16 +0,0 @@ -use slog::Logger; -use sloggers::file::FileLoggerBuilder; -use sloggers::types::Severity; -use sloggers::Build; -use std::env; - -pub fn create() -> Logger { - let mut log_path = env::current_exe() - .ok() - .and_then(|mut p| if p.pop() { Some(p) } else { None }) - .unwrap_or_else(env::temp_dir); - log_path.push("sbzdeck.log"); - let mut logger = FileLoggerBuilder::new(log_path); - logger.level(Severity::Debug); - logger.build().unwrap() -} diff --git a/src/settings.rs b/src/settings.rs deleted file mode 100644 index 44a401a..0000000 --- a/src/settings.rs +++ /dev/null @@ -1,157 +0,0 @@ -use crate::types::*; -use indexmap::IndexMap; -use sbz_switch::soundcore::SoundCoreParamValue; -use serde_derive::{Deserialize, Serialize}; - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct SerdeProfile { - pub volume: Option, - #[serde(default)] - pub parameters: serde_json::Map, -} - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct SerdeProfiles { - #[serde(default)] - pub headphones: SerdeProfile, - #[serde(default)] - pub speakers: SerdeProfile, -} - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct SerdeCardSettings { - #[serde(default)] - pub selected_parameters: serde_json::Map, - #[serde(default)] - pub profiles: SerdeProfiles, -} - -fn convert_to_soundcore( - value: serde_json::Map, -) -> IndexMap> { - value - .into_iter() - .filter_map(|(name, params)| match params { - serde_json::Value::Object(params) => Some(( - name, - params - .into_iter() - .filter_map(|(name, value)| match value { - serde_json::Value::Number(n) => match n.as_i64() { - Some(n) if n < i64::from(i32::min_value()) => None, - Some(n) if n <= i64::from(i32::max_value()) => { - Some((name, SoundCoreParamValue::I32(n as i32))) - } - Some(n) if n <= i64::from(u32::max_value()) => { - Some((name, SoundCoreParamValue::U32(n as u32))) - } - Some(_) => None, - None => { - Some((name, SoundCoreParamValue::Float(n.as_f64().unwrap() as f32))) - } - }, - serde_json::Value::Bool(b) => Some((name, SoundCoreParamValue::Bool(b))), - _ => None, - }) - .collect(), - )), - _ => None, - }) - .collect() -} - -pub fn load(de: SerdeCardSettings) -> Result { - Ok(CardSettings { - selected_parameters: de - .selected_parameters - .into_iter() - .filter_map(|(name, params)| match params { - serde_json::Value::Array(params) => Some(( - name, - params - .into_iter() - .filter_map(|param| match param { - serde_json::Value::String(param) => Some(param), - _ => None, - }) - .collect(), - )), - _ => None, - }) - .collect(), - profiles: Profiles { - headphones: Profile { - volume: de.profiles.headphones.volume, - parameters: convert_to_soundcore(de.profiles.headphones.parameters), - }, - speakers: Profile { - volume: de.profiles.speakers.volume, - parameters: convert_to_soundcore(de.profiles.speakers.parameters), - }, - }, - }) -} - -fn convert_from_soundcore( - value: &IndexMap>, -) -> serde_json::Map { - value - .into_iter() - .map(|(name, params)| { - ( - name.to_string(), - serde_json::Value::Object( - params - .into_iter() - .filter_map(|(name, value)| match value { - SoundCoreParamValue::I32(n) => { - Some((name.to_string(), serde_json::Value::Number((*n).into()))) - } - SoundCoreParamValue::U32(n) => { - Some((name.to_string(), serde_json::Value::Number((*n).into()))) - } - SoundCoreParamValue::Float(n) => { - serde_json::Number::from_f64((*n).into()) - .map(|v| (name.to_string(), serde_json::Value::Number(v))) - } - SoundCoreParamValue::Bool(b) => { - Some((name.to_string(), serde_json::Value::Bool(*b))) - } - _ => None, - }) - .collect(), - ), - ) - }) - .collect() -} - -pub fn prepare_for_save(settings: &CardSettings) -> SerdeCardSettings { - SerdeCardSettings { - selected_parameters: settings - .selected_parameters - .iter() - .map(|(name, params)| { - ( - name.to_string(), - serde_json::Value::Array( - params - .iter() - .map(|param| serde_json::Value::String(param.to_string())) - .collect(), - ), - ) - }) - .collect(), - profiles: SerdeProfiles { - headphones: SerdeProfile { - volume: settings.profiles.headphones.volume, - parameters: convert_from_soundcore(&settings.profiles.headphones.parameters), - }, - speakers: SerdeProfile { - volume: settings.profiles.speakers.volume, - parameters: convert_from_soundcore(&settings.profiles.speakers.parameters), - }, - }, - } -}