split ruby gem into separate repository
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Rust / build (push) Successful in 5m1s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Rust / build (push) Successful in 5m1s
				
			This commit is contained in:
		
							parent
							
								
									2f7a2dfd62
								
							
						
					
					
						commit
						31dac283ef
					
				
					 103 changed files with 114 additions and 14573 deletions
				
			
		|  | @ -1 +0,0 @@ | |||
| use flake | ||||
							
								
								
									
										25
									
								
								.github/workflows/rust.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/rust.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -19,27 +19,22 @@ jobs: | |||
| 
 | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|         with: | ||||
|           submodules: 'recursive' | ||||
| 
 | ||||
|       - name: Update repos | ||||
|         run: sudo apt-get update -qq | ||||
|       - name: Install rust toolchain | ||||
|         run: sudo apt-get install -qy cargo rust-clippy | ||||
|       - name: install lzma | ||||
|         run: sudo apt-get update && sudo apt-get install -y liblzma-dev | ||||
|         run: sudo apt-get install -qy liblzma-dev | ||||
|       - name: install ruby | ||||
|         run: sudo apt-get install -qy ruby | ||||
| 
 | ||||
|       - name: generate bindings | ||||
|         run: ./generate-binding.sh | ||||
|       - name: check that generated files did not change | ||||
|         run: output=$(git status --porcelain) && [ -z "$output" ] | ||||
| 
 | ||||
|       - name: Run Clippy | ||||
|         run: cargo clippy --all-targets --all-features | ||||
|          | ||||
|       - name: build default features | ||||
|         run: cargo build --all --verbose | ||||
|       - name: build default features -- examples | ||||
|         run: cargo build --examples --verbose | ||||
|       - name: test default features | ||||
|         run: cargo test --all --verbose | ||||
| 
 | ||||
|       - name: build all features | ||||
|         run: cargo build --all-features --verbose | ||||
|       - name: build all features -- examples | ||||
|         run: cargo build --all-features --examples --verbose | ||||
|       - name: test all features | ||||
|         run: cargo test --all --all-features --verbose | ||||
|  |  | |||
							
								
								
									
										3
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| [submodule "servicepoint-binding-uniffi"] | ||||
| 	path = servicepoint-binding-uniffi | ||||
| 	url = https://git.berlin.ccc.de/servicepoint/servicepoint-binding-uniffi.git | ||||
|  | @ -1,31 +0,0 @@ | |||
| # Contributing | ||||
| 
 | ||||
| Contributions are accepted in any form (issues, documentation, feature requests, code, review, ...). | ||||
| 
 | ||||
| All creatures welcome. | ||||
| 
 | ||||
| If you have access, please contribute on the [CCCB Forgejo](https://git.berlin.ccc.de/servicepoint/servicepoint). | ||||
| Contributions on GitHub will be copied over and merged there. | ||||
| 
 | ||||
| ## Pull requests | ||||
| 
 | ||||
| Feel free to create a PR, even if your change is not done yet. | ||||
| 
 | ||||
| Mark your PR as a draft as long as you do not want it to be merged. | ||||
| 
 | ||||
| The main branch is supposed to be a working version, including language bindings, | ||||
| which means sometimes your PR may be merged into a temporary development branch. | ||||
| 
 | ||||
| Unit tests and documentation are required for the core library. | ||||
| 
 | ||||
| ## Language bindings | ||||
| 
 | ||||
| Pull requests for your preferred language will be accepted. | ||||
| If there is no code generator, it should call the C ABI methods provided by `servicepoint_binding_c`. | ||||
| It should be able to send most of the basic commands in a way the simulator accepts, receiving is | ||||
| not required for the merge. | ||||
| 
 | ||||
| It is okay for the feature set of a language binding to lag behind the one of the rust crate. | ||||
| This also means you do not have to expose a feature to all the language bindings when adding something to the core. | ||||
| 
 | ||||
| If your change may break other language bindings, please note that in your PR description so someone can check them. | ||||
							
								
								
									
										868
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										868
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										14
									
								
								Cargo.toml
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								Cargo.toml
									
										
									
									
									
								
							|  | @ -1,17 +1,7 @@ | |||
| [workspace] | ||||
| resolver = "2" | ||||
| members = [ | ||||
|     "crates/servicepoint", | ||||
|     "crates/servicepoint_binding_c", | ||||
|     "crates/servicepoint_binding_c/examples/lang_c", | ||||
|     "crates/servicepoint_binding_uniffi" | ||||
|     "uniffi-bindgen", | ||||
|     "servicepoint-binding-uniffi" | ||||
| ] | ||||
| 
 | ||||
| [workspace.package] | ||||
| version = "0.13.1" | ||||
| 
 | ||||
| [workspace.lints.rust] | ||||
| missing-docs = "warn" | ||||
| 
 | ||||
| [workspace.dependencies] | ||||
| thiserror = "2.0" | ||||
|  |  | |||
							
								
								
									
										57
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										57
									
								
								README.md
									
										
									
									
									
								
							|  | @ -1,49 +1,34 @@ | |||
| # servicepoint | ||||
| 
 | ||||
| [](https://crates.io/crates/servicepoint) | ||||
| [](https://crates.io/crates/servicepoint) | ||||
| [](https://docs.rs/servicepoint/latest/servicepoint/) | ||||
| [](./LICENSE) | ||||
| # ServicePoint | ||||
| 
 | ||||
| In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called  "Service Point | ||||
| Display" or "Airport Display". | ||||
| This repository contains a library for parsing, encoding and sending packets to this display via UDP in multiple | ||||
| programming languages. | ||||
| 
 | ||||
| This project moved to [git.berlin.ccc.de/servicepoint/servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint).  | ||||
| The [GitHub repository](https://github.com/cccb/servicepoint) remains available as a mirror. | ||||
| This crate contains C# bindings for the [servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint) library based on [servicepoint-binding-uniffi](https://git.berlin.ccc.de/servicepoint/servicepoint-binding-uniffi). | ||||
| 
 | ||||
| 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. | ||||
| ## Installation | ||||
| 
 | ||||
| If you have access, there is even more software linked in [the wiki](https://wiki.berlin.ccc.de/LED-Riesendisplay). | ||||
| Including this repository as a submodule and building from source is the recommended way of using the library. | ||||
| 
 | ||||
| ## Contributing | ||||
| ```bash | ||||
| git submodule add https://git.berlin.ccc.de/servicepoint/servicepoint.git | ||||
| git commit -m "add servicepoint submodule" | ||||
| ``` | ||||
| 
 | ||||
| See [CONTRIBUTING.md](CONTRIBUTING.md). | ||||
| 
 | ||||
| ## What happened to servicepoint2? | ||||
| 
 | ||||
| After `servicepoint2` has been merged into `servicepoint`, `servicepoint2` will not continue to get any updates. | ||||
| Run `generate-binding.sh` to regenerate all bindings. This will also build `libservicepoint.so` (or equivalent on your | ||||
| platform). | ||||
|  |  | |||
|  | @ -1,41 +0,0 @@ | |||
| # About the display | ||||
| 
 | ||||
| - Resolution: 352x160=56,320 pixels | ||||
| - Pixels are grouped into 44x20=880 tiles (8x8=64 pixels each) | ||||
| - Smallest addressable unit: row of pixels inside of a tile (8 pixels = 1 byte) | ||||
| - The brightness can only be set per tile | ||||
| - Screen content can be changed using a simple UDP protocol | ||||
| - Between each row of tiles, there is a gap of around 4 pixels size. This gap changes the aspect ratio of the display. | ||||
| 
 | ||||
| ### Binary format | ||||
| 
 | ||||
| A UDP package sent to the display has a header size of 10 bytes. | ||||
| Each header value has a size of two bytes (unsigned 16 bit integer). | ||||
| Depending on the command, there can be a payload following the header. | ||||
| 
 | ||||
| To change screen contents, these commands are the most relevant: | ||||
| 
 | ||||
| 1. Clear screen | ||||
|     - command: `0x0002` | ||||
|     - (rest does not matter) | ||||
| 2. Send CP437 data: render specified text into rectangular region | ||||
|     - command: `0x0003` | ||||
|     - top left tile x | ||||
|     - top left tile y | ||||
|     - width in tiles | ||||
|     - height in tiles | ||||
|     - payload: (width in tiles * height in tiles) bytes | ||||
|         - 1 byte = 1 character | ||||
|         - each character is rendered into one tile (mono-spaced) | ||||
|         - characters are encoded using code page 437 | ||||
| 3. Send bitmap window: set pixel states for a rectangular region | ||||
|     - command: `0x0013` | ||||
|     - top left tile x | ||||
|     - top left _pixel_ y | ||||
|     - width in tiles | ||||
|     - height in _pixels_ | ||||
|     - payload: (width in tiles * height in pixels) bytes | ||||
|         - network byte order | ||||
|         - 1 bit = 1 pixel | ||||
| 
 | ||||
| There are other commands implemented as well, e.g. for changing the brightness. | ||||
|  | @ -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. | ||||
|  | @ -1,24 +0,0 @@ | |||
| #!/usr/bin/env bash | ||||
| set -e | ||||
| 
 | ||||
| cargo build --release | ||||
| 
 | ||||
| SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" | ||||
| TARGET_PATH="$(realpath "$SCRIPT_PATH"/../../target/release)" | ||||
| SERVICEPOINT_SO="$TARGET_PATH/libservicepoint_binding_uniffi.so" | ||||
| LIBRARIES_PATH="$SCRIPT_PATH/libraries" | ||||
| 
 | ||||
| echo "Source: $SERVICEPOINT_SO" | ||||
| echo "Output: $LIBRARIES_PATH" | ||||
| 
 | ||||
| BINDGEN="cargo run --features=uniffi/cli --bin uniffi-bindgen -- " | ||||
| BINDGEN_CS="cargo run --features=cs --bin uniffi-bindgen-cs -- " | ||||
| BINDGEN_GO="cargo run --features=go --bin uniffi-bindgen-go -- " | ||||
| COMMON_ARGS="--library $SERVICEPOINT_SO" | ||||
| 
 | ||||
| ${BINDGEN} generate $COMMON_ARGS --language python --out-dir "$LIBRARIES_PATH/python" | ||||
| ${BINDGEN} generate $COMMON_ARGS --language kotlin --out-dir "$LIBRARIES_PATH/kotlin" | ||||
| ${BINDGEN} generate $COMMON_ARGS --language swift --out-dir "$LIBRARIES_PATH/swift" | ||||
| ${BINDGEN} generate $COMMON_ARGS --language ruby --out-dir "$LIBRARIES_PATH/ruby/lib" | ||||
| ${BINDGEN_CS} $COMMON_ARGS --out-dir "$LIBRARIES_PATH/csharp/ServicePoint" | ||||
| ${BINDGEN_GO} $COMMON_ARGS --out-dir "$LIBRARIES_PATH/go/" | ||||
|  | @ -1,4 +0,0 @@ | |||
| go | ||||
| kotlin | ||||
| python | ||||
| swift | ||||
|  | @ -1,2 +0,0 @@ | |||
| bin | ||||
| obj | ||||
|  | @ -1,2 +0,0 @@ | |||
| bin | ||||
| obj | ||||
|  | @ -1,19 +0,0 @@ | |||
| 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); | ||||
| } | ||||
|  | @ -1,15 +0,0 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
|     <PropertyGroup> | ||||
|         <OutputType>Exe</OutputType> | ||||
|         <TargetFramework>net8.0</TargetFramework> | ||||
|         <RootNamespace>ServicePoint.Example</RootNamespace> | ||||
|         <ImplicitUsings>disable</ImplicitUsings> | ||||
|         <Nullable>enable</Nullable> | ||||
|     </PropertyGroup> | ||||
| 
 | ||||
|     <ItemGroup> | ||||
|         <ProjectReference Include="../ServicePoint/ServicePoint.csproj"/> | ||||
|     </ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
|  | @ -1,16 +0,0 @@ | |||
| namespace ServicePoint.Tests; | ||||
| 
 | ||||
| public class BitmapTests | ||||
| { | ||||
|     [Fact] | ||||
|     public void BasicFunctions() | ||||
|     { | ||||
|         var bitmap = new Bitmap(8, 2); | ||||
|         Assert.False(bitmap.Get(0, 0)); | ||||
|         Assert.False(bitmap.Get(bitmap.Width() - 1, bitmap.Height() - 1)); | ||||
|         bitmap.Fill(true); | ||||
|         Assert.True(bitmap.Get(1, 1)); | ||||
|         bitmap.Set(1, 1, false); | ||||
|         Assert.False(bitmap.Get(1, 1)); | ||||
|     } | ||||
| } | ||||
|  | @ -1,31 +0,0 @@ | |||
| namespace ServicePoint.Tests; | ||||
| 
 | ||||
| public class CharGridTests | ||||
| { | ||||
|     [Fact] | ||||
|     public void BasicFunctions() | ||||
|     { | ||||
|         var grid = new CharGrid(8, 2); | ||||
|         Assert.Equal("\0", grid.Get(0, 0)); | ||||
|         Assert.Equal("\0", grid.Get(grid.Width() - 1, grid.Height() - 1)); | ||||
|         grid.Fill(" "); | ||||
|         Assert.Equal(" ", grid.Get(1, 1)); | ||||
|         grid.Set(1, 1, "-"); | ||||
|         Assert.Equal("-", grid.Get(1, 1)); | ||||
|         Assert.Throws<PanicException>(() => grid.Get(8, 2)); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void RowAndCol() | ||||
|     { | ||||
|         var grid = new CharGrid(3, 2); | ||||
|         Assert.Equal("\0\0\0", grid.GetRow(0)); | ||||
|         grid.Fill(" "); | ||||
|         Assert.Equal("  ", grid.GetCol(1)); | ||||
|         Assert.Throws<CharGridException.OutOfBounds>(() => grid.GetCol(3)); | ||||
|         Assert.Throws<CharGridException.InvalidSeriesLength>(() => grid.SetRow(1, "Text")); | ||||
|         grid.SetRow(1, "Foo"); | ||||
|         Assert.Equal("Foo", grid.GetRow(1)); | ||||
|         Assert.Equal(" o", grid.GetCol(2)); | ||||
|     } | ||||
| } | ||||
|  | @ -1,42 +0,0 @@ | |||
| namespace ServicePoint.Tests; | ||||
| 
 | ||||
| public class CommandTests | ||||
| { | ||||
|     private Connection _connection = Connection.NewFake(); | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void ClearSendable() | ||||
|     { | ||||
|         _connection.Send(Command.Clear()); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void BrightnessSendable() | ||||
|     { | ||||
|         _connection.Send(Command.Brightness(5)); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void InvalidBrightnessThrows() | ||||
|     { | ||||
|         Assert.Throws<ServicePointException.InvalidBrightness>(() => Command.Brightness(42)); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void FadeOutSendable() | ||||
|     { | ||||
|         _connection.Send(Command.FadeOut()); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void HardResetSendable() | ||||
|     { | ||||
|         _connection.Send(Command.HardReset()); | ||||
|     } | ||||
| 
 | ||||
|     [Fact] | ||||
|     public void BitmapLinearWinSendable() | ||||
|     { | ||||
|         _connection.Send(Command.BitmapLinearWin(0, 0, Bitmap.NewMaxSized(), CompressionCode.Uncompressed)); | ||||
|     } | ||||
| } | ||||
|  | @ -1,11 +0,0 @@ | |||
| namespace ServicePoint.Tests; | ||||
| 
 | ||||
| public class ConnectionTests | ||||
| { | ||||
|     [Fact] | ||||
|     public void InvalidHostnameThrows() | ||||
|     { | ||||
|         Assert.Throws<ServicePointException.IoException>(() => new Connection("")); | ||||
|         Assert.Throws<ServicePointException.IoException>(() => new Connection("-%6$§")); | ||||
|     } | ||||
| } | ||||
|  | @ -1,2 +0,0 @@ | |||
| global using Xunit; | ||||
| global using ServicePoint; | ||||
|  | @ -1,27 +0,0 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0</TargetFramework> | ||||
|     <ImplicitUsings>disable</ImplicitUsings> | ||||
|     <Nullable>enable</Nullable> | ||||
| 
 | ||||
|     <IsPackable>false</IsPackable> | ||||
|     <IsTestProject>true</IsTestProject> | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="../ServicePoint/ServicePoint.csproj"/> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="coverlet.collector" Version="6.0.0" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> | ||||
|     <PackageReference Include="xunit" Version="2.5.3" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <Using Include="Xunit" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
|  | @ -1,14 +0,0 @@ | |||
| 
 | ||||
| using ServicePoint; | ||||
| 
 | ||||
| public static class ServicePointConstants | ||||
| { | ||||
|     private static readonly Constants _instance = ServicepointBindingUniffiMethods.GetConstants(); | ||||
| 
 | ||||
|     public static readonly ulong PixelWidth = _instance.pixelWidth; | ||||
|     public static readonly ulong PixelHeight = _instance.pixelHeight; | ||||
|     public static readonly ulong PixelCount = _instance.pixelCount; | ||||
|     public static readonly ulong TileWidth = _instance.tileWidth; | ||||
|     public static readonly ulong TileHeight = _instance.tileHeight; | ||||
|     public static readonly ulong TileSize = _instance.tileSize; | ||||
| } | ||||
|  | @ -1,53 +0,0 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
|     <PropertyGroup> | ||||
|         <TargetFramework>net8.0</TargetFramework> | ||||
|         <ImplicitUsings>disable</ImplicitUsings> | ||||
|         <Nullable>enable</Nullable> | ||||
|         <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||
|     </PropertyGroup> | ||||
| 
 | ||||
|     <PropertyGroup> | ||||
|         <PackageId>ServicePoint</PackageId> | ||||
|         <Version>0.13.1</Version> | ||||
|         <Authors>Repository Authors</Authors> | ||||
|         <Company>None</Company> | ||||
|         <Product>ServicePoint</Product> | ||||
|         <PackageTags>CCCB</PackageTags> | ||||
|         <Description> | ||||
|             C# bindings for the rust crate servicepoint. You will need a suitable native shared library to use this. | ||||
|             For documentation, see the rust documentation: https://docs.rs/servicepoint/latest/servicepoint/. | ||||
|             Note that this library is still in early development. Breaking changes are expected before 1.0 is released. | ||||
|         </Description> | ||||
|         <PackageReadmeFile>README.md</PackageReadmeFile> | ||||
|         <PublishRepositoryUrl>true</PublishRepositoryUrl> | ||||
|     </PropertyGroup> | ||||
| 
 | ||||
|     <!-- generate C# bindings --> | ||||
|     <Target Name="BuildBindings" Condition="'$(Configuration)'=='Release'" BeforeTargets="PrepareForBuild"> | ||||
|         <Exec Command="cargo build -p servicepoint_binding_uniffi --release"/> | ||||
|     </Target> | ||||
|     <Target Name="BuildBindings" Condition="'$(Configuration)'=='Debug'" BeforeTargets="PrepareForBuild"> | ||||
|         <Exec Command="cargo build -p servicepoint_binding_uniffi"/> | ||||
|     </Target> | ||||
| 
 | ||||
|     <!-- include native binary in output --> | ||||
|     <ItemGroup Condition="'$(Configuration)'=='Debug'"> | ||||
|         <Content Include="../../../../../target/debug/libservicepoint_binding_uniffi.so" CopyToOutputDirectory="Always"> | ||||
|             <Link>libservicepoint_binding_uniffi.so</Link> | ||||
|         </Content> | ||||
|     </ItemGroup> | ||||
|     <ItemGroup Condition="'$(Configuration)'=='Release'"> | ||||
|         <Content Include="../../../../../target/release/libservicepoint_binding_uniffi.so" CopyToOutputDirectory="Always"> | ||||
|             <Link>libservicepoint_binding_uniffi.so</Link> | ||||
|         </Content> | ||||
|     </ItemGroup> | ||||
| 
 | ||||
|     <ItemGroup> | ||||
|         <!-- add README.md to package --> | ||||
|         <None Include="../README.md" Pack="true" PackagePath="\"/> | ||||
|         <!-- include link to source code at revision --> | ||||
|         <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/> | ||||
|     </ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,34 +0,0 @@ | |||
|  | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio Version 17 | ||||
| VisualStudioVersion = 17.0.31903.59 | ||||
| MinimumVisualStudioVersion = 10.0.40219.1 | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePoint", "ServicePoint\ServicePoint.csproj", "{53576D3C-E32E-49BF-BF10-2DB504E50CE1}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePoint.Example", "ServicePoint.Example\ServicePoint.Example.csproj", "{FEF24227-090E-46C2-B8F6-ACB5AA1A4309}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePoint.Tests", "ServicePoint.Tests\ServicePoint.Tests.csproj", "{9DC15508-A980-4135-9FC6-659FF54B4E5C}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| 		Release|Any CPU = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| 		{53576D3C-E32E-49BF-BF10-2DB504E50CE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{53576D3C-E32E-49BF-BF10-2DB504E50CE1}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{53576D3C-E32E-49BF-BF10-2DB504E50CE1}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{53576D3C-E32E-49BF-BF10-2DB504E50CE1}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{FEF24227-090E-46C2-B8F6-ACB5AA1A4309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{FEF24227-090E-46C2-B8F6-ACB5AA1A4309}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{FEF24227-090E-46C2-B8F6-ACB5AA1A4309}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{FEF24227-090E-46C2-B8F6-ACB5AA1A4309}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{9DC15508-A980-4135-9FC6-659FF54B4E5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{9DC15508-A980-4135-9FC6-659FF54B4E5C}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{9DC15508-A980-4135-9FC6-659FF54B4E5C}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{9DC15508-A980-4135-9FC6-659FF54B4E5C}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
|  | @ -1,3 +0,0 @@ | |||
| #!/usr/bin/env bash | ||||
| 
 | ||||
| LD_LIBRARY_PATH="../../../../../target/release:$LD_LIBRARY_PATH" ruby example.rb | ||||
|  | @ -1,3 +0,0 @@ | |||
| fn main() { | ||||
|     uniffi_bindgen_cs::main().unwrap(); | ||||
| } | ||||
|  | @ -1,3 +0,0 @@ | |||
| fn main() { | ||||
|     uniffi_bindgen_go::main().unwrap(); | ||||
| } | ||||
|  | @ -1,77 +0,0 @@ | |||
| use servicepoint::{DataRef, Grid}; | ||||
| use std::sync::{Arc, RwLock}; | ||||
| 
 | ||||
| #[derive(uniffi::Object)] | ||||
| pub struct Bitmap { | ||||
|     pub(crate) actual: RwLock<servicepoint::Bitmap>, | ||||
| } | ||||
| 
 | ||||
| impl Bitmap { | ||||
|     fn internal_new(actual: servicepoint::Bitmap) -> Arc<Self> { | ||||
|         Arc::new(Self { | ||||
|             actual: RwLock::new(actual), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[uniffi::export] | ||||
| impl Bitmap { | ||||
|     #[uniffi::constructor] | ||||
|     pub fn new(width: u64, height: u64) -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::Bitmap::new( | ||||
|             width as usize, | ||||
|             height as usize, | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn new_max_sized() -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::Bitmap::max_sized()) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn load(width: u64, height: u64, data: Vec<u8>) -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::Bitmap::load( | ||||
|             width as usize, | ||||
|             height as usize, | ||||
|             &data, | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn clone(other: &Arc<Self>) -> Arc<Self> { | ||||
|         Self::internal_new(other.actual.read().unwrap().clone()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn set(&self, x: u64, y: u64, value: bool) { | ||||
|         self.actual | ||||
|             .write() | ||||
|             .unwrap() | ||||
|             .set(x as usize, y as usize, value) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get(&self, x: u64, y: u64) -> bool { | ||||
|         self.actual.read().unwrap().get(x as usize, y as usize) | ||||
|     } | ||||
| 
 | ||||
|     pub fn fill(&self, value: bool) { | ||||
|         self.actual.write().unwrap().fill(value) | ||||
|     } | ||||
|     pub fn width(&self) -> u64 { | ||||
|         self.actual.read().unwrap().width() as u64 | ||||
|     } | ||||
| 
 | ||||
|     pub fn height(&self) -> u64 { | ||||
|         self.actual.read().unwrap().height() as u64 | ||||
|     } | ||||
| 
 | ||||
|     pub fn equals(&self, other: &Bitmap) -> bool { | ||||
|         let a = self.actual.read().unwrap(); | ||||
|         let b = other.actual.read().unwrap(); | ||||
|         *a == *b | ||||
|     } | ||||
| 
 | ||||
|     pub fn copy_raw(&self) -> Vec<u8> { | ||||
|         self.actual.read().unwrap().data_ref().to_vec() | ||||
|     } | ||||
| } | ||||
|  | @ -1,61 +0,0 @@ | |||
| use std::sync::{Arc, RwLock}; | ||||
| 
 | ||||
| #[derive(uniffi::Object)] | ||||
| pub struct BitVec { | ||||
|     pub(crate) actual: RwLock<servicepoint::BitVec>, | ||||
| } | ||||
| 
 | ||||
| impl BitVec { | ||||
|     fn internal_new(actual: servicepoint::BitVec) -> Arc<Self> { | ||||
|         Arc::new(Self { | ||||
|             actual: RwLock::new(actual), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[uniffi::export] | ||||
| impl BitVec { | ||||
|     #[uniffi::constructor] | ||||
|     pub fn new(size: u64) -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::BitVec::repeat(false, size as usize)) | ||||
|     } | ||||
|     #[uniffi::constructor] | ||||
|     pub fn load(data: Vec<u8>) -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::BitVec::from_slice(&data)) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn clone(other: &Arc<Self>) -> Arc<Self> { | ||||
|         Self::internal_new(other.actual.read().unwrap().clone()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn set(&self, index: u64, value: bool) { | ||||
|         self.actual.write().unwrap().set(index as usize, value) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get(&self, index: u64) -> bool { | ||||
|         self.actual | ||||
|             .read() | ||||
|             .unwrap() | ||||
|             .get(index as usize) | ||||
|             .is_some_and(move |bit| *bit) | ||||
|     } | ||||
| 
 | ||||
|     pub fn fill(&self, value: bool) { | ||||
|         self.actual.write().unwrap().fill(value) | ||||
|     } | ||||
| 
 | ||||
|     pub fn len(&self) -> u64 { | ||||
|         self.actual.read().unwrap().len() as u64 | ||||
|     } | ||||
| 
 | ||||
|     pub fn equals(&self, other: &BitVec) -> bool { | ||||
|         let a = self.actual.read().unwrap(); | ||||
|         let b = other.actual.read().unwrap(); | ||||
|         *a == *b | ||||
|     } | ||||
| 
 | ||||
|     pub fn copy_raw(&self) -> Vec<u8> { | ||||
|         self.actual.read().unwrap().clone().into_vec() | ||||
|     } | ||||
| } | ||||
|  | @ -1,86 +0,0 @@ | |||
| use servicepoint::{Brightness, DataRef, Grid}; | ||||
| use std::sync::{Arc, RwLock}; | ||||
| 
 | ||||
| #[derive(uniffi::Object)] | ||||
| pub struct BrightnessGrid { | ||||
|     pub(crate) actual: RwLock<servicepoint::BrightnessGrid>, | ||||
| } | ||||
| 
 | ||||
| impl BrightnessGrid { | ||||
|     fn internal_new(actual: servicepoint::BrightnessGrid) -> Arc<Self> { | ||||
|         Arc::new(Self { | ||||
|             actual: RwLock::new(actual), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[uniffi::export] | ||||
| impl BrightnessGrid { | ||||
|     #[uniffi::constructor] | ||||
|     pub fn new(width: u64, height: u64) -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::BrightnessGrid::new( | ||||
|             width as usize, | ||||
|             height as usize, | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn load(width: u64, height: u64, data: Vec<u8>) -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::BrightnessGrid::saturating_load( | ||||
|             width as usize, | ||||
|             height as usize, | ||||
|             &data, | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn clone(other: &Arc<Self>) -> Arc<Self> { | ||||
|         Self::internal_new(other.actual.read().unwrap().clone()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn set(&self, x: u64, y: u64, value: u8) { | ||||
|         self.actual.write().unwrap().set( | ||||
|             x as usize, | ||||
|             y as usize, | ||||
|             Brightness::saturating_from(value), | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get(&self, x: u64, y: u64) -> u8 { | ||||
|         self.actual | ||||
|             .read() | ||||
|             .unwrap() | ||||
|             .get(x as usize, y as usize) | ||||
|             .into() | ||||
|     } | ||||
| 
 | ||||
|     pub fn fill(&self, value: u8) { | ||||
|         self.actual | ||||
|             .write() | ||||
|             .unwrap() | ||||
|             .fill(Brightness::saturating_from(value)) | ||||
|     } | ||||
|     pub fn width(&self) -> u64 { | ||||
|         self.actual.read().unwrap().width() as u64 | ||||
|     } | ||||
| 
 | ||||
|     pub fn height(&self) -> u64 { | ||||
|         self.actual.read().unwrap().height() as u64 | ||||
|     } | ||||
| 
 | ||||
|     pub fn equals(&self, other: &BrightnessGrid) -> bool { | ||||
|         let a = self.actual.read().unwrap(); | ||||
|         let b = other.actual.read().unwrap(); | ||||
|         *a == *b | ||||
|     } | ||||
| 
 | ||||
|     pub fn copy_raw(&self) -> Vec<u8> { | ||||
|         self.actual | ||||
|             .read() | ||||
|             .unwrap() | ||||
|             .data_ref() | ||||
|             .iter() | ||||
|             .map(u8::from) | ||||
|             .collect() | ||||
|     } | ||||
| } | ||||
|  | @ -1,169 +0,0 @@ | |||
| use crate::cp437_grid::Cp437Grid; | ||||
| use servicepoint::{Grid, SetValueSeriesError}; | ||||
| use std::convert::Into; | ||||
| use std::sync::{Arc, RwLock}; | ||||
| 
 | ||||
| #[derive(uniffi::Object)] | ||||
| pub struct CharGrid { | ||||
|     pub(crate) actual: RwLock<servicepoint::CharGrid>, | ||||
| } | ||||
| 
 | ||||
| #[derive(uniffi::Error, thiserror::Error, Debug)] | ||||
| pub enum CharGridError { | ||||
|     #[error("Exactly one character was expected, but {value:?} was provided")] | ||||
|     StringNotOneChar { value: String }, | ||||
|     #[error("The provided series was expected to have a length of {expected}, but was {actual}")] | ||||
|     InvalidSeriesLength { actual: u64, expected: u64 }, | ||||
|     #[error("The index {index} was out of bounds for size {size}")] | ||||
|     OutOfBounds { index: u64, size: u64 }, | ||||
| } | ||||
| 
 | ||||
| #[uniffi::export] | ||||
| impl CharGrid { | ||||
|     #[uniffi::constructor] | ||||
|     pub fn new(width: u64, height: u64) -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::CharGrid::new( | ||||
|             width as usize, | ||||
|             height as usize, | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn load(data: String) -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::CharGrid::from(&*data)) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn clone(other: &Arc<Self>) -> Arc<Self> { | ||||
|         Self::internal_new(other.actual.read().unwrap().clone()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn set( | ||||
|         &self, | ||||
|         x: u64, | ||||
|         y: u64, | ||||
|         value: String, | ||||
|     ) -> Result<(), CharGridError> { | ||||
|         let value = Self::str_to_char(value)?; | ||||
|         self.actual | ||||
|             .write() | ||||
|             .unwrap() | ||||
|             .set(x as usize, y as usize, value); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get(&self, x: u64, y: u64) -> String { | ||||
|         self.actual | ||||
|             .read() | ||||
|             .unwrap() | ||||
|             .get(x as usize, y as usize) | ||||
|             .into() | ||||
|     } | ||||
| 
 | ||||
|     pub fn fill(&self, value: String) -> Result<(), CharGridError> { | ||||
|         let value = Self::str_to_char(value)?; | ||||
|         self.actual.write().unwrap().fill(value); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn width(&self) -> u64 { | ||||
|         self.actual.read().unwrap().width() as u64 | ||||
|     } | ||||
| 
 | ||||
|     pub fn height(&self) -> u64 { | ||||
|         self.actual.read().unwrap().height() as u64 | ||||
|     } | ||||
| 
 | ||||
|     pub fn equals(&self, other: &CharGrid) -> bool { | ||||
|         let a = self.actual.read().unwrap(); | ||||
|         let b = other.actual.read().unwrap(); | ||||
|         *a == *b | ||||
|     } | ||||
| 
 | ||||
|     pub fn as_string(&self) -> String { | ||||
|         let grid = self.actual.read().unwrap(); | ||||
|         String::from(&*grid) | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_row(&self, y: u64, row: String) -> Result<(), CharGridError> { | ||||
|         self.actual | ||||
|             .write() | ||||
|             .unwrap() | ||||
|             .set_row(y as usize, &row.chars().collect::<Vec<_>>()) | ||||
|             .map_err(CharGridError::from) | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_col(&self, x: u64, col: String) -> Result<(), CharGridError> { | ||||
|         self.actual | ||||
|             .write() | ||||
|             .unwrap() | ||||
|             .set_row(x as usize, &col.chars().collect::<Vec<_>>()) | ||||
|             .map_err(CharGridError::from) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_row(&self, y: u64) -> Result<String, CharGridError> { | ||||
|         self.actual | ||||
|             .read() | ||||
|             .unwrap() | ||||
|             .get_row(y as usize) | ||||
|             .map(String::from_iter) | ||||
|             .ok_or(CharGridError::OutOfBounds { | ||||
|                 index: y, | ||||
|                 size: self.height(), | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_col(&self, x: u64) -> Result<String, CharGridError> { | ||||
|         self.actual | ||||
|             .read() | ||||
|             .unwrap() | ||||
|             .get_col(x as usize) | ||||
|             .map(String::from_iter) | ||||
|             .ok_or(CharGridError::OutOfBounds { | ||||
|                 index: x, | ||||
|                 size: self.width(), | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_cp437(&self) -> Arc<Cp437Grid> { | ||||
|         Cp437Grid::internal_new(servicepoint::Cp437Grid::from( | ||||
|             &*self.actual.read().unwrap(), | ||||
|         )) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl CharGrid { | ||||
|     pub(crate) fn internal_new(actual: servicepoint::CharGrid) -> Arc<Self> { | ||||
|         Arc::new(Self { | ||||
|             actual: RwLock::new(actual), | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     fn str_to_char(value: String) -> Result<char, CharGridError> { | ||||
|         if value.len() != 1 { | ||||
|             return Err(CharGridError::StringNotOneChar { value }); | ||||
|         } | ||||
| 
 | ||||
|         let value = value.chars().nth(0).unwrap(); | ||||
|         Ok(value) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<SetValueSeriesError> for CharGridError { | ||||
|     fn from(e: SetValueSeriesError) -> Self { | ||||
|         match e { | ||||
|             SetValueSeriesError::OutOfBounds { index, size } => { | ||||
|                 CharGridError::OutOfBounds { | ||||
|                     index: index as u64, | ||||
|                     size: size as u64, | ||||
|                 } | ||||
|             } | ||||
|             SetValueSeriesError::InvalidLength { actual, expected } => { | ||||
|                 CharGridError::InvalidSeriesLength { | ||||
|                     actual: actual as u64, | ||||
|                     expected: expected as u64, | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,175 +0,0 @@ | |||
| use crate::bitmap::Bitmap; | ||||
| use crate::bitvec::BitVec; | ||||
| use crate::brightness_grid::BrightnessGrid; | ||||
| use crate::char_grid::CharGrid; | ||||
| use crate::compression_code::CompressionCode; | ||||
| use crate::cp437_grid::Cp437Grid; | ||||
| use crate::errors::ServicePointError; | ||||
| use servicepoint::Origin; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| #[derive(uniffi::Object)] | ||||
| pub struct Command { | ||||
|     pub(crate) actual: servicepoint::Command, | ||||
| } | ||||
| 
 | ||||
| impl Command { | ||||
|     fn internal_new(actual: servicepoint::Command) -> Arc<Command> { | ||||
|         Arc::new(Command { actual }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[uniffi::export] | ||||
| impl Command { | ||||
|     #[uniffi::constructor] | ||||
|     pub fn clear() -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::Command::Clear) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn brightness(brightness: u8) -> Result<Arc<Self>, ServicePointError> { | ||||
|         servicepoint::Brightness::try_from(brightness) | ||||
|             .map_err(move |value| ServicePointError::InvalidBrightness { | ||||
|                 value, | ||||
|             }) | ||||
|             .map(servicepoint::Command::Brightness) | ||||
|             .map(Self::internal_new) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn fade_out() -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::Command::FadeOut) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn hard_reset() -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::Command::HardReset) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn bitmap_linear_win( | ||||
|         offset_x: u64, | ||||
|         offset_y: u64, | ||||
|         bitmap: &Arc<Bitmap>, | ||||
|         compression: CompressionCode, | ||||
|     ) -> Arc<Self> { | ||||
|         let origin = Origin::new(offset_x as usize, offset_y as usize); | ||||
|         let bitmap = bitmap.actual.read().unwrap().clone(); | ||||
|         let actual = servicepoint::Command::BitmapLinearWin( | ||||
|             origin, | ||||
|             bitmap, | ||||
|             servicepoint::CompressionCode::try_from(compression as u16) | ||||
|                 .unwrap(), | ||||
|         ); | ||||
|         Self::internal_new(actual) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn char_brightness( | ||||
|         offset_x: u64, | ||||
|         offset_y: u64, | ||||
|         grid: &Arc<BrightnessGrid>, | ||||
|     ) -> Arc<Self> { | ||||
|         let origin = Origin::new(offset_x as usize, offset_y as usize); | ||||
|         let grid = grid.actual.read().unwrap().clone(); | ||||
|         let actual = servicepoint::Command::CharBrightness(origin, grid); | ||||
|         Self::internal_new(actual) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn bitmap_linear( | ||||
|         offset: u64, | ||||
|         bitmap: &Arc<BitVec>, | ||||
|         compression: CompressionCode, | ||||
|     ) -> Arc<Self> { | ||||
|         let bitmap = bitmap.actual.read().unwrap().clone(); | ||||
|         let actual = servicepoint::Command::BitmapLinear( | ||||
|             offset as usize, | ||||
|             bitmap, | ||||
|             servicepoint::CompressionCode::try_from(compression as u16) | ||||
|                 .unwrap(), | ||||
|         ); | ||||
|         Self::internal_new(actual) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn bitmap_linear_and( | ||||
|         offset: u64, | ||||
|         bitmap: &Arc<BitVec>, | ||||
|         compression: CompressionCode, | ||||
|     ) -> Arc<Self> { | ||||
|         let bitmap = bitmap.actual.read().unwrap().clone(); | ||||
|         let actual = servicepoint::Command::BitmapLinearAnd( | ||||
|             offset as usize, | ||||
|             bitmap, | ||||
|             servicepoint::CompressionCode::try_from(compression as u16) | ||||
|                 .unwrap(), | ||||
|         ); | ||||
|         Self::internal_new(actual) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn bitmap_linear_or( | ||||
|         offset: u64, | ||||
|         bitmap: &Arc<BitVec>, | ||||
|         compression: CompressionCode, | ||||
|     ) -> Arc<Self> { | ||||
|         let bitmap = bitmap.actual.read().unwrap().clone(); | ||||
|         let actual = servicepoint::Command::BitmapLinearOr( | ||||
|             offset as usize, | ||||
|             bitmap, | ||||
|             servicepoint::CompressionCode::try_from(compression as u16) | ||||
|                 .unwrap(), | ||||
|         ); | ||||
|         Self::internal_new(actual) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn bitmap_linear_xor( | ||||
|         offset: u64, | ||||
|         bitmap: &Arc<BitVec>, | ||||
|         compression: CompressionCode, | ||||
|     ) -> Arc<Self> { | ||||
|         let bitmap = bitmap.actual.read().unwrap().clone(); | ||||
|         let actual = servicepoint::Command::BitmapLinearXor( | ||||
|             offset as usize, | ||||
|             bitmap, | ||||
|             servicepoint::CompressionCode::try_from(compression as u16) | ||||
|                 .unwrap(), | ||||
|         ); | ||||
|         Self::internal_new(actual) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn cp437_data( | ||||
|         offset_x: u64, | ||||
|         offset_y: u64, | ||||
|         grid: &Arc<Cp437Grid>, | ||||
|     ) -> Arc<Self> { | ||||
|         let origin = Origin::new(offset_x as usize, offset_y as usize); | ||||
|         let grid = grid.actual.read().unwrap().clone(); | ||||
|         let actual = servicepoint::Command::Cp437Data(origin, grid); | ||||
|         Self::internal_new(actual) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn utf8_data( | ||||
|         offset_x: u64, | ||||
|         offset_y: u64, | ||||
|         grid: &Arc<CharGrid>, | ||||
|     ) -> Arc<Self> { | ||||
|         let origin = Origin::new(offset_x as usize, offset_y as usize); | ||||
|         let grid = grid.actual.read().unwrap().clone(); | ||||
|         let actual = servicepoint::Command::Utf8Data(origin, grid); | ||||
|         Self::internal_new(actual) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn clone(other: &Arc<Self>) -> Arc<Self> { | ||||
|         Self::internal_new(other.actual.clone()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn equals(&self, other: &Command) -> bool { | ||||
|         self.actual == other.actual | ||||
|     } | ||||
| } | ||||
|  | @ -1,14 +0,0 @@ | |||
| #[repr(u16)] | ||||
| #[derive(Debug, Clone, Copy, PartialEq, uniffi::Enum)] | ||||
| pub enum CompressionCode { | ||||
|     /// no compression
 | ||||
|     Uncompressed = 0x0, | ||||
|     /// compress using flate2 with zlib header
 | ||||
|     Zlib = 0x677a, | ||||
|     /// compress using bzip2
 | ||||
|     Bzip2 = 0x627a, | ||||
|     /// compress using lzma
 | ||||
|     Lzma = 0x6c7a, | ||||
|     /// compress using Zstandard
 | ||||
|     Zstd = 0x7a73, | ||||
| } | ||||
|  | @ -1,36 +0,0 @@ | |||
| use std::sync::Arc; | ||||
| 
 | ||||
| use crate::command::Command; | ||||
| use crate::errors::ServicePointError; | ||||
| 
 | ||||
| #[derive(uniffi::Object)] | ||||
| pub struct Connection { | ||||
|     actual: servicepoint::Connection, | ||||
| } | ||||
| 
 | ||||
| #[uniffi::export] | ||||
| impl Connection { | ||||
|     #[uniffi::constructor] | ||||
|     pub fn new(host: String) -> Result<Arc<Self>, ServicePointError> { | ||||
|         servicepoint::Connection::open(host) | ||||
|             .map(|actual| Arc::new(Connection { actual })) | ||||
|             .map_err(|err| ServicePointError::IoError { | ||||
|                 error: err.to_string(), | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn new_fake() -> Arc<Self> { | ||||
|         Arc::new(Self { | ||||
|             actual: servicepoint::Connection::Fake, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn send(&self, command: Arc<Command>) -> Result<(), ServicePointError> { | ||||
|         self.actual.send(command.actual.clone()).map_err(|err| { | ||||
|             ServicePointError::IoError { | ||||
|                 error: format!("{err:?}"), | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | @ -1,23 +0,0 @@ | |||
| #[derive(
 | ||||
|     Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, uniffi::Record, | ||||
| )] | ||||
| pub struct Constants { | ||||
|     pub tile_size: u64, | ||||
|     pub tile_width: u64, | ||||
|     pub tile_height: u64, | ||||
|     pub pixel_width: u64, | ||||
|     pub pixel_height: u64, | ||||
|     pub pixel_count: u64, | ||||
| } | ||||
| 
 | ||||
| #[uniffi::export] | ||||
| fn get_constants() -> Constants { | ||||
|     Constants { | ||||
|         tile_size: servicepoint::TILE_SIZE as u64, | ||||
|         tile_width: servicepoint::TILE_WIDTH as u64, | ||||
|         tile_height: servicepoint::TILE_HEIGHT as u64, | ||||
|         pixel_width: servicepoint::PIXEL_WIDTH as u64, | ||||
|         pixel_height: servicepoint::PIXEL_HEIGHT as u64, | ||||
|         pixel_count: servicepoint::PIXEL_COUNT as u64, | ||||
|     } | ||||
| } | ||||
|  | @ -1,79 +0,0 @@ | |||
| use crate::char_grid::CharGrid; | ||||
| use servicepoint::{DataRef, Grid}; | ||||
| use std::sync::{Arc, RwLock}; | ||||
| 
 | ||||
| #[derive(uniffi::Object)] | ||||
| pub struct Cp437Grid { | ||||
|     pub(crate) actual: RwLock<servicepoint::Cp437Grid>, | ||||
| } | ||||
| 
 | ||||
| impl Cp437Grid { | ||||
|     pub(crate) fn internal_new(actual: servicepoint::Cp437Grid) -> Arc<Self> { | ||||
|         Arc::new(Self { | ||||
|             actual: RwLock::new(actual), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[uniffi::export] | ||||
| impl Cp437Grid { | ||||
|     #[uniffi::constructor] | ||||
|     pub fn new(width: u64, height: u64) -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::Cp437Grid::new( | ||||
|             width as usize, | ||||
|             height as usize, | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn load(width: u64, height: u64, data: Vec<u8>) -> Arc<Self> { | ||||
|         Self::internal_new(servicepoint::Cp437Grid::load( | ||||
|             width as usize, | ||||
|             height as usize, | ||||
|             &data, | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     #[uniffi::constructor] | ||||
|     pub fn clone(other: &Arc<Self>) -> Arc<Self> { | ||||
|         Self::internal_new(other.actual.read().unwrap().clone()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn set(&self, x: u64, y: u64, value: u8) { | ||||
|         self.actual | ||||
|             .write() | ||||
|             .unwrap() | ||||
|             .set(x as usize, y as usize, value) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get(&self, x: u64, y: u64) -> u8 { | ||||
|         self.actual.read().unwrap().get(x as usize, y as usize) | ||||
|     } | ||||
| 
 | ||||
|     pub fn fill(&self, value: u8) { | ||||
|         self.actual.write().unwrap().fill(value) | ||||
|     } | ||||
|     pub fn width(&self) -> u64 { | ||||
|         self.actual.read().unwrap().width() as u64 | ||||
|     } | ||||
| 
 | ||||
|     pub fn height(&self) -> u64 { | ||||
|         self.actual.read().unwrap().height() as u64 | ||||
|     } | ||||
| 
 | ||||
|     pub fn equals(&self, other: &Cp437Grid) -> bool { | ||||
|         let a = self.actual.read().unwrap(); | ||||
|         let b = other.actual.read().unwrap(); | ||||
|         *a == *b | ||||
|     } | ||||
| 
 | ||||
|     pub fn copy_raw(&self) -> Vec<u8> { | ||||
|         self.actual.read().unwrap().data_ref().to_vec() | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_utf8(&self) -> Arc<CharGrid> { | ||||
|         CharGrid::internal_new(servicepoint::CharGrid::from( | ||||
|             &*self.actual.read().unwrap(), | ||||
|         )) | ||||
|     } | ||||
| } | ||||
|  | @ -1,7 +0,0 @@ | |||
| #[derive(uniffi::Error, thiserror::Error, Debug)] | ||||
| pub enum ServicePointError { | ||||
|     #[error("An IO error occurred: {error}")] | ||||
|     IoError { error: String }, | ||||
|     #[error("The specified brightness value {value} is out of range")] | ||||
|     InvalidBrightness { value: u8 }, | ||||
| } | ||||
|  | @ -1,12 +0,0 @@ | |||
| uniffi::setup_scaffolding!(); | ||||
| 
 | ||||
| mod bitmap; | ||||
| mod bitvec; | ||||
| mod brightness_grid; | ||||
| mod char_grid; | ||||
| mod command; | ||||
| mod compression_code; | ||||
| mod connection; | ||||
| mod constants; | ||||
| mod cp437_grid; | ||||
| mod errors; | ||||
|  | @ -1,3 +0,0 @@ | |||
| [bindings.csharp] | ||||
| namespace = "ServicePoint" | ||||
| access_modifier = "public" | ||||
							
								
								
									
										6
									
								
								example/example.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										6
									
								
								example/example.sh
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| #!/usr/bin/env bash | ||||
| set -e | ||||
| 
 | ||||
| SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" | ||||
| 
 | ||||
| LD_LIBRARY_PATH="$SCRIPT_PATH/../target/release:$LD_LIBRARY_PATH" ruby "$SCRIPT_PATH/example.rb" | ||||
							
								
								
									
										21
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										21
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							|  | @ -1,25 +1,5 @@ | |||
| { | ||||
|   "nodes": { | ||||
|     "naersk": { | ||||
|       "inputs": { | ||||
|         "nixpkgs": [ | ||||
|           "nixpkgs" | ||||
|         ] | ||||
|       }, | ||||
|       "locked": { | ||||
|         "lastModified": 1736429655, | ||||
|         "narHash": "sha256-BwMekRuVlSB9C0QgwKMICiJ5EVbLGjfe4qyueyNQyGI=", | ||||
|         "owner": "nix-community", | ||||
|         "repo": "naersk", | ||||
|         "rev": "0621e47bd95542b8e1ce2ee2d65d6a1f887a13ce", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|         "owner": "nix-community", | ||||
|         "repo": "naersk", | ||||
|         "type": "github" | ||||
|       } | ||||
|     }, | ||||
|     "nixpkgs": { | ||||
|       "locked": { | ||||
|         "lastModified": 1739357830, | ||||
|  | @ -38,7 +18,6 @@ | |||
|     }, | ||||
|     "root": { | ||||
|       "inputs": { | ||||
|         "naersk": "naersk", | ||||
|         "nixpkgs": "nixpkgs" | ||||
|       } | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										106
									
								
								flake.nix
									
										
									
									
									
								
							
							
						
						
									
										106
									
								
								flake.nix
									
										
									
									
									
								
							|  | @ -3,17 +3,12 @@ | |||
| 
 | ||||
|   inputs = { | ||||
|     nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; | ||||
|     naersk = { | ||||
|       url = "github:nix-community/naersk"; | ||||
|       inputs.nixpkgs.follows = "nixpkgs"; | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   outputs = | ||||
|     inputs@{ | ||||
|       self, | ||||
|       nixpkgs, | ||||
|       naersk, | ||||
|     }: | ||||
|     let | ||||
|       lib = nixpkgs.lib; | ||||
|  | @ -33,101 +28,13 @@ | |||
|           } | ||||
|         ); | ||||
|     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 ]; | ||||
|           default = pkgs.mkShell { | ||||
|             packages = with pkgs; [ | ||||
|               (pkgs.symlinkJoin | ||||
|               { | ||||
|               (pkgs.symlinkJoin { | ||||
|                 name = "rust-toolchain"; | ||||
|                 paths = with pkgs; [ | ||||
|                   rustc | ||||
|  | @ -140,11 +47,10 @@ | |||
|                 ]; | ||||
|               }) | ||||
|               ruby | ||||
|               dotnet-sdk_8 | ||||
|               gcc | ||||
|               gnumake | ||||
|               xe | ||||
|               xz | ||||
|               pkg-config | ||||
|             ]; | ||||
|             LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (builtins.concatMap (d: d.buildInputs) inputsFrom)}"; | ||||
|             RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; | ||||
|           }; | ||||
|         } | ||||
|  |  | |||
							
								
								
									
										14
									
								
								generate-binding.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										14
									
								
								generate-binding.sh
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| #!/usr/bin/env bash | ||||
| set -e | ||||
| 
 | ||||
| SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" | ||||
| 
 | ||||
| cd "$SCRIPT_PATH" | ||||
| cargo build --release | ||||
| 
 | ||||
| TARGET_PATH="$(realpath "$SCRIPT_PATH"/target/release)" | ||||
| SERVICEPOINT_SO="$TARGET_PATH/libservicepoint_binding_uniffi.so" | ||||
| 
 | ||||
| echo "Source: $SERVICEPOINT_SO" | ||||
| 
 | ||||
| "$TARGET_PATH/uniffi-bindgen" generate --library "$SERVICEPOINT_SO" --language ruby --out-dir "$SCRIPT_PATH/lib" | ||||
							
								
								
									
										1
									
								
								servicepoint-binding-uniffi
									
										
									
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								servicepoint-binding-uniffi
									
										
									
									
									
										Submodule
									
								
							|  | @ -0,0 +1 @@ | |||
| Subproject commit 1169d9f1d294268f699e9c4e088cb1d97d6c36ce | ||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter