move c# bindings into separate repository

This commit is contained in:
Vinzenz Schroeter 2025-02-16 13:59:41 +01:00
parent 2f7a2dfd62
commit f4bdff0e11
100 changed files with 157 additions and 12736 deletions

View file

@ -1 +0,0 @@
use flake

4
.gitignore vendored
View file

@ -4,4 +4,6 @@ out
.direnv
.envrc
result
mutants.*
mutants.*
bin
obj

6
.gitmodules vendored Normal file
View file

@ -0,0 +1,6 @@
[submodule "uniffi-bindgen-cs/servicepoint-binding-uniffi"]
path = uniffi-bindgen-cs/servicepoint-binding-uniffi
url = https://git.berlin.ccc.de/servicepoint/servicepoint-binding-uniffi
[submodule "servicepoint-binding-uniffi"]
path = servicepoint-binding-uniffi
url = https://git.berlin.ccc.de/servicepoint/servicepoint-binding-uniffi

557
Cargo.lock generated
View file

@ -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"
@ -225,19 +155,18 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "bzip2"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bafdbf26611df8c14810e268ddceda071c297570a5fb360ceddf617fe417ef58"
checksum = "75b89e7c29231c673a61a46e722602bcd138298f6b9e81e71119693534585f5c"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
version = "0.1.12+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
checksum = "72ebc2f1a417f01e1da30ef264ee86ae31d2dcd2d603ea283d3c244a883ca2a9"
dependencies = [
"cc",
"libc",
@ -276,30 +205,11 @@ 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"
version = "1.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b"
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
dependencies = [
"jobserver",
"libc",
@ -319,63 +229,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 +261,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 +270,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 +281,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 +291,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 +306,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 +329,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 +344,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 +351,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 +369,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"
@ -690,9 +411,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.2"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
dependencies = [
"adler2",
]
@ -743,15 +464,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 +512,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 +522,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 +589,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"
@ -961,21 +612,10 @@ dependencies = [
"servicepoint",
"thiserror 2.0.11",
"uniffi",
"uniffi-bindgen-cs",
"uniffi-bindgen-cs 0.8.3+v0.25.0",
"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 +646,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 +674,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 +743,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,18 +787,25 @@ 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)",
]
[[package]]
name = "uniffi-bindgen-cs"
version = "0.13.1"
dependencies = [
"uniffi-bindgen-cs 0.8.3+v0.25.0",
]
[[package]]
name = "uniffi-bindgen-go"
version = "0.2.2+v0.25.0"
@ -1246,15 +815,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 +841,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 +864,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 +888,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 +967,7 @@ dependencies = [
"quote",
"serde",
"syn 2.0.96",
"toml 0.5.11",
"toml",
"uniffi_build",
"uniffi_meta 0.25.3",
]
@ -1508,18 +1077,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 +1089,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 +1218,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 +1227,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"

View file

@ -1,17 +1,3 @@
[workspace]
resolver = "2"
members = [
"crates/servicepoint",
"crates/servicepoint_binding_c",
"crates/servicepoint_binding_c/examples/lang_c",
"crates/servicepoint_binding_uniffi"
]
[workspace.package]
version = "0.13.1"
[workspace.lints.rust]
missing-docs = "warn"
[workspace.dependencies]
thiserror = "2.0"
members = ["uniffi-bindgen-cs", "servicepoint-binding-uniffi"]

109
README.md
View file

@ -1,49 +1,90 @@
# servicepoint
[![crates.io](https://img.shields.io/crates/v/servicepoint.svg)](https://crates.io/crates/servicepoint)
[![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint)](https://crates.io/crates/servicepoint)
[![docs.rs](https://img.shields.io/docsrs/servicepoint)](https://docs.rs/servicepoint/latest/servicepoint/)
[![GPLv3 licensed](https://img.shields.io/crates/l/servicepoint)](./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 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.

View file

@ -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

View file

@ -1,67 +0,0 @@
# servicepoint
[![crates.io](https://img.shields.io/crates/v/servicepoint.svg)](https://crates.io/crates/servicepoint)
[![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint)](https://crates.io/crates/servicepoint)
[![docs.rs](https://img.shields.io/docsrs/servicepoint)](https://docs.rs/servicepoint/latest/servicepoint/)
[![GPLv3 licensed](https://img.shields.io/crates/l/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.

View file

@ -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");
}

View file

@ -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");
}

View file

@ -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
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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::*;
}

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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])
);
}
}

View file

@ -1,4 +0,0 @@
use crate::ValueGrid;
/// A 2d grid of bytes - see [ValueGrid].
pub type ByteGrid = ValueGrid<u8>;

View file

@ -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));
}
}

View file

@ -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))
);
}
}

View file

@ -1,214 +0,0 @@
/// The u16 command codes used for the [Command]s.
#[repr(u16)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
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,
Utf8Data = 0x0020,
#[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)
}
value if value == CommandCode::Utf8Data as u16 => {
Ok(CommandCode::Utf8Data)
}
_ => Err(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clear() {
assert_eq!(CommandCode::try_from(0x0002), Ok(CommandCode::Clear));
assert_eq!(u16::from(CommandCode::Clear), 0x0002);
}
#[test]
fn cp437_data() {
assert_eq!(CommandCode::try_from(0x0003), Ok(CommandCode::Cp437Data));
assert_eq!(u16::from(CommandCode::Cp437Data), 0x0003);
}
#[test]
fn char_brightness() {
assert_eq!(CommandCode::try_from(0x0005), Ok(CommandCode::CharBrightness));
assert_eq!(u16::from(CommandCode::CharBrightness), 0x0005);
}
#[test]
fn brightness() {
assert_eq!(CommandCode::try_from(0x0007), Ok(CommandCode::Brightness));
assert_eq!(u16::from(CommandCode::Brightness), 0x0007);
}
#[test]
fn hard_reset() {
assert_eq!(CommandCode::try_from(0x000b), Ok(CommandCode::HardReset));
assert_eq!(u16::from(CommandCode::HardReset), 0x000b);
}
#[test]
fn fade_out() {
assert_eq!(CommandCode::try_from(0x000d), Ok(CommandCode::FadeOut));
assert_eq!(u16::from(CommandCode::FadeOut), 0x000d);
}
#[test]
#[allow(deprecated)]
fn bitmap_legacy() {
assert_eq!(CommandCode::try_from(0x0010), Ok(CommandCode::BitmapLegacy));
assert_eq!(u16::from(CommandCode::BitmapLegacy), 0x0010);
}
#[test]
fn linear() {
assert_eq!(CommandCode::try_from(0x0012), Ok(CommandCode::BitmapLinear));
assert_eq!(u16::from(CommandCode::BitmapLinear), 0x0012);
}
#[test]
fn linear_and() {
assert_eq!(CommandCode::try_from(0x0014), Ok(CommandCode::BitmapLinearAnd));
assert_eq!(u16::from(CommandCode::BitmapLinearAnd), 0x0014);
}
#[test]
fn linear_xor() {
assert_eq!(CommandCode::try_from(0x0016), Ok(CommandCode::BitmapLinearXor));
assert_eq!(u16::from(CommandCode::BitmapLinearXor), 0x0016);
}
#[test]
#[cfg(feature = "compression_zlib")]
fn bitmap_win_zlib() {
assert_eq!(CommandCode::try_from(0x0017), Ok(CommandCode::BitmapLinearWinZlib));
assert_eq!(u16::from(CommandCode::BitmapLinearWinZlib), 0x0017);
}
#[test]
#[cfg(feature = "compression_bzip2")]
fn bitmap_win_bzip2() {
assert_eq!(CommandCode::try_from(0x0018), Ok(CommandCode::BitmapLinearWinBzip2));
assert_eq!(u16::from(CommandCode::BitmapLinearWinBzip2), 0x0018);
}
#[test]
#[cfg(feature = "compression_lzma")]
fn bitmap_win_lzma() {
assert_eq!(CommandCode::try_from(0x0019), Ok(CommandCode::BitmapLinearWinLzma));
assert_eq!(u16::from(CommandCode::BitmapLinearWinLzma), 0x0019);
}
#[test]
#[cfg(feature = "compression_zstd")]
fn bitmap_win_zstd() {
assert_eq!(CommandCode::try_from(0x001A), Ok(CommandCode::BitmapLinearWinZstd));
assert_eq!(u16::from(CommandCode::BitmapLinearWinZstd), 0x001A);
}
#[test]
fn bitmap_win_uncompressed() {
assert_eq!(CommandCode::try_from(0x0013), Ok(CommandCode::BitmapLinearWinUncompressed));
assert_eq!(u16::from(CommandCode::BitmapLinearWinUncompressed), 0x0013);
}
#[test]
fn utf8_data() {
assert_eq!(CommandCode::try_from(0x0020), Ok(CommandCode::Utf8Data));
assert_eq!(u16::from(CommandCode::Utf8Data), 0x0020);
}
#[test]
fn linear_or() {
assert_eq!(CommandCode::try_from(0x0015), Ok(CommandCode::BitmapLinearOr));
assert_eq!(u16::from(CommandCode::BitmapLinearOr), 0x0015);
}
}

View file

@ -1,115 +0,0 @@
#[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")
}
}
}

View file

@ -1,120 +0,0 @@
/// Specifies the kind of compression to use. Availability depends on features.
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{Command, CompressionCode, Origin, Bitmap};
/// // create command without payload compression
/// # let pixels = Bitmap::max_sized();
/// _ = Command::BitmapLinearWin(Origin::ZERO, pixels, CompressionCode::Uncompressed);
///
/// // create command with payload compressed with lzma and appropriate header flags
/// # let pixels = Bitmap::max_sized();
/// _ = Command::BitmapLinearWin(Origin::ZERO, pixels, CompressionCode::Lzma);
/// ```
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CompressionCode {
/// no compression
Uncompressed = 0x0,
#[cfg(feature = "compression_zlib")]
/// compress using flate2 with zlib header
Zlib = 0x677a,
#[cfg(feature = "compression_bzip2")]
/// compress using bzip2
Bzip2 = 0x627a,
#[cfg(feature = "compression_lzma")]
/// compress using lzma
Lzma = 0x6c7a,
#[cfg(feature = "compression_zstd")]
/// compress using Zstandard
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(()),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn uncompressed() {
assert_eq!(
CompressionCode::try_from(0x0000),
Ok(CompressionCode::Uncompressed)
);
assert_eq!(u16::from(CompressionCode::Uncompressed), 0x0000);
}
#[test]
#[cfg(feature = "compression_zlib")]
fn zlib() {
assert_eq!(
CompressionCode::try_from(0x677a),
Ok(CompressionCode::Zlib)
);
assert_eq!(u16::from(CompressionCode::Zlib), 0x677a);
}
#[test]
#[cfg(feature = "compression_bzip2")]
fn bzip2() {
assert_eq!(
CompressionCode::try_from(0x627a),
Ok(CompressionCode::Bzip2)
);
assert_eq!(u16::from(CompressionCode::Bzip2), 0x627a);
}
#[test]
#[cfg(feature = "compression_lzma")]
fn lzma() {
assert_eq!(
CompressionCode::try_from(0x6c7a),
Ok(CompressionCode::Lzma)
);
assert_eq!(u16::from(CompressionCode::Lzma), 0x6c7a);
}
#[test]
#[cfg(feature = "compression_zstd")]
fn zstd() {
assert_eq!(
CompressionCode::try_from(0x7a73),
Ok(CompressionCode::Zstd)
);
assert_eq!(u16::from(CompressionCode::Zstd), 0x7a73);
}
}

View file

@ -1,175 +0,0 @@
use crate::packet::Packet;
use std::fmt::Debug;
/// A connection to the display.
///
/// Used to send [Packets][Packet] or [Commands][crate::Command].
///
/// # Examples
/// ```rust
/// let connection = servicepoint::Connection::open("127.0.0.1:2342")
/// .expect("connection failed");
/// connection.send(servicepoint::Command::Clear)
/// .expect("send failed");
/// ```
#[derive(Debug)]
pub enum Connection {
/// A connection using the UDP protocol.
///
/// Use this when sending commands directly to the display.
///
/// Requires the feature "protocol_udp" which is enabled by default.
#[cfg(feature = "protocol_udp")]
Udp(std::net::UdpSocket),
/// A connection using the WebSocket protocol.
///
/// Note that you will need to forward the WebSocket messages via UDP to the display.
/// You can use [servicepoint-websocket-relay] for this.
///
/// To create a new WebSocket automatically, use [Connection::open_websocket].
///
/// Requires the feature "protocol_websocket" which is disabled by default.
///
/// [servicepoint-websocket-relay]: https://github.com/kaesaecracker/servicepoint-websocket-relay
#[cfg(feature = "protocol_websocket")]
WebSocket(
std::sync::Mutex<
tungstenite::WebSocket<
tungstenite::stream::MaybeTlsStream<std::net::TcpStream>,
>,
>,
),
/// A fake connection for testing that does not actually send anything.
Fake,
}
#[derive(Debug, thiserror::Error)]
pub enum SendError {
#[error("IO error occurred while sending")]
IoError(#[from] std::io::Error),
#[cfg(feature = "protocol_websocket")]
#[error("WebSocket error occurred while sending")]
WebsocketError(#[from] tungstenite::Error),
}
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.
///
/// The address of the display in CCCB is `172.23.42.29:2342`.
///
/// # Errors
///
/// Any errors resulting from binding the udp socket.
///
/// # Examples
/// ```rust
/// let connection = servicepoint::Connection::open("127.0.0.1:2342")
/// .expect("connection failed");
/// ```
#[cfg(feature = "protocol_udp")]
pub fn open(
addr: impl std::net::ToSocketAddrs + Debug,
) -> std::io::Result<Self> {
log::info!("connecting to {addr:?}");
let socket = std::net::UdpSocket::bind("0.0.0.0:0")?;
socket.connect(addr)?;
Ok(Self::Udp(socket))
}
/// Open a new WebSocket and connect to the provided host.
///
/// Requires the feature "protocol_websocket" which is disabled by default.
///
/// # Examples
///
/// ```no_run
/// use tungstenite::http::Uri;
/// use servicepoint::{Command, Connection};
/// let uri = "ws://localhost:8080".parse().unwrap();
/// let mut connection = Connection::open_websocket(uri)
/// .expect("could not connect");
/// connection.send(Command::Clear)
/// .expect("send failed");
/// ```
#[cfg(feature = "protocol_websocket")]
pub fn open_websocket(
uri: tungstenite::http::Uri,
) -> tungstenite::Result<Self> {
use tungstenite::{
client::IntoClientRequest, connect, ClientRequestBuilder,
};
log::info!("connecting to {uri:?}");
let request = ClientRequestBuilder::new(uri).into_client_request()?;
let (sock, _) = connect(request)?;
Ok(Self::WebSocket(std::sync::Mutex::new(sock)))
}
/// Send something packet-like to the display. Usually this is in the form of a Command.
///
/// # Arguments
///
/// - `packet`: the packet-like to send
///
/// returns: true if packet was sent, otherwise false
///
/// # Examples
///
/// ```rust
/// let connection = servicepoint::Connection::Fake;
/// // turn off all pixels on display
/// connection.send(servicepoint::Command::Clear)
/// .expect("send failed");
/// ```
pub fn send(&self, packet: impl Into<Packet>) -> Result<(), SendError> {
let packet = packet.into();
log::debug!("sending {packet:?}");
let data: Vec<u8> = packet.into();
match self {
#[cfg(feature = "protocol_udp")]
Connection::Udp(socket) => {
socket
.send(&data)
.map_err(SendError::IoError)
.map(move |_| ()) // ignore Ok value
}
#[cfg(feature = "protocol_websocket")]
Connection::WebSocket(socket) => {
let mut socket = socket.lock().unwrap();
socket
.send(tungstenite::Message::Binary(data.into()))
.map_err(SendError::WebsocketError)
}
Connection::Fake => {
let _ = data;
Ok(())
}
}
}
}
impl Drop for Connection {
fn drop(&mut self) {
#[cfg(feature = "protocol_websocket")]
if let Connection::WebSocket(sock) = self {
_ = sock.try_lock().map(move |mut sock| sock.close(None));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn send_fake() {
let data: &[u8] = &[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let packet = Packet::try_from(data).unwrap();
Connection::Fake.send(packet).unwrap()
}
}

View file

@ -1,75 +0,0 @@
use std::time::Duration;
/// size of a single tile in one dimension
pub const TILE_SIZE: usize = 8;
/// Display tile count in the x-direction
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH};
/// let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT);
/// ```
pub const TILE_WIDTH: usize = 56;
/// Display tile count in the y-direction
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH};
/// let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT);
/// ```
pub const TILE_HEIGHT: usize = 20;
/// Display width in pixels
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap};
/// let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT);
/// ```
pub const PIXEL_WIDTH: usize = TILE_WIDTH * TILE_SIZE;
/// Display height in pixels
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap};
/// let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT);
/// ```
pub const PIXEL_HEIGHT: usize = TILE_HEIGHT * TILE_SIZE;
/// pixel count on whole screen
pub const PIXEL_COUNT: usize = PIXEL_WIDTH * PIXEL_HEIGHT;
/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets.
///
/// # Examples
///
/// ```rust
/// # use std::time::Instant;
/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, Bitmap};
/// # let connection = servicepoint::Connection::Fake;
/// # let pixels = Bitmap::max_sized();
/// loop {
/// let start = Instant::now();
///
/// // Change pixels here
///
/// connection.send(Command::BitmapLinearWin(
/// Origin::new(0,0),
/// pixels,
/// CompressionCode::Lzma
/// ))
/// .expect("send failed");
///
/// // warning: will crash if resulting duration is negative, e.g. when resuming from standby
/// std::thread::sleep(FRAME_PACING - start.elapsed());
/// # break; // prevent doctest from hanging
/// }
/// ```
pub const FRAME_PACING: Duration = Duration::from_millis(30);

View file

@ -1,115 +0,0 @@
use std::collections::HashMap;
/// Contains functions to convert between UTF-8 and Codepage 437.
///
/// See <https://en.wikipedia.org/wiki/Code_page_437#Character_set>
pub struct Cp437Converter;
/// An array of 256 elements, mapping most of the CP437 values to UTF-8 characters
///
/// Mostly follows CP437, except 0x0A, which is kept for use as line ending.
///
/// See <https://en.wikipedia.org/wiki/Code_page_437#Character_set>
///
/// Mostly copied from <https://github.com/kip93/cp437-tools>. License: GPL-3.0
#[rustfmt::skip]
const CP437_TO_UTF8: [char; 256] = [
/* 0X */ '\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '\n', '♂', '♀', '♪', '♫', '☼',
/* 1X */ '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼',
/* 2X */ ' ', '!', '"', '#', '$', '%', '&', '\'','(', ')', '*', '+', ',', '-', '.', '/',
/* 3X */ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
/* 4X */ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
/* 5X */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\',']', '^', '_',
/* 6X */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
/* 7X */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '⌂',
/* 8X */ 'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è', 'ï', 'î', 'ì', 'Ä', 'Å',
/* 9X */ 'É', 'æ', 'Æ', 'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', '¢', '£', '¥', '₧', 'ƒ',
/* AX */ 'á', 'í', 'ó', 'ú', 'ñ', 'Ñ', 'ª', 'º', '¿', '⌐', '¬', '½', '¼', '¡', '«', '»',
/* BX */ '░', '▒', '▓', '│', '┤', '╡', '╢', '╖', '╕', '╣', '║', '╗', '╝', '╜', '╛', '┐',
/* CX */ '└', '┴', '┬', '├', '─', '┼', '╞', '╟', '╚', '╔', '╩', '╦', '╠', '═', '╬', '╧',
/* DX */ '╨', '╤', '╥', '╙', '╘', '╒', '╓', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐', '▀',
/* EX */ 'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ', 'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩',
/* FX */ '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', ' ',
];
static UTF8_TO_CP437: once_cell::sync::Lazy<HashMap<char, u8>> =
once_cell::sync::Lazy::new(|| {
let pairs = CP437_TO_UTF8
.iter()
.enumerate()
.map(move |(index, char)| (*char, index as u8));
HashMap::from_iter(pairs)
});
impl Cp437Converter {
const MISSING_CHAR_CP437: u8 = 0x3F; // '?'
/// Convert the provided bytes to UTF-8.
pub fn cp437_to_str(cp437: &[u8]) -> String {
cp437
.iter()
.map(move |char| Self::cp437_to_char(*char))
.collect()
}
/// Convert a single CP-437 character to UTF-8.
pub fn cp437_to_char(cp437: u8) -> char {
CP437_TO_UTF8[cp437 as usize]
}
/// Convert the provided text to CP-437 bytes.
///
/// Characters that are not available are mapped to '?'.
pub fn str_to_cp437(utf8: &str) -> Vec<u8> {
utf8.chars().map(Self::char_to_cp437).collect()
}
/// Convert a single UTF-8 character to CP-437.
pub fn char_to_cp437(utf8: char) -> u8 {
*UTF8_TO_CP437
.get(&utf8)
.unwrap_or(&Self::MISSING_CHAR_CP437)
}
}
#[cfg(test)]
mod tests_feature_cp437 {
use super::*;
#[test]
fn convert_str() {
// test text from https://int10h.org/oldschool-pc-fonts/fontlist/font?ibm_bios
let utf8 = r#"A quick brown fox jumps over the lazy dog.
0123456789 ¿?¡!`'"., <>()[]{} &@%*^#$\/
* Wieniläinen sioux'ta puhuva ökyzombie diggaa Åsan roquefort-tacoja.
* Ça me fait peur de fêter noël , sur cette île bizarroïde une mère et sa môme essaient de me tuer avec un gâteau à la cigüe brûlé.
* Zwölf Boxkämpfer jagten Eva quer über den Sylter Deich.
* El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro.
.·°·.
$ ¢ £ ¥
dx Σ x²·δx
"#;
let cp437 = Cp437Converter::str_to_cp437(utf8);
let actual = Cp437Converter::cp437_to_str(&cp437);
assert_eq!(utf8, actual)
}
#[test]
fn convert_invalid() {
assert_eq!(
Cp437Converter::cp437_to_char(Cp437Converter::char_to_cp437('😜')),
'?'
);
}
}

View file

@ -1,163 +0,0 @@
/// A grid containing codepage 437 characters.
///
/// The encoding is currently not enforced.
pub type Cp437Grid = crate::value_grid::ValueGrid<u8>;
/// The error occurring when loading an invalid character
#[derive(Debug, PartialEq, thiserror::Error)]
#[error(
"The character {char:?} at position {index} is not a valid CP437 character"
)]
pub struct InvalidCharError {
/// invalid character is at this position in input
index: usize,
/// the invalid character
char: char,
}
impl Cp437Grid {
/// Load an ASCII-only [&str] into a [Cp437Grid] of specified width.
///
/// # Panics
///
/// - for width == 0
/// - on empty strings
pub fn load_ascii(
value: &str,
width: usize,
wrap: bool,
) -> Result<Self, InvalidCharError> {
assert!(width > 0);
assert!(!value.is_empty());
let mut chars = {
let mut x = 0;
let mut y = 0;
for (index, char) in value.chars().enumerate() {
if !char.is_ascii() {
return Err(InvalidCharError { index, char });
}
let is_lf = char == '\n';
if is_lf || (wrap && x == width) {
y += 1;
x = 0;
if is_lf {
continue;
}
}
x += 1;
}
Cp437Grid::new(width, y + 1)
};
let mut x = 0;
let mut y = 0;
for char in value.chars().map(move |c| c as u8) {
let is_lf = char == b'\n';
if is_lf || (wrap && x == width) {
y += 1;
x = 0;
if is_lf {
continue;
}
}
if wrap || x < width {
chars.set(x, y, char);
}
x += 1;
}
Ok(chars)
}
}
use crate::Grid;
#[allow(unused)] // depends on features
pub use feature_cp437::*;
#[cfg(feature = "cp437")]
mod feature_cp437 {
use super::*;
use crate::{CharGrid, Cp437Converter};
impl From<&Cp437Grid> for CharGrid {
fn from(value: &Cp437Grid) -> Self {
value.map(Cp437Converter::cp437_to_char)
}
}
impl From<Cp437Grid> for CharGrid {
fn from(value: Cp437Grid) -> Self {
Self::from(&value)
}
}
impl From<&CharGrid> for Cp437Grid {
fn from(value: &CharGrid) -> Self {
value.map(Cp437Converter::char_to_cp437)
}
}
impl From<CharGrid> for Cp437Grid {
fn from(value: CharGrid) -> Self {
Self::from(&value)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn load_ascii_nowrap() {
let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
.map(move |c| c as u8);
let expected = Cp437Grid::load(5, 2, &chars);
let actual = Cp437Grid::load_ascii("Hello,\nWorld!", 5, false).unwrap();
// comma will be removed because line is too long and wrap is off
assert_eq!(actual, expected);
}
#[test]
fn load_ascii_wrap() {
let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
.map(move |c| c as u8);
let expected = Cp437Grid::load(5, 2, &chars);
let actual = Cp437Grid::load_ascii("HelloWorld", 5, true).unwrap();
// line break will be added
assert_eq!(actual, expected);
}
#[test]
fn load_ascii_invalid() {
assert_eq!(
Err(InvalidCharError {
char: '🥶',
index: 2
}),
Cp437Grid::load_ascii("?#🥶42", 3, false)
);
}
}
#[cfg(test)]
#[cfg(feature = "cp437")]
mod tests_feature_cp437 {
use super::*;
use crate::CharGrid;
#[test]
fn round_trip_cp437() {
let utf8 = CharGrid::load(2, 2, &['Ä', 'x', '\n', '$']);
let cp437 = Cp437Grid::from(utf8.clone());
let actual = CharGrid::from(cp437);
assert_eq!(actual, utf8);
}
}

View file

@ -1,14 +0,0 @@
/// 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<T> {
/// Get the underlying bytes writable.
///
/// Note that depending on the struct this is implemented on, writing invalid values here might
/// lead to panics later in the lifetime of the program or on the receiving side.
fn data_ref_mut(&mut self) -> &mut [T];
/// Get the underlying bytes read-only.
fn data_ref(&self) -> &[T];
}

View file

@ -1,84 +0,0 @@
/// A two-dimensional grid of `T`
pub trait Grid<T> {
/// Sets the value at the specified position
///
/// # Arguments
///
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
/// When accessing `x` or `y` out of bounds.
fn set(&mut self, x: usize, y: usize, value: T);
/// Get the current value at the specified position
///
/// # Arguments
///
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
/// When accessing `x` or `y` out of bounds.
fn get(&self, x: usize, y: usize) -> T;
/// Get the current value at the specified position if the position is inside of bounds
///
/// # Arguments
///
/// - `x` and `y`: position of the cell to read
///
/// returns: Value at position or None
fn get_optional(&self, x: isize, y: isize) -> Option<T> {
if self.is_in_bounds(x, y) {
Some(self.get(x as usize, y as usize))
} else {
None
}
}
/// Sets the value at the specified position if the position is inside of bounds
///
/// # Arguments
///
/// - `x` and `y`: position of the cell to read
///
/// returns: the old value or None
fn set_optional(&mut self, x: isize, y: isize, value: T) -> bool {
if self.is_in_bounds(x, y) {
self.set(x as usize, y as usize, value);
true
} else {
false
}
}
/// 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;
/// Checks whether the specified signed position is in grid bounds
fn is_in_bounds(&self, x: isize, y: isize) -> bool {
x >= 0
&& x < self.width() as isize
&& y >= 0
&& y < self.height() as isize
}
/// Asserts that the specified unsigned position is in grid bounds.
///
/// # Panics
///
/// When the specified position is out of bounds for this grid.
fn assert_in_bounds(&self, x: usize, y: usize) {
let width = self.width();
assert!(x < width, "cannot access index [{x}, {y}] because x is outside of bounds [0..{width})");
let height = self.height();
assert!(y < height, "cannot access index [{x}, {y}] because x is outside of bounds [0..{height})");
}
}

View file

@ -1,103 +0,0 @@
//! Abstractions for the UDP protocol of the CCCB servicepoint display.
//!
//! Your starting point is a [Connection] to the display.
//! With a connection, you can send [Command]s.
//! When received, the display will update the state of its pixels.
//!
//! # Examples
//!
//! ### Clear display
//!
//! ```rust
//! use servicepoint::{Connection, Command};
//!
//! // establish a connection
//! let connection = Connection::open("127.0.0.1:2342")
//! .expect("connection failed");
//!
//! // turn off all pixels on display
//! connection.send(Command::Clear)
//! .expect("send failed");
//! ```
//!
//! ### Set all pixels to on
//!
//! ```rust
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap};
//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed");
//! // turn on all pixels in a grid
//! let mut pixels = Bitmap::max_sized();
//! pixels.fill(true);
//!
//! // create command to send pixels
//! let command = Command::BitmapLinearWin(
//! servicepoint::Origin::ZERO,
//! pixels,
//! CompressionCode::Uncompressed
//! );
//!
//! // send command to display
//! connection.send(command).expect("send failed");
//! ```
//!
//! ### Send text
//!
//! ```rust
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap, CharGrid};
//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed");
//! // create a text grid
//! let mut grid = CharGrid::from("Hello\nCCCB?");
//! // modify the grid
//! grid.set(grid.width() - 1, 1, '!');
//! // create the command to send the data
//! let command = Command::Utf8Data(servicepoint::Origin::ZERO, grid);
//! // send command to display
//! connection.send(command).expect("send failed");
//! ```
pub use crate::bit_vec::{bitvec, BitVec};
pub use crate::bitmap::Bitmap;
pub use crate::brightness::Brightness;
pub use crate::brightness_grid::BrightnessGrid;
pub use crate::byte_grid::ByteGrid;
pub use crate::char_grid::CharGrid;
pub use crate::command::{Command, Offset};
pub use crate::compression_code::CompressionCode;
pub use crate::connection::Connection;
pub use crate::constants::*;
pub use crate::cp437::Cp437Converter;
pub use crate::cp437_grid::Cp437Grid;
pub use crate::data_ref::DataRef;
pub use crate::grid::Grid;
pub use crate::origin::{Origin, Pixels, Tiles};
pub use crate::packet::{Header, Packet, Payload};
pub use crate::value_grid::{
IterGridRows, SetValueSeriesError, TryLoadValueGridError, Value, ValueGrid,
};
mod bit_vec;
mod bitmap;
mod brightness;
mod brightness_grid;
mod byte_grid;
mod char_grid;
mod command;
mod command_code;
mod compression;
mod compression_code;
mod connection;
mod constants;
mod cp437_grid;
mod data_ref;
mod grid;
mod origin;
mod packet;
mod value_grid;
#[cfg(feature = "cp437")]
mod cp437;
// include README.md in doctest
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
pub struct ReadmeDocTests;

View file

@ -1,122 +0,0 @@
use crate::TILE_SIZE;
use std::marker::PhantomData;
/// An origin marks the top left position of a window sent to the display.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Origin<Unit: DisplayUnit> {
/// position in the width direction
pub x: usize,
/// position in the height direction
pub y: usize,
phantom_data: PhantomData<Unit>,
}
impl<Unit: DisplayUnit> Origin<Unit> {
/// Top-left. Equivalent to `Origin::ZERO`.
pub const ZERO: Self = Self {
x: 0,
y: 0,
phantom_data: PhantomData,
};
/// Create a new [Origin] instance for the provided position.
pub fn new(x: usize, y: usize) -> Self {
Self {
x,
y,
phantom_data: PhantomData,
}
}
}
impl<T: DisplayUnit> std::ops::Add<Origin<T>> for Origin<T> {
type Output = Origin<T>;
fn add(self, rhs: Origin<T>) -> Self::Output {
Origin {
x: self.x + rhs.x,
y: self.y + rhs.y,
phantom_data: PhantomData,
}
}
}
pub trait DisplayUnit {}
/// Marks something to be measured in number of pixels.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Pixels();
/// Marks something to be measured in number of iles.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Tiles();
impl DisplayUnit for Pixels {}
impl DisplayUnit for Tiles {}
impl From<&Origin<Tiles>> for Origin<Pixels> {
fn from(value: &Origin<Tiles>) -> Self {
Self {
x: value.x * TILE_SIZE,
y: value.y * TILE_SIZE,
phantom_data: PhantomData,
}
}
}
impl TryFrom<&Origin<Pixels>> for Origin<Tiles> {
type Error = ();
fn try_from(value: &Origin<Pixels>) -> Result<Self, Self::Error> {
let (x, x_rem) = (value.x / TILE_SIZE, value.x % TILE_SIZE);
if x_rem != 0 {
return Err(());
}
let (y, y_rem) = (value.y / TILE_SIZE, value.y % TILE_SIZE);
if y_rem != 0 {
return Err(());
}
Ok(Self {
x,
y,
phantom_data: PhantomData,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn origin_tile_to_pixel() {
let tile: Origin<Tiles> = Origin::new(1, 2);
let actual: Origin<Pixels> = Origin::from(&tile);
let expected: Origin<Pixels> = Origin::new(8, 16);
assert_eq!(actual, expected);
}
#[test]
fn origin_pixel_to_tile() {
let pixel: Origin<Pixels> = Origin::new(8, 16);
let actual: Origin<Tiles> = Origin::try_from(&pixel).unwrap();
let expected: Origin<Tiles> = Origin::new(1, 2);
assert_eq!(actual, expected);
}
#[test]
#[should_panic]
fn origin_pixel_to_tile_fail_y() {
let pixel: Origin<Pixels> = Origin::new(8, 15);
let _: Origin<Tiles> = Origin::try_from(&pixel).unwrap();
}
#[test]
#[should_panic]
fn origin_pixel_to_tile_fail_x() {
let pixel: Origin<Pixels> = Origin::new(7, 16);
let _: Origin<Tiles> = Origin::try_from(&pixel).unwrap();
}
}

View file

@ -1,358 +0,0 @@
//! Raw packet manipulation.
//!
//! Should probably only be used directly to use features not exposed by the library.
//!
//! # Examples
//!
//! Converting a packet to a command and back:
//!
//! ```rust
//! use servicepoint::{Command, Packet};
//! # let command = Command::Clear;
//! let packet: Packet = command.into();
//! let command: Command = Command::try_from(packet).expect("could not read command from packet");
//! ```
//!
//! Converting a packet to bytes and back:
//!
//! ```rust
//! use servicepoint::{Command, Packet};
//! # let command = Command::Clear;
//! # let packet: Packet = command.into();
//! let bytes: Vec<u8> = packet.into();
//! let packet = Packet::try_from(bytes).expect("could not read packet from bytes");
//! ```
use crate::command_code::CommandCode;
use crate::compression::into_compressed;
use crate::{
Bitmap, Command, CompressionCode, Grid, Offset, Origin, Pixels, Tiles,
TILE_SIZE,
};
use std::mem::size_of;
/// A raw header.
///
/// The header specifies the kind of command, the size of the payload and where to display the
/// payload, where applicable.
///
/// Because the meaning of most fields depend on the command, there are no speaking names for them.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Header {
/// The first two bytes specify which command this packet represents.
pub command_code: u16,
/// First command-specific value
pub a: u16,
/// Second command-specific value
pub b: u16,
/// Third command-specific value
pub c: u16,
/// Fourth command-specific value
pub d: u16,
}
/// The raw payload.
///
/// Should probably only be used directly to use features not exposed by the library.
pub type Payload = Vec<u8>;
/// The raw packet.
///
/// Contents should probably only be used directly to use features not exposed by the library.
///
/// You may want to use [Command] instead.
///
///
#[derive(Clone, Debug, PartialEq)]
pub struct Packet {
/// Meta-information for the packed command
pub header: Header,
/// The data for the packed command
pub payload: Payload,
}
impl From<Packet> for Vec<u8> {
/// Turn the packet into raw bytes ready to send
fn from(value: Packet) -> Self {
let Packet {
header:
Header {
command_code: mode,
a,
b,
c,
d,
},
payload,
} = value;
let mut packet = vec![0u8; 10 + payload.len()];
packet[0..=1].copy_from_slice(&u16::to_be_bytes(mode));
packet[2..=3].copy_from_slice(&u16::to_be_bytes(a));
packet[4..=5].copy_from_slice(&u16::to_be_bytes(b));
packet[6..=7].copy_from_slice(&u16::to_be_bytes(c));
packet[8..=9].copy_from_slice(&u16::to_be_bytes(d));
packet[10..].copy_from_slice(&payload);
packet
}
}
impl TryFrom<&[u8]> for Packet {
type Error = ();
/// Tries to interpret the bytes as a [Packet].
///
/// returns: `Error` if slice is not long enough to be a [Packet]
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() < size_of::<Header>() {
return Err(());
}
let header = {
let command_code = Self::u16_from_be_slice(&value[0..=1]);
let a = Self::u16_from_be_slice(&value[2..=3]);
let b = Self::u16_from_be_slice(&value[4..=5]);
let c = Self::u16_from_be_slice(&value[6..=7]);
let d = Self::u16_from_be_slice(&value[8..=9]);
Header {
command_code,
a,
b,
c,
d,
}
};
let payload = value[10..].to_vec();
Ok(Packet { header, payload })
}
}
impl TryFrom<Vec<u8>> for Packet {
type Error = ();
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Self::try_from(value.as_slice())
}
}
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 => Self::command_code_only(CommandCode::Clear),
Command::FadeOut => Self::command_code_only(CommandCode::FadeOut),
Command::HardReset => {
Self::command_code_only(CommandCode::HardReset)
}
#[allow(deprecated)]
Command::BitmapLegacy => {
Self::command_code_only(CommandCode::BitmapLegacy)
}
Command::CharBrightness(origin, grid) => {
Self::origin_grid_to_packet(
origin,
grid,
CommandCode::CharBrightness,
)
}
Command::Brightness(brightness) => Packet {
header: Header {
command_code: CommandCode::Brightness.into(),
a: 0x00000,
b: 0x0000,
c: 0x0000,
d: 0x0000,
},
payload: vec![brightness.into()],
},
Command::BitmapLinearWin(origin, pixels, compression) => {
Self::bitmap_win_into_packet(origin, pixels, compression)
}
Command::BitmapLinear(offset, bits, compression) => {
Self::bitmap_linear_into_packet(
CommandCode::BitmapLinear,
offset,
compression,
bits.into(),
)
}
Command::BitmapLinearAnd(offset, bits, compression) => {
Self::bitmap_linear_into_packet(
CommandCode::BitmapLinearAnd,
offset,
compression,
bits.into(),
)
}
Command::BitmapLinearOr(offset, bits, compression) => {
Self::bitmap_linear_into_packet(
CommandCode::BitmapLinearOr,
offset,
compression,
bits.into(),
)
}
Command::BitmapLinearXor(offset, bits, compression) => {
Self::bitmap_linear_into_packet(
CommandCode::BitmapLinearXor,
offset,
compression,
bits.into(),
)
}
Command::Cp437Data(origin, grid) => Self::origin_grid_to_packet(
origin,
grid,
CommandCode::Cp437Data,
),
Command::Utf8Data(origin, grid) => {
Self::origin_grid_to_packet(origin, grid, CommandCode::Utf8Data)
}
}
}
}
impl Packet {
/// 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: Header {
command_code: command.into(),
a: offset as u16,
b: length,
c: compression.into(),
d: 0,
},
payload,
}
}
#[allow(clippy::cast_possible_truncation)]
fn bitmap_win_into_packet(
origin: Origin<Pixels>,
pixels: Bitmap,
compression: CompressionCode,
) -> Packet {
debug_assert_eq!(origin.x % 8, 0);
debug_assert_eq!(pixels.width() % 8, 0);
let tile_x = (origin.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: Header {
command_code: command.into(),
a: tile_x,
b: origin.y as u16,
c: tile_w,
d: pixel_h,
},
payload,
}
}
/// Helper method for creating empty packets only containing the command code
fn command_code_only(code: CommandCode) -> Packet {
Packet {
header: Header {
command_code: code.into(),
a: 0x0000,
b: 0x0000,
c: 0x0000,
d: 0x0000,
},
payload: vec![],
}
}
fn u16_from_be_slice(slice: &[u8]) -> u16 {
let mut bytes = [0u8; 2];
bytes[0] = slice[0];
bytes[1] = slice[1];
u16::from_be_bytes(bytes)
}
fn origin_grid_to_packet<T>(
origin: Origin<Tiles>,
grid: impl Grid<T> + Into<Payload>,
command_code: CommandCode,
) -> Packet {
Packet {
header: Header {
command_code: command_code.into(),
a: origin.x as u16,
b: origin.y as u16,
c: grid.width() as u16,
d: grid.height() as u16,
},
payload: grid.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
let p = Packet {
header: Header {
command_code: 0,
a: 1,
b: 2,
c: 3,
d: 4,
},
payload: vec![42u8; 23],
};
let data: Vec<u8> = p.into();
let p = Packet::try_from(data).unwrap();
assert_eq!(
p,
Packet {
header: Header {
command_code: 0,
a: 1,
b: 2,
c: 3,
d: 4
},
payload: vec![42u8; 23]
}
);
}
#[test]
fn too_small() {
let data = vec![0u8; 4];
assert_eq!(Packet::try_from(data.as_slice()), Err(()))
}
}

View file

@ -1,590 +0,0 @@
use std::fmt::Debug;
use std::slice::{Iter, IterMut};
use crate::*;
/// A type that can be stored in a [ValueGrid], e.g. [char], [u8].
pub trait Value: Sized + Default + Copy + Clone + Debug {}
impl<T: Sized + Default + Copy + Clone + Debug> Value for T {}
/// A 2D grid of values.
///
/// The memory layout is the one the display expects in [Command]s.
///
/// This structure can be used with any type that implements the [Value] trait.
/// You can also use the concrete type aliases provided in this crate, e.g. [CharGrid] and [ByteGrid].
#[derive(Debug, Clone, PartialEq)]
pub struct ValueGrid<T: Value> {
width: usize,
height: usize,
data: Vec<T>,
}
/// Error type for methods that change a whole column or row at once
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum SetValueSeriesError {
#[error("The index {index} was out of bounds for size {size}")]
/// The index {index} was out of bounds for size {size}
OutOfBounds {
/// the index where access was tried
index: usize,
/// the size in that direction
size: usize,
},
#[error("The provided series was expected to have a length of {expected}, but was {actual}")]
/// The provided series was expected to have a length of {expected}, but was {actual}
InvalidLength {
/// actual size of the provided series
actual: usize,
/// expected size
expected: usize,
},
}
impl<T: Value> ValueGrid<T> {
/// Creates a new [ValueGrid] with the specified dimensions.
///
/// # Arguments
///
/// - width: size in x-direction
/// - height: size in y-direction
///
/// returns: [ValueGrid] initialized to default value.
pub fn new(width: usize, height: usize) -> Self {
Self {
data: vec![Default::default(); width * height],
width,
height,
}
}
/// Loads a [ValueGrid] with the specified dimensions from the provided data.
///
/// returns: [ValueGrid] 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: &[T]) -> Self {
assert_eq!(
width * height,
data.len(),
"dimension mismatch for data {data:?}"
);
Self {
data: Vec::from(data),
width,
height,
}
}
/// Creates a [ValueGrid] with the specified width from the provided data without copying it.
///
/// returns: [ValueGrid] that contains the provided data.
///
/// # Panics
///
/// - when the data size is not dividable by the width.
#[must_use]
pub fn from_vec(width: usize, data: Vec<T>) -> Self {
let len = data.len();
let height = len / width;
assert_eq!(0, len % width, "dimension mismatch - len {len} is not dividable by {width}");
Self { data, width, height }
}
/// Loads a [ValueGrid] with the specified width from the provided data, wrapping to as many rows as needed.
///
/// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadValueGridError].
///
/// # Examples
///
/// ```
/// # use servicepoint::ValueGrid;
/// let grid = ValueGrid::wrap(2, &[0, 1, 2, 3, 4, 5]).unwrap();
/// ```
pub fn wrap(
width: usize,
data: &[T],
) -> Result<Self, TryLoadValueGridError> {
let len = data.len();
if len % width != 0 {
return Err(TryLoadValueGridError::InvalidDimensions);
}
Ok(Self::load(width, len / width, data))
}
/// Loads a [ValueGrid] with the specified dimensions from the provided data.
///
/// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadValueGridError].
pub fn try_load(
width: usize,
height: usize,
data: Vec<T>,
) -> Result<Self, TryLoadValueGridError> {
if width * height != data.len() {
return Err(TryLoadValueGridError::InvalidDimensions);
}
Ok(Self {
data,
width,
height,
})
}
/// Iterate over all cells in [ValueGrid].
///
/// Order is equivalent to the following loop:
/// ```
/// # use servicepoint::{ByteGrid, Grid};
/// # let grid = ByteGrid::new(2,2);
/// for y in 0..grid.height() {
/// for x in 0..grid.width() {
/// grid.get(x, y);
/// }
/// }
/// ```
pub fn iter(&self) -> Iter<T> {
self.data.iter()
}
/// Iterate over all rows in [ValueGrid] top to bottom.
pub fn iter_rows(&self) -> IterGridRows<T> {
IterGridRows {
byte_grid: self,
row: 0,
}
}
/// Returns an iterator that allows modifying each value.
///
/// The iterator yields all cells from top left to bottom right.
pub fn iter_mut(&mut self) -> IterMut<T> {
self.data.iter_mut()
}
/// Get a mutable reference to the current value at the specified position.
///
/// # Arguments
///
/// - `x` and `y`: position of the cell
///
/// # Panics
///
/// When accessing `x` or `y` out of bounds.
pub fn get_ref_mut(&mut self, x: usize, y: usize) -> &mut T {
self.assert_in_bounds(x, y);
&mut self.data[x + y * self.width]
}
/// Get a mutable reference to the current value at the specified position if position is in bounds.
///
/// # Arguments
///
/// - `x` and `y`: position of the cell
///
/// returns: Reference to cell or None
pub fn get_ref_mut_optional(
&mut self,
x: isize,
y: isize,
) -> Option<&mut T> {
if self.is_in_bounds(x, y) {
Some(&mut self.data[x as usize + y as usize * self.width])
} else {
None
}
}
/// Convert between ValueGrid types.
///
/// See also [Iterator::map].
///
/// # Examples
///
/// Use logic written for u8s and then convert to [Brightness] values for sending in a [Command].
/// ```
/// # fn foo(grid: &mut ByteGrid) {}
/// # use servicepoint::{Brightness, BrightnessGrid, ByteGrid, Command, Origin, TILE_HEIGHT, TILE_WIDTH};
/// let mut grid: ByteGrid = ByteGrid::new(TILE_WIDTH, TILE_HEIGHT);
/// foo(&mut grid);
/// let grid: BrightnessGrid = grid.map(Brightness::saturating_from);
/// let command = Command::CharBrightness(Origin::ZERO, grid);
/// ```
/// [Brightness]: [crate::Brightness]
/// [Command]: [crate::Command]
pub fn map<TConverted, F>(&self, f: F) -> ValueGrid<TConverted>
where
TConverted: Value,
F: Fn(T) -> TConverted,
{
let data = self
.data_ref()
.iter()
.map(|elem| f(*elem))
.collect::<Vec<_>>();
ValueGrid::load(self.width(), self.height(), &data)
}
/// Copies a row from the grid.
///
/// Returns [None] if y is out of bounds.
pub fn get_row(&self, y: usize) -> Option<Vec<T>> {
self.data
.chunks_exact(self.width())
.nth(y)
.map(|row| row.to_vec())
}
/// Copies a column from the grid.
///
/// Returns [None] if x is out of bounds.
pub fn get_col(&self, x: usize) -> Option<Vec<T>> {
self.data
.chunks_exact(self.width())
.map(|row| row.get(x).copied())
.collect()
}
/// Overwrites a column in the grid.
///
/// Returns [Err] if x is out of bounds or `col` is not of the correct size.
pub fn set_col(
&mut self,
x: usize,
col: &[T],
) -> Result<(), SetValueSeriesError> {
if col.len() != self.height() {
return Err(SetValueSeriesError::InvalidLength {
expected: self.height(),
actual: col.len(),
});
}
let width = self.width();
if self
.data
.chunks_exact_mut(width)
.zip(col.iter())
.map(|(row, column_value)| {
row.get_mut(x).map(move |cell| *cell = *column_value)
})
.all(|cell| cell.is_some())
{
Ok(())
} else {
Err(SetValueSeriesError::OutOfBounds {
index: x,
size: width,
})
}
}
/// Overwrites a row in the grid.
///
/// Returns [Err] if y is out of bounds or `row` is not of the correct size.
pub fn set_row(
&mut self,
y: usize,
row: &[T],
) -> Result<(), SetValueSeriesError> {
let width = self.width();
if row.len() != width {
return Err(SetValueSeriesError::InvalidLength {
expected: width,
actual: row.len(),
});
}
let chunk = match self.data.chunks_exact_mut(width).nth(y) {
Some(row) => row,
None => {
return Err(SetValueSeriesError::OutOfBounds {
size: self.height(),
index: y,
})
}
};
chunk.copy_from_slice(row);
Ok(())
}
}
/// Errors that can occur when loading a grid
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum TryLoadValueGridError {
#[error("The provided dimensions do not match with the data size")]
/// The provided dimensions do not match with the data size
InvalidDimensions,
}
impl<T: Value> Grid<T> for ValueGrid<T> {
/// Sets the value of the cell at the specified position in the `ValueGrid.
///
/// # Arguments
///
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// # Panics
///
/// When accessing `x` or `y` out of bounds.
fn set(&mut self, x: usize, y: usize, value: T) {
self.assert_in_bounds(x, y);
self.data[x + y * self.width] = value;
}
/// Gets the current value at the specified position.
///
/// # Arguments
///
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
/// When accessing `x` or `y` out of bounds.
fn get(&self, x: usize, y: usize) -> T {
self.assert_in_bounds(x, y);
self.data[x + y * self.width]
}
fn fill(&mut self, value: T) {
self.data.fill(value);
}
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.height
}
}
impl<T: Value> DataRef<T> for ValueGrid<T> {
/// Get the underlying byte rows mutable
fn data_ref_mut(&mut self) -> &mut [T] {
self.data.as_mut_slice()
}
/// Get the underlying byte rows read only
fn data_ref(&self) -> &[T] {
self.data.as_slice()
}
}
impl<T: Value> From<ValueGrid<T>> for Vec<T> {
/// Turn into the underlying [`Vec<u8>`] containing the rows of bytes.
fn from(value: ValueGrid<T>) -> Self {
value.data
}
}
/// An iterator iver the rows in a [ValueGrid]
pub struct IterGridRows<'t, T: Value> {
byte_grid: &'t ValueGrid<T>,
row: usize,
}
impl<'t, T: Value> Iterator for IterGridRows<'t, T> {
type Item = Iter<'t, T>;
fn next(&mut self) -> Option<Self::Item> {
if self.row >= self.byte_grid.height {
return None;
}
let start = self.row * self.byte_grid.width;
let end = start + self.byte_grid.width;
let result = self.byte_grid.data[start..end].iter();
self.row += 1;
Some(result)
}
}
#[cfg(test)]
mod tests {
use crate::{
value_grid::{SetValueSeriesError, ValueGrid},
*,
};
#[test]
fn fill() {
let mut grid = ValueGrid::<usize>::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 = ValueGrid::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 = ValueGrid::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 = ValueGrid::load(2, 3, &data);
assert_eq!(grid.data, [0, 1, 1, 2, 2, 3]);
}
#[test]
fn mut_data_ref() {
let mut vec = ValueGrid::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)
}
#[test]
fn iter() {
let mut vec = ValueGrid::new(2, 2);
vec.set(1, 1, 5);
let mut iter = vec.iter();
assert_eq!(*iter.next().unwrap(), 0);
assert_eq!(*iter.next().unwrap(), 0);
assert_eq!(*iter.next().unwrap(), 0);
assert_eq!(*iter.next().unwrap(), 5);
}
#[test]
fn iter_mut() {
let mut vec = ValueGrid::new(2, 3);
for (index, cell) in vec.iter_mut().enumerate() {
*cell = index as u8;
}
assert_eq!(vec.data_ref(), [0, 1, 2, 3, 4, 5]);
}
#[test]
fn iter_rows() {
let vec = ValueGrid::load(2, 3, &[0, 1, 1, 2, 2, 3]);
for (y, row) in vec.iter_rows().enumerate() {
for (x, val) in row.enumerate() {
assert_eq!(*val, (x + y) as u8);
}
}
}
#[test]
#[should_panic]
fn out_of_bounds_x() {
let mut vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]);
vec.set(2, 1, 5);
}
#[test]
#[should_panic]
fn out_of_bounds_y() {
let vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]);
vec.get(1, 2);
}
#[test]
fn ref_mut() {
let mut vec = ValueGrid::from_vec(3, vec![0, 1, 2, 3,4,5,6,7,8]);
let top_left = vec.get_ref_mut(0, 0);
*top_left += 5;
let somewhere = vec.get_ref_mut(2, 1);
*somewhere = 42;
assert_eq!(None, vec.get_ref_mut_optional(3, 2));
assert_eq!(None, vec.get_ref_mut_optional(2, 3));
assert_eq!(Some(&mut 5), vec.get_ref_mut_optional(0, 0));
assert_eq!(Some(&mut 42), vec.get_ref_mut_optional(2, 1));
assert_eq!(Some(&mut 8), vec.get_ref_mut_optional(2, 2));
}
#[test]
fn optional() {
let mut grid = ValueGrid::load(2, 2, &[0, 1, 2, 3]);
grid.set_optional(0, 0, 5);
grid.set_optional(-1, 0, 8);
grid.set_optional(0, 8, 42);
assert_eq!(grid.data, [5, 1, 2, 3]);
assert_eq!(grid.get_optional(0, 0), Some(5));
assert_eq!(grid.get_optional(0, 8), None);
}
#[test]
fn col() {
let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]);
assert_eq!(grid.get_col(0), Some(vec![0, 2, 4]));
assert_eq!(grid.get_col(1), Some(vec![1, 3, 5]));
assert_eq!(grid.get_col(2), None);
assert_eq!(grid.set_col(0, &[5, 7, 9]), Ok(()));
assert_eq!(
grid.set_col(2, &[5, 7, 9]),
Err(SetValueSeriesError::OutOfBounds { size: 2, index: 2 })
);
assert_eq!(
grid.set_col(0, &[5, 7]),
Err(SetValueSeriesError::InvalidLength {
expected: 3,
actual: 2
})
);
assert_eq!(grid.get_col(0), Some(vec![5, 7, 9]));
}
#[test]
fn row() {
let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]);
assert_eq!(grid.get_row(0), Some(vec![0, 1]));
assert_eq!(grid.get_row(2), Some(vec![4, 5]));
assert_eq!(grid.get_row(3), None);
assert_eq!(grid.set_row(0, &[5, 7]), Ok(()));
assert_eq!(grid.get_row(0), Some(vec![5, 7]));
assert_eq!(
grid.set_row(3, &[5, 7]),
Err(SetValueSeriesError::OutOfBounds { size: 3, index: 3 })
);
assert_eq!(
grid.set_row(2, &[5, 7, 3]),
Err(SetValueSeriesError::InvalidLength {
expected: 2,
actual: 3
})
);
}
#[test]
fn wrap() {
let grid = ValueGrid::wrap(2, &[0, 1, 2, 3, 4, 5]).unwrap();
assert_eq!(grid.height(), 3);
let grid = ValueGrid::wrap(4, &[0, 1, 2, 3, 4, 5]);
assert_eq!(grid.err(), Some(TryLoadValueGridError::InvalidDimensions));
}
}

View file

@ -1,29 +0,0 @@
[package]
name = "servicepoint_binding_c"
version.workspace = true
publish = true
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"
links = "servicepoint"
keywords = ["cccb", "cccb-servicepoint", "cbindgen"]
[lib]
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
cbindgen = "0.27.0"
[dependencies.servicepoint]
version = "0.13.1"
path = "../servicepoint"
features = ["all_compressions"]
[lints]
workspace = true
[package.metadata.docs.rs]
all-features = true

View file

@ -1,63 +0,0 @@
# servicepoint_binding_c
[![crates.io](https://img.shields.io/crates/v/servicepoint_binding_c.svg)](https://crates.io/crates/servicepoint)
[![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint_binding_c)](https://crates.io/crates/servicepoint)
[![docs.rs](https://img.shields.io/docsrs/servicepoint_binding_c)](https://docs.rs/servicepoint/latest/servicepoint/)
[![GPLv3 licensed](https://img.shields.io/crates/l/servicepoint_binding_c)](../../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 C bindings for the `servicepoint` library, enabling users to parse, encode and send packets to this display via UDP.
## Examples
```c++
#include <stdio.h>
#include "servicepoint.h"
int main(void) {
SPConnection *connection = sp_connection_open("172.23.42.29:2342");
if (connection == NULL)
return 1;
SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
sp_bitmap_fill(pixels, true);
SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
while (sp_connection_send_command(connection, sp_command_clone(command)));
sp_command_free(command);
sp_connection_free(connection);
return 0;
}
```
A full example including Makefile is available as part of this crate.
## 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
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.
## Notes on differences to rust library
- function names are: `sp_` \<struct_name\> \<rust name\>.
- Instances get consumed in the same way they do when writing rust 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.
- documentation is included in the header and available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/)
## Everything else
Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for further information.

View file

@ -1,33 +0,0 @@
//! Build script generating the header for the `servicepoint` C library.
//!
//! When the environment variable `SERVICEPOINT_HEADER_OUT` is set, the header is copied there from
//! the out directory. This can be used to use the build script as a command line tool from other
//! build tools.
use std::{env, fs::copy};
use cbindgen::{generate_with_config, Config};
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
println!("cargo::rerun-if-changed={crate_dir}");
let config =
Config::from_file(crate_dir.clone() + "/cbindgen.toml").unwrap();
let output_dir = env::var("OUT_DIR").unwrap();
let header_file = output_dir.clone() + "/servicepoint.h";
generate_with_config(crate_dir, config)
.unwrap()
.write_to_file(&header_file);
println!("cargo:include={output_dir}");
println!("cargo::rerun-if-env-changed=SERVICEPOINT_HEADER_OUT");
if let Ok(header_out) = env::var("SERVICEPOINT_HEADER_OUT") {
let header_copy = header_out + "/servicepoint.h";
println!("cargo:warning=Copying header to {header_copy}");
copy(header_file, &header_copy).unwrap();
println!("cargo::rerun-if-changed={header_copy}");
}
}

View file

@ -1,36 +0,0 @@
language = "C"
include_version = true
cpp_compat = true
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
############################ Code Style Options ################################
braces = "SameLine"
line_length = 80
tab_width = 4
documentation = true
documentation_style = "auto"
documentation_length = "full"
line_endings = "LF"
############################# Codegen Options ##################################
style = "type"
usize_is_size_t = true
# this is needed because otherwise the order in the C# bindings is different on different machines
sort_by = "Name"
[parse]
parse_deps = false
[parse.expand]
all_features = true
[export]
include = []
exclude = []
[enum]
rename_variants = "QualifiedScreamingSnakeCase"

View file

@ -1,14 +0,0 @@
[package]
name = "lang_c"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
test = false
[build-dependencies]
cc = "1.2"
[dependencies]
servicepoint_binding_c = { path = "../.." }

View file

@ -1,34 +0,0 @@
CC := gcc
THIS_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
REPO_ROOT := $(THIS_DIR)/../../../..
build: out/lang_c
clean:
rm -r out || true
rm include/servicepoint.h || true
cargo clean
run: out/lang_c
out/lang_c
PHONY: build clean dependencies run
out/lang_c: dependencies src/main.c
mkdir -p out || true
${CC} src/main.c \
-I include \
-L $(REPO_ROOT)/target/release \
-Wl,-Bstatic -lservicepoint_binding_c \
-Wl,-Bdynamic -llzma \
-o out/lang_c
dependencies: FORCE
mkdir -p include || true
# generate servicepoint header and binary to link against
SERVICEPOINT_HEADER_OUT=$(THIS_DIR)/include cargo build \
--manifest-path=$(REPO_ROOT)/crates/servicepoint_binding_c/Cargo.toml \
--release
FORCE: ;

View file

@ -1,17 +0,0 @@
const SP_INCLUDE: &str = "DEP_SERVICEPOINT_INCLUDE";
fn main() {
println!("cargo::rerun-if-changed=src/main.c");
println!("cargo::rerun-if-changed=build.rs");
println!("cargo::rerun-if-env-changed={SP_INCLUDE}");
let sp_include =
std::env::var_os(SP_INCLUDE).unwrap().into_string().unwrap();
// this builds a lib, this is only to check that the example compiles
let mut cc = cc::Build::new();
cc.file("src/main.c");
cc.include(&sp_include);
cc.opt_level(2);
cc.compile("lang_c");
}

View file

@ -1,17 +0,0 @@
#include <stdio.h>
#include "servicepoint.h"
int main(void) {
SPConnection *connection = sp_connection_open("localhost:2342");
if (connection == NULL)
return 1;
SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
sp_bitmap_fill(pixels, true);
SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, SP_COMPRESSION_CODE_UNCOMPRESSED);
sp_connection_send_command(connection, command);
sp_connection_free(connection);
return 0;
}

View file

@ -1,296 +0,0 @@
//! C functions for interacting with [SPBitmap]s
//!
//! prefix `sp_bitmap_`
use servicepoint::{DataRef, Grid};
use std::ptr::NonNull;
use crate::byte_slice::SPByteSlice;
/// A grid of pixels.
///
/// # Examples
///
/// ```C
/// Cp437Grid grid = sp_bitmap_new(8, 3);
/// sp_bitmap_fill(grid, true);
/// sp_bitmap_set(grid, 0, 0, false);
/// sp_bitmap_free(grid);
/// ```
pub struct SPBitmap(pub(crate) servicepoint::Bitmap);
/// Creates a new [SPBitmap] with the specified dimensions.
///
/// # Arguments
///
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL.
///
/// # Panics
///
/// - when the width is not dividable by 8
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_new(
width: usize,
height: usize,
) -> NonNull<SPBitmap> {
let result = Box::new(SPBitmap(servicepoint::Bitmap::new(width, height)));
NonNull::from(Box::leak(result))
}
/// Creates a new [SPBitmap] with a size matching the screen.
///
/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling [sp_bitmap_free].
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_new_screen_sized() -> NonNull<SPBitmap> {
let result = Box::new(SPBitmap(servicepoint::Bitmap::max_sized()));
NonNull::from(Box::leak(result))
}
/// Loads a [SPBitmap] with the specified dimensions from the provided data.
///
/// # Arguments
///
/// - `width`: size in pixels in x-direction
/// - `height`: size in pixels in y-direction
///
/// returns: [SPBitmap] that contains a copy of the provided data. Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the dimensions and data size do not match exactly.
/// - when the width is not dividable by 8
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length` bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
) -> NonNull<SPBitmap> {
assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, data_length);
let result =
Box::new(SPBitmap(servicepoint::Bitmap::load(width, height, data)));
NonNull::from(Box::leak(result))
}
/// Clones a [SPBitmap].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitmap_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_clone(
bitmap: *const SPBitmap,
) -> NonNull<SPBitmap> {
assert!(!bitmap.is_null());
let result = Box::new(SPBitmap((*bitmap).0.clone()));
NonNull::from(Box::leak(result))
}
/// Deallocates a [SPBitmap].
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not used concurrently or after bitmap call
/// - `bitmap` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_free(bitmap: *mut SPBitmap) {
assert!(!bitmap.is_null());
_ = Box::from_raw(bitmap);
}
/// Gets the current value at the specified position in the [SPBitmap].
///
/// # Arguments
///
/// - `bitmap`: instance to read from
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
/// - when `bitmap` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_get(
bitmap: *const SPBitmap,
x: usize,
y: usize,
) -> bool {
assert!(!bitmap.is_null());
(*bitmap).0.get(x, y)
}
/// Sets the value of the specified position in the [SPBitmap].
///
/// # Arguments
///
/// - `bitmap`: instance to write to
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
/// # Panics
///
/// - when `bitmap` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_set(
bitmap: *mut SPBitmap,
x: usize,
y: usize,
value: bool,
) {
assert!(!bitmap.is_null());
(*bitmap).0.set(x, y, value);
}
/// Sets the state of all pixels in the [SPBitmap].
///
/// # Arguments
///
/// - `bitmap`: instance to write to
/// - `value`: the value to set all pixels to
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - `bitmap` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_fill(bitmap: *mut SPBitmap, value: bool) {
assert!(!bitmap.is_null());
(*bitmap).0.fill(value);
}
/// Gets the width in pixels of the [SPBitmap] instance.
///
/// # Arguments
///
/// - `bitmap`: instance to read from
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_width(bitmap: *const SPBitmap) -> usize {
assert!(!bitmap.is_null());
(*bitmap).0.width()
}
/// Gets the height in pixels of the [SPBitmap] instance.
///
/// # Arguments
///
/// - `bitmap`: instance to read from
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_height(bitmap: *const SPBitmap) -> usize {
assert!(!bitmap.is_null());
(*bitmap).0.height()
}
/// Gets an unsafe reference to the data of the [SPBitmap] instance.
///
/// # Panics
///
/// - when `bitmap` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid [SPBitmap]
/// - the returned memory range is never accessed after the passed [SPBitmap] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBitmap] or directly
#[no_mangle]
pub unsafe extern "C" fn sp_bitmap_unsafe_data_ref(
bitmap: *mut SPBitmap,
) -> SPByteSlice {
assert!(!bitmap.is_null());
let data = (*bitmap).0.data_ref_mut();
SPByteSlice {
start: NonNull::new(data.as_mut_ptr_range().start).unwrap(),
length: data.len(),
}
}

View file

@ -1,283 +0,0 @@
//! C functions for interacting with [SPBitVec]s
//!
//! prefix `sp_bitvec_`
use crate::SPByteSlice;
use std::ptr::NonNull;
/// A vector of bits
///
/// # Examples
/// ```C
/// SPBitVec vec = sp_bitvec_new(8);
/// sp_bitvec_set(vec, 5, true);
/// sp_bitvec_free(vec);
/// ```
pub struct SPBitVec(servicepoint::BitVec);
impl From<servicepoint::BitVec> for SPBitVec {
fn from(actual: servicepoint::BitVec) -> Self {
Self(actual)
}
}
impl From<SPBitVec> for servicepoint::BitVec {
fn from(value: SPBitVec) -> Self {
value.0
}
}
impl Clone for SPBitVec {
fn clone(&self) -> Self {
SPBitVec(self.0.clone())
}
}
/// Creates a new [SPBitVec] instance.
///
/// # Arguments
///
/// - `size`: size in bits.
///
/// returns: [SPBitVec] with all bits set to false. Will never return NULL.
///
/// # Panics
///
/// - when `size` is not divisible by 8.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_new(size: usize) -> NonNull<SPBitVec> {
let result = Box::new(SPBitVec(servicepoint::BitVec::repeat(false, size)));
NonNull::from(Box::leak(result))
}
/// Interpret the data as a series of bits and load then into a new [SPBitVec] instance.
///
/// returns: [SPBitVec] instance containing data. Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_load(
data: *const u8,
data_length: usize,
) -> NonNull<SPBitVec> {
assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, data_length);
let result = Box::new(SPBitVec(servicepoint::BitVec::from_slice(data)));
NonNull::from(Box::leak(result))
}
/// Clones a [SPBitVec].
///
/// returns: new [SPBitVec] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_bitvec_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_clone(
bit_vec: *const SPBitVec,
) -> NonNull<SPBitVec> {
assert!(!bit_vec.is_null());
let result = Box::new((*bit_vec).clone());
NonNull::from(Box::leak(result))
}
/// Deallocates a [SPBitVec].
///
/// # Panics
///
/// - when `but_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `bit_vec` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_free(bit_vec: *mut SPBitVec) {
assert!(!bit_vec.is_null());
_ = Box::from_raw(bit_vec);
}
/// Gets the value of a bit from the [SPBitVec].
///
/// # Arguments
///
/// - `bit_vec`: instance to read from
/// - `index`: the bit index to read
///
/// returns: value of the bit
///
/// # Panics
///
/// - when `bit_vec` is NULL
/// - when accessing `index` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_get(
bit_vec: *const SPBitVec,
index: usize,
) -> bool {
assert!(!bit_vec.is_null());
*(*bit_vec).0.get(index).unwrap()
}
/// Sets the value of a bit in the [SPBitVec].
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
/// - `index`: the bit index to edit
/// - `value`: the value to set the bit to
///
/// # Panics
///
/// - when `bit_vec` is NULL
/// - when accessing `index` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_set(
bit_vec: *mut SPBitVec,
index: usize,
value: bool,
) {
assert!(!bit_vec.is_null());
(*bit_vec).0.set(index, value)
}
/// Sets the value of all bits in the [SPBitVec].
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
/// - `value`: the value to set all bits to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - `bit_vec` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_fill(bit_vec: *mut SPBitVec, value: bool) {
assert!(!bit_vec.is_null());
(*bit_vec).0.fill(value)
}
/// Gets the length of the [SPBitVec] in bits.
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_len(bit_vec: *const SPBitVec) -> usize {
assert!(!bit_vec.is_null());
(*bit_vec).0.len()
}
/// Returns true if length is 0.
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_is_empty(bit_vec: *const SPBitVec) -> bool {
assert!(!bit_vec.is_null());
(*bit_vec).0.is_empty()
}
/// Gets an unsafe reference to the data of the [SPBitVec] instance.
///
/// # Arguments
///
/// - `bit_vec`: instance to write to
///
/// # Panics
///
/// - when `bit_vec` is NULL
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid [SPBitVec]
/// - the returned memory range is never accessed after the passed [SPBitVec] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBitVec] or directly
#[no_mangle]
pub unsafe extern "C" fn sp_bitvec_unsafe_data_ref(
bit_vec: *mut SPBitVec,
) -> SPByteSlice {
assert!(!bit_vec.is_null());
let data = (*bit_vec).0.as_raw_mut_slice();
SPByteSlice {
start: NonNull::new(data.as_mut_ptr_range().start).unwrap(),
length: data.len(),
}
}

View file

@ -1,322 +0,0 @@
//! C functions for interacting with [SPBrightnessGrid]s
//!
//! prefix `sp_brightness_grid_`
use crate::SPByteSlice;
use servicepoint::{DataRef, Grid};
use std::convert::Into;
use std::intrinsics::transmute;
use std::ptr::NonNull;
/// see [servicepoint::Brightness::MIN]
pub const SP_BRIGHTNESS_MIN: u8 = 0;
/// see [servicepoint::Brightness::MAX]
pub const SP_BRIGHTNESS_MAX: u8 = 11;
/// Count of possible brightness values
pub const SP_BRIGHTNESS_LEVELS: u8 = 12;
/// A grid containing brightness values.
///
/// # Examples
/// ```C
/// SPConnection connection = sp_connection_open("127.0.0.1:2342");
/// if (connection == NULL)
/// return 1;
///
/// SPBrightnessGrid grid = sp_brightness_grid_new(2, 2);
/// sp_brightness_grid_set(grid, 0, 0, 0);
/// sp_brightness_grid_set(grid, 1, 1, 10);
///
/// SPCommand command = sp_command_char_brightness(grid);
/// sp_connection_free(connection);
/// ```
#[derive(Clone)]
pub struct SPBrightnessGrid(pub(crate) servicepoint::BrightnessGrid);
/// Creates a new [SPBrightnessGrid] with the specified dimensions.
///
/// returns: [SPBrightnessGrid] initialized to 0. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_new(
width: usize,
height: usize,
) -> NonNull<SPBrightnessGrid> {
let result = Box::new(SPBrightnessGrid(servicepoint::BrightnessGrid::new(
width, height,
)));
NonNull::from(Box::leak(result))
}
/// Loads a [SPBrightnessGrid] with the specified dimensions from the provided data.
///
/// returns: new [SPBrightnessGrid] instance. Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
) -> NonNull<SPBrightnessGrid> {
assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, data_length);
let grid = servicepoint::ByteGrid::load(width, height, data);
let grid = servicepoint::BrightnessGrid::try_from(grid)
.expect("invalid brightness value");
let result = Box::new(SPBrightnessGrid(grid));
NonNull::from(Box::leak(result))
}
/// Clones a [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: new [SPBrightnessGrid] instance. Will never return NULL.
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_brightness_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_clone(
brightness_grid: *const SPBrightnessGrid,
) -> NonNull<SPBrightnessGrid> {
assert!(!brightness_grid.is_null());
let result = Box::new((*brightness_grid).clone());
NonNull::from(Box::leak(result))
}
/// Deallocates a [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not used concurrently or after this call
/// - `brightness_grid` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_free(
brightness_grid: *mut SPBrightnessGrid,
) {
assert!(!brightness_grid.is_null());
_ = Box::from_raw(brightness_grid);
}
/// Gets the current value at the specified position.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
/// - `x` and `y`: position of the cell to read
///
/// returns: value at position
///
/// # Panics
///
/// - when `brightness_grid` is NULL
/// - When accessing `x` or `y` out of bounds.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_get(
brightness_grid: *const SPBrightnessGrid,
x: usize,
y: usize,
) -> u8 {
assert!(!brightness_grid.is_null());
(*brightness_grid).0.get(x, y).into()
}
/// Sets the value of the specified position in the [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to write to
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
/// # Panics
///
/// - when `brightness_grid` is NULL
/// - When accessing `x` or `y` out of bounds.
/// - When providing an invalid brightness value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_set(
brightness_grid: *mut SPBrightnessGrid,
x: usize,
y: usize,
value: u8,
) {
assert!(!brightness_grid.is_null());
let brightness = servicepoint::Brightness::try_from(value)
.expect("invalid brightness value");
(*brightness_grid).0.set(x, y, brightness);
}
/// Sets the value of all cells in the [SPBrightnessGrid].
///
/// # Arguments
///
/// - `brightness_grid`: instance to write to
/// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `brightness_grid` is NULL
/// - When providing an invalid brightness value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - `brightness_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_fill(
brightness_grid: *mut SPBrightnessGrid,
value: u8,
) {
assert!(!brightness_grid.is_null());
let brightness = servicepoint::Brightness::try_from(value)
.expect("invalid brightness value");
(*brightness_grid).0.fill(brightness);
}
/// Gets the width of the [SPBrightnessGrid] instance.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: width
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_width(
brightness_grid: *const SPBrightnessGrid,
) -> usize {
assert!(!brightness_grid.is_null());
(*brightness_grid).0.width()
}
/// Gets the height of the [SPBrightnessGrid] instance.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: height
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_height(
brightness_grid: *const SPBrightnessGrid,
) -> usize {
assert!(!brightness_grid.is_null());
(*brightness_grid).0.height()
}
/// Gets an unsafe reference to the data of the [SPBrightnessGrid] instance.
///
/// # Arguments
///
/// - `brightness_grid`: instance to read from
///
/// returns: slice of bytes underlying the `brightness_grid`.
///
/// # Panics
///
/// - when `brightness_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `brightness_grid` points to a valid [SPBrightnessGrid]
/// - the returned memory range is never accessed after the passed [SPBrightnessGrid] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPBrightnessGrid] or directly
#[no_mangle]
pub unsafe extern "C" fn sp_brightness_grid_unsafe_data_ref(
brightness_grid: *mut SPBrightnessGrid,
) -> SPByteSlice {
assert!(!brightness_grid.is_null());
assert_eq!(core::mem::size_of::<servicepoint::Brightness>(), 1);
let data = (*brightness_grid).0.data_ref_mut();
// this assumes more about the memory layout than rust guarantees. yikes!
let data: &mut [u8] = transmute(data);
SPByteSlice {
start: NonNull::new(data.as_mut_ptr_range().start).unwrap(),
length: data.len(),
}
}

View file

@ -1,24 +0,0 @@
//! FFI slice helper
use std::ptr::NonNull;
#[repr(C)]
/// Represents a span of memory (`&mut [u8]` ) as a struct usable by C code.
///
/// You should not create an instance of this type in your C code.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - accesses to the memory pointed to with `start` is never accessed outside `length`
/// - the lifetime of the `CByteSlice` does not outlive the memory it points to, as described in
/// the function returning this type.
/// - an instance of this created from C is never passed to a consuming function, as the rust code
/// will try to free the memory of a potentially separate allocator.
pub struct SPByteSlice {
/// The start address of the memory
pub start: NonNull<u8>,
/// The amount of memory in bytes
pub length: usize,
}

View file

@ -1,263 +0,0 @@
//! C functions for interacting with [SPCharGrid]s
//!
//! prefix `sp_char_grid_`
use servicepoint::Grid;
use std::ptr::NonNull;
/// A C-wrapper for grid containing UTF-8 characters.
///
/// As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers.
///
/// The encoding is enforced in most cases by the rust standard library
/// and will panic when provided with illegal characters.
///
/// # Examples
///
/// ```C
/// CharGrid grid = sp_char_grid_new(4, 3);
/// sp_char_grid_fill(grid, '?');
/// sp_char_grid_set(grid, 0, 0, '!');
/// sp_char_grid_free(grid);
/// ```
pub struct SPCharGrid(pub(crate) servicepoint::CharGrid);
impl Clone for SPCharGrid {
fn clone(&self) -> Self {
SPCharGrid(self.0.clone())
}
}
/// Creates a new [SPCharGrid] with the specified dimensions.
///
/// returns: [SPCharGrid] initialized to 0. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_char_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_new(
width: usize,
height: usize,
) -> NonNull<SPCharGrid> {
let result =
Box::new(SPCharGrid(servicepoint::CharGrid::new(width, height)));
NonNull::from(Box::leak(result))
}
/// Loads a [SPCharGrid] with the specified dimensions from the provided data.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
/// - when `data` is not valid UTF-8
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_char_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
) -> NonNull<SPCharGrid> {
assert!(data.is_null());
let data = std::slice::from_raw_parts(data, data_length);
let result = Box::new(SPCharGrid(
servicepoint::CharGrid::load_utf8(width, height, data.to_vec())
.unwrap(),
));
NonNull::from(Box::leak(result))
}
/// Clones a [SPCharGrid].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_char_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_clone(
char_grid: *const SPCharGrid,
) -> NonNull<SPCharGrid> {
assert!(!char_grid.is_null());
let result = Box::new((*char_grid).clone());
NonNull::from(Box::leak(result))
}
/// Deallocates a [SPCharGrid].
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not used concurrently or after char_grid call
/// - `char_grid` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_free(char_grid: *mut SPCharGrid) {
assert!(!char_grid.is_null());
_ = Box::from_raw(char_grid);
}
/// Gets the current value at the specified position.
///
/// # Arguments
///
/// - `char_grid`: instance to read from
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
/// - when `char_grid` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_get(
char_grid: *const SPCharGrid,
x: usize,
y: usize,
) -> u32 {
assert!(!char_grid.is_null());
(*char_grid).0.get(x, y) as u32
}
/// Sets the value of the specified position in the [SPCharGrid].
///
/// # Arguments
///
/// - `char_grid`: instance to write to
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
/// # Panics
///
/// - when `char_grid` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPBitVec]
/// - `char_grid` is not written to or read from concurrently
///
/// [SPBitVec]: [crate::SPBitVec]
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_set(
char_grid: *mut SPCharGrid,
x: usize,
y: usize,
value: u32,
) {
assert!(!char_grid.is_null());
(*char_grid).0.set(x, y, char::from_u32(value).unwrap());
}
/// Sets the value of all cells in the [SPCharGrid].
///
/// # Arguments
///
/// - `char_grid`: instance to write to
/// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
/// - `char_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_fill(
char_grid: *mut SPCharGrid,
value: u32,
) {
assert!(!char_grid.is_null());
(*char_grid).0.fill(char::from_u32(value).unwrap());
}
/// Gets the width of the [SPCharGrid] instance.
///
/// # Arguments
///
/// - `char_grid`: instance to read from
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_width(
char_grid: *const SPCharGrid,
) -> usize {
assert!(!char_grid.is_null());
(*char_grid).0.width()
}
/// Gets the height of the [SPCharGrid] instance.
///
/// # Arguments
///
/// - `char_grid`: instance to read from
///
/// # Panics
///
/// - when `char_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `char_grid` points to a valid [SPCharGrid]
#[no_mangle]
pub unsafe extern "C" fn sp_char_grid_height(
char_grid: *const SPCharGrid,
) -> usize {
assert!(!char_grid.is_null());
(*char_grid).0.height()
}

View file

@ -1,498 +0,0 @@
//! C functions for interacting with [SPCommand]s
//!
//! prefix `sp_command_`
use std::ptr::{null_mut, NonNull};
use crate::{
SPBitVec, SPBitmap, SPBrightnessGrid, SPCharGrid, SPCompressionCode,
SPCp437Grid, SPPacket,
};
/// A low-level display command.
///
/// This struct and associated functions implement the UDP protocol for the display.
///
/// To send a [SPCommand], use a [SPConnection].
///
/// # Examples
///
/// ```C
/// sp_connection_send_command(connection, sp_command_clear());
/// sp_connection_send_command(connection, sp_command_brightness(5));
/// ```
///
/// [SPConnection]: [crate::SPConnection]
pub struct SPCommand(pub(crate) servicepoint::Command);
impl Clone for SPCommand {
fn clone(&self) -> Self {
SPCommand(self.0.clone())
}
}
/// Tries to turn a [SPPacket] into a [SPCommand].
///
/// The packet is deallocated in the process.
///
/// Returns: pointer to new [SPCommand] instance or NULL if parsing failed.
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - [SPPacket] points to a valid instance of [SPPacket]
/// - [SPPacket] is not used concurrently or after this call
/// - the result is checked for NULL
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_try_from_packet(
packet: *mut SPPacket,
) -> *mut SPCommand {
let packet = *Box::from_raw(packet);
match servicepoint::Command::try_from(packet.0) {
Err(_) => null_mut(),
Ok(command) => Box::into_raw(Box::new(SPCommand(command))),
}
}
/// Clones a [SPCommand] instance.
///
/// returns: new [SPCommand] instance. Will never return NULL.
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `command` points to a valid instance of [SPCommand]
/// - `command` is not written to concurrently
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_clone(
command: *const SPCommand,
) -> NonNull<SPCommand> {
assert!(!command.is_null());
let result = Box::new((*command).clone());
NonNull::from(Box::leak(result))
}
/// Set all pixels to the off state.
///
/// Does not affect brightness.
///
/// Returns: a new [servicepoint::Command::Clear] instance. Will never return NULL.
///
/// # Examples
///
/// ```C
/// sp_connection_send_command(connection, sp_command_clear());
/// ```
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_clear() -> NonNull<SPCommand> {
let result = Box::new(SPCommand(servicepoint::Command::Clear));
NonNull::from(Box::leak(result))
}
/// Kills the udp daemon on the display, which usually results in a restart.
///
/// Please do not send this in your normal program flow.
///
/// Returns: a new [servicepoint::Command::HardReset] instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_hard_reset() -> NonNull<SPCommand> {
let result = Box::new(SPCommand(servicepoint::Command::HardReset));
NonNull::from(Box::leak(result))
}
/// A yet-to-be-tested command.
///
/// Returns: a new [servicepoint::Command::FadeOut] instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_fade_out() -> NonNull<SPCommand> {
let result = Box::new(SPCommand(servicepoint::Command::FadeOut));
NonNull::from(Box::leak(result))
}
/// Set the brightness of all tiles to the same value.
///
/// Returns: a new [servicepoint::Command::Brightness] instance. Will never return NULL.
///
/// # Panics
///
/// - When the provided brightness value is out of range (0-11).
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_brightness(
brightness: u8,
) -> NonNull<SPCommand> {
let brightness = servicepoint::Brightness::try_from(brightness)
.expect("invalid brightness");
let result =
Box::new(SPCommand(servicepoint::Command::Brightness(brightness)));
NonNull::from(Box::leak(result))
}
/// Set the brightness of individual tiles in a rectangular area of the display.
///
/// The passed [SPBrightnessGrid] gets consumed.
///
/// Returns: a new [servicepoint::Command::CharBrightness] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPBrightnessGrid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_char_brightness(
x: usize,
y: usize,
grid: *mut SPBrightnessGrid,
) -> NonNull<SPCommand> {
assert!(!grid.is_null());
let byte_grid = *Box::from_raw(grid);
let result = Box::new(SPCommand(servicepoint::Command::CharBrightness(
servicepoint::Origin::new(x, y),
byte_grid.0,
)));
NonNull::from(Box::leak(result))
}
/// 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 [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinear] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinear(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
/// 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 [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearAnd] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_and(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearAnd(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
/// 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 [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearOr] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_or(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearOr(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
/// 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 [SPBitVec] is always uncompressed.
///
/// The passed [SPBitVec] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearXor] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bit_vec` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bit_vec` points to a valid instance of [SPBitVec]
/// - `bit_vec` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_xor(
offset: usize,
bit_vec: *mut SPBitVec,
compression: SPCompressionCode,
) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearXor(
offset,
bit_vec.into(),
compression.try_into().expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
/// Show codepage 437 encoded text on the screen.
///
/// The passed [SPCp437Grid] gets consumed.
///
/// Returns: a new [servicepoint::Command::Cp437Data] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is null
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPCp437Grid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_cp437_data(
x: usize,
y: usize,
grid: *mut SPCp437Grid,
) -> NonNull<SPCommand> {
assert!(!grid.is_null());
let grid = *Box::from_raw(grid);
let result = Box::new(SPCommand(servicepoint::Command::Cp437Data(
servicepoint::Origin::new(x, y),
grid.0,
)));
NonNull::from(Box::leak(result))
}
/// Show UTF-8 encoded text on the screen.
///
/// The passed [SPCharGrid] gets consumed.
///
/// Returns: a new [servicepoint::Command::Utf8Data] instance. Will never return NULL.
///
/// # Panics
///
/// - when `grid` is null
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `grid` points to a valid instance of [SPCharGrid]
/// - `grid` is not used concurrently or after this call
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_utf8_data(
x: usize,
y: usize,
grid: *mut SPCharGrid,
) -> NonNull<SPCommand> {
assert!(!grid.is_null());
let grid = *Box::from_raw(grid);
let result = Box::new(SPCommand(servicepoint::Command::Utf8Data(
servicepoint::Origin::new(x, y),
grid.0,
)));
NonNull::from(Box::leak(result))
}
/// Sets a window of pixels to the specified values.
///
/// The passed [SPBitmap] gets consumed.
///
/// Returns: a new [servicepoint::Command::BitmapLinearWin] instance. Will never return NULL.
///
/// # Panics
///
/// - when `bitmap` is null
/// - when `compression_code` is not a valid value
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `bitmap` points to a valid instance of [SPBitmap]
/// - `bitmap` is not used concurrently or after this call
/// - `compression` matches one of the allowed enum values
/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_command_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_command_bitmap_linear_win(
x: usize,
y: usize,
bitmap: *mut SPBitmap,
compression_code: SPCompressionCode,
) -> NonNull<SPCommand> {
assert!(!bitmap.is_null());
let byte_grid = (*Box::from_raw(bitmap)).0;
let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearWin(
servicepoint::Origin::new(x, y),
byte_grid,
compression_code
.try_into()
.expect("invalid compression code"),
)));
NonNull::from(Box::leak(result))
}
/// Deallocates a [SPCommand].
///
/// # Examples
///
/// ```C
/// SPCommand c = sp_command_clear();
/// sp_command_free(c);
/// ```
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `command` points to a valid [SPCommand]
/// - `command` is not used concurrently or after this call
/// - `command` was not passed to another consuming function, e.g. to create a [SPPacket]
#[no_mangle]
pub unsafe extern "C" fn sp_command_free(command: *mut SPCommand) {
assert!(!command.is_null());
_ = Box::from_raw(command);
}

View file

@ -1,139 +0,0 @@
//! C functions for interacting with [SPConnection]s
//!
//! prefix `sp_connection_`
use std::ffi::{c_char, CStr};
use std::ptr::{null_mut, NonNull};
use crate::{SPCommand, SPPacket};
/// A connection to the display.
///
/// # Examples
///
/// ```C
/// CConnection connection = sp_connection_open("172.23.42.29:2342");
/// if (connection != NULL)
/// sp_connection_send_command(connection, sp_command_clear());
/// ```
pub struct SPConnection(pub(crate) servicepoint::Connection);
/// Creates a new instance of [SPConnection].
///
/// returns: NULL if connection fails, or connected instance
///
/// # Panics
///
/// - when `host` is null or an invalid host
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_connection_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_connection_open(
host: *const c_char,
) -> *mut SPConnection {
assert!(!host.is_null());
let host = CStr::from_ptr(host).to_str().expect("Bad encoding");
let connection = match servicepoint::Connection::open(host) {
Err(_) => return null_mut(),
Ok(value) => value,
};
Box::into_raw(Box::new(SPConnection(connection)))
}
/// Creates a new instance of [SPConnection] for testing that does not actually send anything.
///
/// returns: a new instance. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_connection_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_connection_fake() -> NonNull<SPConnection> {
let result = Box::new(SPConnection(servicepoint::Connection::Fake));
NonNull::from(Box::leak(result))
}
/// Sends a [SPPacket] to the display using the [SPConnection].
///
/// The passed `packet` gets consumed.
///
/// returns: true in case of success
///
/// # Panics
///
/// - when `connection` is NULL
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid instance of [SPConnection]
/// - `packet` points to a valid instance of [SPPacket]
/// - `packet` is not used concurrently or after this call
#[no_mangle]
pub unsafe extern "C" fn sp_connection_send_packet(
connection: *const SPConnection,
packet: *mut SPPacket,
) -> bool {
assert!(!connection.is_null());
assert!(!packet.is_null());
let packet = Box::from_raw(packet);
(*connection).0.send((*packet).0).is_ok()
}
/// Sends a [SPCommand] to the display using the [SPConnection].
///
/// The passed `command` gets consumed.
///
/// returns: true in case of success
///
/// # Panics
///
/// - when `connection` is NULL
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid instance of [SPConnection]
/// - `command` points to a valid instance of [SPPacket]
/// - `command` is not used concurrently or after this call
#[no_mangle]
pub unsafe extern "C" fn sp_connection_send_command(
connection: *const SPConnection,
command: *mut SPCommand,
) -> bool {
assert!(!connection.is_null());
assert!(!command.is_null());
let command = (*Box::from_raw(command)).0;
(*connection).0.send(command).is_ok()
}
/// Closes and deallocates a [SPConnection].
///
/// # Panics
///
/// - when `connection` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `connection` points to a valid [SPConnection]
/// - `connection` is not used concurrently or after this call
#[no_mangle]
pub unsafe extern "C" fn sp_connection_free(connection: *mut SPConnection) {
assert!(!connection.is_null());
_ = Box::from_raw(connection);
}

View file

@ -1,48 +0,0 @@
//! re-exported constants for use in C
use servicepoint::CompressionCode;
use std::time::Duration;
/// size of a single tile in one dimension
pub const SP_TILE_SIZE: usize = 8;
/// Display tile count in the x-direction
pub const SP_TILE_WIDTH: usize = 56;
/// Display tile count in the y-direction
pub const SP_TILE_HEIGHT: usize = 20;
/// Display width in pixels
pub const SP_PIXEL_WIDTH: usize = SP_TILE_WIDTH * SP_TILE_SIZE;
/// Display height in pixels
pub const SP_PIXEL_HEIGHT: usize = SP_TILE_HEIGHT * SP_TILE_SIZE;
/// pixel count on whole screen
pub const SP_PIXEL_COUNT: usize = SP_PIXEL_WIDTH * SP_PIXEL_HEIGHT;
/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets.
pub const SP_FRAME_PACING_MS: u128 = Duration::from_millis(30).as_millis();
/// Specifies the kind of compression to use.
#[repr(u16)]
pub enum SPCompressionCode {
/// no compression
Uncompressed = 0x0,
/// compress using flate2 with zlib header
Zlib = 0x677a,
/// compress using bzip2
Bzip2 = 0x627a,
/// compress using lzma
Lzma = 0x6c7a,
/// compress using Zstandard
Zstd = 0x7a73,
}
impl TryFrom<SPCompressionCode> for CompressionCode {
type Error = ();
fn try_from(value: SPCompressionCode) -> Result<Self, Self::Error> {
CompressionCode::try_from(value as u16)
}
}

View file

@ -1,285 +0,0 @@
//! C functions for interacting with [SPCp437Grid]s
//!
//! prefix `sp_cp437_grid_`
use crate::SPByteSlice;
use servicepoint::{DataRef, Grid};
use std::ptr::NonNull;
/// A C-wrapper for grid containing codepage 437 characters.
///
/// The encoding is currently not enforced.
///
/// # Examples
///
/// ```C
/// Cp437Grid grid = sp_cp437_grid_new(4, 3);
/// sp_cp437_grid_fill(grid, '?');
/// sp_cp437_grid_set(grid, 0, 0, '!');
/// sp_cp437_grid_free(grid);
/// ```
pub struct SPCp437Grid(pub(crate) servicepoint::Cp437Grid);
impl Clone for SPCp437Grid {
fn clone(&self) -> Self {
SPCp437Grid(self.0.clone())
}
}
/// Creates a new [SPCp437Grid] with the specified dimensions.
///
/// returns: [SPCp437Grid] initialized to 0. Will never return NULL.
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_new(
width: usize,
height: usize,
) -> NonNull<SPCp437Grid> {
let result =
Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height)));
NonNull::from(Box::leak(result))
}
/// Loads a [SPCp437Grid] with the specified dimensions from the provided data.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `data` is NULL
/// - when the provided `data_length` does not match `height` and `width`
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory location of at least `data_length`
/// bytes in size.
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_load(
width: usize,
height: usize,
data: *const u8,
data_length: usize,
) -> NonNull<SPCp437Grid> {
assert!(data.is_null());
let data = std::slice::from_raw_parts(data, data_length);
let result = Box::new(SPCp437Grid(servicepoint::Cp437Grid::load(
width, height, data,
)));
NonNull::from(Box::leak(result))
}
/// Clones a [SPCp437Grid].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_cp437_grid_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_clone(
cp437_grid: *const SPCp437Grid,
) -> NonNull<SPCp437Grid> {
assert!(!cp437_grid.is_null());
let result = Box::new((*cp437_grid).clone());
NonNull::from(Box::leak(result))
}
/// Deallocates a [SPCp437Grid].
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not used concurrently or after cp437_grid call
/// - `cp437_grid` was not passed to another consuming function, e.g. to create a [SPCommand]
///
/// [SPCommand]: [crate::SPCommand]
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_free(cp437_grid: *mut SPCp437Grid) {
assert!(!cp437_grid.is_null());
_ = Box::from_raw(cp437_grid);
}
/// Gets the current value at the specified position.
///
/// # Arguments
///
/// - `cp437_grid`: instance to read from
/// - `x` and `y`: position of the cell to read
///
/// # Panics
///
/// - when `cp437_grid` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_get(
cp437_grid: *const SPCp437Grid,
x: usize,
y: usize,
) -> u8 {
assert!(!cp437_grid.is_null());
(*cp437_grid).0.get(x, y)
}
/// Sets the value of the specified position in the [SPCp437Grid].
///
/// # Arguments
///
/// - `cp437_grid`: instance to write to
/// - `x` and `y`: position of the cell
/// - `value`: the value to write to the cell
///
/// returns: old value of the cell
///
/// # Panics
///
/// - when `cp437_grid` is NULL
/// - when accessing `x` or `y` out of bounds
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPBitVec]
/// - `cp437_grid` is not written to or read from concurrently
///
/// [SPBitVec]: [crate::SPBitVec]
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_set(
cp437_grid: *mut SPCp437Grid,
x: usize,
y: usize,
value: u8,
) {
assert!(!cp437_grid.is_null());
(*cp437_grid).0.set(x, y, value);
}
/// Sets the value of all cells in the [SPCp437Grid].
///
/// # Arguments
///
/// - `cp437_grid`: instance to write to
/// - `value`: the value to set all cells to
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - `cp437_grid` is not written to or read from concurrently
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_fill(
cp437_grid: *mut SPCp437Grid,
value: u8,
) {
assert!(!cp437_grid.is_null());
(*cp437_grid).0.fill(value);
}
/// Gets the width of the [SPCp437Grid] instance.
///
/// # Arguments
///
/// - `cp437_grid`: instance to read from
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_width(
cp437_grid: *const SPCp437Grid,
) -> usize {
assert!(!cp437_grid.is_null());
(*cp437_grid).0.width()
}
/// Gets the height of the [SPCp437Grid] instance.
///
/// # Arguments
///
/// - `cp437_grid`: instance to read from
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_height(
cp437_grid: *const SPCp437Grid,
) -> usize {
assert!(!cp437_grid.is_null());
(*cp437_grid).0.height()
}
/// Gets an unsafe reference to the data of the [SPCp437Grid] instance.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `cp437_grid` is NULL
///
/// ## Safety
///
/// The caller has to make sure that:
///
/// - `cp437_grid` points to a valid [SPCp437Grid]
/// - the returned memory range is never accessed after the passed [SPCp437Grid] has been freed
/// - the returned memory range is never accessed concurrently, either via the [SPCp437Grid] or directly
#[no_mangle]
pub unsafe extern "C" fn sp_cp437_grid_unsafe_data_ref(
cp437_grid: *mut SPCp437Grid,
) -> SPByteSlice {
let data = (*cp437_grid).0.data_ref_mut();
SPByteSlice {
start: NonNull::new(data.as_mut_ptr_range().start).unwrap(),
length: data.len(),
}
}

View file

@ -1,48 +0,0 @@
//! C API wrapper for the [servicepoint](https://docs.rs/servicepoint/latest/servicepoint/) crate.
//!
//! # Examples
//!
//! Make sure to check out [this GitHub repo](https://github.com/arfst23/ServicePoint) as well!
//!
//! ```C
//! #include <stdio.h>
//! #include "servicepoint.h"
//!
//! int main(void) {
//! SPConnection *connection = sp_connection_open("172.23.42.29:2342");
//! if (connection == NULL)
//! return 1;
//!
//! SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
//! sp_bitmap_fill(pixels, true);
//!
//! SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
//! while (sp_connection_send_command(connection, sp_command_clone(command)));
//!
//! sp_command_free(command);
//! sp_connection_free(connection);
//! return 0;
//! }
//! ```
pub use crate::bitmap::*;
pub use crate::bitvec::*;
pub use crate::brightness_grid::*;
pub use crate::byte_slice::*;
pub use crate::char_grid::*;
pub use crate::command::*;
pub use crate::connection::*;
pub use crate::constants::*;
pub use crate::cp437_grid::*;
pub use crate::packet::*;
mod bitmap;
mod bitvec;
mod brightness_grid;
mod byte_slice;
mod char_grid;
mod command;
mod connection;
mod constants;
mod cp437_grid;
mod packet;

View file

@ -1,166 +0,0 @@
//! C functions for interacting with [SPPacket]s
//!
//! prefix `sp_packet_`
use std::ptr::{null_mut, NonNull};
use crate::SPCommand;
/// The raw packet
pub struct SPPacket(pub(crate) servicepoint::Packet);
/// Turns a [SPCommand] into a [SPPacket].
/// The [SPCommand] gets consumed.
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `command` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - [SPCommand] points to a valid instance of [SPCommand]
/// - [SPCommand] is not used concurrently or after this call
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_from_command(
command: *mut SPCommand,
) -> NonNull<SPPacket> {
assert!(!command.is_null());
let command = *Box::from_raw(command);
let result = Box::new(SPPacket(command.0.into()));
NonNull::from(Box::leak(result))
}
/// Tries to load a [SPPacket] from the passed array with the specified length.
///
/// returns: NULL in case of an error, pointer to the allocated packet otherwise
///
/// # Panics
///
/// - when `data` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `data` points to a valid memory region of at least `length` bytes
/// - `data` is not written to concurrently
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_try_load(
data: *const u8,
length: usize,
) -> *mut SPPacket {
assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, length);
match servicepoint::Packet::try_from(data) {
Err(_) => null_mut(),
Ok(packet) => Box::into_raw(Box::new(SPPacket(packet))),
}
}
/// Creates a raw [SPPacket] from parts.
///
/// # Arguments
///
/// - `command_code` specifies which command this packet contains
/// - `a`, `b`, `c` and `d` are command-specific header values
/// - `payload` is the optional data that is part of the command
/// - `payload_len` is the size of the payload
///
/// returns: new instance. Will never return null.
///
/// # Panics
///
/// - when `payload` is null, but `payload_len` is not zero
/// - when `payload_len` is zero, but `payload` is nonnull
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `payload` points to a valid memory region of at least `payload_len` bytes
/// - `payload` is not written to concurrently
/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
/// by explicitly calling [sp_packet_free].
#[no_mangle]
pub unsafe extern "C" fn sp_packet_from_parts(
command_code: u16,
a: u16,
b: u16,
c: u16,
d: u16,
payload: *const u8,
payload_len: usize,
) -> NonNull<SPPacket> {
assert_eq!(payload.is_null(), payload_len == 0);
let payload = if payload.is_null() {
vec![]
} else {
let payload = std::slice::from_raw_parts(payload, payload_len);
Vec::from(payload)
};
let packet = servicepoint::Packet {
header: servicepoint::Header {
command_code,
a,
b,
c,
d,
},
payload,
};
let result = Box::new(SPPacket(packet));
NonNull::from(Box::leak(result))
}
/// Clones a [SPPacket].
///
/// Will never return NULL.
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `packet` points to a valid [SPPacket]
/// - `packet` is not written to concurrently
/// - the returned instance is freed in some way, either by using a consuming function or
/// by explicitly calling `sp_packet_free`.
#[no_mangle]
pub unsafe extern "C" fn sp_packet_clone(
packet: *const SPPacket,
) -> NonNull<SPPacket> {
assert!(!packet.is_null());
let result = Box::new(SPPacket((*packet).0.clone()));
NonNull::from(Box::leak(result))
}
/// Deallocates a [SPPacket].
///
/// # Panics
///
/// - when `packet` is NULL
///
/// # Safety
///
/// The caller has to make sure that:
///
/// - `packet` points to a valid [SPPacket]
/// - `packet` is not used concurrently or after this call
#[no_mangle]
pub unsafe extern "C" fn sp_packet_free(packet: *mut SPPacket) {
assert!(!packet.is_null());
_ = Box::from_raw(packet)
}

View file

@ -1,61 +0,0 @@
[package]
name = "servicepoint_binding_uniffi"
version.workspace = true
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"]
[lib]
crate-type = ["cdylib"]
[build-dependencies]
uniffi = { version = "0.25.3", features = ["build"] }
[dependencies]
uniffi = { version = "0.25.3" }
thiserror.workspace = true
[dependencies.servicepoint]
version = "0.13.1"
path = "../servicepoint"
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]
#workspace = true
[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"]

View file

@ -1,90 +0,0 @@
# 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 crate contains bindings for multiple programming languages, enabling non-rust-developers to use the library.
Also take a look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for more
information.
## 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.
## Notes on differences to rust library
- 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)
## Supported languages
| 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 | |
## Installation
Including this repository as a submodule and building from source is the recommended way of using the library.
```bash
git submodule add https://git.berlin.ccc.de/servicepoint/servicepoint.git
git commit -m "add servicepoint submodule"
```
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.

View file

@ -1,24 +0,0 @@
#!/usr/bin/env bash
set -e
cargo build --release
SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
TARGET_PATH="$(realpath "$SCRIPT_PATH"/../../target/release)"
SERVICEPOINT_SO="$TARGET_PATH/libservicepoint_binding_uniffi.so"
LIBRARIES_PATH="$SCRIPT_PATH/libraries"
echo "Source: $SERVICEPOINT_SO"
echo "Output: $LIBRARIES_PATH"
BINDGEN="cargo run --features=uniffi/cli --bin uniffi-bindgen -- "
BINDGEN_CS="cargo run --features=cs --bin uniffi-bindgen-cs -- "
BINDGEN_GO="cargo run --features=go --bin uniffi-bindgen-go -- "
COMMON_ARGS="--library $SERVICEPOINT_SO"
${BINDGEN} generate $COMMON_ARGS --language python --out-dir "$LIBRARIES_PATH/python"
${BINDGEN} generate $COMMON_ARGS --language kotlin --out-dir "$LIBRARIES_PATH/kotlin"
${BINDGEN} generate $COMMON_ARGS --language swift --out-dir "$LIBRARIES_PATH/swift"
${BINDGEN} generate $COMMON_ARGS --language ruby --out-dir "$LIBRARIES_PATH/ruby/lib"
${BINDGEN_CS} $COMMON_ARGS --out-dir "$LIBRARIES_PATH/csharp/ServicePoint"
${BINDGEN_GO} $COMMON_ARGS --out-dir "$LIBRARIES_PATH/go/"

View file

@ -1,4 +0,0 @@
go
kotlin
python
swift

View file

@ -1,3 +0,0 @@
source 'https://rubygems.org'
gem 'servicepoint', path: '..'

View file

@ -1,19 +0,0 @@
PATH
remote: ..
specs:
servicepoint (0.0.0)
ffi
GEM
remote: https://rubygems.org/
specs:
ffi (1.17.0-x86_64-linux-gnu)
PLATFORMS
x86_64-linux
DEPENDENCIES
servicepoint!
BUNDLED WITH
2.3.27

View file

@ -1,25 +0,0 @@
require_relative "../lib/servicepoint_binding_uniffi"
include ServicepointBindingUniffi
connection = Connection.new("172.23.42.29:2342")
pixels = Bitmap.new_max_sized
x_offset = 0
loop do
pixels.fill(false)
(0..((pixels.height) -1)).each do |y|
pixels.set((y + x_offset) % pixels.width, y, true);
end
command = Command.bitmap_linear_win(0, 0, pixels, CompressionCode::UNCOMPRESSED)
connection.send(command)
sleep 0.0005
x_offset += 1
end

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
LD_LIBRARY_PATH="../../../../../target/release:$LD_LIBRARY_PATH" ruby example.rb

View file

@ -1,13 +0,0 @@
Gem::Specification.new do |s|
s.name = "servicepoint"
s.version = "0.13.1"
s.summary = ""
s.description = ""
s.authors = ["kaesaecracker"]
s.email = ""
s.files = ["lib/servicepoint_binding_uniffi.rb"]
s.homepage =
"https://rubygems.org/gems/hola"
s.license = "MIT"
s.add_dependency 'ffi'
end

View file

@ -1,3 +0,0 @@
fn main() {
uniffi_bindgen_go::main().unwrap();
}

View file

@ -1,3 +0,0 @@
fn main() {
uniffi::uniffi_bindgen_main()
}

View file

@ -1,77 +0,0 @@
use servicepoint::{DataRef, Grid};
use std::sync::{Arc, RwLock};
#[derive(uniffi::Object)]
pub struct Bitmap {
pub(crate) actual: RwLock<servicepoint::Bitmap>,
}
impl Bitmap {
fn internal_new(actual: servicepoint::Bitmap) -> Arc<Self> {
Arc::new(Self {
actual: RwLock::new(actual),
})
}
}
#[uniffi::export]
impl Bitmap {
#[uniffi::constructor]
pub fn new(width: u64, height: u64) -> Arc<Self> {
Self::internal_new(servicepoint::Bitmap::new(
width as usize,
height as usize,
))
}
#[uniffi::constructor]
pub fn new_max_sized() -> Arc<Self> {
Self::internal_new(servicepoint::Bitmap::max_sized())
}
#[uniffi::constructor]
pub fn load(width: u64, height: u64, data: Vec<u8>) -> Arc<Self> {
Self::internal_new(servicepoint::Bitmap::load(
width as usize,
height as usize,
&data,
))
}
#[uniffi::constructor]
pub fn clone(other: &Arc<Self>) -> Arc<Self> {
Self::internal_new(other.actual.read().unwrap().clone())
}
pub fn set(&self, x: u64, y: u64, value: bool) {
self.actual
.write()
.unwrap()
.set(x as usize, y as usize, value)
}
pub fn get(&self, x: u64, y: u64) -> bool {
self.actual.read().unwrap().get(x as usize, y as usize)
}
pub fn fill(&self, value: bool) {
self.actual.write().unwrap().fill(value)
}
pub fn width(&self) -> u64 {
self.actual.read().unwrap().width() as u64
}
pub fn height(&self) -> u64 {
self.actual.read().unwrap().height() as u64
}
pub fn equals(&self, other: &Bitmap) -> bool {
let a = self.actual.read().unwrap();
let b = other.actual.read().unwrap();
*a == *b
}
pub fn copy_raw(&self) -> Vec<u8> {
self.actual.read().unwrap().data_ref().to_vec()
}
}

View file

@ -1,61 +0,0 @@
use std::sync::{Arc, RwLock};
#[derive(uniffi::Object)]
pub struct BitVec {
pub(crate) actual: RwLock<servicepoint::BitVec>,
}
impl BitVec {
fn internal_new(actual: servicepoint::BitVec) -> Arc<Self> {
Arc::new(Self {
actual: RwLock::new(actual),
})
}
}
#[uniffi::export]
impl BitVec {
#[uniffi::constructor]
pub fn new(size: u64) -> Arc<Self> {
Self::internal_new(servicepoint::BitVec::repeat(false, size as usize))
}
#[uniffi::constructor]
pub fn load(data: Vec<u8>) -> Arc<Self> {
Self::internal_new(servicepoint::BitVec::from_slice(&data))
}
#[uniffi::constructor]
pub fn clone(other: &Arc<Self>) -> Arc<Self> {
Self::internal_new(other.actual.read().unwrap().clone())
}
pub fn set(&self, index: u64, value: bool) {
self.actual.write().unwrap().set(index as usize, value)
}
pub fn get(&self, index: u64) -> bool {
self.actual
.read()
.unwrap()
.get(index as usize)
.is_some_and(move |bit| *bit)
}
pub fn fill(&self, value: bool) {
self.actual.write().unwrap().fill(value)
}
pub fn len(&self) -> u64 {
self.actual.read().unwrap().len() as u64
}
pub fn equals(&self, other: &BitVec) -> bool {
let a = self.actual.read().unwrap();
let b = other.actual.read().unwrap();
*a == *b
}
pub fn copy_raw(&self) -> Vec<u8> {
self.actual.read().unwrap().clone().into_vec()
}
}

View file

@ -1,86 +0,0 @@
use servicepoint::{Brightness, DataRef, Grid};
use std::sync::{Arc, RwLock};
#[derive(uniffi::Object)]
pub struct BrightnessGrid {
pub(crate) actual: RwLock<servicepoint::BrightnessGrid>,
}
impl BrightnessGrid {
fn internal_new(actual: servicepoint::BrightnessGrid) -> Arc<Self> {
Arc::new(Self {
actual: RwLock::new(actual),
})
}
}
#[uniffi::export]
impl BrightnessGrid {
#[uniffi::constructor]
pub fn new(width: u64, height: u64) -> Arc<Self> {
Self::internal_new(servicepoint::BrightnessGrid::new(
width as usize,
height as usize,
))
}
#[uniffi::constructor]
pub fn load(width: u64, height: u64, data: Vec<u8>) -> Arc<Self> {
Self::internal_new(servicepoint::BrightnessGrid::saturating_load(
width as usize,
height as usize,
&data,
))
}
#[uniffi::constructor]
pub fn clone(other: &Arc<Self>) -> Arc<Self> {
Self::internal_new(other.actual.read().unwrap().clone())
}
pub fn set(&self, x: u64, y: u64, value: u8) {
self.actual.write().unwrap().set(
x as usize,
y as usize,
Brightness::saturating_from(value),
)
}
pub fn get(&self, x: u64, y: u64) -> u8 {
self.actual
.read()
.unwrap()
.get(x as usize, y as usize)
.into()
}
pub fn fill(&self, value: u8) {
self.actual
.write()
.unwrap()
.fill(Brightness::saturating_from(value))
}
pub fn width(&self) -> u64 {
self.actual.read().unwrap().width() as u64
}
pub fn height(&self) -> u64 {
self.actual.read().unwrap().height() as u64
}
pub fn equals(&self, other: &BrightnessGrid) -> bool {
let a = self.actual.read().unwrap();
let b = other.actual.read().unwrap();
*a == *b
}
pub fn copy_raw(&self) -> Vec<u8> {
self.actual
.read()
.unwrap()
.data_ref()
.iter()
.map(u8::from)
.collect()
}
}

View file

@ -1,169 +0,0 @@
use crate::cp437_grid::Cp437Grid;
use servicepoint::{Grid, SetValueSeriesError};
use std::convert::Into;
use std::sync::{Arc, RwLock};
#[derive(uniffi::Object)]
pub struct CharGrid {
pub(crate) actual: RwLock<servicepoint::CharGrid>,
}
#[derive(uniffi::Error, thiserror::Error, Debug)]
pub enum CharGridError {
#[error("Exactly one character was expected, but {value:?} was provided")]
StringNotOneChar { value: String },
#[error("The provided series was expected to have a length of {expected}, but was {actual}")]
InvalidSeriesLength { actual: u64, expected: u64 },
#[error("The index {index} was out of bounds for size {size}")]
OutOfBounds { index: u64, size: u64 },
}
#[uniffi::export]
impl CharGrid {
#[uniffi::constructor]
pub fn new(width: u64, height: u64) -> Arc<Self> {
Self::internal_new(servicepoint::CharGrid::new(
width as usize,
height as usize,
))
}
#[uniffi::constructor]
pub fn load(data: String) -> Arc<Self> {
Self::internal_new(servicepoint::CharGrid::from(&*data))
}
#[uniffi::constructor]
pub fn clone(other: &Arc<Self>) -> Arc<Self> {
Self::internal_new(other.actual.read().unwrap().clone())
}
pub fn set(
&self,
x: u64,
y: u64,
value: String,
) -> Result<(), CharGridError> {
let value = Self::str_to_char(value)?;
self.actual
.write()
.unwrap()
.set(x as usize, y as usize, value);
Ok(())
}
pub fn get(&self, x: u64, y: u64) -> String {
self.actual
.read()
.unwrap()
.get(x as usize, y as usize)
.into()
}
pub fn fill(&self, value: String) -> Result<(), CharGridError> {
let value = Self::str_to_char(value)?;
self.actual.write().unwrap().fill(value);
Ok(())
}
pub fn width(&self) -> u64 {
self.actual.read().unwrap().width() as u64
}
pub fn height(&self) -> u64 {
self.actual.read().unwrap().height() as u64
}
pub fn equals(&self, other: &CharGrid) -> bool {
let a = self.actual.read().unwrap();
let b = other.actual.read().unwrap();
*a == *b
}
pub fn as_string(&self) -> String {
let grid = self.actual.read().unwrap();
String::from(&*grid)
}
pub fn set_row(&self, y: u64, row: String) -> Result<(), CharGridError> {
self.actual
.write()
.unwrap()
.set_row(y as usize, &row.chars().collect::<Vec<_>>())
.map_err(CharGridError::from)
}
pub fn set_col(&self, x: u64, col: String) -> Result<(), CharGridError> {
self.actual
.write()
.unwrap()
.set_row(x as usize, &col.chars().collect::<Vec<_>>())
.map_err(CharGridError::from)
}
pub fn get_row(&self, y: u64) -> Result<String, CharGridError> {
self.actual
.read()
.unwrap()
.get_row(y as usize)
.map(String::from_iter)
.ok_or(CharGridError::OutOfBounds {
index: y,
size: self.height(),
})
}
pub fn get_col(&self, x: u64) -> Result<String, CharGridError> {
self.actual
.read()
.unwrap()
.get_col(x as usize)
.map(String::from_iter)
.ok_or(CharGridError::OutOfBounds {
index: x,
size: self.width(),
})
}
pub fn to_cp437(&self) -> Arc<Cp437Grid> {
Cp437Grid::internal_new(servicepoint::Cp437Grid::from(
&*self.actual.read().unwrap(),
))
}
}
impl CharGrid {
pub(crate) fn internal_new(actual: servicepoint::CharGrid) -> Arc<Self> {
Arc::new(Self {
actual: RwLock::new(actual),
})
}
fn str_to_char(value: String) -> Result<char, CharGridError> {
if value.len() != 1 {
return Err(CharGridError::StringNotOneChar { value });
}
let value = value.chars().nth(0).unwrap();
Ok(value)
}
}
impl From<SetValueSeriesError> for CharGridError {
fn from(e: SetValueSeriesError) -> Self {
match e {
SetValueSeriesError::OutOfBounds { index, size } => {
CharGridError::OutOfBounds {
index: index as u64,
size: size as u64,
}
}
SetValueSeriesError::InvalidLength { actual, expected } => {
CharGridError::InvalidSeriesLength {
actual: actual as u64,
expected: expected as u64,
}
}
}
}
}

View file

@ -1,175 +0,0 @@
use crate::bitmap::Bitmap;
use crate::bitvec::BitVec;
use crate::brightness_grid::BrightnessGrid;
use crate::char_grid::CharGrid;
use crate::compression_code::CompressionCode;
use crate::cp437_grid::Cp437Grid;
use crate::errors::ServicePointError;
use servicepoint::Origin;
use std::sync::Arc;
#[derive(uniffi::Object)]
pub struct Command {
pub(crate) actual: servicepoint::Command,
}
impl Command {
fn internal_new(actual: servicepoint::Command) -> Arc<Command> {
Arc::new(Command { actual })
}
}
#[uniffi::export]
impl Command {
#[uniffi::constructor]
pub fn clear() -> Arc<Self> {
Self::internal_new(servicepoint::Command::Clear)
}
#[uniffi::constructor]
pub fn brightness(brightness: u8) -> Result<Arc<Self>, ServicePointError> {
servicepoint::Brightness::try_from(brightness)
.map_err(move |value| ServicePointError::InvalidBrightness {
value,
})
.map(servicepoint::Command::Brightness)
.map(Self::internal_new)
}
#[uniffi::constructor]
pub fn fade_out() -> Arc<Self> {
Self::internal_new(servicepoint::Command::FadeOut)
}
#[uniffi::constructor]
pub fn hard_reset() -> Arc<Self> {
Self::internal_new(servicepoint::Command::HardReset)
}
#[uniffi::constructor]
pub fn bitmap_linear_win(
offset_x: u64,
offset_y: u64,
bitmap: &Arc<Bitmap>,
compression: CompressionCode,
) -> Arc<Self> {
let origin = Origin::new(offset_x as usize, offset_y as usize);
let bitmap = bitmap.actual.read().unwrap().clone();
let actual = servicepoint::Command::BitmapLinearWin(
origin,
bitmap,
servicepoint::CompressionCode::try_from(compression as u16)
.unwrap(),
);
Self::internal_new(actual)
}
#[uniffi::constructor]
pub fn char_brightness(
offset_x: u64,
offset_y: u64,
grid: &Arc<BrightnessGrid>,
) -> Arc<Self> {
let origin = Origin::new(offset_x as usize, offset_y as usize);
let grid = grid.actual.read().unwrap().clone();
let actual = servicepoint::Command::CharBrightness(origin, grid);
Self::internal_new(actual)
}
#[uniffi::constructor]
pub fn bitmap_linear(
offset: u64,
bitmap: &Arc<BitVec>,
compression: CompressionCode,
) -> Arc<Self> {
let bitmap = bitmap.actual.read().unwrap().clone();
let actual = servicepoint::Command::BitmapLinear(
offset as usize,
bitmap,
servicepoint::CompressionCode::try_from(compression as u16)
.unwrap(),
);
Self::internal_new(actual)
}
#[uniffi::constructor]
pub fn bitmap_linear_and(
offset: u64,
bitmap: &Arc<BitVec>,
compression: CompressionCode,
) -> Arc<Self> {
let bitmap = bitmap.actual.read().unwrap().clone();
let actual = servicepoint::Command::BitmapLinearAnd(
offset as usize,
bitmap,
servicepoint::CompressionCode::try_from(compression as u16)
.unwrap(),
);
Self::internal_new(actual)
}
#[uniffi::constructor]
pub fn bitmap_linear_or(
offset: u64,
bitmap: &Arc<BitVec>,
compression: CompressionCode,
) -> Arc<Self> {
let bitmap = bitmap.actual.read().unwrap().clone();
let actual = servicepoint::Command::BitmapLinearOr(
offset as usize,
bitmap,
servicepoint::CompressionCode::try_from(compression as u16)
.unwrap(),
);
Self::internal_new(actual)
}
#[uniffi::constructor]
pub fn bitmap_linear_xor(
offset: u64,
bitmap: &Arc<BitVec>,
compression: CompressionCode,
) -> Arc<Self> {
let bitmap = bitmap.actual.read().unwrap().clone();
let actual = servicepoint::Command::BitmapLinearXor(
offset as usize,
bitmap,
servicepoint::CompressionCode::try_from(compression as u16)
.unwrap(),
);
Self::internal_new(actual)
}
#[uniffi::constructor]
pub fn cp437_data(
offset_x: u64,
offset_y: u64,
grid: &Arc<Cp437Grid>,
) -> Arc<Self> {
let origin = Origin::new(offset_x as usize, offset_y as usize);
let grid = grid.actual.read().unwrap().clone();
let actual = servicepoint::Command::Cp437Data(origin, grid);
Self::internal_new(actual)
}
#[uniffi::constructor]
pub fn utf8_data(
offset_x: u64,
offset_y: u64,
grid: &Arc<CharGrid>,
) -> Arc<Self> {
let origin = Origin::new(offset_x as usize, offset_y as usize);
let grid = grid.actual.read().unwrap().clone();
let actual = servicepoint::Command::Utf8Data(origin, grid);
Self::internal_new(actual)
}
#[uniffi::constructor]
pub fn clone(other: &Arc<Self>) -> Arc<Self> {
Self::internal_new(other.actual.clone())
}
pub fn equals(&self, other: &Command) -> bool {
self.actual == other.actual
}
}

View file

@ -1,14 +0,0 @@
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, uniffi::Enum)]
pub enum CompressionCode {
/// no compression
Uncompressed = 0x0,
/// compress using flate2 with zlib header
Zlib = 0x677a,
/// compress using bzip2
Bzip2 = 0x627a,
/// compress using lzma
Lzma = 0x6c7a,
/// compress using Zstandard
Zstd = 0x7a73,
}

View file

@ -1,36 +0,0 @@
use std::sync::Arc;
use crate::command::Command;
use crate::errors::ServicePointError;
#[derive(uniffi::Object)]
pub struct Connection {
actual: servicepoint::Connection,
}
#[uniffi::export]
impl Connection {
#[uniffi::constructor]
pub fn new(host: String) -> Result<Arc<Self>, ServicePointError> {
servicepoint::Connection::open(host)
.map(|actual| Arc::new(Connection { actual }))
.map_err(|err| ServicePointError::IoError {
error: err.to_string(),
})
}
#[uniffi::constructor]
pub fn new_fake() -> Arc<Self> {
Arc::new(Self {
actual: servicepoint::Connection::Fake,
})
}
pub fn send(&self, command: Arc<Command>) -> Result<(), ServicePointError> {
self.actual.send(command.actual.clone()).map_err(|err| {
ServicePointError::IoError {
error: format!("{err:?}"),
}
})
}
}

View file

@ -1,23 +0,0 @@
#[derive(
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, uniffi::Record,
)]
pub struct Constants {
pub tile_size: u64,
pub tile_width: u64,
pub tile_height: u64,
pub pixel_width: u64,
pub pixel_height: u64,
pub pixel_count: u64,
}
#[uniffi::export]
fn get_constants() -> Constants {
Constants {
tile_size: servicepoint::TILE_SIZE as u64,
tile_width: servicepoint::TILE_WIDTH as u64,
tile_height: servicepoint::TILE_HEIGHT as u64,
pixel_width: servicepoint::PIXEL_WIDTH as u64,
pixel_height: servicepoint::PIXEL_HEIGHT as u64,
pixel_count: servicepoint::PIXEL_COUNT as u64,
}
}

View file

@ -1,79 +0,0 @@
use crate::char_grid::CharGrid;
use servicepoint::{DataRef, Grid};
use std::sync::{Arc, RwLock};
#[derive(uniffi::Object)]
pub struct Cp437Grid {
pub(crate) actual: RwLock<servicepoint::Cp437Grid>,
}
impl Cp437Grid {
pub(crate) fn internal_new(actual: servicepoint::Cp437Grid) -> Arc<Self> {
Arc::new(Self {
actual: RwLock::new(actual),
})
}
}
#[uniffi::export]
impl Cp437Grid {
#[uniffi::constructor]
pub fn new(width: u64, height: u64) -> Arc<Self> {
Self::internal_new(servicepoint::Cp437Grid::new(
width as usize,
height as usize,
))
}
#[uniffi::constructor]
pub fn load(width: u64, height: u64, data: Vec<u8>) -> Arc<Self> {
Self::internal_new(servicepoint::Cp437Grid::load(
width as usize,
height as usize,
&data,
))
}
#[uniffi::constructor]
pub fn clone(other: &Arc<Self>) -> Arc<Self> {
Self::internal_new(other.actual.read().unwrap().clone())
}
pub fn set(&self, x: u64, y: u64, value: u8) {
self.actual
.write()
.unwrap()
.set(x as usize, y as usize, value)
}
pub fn get(&self, x: u64, y: u64) -> u8 {
self.actual.read().unwrap().get(x as usize, y as usize)
}
pub fn fill(&self, value: u8) {
self.actual.write().unwrap().fill(value)
}
pub fn width(&self) -> u64 {
self.actual.read().unwrap().width() as u64
}
pub fn height(&self) -> u64 {
self.actual.read().unwrap().height() as u64
}
pub fn equals(&self, other: &Cp437Grid) -> bool {
let a = self.actual.read().unwrap();
let b = other.actual.read().unwrap();
*a == *b
}
pub fn copy_raw(&self) -> Vec<u8> {
self.actual.read().unwrap().data_ref().to_vec()
}
pub fn to_utf8(&self) -> Arc<CharGrid> {
CharGrid::internal_new(servicepoint::CharGrid::from(
&*self.actual.read().unwrap(),
))
}
}

View file

@ -1,7 +0,0 @@
#[derive(uniffi::Error, thiserror::Error, Debug)]
pub enum ServicePointError {
#[error("An IO error occurred: {error}")]
IoError { error: String },
#[error("The specified brightness value {value} is out of range")]
InvalidBrightness { value: u8 },
}

View file

@ -1,12 +0,0 @@
uniffi::setup_scaffolding!();
mod bitmap;
mod bitvec;
mod brightness_grid;
mod char_grid;
mod command;
mod compression_code;
mod connection;
mod constants;
mod cp437_grid;
mod errors;

View file

@ -1,25 +1,5 @@
{
"nodes": {
"naersk": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1736429655,
"narHash": "sha256-BwMekRuVlSB9C0QgwKMICiJ5EVbLGjfe4qyueyNQyGI=",
"owner": "nix-community",
"repo": "naersk",
"rev": "0621e47bd95542b8e1ce2ee2d65d6a1f887a13ce",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1739357830,
@ -38,7 +18,6 @@
},
"root": {
"inputs": {
"naersk": "naersk",
"nixpkgs": "nixpkgs"
}
}

View file

@ -3,17 +3,12 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
naersk = {
url = "github:nix-community/naersk";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
inputs@{
self,
nixpkgs,
naersk,
}:
let
lib = nixpkgs.lib;
@ -33,98 +28,11 @@
}
);
in
rec {
packages = forAllSystems (
{ pkgs, ... }:
let
naersk' = pkgs.callPackage naersk { };
nativeBuildInputs = with pkgs; [
pkg-config
makeWrapper
];
buildInputs = with pkgs; [
xe
xz
];
makeExample =
{
package,
example,
features ? "",
}:
naersk'.buildPackage {
pname = example;
cargoBuildOptions =
x:
x
++ [
"--package"
package
];
src = ./.;
inherit nativeBuildInputs buildInputs;
strictDeps = true;
gitSubmodules = true;
overrideMain = old: {
preConfigure = ''
cargo_build_options="$cargo_build_options --example ${example} ${
if features == "" then "" else "--features " + features
}"
'';
};
};
makePackage =
package:
let
package-param = [
"--package"
package
];
in
naersk'.buildPackage {
pname = package;
cargoBuildOptions = x: x ++ package-param;
cargoTestOptions = x: x ++ package-param;
src = ./.;
doCheck = true;
strictDeps = true;
inherit nativeBuildInputs buildInputs;
};
in
rec {
servicepoint = makePackage "servicepoint";
announce = makeExample {
package = "servicepoint";
example = "announce";
};
game-of-life = makeExample {
package = "servicepoint";
example = "game_of_life";
features = "rand";
};
moving-line = makeExample {
package = "servicepoint";
example = "moving_line";
};
random-brightness = makeExample {
package = "servicepoint";
example = "random_brightness";
features = "rand";
};
wiping-clear = makeExample {
package = "servicepoint";
example = "wiping_clear";
};
}
);
legacyPackages = packages;
{
devShells = forAllSystems (
{ pkgs, system }:
{
default = pkgs.mkShell rec {
inputsFrom = [ self.packages.${system}.servicepoint ];
packages = with pkgs; [
(pkgs.symlinkJoin
{
@ -139,12 +47,13 @@
cargo-tarpaulin
];
})
ruby
dotnet-sdk_8
gcc
gnumake
xe
xz
pkg-config
];
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (builtins.concatMap (d: d.buildInputs) inputsFrom)}";
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
};
}

15
generate-binding.sh Executable file
View file

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e
SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
cd "$SCRIPT_PATH"
cargo build --release
TARGET_PATH="$(realpath "$SCRIPT_PATH"/target/release)"
SERVICEPOINT_SO="$TARGET_PATH/libservicepoint_binding_uniffi.so"
echo "Source: $SERVICEPOINT_SO"
echo "Output: $LIBRARIES_PATH"
"$TARGET_PATH/uniffi-bindgen-cs" --library "$SERVICEPOINT_SO" --out-dir "$SCRIPT_PATH/ServicePoint"

@ -0,0 +1 @@
Subproject commit c6d2ee6fe1edcbc630ce654e93cf94f510b19f7a

View file

@ -0,0 +1,13 @@
[package]
name = "uniffi-bindgen-cs"
version = "0.13.1"
publish = false
edition = "2021"
license = "GPL-3.0-or-later"
#readme = "README.md"
keywords = ["cccb", "cccb-servicepoint", "uniffi"]
[dependencies.uniffi-bindgen-cs]
git = "https://github.com/NordSecurity/uniffi-bindgen-cs"
# tag="v0.8.3+v0.25.0"
rev = "f68639fbc720b50ebe561ba75c66c84dc456bdce"