Compare commits

...

8 commits

Author SHA1 Message Date
vinzenz de9a09e171 Merge pull request 'split language bindings into separate repositories' (#2) from split into main
Some checks failed
Rust / build (push) Has been cancelled
Reviewed-on: #2
2025-02-17 22:42:11 +01:00
Vinzenz Schroeter 6a19c5bc6c update README
All checks were successful
Rust / build (pull_request) Successful in 2m11s
2025-02-17 22:37:48 +01:00
Vinzenz Schroeter a1316b0271 impl Default for CompressionCode
also more feature-agnostic examples
2025-02-17 22:32:45 +01:00
Vinzenz Schroeter c534929089 format code 2025-02-17 21:35:05 +01:00
Vinzenz Schroeter 319ef4572a move all of cp437 feature code into one unit 2025-02-17 21:35:05 +01:00
Vinzenz Schroeter 62a9969cb5 CI check --no-default-features works 2025-02-17 21:35:05 +01:00
Vinzenz Schroeter 0604609fdf update dependencies 2025-02-17 21:13:51 +01:00
Vinzenz Schroeter 04aa4aa95a split language bindings into separate repositories
All checks were successful
Rust / build (pull_request) Successful in 2m22s
2025-02-16 17:36:08 +01:00
95 changed files with 357 additions and 12331 deletions

View file

@ -1 +0,0 @@
use flake

View file

@ -29,17 +29,16 @@ jobs:
- 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
- name: no features -- test (without doctest)
run: cargo test --lib --no-default-features
- name: default features -- test
run: cargo test --all
- name: default features -- examples
run: cargo build --examples
- name: all features -- test
run: cargo test --all --all-features
- name: all features -- examples
run: cargo build --examples --all-features

1143
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,60 @@
[workspace]
resolver = "2"
members = [
"crates/servicepoint",
"crates/servicepoint_binding_c",
"crates/servicepoint_binding_c/examples/lang_c",
"crates/servicepoint_binding_uniffi"
]
[workspace.package]
[package]
name = "servicepoint"
version = "0.13.1"
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"]
[workspace.lints.rust]
[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 = "2.0"
[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.rust]
missing-docs = "warn"
[workspace.dependencies]
thiserror = "2.0"
[package.metadata.docs.rs]
all-features = true

View file

@ -7,19 +7,87 @@
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).
This crate contains a library for parsing, encoding and sending packets to this display via UDP.
The library itself is written in Rust, but can be used from multiple languages
via [language bindings](#supported-language-bindings).
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.
Take a look at the contained crates for language specific information:
## Examples
| 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) |
```rust no_run
// everything you need is in the top-level
use servicepoint::*;
fn main() {
// establish connection
let connection = Connection::open("172.23.42.29:2342")
.expect("connection failed");
// clear screen content
connection.send(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.
## Installation
```bash
cargo add servicepoint
```
or
```toml
[dependencies]
servicepoint = "0.13.1"
```
## 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 breaking changes with every minor version bump.
There should be no breaking changes in patch releases, but there may also be features hiding in those.
All of this means for you: 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 | Dependencies |
|--------------------|---------|----------------------------------------------|-----------------------------------------------------|
| protocol_udp | true | `Connection::Udp` | |
| cp437 | true | Conversion to and from CP-437 | [once_cell](https://crates.io/crates/once_cell) |
| compression_lzma | true | Enable additional compression algo | [rust-lzma](https://crates.io/crates/rust-lzma) |
| compression_zlib | false | Enable additional compression algo | [flate2](https://crates.io/crates/flate2) |
| compression_bzip2 | false | Enable additional compression algo | [bzip2](https://crates.io/crates/bzip2) |
| compression_zstd | false | Enable additional compression algo | [zstd](https://crates.io/crates/zstd) |
| protocol_websocket | false | `Connection::WebSocket` | [tungstenite](https://crates.io/crates/tungstenite) |
| rand | false | `impl Distribution<Brightness> for Standard` | [rand](https://crates.io/crates/rand) |
## Supported language bindings
| Language | Support level | Repo |
|-----------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
| .NET (C#) | Full | [servicepoint-binding-csharp](https://git.berlin.ccc.de/servicepoint/servicepoint-binding-csharp) contains bindings and a `.csproj` to reference |
| C | Full | [servicepoint-binding-c](https://git.berlin.ccc.de/servicepoint/servicepoint-binding-c) contains a header and a library to link against |
| Ruby | Working | [servicepoint-binding-ruby](https://git.berlin.ccc.de/servicepoint/servicepoint-binding-ruby) contains bindings |
| Python | Unsupported | bindings can be generated from [servicepoint-binding-uniffi](https://git.berlin.ccc.de/servicepoint/servicepoint-binding-uniffi), tested once |
| Go | Unsupported | bindings can be generated from [servicepoint-binding-uniffi](https://git.berlin.ccc.de/servicepoint/servicepoint-binding-uniffi) |
| Kotlin | Unsupported | bindings can be generated from [servicepoint-binding-uniffi](https://git.berlin.ccc.de/servicepoint/servicepoint-binding-uniffi) |
| Swift | Unsupported | bindings can be generated from [servicepoint-binding-uniffi](https://git.berlin.ccc.de/servicepoint/servicepoint-binding-uniffi) |
## Projects using the library
@ -31,12 +99,14 @@ Take a look at the contained crates for language specific information:
- 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)
- partial typescript implementation inspired by this library and browser
stream: [cccb-servicepoint-browser](https://github.com/SamuelScheit/cccb-servicepoint-browser)
- a CLI, can also share your screen: [servicepoint-cli](https://git.berlin.ccc.de/servicepoint/servicepoint-cli)
To add yourself to the list, open a pull request.
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.
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.
If you have access, there is even more software linked in [the wiki](https://wiki.berlin.ccc.de/LED-Riesendisplay).

View file

@ -1,60 +0,0 @@
[package]
name = "servicepoint"
version.workspace = true
publish = true
edition = "2021"
license = "GPL-3.0-or-later"
description = "A rust library for the CCCB Service Point Display."
homepage = "https://docs.rs/crate/servicepoint"
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint"
readme = "README.md"
keywords = ["cccb", "cccb-servicepoint"]
[lib]
crate-type = ["rlib"]
[dependencies]
log = "0.4"
bitvec = "1.0"
flate2 = { version = "1.0", optional = true }
bzip2 = { version = "0.5", optional = true }
zstd = { version = "0.13", optional = true }
rust-lzma = { version = "0.6", optional = true }
rand = { version = "0.8", optional = true }
tungstenite = { version = "0.26", optional = true }
once_cell = { version = "1.20", optional = true }
thiserror.workspace = true
[features]
default = ["compression_lzma", "protocol_udp", "cp437"]
compression_zlib = ["dep:flate2"]
compression_bzip2 = ["dep:bzip2"]
compression_lzma = ["dep:rust-lzma"]
compression_zstd = ["dep:zstd"]
all_compressions = ["compression_zlib", "compression_bzip2", "compression_lzma", "compression_zstd"]
rand = ["dep:rand"]
protocol_udp = []
protocol_websocket = ["dep:tungstenite"]
cp437 = ["dep:once_cell"]
[[example]]
name = "random_brightness"
required-features = ["rand"]
[[example]]
name = "game_of_life"
required-features = ["rand"]
[[example]]
name = "websocket"
required-features = ["protocol_websocket"]
[dev-dependencies]
# for examples
clap = { version = "4.5", features = ["derive"] }
[lints]
workspace = true
[package.metadata.docs.rs]
all-features = true

View file

@ -1,67 +0,0 @@
# servicepoint
[![crates.io](https://img.shields.io/crates/v/servicepoint.svg)](https://crates.io/crates/servicepoint)
[![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint)](https://crates.io/crates/servicepoint)
[![docs.rs](https://img.shields.io/docsrs/servicepoint)](https://docs.rs/servicepoint/latest/servicepoint/)
[![GPLv3 licensed](https://img.shields.io/crates/l/servicepoint)](../../LICENSE)
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
Display" or "Airport Display".
This crate contains a library for parsing, encoding and sending packets to this display via UDP.
## Installation
```bash
cargo add servicepoint
```
or
```toml
[dependencies]
servicepoint = "0.13.1"
```
## Examples
```rust no_run
fn main() {
// establish connection
let connection = servicepoint::Connection::open("172.23.42.29:2342")
.expect("connection failed");
// clear screen content
connection.send(servicepoint::Command::Clear)
.expect("send failed");
}
```
More examples are available in the crate.
Execute `cargo run --example` for a list of available examples and `cargo run --example <name>` to run one.
## Note on stability
This library can be used for creative project or just to play around with the display.
A decent coverage by unit tests prevents major problems and I also test this with my own projects, which mostly use up-to-date versions.
That being said, the API is still being worked on.
Expect minor breaking changes with every version bump.
Please specify the full version including patch in your Cargo.toml until 1.0 is released.
## Features
This library has multiple optional dependencies.
You can choose to (not) include them by toggling the related features.
| Name | Default | Description |
|--------------------|---------|--------------------------------------------|
| compression_zlib | false | Enable additional compression algo |
| compression_bzip2 | false | Enable additional compression algo |
| compression_lzma | true | Enable additional compression algo |
| compression_zstd | false | Enable additional compression algo |
| protocol_udp | true | Connection::Udp |
| protocol_websocket | false | Connection::WebSocket |
| rand | false | impl Distribution<Brightness> for Standard |
| cp437 | true | Conversion to and from CP-437 |
## Everything else
Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for further information.

View file

@ -1,29 +0,0 @@
[package]
name = "servicepoint_binding_c"
version.workspace = true
publish = true
edition = "2021"
license = "GPL-3.0-or-later"
description = "C bindings for the servicepoint crate."
homepage = "https://docs.rs/crate/servicepoint_binding_c"
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint"
readme = "README.md"
links = "servicepoint"
keywords = ["cccb", "cccb-servicepoint", "cbindgen"]
[lib]
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
cbindgen = "0.27.0"
[dependencies.servicepoint]
version = "0.13.1"
path = "../servicepoint"
features = ["all_compressions"]
[lints]
workspace = true
[package.metadata.docs.rs]
all-features = true

View file

@ -1,63 +0,0 @@
# servicepoint_binding_c
[![crates.io](https://img.shields.io/crates/v/servicepoint_binding_c.svg)](https://crates.io/crates/servicepoint)
[![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint_binding_c)](https://crates.io/crates/servicepoint)
[![docs.rs](https://img.shields.io/docsrs/servicepoint_binding_c)](https://docs.rs/servicepoint/latest/servicepoint/)
[![GPLv3 licensed](https://img.shields.io/crates/l/servicepoint_binding_c)](../../LICENSE)
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall.
It is called "Service Point Display" or "Airport Display".
This crate contains C bindings for the `servicepoint` library, enabling users to parse, encode and send packets to this display via UDP.
## Examples
```c++
#include <stdio.h>
#include "servicepoint.h"
int main(void) {
SPConnection *connection = sp_connection_open("172.23.42.29:2342");
if (connection == NULL)
return 1;
SPBitmap *pixels = sp_bitmap_new(SP_PIXEL_WIDTH, SP_PIXEL_HEIGHT);
sp_bitmap_fill(pixels, true);
SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, Uncompressed);
while (sp_connection_send_command(connection, sp_command_clone(command)));
sp_command_free(command);
sp_connection_free(connection);
return 0;
}
```
A full example including Makefile is available as part of this crate.
## Note on stability
This library is still in early development.
You can absolutely use it, and it works, but expect minor breaking changes with every version bump.
Please specify the full version including patch in your Cargo.toml until 1.0 is released.
## Installation
Copy the header to your project and compile against.
You have the choice of linking statically (recommended) or dynamically.
- The C example shows how to link statically against the `staticlib` variant.
- When linked dynamically, you have to provide the `cdylib` at runtime in the _same_ version, as there are no API/ABI guarantees yet.
## Notes on differences to rust library
- function names are: `sp_` \<struct_name\> \<rust name\>.
- Instances get consumed in the same way they do when writing rust code. Do not use an instance after an (implicit!) free.
- Option<T> or Result<T, E> turn into nullable return values - check for NULL!
- There are no specifics for C++ here yet. You might get a nicer header when generating directly for C++, but it should be usable.
- Reading and writing to instances concurrently is not safe. Only reading concurrently is safe.
- documentation is included in the header and available [online](https://docs.rs/servicepoint_binding_c/latest/servicepoint_binding_c/)
## Everything else
Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for further information.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,61 +0,0 @@
[package]
name = "servicepoint_binding_uniffi"
version.workspace = true
publish = false
edition = "2021"
license = "GPL-3.0-or-later"
description = "C bindings for the servicepoint crate."
homepage = "https://docs.rs/crate/servicepoint_binding_c"
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint"
#readme = "README.md"
keywords = ["cccb", "cccb-servicepoint", "uniffi"]
[lib]
crate-type = ["cdylib"]
[build-dependencies]
uniffi = { version = "0.25.3", features = ["build"] }
[dependencies]
uniffi = { version = "0.25.3" }
thiserror.workspace = true
[dependencies.servicepoint]
version = "0.13.1"
path = "../servicepoint"
features = ["all_compressions"]
[dependencies.uniffi-bindgen-cs]
git = "https://github.com/NordSecurity/uniffi-bindgen-cs"
# tag="v0.8.3+v0.25.0"
rev = "f68639fbc720b50ebe561ba75c66c84dc456bdce"
optional = true
[dependencies.uniffi-bindgen-go]
git = "https://github.com/NordSecurity/uniffi-bindgen-go.git"
# tag = "0.2.2+v0.25.0"
rev = "ba23bab72f1a9bcc39ce81924d3d9265598e017c"
optional = true
[lints]
#workspace = true
[package.metadata.docs.rs]
all-features = true
[[bin]]
name = "uniffi-bindgen"
required-features = ["uniffi/cli"]
[[bin]]
name = "uniffi-bindgen-cs"
required-features = ["cs"]
[[bin]]
name = "uniffi-bindgen-go"
required-features = ["go"]
[features]
default = []
cs = ["dep:uniffi-bindgen-cs"]
go = ["dep:uniffi-bindgen-go"]

View file

@ -1,90 +0,0 @@
# ServicePoint
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
Display" or "Airport Display".
This crate contains bindings for multiple programming languages, enabling non-rust-developers to use the library.
Also take a look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for more
information.
## Note on stability
This library is still in early development.
You can absolutely use it, and it works, but expect minor breaking changes with every version bump.
## Notes on differences to rust library
- Performance will not be as good as the rust version:
- most objects are reference counted.
- objects with mutating methods will also have a MRSW lock
- You will not get rust backtraces in release builds of the native code
- Panic messages will work (PanicException)
## Supported languages
| Language | Support level | Notes |
|-----------|---------------|-------------------------------------------------------------------------------------------------|
| .NET (C#) | Full | see dedicated section |
| Ruby | Working | LD_LIBRARY_PATH has to be set, see example project |
| Python | Tested once | Required project file not included. The shared library will be loaded from the script location. |
| Go | untested | |
| Kotlin | untested | |
| Swift | untested | |
## Installation
Including this repository as a submodule and building from source is the recommended way of using the library.
```bash
git submodule add https://git.berlin.ccc.de/servicepoint/servicepoint.git
git commit -m "add servicepoint submodule"
```
Run `generate-bindings.sh` to regenerate all bindings. This will also build `libservicepoint.so` (or equivalent on your
platform).
For languages not fully supported, there will be no project file for the library, just the naked source file(s).
If you successfully use a language, please open an issue or PR to add the missing ones.
## .NET (C#)
This is the best supported language.
F# is not tested. If there are usability or functionality problems, please open an issue.
Currently, the project file is hard-coded for Linux and will need tweaks for other platforms (e.g. `.dylib` instead of `.so`).
You do not have to compile or copy the rust crate manually, as building `ServicePoint.csproj` also builds it.
### Example
```csharp
using System.Threading;
using ServicePoint;
var connection = new Connection("127.0.0.1:2342");
connection.Send(Command.Clear());
connection.Send(Command.Brightness(5));
var pixels = Bitmap.NewMaxSized();
for (ulong offset = 0; offset < ulong.MaxValue; offset++)
{
pixels.Fill(false);
for (ulong y = 0; y < pixels.Height(); y++)
pixels.Set((y + offset) % pixels.Width(), y, true);
connection.Send(Command.BitmapLinearWin(0, 0, pixels));
Thread.Sleep(14);
}
```
A full example including project files is available as part of this crate.
### Why is there no NuGet-Package?
NuGet packages are not a good way to distribute native
binaries ([relevant issue](https://github.com/dotnet/sdk/issues/33845)).
Because of that, there is no NuGet package you can use directly.

View file

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

View file

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

View file

@ -1,2 +0,0 @@
bin
obj

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,2 +0,0 @@
global using Xunit;
global using ServicePoint;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +0,0 @@
[bindings.csharp]
namespace = "ServicePoint"
access_modifier = "public"

View file

@ -20,7 +20,7 @@ fn main() {
let command = Command::BitmapLinearWin(
Origin::ZERO,
pixels,
CompressionCode::Uncompressed,
CompressionCode::default(),
);
connection.send(command).expect("send failed");

View file

@ -24,7 +24,7 @@ fn main() {
let command = Command::BitmapLinearWin(
Origin::ZERO,
field.clone(),
CompressionCode::Lzma,
CompressionCode::default(),
);
connection.send(command).expect("could not send");
thread::sleep(FRAME_PACING);

View file

@ -25,7 +25,7 @@ fn main() {
let command = Command::BitmapLinearWin(
Origin::ZERO,
pixels.clone(),
CompressionCode::Lzma,
CompressionCode::default(),
);
connection.send(command).expect("send failed");
thread::sleep(FRAME_PACING);

View file

@ -31,7 +31,7 @@ fn main() {
let command = Command::BitmapLinearWin(
Origin::ZERO,
filled_grid,
CompressionCode::Lzma,
CompressionCode::default(),
);
connection.send(command).expect("send failed");
}

View file

@ -18,7 +18,7 @@ fn main() {
.send(Command::BitmapLinearWin(
Origin::ZERO,
pixels,
CompressionCode::Lzma,
CompressionCode::default(),
))
.unwrap();
}

View file

@ -35,7 +35,7 @@ fn main() {
.send(Command::BitmapLinearWin(
Origin::ZERO,
enabled_pixels.clone(),
CompressionCode::Lzma,
CompressionCode::default(),
))
.expect("could not send command to display");
thread::sleep(sleep_duration);

View file

@ -139,10 +139,6 @@
cargo-tarpaulin
];
})
ruby
dotnet-sdk_8
gcc
gnumake
];
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (builtins.concatMap (d: d.buildInputs) inputsFrom)}";
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";

View file

@ -72,8 +72,16 @@ impl Bitmap {
/// - 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.");
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,
@ -91,11 +99,23 @@ impl Bitmap {
/// - 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}");
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 }
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].
@ -218,7 +238,7 @@ impl From<Bitmap> for BitVec {
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

View file

@ -14,7 +14,7 @@ use rand::{
/// let val: u8 = b.into();
///
/// let b = Brightness::try_from(7).unwrap();
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
/// # let connection = Connection::Fake;
/// let result = connection.send(Command::Brightness(b));
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
@ -99,7 +99,7 @@ mod tests {
assert_eq!(Brightness::MAX, Brightness::saturating_from(100));
assert_eq!(Brightness(5), Brightness::saturating_from(5));
}
#[test]
#[cfg(feature = "rand")]
fn test() {

View file

@ -13,7 +13,7 @@ use crate::ByteGrid;
/// grid.set(0, 0, Brightness::MIN);
/// grid.set(1, 1, Brightness::MIN);
///
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
/// # let connection = Connection::Fake;
/// connection.send(Command::CharBrightness(Origin::new(3, 7), grid)).unwrap()
/// ```
pub type BrightnessGrid = ValueGrid<Brightness>;

View file

@ -26,7 +26,7 @@ pub type Offset = usize;
/// # Compression
///
/// Some commands can contain compressed payloads.
/// To get started, use [CompressionCode::Uncompressed].
/// To get started, use [CompressionCode::default].
///
/// If you want to archive the best performance (e.g. latency),
/// you can try the different compression algorithms for your hardware and use case.
@ -52,7 +52,7 @@ pub type Offset = usize;
/// assert_eq!(command, round_tripped);
///
/// // send command
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
/// # let connection = Connection::Fake;
/// connection.send(command).unwrap();
/// ```
#[derive(Debug, Clone, PartialEq)]
@ -63,7 +63,7 @@ pub enum Command {
///
/// ```rust
/// # use servicepoint::{Command, Connection};
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
/// # let connection = Connection::Fake;
/// connection.send(Command::Clear).unwrap();
/// ```
Clear,
@ -125,7 +125,7 @@ pub enum Command {
/// let command = Command::BitmapLinearWin(
/// servicepoint::Origin::ZERO,
/// pixels,
/// CompressionCode::Uncompressed
/// CompressionCode::default()
/// );
///
/// connection.send(command).expect("send failed");
@ -138,7 +138,7 @@ pub enum Command {
///
/// ```rust
/// # use servicepoint::{Brightness, Command, Connection};
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
/// # let connection = Connection::Fake;
/// let command = Command::Brightness(Brightness::MAX);
/// connection.send(command).unwrap();
/// ```
@ -187,7 +187,7 @@ pub enum Command {
///
/// ```rust
/// # use servicepoint::{Command, Connection};
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
/// # let connection = Connection::Fake;
/// connection.send(Command::HardReset).unwrap();
/// ```
HardReset,
@ -200,7 +200,7 @@ pub enum Command {
///
/// ```rust
/// # use servicepoint::{Command, Connection};
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
/// # let connection = Connection::Fake;
/// connection.send(Command::FadeOut).unwrap();
/// ```
FadeOut,
@ -213,7 +213,7 @@ pub enum Command {
///
/// ```rust
/// # use servicepoint::{Command, Connection};
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
/// # let connection = Connection::Fake;
/// // this sends a packet that does nothing
/// # #[allow(deprecated)]
/// connection.send(Command::BitmapLegacy).unwrap();

View file

@ -119,7 +119,10 @@ mod tests {
#[test]
fn char_brightness() {
assert_eq!(CommandCode::try_from(0x0005), Ok(CommandCode::CharBrightness));
assert_eq!(
CommandCode::try_from(0x0005),
Ok(CommandCode::CharBrightness)
);
assert_eq!(u16::from(CommandCode::CharBrightness), 0x0005);
}
@ -144,59 +147,86 @@ mod tests {
#[test]
#[allow(deprecated)]
fn bitmap_legacy() {
assert_eq!(CommandCode::try_from(0x0010), Ok(CommandCode::BitmapLegacy));
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!(
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!(
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!(
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!(
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!(
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!(
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!(
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!(
CommandCode::try_from(0x0013),
Ok(CommandCode::BitmapLinearWinUncompressed)
);
assert_eq!(u16::from(CommandCode::BitmapLinearWinUncompressed), 0x0013);
}
@ -208,7 +238,10 @@ mod tests {
#[test]
fn linear_or() {
assert_eq!(CommandCode::try_from(0x0015), Ok(CommandCode::BitmapLinearOr));
assert_eq!(
CommandCode::try_from(0x0015),
Ok(CommandCode::BitmapLinearOr)
);
assert_eq!(u16::from(CommandCode::BitmapLinearOr), 0x0015);
}
}
}

View file

@ -66,6 +66,17 @@ impl TryFrom<u16> for CompressionCode {
}
}
impl Default for CompressionCode {
#[cfg(feature = "compression_lzma")]
fn default() -> Self {
CompressionCode::Lzma
}
#[cfg(not(feature = "compression_lzma"))]
fn default() -> Self {
CompressionCode::Uncompressed
}
}
#[cfg(test)]
mod test {
use super::*;
@ -117,4 +128,16 @@ mod test {
);
assert_eq!(u16::from(CompressionCode::Zstd), 0x7a73);
}
#[test]
#[cfg(feature = "compression_lzma")]
fn default_lzma() {
assert_eq!(CompressionCode::default(), CompressionCode::Lzma);
}
#[test]
#[cfg(not(feature = "compression_lzma"))]
fn default_uncompressed() {
assert_eq!(CompressionCode::default(), CompressionCode::Uncompressed);
}
}

View file

@ -63,7 +63,7 @@ pub const PIXEL_COUNT: usize = PIXEL_WIDTH * PIXEL_HEIGHT;
/// connection.send(Command::BitmapLinearWin(
/// Origin::new(0,0),
/// pixels,
/// CompressionCode::Lzma
/// CompressionCode::default()
/// ))
/// .expect("send failed");
///

View file

@ -1,3 +1,4 @@
use crate::{CharGrid, Cp437Grid};
use std::collections::HashMap;
/// Contains functions to convert between UTF-8 and Codepage 437.
@ -71,6 +72,30 @@ impl 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_feature_cp437 {
use super::*;
@ -112,4 +137,12 @@ mod tests_feature_cp437 {
'?'
);
}
#[test]
fn round_trip_cp437() {
let utf8 = CharGrid::load(2, 2, &['Ä', 'x', '\n', '$']);
let cp437 = Cp437Grid::from(utf8.clone());
let actual = CharGrid::from(cp437);
assert_eq!(actual, utf8);
}
}

View file

@ -1,3 +1,5 @@
use crate::Grid;
/// A grid containing codepage 437 characters.
///
/// The encoding is currently not enforced.
@ -76,39 +78,6 @@ impl Cp437Grid {
}
}
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::*;
@ -146,18 +115,3 @@ mod tests {
);
}
}
#[cfg(test)]
#[cfg(feature = "cp437")]
mod tests_feature_cp437 {
use super::*;
use crate::CharGrid;
#[test]
fn round_trip_cp437() {
let utf8 = CharGrid::load(2, 2, &['Ä', 'x', '\n', '$']);
let cp437 = Cp437Grid::from(utf8.clone());
let actual = CharGrid::from(cp437);
assert_eq!(actual, utf8);
}
}

View file

@ -33,7 +33,7 @@
//! let command = Command::BitmapLinearWin(
//! servicepoint::Origin::ZERO,
//! pixels,
//! CompressionCode::Uncompressed
//! CompressionCode::default()
//! );
//!
//! // send command to display
@ -65,7 +65,6 @@ 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;
@ -97,6 +96,9 @@ mod value_grid;
#[cfg(feature = "cp437")]
mod cp437;
#[cfg(feature = "cp437")]
pub use crate::cp437::Cp437Converter;
// include README.md in doctest
#[doc = include_str!("../README.md")]
#[cfg(doctest)]

View file

@ -90,8 +90,16 @@ impl<T: Value> ValueGrid<T> {
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 }
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.
@ -511,7 +519,7 @@ mod tests {
#[test]
fn ref_mut() {
let mut vec = ValueGrid::from_vec(3, vec![0, 1, 2, 3,4,5,6,7,8]);
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;