Compare commits
12 changed files with 810 additions and 866 deletions
1387
Cargo.lock
generated
1387
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
11
Cargo.toml
11
Cargo.toml
|
|
@ -1,13 +1,13 @@
|
|||
[package]
|
||||
name = "servicepoint-simulator"
|
||||
version = "0.2.4"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
publish = true
|
||||
license = "GPL-3.0-or-later"
|
||||
keywords = ["cccb", "cccb-servicepoint", "cli"]
|
||||
description = "A simulator for the Service Point display."
|
||||
homepage = "https://git.berlin.ccc.de/servicepoint/servicepoint-simulator"
|
||||
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint-simulator.git"
|
||||
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint-simulator"
|
||||
readme = "README.md"
|
||||
rust-version = "1.80.0"
|
||||
resolver = "2"
|
||||
|
|
@ -19,6 +19,9 @@ env_logger = "0.11"
|
|||
clap = { version = "4.5", features = ["derive"] }
|
||||
thiserror = "2.0"
|
||||
|
||||
# package parsing
|
||||
servicepoint = { features = ["all_compressions"], version = "0.15.1" }
|
||||
|
||||
# font rendering
|
||||
font-kit = "0.14.2"
|
||||
# I should not need this as a direct dependency, but then I cannot spell the types needed to use font-kit...
|
||||
|
|
@ -29,10 +32,6 @@ winit = "0.30"
|
|||
# for drawing pixels onto the surface of the window
|
||||
softbuffer = "0.4.6"
|
||||
|
||||
[dependencies.servicepoint]
|
||||
version = "0.16.0"
|
||||
features = ["all_compressions"]
|
||||
|
||||
[profile.release]
|
||||
lto = true # Enable link-time optimization
|
||||
codegen-units = 1 # Reduce number of codegen units to increase optimizations
|
||||
|
|
|
|||
26
README.md
26
README.md
|
|
@ -42,24 +42,14 @@ Make sure to run a release build, because a debug build _way_ slower.
|
|||
Usage: servicepoint-simulator [OPTIONS]
|
||||
|
||||
Options:
|
||||
--bind <BIND>
|
||||
address and port to bind to [default: 0.0.0.0:2342]
|
||||
-f, --font <FONT>
|
||||
The name of the font family to use. This defaults to the system monospace font.
|
||||
-s, --spacers
|
||||
add spacers between tile rows to simulate gaps in real display
|
||||
-r, --red
|
||||
Use the red color channel
|
||||
-g, --green
|
||||
Use the green color channel
|
||||
-b, --blue
|
||||
Use the blue color channel
|
||||
-v, --verbose
|
||||
Set default log level lower. You can also change this via the RUST_LOG environment variable.
|
||||
--experimental-null-char-handling
|
||||
When receiving a null byte as a char in the CharGridCommand, do not overwrite any pixels instead of clearing all pixels.
|
||||
-h, --help
|
||||
Print help
|
||||
--bind <BIND> address and port to bind to [default: 0.0.0.0:2342]
|
||||
-f, --font <FONT> The name of the font family to use. This defaults to the system monospace font.
|
||||
-s, --spacers add spacers between tile rows to simulate gaps in real display
|
||||
-r, --red Use the red color channel
|
||||
-g, --green Use the green color channel
|
||||
-b, --blue Use the blue color channel
|
||||
-v, --verbose Set default log level lower. You can also change this via the RUST_LOG environment variable.
|
||||
-h, --help Print help
|
||||
```
|
||||
|
||||
See [env_logger](https://docs.rs/env_logger/latest/env_logger/) to configure logging.
|
||||
|
|
|
|||
47
flake.lock
generated
47
flake.lock
generated
|
|
@ -1,39 +1,60 @@
|
|||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1785541369,
|
||||
"narHash": "sha256-6VCYI5xashcnAR1lambqt9FGh0F6kYhg1P2Z0VDlr48=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "508ff4829aefddee8ea62291e86b144b58365724",
|
||||
"lastModified": 1745925850,
|
||||
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1731533336,
|
||||
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1785386831,
|
||||
"narHash": "sha256-sPS3CaXH8RAT3FZRuy4VcV47iuYIWMMfa0GbyJKC3o4=",
|
||||
"lastModified": 1750969886,
|
||||
"narHash": "sha256-zW/OFnotiz/ndPFdebpo3X0CrbVNf22n4DjN2vxlb58=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "21ea275a7c46aef9d4d6ddc962e6d562e9d94183",
|
||||
"rev": "a676066377a2fe7457369dd37c31fd2263b662f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-26.05",
|
||||
"ref": "nixos-25.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"naersk": "naersk",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
flake.nix
29
flake.nix
|
|
@ -2,15 +2,20 @@
|
|||
description = "Flake for servicepoint-simulator";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-26.05";
|
||||
crane.url = "github:ipetkov/crane";
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
|
||||
nix-filter.url = "github:numtide/nix-filter";
|
||||
naersk = {
|
||||
url = "github:nix-community/naersk";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
inputs@{
|
||||
self,
|
||||
nixpkgs,
|
||||
crane,
|
||||
naersk,
|
||||
nix-filter,
|
||||
}:
|
||||
let
|
||||
lib = nixpkgs.lib;
|
||||
|
|
@ -35,22 +40,13 @@
|
|||
{ pkgs, ... }:
|
||||
rec {
|
||||
servicepoint-simulator = import ./servicepoint-simulator.nix {
|
||||
inherit pkgs;
|
||||
craneLib = crane.mkLib pkgs;
|
||||
inherit nix-filter pkgs;
|
||||
naersk' = pkgs.callPackage naersk { };
|
||||
};
|
||||
default = servicepoint-simulator;
|
||||
}
|
||||
);
|
||||
|
||||
nixosModules.default = {
|
||||
nixpkgs.overlays = [ self.overlays.default ];
|
||||
};
|
||||
|
||||
overlays.default = final: prev: {
|
||||
servicepoint-simulator =
|
||||
self.legacyPackages."${prev.stdenv.hostPlatform.system}".servicepoint-simulator;
|
||||
};
|
||||
|
||||
legacyPackages = packages;
|
||||
|
||||
devShells = forAllSystems (
|
||||
|
|
@ -79,11 +75,10 @@
|
|||
NIX_LD_LIBRARY_PATH = LD_LIBRARY_PATH;
|
||||
NIX_LD = pkgs.stdenv.cc.bintools.dynamicLinker;
|
||||
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||
RUST_BACKTRACE = "1";
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
formatter = forAllSystems ({ pkgs, ... }: pkgs.nixfmt-tree);
|
||||
formatter = forAllSystems ({ pkgs, ... }: pkgs.nixfmt-rfc-style);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
{
|
||||
craneLib,
|
||||
naersk',
|
||||
pkgs,
|
||||
nix-filter,
|
||||
}:
|
||||
let
|
||||
commonArgs = {
|
||||
src = craneLib.cleanCargoSource ./.;
|
||||
naersk'.buildPackage rec {
|
||||
src = nix-filter.lib.filter {
|
||||
root = ./.;
|
||||
include = [
|
||||
./Cargo.toml
|
||||
./Cargo.lock
|
||||
./src
|
||||
./Web437_IBM_BIOS.woff
|
||||
./README.md
|
||||
./LICENSE
|
||||
];
|
||||
};
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkg-config
|
||||
makeWrapper
|
||||
|
|
@ -41,23 +51,24 @@ let
|
|||
wayland
|
||||
|
||||
# WINIT_UNIX_BACKEND=x11
|
||||
libxcursor
|
||||
libxrandr
|
||||
libxi
|
||||
libx11
|
||||
libx11.dev
|
||||
xorg.libXcursor
|
||||
xorg.libXrandr
|
||||
xorg.libXi
|
||||
xorg.libX11
|
||||
xorg.libX11.dev
|
||||
]
|
||||
)
|
||||
++ lib.optionals pkgs.stdenv.isDarwin (
|
||||
with pkgs.darwin.apple_sdk.frameworks;
|
||||
[
|
||||
Carbon
|
||||
QuartzCore
|
||||
AppKit
|
||||
]
|
||||
);
|
||||
};
|
||||
in
|
||||
craneLib.buildPackage (
|
||||
commonArgs
|
||||
// {
|
||||
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||
|
||||
postInstall = ''
|
||||
wrapProgram $out/bin/servicepoint-simulator \
|
||||
--suffix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath commonArgs.buildInputs}
|
||||
--suffix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath buildInputs}
|
||||
'';
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,11 +22,6 @@ pub struct Cli {
|
|||
help = "Set default log level lower. You can also change this via the RUST_LOG environment variable."
|
||||
)]
|
||||
pub verbose: bool,
|
||||
#[arg(
|
||||
long,
|
||||
help = "When receiving a null byte as a char in the CharGridCommand, do not overwrite any pixels instead of clearing all pixels."
|
||||
)]
|
||||
pub experimental_null_char_handling: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ use crate::{
|
|||
};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use servicepoint::{
|
||||
BinaryOperation, BitVecCommand, Bitmap, BitmapCommand, BrightnessGrid,
|
||||
BrightnessGridCommand, CharGridCommand, ClearCommand, CompressionCode,
|
||||
Cp437GridCommand, FadeOutCommand, GlobalBrightnessCommand, Grid, GridMut,
|
||||
HardResetCommand, Origin, TypedCommand, PIXEL_COUNT, PIXEL_WIDTH,
|
||||
TILE_SIZE,
|
||||
BinaryOperation, BitVecCommand, Bitmap, BitmapCommand, GlobalBrightnessCommand,
|
||||
BrightnessGrid, BrightnessGridCommand, CharGridCommand, ClearCommand,
|
||||
CompressionCode, Cp437GridCommand, FadeOutCommand, Grid, HardResetCommand,
|
||||
Origin, TypedCommand, PIXEL_COUNT, PIXEL_WIDTH, TILE_SIZE,
|
||||
};
|
||||
use std::{
|
||||
ops::{BitAnd, BitOr, BitXor},
|
||||
|
|
@ -22,7 +21,6 @@ pub struct CommandExecutionContext<'t> {
|
|||
luma: &'t RwLock<BrightnessGrid>,
|
||||
cp437_font: Cp437Font,
|
||||
font_renderer: FontRenderer8x8,
|
||||
experimental_null_char_handling: bool,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -194,30 +192,16 @@ impl CommandExecute for CharGridCommand {
|
|||
for char_y in 0usize..grid.height() {
|
||||
for char_x in 0usize..grid.width() {
|
||||
let char = grid.get(char_x, char_y);
|
||||
let mut bitmap_window = {
|
||||
let pixel_x = (char_x + x) * TILE_SIZE;
|
||||
let pixel_y = (char_y + y) * TILE_SIZE;
|
||||
display
|
||||
.window_mut(
|
||||
pixel_x..pixel_x + TILE_SIZE,
|
||||
pixel_y..pixel_y + TILE_SIZE,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
if char == '\0' {
|
||||
if context.experimental_null_char_handling {
|
||||
trace!("skipping {char:?}");
|
||||
} else {
|
||||
bitmap_window.fill(false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
trace!("drawing {char}");
|
||||
if let Err(e) =
|
||||
context.font_renderer.render(char, &mut bitmap_window)
|
||||
{
|
||||
|
||||
let tile_x = char_x + x;
|
||||
let tile_y = char_y + y;
|
||||
|
||||
if let Err(e) = context.font_renderer.render(
|
||||
char,
|
||||
&mut display,
|
||||
Origin::new(tile_x * TILE_SIZE, tile_y * TILE_SIZE),
|
||||
) {
|
||||
error!(
|
||||
"stopping drawing text because char draw failed: {e}"
|
||||
);
|
||||
|
|
@ -267,14 +251,12 @@ impl<'t> CommandExecutionContext<'t> {
|
|||
display: &'t RwLock<Bitmap>,
|
||||
luma: &'t RwLock<BrightnessGrid>,
|
||||
font_renderer: FontRenderer8x8,
|
||||
experimental_null_char_handling: bool,
|
||||
) -> Self {
|
||||
CommandExecutionContext {
|
||||
display,
|
||||
luma,
|
||||
font_renderer,
|
||||
cp437_font: Cp437Font::default(),
|
||||
experimental_null_char_handling,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,8 @@ use pathfinder_geometry::{
|
|||
transform2d::Transform2F,
|
||||
vector::{vec2f, vec2i},
|
||||
};
|
||||
use servicepoint::{Bitmap, GridMut, WindowMut, TILE_SIZE};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Mutex, MutexGuard},
|
||||
};
|
||||
use servicepoint::{Bitmap, Grid, Origin, Pixels, TILE_SIZE};
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SendFont(Font);
|
||||
|
|
@ -35,7 +32,6 @@ pub struct FontRenderer8x8 {
|
|||
font: SendFont,
|
||||
canvas: Mutex<Canvas>,
|
||||
fallback_char: Option<u32>,
|
||||
cache: Mutex<HashMap<char, Bitmap>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
|
@ -60,7 +56,6 @@ impl FontRenderer8x8 {
|
|||
font: SendFont(font),
|
||||
fallback_char,
|
||||
canvas: Mutex::new(canvas),
|
||||
cache: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,13 +74,9 @@ impl FontRenderer8x8 {
|
|||
pub fn render(
|
||||
&self,
|
||||
char: char,
|
||||
target: &mut WindowMut<bool, Bitmap>,
|
||||
bitmap: &mut Bitmap,
|
||||
offset: Origin<Pixels>,
|
||||
) -> Result<(), RenderError> {
|
||||
let cache = &mut *self.cache.lock().unwrap();
|
||||
if let Some(drawn_char) = cache.get(&char) {
|
||||
target.deref_assign(drawn_char);
|
||||
}
|
||||
|
||||
let glyph_id = self.get_glyph(char)?;
|
||||
|
||||
let mut canvas = self.canvas.lock().unwrap();
|
||||
|
|
@ -100,21 +91,20 @@ impl FontRenderer8x8 {
|
|||
RasterizationOptions::Bilevel,
|
||||
)?;
|
||||
|
||||
let mut bitmap = Bitmap::new(TILE_SIZE, TILE_SIZE).unwrap();
|
||||
Self::copy_to_bitmap(canvas, &mut bitmap)?;
|
||||
target.deref_assign(&bitmap);
|
||||
cache.insert(char, bitmap);
|
||||
Ok(())
|
||||
Self::copy_to_bitmap(canvas, bitmap, offset)
|
||||
}
|
||||
|
||||
fn copy_to_bitmap(
|
||||
canvas: MutexGuard<Canvas>,
|
||||
bitmap: &mut Bitmap,
|
||||
offset: Origin<Pixels>,
|
||||
) -> Result<(), RenderError> {
|
||||
for y in 0..TILE_SIZE {
|
||||
for x in 0..TILE_SIZE {
|
||||
let canvas_val = canvas.pixels[x + y * TILE_SIZE] != 0;
|
||||
if !bitmap.set_optional(x, y, canvas_val) {
|
||||
let bitmap_x = (offset.x + x) as isize;
|
||||
let bitmap_y = (offset.y + y) as isize;
|
||||
if !bitmap.set_optional(bitmap_x, bitmap_y, canvas_val) {
|
||||
return Err(OutOfBounds(x, y));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ const PIXEL_HEIGHT_WITH_SPACERS: usize =
|
|||
PIXEL_HEIGHT + NUM_SPACERS * SPACER_HEIGHT;
|
||||
|
||||
const OFF_COLOR: u32 = u32::from_ne_bytes([0u8, 0, 0, 0]);
|
||||
const SPACER_COLOR: u32 = u32::from_ne_bytes([100u8, 100, 100, 0]);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppEvents {
|
||||
|
|
@ -62,7 +61,7 @@ impl<'t> Gui<'t> {
|
|||
if self.options.spacers && tile_y != 0 {
|
||||
// cannot just frame.skip(PIXEL_WIDTH as usize * SPACER_HEIGHT as usize) because of typing
|
||||
for _ in 0..PIXEL_WIDTH * SPACER_HEIGHT {
|
||||
*frame.next().unwrap() = SPACER_COLOR;
|
||||
frame.next().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ impl GuiWindow {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_buffer(&mut self) -> Buffer<'_, Rc<Window>, Rc<Window>> {
|
||||
pub fn get_buffer(&mut self) -> Buffer<Rc<Window>, Rc<Window>> {
|
||||
self.surface.buffer_mut().unwrap()
|
||||
}
|
||||
pub(crate) fn request_redraw(&self) {
|
||||
|
|
|
|||
|
|
@ -39,12 +39,7 @@ fn main() {
|
|||
.font
|
||||
.map(FontRenderer8x8::from_name)
|
||||
.unwrap_or_else(FontRenderer8x8::default);
|
||||
let context = CommandExecutionContext::new(
|
||||
&display,
|
||||
&luma,
|
||||
font_renderer,
|
||||
cli.experimental_null_char_handling,
|
||||
);
|
||||
let context = CommandExecutionContext::new(&display, &luma, font_renderer);
|
||||
let mut udp_server = UdpServer::new(
|
||||
cli.bind,
|
||||
stop_udp_rx,
|
||||
|
|
|
|||
Loading…
Reference in a new issue