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