Merge pull request #1 from kaesaecracker/restructure
Restructure, build.rs, move c_api
This commit is contained in:
commit
333cf954f8
73 changed files with 1643 additions and 1762 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,3 +3,4 @@ target
|
|||
out
|
||||
bin
|
||||
obj
|
||||
.direnv
|
||||
|
|
|
|||
379
Cargo.lock
generated
379
Cargo.lock
generated
|
|
@ -17,15 +17,6 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "announce"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
"servicepoint2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.14"
|
||||
|
|
@ -75,6 +66,35 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.4"
|
||||
|
|
@ -97,10 +117,29 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.97"
|
||||
name = "cbindgen"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
|
||||
checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49"
|
||||
dependencies = [
|
||||
"clap 3.2.25",
|
||||
"heck 0.4.1",
|
||||
"indexmap",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 1.0.109",
|
||||
"tempfile",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
|
|
@ -113,6 +152,21 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags 1.3.2",
|
||||
"clap_lex 0.2.4",
|
||||
"indexmap",
|
||||
"strsim 0.10.0",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
|
|
@ -131,8 +185,8 @@ checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
|||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
"clap_lex 0.7.0",
|
||||
"strsim 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -141,10 +195,19 @@ version = "4.5.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.63",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -161,9 +224,9 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
|||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.0"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
|
@ -179,27 +242,20 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.0"
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.3"
|
||||
name = "fastrand"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"humantime",
|
||||
"log",
|
||||
]
|
||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
|
|
@ -211,16 +267,6 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "game_of_life"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
"rand",
|
||||
"servicepoint2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
|
|
@ -232,6 +278,18 @@ dependencies = [
|
|||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[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"
|
||||
|
|
@ -239,10 +297,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
|
|
@ -250,6 +321,12 @@ version = "1.70.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.31"
|
||||
|
|
@ -259,11 +336,25 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lang_c"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"servicepoint_binding_c",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.154"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
|
|
@ -279,28 +370,25 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
|||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||
checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moving_line"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
"servicepoint2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
|
|
@ -315,9 +403,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.82"
|
||||
version = "1.0.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
|
||||
checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
|
@ -361,16 +449,6 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "random_brightness"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
"rand",
|
||||
"servicepoint2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.4"
|
||||
|
|
@ -411,24 +489,91 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "servicepoint-binding-cs"
|
||||
version = "0.4.2"
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"csbindgen",
|
||||
"servicepoint2",
|
||||
"bitflags 2.5.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servicepoint2"
|
||||
version = "0.4.2"
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.203"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.203"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servicepoint"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bzip2",
|
||||
"clap 4.5.4",
|
||||
"flate2",
|
||||
"log",
|
||||
"rand",
|
||||
"rust-lzma",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servicepoint_binding_c"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
"servicepoint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servicepoint_binding_cs"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"csbindgen",
|
||||
"servicepoint",
|
||||
"servicepoint_binding_c",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
|
|
@ -448,15 +593,51 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.63"
|
||||
version = "2.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
|
||||
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
|
|
@ -481,6 +662,37 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
|
@ -554,15 +766,6 @@ version = "0.52.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
|
||||
[[package]]
|
||||
name = "wiping_clear"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
"servicepoint2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.1"
|
||||
|
|
|
|||
14
Cargo.toml
14
Cargo.toml
|
|
@ -1,11 +1,11 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"servicepoint2",
|
||||
"servicepoint2-binding-cs",
|
||||
"examples/announce",
|
||||
"examples/game_of_life",
|
||||
"examples/moving_line",
|
||||
"examples/wiping_clear",
|
||||
"examples/random_brightness"
|
||||
"crates/servicepoint",
|
||||
"crates/servicepoint_binding_c",
|
||||
"crates/servicepoint_binding_cs",
|
||||
"crates/servicepoint_binding_c/examples/lang_c"
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.5.0"
|
||||
|
|
|
|||
203
README.md
203
README.md
|
|
@ -1,160 +1,73 @@
|
|||
# servicepoint2
|
||||
|
||||
[](https://crates.io/crates/servicepoint2)
|
||||
[](https://crates.io/crates/servicepoint2)
|
||||
[](https://docs.rs/servicepoint2/latest/servicepoint2/)
|
||||
[](./LICENSE)
|
||||
# servicepoint
|
||||
|
||||
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.
|
||||
This repository contains a library for parsing, encoding and sending packets to this display via UDP in multiple
|
||||
programming languages.
|
||||
|
||||
## Note on stability
|
||||
Take a look at the contained crates for language specific information:
|
||||
|
||||
This library is still in early development.
|
||||
You can absolutely use it, and it works, but expect minor breaking changes with every version bump.
|
||||
Please specify the full version including patch in your Cargo.toml until 1.0 is released.
|
||||
|
||||
Expect bugs and/or missing features in the language bindings for now. If you need something specific, open an issue or a pull request.
|
||||
|
||||
## Rust
|
||||
|
||||
This is where the library works the best.
|
||||
Any API usage accepted by the compiler in a safe context is either safe or buggy (issues welcome)
|
||||
|
||||
```bash
|
||||
cargo add servicepoint2
|
||||
```
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// establish connection
|
||||
let connection = servicepoint2::Connection::open("172.23.42.29:2342")
|
||||
.expect("connection failed");
|
||||
|
||||
// clear screen content
|
||||
connection.send(servicepoint2::Command::Clear.into())
|
||||
.expect("send failed");
|
||||
}
|
||||
```
|
||||
|
||||
More examples are available in the repository folder and in the [Projects using the library]() section
|
||||
|
||||
## C / C++
|
||||
|
||||
The lowest common denominator. Things to keep in mind:
|
||||
|
||||
- This is a chainsaw. You will cut your leg.
|
||||
- function names are: `sp2_` \<struct_name\> \<rust name\>.
|
||||
- Use the rust documentation.
|
||||
- Instances get consumed in the same way they do when writing rust / C# code. Do not use an instance after an (implicit!) free.
|
||||
- Option<T> or Result<T, E> turn into nullable return values - check for NULL!
|
||||
- There are no specifics for C++ here yet. You might get a nicer header when generating directly for C++, but it should be usable.
|
||||
- Reading and writing to instances concurrently is not safe. Only reading concurrently is safe.
|
||||
|
||||
```c++
|
||||
#include <stdio.h>
|
||||
#include "servicepoint2.h"
|
||||
|
||||
int main(void) {
|
||||
sp2_Connection *connection = sp2_connection_open("localhost:2342");
|
||||
if (connection == NULL)
|
||||
return 1;
|
||||
|
||||
sp2_PixelGrid *pixels = sp2_pixel_grid_new(sp2_PIXEL_WIDTH, sp2_PIXEL_HEIGHT);
|
||||
sp2_pixel_grid_fill(pixels, true);
|
||||
|
||||
sp2_Command *command = sp2_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
|
||||
sp2_Packet *packet = sp2_packet_from_command(command);
|
||||
if (!sp2_connection_send(connection, packet))
|
||||
return 1;
|
||||
|
||||
sp2_connection_dealloc(connection);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## C# / F#
|
||||
|
||||
Uses C bindings internally to provide a similar API to rust. Things to keep in mind:
|
||||
|
||||
- You will get a `NullPointerException` when trying to call a method where the native instance has been consumed already (e.g. when `Send`ing a command instance twice). Send a clone instead of the original if you want to keep using it.
|
||||
- Some lower-level APIs _will_ panic in native code when used improperly.
|
||||
Example: manipulating the `Span<byte>` of an object after freeing the instance.
|
||||
- C# specifics are documented in the library. Use the rust documentation for everything else. Naming and semantics are the same apart from CamelCase instead of kebab_case.
|
||||
- You will only get rust backtraces in debug builds of the native code.
|
||||
- F# is not explicitly tested. If there are usability or functionality problems, please open an issue.
|
||||
- Reading and writing to instances concurrently is not safe. Only reading concurrently is safe.
|
||||
|
||||
```csharp
|
||||
using ServicePoint2;
|
||||
|
||||
// using statement calls Dispose() on scope exit, which frees unmanaged instances
|
||||
using var connection = Connection.Open("127.0.0.1:2342");
|
||||
using var pixels = PixelGrid.New(Constants.PixelWidth, Constants.PixelHeight);
|
||||
|
||||
while (true)
|
||||
{
|
||||
pixels.Fill(true);
|
||||
connection.Send(Command.BitmapLinearWin(0, 0, pixels.Clone()));
|
||||
Thread.Sleep(5000);
|
||||
|
||||
pixels.Fill(false);
|
||||
connection.Send(Command.BitmapLinearWin(0, 0, pixels.Clone()));
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
```
|
||||
|
||||
### Installation
|
||||
|
||||
NuGet packages are not a good way to distribute native projects ([relevant issue](https://github.com/dotnet/sdk/issues/33845)).
|
||||
Because of that, there is no NuGet package you can use directly.
|
||||
Including this repository as a submodule and building from source is the recommended way of using the library.
|
||||
|
||||
```bash
|
||||
git submodule add https://github.com/kaesaecracker/servicepoint.git
|
||||
git commit -m "add servicepoint submodule"
|
||||
```
|
||||
|
||||
You can now reference `servicepoint2-bindings-cs/src/ServicePoint2.csproj` in your project.
|
||||
The rust library will automatically be built.
|
||||
|
||||
Please provide more information in the form of an issue if you need the build to copy a different library file for your platform.
|
||||
|
||||
### Installation
|
||||
|
||||
Copy the header to your project and compile against.
|
||||
|
||||
You have the choice of linking statically (recommended) or dynamically.
|
||||
- The C example shows how to link statically against the `staticlib` variant.
|
||||
- When linked dynamically, you have to provide the `cdylib` at runtime in the _same_ version, as there are no API/ABI guarantees yet.
|
||||
|
||||
## Features
|
||||
|
||||
This library has multiple compression libraries as optional dependencies.
|
||||
If you do not need compression/decompression support you can disable those features.
|
||||
In the likely case you only need one of them, you can include that one specifically.
|
||||
|
||||
```toml
|
||||
[dependencies.servicepoint2]
|
||||
git = "https://github.com/kaesaecracker/servicepoint.git"
|
||||
default-features = false
|
||||
features = ["compression-bz"]
|
||||
```
|
||||
|
||||
Language bindings will not know which features are available and may fail at runtime.
|
||||
It is recommended to include all features for builds used outside of rust.
|
||||
| Language | Readme |
|
||||
|----------|---------------------------------------------------------------------|
|
||||
| Rust | [servicepoint](crates/servicepoint/README.md) |
|
||||
| C / C++ | [servicepoint_binding_c](crates/servicepoint_binding_c/README.md) |
|
||||
| C# / F# | [servicepoint_binding_cs](crates/servicepoint_binding_cs/README.md) |
|
||||
|
||||
## Projects using the library
|
||||
|
||||
- screen simulator (rust): https://github.com/kaesaecracker/pixel-receiver-rs
|
||||
- tanks game (C#): https://github.com/kaesaecracker/cccb-tanks-cs
|
||||
- screen simulator (rust): [servicepoint-simulator](https://github.com/kaesaecracker/servicepoint-simulator)
|
||||
- tanks game (C#): [servicepoint-tanks](https://github.com/kaesaecracker/cccb-tanks-cs)
|
||||
- cellular automata slideshow (rust): [servicepoint-life](https://github.com/kaesaecracker/servicepoint-life)
|
||||
|
||||
To add yourself to the list, open a pull request.
|
||||
|
||||
## Where is servicepoint1?
|
||||
## About the display
|
||||
|
||||
This library is a spiritual mix of a not-yet-working rust library called `servicepoint` and a bunch of working but also unfinished C# code. Because most of the API concept and a bunch of code is taken from the rust library, the result is called `servicepoint2`.
|
||||
- Resolution: 352x160=56,320 pixels
|
||||
- Pixels are grouped into 44x20=880 tiles (8x8=64 pixels each)
|
||||
- Smallest addressable unit: row of pixels inside of a tile (8 pixels = 1 byte)
|
||||
- The brightness can only be set per tile
|
||||
- Screen content can be changed using a simple UDP protocol
|
||||
- Between each row of tiles, there is a gap of around 4 pixels size. This gap changes the aspect ratio of the display.
|
||||
|
||||
### Binary format
|
||||
|
||||
A UDP package sent to the display has a header size of 10 bytes.
|
||||
Each header value has a size of two bytes (unsigned 16 bit integer).
|
||||
Depending on the command, there can be a payload following the header.
|
||||
|
||||
The commands are implemented in DisplayCommands.
|
||||
|
||||
To change screen contents, these commands are the most relevant:
|
||||
|
||||
1. Clear screen
|
||||
- command: `0x0002`
|
||||
- (rest does not matter)
|
||||
2. Send CP437 data: render specified text into rectangular region
|
||||
- command: `0x0003`
|
||||
- top left tile x
|
||||
- top left tile y
|
||||
- width in tiles
|
||||
- height in tiles
|
||||
- payload: (width in tiles * height in tiles) bytes
|
||||
- 1 byte = 1 character
|
||||
- each character is rendered into one tile (mono-spaced)
|
||||
- characters are encoded using code page 437
|
||||
3. Send bitmap window: set pixel states for a rectangular region
|
||||
- command: `0x0013`
|
||||
- top left tile x
|
||||
- top left _pixel_ y
|
||||
- width in tiles
|
||||
- height in _pixels_
|
||||
- payload: (width in tiles * height in pixels) bytes
|
||||
- network byte order
|
||||
- 1 bit = 1 pixel
|
||||
|
||||
There are other commands implemented as well, e.g. for changing the brightness.
|
||||
|
||||
## What happened to servicepoint2?
|
||||
|
||||
After `servicepoint2` has been merged into `servicepoint`, `servicepoint2` will not continue to get any updates.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
|||
33
crates/servicepoint/Cargo.toml
Normal file
33
crates/servicepoint/Cargo.toml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
[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://github.com/cccb/servicepoint"
|
||||
readme = "README.md"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
flate2 = { version = "1.0", optional = true }
|
||||
bzip2 = { version = "0.4", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
rust-lzma = { version = "0.6.0", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["compression_lzma"]
|
||||
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"]
|
||||
|
||||
[dev-dependencies]
|
||||
# for examples
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
rand = "0.8"
|
||||
54
crates/servicepoint/README.md
Normal file
54
crates/servicepoint/README.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# 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.
|
||||
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
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.into())
|
||||
.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 is still in early development.
|
||||
You can absolutely use it, and it works, but expect minor breaking changes with every version bump.
|
||||
Please specify the full version including patch in your Cargo.toml until 1.0 is released.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cargo add servicepoint
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
This library has multiple compression libraries as optional dependencies.
|
||||
If you do not need compression/decompression support you can disable those features.
|
||||
In the likely case you only need one of them, you can include that one specifically.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
servicepoint = { version = "0.5.0", default-features = false, features = ["compression-bz"] }
|
||||
```
|
||||
|
||||
## Everything else
|
||||
|
||||
Look at the main project [README](https://github.com/cccb/servicepoint/blob/main/README.md) for further information.
|
||||
43
crates/servicepoint/examples/announce.rs
Normal file
43
crates/servicepoint/examples/announce.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use clap::Parser;
|
||||
|
||||
use servicepoint::{ByteGrid, Command, Connection, Grid, Origin};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
#[arg(short, long, num_args = 1.., value_delimiter = '\n')]
|
||||
text: Vec<String>,
|
||||
#[arg(short, long, default_value_t = true)]
|
||||
clear: bool,
|
||||
}
|
||||
|
||||
/// example: `cargo run -- --text "Hallo,
|
||||
/// 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).unwrap();
|
||||
if cli.clear {
|
||||
connection.send(Command::Clear).unwrap();
|
||||
}
|
||||
|
||||
let max_width = cli.text.iter().map(|t| t.len()).max().unwrap();
|
||||
|
||||
let mut chars = ByteGrid::new(max_width, cli.text.len());
|
||||
for y in 0..cli.text.len() {
|
||||
let row = &cli.text[y];
|
||||
|
||||
for (x, char) in row.chars().enumerate() {
|
||||
let char = char.try_into().expect("invalid input char");
|
||||
chars.set(x, y, char);
|
||||
}
|
||||
}
|
||||
|
||||
connection
|
||||
.send(Command::Cp437Data(Origin(0, 0), chars))
|
||||
.unwrap();
|
||||
}
|
||||
89
crates/servicepoint/examples/game_of_life.rs
Normal file
89
crates/servicepoint/examples/game_of_life.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use std::thread;
|
||||
|
||||
use clap::Parser;
|
||||
use rand::{distributions, Rng};
|
||||
|
||||
use servicepoint::*;
|
||||
|
||||
#[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).unwrap();
|
||||
let mut field = make_random_field(cli.probability);
|
||||
|
||||
loop {
|
||||
connection
|
||||
.send(Command::BitmapLinearWin(
|
||||
Origin(0, 0),
|
||||
field.clone(),
|
||||
CompressionCode::Lzma,
|
||||
))
|
||||
.expect("could not send");
|
||||
thread::sleep(FRAME_PACING);
|
||||
field = iteration(field);
|
||||
}
|
||||
}
|
||||
|
||||
fn iteration(field: PixelGrid) -> PixelGrid {
|
||||
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: &PixelGrid, 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) -> PixelGrid {
|
||||
let mut field = PixelGrid::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
|
||||
}
|
||||
32
crates/servicepoint/examples/moving_line.rs
Normal file
32
crates/servicepoint/examples/moving_line.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use std::thread;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use servicepoint::*;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "localhost:2342")]
|
||||
destination: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let connection = Connection::open(Cli::parse().destination).unwrap();
|
||||
|
||||
let mut pixels = PixelGrid::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);
|
||||
}
|
||||
connection
|
||||
.send(Command::BitmapLinearWin(
|
||||
Origin(0, 0),
|
||||
pixels.clone(),
|
||||
CompressionCode::Lzma,
|
||||
))
|
||||
.unwrap();
|
||||
thread::sleep(FRAME_PACING);
|
||||
}
|
||||
}
|
||||
60
crates/servicepoint/examples/random_brightness.rs
Normal file
60
crates/servicepoint/examples/random_brightness.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use rand::Rng;
|
||||
|
||||
use servicepoint::Command::{BitmapLinearWin, Brightness, CharBrightness};
|
||||
use servicepoint::*;
|
||||
|
||||
#[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).unwrap();
|
||||
let wait_duration = Duration::from_millis(cli.wait_ms);
|
||||
|
||||
// put all pixels in on state
|
||||
if cli.enable_all {
|
||||
let mut filled_grid = PixelGrid::max_sized();
|
||||
filled_grid.fill(true);
|
||||
|
||||
let command =
|
||||
BitmapLinearWin(Origin(0, 0), 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(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(x, y);
|
||||
let mut luma = ByteGrid::new(w, h);
|
||||
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
luma.set(x, y, rng.gen());
|
||||
}
|
||||
}
|
||||
|
||||
connection.send(CharBrightness(origin, luma)).unwrap();
|
||||
std::thread::sleep(wait_duration);
|
||||
}
|
||||
}
|
||||
43
crates/servicepoint/examples/wiping_clear.rs
Normal file
43
crates/servicepoint/examples/wiping_clear.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use servicepoint::*;
|
||||
|
||||
#[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).unwrap();
|
||||
|
||||
let mut enabled_pixels = PixelGrid::new(PIXEL_WIDTH, PIXEL_HEIGHT);
|
||||
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);
|
||||
}
|
||||
|
||||
// this works because the pixel grid has max size
|
||||
let pixel_data: Vec<u8> = enabled_pixels.clone().into();
|
||||
let bit_vec = BitVec::from(&*pixel_data);
|
||||
|
||||
connection
|
||||
.send(Command::BitmapLinearAnd(0, bit_vec, CompressionCode::Lzma))
|
||||
.unwrap();
|
||||
thread::sleep(sleep_duration);
|
||||
}
|
||||
}
|
||||
220
crates/servicepoint/src/bit_vec.rs
Normal file
220
crates/servicepoint/src/bit_vec.rs
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
use crate::DataRef;
|
||||
|
||||
/// A vector of bits
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BitVec {
|
||||
size: usize,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl BitVec {
|
||||
/// Create a new bit vector.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `size`: size in bits. Must be dividable by 8.
|
||||
///
|
||||
/// returns: bit vector with all bits set to false.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// When size is not a multiple of 8.
|
||||
#[must_use]
|
||||
pub fn new(size: usize) -> BitVec {
|
||||
assert_eq!(size % 8, 0);
|
||||
Self {
|
||||
size,
|
||||
data: vec![0; size / 8],
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the value of a bit.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `index`: the bit index to edit
|
||||
/// * `value`: the value to set the bit to
|
||||
///
|
||||
/// returns: old value of the bit
|
||||
pub fn set(&mut self, index: usize, value: bool) -> bool {
|
||||
let (byte_index, bit_mask) = self.get_indexes(index);
|
||||
|
||||
let byte = self.data[byte_index];
|
||||
let old_value = byte & bit_mask != 0;
|
||||
|
||||
self.data[byte_index] = if value {
|
||||
byte | bit_mask
|
||||
} else {
|
||||
byte & (u8::MAX ^ bit_mask)
|
||||
};
|
||||
|
||||
old_value
|
||||
}
|
||||
|
||||
/// Gets the value of a bit.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `index`: the bit index to read
|
||||
///
|
||||
/// returns: value of the bit
|
||||
#[must_use]
|
||||
pub fn get(&self, index: usize) -> bool {
|
||||
let (byte_index, bit_mask) = self.get_indexes(index);
|
||||
self.data[byte_index] & bit_mask != 0
|
||||
}
|
||||
|
||||
/// Sets all bits to the specified value
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value`: the value to set all bits to
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use servicepoint::BitVec;
|
||||
/// let mut vec = BitVec::new(8);
|
||||
/// vec.fill(true);
|
||||
/// ```
|
||||
pub fn fill(&mut self, value: bool) {
|
||||
let byte: u8 = if value { 0xFF } else { 0x00 };
|
||||
self.data.fill(byte);
|
||||
}
|
||||
|
||||
/// Gets the length in bits
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// returns true if length is 0.
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
|
||||
/// Calculates the byte index and bitmask for a specific bit in the vector
|
||||
fn get_indexes(&self, bit_index: usize) -> (usize, u8) {
|
||||
assert!(
|
||||
bit_index < self.size,
|
||||
"bit index {bit_index} is outside of range 0..<{}",
|
||||
self.size
|
||||
);
|
||||
|
||||
let byte_index = bit_index / 8;
|
||||
let bit_in_byte_index = 7 - bit_index % 8;
|
||||
let bit_mask: u8 = 1 << bit_in_byte_index;
|
||||
(byte_index, bit_mask)
|
||||
}
|
||||
}
|
||||
|
||||
impl DataRef for BitVec {
|
||||
fn data_ref_mut(&mut self) -> &mut [u8] {
|
||||
self.data.as_mut_slice()
|
||||
}
|
||||
|
||||
fn data_ref(&self) -> &[u8] {
|
||||
self.data.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BitVec> for Vec<u8> {
|
||||
/// Turns the `BitVec` into the underlying `Vec<u8>`
|
||||
fn from(value: BitVec) -> Self {
|
||||
value.data
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for BitVec {
|
||||
/// Interpret the data as a series of bits and load then into a new `BitVec` instance.
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self {
|
||||
size: value.len() * 8,
|
||||
data: Vec::from(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{BitVec, DataRef};
|
||||
|
||||
#[test]
|
||||
fn fill() {
|
||||
let mut vec = BitVec::new(8 * 3);
|
||||
assert_eq!(vec.data, [0x00, 0x00, 0x00]);
|
||||
|
||||
vec.fill(true);
|
||||
assert_eq!(vec.data, [0xFF, 0xFF, 0xFF]);
|
||||
|
||||
vec.fill(false);
|
||||
assert_eq!(vec.data, [0x00, 0x00, 0x00]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_set() {
|
||||
let mut vec = BitVec::new(8 * 3);
|
||||
assert!(!vec.get(1));
|
||||
assert!(!vec.get(11));
|
||||
|
||||
vec.set(1, true);
|
||||
vec.set(11, true);
|
||||
assert_eq!(vec.data, [0x40, 0x10, 0x00]);
|
||||
assert!(!vec.get(0));
|
||||
assert!(vec.get(1));
|
||||
assert!(vec.get(11));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load() {
|
||||
let mut vec = BitVec::new(8 * 3);
|
||||
vec.set(6, true);
|
||||
vec.set(7, true);
|
||||
vec.set(8, true);
|
||||
vec.set(9, true);
|
||||
vec.set(10, true);
|
||||
vec.set(vec.len() - 1, true);
|
||||
|
||||
assert_eq!(vec.data, [0x03, 0xE0, 0x01]);
|
||||
|
||||
let data: Vec<u8> = vec.into();
|
||||
|
||||
let vec = BitVec::from(&*data);
|
||||
assert_eq!(vec.data, [0x03, 0xE0, 0x01]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mut_data_ref() {
|
||||
let mut vec = BitVec::new(8 * 3);
|
||||
|
||||
let data_ref = vec.data_ref_mut();
|
||||
data_ref.copy_from_slice(&[0x40, 0x10, 0x00]);
|
||||
|
||||
assert_eq!(vec.data, [0x40, 0x10, 0x00]);
|
||||
assert!(vec.get(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_empty() {
|
||||
let vec = BitVec::new(8 * 3);
|
||||
assert!(!vec.is_empty());
|
||||
|
||||
let vec = BitVec::new(0);
|
||||
assert!(vec.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_returns_old() {
|
||||
let mut vec = BitVec::new(8);
|
||||
assert!(!vec.set(1, true));
|
||||
assert!(vec.set(1, true));
|
||||
assert!(vec.set(1, false));
|
||||
assert!(!vec.set(1, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_print() {
|
||||
let vec = BitVec::new(8 * 3);
|
||||
format!("{vec:?}");
|
||||
}
|
||||
}
|
||||
166
crates/servicepoint/src/byte_grid.rs
Normal file
166
crates/servicepoint/src/byte_grid.rs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
use crate::{DataRef, Grid};
|
||||
|
||||
/// A 2D grid of bytes
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ByteGrid {
|
||||
width: usize,
|
||||
height: usize,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ByteGrid {
|
||||
/// Loads a `ByteGrid` with the specified dimensions from the provided data.
|
||||
///
|
||||
/// returns: `ByteGrid` that contains a copy of the provided data
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - when the dimensions and data size do not match exactly.
|
||||
#[must_use]
|
||||
pub fn load(width: usize, height: usize, data: &[u8]) -> Self {
|
||||
assert_eq!(width * height, data.len());
|
||||
Self {
|
||||
data: Vec::from(data),
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_indexes(&self, x: usize, y: usize) {
|
||||
assert!(
|
||||
x < self.width,
|
||||
"cannot access byte {x}-{y} because x is outside of bounds 0..{}",
|
||||
self.width
|
||||
);
|
||||
assert!(
|
||||
y < self.height,
|
||||
"cannot access byte {x}-{y} because y is outside of bounds 0..{}",
|
||||
self.height
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Grid<u8> for ByteGrid {
|
||||
/// Creates a new `ByteGrid` with the specified dimensions.
|
||||
///
|
||||
/// returns: `ByteGrid` initialized to 0.
|
||||
fn new(width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
data: vec![0; width * height],
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&mut self, x: usize, y: usize, value: u8) -> u8 {
|
||||
self.check_indexes(x, y);
|
||||
let pos = &mut self.data[x + y * self.width];
|
||||
let old_val = *pos;
|
||||
*pos = value;
|
||||
old_val
|
||||
}
|
||||
|
||||
fn get(&self, x: usize, y: usize) -> u8 {
|
||||
self.check_indexes(x, y);
|
||||
self.data[x + y * self.width]
|
||||
}
|
||||
|
||||
fn fill(&mut self, value: u8) {
|
||||
self.data.fill(value);
|
||||
}
|
||||
|
||||
fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn window(&self, x: usize, y: usize, w: usize, h: usize) -> Self {
|
||||
let mut win = Self::new(w, h);
|
||||
for win_x in 0..w {
|
||||
for win_y in 0..h {
|
||||
let value = self.get(x + win_x, y + win_y);
|
||||
win.set(win_x, win_y, value);
|
||||
}
|
||||
}
|
||||
win
|
||||
}
|
||||
}
|
||||
|
||||
impl DataRef for ByteGrid {
|
||||
/// Get the underlying byte rows mutable
|
||||
fn data_ref_mut(&mut self) -> &mut [u8] {
|
||||
self.data.as_mut_slice()
|
||||
}
|
||||
|
||||
/// Get the underlying byte rows read only
|
||||
fn data_ref(&self) -> &[u8] {
|
||||
self.data.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ByteGrid> for Vec<u8> {
|
||||
/// Turn into the underlying `Vec<u8>` containing the rows of bytes.
|
||||
fn from(value: ByteGrid) -> Self {
|
||||
value.data
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{ByteGrid, DataRef, Grid};
|
||||
|
||||
#[test]
|
||||
fn fill() {
|
||||
let mut grid = ByteGrid::new(2, 2);
|
||||
assert_eq!(grid.data, [0x00, 0x00, 0x00, 0x00]);
|
||||
|
||||
grid.fill(42);
|
||||
assert_eq!(grid.data, [42; 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_set() {
|
||||
let mut grid = ByteGrid::new(2, 2);
|
||||
assert_eq!(grid.get(0, 0), 0);
|
||||
assert_eq!(grid.get(1, 1), 0);
|
||||
|
||||
grid.set(0, 0, 42);
|
||||
grid.set(1, 0, 23);
|
||||
assert_eq!(grid.data, [42, 23, 0, 0]);
|
||||
|
||||
assert_eq!(grid.get(0, 0), 42);
|
||||
assert_eq!(grid.get(1, 0), 23);
|
||||
assert_eq!(grid.get(1, 1), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load() {
|
||||
let mut grid = ByteGrid::new(2, 3);
|
||||
for x in 0..grid.width {
|
||||
for y in 0..grid.height {
|
||||
grid.set(x, y, (x + y) as u8);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(grid.data, [0, 1, 1, 2, 2, 3]);
|
||||
|
||||
let data: Vec<u8> = grid.into();
|
||||
|
||||
let grid = ByteGrid::load(2, 3, &data);
|
||||
assert_eq!(grid.data, [0, 1, 1, 2, 2, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mut_data_ref() {
|
||||
let mut vec = ByteGrid::new(2, 2);
|
||||
|
||||
let data_ref = vec.data_ref_mut();
|
||||
data_ref.copy_from_slice(&[1, 2, 3, 4]);
|
||||
|
||||
assert_eq!(vec.data, [1, 2, 3, 4]);
|
||||
assert_eq!(vec.get(1, 0), 2)
|
||||
}
|
||||
}
|
||||
690
crates/servicepoint/src/command.rs
Normal file
690
crates/servicepoint/src/command.rs
Normal file
|
|
@ -0,0 +1,690 @@
|
|||
use crate::command_code::CommandCode;
|
||||
use crate::compression::{into_compressed, into_decompressed};
|
||||
use crate::{
|
||||
BitVec, ByteGrid, CompressionCode, Grid, Header, Packet, PixelGrid,
|
||||
TILE_SIZE,
|
||||
};
|
||||
|
||||
/// An origin marks the top left position of a window sent to the display.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Origin(pub usize, pub usize);
|
||||
|
||||
impl std::ops::Add<Origin> for Origin {
|
||||
type Output = Origin;
|
||||
|
||||
fn add(self, rhs: Origin) -> Self::Output {
|
||||
let Origin(x1, y1) = self;
|
||||
let Origin(x2, y2) = rhs;
|
||||
Origin(x1 + x2, y1 + y2)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for documenting the meaning of the u16 in enum values
|
||||
pub type Offset = usize;
|
||||
|
||||
/// Type alias for documenting the meaning of the u16 in enum values
|
||||
pub type Brightness = u8;
|
||||
|
||||
/// A command to send to the display.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Command {
|
||||
/// Set all pixels to the off state
|
||||
Clear,
|
||||
/// Kills the udp daemon, usually results in a reboot of the display.
|
||||
HardReset,
|
||||
/// Slowly decrease brightness until off? Untested.
|
||||
FadeOut,
|
||||
/// Set the brightness of tiles
|
||||
CharBrightness(Origin, ByteGrid),
|
||||
/// Set the brightness of all tiles
|
||||
Brightness(Brightness),
|
||||
#[deprecated]
|
||||
/// Legacy command code, gets ignored by the real display.
|
||||
BitmapLegacy,
|
||||
/// Set pixel data starting at the offset.
|
||||
/// The contained `BitVec` is always uncompressed.
|
||||
BitmapLinear(Offset, BitVec, CompressionCode),
|
||||
/// Set pixel data according to an and-mask starting at the offset.
|
||||
/// The contained `BitVec` is always uncompressed.
|
||||
BitmapLinearAnd(Offset, BitVec, CompressionCode),
|
||||
/// Set pixel data according to an or-mask starting at the offset.
|
||||
/// The contained `BitVec` is always uncompressed.
|
||||
BitmapLinearOr(Offset, BitVec, CompressionCode),
|
||||
/// Set pixel data according to a xor-mask starting at the offset.
|
||||
/// The contained `BitVec` is always uncompressed.
|
||||
BitmapLinearXor(Offset, BitVec, CompressionCode),
|
||||
/// Show text on the screen. Note that the byte data has to be CP437 encoded.
|
||||
Cp437Data(Origin, ByteGrid),
|
||||
/// Sets a window of pixels to the specified values
|
||||
BitmapLinearWin(Origin, PixelGrid, CompressionCode),
|
||||
}
|
||||
|
||||
impl From<Command> for Packet {
|
||||
/// Move the `Command` into a `Packet` instance for sending.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn from(value: Command) -> Self {
|
||||
match value {
|
||||
Command::Clear => Command::command_code_only(CommandCode::Clear),
|
||||
Command::FadeOut => {
|
||||
Command::command_code_only(CommandCode::FadeOut)
|
||||
}
|
||||
Command::HardReset => {
|
||||
Command::command_code_only(CommandCode::HardReset)
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
Command::BitmapLegacy => {
|
||||
Command::command_code_only(CommandCode::BitmapLegacy)
|
||||
}
|
||||
Command::CharBrightness(Origin(x, y), grid) => Packet(
|
||||
Header(
|
||||
CommandCode::CharBrightness.into(),
|
||||
x as u16,
|
||||
y as u16,
|
||||
grid.width() as u16,
|
||||
grid.height() as u16,
|
||||
),
|
||||
grid.into(),
|
||||
),
|
||||
Command::Brightness(brightness) => Packet(
|
||||
Header(
|
||||
CommandCode::Brightness.into(),
|
||||
0x00000,
|
||||
0x0000,
|
||||
0x0000,
|
||||
0x0000,
|
||||
),
|
||||
vec![brightness],
|
||||
),
|
||||
Command::BitmapLinearWin(origin, pixels, compression) => {
|
||||
bitmap_win_into_packet(origin, pixels, compression)
|
||||
}
|
||||
Command::BitmapLinear(offset, bits, compression) => {
|
||||
Command::bitmap_linear_into_packet(
|
||||
CommandCode::BitmapLinear,
|
||||
offset,
|
||||
compression,
|
||||
bits.into(),
|
||||
)
|
||||
}
|
||||
Command::BitmapLinearAnd(offset, bits, compression) => {
|
||||
Command::bitmap_linear_into_packet(
|
||||
CommandCode::BitmapLinearAnd,
|
||||
offset,
|
||||
compression,
|
||||
bits.into(),
|
||||
)
|
||||
}
|
||||
Command::BitmapLinearOr(offset, bits, compression) => {
|
||||
Command::bitmap_linear_into_packet(
|
||||
CommandCode::BitmapLinearOr,
|
||||
offset,
|
||||
compression,
|
||||
bits.into(),
|
||||
)
|
||||
}
|
||||
Command::BitmapLinearXor(offset, bits, compression) => {
|
||||
Command::bitmap_linear_into_packet(
|
||||
CommandCode::BitmapLinearXor,
|
||||
offset,
|
||||
compression,
|
||||
bits.into(),
|
||||
)
|
||||
}
|
||||
Command::Cp437Data(Origin(x, y), grid) => Packet(
|
||||
Header(
|
||||
CommandCode::Cp437Data.into(),
|
||||
x as u16,
|
||||
y as u16,
|
||||
grid.width() as u16,
|
||||
grid.height() as u16,
|
||||
),
|
||||
grid.into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn bitmap_win_into_packet(
|
||||
origin: Origin,
|
||||
pixels: PixelGrid,
|
||||
compression: CompressionCode,
|
||||
) -> Packet {
|
||||
let Origin(pixel_x, pixel_y) = origin;
|
||||
debug_assert_eq!(pixel_x % 8, 0);
|
||||
debug_assert_eq!(pixels.width() % 8, 0);
|
||||
|
||||
let tile_x = (pixel_x / TILE_SIZE) as u16;
|
||||
let tile_w = (pixels.width() / TILE_SIZE) as u16;
|
||||
let pixel_h = pixels.height() as u16;
|
||||
let payload = into_compressed(compression, pixels.into());
|
||||
let command = match compression {
|
||||
CompressionCode::Uncompressed => {
|
||||
CommandCode::BitmapLinearWinUncompressed
|
||||
}
|
||||
#[cfg(feature = "compression_zlib")]
|
||||
CompressionCode::Zlib => CommandCode::BitmapLinearWinZlib,
|
||||
#[cfg(feature = "compression_bzip2")]
|
||||
CompressionCode::Bzip2 => CommandCode::BitmapLinearWinBzip2,
|
||||
#[cfg(feature = "compression_lzma")]
|
||||
CompressionCode::Lzma => CommandCode::BitmapLinearWinLzma,
|
||||
#[cfg(feature = "compression_zstd")]
|
||||
CompressionCode::Zstd => CommandCode::BitmapLinearWinZstd,
|
||||
};
|
||||
|
||||
Packet(
|
||||
Header(command.into(), tile_x, pixel_y as u16, tile_w, pixel_h),
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Err values for `Command::try_from`.
|
||||
#[derive(PartialEq)]
|
||||
pub enum TryFromPacketError {
|
||||
/// the contained command code does not correspond to a known command
|
||||
InvalidCommand(u16),
|
||||
/// the expected payload size was n, but size m 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.
|
||||
ExtraneousHeaderValues,
|
||||
/// The contained compression code is not known. This could be of disabled features.
|
||||
InvalidCompressionCode(u16),
|
||||
/// Decompression of the payload failed. This can be caused by corrupted packets.
|
||||
DecompressionFailed,
|
||||
}
|
||||
|
||||
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(command_u16, a, b, c, d), _) = packet;
|
||||
let command_code = match CommandCode::try_from(command_u16) {
|
||||
Err(()) => {
|
||||
return Err(TryFromPacketError::InvalidCommand(command_u16));
|
||||
}
|
||||
Ok(value) => value,
|
||||
};
|
||||
|
||||
match command_code {
|
||||
CommandCode::Clear => match Self::check_command_only(packet) {
|
||||
Some(err) => Err(err),
|
||||
None => Ok(Command::Clear),
|
||||
},
|
||||
CommandCode::Brightness => {
|
||||
let Packet(header, payload) = packet;
|
||||
if payload.len() != 1 {
|
||||
return Err(TryFromPacketError::UnexpectedPayloadSize(
|
||||
1,
|
||||
payload.len(),
|
||||
));
|
||||
}
|
||||
|
||||
let Header(_, a, b, c, d) = header;
|
||||
if a != 0 || b != 0 || c != 0 || d != 0 {
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
} else {
|
||||
Ok(Command::Brightness(payload[0]))
|
||||
}
|
||||
}
|
||||
CommandCode::HardReset => match Self::check_command_only(packet) {
|
||||
Some(err) => Err(err),
|
||||
None => Ok(Command::HardReset),
|
||||
},
|
||||
CommandCode::FadeOut => match Self::check_command_only(packet) {
|
||||
Some(err) => Err(err),
|
||||
None => Ok(Command::FadeOut),
|
||||
},
|
||||
CommandCode::Cp437Data => {
|
||||
let Packet(_, payload) = packet;
|
||||
Ok(Command::Cp437Data(
|
||||
Origin(a as usize, b as usize),
|
||||
ByteGrid::load(c as usize, d as usize, &payload),
|
||||
))
|
||||
}
|
||||
CommandCode::CharBrightness => {
|
||||
let Packet(_, payload) = packet;
|
||||
Ok(Command::CharBrightness(
|
||||
Origin(a as usize, b as usize),
|
||||
ByteGrid::load(c as usize, d as usize, &payload),
|
||||
))
|
||||
}
|
||||
#[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(_, tiles_x, pixels_y, tile_w, pixel_h), payload) =
|
||||
packet;
|
||||
|
||||
let payload = match into_decompressed(compression, payload) {
|
||||
None => return Err(TryFromPacketError::DecompressionFailed),
|
||||
Some(decompressed) => decompressed,
|
||||
};
|
||||
|
||||
Ok(Command::BitmapLinearWin(
|
||||
Origin(tiles_x as usize * TILE_SIZE, pixels_y as usize),
|
||||
PixelGrid::load(
|
||||
tile_w as usize * TILE_SIZE,
|
||||
pixel_h as usize,
|
||||
&payload,
|
||||
),
|
||||
compression,
|
||||
))
|
||||
}
|
||||
|
||||
/// Helper method for `BitMapLinear*`-Commands into `Packet`
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn bitmap_linear_into_packet(
|
||||
command: CommandCode,
|
||||
offset: Offset,
|
||||
compression: CompressionCode,
|
||||
payload: Vec<u8>,
|
||||
) -> Packet {
|
||||
let length = payload.len() as u16;
|
||||
let payload = into_compressed(compression, payload);
|
||||
Packet(
|
||||
Header(
|
||||
command.into(),
|
||||
offset as u16,
|
||||
length,
|
||||
compression.into(),
|
||||
0,
|
||||
),
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
/// Helper method for creating empty packets only containing the command code
|
||||
fn command_code_only(code: CommandCode) -> Packet {
|
||||
Packet(Header(code.into(), 0x0000, 0x0000, 0x0000, 0x0000), vec![])
|
||||
}
|
||||
|
||||
/// Helper method for checking that a packet is empty and only contains a command code
|
||||
fn check_command_only(packet: Packet) -> Option<TryFromPacketError> {
|
||||
let Packet(Header(_, a, b, c, d), payload) = packet;
|
||||
if !payload.is_empty() {
|
||||
Some(TryFromPacketError::UnexpectedPayloadSize(0, payload.len()))
|
||||
} else if a != 0 || b != 0 || c != 0 || d != 0 {
|
||||
Some(TryFromPacketError::ExtraneousHeaderValues)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method for Packets into `BitMapLinear*`-Commands
|
||||
fn packet_into_linear_bitmap(
|
||||
packet: Packet,
|
||||
) -> Result<(BitVec, CompressionCode), TryFromPacketError> {
|
||||
let Packet(Header(_, _, length, sub, 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(&*payload), sub))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::command::TryFromPacketError;
|
||||
use crate::command_code::CommandCode;
|
||||
use crate::{
|
||||
BitVec, ByteGrid, Command, CompressionCode, Grid, Header, Origin,
|
||||
Packet, PixelGrid,
|
||||
};
|
||||
|
||||
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(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn round_trip_bitmap_legacy() {
|
||||
round_trip(Command::BitmapLegacy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_char_brightness() {
|
||||
round_trip(Command::CharBrightness(Origin(5, 2), ByteGrid::new(7, 5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_cp437_data() {
|
||||
round_trip(Command::Cp437Data(Origin(5, 2), ByteGrid::new(7, 5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_bitmap_linear() {
|
||||
for compression in all_compressions().to_owned() {
|
||||
round_trip(Command::BitmapLinear(23, BitVec::new(40), compression));
|
||||
round_trip(Command::BitmapLinearAnd(
|
||||
23,
|
||||
BitVec::new(40),
|
||||
compression,
|
||||
));
|
||||
round_trip(Command::BitmapLinearOr(
|
||||
23,
|
||||
BitVec::new(40),
|
||||
compression,
|
||||
));
|
||||
round_trip(Command::BitmapLinearXor(
|
||||
23,
|
||||
BitVec::new(40),
|
||||
compression,
|
||||
));
|
||||
round_trip(Command::BitmapLinearWin(
|
||||
Origin(0, 0),
|
||||
PixelGrid::max_sized(),
|
||||
compression,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_invalid_command() {
|
||||
let p = Packet(Header(0xFF, 0x00, 0x00, 0x00, 0x00), 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(CommandCode::Clear.into(), 0x05, 0x00, 0x00, 0x00),
|
||||
vec![],
|
||||
);
|
||||
let result = Command::try_from(p);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_extraneous_header_values_brightness() {
|
||||
let p = Packet(
|
||||
Header(CommandCode::Brightness.into(), 0x00, 0x13, 0x37, 0x00),
|
||||
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(CommandCode::HardReset.into(), 0x00, 0x00, 0x00, 0x01),
|
||||
vec![],
|
||||
);
|
||||
let result = Command::try_from(p);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_extraneous_header_fade_out() {
|
||||
let p = Packet(
|
||||
Header(CommandCode::FadeOut.into(), 0x10, 0x00, 0x00, 0x01),
|
||||
vec![],
|
||||
);
|
||||
let result = Command::try_from(p);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_unexpected_payload() {
|
||||
let p = Packet(
|
||||
Header(CommandCode::FadeOut.into(), 0x00, 0x00, 0x00, 0x00),
|
||||
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().to_owned() {
|
||||
let p: Packet = Command::BitmapLinearWin(
|
||||
Origin(16, 8),
|
||||
PixelGrid::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().to_owned() {
|
||||
let p: Packet =
|
||||
Command::BitmapLinearAnd(0, BitVec::new(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(CommandCode::Brightness.into(), 0, 0, 0, 0),
|
||||
vec!(),
|
||||
)),
|
||||
Err(TryFromPacketError::UnexpectedPayloadSize(1, 0))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Command::try_from(Packet(
|
||||
Header(CommandCode::Brightness.into(), 0, 0, 0, 0),
|
||||
vec!(0, 0),
|
||||
)),
|
||||
Err(TryFromPacketError::UnexpectedPayloadSize(1, 2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_reserved_used() {
|
||||
let Packet(header, payload) = Command::BitmapLinear(
|
||||
0,
|
||||
BitVec::new(8),
|
||||
CompressionCode::Uncompressed,
|
||||
)
|
||||
.into();
|
||||
let Header(command, offset, length, sub, _reserved) = header;
|
||||
let p = Packet(Header(command, offset, length, sub, 69), payload);
|
||||
assert_eq!(
|
||||
Command::try_from(p),
|
||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_invalid_compression() {
|
||||
let Packet(header, payload) = Command::BitmapLinear(
|
||||
0,
|
||||
BitVec::new(8),
|
||||
CompressionCode::Uncompressed,
|
||||
)
|
||||
.into();
|
||||
let Header(command, offset, length, _sub, reserved) = header;
|
||||
let p = Packet(Header(command, offset, length, 42, 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::new(8),
|
||||
CompressionCode::Uncompressed,
|
||||
)
|
||||
.into();
|
||||
let Header(command, offset, length, compression, reserved) = header;
|
||||
let p = Packet(
|
||||
Header(command, offset, 420, compression, reserved),
|
||||
payload,
|
||||
);
|
||||
assert_eq!(
|
||||
Command::try_from(p),
|
||||
Err(TryFromPacketError::UnexpectedPayloadSize(
|
||||
420,
|
||||
length as usize,
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn origin_add() {
|
||||
assert_eq!(Origin(4, 2), Origin(1, 0) + Origin(3, 2));
|
||||
}
|
||||
}
|
||||
99
crates/servicepoint/src/command_code.rs
Normal file
99
crates/servicepoint/src/command_code.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/// The u16 command codes used for the `Commands`.
|
||||
#[repr(u16)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum CommandCode {
|
||||
Clear = 0x0002,
|
||||
Cp437Data = 0x0003,
|
||||
CharBrightness = 0x0005,
|
||||
Brightness = 0x0007,
|
||||
HardReset = 0x000b,
|
||||
FadeOut = 0x000d,
|
||||
#[deprecated]
|
||||
BitmapLegacy = 0x0010,
|
||||
BitmapLinear = 0x0012,
|
||||
BitmapLinearWinUncompressed = 0x0013,
|
||||
BitmapLinearAnd = 0x0014,
|
||||
BitmapLinearOr = 0x0015,
|
||||
BitmapLinearXor = 0x0016,
|
||||
#[cfg(feature = "compression_zlib")]
|
||||
BitmapLinearWinZlib = 0x0017,
|
||||
#[cfg(feature = "compression_bzip2")]
|
||||
BitmapLinearWinBzip2 = 0x0018,
|
||||
#[cfg(feature = "compression_lzma")]
|
||||
BitmapLinearWinLzma = 0x0019,
|
||||
#[cfg(feature = "compression_zstd")]
|
||||
BitmapLinearWinZstd = 0x001A,
|
||||
}
|
||||
|
||||
impl From<CommandCode> for u16 {
|
||||
/// returns the u16 command code corresponding to the enum value
|
||||
fn from(value: CommandCode) -> Self {
|
||||
value as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for CommandCode {
|
||||
type Error = ();
|
||||
|
||||
/// Returns the enum value for the specified `u16` or `Error` if the code is unknown.
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
value if value == CommandCode::Clear as u16 => {
|
||||
Ok(CommandCode::Clear)
|
||||
}
|
||||
value if value == CommandCode::Cp437Data as u16 => {
|
||||
Ok(CommandCode::Cp437Data)
|
||||
}
|
||||
value if value == CommandCode::CharBrightness as u16 => {
|
||||
Ok(CommandCode::CharBrightness)
|
||||
}
|
||||
value if value == CommandCode::Brightness as u16 => {
|
||||
Ok(CommandCode::Brightness)
|
||||
}
|
||||
value if value == CommandCode::HardReset as u16 => {
|
||||
Ok(CommandCode::HardReset)
|
||||
}
|
||||
value if value == CommandCode::FadeOut as u16 => {
|
||||
Ok(CommandCode::FadeOut)
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
value if value == CommandCode::BitmapLegacy as u16 => {
|
||||
Ok(CommandCode::BitmapLegacy)
|
||||
}
|
||||
value if value == CommandCode::BitmapLinear as u16 => {
|
||||
Ok(CommandCode::BitmapLinear)
|
||||
}
|
||||
value
|
||||
if value == CommandCode::BitmapLinearWinUncompressed as u16 =>
|
||||
{
|
||||
Ok(CommandCode::BitmapLinearWinUncompressed)
|
||||
}
|
||||
value if value == CommandCode::BitmapLinearAnd as u16 => {
|
||||
Ok(CommandCode::BitmapLinearAnd)
|
||||
}
|
||||
value if value == CommandCode::BitmapLinearOr as u16 => {
|
||||
Ok(CommandCode::BitmapLinearOr)
|
||||
}
|
||||
value if value == CommandCode::BitmapLinearXor as u16 => {
|
||||
Ok(CommandCode::BitmapLinearXor)
|
||||
}
|
||||
#[cfg(feature = "compression_zstd")]
|
||||
value if value == CommandCode::BitmapLinearWinZstd as u16 => {
|
||||
Ok(CommandCode::BitmapLinearWinZstd)
|
||||
}
|
||||
#[cfg(feature = "compression_lzma")]
|
||||
value if value == CommandCode::BitmapLinearWinLzma as u16 => {
|
||||
Ok(CommandCode::BitmapLinearWinLzma)
|
||||
}
|
||||
#[cfg(feature = "compression_zlib")]
|
||||
value if value == CommandCode::BitmapLinearWinZlib as u16 => {
|
||||
Ok(CommandCode::BitmapLinearWinZlib)
|
||||
}
|
||||
#[cfg(feature = "compression_bzip2")]
|
||||
value if value == CommandCode::BitmapLinearWinBzip2 as u16 => {
|
||||
Ok(CommandCode::BitmapLinearWinBzip2)
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
115
crates/servicepoint/src/compression.rs
Normal file
115
crates/servicepoint/src/compression.rs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#[allow(unused)]
|
||||
use std::io::{Read, Write};
|
||||
|
||||
#[cfg(feature = "compression_bzip2")]
|
||||
use bzip2::read::{BzDecoder, BzEncoder};
|
||||
#[cfg(feature = "compression_zlib")]
|
||||
use flate2::{FlushCompress, FlushDecompress, Status};
|
||||
#[cfg(feature = "compression_zstd")]
|
||||
use zstd::{Decoder as ZstdDecoder, Encoder as ZstdEncoder};
|
||||
|
||||
use crate::{CompressionCode, Payload};
|
||||
|
||||
pub(crate) fn into_decompressed(
|
||||
kind: CompressionCode,
|
||||
payload: Payload,
|
||||
) -> Option<Payload> {
|
||||
match kind {
|
||||
CompressionCode::Uncompressed => Some(payload),
|
||||
#[cfg(feature = "compression_zlib")]
|
||||
CompressionCode::Zlib => {
|
||||
let mut decompress = flate2::Decompress::new(true);
|
||||
let mut buffer = [0u8; 10000];
|
||||
|
||||
let status = match decompress.decompress(
|
||||
&payload,
|
||||
&mut buffer,
|
||||
FlushDecompress::Finish,
|
||||
) {
|
||||
Err(_) => return None,
|
||||
Ok(status) => status,
|
||||
};
|
||||
|
||||
match status {
|
||||
Status::Ok => None,
|
||||
Status::BufError => None,
|
||||
Status::StreamEnd => Some(
|
||||
buffer[0..(decompress.total_out() as usize)].to_owned(),
|
||||
),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "compression_bzip2")]
|
||||
CompressionCode::Bzip2 => {
|
||||
let mut decoder = BzDecoder::new(&*payload);
|
||||
let mut decompressed = vec![];
|
||||
match decoder.read_to_end(&mut decompressed) {
|
||||
Err(_) => None,
|
||||
Ok(_) => Some(decompressed),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "compression_lzma")]
|
||||
CompressionCode::Lzma => match lzma::decompress(&payload) {
|
||||
Err(_) => None,
|
||||
Ok(decompressed) => Some(decompressed),
|
||||
},
|
||||
#[cfg(feature = "compression_zstd")]
|
||||
CompressionCode::Zstd => {
|
||||
let mut decoder = match ZstdDecoder::new(&*payload) {
|
||||
Err(_) => return None,
|
||||
Ok(value) => value,
|
||||
};
|
||||
let mut decompressed = vec![];
|
||||
match decoder.read_to_end(&mut decompressed) {
|
||||
Err(_) => None,
|
||||
Ok(_) => Some(decompressed),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_compressed(
|
||||
kind: CompressionCode,
|
||||
payload: Payload,
|
||||
) -> Payload {
|
||||
match kind {
|
||||
CompressionCode::Uncompressed => payload,
|
||||
#[cfg(feature = "compression_zlib")]
|
||||
CompressionCode::Zlib => {
|
||||
let mut compress =
|
||||
flate2::Compress::new(flate2::Compression::fast(), true);
|
||||
let mut buffer = [0u8; 10000];
|
||||
|
||||
match compress
|
||||
.compress(&payload, &mut buffer, FlushCompress::Finish)
|
||||
.expect("compress failed")
|
||||
{
|
||||
Status::Ok => panic!("buffer should be big enough"),
|
||||
Status::BufError => panic!("BufError"),
|
||||
Status::StreamEnd => {}
|
||||
};
|
||||
buffer[..compress.total_out() as usize].to_owned()
|
||||
}
|
||||
#[cfg(feature = "compression_bzip2")]
|
||||
CompressionCode::Bzip2 => {
|
||||
let mut encoder =
|
||||
BzEncoder::new(&*payload, bzip2::Compression::fast());
|
||||
let mut compressed = vec![];
|
||||
match encoder.read_to_end(&mut compressed) {
|
||||
Err(err) => panic!("could not compress payload: {}", err),
|
||||
Ok(_) => compressed,
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "compression_lzma")]
|
||||
CompressionCode::Lzma => lzma::compress(&payload, 6).unwrap(),
|
||||
#[cfg(feature = "compression_zstd")]
|
||||
CompressionCode::Zstd => {
|
||||
let mut encoder =
|
||||
ZstdEncoder::new(vec![], zstd::DEFAULT_COMPRESSION_LEVEL)
|
||||
.expect("could not create encoder");
|
||||
encoder
|
||||
.write_all(&payload)
|
||||
.expect("could not compress payload");
|
||||
encoder.finish().expect("could not finish encoding")
|
||||
}
|
||||
}
|
||||
}
|
||||
49
crates/servicepoint/src/compression_code.rs
Normal file
49
crates/servicepoint/src/compression_code.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/// Specifies the kind of compression to use. Availability depends on features.
|
||||
#[repr(u16)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum CompressionCode {
|
||||
Uncompressed = 0x0,
|
||||
#[cfg(feature = "compression_zlib")]
|
||||
Zlib = 0x677a,
|
||||
#[cfg(feature = "compression_bzip2")]
|
||||
Bzip2 = 0x627a,
|
||||
#[cfg(feature = "compression_lzma")]
|
||||
Lzma = 0x6c7a,
|
||||
#[cfg(feature = "compression_zstd")]
|
||||
Zstd = 0x7a73,
|
||||
}
|
||||
|
||||
impl From<CompressionCode> for u16 {
|
||||
fn from(value: CompressionCode) -> Self {
|
||||
value as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for CompressionCode {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
value if value == CompressionCode::Uncompressed as u16 => {
|
||||
Ok(CompressionCode::Uncompressed)
|
||||
}
|
||||
#[cfg(feature = "compression_zlib")]
|
||||
value if value == CompressionCode::Zlib as u16 => {
|
||||
Ok(CompressionCode::Zlib)
|
||||
}
|
||||
#[cfg(feature = "compression_bzip2")]
|
||||
value if value == CompressionCode::Bzip2 as u16 => {
|
||||
Ok(CompressionCode::Bzip2)
|
||||
}
|
||||
#[cfg(feature = "compression_lzma")]
|
||||
value if value == CompressionCode::Lzma as u16 => {
|
||||
Ok(CompressionCode::Lzma)
|
||||
}
|
||||
#[cfg(feature = "compression_zstd")]
|
||||
value if value == CompressionCode::Zstd as u16 => {
|
||||
Ok(CompressionCode::Zstd)
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
75
crates/servicepoint/src/connection.rs
Normal file
75
crates/servicepoint/src/connection.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
use std::fmt::Debug;
|
||||
use std::net::{ToSocketAddrs, UdpSocket};
|
||||
|
||||
use log::{debug, info};
|
||||
|
||||
use crate::Packet;
|
||||
|
||||
/// A connection to the display.
|
||||
pub struct Connection {
|
||||
socket: UdpSocket,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
/// Open a new UDP socket and connect to the provided host.
|
||||
///
|
||||
/// Note that this is UDP, which means that the open call can succeed even if the display is unreachable.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Any errors resulting from binding the udp socket.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// let connection = servicepoint::Connection::open("172.23.42.29:2342")
|
||||
/// .expect("connection failed");
|
||||
/// ```
|
||||
pub fn open(addr: impl ToSocketAddrs + Debug) -> std::io::Result<Self> {
|
||||
info!("connecting to {addr:?}");
|
||||
let socket = UdpSocket::bind("0.0.0.0:0")?;
|
||||
socket.connect(addr)?;
|
||||
Ok(Self { socket })
|
||||
}
|
||||
|
||||
/// Send something packet-like to the display. Usually this is in the form of a Command.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `packet`: the packet-like to send
|
||||
///
|
||||
/// returns: Ok if packet was sent, otherwise socket error
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Any errors produced while sending using the underlying socket.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use servicepoint::{Command, CompressionCode, Grid, PixelGrid};
|
||||
/// let connection = servicepoint::Connection::open("172.23.42.29:2342")
|
||||
/// .expect("connection failed");
|
||||
///
|
||||
/// // turn off all pixels
|
||||
/// connection.send(Command::Clear)
|
||||
/// .expect("send failed");
|
||||
///
|
||||
/// // turn on all pixels
|
||||
/// let mut pixels = PixelGrid::max_sized();
|
||||
/// pixels.fill(true);
|
||||
///
|
||||
/// // send pixels to display
|
||||
/// connection.send(Command::BitmapLinearWin(servicepoint::Origin(0, 0), pixels, CompressionCode::Uncompressed))
|
||||
/// .expect("send failed");
|
||||
/// ```
|
||||
pub fn send(
|
||||
&self,
|
||||
packet: impl Into<Packet>,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let packet = packet.into();
|
||||
debug!("sending {packet:?}");
|
||||
let data: Vec<u8> = packet.into();
|
||||
self.socket.send(&data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
11
crates/servicepoint/src/data_ref.rs
Normal file
11
crates/servicepoint/src/data_ref.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/// A trait for getting the underlying raw byte slices of data containers.
|
||||
///
|
||||
/// The expectation is that you can create an equal instance with this data given the additional
|
||||
/// metadata needed.
|
||||
pub trait DataRef {
|
||||
/// Get the underlying bytes writable.
|
||||
fn data_ref_mut(&mut self) -> &mut [u8];
|
||||
|
||||
/// Get the underlying bytes read-only.
|
||||
fn data_ref(&self) -> &[u8];
|
||||
}
|
||||
53
crates/servicepoint/src/grid.rs
Normal file
53
crates/servicepoint/src/grid.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
pub trait Grid<T> {
|
||||
#[must_use]
|
||||
fn new(width: usize, height: usize) -> Self;
|
||||
|
||||
/// Sets the value at the specified position
|
||||
///
|
||||
/// returns: the old value
|
||||
fn set(&mut self, x: usize, y: usize, value: T) -> T;
|
||||
|
||||
/// Get the current value at the specified position
|
||||
fn get(&self, x: usize, y: usize) -> T;
|
||||
|
||||
/// Sets all cells in the grid to the specified value
|
||||
fn fill(&mut self, value: T);
|
||||
|
||||
/// the size in x-direction
|
||||
fn width(&self) -> usize;
|
||||
|
||||
/// the height in y-direction
|
||||
fn height(&self) -> usize;
|
||||
|
||||
/// Creates a new instance containing the specified window.
|
||||
///
|
||||
/// Use concrete types to avoid boxing.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `x`: column of the top left cell
|
||||
/// * `y`: row of the top left cell
|
||||
/// * `w`: size of window in x-direction
|
||||
/// * `h`: size of window in y-direction
|
||||
///
|
||||
/// returns: Self
|
||||
///
|
||||
/// # Examples
|
||||
/// To avoid boxing, this example is using the concrete type `ByteGrid`.
|
||||
/// ```
|
||||
/// use servicepoint::{ByteGrid, Grid};
|
||||
/// fn split(grid: ByteGrid) -> (ByteGrid, ByteGrid) {
|
||||
/// assert!(grid.width() >= 2);
|
||||
/// let split_x = grid.width() / 2;
|
||||
/// let right_w = grid.width() - split_x;
|
||||
///
|
||||
/// let left = grid.window(0, 0, split_x, grid.height());
|
||||
/// let right = grid.window(split_x, 0, right_w, grid.height());
|
||||
/// (left, right)
|
||||
/// }
|
||||
///
|
||||
/// let (l, r) = split(ByteGrid::new(9, 5));
|
||||
/// ```
|
||||
#[must_use]
|
||||
fn window(&self, x: usize, y: usize, w: usize, h: usize) -> Self;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue