split servicepoint_binding_uniffi into own crate
Some checks failed
Rust / build (push) Has been cancelled
Some checks failed
Rust / build (push) Has been cancelled
This commit is contained in:
parent
2f7a2dfd62
commit
21931f847f
94 changed files with 158 additions and 9856 deletions
|
|
@ -1 +0,0 @@
|
|||
use flake
|
||||
531
Cargo.lock
generated
531
Cargo.lock
generated
|
|
@ -8,55 +8,6 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.95"
|
||||
|
|
@ -140,7 +91,7 @@ dependencies = [
|
|||
"quote",
|
||||
"serde",
|
||||
"syn 1.0.109",
|
||||
"toml 0.5.11",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -184,12 +135,6 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
|
|
@ -202,21 +147,6 @@ dependencies = [
|
|||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.9.0"
|
||||
|
|
@ -276,25 +206,6 @@ dependencies = [
|
|||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
|
||||
dependencies = [
|
||||
"clap 4.5.26",
|
||||
"heck 0.4.1",
|
||||
"indexmap 2.7.0",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 2.0.96",
|
||||
"tempfile",
|
||||
"toml 0.8.19",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.9"
|
||||
|
|
@ -319,63 +230,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags 1.3.2",
|
||||
"clap_derive 3.2.25",
|
||||
"clap_lex 0.2.4",
|
||||
"indexmap 1.9.3",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"once_cell",
|
||||
"strsim 0.10.0",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive 4.5.24",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex 0.7.4",
|
||||
"strsim 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
|
|
@ -385,27 +262,6 @@ dependencies = [
|
|||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
|
|
@ -415,48 +271,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "extend"
|
||||
version = "1.2.0"
|
||||
|
|
@ -468,12 +282,6 @@ dependencies = [
|
|||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.35"
|
||||
|
|
@ -484,12 +292,6 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fs-err"
|
||||
version = "2.11.0"
|
||||
|
|
@ -505,27 +307,6 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
|
|
@ -549,24 +330,12 @@ version = "0.12.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
|
|
@ -576,23 +345,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
|
|
@ -600,25 +352,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.14"
|
||||
|
|
@ -634,26 +370,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lang_c"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"servicepoint_binding_c",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
|
|
@ -743,15 +465,6 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
|
|
@ -800,36 +513,6 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-lzma"
|
||||
version = "0.6.0"
|
||||
|
|
@ -840,19 +523,6 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
|
||||
dependencies = [
|
||||
"bitflags 2.7.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
|
|
@ -920,40 +590,22 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servicepoint"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93b52049be55a15fe37c13249d7f96aa8a5de56e1a41838e74a822ee8316a0c4"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"bzip2",
|
||||
"clap 4.5.26",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"rust-lzma",
|
||||
"thiserror 2.0.11",
|
||||
"tungstenite",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servicepoint_binding_c"
|
||||
version = "0.13.1"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
"servicepoint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servicepoint_binding_uniffi"
|
||||
version = "0.13.1"
|
||||
|
|
@ -965,17 +617,6 @@ dependencies = [
|
|||
"uniffi-bindgen-go",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
|
@ -1006,12 +647,6 @@ version = "0.10.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
|
|
@ -1040,20 +675,6 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
|
|
@ -1123,64 +744,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap 2.7.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"sha1",
|
||||
"thiserror 2.0.11",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
|
|
@ -1225,15 +788,15 @@ dependencies = [
|
|||
"anyhow",
|
||||
"askama 0.11.1",
|
||||
"camino",
|
||||
"clap 3.2.25",
|
||||
"clap",
|
||||
"extend",
|
||||
"fs-err",
|
||||
"heck 0.4.1",
|
||||
"heck",
|
||||
"paste",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"textwrap",
|
||||
"toml 0.5.11",
|
||||
"toml",
|
||||
"uniffi_bindgen 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-cs?rev=f68639fbc720b50ebe561ba75c66c84dc456bdce)",
|
||||
]
|
||||
|
||||
|
|
@ -1246,15 +809,15 @@ dependencies = [
|
|||
"askama 0.12.1",
|
||||
"camino",
|
||||
"cargo_metadata",
|
||||
"clap 3.2.25",
|
||||
"clap",
|
||||
"extend",
|
||||
"fs-err",
|
||||
"heck 0.4.1",
|
||||
"heck",
|
||||
"paste",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"textwrap",
|
||||
"toml 0.5.11",
|
||||
"toml",
|
||||
"uniffi_bindgen 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)",
|
||||
"uniffi_meta 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)",
|
||||
"uniffi_udl 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)",
|
||||
|
|
@ -1272,12 +835,12 @@ dependencies = [
|
|||
"fs-err",
|
||||
"glob",
|
||||
"goblin",
|
||||
"heck 0.4.1",
|
||||
"heck",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"serde",
|
||||
"textwrap",
|
||||
"toml 0.5.11",
|
||||
"toml",
|
||||
"uniffi_meta 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)",
|
||||
"uniffi_testing 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)",
|
||||
"uniffi_udl 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)",
|
||||
|
|
@ -1295,12 +858,12 @@ dependencies = [
|
|||
"fs-err",
|
||||
"glob",
|
||||
"goblin",
|
||||
"heck 0.4.1",
|
||||
"heck",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"serde",
|
||||
"textwrap",
|
||||
"toml 0.5.11",
|
||||
"toml",
|
||||
"uniffi_meta 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-cs?rev=f68639fbc720b50ebe561ba75c66c84dc456bdce)",
|
||||
"uniffi_testing 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-cs?rev=f68639fbc720b50ebe561ba75c66c84dc456bdce)",
|
||||
"uniffi_udl 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-cs?rev=f68639fbc720b50ebe561ba75c66c84dc456bdce)",
|
||||
|
|
@ -1319,11 +882,11 @@ dependencies = [
|
|||
"fs-err",
|
||||
"glob",
|
||||
"goblin",
|
||||
"heck 0.4.1",
|
||||
"heck",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"serde",
|
||||
"toml 0.5.11",
|
||||
"toml",
|
||||
"uniffi_meta 0.25.3",
|
||||
"uniffi_testing 0.25.3",
|
||||
"uniffi_udl 0.25.3",
|
||||
|
|
@ -1398,7 +961,7 @@ dependencies = [
|
|||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.96",
|
||||
"toml 0.5.11",
|
||||
"toml",
|
||||
"uniffi_build",
|
||||
"uniffi_meta 0.25.3",
|
||||
]
|
||||
|
|
@ -1508,18 +1071,6 @@ dependencies = [
|
|||
"weedle2 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
|
@ -1532,12 +1083,6 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "weedle2"
|
||||
version = "4.0.0"
|
||||
|
|
@ -1667,15 +1212,6 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
|
|
@ -1685,27 +1221,6 @@ dependencies = [
|
|||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.2"
|
||||
|
|
|
|||
69
Cargo.toml
69
Cargo.toml
|
|
@ -1,17 +1,60 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/servicepoint",
|
||||
"crates/servicepoint_binding_c",
|
||||
"crates/servicepoint_binding_c/examples/lang_c",
|
||||
"crates/servicepoint_binding_uniffi"
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
[package]
|
||||
name = "servicepoint_binding_uniffi"
|
||||
version = "0.13.1"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
description = "C bindings for the servicepoint crate."
|
||||
homepage = "https://docs.rs/crate/servicepoint_binding_c"
|
||||
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint"
|
||||
readme = "README.md"
|
||||
keywords = ["cccb", "cccb-servicepoint", "uniffi"]
|
||||
|
||||
[workspace.lints.rust]
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[build-dependencies]
|
||||
uniffi = { version = "0.25.3", features = ["build"] }
|
||||
|
||||
[dependencies]
|
||||
uniffi = { version = "0.25.3" }
|
||||
thiserror = "2.0"
|
||||
|
||||
[dependencies.servicepoint]
|
||||
version = "0.13.1"
|
||||
features = ["all_compressions"]
|
||||
|
||||
[dependencies.uniffi-bindgen-cs]
|
||||
git = "https://github.com/NordSecurity/uniffi-bindgen-cs"
|
||||
# tag="v0.8.3+v0.25.0"
|
||||
rev = "f68639fbc720b50ebe561ba75c66c84dc456bdce"
|
||||
optional = true
|
||||
|
||||
[dependencies.uniffi-bindgen-go]
|
||||
git = "https://github.com/NordSecurity/uniffi-bindgen-go.git"
|
||||
# tag = "0.2.2+v0.25.0"
|
||||
rev = "ba23bab72f1a9bcc39ce81924d3d9265598e017c"
|
||||
optional = true
|
||||
|
||||
[lints.rust]
|
||||
missing-docs = "warn"
|
||||
|
||||
[workspace.dependencies]
|
||||
thiserror = "2.0"
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[[bin]]
|
||||
name = "uniffi-bindgen"
|
||||
required-features = ["uniffi/cli"]
|
||||
|
||||
[[bin]]
|
||||
name = "uniffi-bindgen-cs"
|
||||
required-features = ["cs"]
|
||||
|
||||
[[bin]]
|
||||
name = "uniffi-bindgen-go"
|
||||
required-features = ["go"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
cs = ["dep:uniffi-bindgen-cs"]
|
||||
go = ["dep:uniffi-bindgen-go"]
|
||||
|
|
|
|||
109
README.md
109
README.md
|
|
@ -1,49 +1,90 @@
|
|||
# servicepoint
|
||||
|
||||
[](https://crates.io/crates/servicepoint)
|
||||
[](https://crates.io/crates/servicepoint)
|
||||
[](https://docs.rs/servicepoint/latest/servicepoint/)
|
||||
[](./LICENSE)
|
||||
# servicepoint-binding-uniffi
|
||||
|
||||
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
|
||||
Display" or "Airport Display".
|
||||
This repository contains a library for parsing, encoding and sending packets to this display via UDP in multiple
|
||||
programming languages.
|
||||
|
||||
This project moved to [git.berlin.ccc.de/servicepoint/servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint).
|
||||
The [GitHub repository](https://github.com/cccb/servicepoint) remains available as a mirror.
|
||||
This crate contains bindings for multiple programming languages, enabling non-rust-developers to use the library.
|
||||
|
||||
Take a look at the contained crates for language specific information:
|
||||
Also take a look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for more
|
||||
information.
|
||||
|
||||
| Crate | Languages | Readme |
|
||||
|-----------------------------|-----------------------------------|-----------------------------------------------------------------------------|
|
||||
| servicepoint | Rust | [servicepoint](crates/servicepoint/README.md) |
|
||||
| servicepoint_binding_c | C / C++ | [servicepoint_binding_c](crates/servicepoint_binding_c/README.md) |
|
||||
| servicepoint_binding_uniffi | C# / Python / Go / Kotlin / Swift | [servicepoint_binding_uniffi](crates/servicepoint_binding_uniffi/README.md) |
|
||||
## Note on stability
|
||||
|
||||
## Projects using the library
|
||||
This library is still in early development.
|
||||
You can absolutely use it, and it works, but expect minor breaking changes with every version bump.
|
||||
|
||||
- screen simulator (rust): [servicepoint-simulator](https://git.berlin.ccc.de/servicepoint/servicepoint-simulator)
|
||||
- A bunch of projects (C): [arfst23/ServicePoint](https://github.com/arfst23/ServicePoint), including
|
||||
- a CLI tool to display image files on the display or use the display as a TTY
|
||||
- a BSD games robots clone
|
||||
- a split-flap-display simulator
|
||||
- animations that play on the display
|
||||
- tanks game (C#): [servicepoint-tanks](https://github.com/kaesaecracker/cccb-tanks-cs)
|
||||
- cellular automata slideshow (rust): [servicepoint-life](https://github.com/kaesaecracker/servicepoint-life)
|
||||
- partial typescript implementation inspired by this library and browser stream: [cccb-servicepoint-browser](https://github.com/SamuelScheit/cccb-servicepoint-browser)
|
||||
- a CLI: [servicepoint-cli](https://git.berlin.ccc.de/servicepoint/servicepoint-cli)
|
||||
## Notes on differences to rust library
|
||||
|
||||
To add yourself to the list, open a pull request.
|
||||
- Performance will not be as good as the rust version:
|
||||
- most objects are reference counted.
|
||||
- objects with mutating methods will also have a MRSW lock
|
||||
- You will not get rust backtraces in release builds of the native code
|
||||
- Panic messages will work (PanicException)
|
||||
|
||||
You can also check out [awesome-servicepoint](https://github.com/stars/kaesaecracker/lists/awesome-servicepoint) for a bigger collection of projects, including some not related to this library.
|
||||
## Supported languages
|
||||
|
||||
If you have access, there is even more software linked in [the wiki](https://wiki.berlin.ccc.de/LED-Riesendisplay).
|
||||
| Language | Support level | Notes |
|
||||
|-----------|---------------|-------------------------------------------------------------------------------------------------|
|
||||
| .NET (C#) | Full | see dedicated section |
|
||||
| Ruby | Working | LD_LIBRARY_PATH has to be set, see example project |
|
||||
| Python | Tested once | Required project file not included. The shared library will be loaded from the script location. |
|
||||
| Go | untested | |
|
||||
| Kotlin | untested | |
|
||||
| Swift | untested | |
|
||||
|
||||
## Contributing
|
||||
## Installation
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
Including this repository as a submodule and building from source is the recommended way of using the library.
|
||||
|
||||
## What happened to servicepoint2?
|
||||
```bash
|
||||
git submodule add https://git.berlin.ccc.de/servicepoint/servicepoint.git
|
||||
git commit -m "add servicepoint submodule"
|
||||
```
|
||||
|
||||
After `servicepoint2` has been merged into `servicepoint`, `servicepoint2` will not continue to get any updates.
|
||||
Run `generate-bindings.sh` to regenerate all bindings. This will also build `libservicepoint.so` (or equivalent on your
|
||||
platform).
|
||||
|
||||
For languages not fully supported, there will be no project file for the library, just the naked source file(s).
|
||||
If you successfully use a language, please open an issue or PR to add the missing ones.
|
||||
|
||||
## .NET (C#)
|
||||
|
||||
This is the best supported language.
|
||||
|
||||
F# is not tested. If there are usability or functionality problems, please open an issue.
|
||||
|
||||
Currently, the project file is hard-coded for Linux and will need tweaks for other platforms (e.g. `.dylib` instead of `.so`).
|
||||
|
||||
You do not have to compile or copy the rust crate manually, as building `ServicePoint.csproj` also builds it.
|
||||
|
||||
### Example
|
||||
|
||||
```csharp
|
||||
using System.Threading;
|
||||
using ServicePoint;
|
||||
|
||||
var connection = new Connection("127.0.0.1:2342");
|
||||
connection.Send(Command.Clear());
|
||||
|
||||
connection.Send(Command.Brightness(5));
|
||||
|
||||
var pixels = Bitmap.NewMaxSized();
|
||||
for (ulong offset = 0; offset < ulong.MaxValue; offset++)
|
||||
{
|
||||
pixels.Fill(false);
|
||||
|
||||
for (ulong y = 0; y < pixels.Height(); y++)
|
||||
pixels.Set((y + offset) % pixels.Width(), y, true);
|
||||
|
||||
connection.Send(Command.BitmapLinearWin(0, 0, pixels));
|
||||
Thread.Sleep(14);
|
||||
}
|
||||
```
|
||||
|
||||
A full example including project files is available as part of this crate.
|
||||
|
||||
### Why is there no NuGet-Package?
|
||||
|
||||
NuGet packages are not a good way to distribute native
|
||||
binaries ([relevant issue](https://github.com/dotnet/sdk/issues/33845)).
|
||||
Because of that, there is no NuGet package you can use directly.
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
[package]
|
||||
name = "servicepoint"
|
||||
version.workspace = true
|
||||
publish = true
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
description = "A rust library for the CCCB Service Point Display."
|
||||
homepage = "https://docs.rs/crate/servicepoint"
|
||||
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint"
|
||||
readme = "README.md"
|
||||
keywords = ["cccb", "cccb-servicepoint"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
bitvec = "1.0"
|
||||
flate2 = { version = "1.0", optional = true }
|
||||
bzip2 = { version = "0.5", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
rust-lzma = { version = "0.6", optional = true }
|
||||
rand = { version = "0.8", optional = true }
|
||||
tungstenite = { version = "0.26", optional = true }
|
||||
once_cell = { version = "1.20", optional = true }
|
||||
thiserror.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["compression_lzma", "protocol_udp", "cp437"]
|
||||
compression_zlib = ["dep:flate2"]
|
||||
compression_bzip2 = ["dep:bzip2"]
|
||||
compression_lzma = ["dep:rust-lzma"]
|
||||
compression_zstd = ["dep:zstd"]
|
||||
all_compressions = ["compression_zlib", "compression_bzip2", "compression_lzma", "compression_zstd"]
|
||||
rand = ["dep:rand"]
|
||||
protocol_udp = []
|
||||
protocol_websocket = ["dep:tungstenite"]
|
||||
cp437 = ["dep:once_cell"]
|
||||
|
||||
[[example]]
|
||||
name = "random_brightness"
|
||||
required-features = ["rand"]
|
||||
|
||||
[[example]]
|
||||
name = "game_of_life"
|
||||
required-features = ["rand"]
|
||||
|
||||
[[example]]
|
||||
name = "websocket"
|
||||
required-features = ["protocol_websocket"]
|
||||
|
||||
[dev-dependencies]
|
||||
# for examples
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
# servicepoint
|
||||
|
||||
[](https://crates.io/crates/servicepoint)
|
||||
[](https://crates.io/crates/servicepoint)
|
||||
[](https://docs.rs/servicepoint/latest/servicepoint/)
|
||||
[](../../LICENSE)
|
||||
|
||||
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
|
||||
Display" or "Airport Display".
|
||||
This crate contains a library for parsing, encoding and sending packets to this display via UDP.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cargo add servicepoint
|
||||
```
|
||||
or
|
||||
```toml
|
||||
[dependencies]
|
||||
servicepoint = "0.13.1"
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```rust no_run
|
||||
fn main() {
|
||||
// establish connection
|
||||
let connection = servicepoint::Connection::open("172.23.42.29:2342")
|
||||
.expect("connection failed");
|
||||
|
||||
// clear screen content
|
||||
connection.send(servicepoint::Command::Clear)
|
||||
.expect("send failed");
|
||||
}
|
||||
```
|
||||
|
||||
More examples are available in the crate.
|
||||
Execute `cargo run --example` for a list of available examples and `cargo run --example <name>` to run one.
|
||||
|
||||
## Note on stability
|
||||
|
||||
This library can be used for creative project or just to play around with the display.
|
||||
A decent coverage by unit tests prevents major problems and I also test this with my own projects, which mostly use up-to-date versions.
|
||||
|
||||
That being said, the API is still being worked on.
|
||||
Expect minor breaking changes with every version bump.
|
||||
Please specify the full version including patch in your Cargo.toml until 1.0 is released.
|
||||
|
||||
## Features
|
||||
|
||||
This library has multiple optional dependencies.
|
||||
You can choose to (not) include them by toggling the related features.
|
||||
|
||||
| Name | Default | Description |
|
||||
|--------------------|---------|--------------------------------------------|
|
||||
| compression_zlib | false | Enable additional compression algo |
|
||||
| compression_bzip2 | false | Enable additional compression algo |
|
||||
| compression_lzma | true | Enable additional compression algo |
|
||||
| compression_zstd | false | Enable additional compression algo |
|
||||
| protocol_udp | true | Connection::Udp |
|
||||
| protocol_websocket | false | Connection::WebSocket |
|
||||
| rand | false | impl Distribution<Brightness> for Standard |
|
||||
| cp437 | true | Conversion to and from CP-437 |
|
||||
|
||||
## Everything else
|
||||
|
||||
Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for further information.
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
//! An example for how to send text to the display.
|
||||
|
||||
use clap::Parser;
|
||||
use servicepoint::*;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value = "localhost:2342",
|
||||
help = "Address of the display"
|
||||
)]
|
||||
destination: String,
|
||||
#[arg(short, long, num_args = 1.., value_delimiter = '\n',
|
||||
help = "Text to send - specify multiple times for multiple lines")]
|
||||
text: Vec<String>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value_t = true,
|
||||
help = "Clear screen before sending text"
|
||||
)]
|
||||
clear: bool,
|
||||
}
|
||||
|
||||
/// example: `cargo run -- --text "Hallo" --text "CCCB"`
|
||||
fn main() {
|
||||
let mut cli = Cli::parse();
|
||||
if cli.text.is_empty() {
|
||||
cli.text.push("Hello, CCCB!".to_string());
|
||||
}
|
||||
|
||||
let connection = Connection::open(&cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
if cli.clear {
|
||||
connection
|
||||
.send(Command::Clear)
|
||||
.expect("sending clear failed");
|
||||
}
|
||||
|
||||
let text = cli.text.join("\n");
|
||||
let grid = CharGrid::wrap_str(TILE_WIDTH, &text);
|
||||
connection
|
||||
.send(Command::Utf8Data(Origin::ZERO, grid))
|
||||
.expect("sending text failed");
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
//! Show a brightness level test pattern on screen
|
||||
|
||||
use clap::Parser;
|
||||
use servicepoint::*;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
let connection = Connection::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
pixels.fill(true);
|
||||
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
pixels,
|
||||
CompressionCode::Uncompressed,
|
||||
);
|
||||
connection.send(command).expect("send failed");
|
||||
|
||||
let max_brightness: u8 = Brightness::MAX.into();
|
||||
let mut brightnesses = BrightnessGrid::new(TILE_WIDTH, TILE_HEIGHT);
|
||||
for (index, byte) in brightnesses.data_ref_mut().iter_mut().enumerate() {
|
||||
let level = index as u8 % max_brightness;
|
||||
*byte = Brightness::try_from(level).unwrap();
|
||||
}
|
||||
|
||||
connection
|
||||
.send(Command::CharBrightness(Origin::ZERO, brightnesses))
|
||||
.expect("send failed");
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
//! A simple game of life implementation to show how to render graphics to the display.
|
||||
|
||||
use clap::Parser;
|
||||
use rand::{distributions, Rng};
|
||||
use servicepoint::*;
|
||||
use std::thread;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
#[arg(short, long, default_value_t = 0.5f64)]
|
||||
probability: f64,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let connection = Connection::open(&cli.destination)
|
||||
.expect("could not connect to display");
|
||||
let mut field = make_random_field(cli.probability);
|
||||
|
||||
loop {
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
field.clone(),
|
||||
CompressionCode::Lzma,
|
||||
);
|
||||
connection.send(command).expect("could not send");
|
||||
thread::sleep(FRAME_PACING);
|
||||
field = iteration(field);
|
||||
}
|
||||
}
|
||||
|
||||
fn iteration(field: Bitmap) -> Bitmap {
|
||||
let mut next = field.clone();
|
||||
for x in 0..field.width() {
|
||||
for y in 0..field.height() {
|
||||
let old_state = field.get(x, y);
|
||||
let neighbors = count_neighbors(&field, x as i32, y as i32);
|
||||
|
||||
let new_state = matches!(
|
||||
(old_state, neighbors),
|
||||
(true, 2) | (true, 3) | (false, 3)
|
||||
);
|
||||
next.set(x, y, new_state);
|
||||
}
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
fn count_neighbors(field: &Bitmap, x: i32, y: i32) -> i32 {
|
||||
let mut count = 0;
|
||||
for nx in x - 1..=x + 1 {
|
||||
for ny in y - 1..=y + 1 {
|
||||
if nx == x && ny == y {
|
||||
continue; // the cell itself does not count
|
||||
}
|
||||
|
||||
if nx < 0
|
||||
|| ny < 0
|
||||
|| nx >= field.width() as i32
|
||||
|| ny >= field.height() as i32
|
||||
{
|
||||
continue; // pixels outside the grid do not count
|
||||
}
|
||||
|
||||
if !field.get(nx as usize, ny as usize) {
|
||||
continue; // dead cells do not count
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
fn make_random_field(probability: f64) -> Bitmap {
|
||||
let mut field = Bitmap::max_sized();
|
||||
let mut rng = rand::thread_rng();
|
||||
let d = distributions::Bernoulli::new(probability).unwrap();
|
||||
for x in 0..field.width() {
|
||||
for y in 0..field.height() {
|
||||
field.set(x, y, rng.sample(d));
|
||||
}
|
||||
}
|
||||
field
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
//! A simple example for how to send pixel data to the display.
|
||||
|
||||
use clap::Parser;
|
||||
use servicepoint::*;
|
||||
use std::thread;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let connection = Connection::open(Cli::parse().destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
for x_offset in 0..usize::MAX {
|
||||
pixels.fill(false);
|
||||
|
||||
for y in 0..PIXEL_HEIGHT {
|
||||
pixels.set((y + x_offset) % PIXEL_WIDTH, y, true);
|
||||
}
|
||||
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
pixels.clone(),
|
||||
CompressionCode::Lzma,
|
||||
);
|
||||
connection.send(command).expect("send failed");
|
||||
thread::sleep(FRAME_PACING);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
//! A simple example for how to set brightnesses for tiles on the screen.
|
||||
//! Continuously changes the tiles in a random window to random brightnesses.
|
||||
|
||||
use clap::Parser;
|
||||
use rand::Rng;
|
||||
use servicepoint::*;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
#[arg(short, long, default_value_t = true)]
|
||||
enable_all: bool,
|
||||
#[arg(short, long, default_value_t = 100, allow_negative_numbers = false)]
|
||||
wait_ms: u64,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let connection = Connection::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
let wait_duration = Duration::from_millis(cli.wait_ms);
|
||||
|
||||
// put all pixels in on state
|
||||
if cli.enable_all {
|
||||
let mut filled_grid = Bitmap::max_sized();
|
||||
filled_grid.fill(true);
|
||||
|
||||
let command = Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
filled_grid,
|
||||
CompressionCode::Lzma,
|
||||
);
|
||||
connection.send(command).expect("send failed");
|
||||
}
|
||||
|
||||
// set all pixels to the same random brightness
|
||||
let mut rng = rand::thread_rng();
|
||||
connection.send(Command::Brightness(rng.gen())).unwrap();
|
||||
|
||||
// continuously update random windows to new random brightness
|
||||
loop {
|
||||
let min_size = 1;
|
||||
let x = rng.gen_range(0..TILE_WIDTH - min_size);
|
||||
let y = rng.gen_range(0..TILE_HEIGHT - min_size);
|
||||
|
||||
let w = rng.gen_range(min_size..=TILE_WIDTH - x);
|
||||
let h = rng.gen_range(min_size..=TILE_HEIGHT - y);
|
||||
|
||||
let origin = Origin::new(x, y);
|
||||
let mut luma = BrightnessGrid::new(w, h);
|
||||
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
luma.set(x, y, rng.gen());
|
||||
}
|
||||
}
|
||||
|
||||
connection
|
||||
.send(Command::CharBrightness(origin, luma))
|
||||
.unwrap();
|
||||
std::thread::sleep(wait_duration);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
//! Example for how to use the WebSocket connection
|
||||
|
||||
use servicepoint::{
|
||||
Bitmap, Command, CompressionCode, Connection, Grid, Origin,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let connection =
|
||||
Connection::open_websocket("ws://localhost:8080".parse().unwrap())
|
||||
.unwrap();
|
||||
|
||||
connection.send(Command::Clear).unwrap();
|
||||
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
pixels.fill(true);
|
||||
|
||||
connection
|
||||
.send(Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
pixels,
|
||||
CompressionCode::Lzma,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
//! An example on how to modify the image on screen without knowing the current content.
|
||||
use clap::Parser;
|
||||
use servicepoint::*;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
#[arg(short, long = "duration-ms", default_value_t = 5000)]
|
||||
time: u64,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let sleep_duration = Duration::max(
|
||||
FRAME_PACING,
|
||||
Duration::from_millis(cli.time / PIXEL_WIDTH as u64),
|
||||
);
|
||||
|
||||
let connection = Connection::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut enabled_pixels = Bitmap::max_sized();
|
||||
enabled_pixels.fill(true);
|
||||
|
||||
for x_offset in 0..PIXEL_WIDTH {
|
||||
for y in 0..PIXEL_HEIGHT {
|
||||
enabled_pixels.set(x_offset % PIXEL_WIDTH, y, false);
|
||||
}
|
||||
|
||||
connection
|
||||
.send(Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
enabled_pixels.clone(),
|
||||
CompressionCode::Lzma,
|
||||
))
|
||||
.expect("could not send command to display");
|
||||
thread::sleep(sleep_duration);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/// A byte-packed vector of booleans.
|
||||
///
|
||||
/// The implementation is provided by [bitvec].
|
||||
/// This is an alias for the specific type of [bitvec::BitVec] used in this crate.
|
||||
pub type BitVec = bitvec::BitVec<u8, bitvec::Msb0>;
|
||||
|
||||
pub mod bitvec {
|
||||
//! Re-export of the used library [mod@bitvec].
|
||||
pub use bitvec::prelude::*;
|
||||
}
|
||||
|
|
@ -1,379 +0,0 @@
|
|||
use crate::data_ref::DataRef;
|
||||
use crate::BitVec;
|
||||
use crate::*;
|
||||
use ::bitvec::order::Msb0;
|
||||
use ::bitvec::prelude::BitSlice;
|
||||
use ::bitvec::slice::IterMut;
|
||||
|
||||
/// A fixed-size 2D grid of booleans.
|
||||
///
|
||||
/// The values are stored in packed bytes (8 values per byte) in the same order as used by the display for storing pixels.
|
||||
/// This means that no conversion is necessary for sending the data to the display.
|
||||
/// The downside is that the width has to be a multiple of 8.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use servicepoint::Bitmap;
|
||||
/// let mut bitmap = Bitmap::new(8, 2);
|
||||
///
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Bitmap {
|
||||
width: usize,
|
||||
height: usize,
|
||||
bit_vec: BitVec,
|
||||
}
|
||||
|
||||
impl Bitmap {
|
||||
/// Creates a new [Bitmap] with the specified dimensions.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `width`: size in pixels in x-direction
|
||||
/// - `height`: size in pixels in y-direction
|
||||
///
|
||||
/// returns: [Bitmap] initialized to all pixels off
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - when the width is not dividable by 8
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
assert_eq!(
|
||||
width % 8,
|
||||
0,
|
||||
"width must be a multiple of 8, but is {width}"
|
||||
);
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
bit_vec: BitVec::repeat(false, width * height),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new pixel grid with the size of the whole screen.
|
||||
#[must_use]
|
||||
pub fn max_sized() -> Self {
|
||||
Self::new(PIXEL_WIDTH, PIXEL_HEIGHT)
|
||||
}
|
||||
|
||||
/// Loads a [Bitmap] with the specified dimensions from the provided data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `width`: size in pixels in x-direction
|
||||
/// - `height`: size in pixels in y-direction
|
||||
///
|
||||
/// returns: [Bitmap] that contains a copy of the provided data
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - when the dimensions and data size do not match exactly.
|
||||
/// - when the width is not dividable by 8
|
||||
#[must_use]
|
||||
pub fn load(width: usize, height: usize, data: &[u8]) -> Self {
|
||||
assert_eq!(width % 8, 0, "width must be a multiple of 8, but is {width}");
|
||||
assert_eq!(data.len(), height * width / 8, "data length must match dimensions, with 8 pixels per byte.");
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
bit_vec: BitVec::from_slice(data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [Bitmap] with the specified width from the provided [BitVec] without copying it.
|
||||
///
|
||||
/// returns: [Bitmap] that contains the provided data.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - when the bitvec size is not dividable by the provided width
|
||||
/// - when the width is not dividable by 8
|
||||
#[must_use]
|
||||
pub fn from_bitvec(width: usize, bit_vec: BitVec) -> Self {
|
||||
assert_eq!(width % 8, 0, "width must be a multiple of 8, but is {width}");
|
||||
let len = bit_vec.len();
|
||||
let height = len / width;
|
||||
assert_eq!(0, len % width, "dimension mismatch - len {len} is not dividable by {width}");
|
||||
Self { width, height, bit_vec }
|
||||
}
|
||||
|
||||
/// Iterate over all cells in [Bitmap].
|
||||
///
|
||||
/// Order is equivalent to the following loop:
|
||||
/// ```
|
||||
/// # use servicepoint::{Bitmap, Grid};
|
||||
/// # let grid = Bitmap::new(8,2);
|
||||
/// for y in 0..grid.height() {
|
||||
/// for x in 0..grid.width() {
|
||||
/// grid.get(x, y);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn iter(&self) -> impl Iterator<Item = &bool> {
|
||||
self.bit_vec.iter().by_refs()
|
||||
}
|
||||
|
||||
/// Iterate over all cells in [Bitmap] mutably.
|
||||
///
|
||||
/// Order is equivalent to the following loop:
|
||||
/// ```
|
||||
/// # use servicepoint::{Bitmap, Grid};
|
||||
/// # let mut grid = Bitmap::new(8,2);
|
||||
/// # let value = false;
|
||||
/// for y in 0..grid.height() {
|
||||
/// for x in 0..grid.width() {
|
||||
/// grid.set(x, y, value);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use servicepoint::{Bitmap, Grid};
|
||||
/// # let mut grid = Bitmap::new(8,2);
|
||||
/// # let value = false;
|
||||
/// for (index, mut pixel) in grid.iter_mut().enumerate() {
|
||||
/// pixel.set(index % 2 == 0)
|
||||
/// }
|
||||
/// ```
|
||||
pub fn iter_mut(&mut self) -> IterMut<u8, Msb0> {
|
||||
self.bit_vec.iter_mut()
|
||||
}
|
||||
|
||||
/// Iterate over all rows in [Bitmap] top to bottom.
|
||||
pub fn iter_rows(&self) -> IterRows {
|
||||
IterRows {
|
||||
bitmap: self,
|
||||
row: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Grid<bool> for Bitmap {
|
||||
/// Sets the value of the specified position in the [Bitmap].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `x` and `y`: position of the cell
|
||||
/// - `value`: the value to write to the cell
|
||||
///
|
||||
/// returns: old value of the cell
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// When accessing `x` or `y` out of bounds.
|
||||
fn set(&mut self, x: usize, y: usize, value: bool) {
|
||||
self.assert_in_bounds(x, y);
|
||||
self.bit_vec.set(x + y * self.width, value)
|
||||
}
|
||||
|
||||
fn get(&self, x: usize, y: usize) -> bool {
|
||||
self.assert_in_bounds(x, y);
|
||||
self.bit_vec[x + y * self.width]
|
||||
}
|
||||
|
||||
/// Sets the state of all pixels in the [Bitmap].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `this`: instance to write to
|
||||
/// - `value`: the value to set all pixels to
|
||||
fn fill(&mut self, value: bool) {
|
||||
self.bit_vec.fill(value);
|
||||
}
|
||||
|
||||
fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
|
||||
impl DataRef<u8> for Bitmap {
|
||||
fn data_ref_mut(&mut self) -> &mut [u8] {
|
||||
self.bit_vec.as_raw_mut_slice()
|
||||
}
|
||||
|
||||
fn data_ref(&self) -> &[u8] {
|
||||
self.bit_vec.as_raw_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bitmap> for Vec<u8> {
|
||||
/// Turns a [Bitmap] into the underlying [`Vec<u8>`].
|
||||
fn from(value: Bitmap) -> Self {
|
||||
value.bit_vec.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bitmap> for BitVec {
|
||||
/// Turns a [Bitmap] into the underlying [BitVec].
|
||||
fn from(value: Bitmap) -> Self {
|
||||
value.bit_vec
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ValueGrid<bool>> for Bitmap {
|
||||
/// Converts a grid of [bool]s into a [Bitmap].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - when the width of `value` is not dividable by 8
|
||||
fn from(value: &ValueGrid<bool>) -> Self {
|
||||
let mut result = Self::new(value.width(), value.height());
|
||||
for (mut to, from) in result.iter_mut().zip(value.iter()) {
|
||||
*to = *from;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Bitmap> for ValueGrid<bool> {
|
||||
/// Converts a [Bitmap] into a grid of [bool]s.
|
||||
fn from(value: &Bitmap) -> Self {
|
||||
let mut result = Self::new(value.width(), value.height());
|
||||
for (to, from) in result.iter_mut().zip(value.iter()) {
|
||||
*to = *from;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IterRows<'t> {
|
||||
bitmap: &'t Bitmap,
|
||||
row: usize,
|
||||
}
|
||||
|
||||
impl<'t> Iterator for IterRows<'t> {
|
||||
type Item = &'t BitSlice<u8, Msb0>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.row >= self.bitmap.height {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = self.row * self.bitmap.width;
|
||||
let end = start + self.bitmap.width;
|
||||
self.row += 1;
|
||||
Some(&self.bitmap.bit_vec[start..end])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{BitVec, Bitmap, DataRef, Grid, ValueGrid};
|
||||
|
||||
#[test]
|
||||
fn fill() {
|
||||
let mut grid = Bitmap::new(8, 2);
|
||||
assert_eq!(grid.data_ref(), [0x00, 0x00]);
|
||||
|
||||
grid.fill(true);
|
||||
assert_eq!(grid.data_ref(), [0xFF, 0xFF]);
|
||||
|
||||
grid.fill(false);
|
||||
assert_eq!(grid.data_ref(), [0x00, 0x00]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_set() {
|
||||
let mut grid = Bitmap::new(8, 2);
|
||||
assert!(!grid.get(0, 0));
|
||||
assert!(!grid.get(1, 1));
|
||||
|
||||
grid.set(5, 0, true);
|
||||
grid.set(1, 1, true);
|
||||
assert_eq!(grid.data_ref(), [0x04, 0x40]);
|
||||
|
||||
assert!(grid.get(5, 0));
|
||||
assert!(grid.get(1, 1));
|
||||
assert!(!grid.get(1, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load() {
|
||||
let mut grid = Bitmap::new(8, 3);
|
||||
for x in 0..grid.width {
|
||||
for y in 0..grid.height {
|
||||
grid.set(x, y, (x + y) % 2 == 0);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(grid.data_ref(), [0xAA, 0x55, 0xAA]);
|
||||
|
||||
let data: Vec<u8> = grid.into();
|
||||
|
||||
let grid = Bitmap::load(8, 3, &data);
|
||||
assert_eq!(grid.data_ref(), [0xAA, 0x55, 0xAA]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn out_of_bounds_x() {
|
||||
let vec = Bitmap::new(8, 2);
|
||||
vec.get(8, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn out_of_bounds_y() {
|
||||
let mut vec = Bitmap::new(8, 2);
|
||||
vec.set(1, 2, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
let grid = Bitmap::new(8, 2);
|
||||
assert_eq!(16, grid.iter().count())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_rows() {
|
||||
let grid = Bitmap::load(8, 2, &[0x04, 0x40]);
|
||||
let mut iter = grid.iter_rows();
|
||||
|
||||
assert_eq!(iter.next().unwrap().count_ones(), 1);
|
||||
assert_eq!(iter.next().unwrap().count_ones(), 1);
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_mut() {
|
||||
let mut grid = Bitmap::new(8, 2);
|
||||
for (index, mut pixel) in grid.iter_mut().enumerate() {
|
||||
pixel.set(index % 2 == 0);
|
||||
}
|
||||
assert_eq!(grid.data_ref(), [0xAA, 0xAA]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_ref_mut() {
|
||||
let mut grid = Bitmap::new(8, 2);
|
||||
let data = grid.data_ref_mut();
|
||||
data[1] = 0x0F;
|
||||
assert!(grid.get(7, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_bitvec() {
|
||||
let mut grid = Bitmap::new(8, 2);
|
||||
grid.set(0, 0, true);
|
||||
let bitvec: BitVec = grid.into();
|
||||
assert_eq!(bitvec.as_raw_slice(), [0x80, 0x00]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_bool_grid() {
|
||||
let original = ValueGrid::load(
|
||||
8,
|
||||
1,
|
||||
&[true, false, true, false, true, false, true, false],
|
||||
);
|
||||
let converted = Bitmap::from(&original);
|
||||
let reconverted = ValueGrid::from(&converted);
|
||||
assert_eq!(original, reconverted);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
#[cfg(feature = "rand")]
|
||||
use rand::{
|
||||
distributions::{Distribution, Standard},
|
||||
Rng,
|
||||
};
|
||||
|
||||
/// A display brightness value, checked for correct value range
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use servicepoint::{Brightness, Command, Connection};
|
||||
/// let b = Brightness::MAX;
|
||||
/// let val: u8 = b.into();
|
||||
///
|
||||
/// let b = Brightness::try_from(7).unwrap();
|
||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
||||
/// let result = connection.send(Command::Brightness(b));
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub struct Brightness(u8);
|
||||
|
||||
impl From<Brightness> for u8 {
|
||||
fn from(brightness: Brightness) -> Self {
|
||||
Self::from(&brightness)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Brightness> for u8 {
|
||||
fn from(brightness: &Brightness) -> Self {
|
||||
brightness.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Brightness {
|
||||
type Error = u8;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
if value > Self::MAX.0 {
|
||||
Err(value)
|
||||
} else {
|
||||
Ok(Brightness(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Brightness {
|
||||
/// highest possible brightness value, 11
|
||||
pub const MAX: Brightness = Brightness(11);
|
||||
/// lowest possible brightness value, 0
|
||||
pub const MIN: Brightness = Brightness(0);
|
||||
|
||||
/// Create a brightness value without returning an error for brightnesses above [Brightness::MAX].
|
||||
///
|
||||
/// returns: the specified value as a [Brightness], or [Brightness::MAX].
|
||||
pub fn saturating_from(value: u8) -> Brightness {
|
||||
if value > Brightness::MAX.into() {
|
||||
Brightness::MAX
|
||||
} else {
|
||||
Brightness(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Brightness {
|
||||
fn default() -> Self {
|
||||
Self::MAX
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rand")]
|
||||
impl Distribution<Brightness> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Brightness {
|
||||
Brightness(rng.gen_range(Brightness::MIN.0..=Brightness::MAX.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn brightness_from_u8() {
|
||||
assert_eq!(Err(100), Brightness::try_from(100));
|
||||
assert_eq!(Ok(Brightness(1)), Brightness::try_from(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rand")]
|
||||
fn rand_brightness() {
|
||||
let mut rng = rand::thread_rng();
|
||||
for _ in 0..100 {
|
||||
let _: Brightness = rng.gen();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saturating_convert() {
|
||||
assert_eq!(Brightness::MAX, Brightness::saturating_from(100));
|
||||
assert_eq!(Brightness(5), Brightness::saturating_from(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rand")]
|
||||
fn test() {
|
||||
let mut rng = rand::thread_rng();
|
||||
assert_ne!(rng.gen::<Brightness>(), rng.gen());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
use crate::brightness::Brightness;
|
||||
use crate::grid::Grid;
|
||||
use crate::value_grid::ValueGrid;
|
||||
use crate::ByteGrid;
|
||||
|
||||
/// A grid containing brightness values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Connection, Grid, Origin};
|
||||
/// let mut grid = BrightnessGrid::new(2,2);
|
||||
/// grid.set(0, 0, Brightness::MIN);
|
||||
/// grid.set(1, 1, Brightness::MIN);
|
||||
///
|
||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
||||
/// connection.send(Command::CharBrightness(Origin::new(3, 7), grid)).unwrap()
|
||||
/// ```
|
||||
pub type BrightnessGrid = ValueGrid<Brightness>;
|
||||
|
||||
impl BrightnessGrid {
|
||||
/// Like [Self::load], but ignoring any out-of-range brightness values
|
||||
pub fn saturating_load(width: usize, height: usize, data: &[u8]) -> Self {
|
||||
ValueGrid::load(width, height, data).map(Brightness::saturating_from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BrightnessGrid> for Vec<u8> {
|
||||
fn from(value: BrightnessGrid) -> Self {
|
||||
value
|
||||
.iter()
|
||||
.map(|brightness| (*brightness).into())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BrightnessGrid> for ByteGrid {
|
||||
fn from(value: &BrightnessGrid) -> Self {
|
||||
let u8s = value
|
||||
.iter()
|
||||
.map(|brightness| (*brightness).into())
|
||||
.collect::<Vec<u8>>();
|
||||
ValueGrid::load(value.width(), value.height(), &u8s)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ByteGrid> for BrightnessGrid {
|
||||
type Error = u8;
|
||||
|
||||
fn try_from(value: ByteGrid) -> Result<Self, Self::Error> {
|
||||
let brightnesses = value
|
||||
.iter()
|
||||
.map(|b| Brightness::try_from(*b))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(BrightnessGrid::load(
|
||||
value.width(),
|
||||
value.height(),
|
||||
&brightnesses,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::value_grid::ValueGrid;
|
||||
use crate::{Brightness, BrightnessGrid, DataRef, Grid};
|
||||
|
||||
#[test]
|
||||
fn to_u8_grid() {
|
||||
let mut grid = BrightnessGrid::new(2, 2);
|
||||
grid.set(1, 0, Brightness::MIN);
|
||||
grid.set(0, 1, Brightness::MAX);
|
||||
let actual = ValueGrid::from(&grid);
|
||||
assert_eq!(actual.data_ref(), &[11, 0, 11, 11]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saturating_load() {
|
||||
assert_eq!(
|
||||
BrightnessGrid::load(
|
||||
2,
|
||||
2,
|
||||
&[
|
||||
Brightness::MAX,
|
||||
Brightness::MAX,
|
||||
Brightness::MIN,
|
||||
Brightness::MAX
|
||||
]
|
||||
),
|
||||
BrightnessGrid::saturating_load(2, 2, &[255u8, 23, 0, 42])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
use crate::ValueGrid;
|
||||
|
||||
/// A 2d grid of bytes - see [ValueGrid].
|
||||
pub type ByteGrid = ValueGrid<u8>;
|
||||
|
|
@ -1,298 +0,0 @@
|
|||
use crate::{Grid, SetValueSeriesError, TryLoadValueGridError, ValueGrid};
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
/// A grid containing UTF-8 characters.
|
||||
///
|
||||
/// To send a CharGrid to the display, use [Command::Utf8Data](crate::Command::Utf8Data).
|
||||
///
|
||||
/// Also see [ValueGrid] for the non-specialized operations and examples.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{CharGrid, Command, Connection, Origin};
|
||||
/// let grid = CharGrid::from("You can\nload multiline\nstrings directly");
|
||||
/// assert_eq!(grid.get_row_str(1), Some("load multiline\0\0".to_string()));
|
||||
///
|
||||
/// # let connection = Connection::Fake;
|
||||
/// let command = Command::Utf8Data(Origin::ZERO, grid);
|
||||
/// ```
|
||||
pub type CharGrid = ValueGrid<char>;
|
||||
|
||||
impl CharGrid {
|
||||
/// Loads a [CharGrid] with the specified width from the provided text, wrapping to as many rows as needed.
|
||||
///
|
||||
/// The passed rows are extended with '\0' if needed.
|
||||
///
|
||||
/// returns: [CharGrid] that contains a copy of the provided data.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use servicepoint::CharGrid;
|
||||
/// let grid = CharGrid::wrap_str(2, "abc\ndef");
|
||||
/// ```
|
||||
pub fn wrap_str(width: usize, text: &str) -> Self {
|
||||
let lines = text
|
||||
.split('\n')
|
||||
.flat_map(move |x| {
|
||||
x.chars()
|
||||
.collect::<Vec<char>>()
|
||||
.chunks(width)
|
||||
.map(|c| {
|
||||
let mut s = String::from_iter(c);
|
||||
s.push_str(&"\0".repeat(width - s.chars().count()));
|
||||
s
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
let height = lines.len();
|
||||
let mut result = Self::new(width, height);
|
||||
for (row, text_line) in lines.iter().enumerate() {
|
||||
result.set_row_str(row, text_line).unwrap()
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Copies a column from the grid as a String.
|
||||
///
|
||||
/// Returns [None] if x is out of bounds.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use servicepoint::CharGrid;
|
||||
/// let grid = CharGrid::from("ab\ncd");
|
||||
/// let col = grid.get_col_str(0).unwrap(); // "ac"
|
||||
/// ```
|
||||
pub fn get_col_str(&self, x: usize) -> Option<String> {
|
||||
Some(String::from_iter(self.get_col(x)?))
|
||||
}
|
||||
|
||||
/// Copies a row from the grid as a String.
|
||||
///
|
||||
/// Returns [None] if y is out of bounds.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use servicepoint::CharGrid;
|
||||
/// let grid = CharGrid::from("ab\ncd");
|
||||
/// let row = grid.get_row_str(0).unwrap(); // "ab"
|
||||
/// ```
|
||||
pub fn get_row_str(&self, y: usize) -> Option<String> {
|
||||
Some(String::from_iter(self.get_row(y)?))
|
||||
}
|
||||
|
||||
/// Overwrites a row in the grid with a str.
|
||||
///
|
||||
/// Returns [SetValueSeriesError] if y is out of bounds or `row` is not of the correct size.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use servicepoint::CharGrid;
|
||||
/// let mut grid = CharGrid::from("ab\ncd");
|
||||
/// grid.set_row_str(0, "ef").unwrap();
|
||||
/// ```
|
||||
pub fn set_row_str(
|
||||
&mut self,
|
||||
y: usize,
|
||||
value: &str,
|
||||
) -> Result<(), SetValueSeriesError> {
|
||||
self.set_row(y, value.chars().collect::<Vec<_>>().as_ref())
|
||||
}
|
||||
|
||||
/// Overwrites a column in the grid with a str.
|
||||
///
|
||||
/// Returns [SetValueSeriesError] if y is out of bounds or `row` is not of the correct size.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use servicepoint::CharGrid;
|
||||
/// let mut grid = CharGrid::from("ab\ncd");
|
||||
/// grid.set_col_str(0, "ef").unwrap();
|
||||
/// ```
|
||||
pub fn set_col_str(
|
||||
&mut self,
|
||||
x: usize,
|
||||
value: &str,
|
||||
) -> Result<(), SetValueSeriesError> {
|
||||
self.set_col(x, value.chars().collect::<Vec<_>>().as_ref())
|
||||
}
|
||||
|
||||
/// Loads a [CharGrid] with the specified dimensions from the provided UTF-8 bytes.
|
||||
///
|
||||
/// returns: [CharGrid] that contains the provided data, or [FromUtf8Error] if the data is invalid.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use servicepoint::CharGrid;
|
||||
/// let grid = CharGrid::load_utf8(2, 2, [97u8, 98, 99, 100].to_vec());
|
||||
/// ```
|
||||
pub fn load_utf8(
|
||||
width: usize,
|
||||
height: usize,
|
||||
bytes: Vec<u8>,
|
||||
) -> Result<CharGrid, LoadUtf8Error> {
|
||||
let s: Vec<char> = String::from_utf8(bytes)?.chars().collect();
|
||||
Ok(CharGrid::try_load(width, height, s)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LoadUtf8Error {
|
||||
#[error(transparent)]
|
||||
FromUtf8Error(#[from] FromUtf8Error),
|
||||
#[error(transparent)]
|
||||
TryLoadError(#[from] TryLoadValueGridError),
|
||||
}
|
||||
|
||||
impl From<&str> for CharGrid {
|
||||
fn from(value: &str) -> Self {
|
||||
let value = value.replace("\r\n", "\n");
|
||||
let mut lines = value.split('\n').collect::<Vec<_>>();
|
||||
let width = lines
|
||||
.iter()
|
||||
.fold(0, move |a, x| std::cmp::max(a, x.chars().count()));
|
||||
|
||||
while lines.last().is_some_and(move |line| line.is_empty()) {
|
||||
_ = lines.pop();
|
||||
}
|
||||
|
||||
let mut grid = Self::new(width, lines.len());
|
||||
for (y, line) in lines.iter().enumerate() {
|
||||
for (x, char) in line.chars().enumerate() {
|
||||
grid.set(x, y, char);
|
||||
}
|
||||
}
|
||||
|
||||
grid
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for CharGrid {
|
||||
fn from(value: String) -> Self {
|
||||
CharGrid::from(&*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CharGrid> for String {
|
||||
fn from(grid: CharGrid) -> Self {
|
||||
String::from(&grid)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CharGrid> for String {
|
||||
/// Converts a [CharGrid] into a [String].
|
||||
///
|
||||
/// Rows are separated by '\n'.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::CharGrid;
|
||||
/// let grid = CharGrid::from("ab\ncd");
|
||||
/// let string = String::from(grid);
|
||||
/// let grid = CharGrid::from(string);
|
||||
/// ```
|
||||
fn from(value: &CharGrid) -> Self {
|
||||
value
|
||||
.iter_rows()
|
||||
.map(String::from_iter)
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CharGrid> for Vec<u8> {
|
||||
/// Converts a [CharGrid] into a [`Vec<u8>`].
|
||||
///
|
||||
/// Rows are not separated.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{CharGrid, Grid};
|
||||
/// let grid = CharGrid::from("ab\ncd");
|
||||
/// let height = grid.height();
|
||||
/// let width = grid.width();
|
||||
/// let grid = CharGrid::load_utf8(width, height, grid.into());
|
||||
/// ```
|
||||
fn from(value: &CharGrid) -> Self {
|
||||
String::from_iter(value.iter()).into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CharGrid> for Vec<u8> {
|
||||
/// See [`From<&CharGrid>::from`].
|
||||
fn from(value: CharGrid) -> Self {
|
||||
Self::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn col_str() {
|
||||
let mut grid = CharGrid::new(2, 3);
|
||||
assert_eq!(grid.get_col_str(2), None);
|
||||
assert_eq!(grid.get_col_str(1), Some(String::from("\0\0\0")));
|
||||
assert_eq!(grid.set_col_str(1, "abc"), Ok(()));
|
||||
assert_eq!(grid.get_col_str(1), Some(String::from("abc")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_str() {
|
||||
let mut grid = CharGrid::new(2, 3);
|
||||
assert_eq!(grid.get_row_str(3), None);
|
||||
assert_eq!(grid.get_row_str(1), Some(String::from("\0\0")));
|
||||
assert_eq!(
|
||||
grid.set_row_str(1, "abc"),
|
||||
Err(SetValueSeriesError::InvalidLength {
|
||||
expected: 2,
|
||||
actual: 3
|
||||
})
|
||||
);
|
||||
assert_eq!(grid.set_row_str(1, "ab"), Ok(()));
|
||||
assert_eq!(grid.get_row_str(1), Some(String::from("ab")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_to_char_grid() {
|
||||
// conversion with .to_string() covers one more line
|
||||
let original = "Hello\r\nWorld!\n...\n".to_string();
|
||||
|
||||
let grid = CharGrid::from(original);
|
||||
assert_eq!(3, grid.height());
|
||||
assert_eq!("Hello\0\nWorld!\n...\0\0\0", String::from(grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_bytes() {
|
||||
let grid = CharGrid::from("Hello\0\nWorld!\n...\0\0\0");
|
||||
let bytes: Vec<u8> = grid.clone().into();
|
||||
let copy =
|
||||
CharGrid::load_utf8(grid.width(), grid.height(), bytes).unwrap();
|
||||
assert_eq!(grid, copy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_string() {
|
||||
let grid = CharGrid::from("Hello\0\nWorld!\n...\0\0\0");
|
||||
let str: String = grid.clone().into();
|
||||
let copy = CharGrid::from(str);
|
||||
assert_eq!(grid, copy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_str() {
|
||||
let grid = CharGrid::wrap_str(2, "abc\ndef");
|
||||
assert_eq!(4, grid.height());
|
||||
assert_eq!("ab\nc\0\nde\nf\0", String::from(grid));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,968 +0,0 @@
|
|||
use crate::command_code::CommandCode;
|
||||
use crate::compression::into_decompressed;
|
||||
use crate::*;
|
||||
|
||||
/// Type alias for documenting the meaning of the u16 in enum values
|
||||
pub type Offset = usize;
|
||||
|
||||
/// A low-level display command.
|
||||
///
|
||||
/// This struct and associated functions implement the UDP protocol for the display.
|
||||
///
|
||||
/// To send a [Command], use a [connection][crate::Connection].
|
||||
///
|
||||
/// # Available commands
|
||||
///
|
||||
/// To send text, take a look at [Command::Cp437Data].
|
||||
///
|
||||
/// To draw pixels, the easiest command to use is [Command::BitmapLinearWin].
|
||||
///
|
||||
/// The other BitmapLinear-Commands operate on a region of pixel memory directly.
|
||||
/// [Command::BitmapLinear] overwrites a region.
|
||||
/// [Command::BitmapLinearOr], [Command::BitmapLinearAnd] and [Command::BitmapLinearXor] apply logical operations per pixel.
|
||||
///
|
||||
/// Out of bounds operations may be truncated or ignored by the display.
|
||||
///
|
||||
/// # Compression
|
||||
///
|
||||
/// Some commands can contain compressed payloads.
|
||||
/// To get started, use [CompressionCode::Uncompressed].
|
||||
///
|
||||
/// If you want to archive the best performance (e.g. latency),
|
||||
/// you can try the different compression algorithms for your hardware and use case.
|
||||
///
|
||||
/// In memory, the payload is not compressed in the [Command].
|
||||
/// Payload (de-)compression happens when converting the [Command] into a [Packet] or vice versa.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use servicepoint::{Brightness, Command, Connection, Packet};
|
||||
/// #
|
||||
/// // create command
|
||||
/// let command = Command::Brightness(Brightness::MAX);
|
||||
///
|
||||
/// // turn command into Packet
|
||||
/// let packet: Packet = command.clone().into();
|
||||
///
|
||||
/// // read command from packet
|
||||
/// let round_tripped = Command::try_from(packet).unwrap();
|
||||
///
|
||||
/// // round tripping produces exact copy
|
||||
/// assert_eq!(command, round_tripped);
|
||||
///
|
||||
/// // send command
|
||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
||||
/// connection.send(command).unwrap();
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Command {
|
||||
/// Set all pixels to the off state. Does not affect brightness.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection};
|
||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
||||
/// connection.send(Command::Clear).unwrap();
|
||||
/// ```
|
||||
Clear,
|
||||
|
||||
/// Show text on the screen.
|
||||
///
|
||||
/// The text is sent in the form of a 2D grid of UTF-8 encoded characters (the default encoding in rust).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection, Origin, CharGrid};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// let grid = CharGrid::from("Hello,\nWorld!");
|
||||
/// connection.send(Command::Utf8Data(Origin::ZERO, grid)).expect("send failed");
|
||||
/// ```
|
||||
Utf8Data(Origin<Tiles>, CharGrid),
|
||||
|
||||
/// Show text on the screen.
|
||||
///
|
||||
/// The text is sent in the form of a 2D grid of [CP-437] encoded characters.
|
||||
///
|
||||
/// <div class="warning">You probably want to use [Command::Utf8Data] instead</div>
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection, Origin, CharGrid, Cp437Grid};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// let grid = CharGrid::from("Hello,\nWorld!");
|
||||
/// let grid = Cp437Grid::from(&grid);
|
||||
/// connection.send(Command::Cp437Data(Origin::ZERO, grid)).expect("send failed");
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection, Cp437Grid, Origin};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// let grid = Cp437Grid::load_ascii("Hello\nWorld", 5, false).unwrap();
|
||||
/// connection.send(Command::Cp437Data(Origin::new(2, 2), grid)).unwrap();
|
||||
/// ```
|
||||
/// [CP-437]: https://en.wikipedia.org/wiki/Code_page_437
|
||||
Cp437Data(Origin<Tiles>, Cp437Grid),
|
||||
|
||||
/// Overwrites a rectangular region of pixels.
|
||||
///
|
||||
/// Origin coordinates must be divisible by 8.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, CompressionCode, Grid, Bitmap};
|
||||
/// # let connection = servicepoint::Connection::Fake;
|
||||
/// #
|
||||
/// let mut pixels = Bitmap::max_sized();
|
||||
/// // draw something to the pixels here
|
||||
/// # pixels.set(2, 5, true);
|
||||
///
|
||||
/// // create command to send pixels
|
||||
/// let command = Command::BitmapLinearWin(
|
||||
/// servicepoint::Origin::ZERO,
|
||||
/// pixels,
|
||||
/// CompressionCode::Uncompressed
|
||||
/// );
|
||||
///
|
||||
/// connection.send(command).expect("send failed");
|
||||
/// ```
|
||||
BitmapLinearWin(Origin<Pixels>, Bitmap, CompressionCode),
|
||||
|
||||
/// Set the brightness of all tiles to the same value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Brightness, Command, Connection};
|
||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
||||
/// let command = Command::Brightness(Brightness::MAX);
|
||||
/// connection.send(command).unwrap();
|
||||
/// ```
|
||||
Brightness(Brightness),
|
||||
|
||||
/// Set the brightness of individual tiles in a rectangular area of the display.
|
||||
CharBrightness(Origin<Tiles>, BrightnessGrid),
|
||||
|
||||
/// Set pixel data starting at the pixel offset on screen.
|
||||
///
|
||||
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
||||
/// once the starting row is full, overwriting will continue on column 0.
|
||||
///
|
||||
/// The contained [BitVec] is always uncompressed.
|
||||
BitmapLinear(Offset, BitVec, CompressionCode),
|
||||
|
||||
/// Set pixel data according to an and-mask starting at the offset.
|
||||
///
|
||||
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
||||
/// once the starting row is full, overwriting will continue on column 0.
|
||||
///
|
||||
/// The contained [BitVec] is always uncompressed.
|
||||
BitmapLinearAnd(Offset, BitVec, CompressionCode),
|
||||
|
||||
/// Set pixel data according to an or-mask starting at the offset.
|
||||
///
|
||||
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
||||
/// once the starting row is full, overwriting will continue on column 0.
|
||||
///
|
||||
/// The contained [BitVec] is always uncompressed.
|
||||
BitmapLinearOr(Offset, BitVec, CompressionCode),
|
||||
|
||||
/// Set pixel data according to a xor-mask starting at the offset.
|
||||
///
|
||||
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
||||
/// once the starting row is full, overwriting will continue on column 0.
|
||||
///
|
||||
/// The contained [BitVec] is always uncompressed.
|
||||
BitmapLinearXor(Offset, BitVec, CompressionCode),
|
||||
|
||||
/// Kills the udp daemon on the display, which usually results in a restart.
|
||||
///
|
||||
/// Please do not send this in your normal program flow.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection};
|
||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
||||
/// connection.send(Command::HardReset).unwrap();
|
||||
/// ```
|
||||
HardReset,
|
||||
|
||||
/// <div class="warning">Untested</div>
|
||||
///
|
||||
/// Slowly decrease brightness until off or something like that?
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection};
|
||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
||||
/// connection.send(Command::FadeOut).unwrap();
|
||||
/// ```
|
||||
FadeOut,
|
||||
|
||||
/// Legacy command code, gets ignored by the real display.
|
||||
///
|
||||
/// Might be useful as a noop package.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection};
|
||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
||||
/// // this sends a packet that does nothing
|
||||
/// # #[allow(deprecated)]
|
||||
/// connection.send(Command::BitmapLegacy).unwrap();
|
||||
/// ```
|
||||
#[deprecated]
|
||||
BitmapLegacy,
|
||||
}
|
||||
|
||||
/// Err values for [Command::try_from].
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
pub enum TryFromPacketError {
|
||||
/// the contained command code does not correspond to a known command
|
||||
#[error("The command code {0:?} does not correspond to a known command")]
|
||||
InvalidCommand(u16),
|
||||
/// the expected payload size was n, but size m was found
|
||||
#[error("the expected payload size was {0}, but size {1} was found")]
|
||||
UnexpectedPayloadSize(usize, usize),
|
||||
/// Header fields not needed for the command have been used.
|
||||
///
|
||||
/// Note that these commands would usually still work on the actual display.
|
||||
#[error("Header fields not needed for the command have been used")]
|
||||
ExtraneousHeaderValues,
|
||||
/// The contained compression code is not known. This could be of disabled features.
|
||||
#[error("The compression code {0:?} does not correspond to a known compression algorithm.")]
|
||||
InvalidCompressionCode(u16),
|
||||
/// Decompression of the payload failed. This can be caused by corrupted packets.
|
||||
#[error("The decompression of the payload failed")]
|
||||
DecompressionFailed,
|
||||
/// The given brightness value is out of bounds
|
||||
#[error("The given brightness value {0} is out of bounds.")]
|
||||
InvalidBrightness(u8),
|
||||
#[error(transparent)]
|
||||
InvalidUtf8(#[from] std::string::FromUtf8Error),
|
||||
}
|
||||
|
||||
impl TryFrom<Packet> for Command {
|
||||
type Error = TryFromPacketError;
|
||||
|
||||
/// Try to interpret the [Packet] as one containing a [Command]
|
||||
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||
let Packet {
|
||||
header: Header {
|
||||
command_code, a, ..
|
||||
},
|
||||
..
|
||||
} = packet;
|
||||
let command_code = match CommandCode::try_from(command_code) {
|
||||
Err(()) => {
|
||||
return Err(TryFromPacketError::InvalidCommand(command_code));
|
||||
}
|
||||
Ok(value) => value,
|
||||
};
|
||||
|
||||
match command_code {
|
||||
CommandCode::Clear => {
|
||||
Self::packet_into_command_only(packet, Command::Clear)
|
||||
}
|
||||
CommandCode::Brightness => Self::packet_into_brightness(&packet),
|
||||
CommandCode::HardReset => {
|
||||
Self::packet_into_command_only(packet, Command::HardReset)
|
||||
}
|
||||
CommandCode::FadeOut => {
|
||||
Self::packet_into_command_only(packet, Command::FadeOut)
|
||||
}
|
||||
CommandCode::Cp437Data => Self::packet_into_cp437(&packet),
|
||||
CommandCode::CharBrightness => {
|
||||
Self::packet_into_char_brightness(&packet)
|
||||
}
|
||||
CommandCode::Utf8Data => Self::packet_into_utf8(&packet),
|
||||
#[allow(deprecated)]
|
||||
CommandCode::BitmapLegacy => Ok(Command::BitmapLegacy),
|
||||
CommandCode::BitmapLinear => {
|
||||
let (vec, compression) =
|
||||
Self::packet_into_linear_bitmap(packet)?;
|
||||
Ok(Command::BitmapLinear(a as Offset, vec, compression))
|
||||
}
|
||||
CommandCode::BitmapLinearAnd => {
|
||||
let (vec, compression) =
|
||||
Self::packet_into_linear_bitmap(packet)?;
|
||||
Ok(Command::BitmapLinearAnd(a as Offset, vec, compression))
|
||||
}
|
||||
CommandCode::BitmapLinearOr => {
|
||||
let (vec, compression) =
|
||||
Self::packet_into_linear_bitmap(packet)?;
|
||||
Ok(Command::BitmapLinearOr(a as Offset, vec, compression))
|
||||
}
|
||||
CommandCode::BitmapLinearXor => {
|
||||
let (vec, compression) =
|
||||
Self::packet_into_linear_bitmap(packet)?;
|
||||
Ok(Command::BitmapLinearXor(a as Offset, vec, compression))
|
||||
}
|
||||
CommandCode::BitmapLinearWinUncompressed => {
|
||||
Self::packet_into_bitmap_win(
|
||||
packet,
|
||||
CompressionCode::Uncompressed,
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "compression_zlib")]
|
||||
CommandCode::BitmapLinearWinZlib => {
|
||||
Self::packet_into_bitmap_win(packet, CompressionCode::Zlib)
|
||||
}
|
||||
#[cfg(feature = "compression_bzip2")]
|
||||
CommandCode::BitmapLinearWinBzip2 => {
|
||||
Self::packet_into_bitmap_win(packet, CompressionCode::Bzip2)
|
||||
}
|
||||
#[cfg(feature = "compression_lzma")]
|
||||
CommandCode::BitmapLinearWinLzma => {
|
||||
Self::packet_into_bitmap_win(packet, CompressionCode::Lzma)
|
||||
}
|
||||
#[cfg(feature = "compression_zstd")]
|
||||
CommandCode::BitmapLinearWinZstd => {
|
||||
Self::packet_into_bitmap_win(packet, CompressionCode::Zstd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
fn packet_into_bitmap_win(
|
||||
packet: Packet,
|
||||
compression: CompressionCode,
|
||||
) -> Result<Command, TryFromPacketError> {
|
||||
let Packet {
|
||||
header:
|
||||
Header {
|
||||
command_code: _,
|
||||
a: tiles_x,
|
||||
b: pixels_y,
|
||||
c: tile_w,
|
||||
d: pixel_h,
|
||||
},
|
||||
payload,
|
||||
} = packet;
|
||||
|
||||
let payload = match into_decompressed(compression, payload) {
|
||||
None => return Err(TryFromPacketError::DecompressionFailed),
|
||||
Some(decompressed) => decompressed,
|
||||
};
|
||||
|
||||
Ok(Command::BitmapLinearWin(
|
||||
Origin::new(tiles_x as usize * TILE_SIZE, pixels_y as usize),
|
||||
Bitmap::load(
|
||||
tile_w as usize * TILE_SIZE,
|
||||
pixel_h as usize,
|
||||
&payload,
|
||||
),
|
||||
compression,
|
||||
))
|
||||
}
|
||||
|
||||
/// Helper method for checking that a packet is empty and only contains a command code
|
||||
fn packet_into_command_only(
|
||||
packet: Packet,
|
||||
command: Command,
|
||||
) -> Result<Command, TryFromPacketError> {
|
||||
let Packet {
|
||||
header:
|
||||
Header {
|
||||
command_code: _,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
},
|
||||
payload,
|
||||
} = packet;
|
||||
if !payload.is_empty() {
|
||||
Err(TryFromPacketError::UnexpectedPayloadSize(0, payload.len()))
|
||||
} else if a != 0 || b != 0 || c != 0 || d != 0 {
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
} else {
|
||||
Ok(command)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method for Packets into `BitmapLinear*`-Commands
|
||||
fn packet_into_linear_bitmap(
|
||||
packet: Packet,
|
||||
) -> Result<(BitVec, CompressionCode), TryFromPacketError> {
|
||||
let Packet {
|
||||
header:
|
||||
Header {
|
||||
b: length,
|
||||
c: sub,
|
||||
d: reserved,
|
||||
..
|
||||
},
|
||||
payload,
|
||||
} = packet;
|
||||
if reserved != 0 {
|
||||
return Err(TryFromPacketError::ExtraneousHeaderValues);
|
||||
}
|
||||
let sub = match CompressionCode::try_from(sub) {
|
||||
Err(()) => {
|
||||
return Err(TryFromPacketError::InvalidCompressionCode(sub));
|
||||
}
|
||||
Ok(value) => value,
|
||||
};
|
||||
let payload = match into_decompressed(sub, payload) {
|
||||
None => return Err(TryFromPacketError::DecompressionFailed),
|
||||
Some(value) => value,
|
||||
};
|
||||
if payload.len() != length as usize {
|
||||
return Err(TryFromPacketError::UnexpectedPayloadSize(
|
||||
length as usize,
|
||||
payload.len(),
|
||||
));
|
||||
}
|
||||
Ok((BitVec::from_vec(payload), sub))
|
||||
}
|
||||
|
||||
fn packet_into_char_brightness(
|
||||
packet: &Packet,
|
||||
) -> Result<Command, TryFromPacketError> {
|
||||
let Packet {
|
||||
header:
|
||||
Header {
|
||||
command_code: _,
|
||||
a: x,
|
||||
b: y,
|
||||
c: width,
|
||||
d: height,
|
||||
},
|
||||
payload,
|
||||
} = packet;
|
||||
|
||||
let grid = ByteGrid::load(*width as usize, *height as usize, payload);
|
||||
let grid = match BrightnessGrid::try_from(grid) {
|
||||
Ok(grid) => grid,
|
||||
Err(val) => return Err(TryFromPacketError::InvalidBrightness(val)),
|
||||
};
|
||||
|
||||
Ok(Command::CharBrightness(
|
||||
Origin::new(*x as usize, *y as usize),
|
||||
grid,
|
||||
))
|
||||
}
|
||||
|
||||
fn packet_into_brightness(
|
||||
packet: &Packet,
|
||||
) -> Result<Command, TryFromPacketError> {
|
||||
let Packet {
|
||||
header:
|
||||
Header {
|
||||
command_code: _,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
},
|
||||
payload,
|
||||
} = packet;
|
||||
if payload.len() != 1 {
|
||||
return Err(TryFromPacketError::UnexpectedPayloadSize(
|
||||
1,
|
||||
payload.len(),
|
||||
));
|
||||
}
|
||||
|
||||
if *a != 0 || *b != 0 || *c != 0 || *d != 0 {
|
||||
return Err(TryFromPacketError::ExtraneousHeaderValues);
|
||||
}
|
||||
|
||||
match Brightness::try_from(payload[0]) {
|
||||
Ok(b) => Ok(Command::Brightness(b)),
|
||||
Err(_) => Err(TryFromPacketError::InvalidBrightness(payload[0])),
|
||||
}
|
||||
}
|
||||
|
||||
fn packet_into_cp437(
|
||||
packet: &Packet,
|
||||
) -> Result<Command, TryFromPacketError> {
|
||||
let Packet {
|
||||
header:
|
||||
Header {
|
||||
command_code: _,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
},
|
||||
payload,
|
||||
} = packet;
|
||||
Ok(Command::Cp437Data(
|
||||
Origin::new(*a as usize, *b as usize),
|
||||
Cp437Grid::load(*c as usize, *d as usize, payload),
|
||||
))
|
||||
}
|
||||
|
||||
fn packet_into_utf8(
|
||||
packet: &Packet,
|
||||
) -> Result<Command, TryFromPacketError> {
|
||||
let Packet {
|
||||
header:
|
||||
Header {
|
||||
command_code: _,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
},
|
||||
payload,
|
||||
} = packet;
|
||||
let payload: Vec<_> =
|
||||
String::from_utf8(payload.clone())?.chars().collect();
|
||||
Ok(Command::Utf8Data(
|
||||
Origin::new(*a as usize, *b as usize),
|
||||
CharGrid::load(*c as usize, *d as usize, &payload),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::command::TryFromPacketError;
|
||||
use crate::command_code::CommandCode;
|
||||
use crate::{
|
||||
BitVec, Bitmap, Brightness, BrightnessGrid, CharGrid, Command,
|
||||
CompressionCode, Cp437Grid, Header, Origin, Packet, Pixels,
|
||||
};
|
||||
|
||||
fn round_trip(original: Command) {
|
||||
let packet: Packet = original.clone().into();
|
||||
let copy: Command = match Command::try_from(packet) {
|
||||
Ok(command) => command,
|
||||
Err(err) => panic!("could not reload {original:?}: {err:?}"),
|
||||
};
|
||||
assert_eq!(copy, original);
|
||||
}
|
||||
|
||||
fn all_compressions<'t>() -> &'t [CompressionCode] {
|
||||
&[
|
||||
CompressionCode::Uncompressed,
|
||||
#[cfg(feature = "compression_lzma")]
|
||||
CompressionCode::Lzma,
|
||||
#[cfg(feature = "compression_bzip2")]
|
||||
CompressionCode::Bzip2,
|
||||
#[cfg(feature = "compression_zlib")]
|
||||
CompressionCode::Zlib,
|
||||
#[cfg(feature = "compression_zstd")]
|
||||
CompressionCode::Zstd,
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_clear() {
|
||||
round_trip(Command::Clear);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_hard_reset() {
|
||||
round_trip(Command::HardReset);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_fade_out() {
|
||||
round_trip(Command::FadeOut);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_brightness() {
|
||||
round_trip(Command::Brightness(Brightness::try_from(6).unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn round_trip_bitmap_legacy() {
|
||||
round_trip(Command::BitmapLegacy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_char_brightness() {
|
||||
round_trip(Command::CharBrightness(
|
||||
Origin::new(5, 2),
|
||||
BrightnessGrid::new(7, 5),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_cp437_data() {
|
||||
round_trip(Command::Cp437Data(Origin::new(5, 2), Cp437Grid::new(7, 5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_utf8_data() {
|
||||
round_trip(Command::Utf8Data(Origin::new(5, 2), CharGrid::new(7, 5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_bitmap_linear() {
|
||||
for compression in all_compressions().iter().copied() {
|
||||
round_trip(Command::BitmapLinear(
|
||||
23,
|
||||
BitVec::repeat(false, 40),
|
||||
compression,
|
||||
));
|
||||
round_trip(Command::BitmapLinearAnd(
|
||||
23,
|
||||
BitVec::repeat(false, 40),
|
||||
compression,
|
||||
));
|
||||
round_trip(Command::BitmapLinearOr(
|
||||
23,
|
||||
BitVec::repeat(false, 40),
|
||||
compression,
|
||||
));
|
||||
round_trip(Command::BitmapLinearXor(
|
||||
23,
|
||||
BitVec::repeat(false, 40),
|
||||
compression,
|
||||
));
|
||||
round_trip(Command::BitmapLinearWin(
|
||||
Origin::ZERO,
|
||||
Bitmap::max_sized(),
|
||||
compression,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_invalid_command() {
|
||||
let p = Packet {
|
||||
header: Header {
|
||||
command_code: 0xFF,
|
||||
a: 0x00,
|
||||
b: 0x00,
|
||||
c: 0x00,
|
||||
d: 0x00,
|
||||
},
|
||||
payload: vec![],
|
||||
};
|
||||
let result = Command::try_from(p);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(TryFromPacketError::InvalidCommand(0xFF))
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_extraneous_header_values_clear() {
|
||||
let p = Packet {
|
||||
header: Header {
|
||||
command_code: CommandCode::Clear.into(),
|
||||
a: 0x05,
|
||||
b: 0x00,
|
||||
c: 0x00,
|
||||
d: 0x00,
|
||||
},
|
||||
payload: vec![],
|
||||
};
|
||||
let result = Command::try_from(p);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_extraneous_header_values_brightness() {
|
||||
let p = Packet {
|
||||
header: Header {
|
||||
command_code: CommandCode::Brightness.into(),
|
||||
a: 0x00,
|
||||
b: 0x13,
|
||||
c: 0x37,
|
||||
d: 0x00,
|
||||
},
|
||||
payload: vec![5],
|
||||
};
|
||||
let result = Command::try_from(p);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_extraneous_header_hard_reset() {
|
||||
let p = Packet {
|
||||
header: Header {
|
||||
command_code: CommandCode::HardReset.into(),
|
||||
a: 0x00,
|
||||
b: 0x00,
|
||||
c: 0x00,
|
||||
d: 0x01,
|
||||
},
|
||||
payload: vec![],
|
||||
};
|
||||
let result = Command::try_from(p);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_extraneous_header_fade_out() {
|
||||
let p = Packet {
|
||||
header: Header {
|
||||
command_code: CommandCode::FadeOut.into(),
|
||||
a: 0x10,
|
||||
b: 0x00,
|
||||
c: 0x00,
|
||||
d: 0x01,
|
||||
},
|
||||
payload: vec![],
|
||||
};
|
||||
let result = Command::try_from(p);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_unexpected_payload() {
|
||||
let p = Packet {
|
||||
header: Header {
|
||||
command_code: CommandCode::FadeOut.into(),
|
||||
a: 0x00,
|
||||
b: 0x00,
|
||||
c: 0x00,
|
||||
d: 0x00,
|
||||
},
|
||||
payload: vec![5, 7],
|
||||
};
|
||||
let result = Command::try_from(p);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(TryFromPacketError::UnexpectedPayloadSize(0, 2))
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_decompression_failed_win() {
|
||||
for compression in all_compressions().iter().copied() {
|
||||
let p: Packet = Command::BitmapLinearWin(
|
||||
Origin::new(16, 8),
|
||||
Bitmap::new(8, 8),
|
||||
compression,
|
||||
)
|
||||
.into();
|
||||
|
||||
let Packet {
|
||||
header,
|
||||
mut payload,
|
||||
} = p;
|
||||
|
||||
// mangle it
|
||||
for byte in payload.iter_mut() {
|
||||
*byte -= *byte / 2;
|
||||
}
|
||||
|
||||
let p = Packet { header, payload };
|
||||
let result = Command::try_from(p);
|
||||
if compression != CompressionCode::Uncompressed {
|
||||
assert_eq!(result, Err(TryFromPacketError::DecompressionFailed))
|
||||
} else {
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_decompression_failed_and() {
|
||||
for compression in all_compressions().iter().copied() {
|
||||
let p: Packet = Command::BitmapLinearAnd(
|
||||
0,
|
||||
BitVec::repeat(false, 8),
|
||||
compression,
|
||||
)
|
||||
.into();
|
||||
let Packet {
|
||||
header,
|
||||
mut payload,
|
||||
} = p;
|
||||
|
||||
// mangle it
|
||||
for byte in payload.iter_mut() {
|
||||
*byte -= *byte / 2;
|
||||
}
|
||||
|
||||
let p = Packet { header, payload };
|
||||
let result = Command::try_from(p);
|
||||
if compression != CompressionCode::Uncompressed {
|
||||
assert_eq!(result, Err(TryFromPacketError::DecompressionFailed))
|
||||
} else {
|
||||
// when not compressing, there is no way to detect corrupted data
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unexpected_payload_size_brightness() {
|
||||
assert_eq!(
|
||||
Command::try_from(Packet {
|
||||
header: Header {
|
||||
command_code: CommandCode::Brightness.into(),
|
||||
a: 0,
|
||||
b: 0,
|
||||
c: 0,
|
||||
d: 0,
|
||||
},
|
||||
payload: vec!()
|
||||
}),
|
||||
Err(TryFromPacketError::UnexpectedPayloadSize(1, 0))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Command::try_from(Packet {
|
||||
header: Header {
|
||||
command_code: CommandCode::Brightness.into(),
|
||||
a: 0,
|
||||
b: 0,
|
||||
c: 0,
|
||||
d: 0,
|
||||
},
|
||||
payload: vec!(0, 0)
|
||||
}),
|
||||
Err(TryFromPacketError::UnexpectedPayloadSize(1, 2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_reserved_used() {
|
||||
let Packet { header, payload } = Command::BitmapLinear(
|
||||
0,
|
||||
BitVec::repeat(false, 8),
|
||||
CompressionCode::Uncompressed,
|
||||
)
|
||||
.into();
|
||||
let Header {
|
||||
command_code: command,
|
||||
a: offset,
|
||||
b: length,
|
||||
c: sub,
|
||||
d: _reserved,
|
||||
} = header;
|
||||
let p = Packet {
|
||||
header: Header {
|
||||
command_code: command,
|
||||
a: offset,
|
||||
b: length,
|
||||
c: sub,
|
||||
d: 69,
|
||||
},
|
||||
payload,
|
||||
};
|
||||
assert_eq!(
|
||||
Command::try_from(p),
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_invalid_compression() {
|
||||
let Packet { header, payload } = Command::BitmapLinear(
|
||||
0,
|
||||
BitVec::repeat(false, 8),
|
||||
CompressionCode::Uncompressed,
|
||||
)
|
||||
.into();
|
||||
let Header {
|
||||
command_code: command,
|
||||
a: offset,
|
||||
b: length,
|
||||
c: _sub,
|
||||
d: reserved,
|
||||
} = header;
|
||||
let p = Packet {
|
||||
header: Header {
|
||||
command_code: command,
|
||||
a: offset,
|
||||
b: length,
|
||||
c: 42,
|
||||
d: reserved,
|
||||
},
|
||||
payload,
|
||||
};
|
||||
assert_eq!(
|
||||
Command::try_from(p),
|
||||
Err(TryFromPacketError::InvalidCompressionCode(42))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_unexpected_size() {
|
||||
let Packet { header, payload } = Command::BitmapLinear(
|
||||
0,
|
||||
BitVec::repeat(false, 8),
|
||||
CompressionCode::Uncompressed,
|
||||
)
|
||||
.into();
|
||||
let Header {
|
||||
command_code: command,
|
||||
a: offset,
|
||||
b: length,
|
||||
c: compression,
|
||||
d: reserved,
|
||||
} = header;
|
||||
let p = Packet {
|
||||
header: Header {
|
||||
command_code: command,
|
||||
a: offset,
|
||||
b: 420,
|
||||
c: compression,
|
||||
d: reserved,
|
||||
},
|
||||
payload,
|
||||
};
|
||||
assert_eq!(
|
||||
Command::try_from(p),
|
||||
Err(TryFromPacketError::UnexpectedPayloadSize(
|
||||
420,
|
||||
length as usize,
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn origin_add() {
|
||||
assert_eq!(
|
||||
Origin::<Pixels>::new(4, 2),
|
||||
Origin::new(1, 0) + Origin::new(3, 2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packet_into_char_brightness_invalid() {
|
||||
let grid = BrightnessGrid::new(2, 2);
|
||||
let command = Command::CharBrightness(Origin::ZERO, grid);
|
||||
let mut packet: Packet = command.into();
|
||||
let slot = packet.payload.get_mut(1).unwrap();
|
||||
*slot = 23;
|
||||
assert_eq!(
|
||||
Command::try_from(packet),
|
||||
Err(TryFromPacketError::InvalidBrightness(23))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packet_into_brightness_invalid() {
|
||||
let mut packet: Packet = Command::Brightness(Brightness::MAX).into();
|
||||
let slot = packet.payload.get_mut(0).unwrap();
|
||||
*slot = 42;
|
||||
assert_eq!(
|
||||
Command::try_from(packet),
|
||||
Err(TryFromPacketError::InvalidBrightness(42))
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue