text rendering
This commit is contained in:
		
							parent
							
								
									05c7b4cb71
								
							
						
					
					
						commit
						ae12c82c2e
					
				
					 9 changed files with 447 additions and 166 deletions
				
			
		
							
								
								
									
										211
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										211
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -534,6 +534,18 @@ dependencies = [ | |||
|  "libc", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "core-text" | ||||
| version = "20.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" | ||||
| dependencies = [ | ||||
|  "core-foundation", | ||||
|  "core-graphics", | ||||
|  "foreign-types 0.5.0", | ||||
|  "libc", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "crc32fast" | ||||
| version = "1.4.0" | ||||
|  | @ -574,6 +586,16 @@ version = "0.2.2" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "cstr" | ||||
| version = "0.2.12" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "cursor-icon" | ||||
| version = "1.1.0" | ||||
|  | @ -591,6 +613,27 @@ dependencies = [ | |||
|  "winapi", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "dirs-next" | ||||
| version = "2.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "dirs-sys-next", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "dirs-sys-next" | ||||
| version = "0.1.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "redox_users", | ||||
|  "winapi", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "dispatch" | ||||
| version = "0.2.0" | ||||
|  | @ -618,6 +661,18 @@ version = "0.1.1" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "dwrote" | ||||
| version = "0.11.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" | ||||
| dependencies = [ | ||||
|  "lazy_static", | ||||
|  "libc", | ||||
|  "winapi", | ||||
|  "wio", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "either" | ||||
| version = "1.11.0" | ||||
|  | @ -698,6 +753,12 @@ dependencies = [ | |||
|  "miniz_oxide", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "float-ord" | ||||
| version = "0.3.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "flume" | ||||
| version = "0.11.0" | ||||
|  | @ -707,6 +768,31 @@ dependencies = [ | |||
|  "spin", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "font-kit" | ||||
| version = "0.13.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d50ba02d3a19ab9012a00314ff4d105128cdc7ba223d69d48181f2d257244d51" | ||||
| dependencies = [ | ||||
|  "bitflags 2.5.0", | ||||
|  "byteorder", | ||||
|  "core-foundation", | ||||
|  "core-graphics", | ||||
|  "core-text", | ||||
|  "dirs-next", | ||||
|  "dwrote", | ||||
|  "float-ord", | ||||
|  "freetype", | ||||
|  "lazy_static", | ||||
|  "libc", | ||||
|  "log", | ||||
|  "pathfinder_geometry", | ||||
|  "pathfinder_simd", | ||||
|  "walkdir", | ||||
|  "winapi", | ||||
|  "yeslogic-fontconfig-sys", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "foreign-types" | ||||
| version = "0.3.2" | ||||
|  | @ -749,6 +835,27 @@ version = "0.3.1" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "freetype" | ||||
| version = "0.7.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5a440748e063798e4893ceb877151e84acef9bea9a8c6800645cf3f1b3a7806e" | ||||
| dependencies = [ | ||||
|  "freetype-sys", | ||||
|  "libc", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "freetype-sys" | ||||
| version = "0.20.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" | ||||
| dependencies = [ | ||||
|  "cc", | ||||
|  "libc", | ||||
|  "pkg-config", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "gethostname" | ||||
| version = "0.4.3" | ||||
|  | @ -1057,6 +1164,12 @@ dependencies = [ | |||
|  "pkg-config", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "lazy_static" | ||||
| version = "1.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "lebe" | ||||
| version = "0.5.2" | ||||
|  | @ -1111,6 +1224,16 @@ dependencies = [ | |||
|  "redox_syscall 0.4.1", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "libredox" | ||||
| version = "0.1.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" | ||||
| dependencies = [ | ||||
|  "bitflags 2.5.0", | ||||
|  "libc", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "linux-raw-sys" | ||||
| version = "0.4.13" | ||||
|  | @ -1480,7 +1603,7 @@ version = "0.3.47" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" | ||||
| dependencies = [ | ||||
|  "libredox", | ||||
|  "libredox 0.0.2", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
|  | @ -1521,6 +1644,25 @@ version = "1.0.15" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pathfinder_geometry" | ||||
| version = "0.5.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" | ||||
| dependencies = [ | ||||
|  "log", | ||||
|  "pathfinder_simd", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pathfinder_simd" | ||||
| version = "0.5.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ebf45976c56919841273f2a0fc684c28437e2f304e264557d9c72be5d5a718be" | ||||
| dependencies = [ | ||||
|  "rustc_version", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "percent-encoding" | ||||
| version = "2.3.1" | ||||
|  | @ -1559,14 +1701,18 @@ version = "0.1.0" | |||
| dependencies = [ | ||||
|  "clap", | ||||
|  "env_logger", | ||||
|  "font-kit", | ||||
|  "image", | ||||
|  "log", | ||||
|  "num", | ||||
|  "num-derive", | ||||
|  "num-traits", | ||||
|  "pathfinder_geometry", | ||||
|  "pixels", | ||||
|  "raw-window-handle 0.6.1", | ||||
|  "servicepoint2", | ||||
|  "winit", | ||||
|  "yeslogic-fontconfig-sys", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
|  | @ -1631,12 +1777,11 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" | |||
| 
 | ||||
| [[package]] | ||||
| name = "proc-macro-crate" | ||||
| version = "1.3.1" | ||||
| version = "3.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" | ||||
| checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" | ||||
| dependencies = [ | ||||
|  "once_cell", | ||||
|  "toml_edit 0.19.15", | ||||
|  "toml_edit 0.21.1", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
|  | @ -1836,6 +1981,17 @@ dependencies = [ | |||
|  "bitflags 2.5.0", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "redox_users" | ||||
| version = "0.4.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" | ||||
| dependencies = [ | ||||
|  "getrandom", | ||||
|  "libredox 0.1.3", | ||||
|  "thiserror", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "regex" | ||||
| version = "1.10.4" | ||||
|  | @ -1892,6 +2048,15 @@ version = "1.1.0" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rustc_version" | ||||
| version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" | ||||
| dependencies = [ | ||||
|  "semver", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rustix" | ||||
| version = "0.38.34" | ||||
|  | @ -1948,6 +2113,12 @@ dependencies = [ | |||
|  "tiny-skia", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "semver" | ||||
| version = "1.0.23" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "serde" | ||||
| version = "1.0.201" | ||||
|  | @ -1977,6 +2148,11 @@ dependencies = [ | |||
|  "serde", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "servicepoint2" | ||||
| version = "0.1.0" | ||||
| source = "git+https://github.com/kaesaecracker/servicepoint.git#a23ca55f607b64324eda02f27ae347998d227deb" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "simd-adler32" | ||||
| version = "0.3.7" | ||||
|  | @ -2205,9 +2381,9 @@ dependencies = [ | |||
| 
 | ||||
| [[package]] | ||||
| name = "toml_edit" | ||||
| version = "0.19.15" | ||||
| version = "0.21.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" | ||||
| checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" | ||||
| dependencies = [ | ||||
|  "indexmap 2.2.6", | ||||
|  "toml_datetime", | ||||
|  | @ -2949,6 +3125,15 @@ dependencies = [ | |||
|  "memchr", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "wio" | ||||
| version = "0.2.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" | ||||
| dependencies = [ | ||||
|  "winapi", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "x11-dl" | ||||
| version = "2.21.0" | ||||
|  | @ -3006,6 +3191,18 @@ version = "0.2.0" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "yeslogic-fontconfig-sys" | ||||
| version = "5.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ffb6b23999a8b1a997bf47c7bb4d19ad4029c3327bb3386ebe0a5ff584b33c7a" | ||||
| dependencies = [ | ||||
|  "cstr", | ||||
|  "dlib", | ||||
|  "once_cell", | ||||
|  "pkg-config", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zerocopy" | ||||
| version = "0.7.34" | ||||
|  |  | |||
|  | @ -15,3 +15,7 @@ env_logger = "0.11" | |||
| num = "0.4" | ||||
| num-derive = "0.4" | ||||
| num-traits = "0.2" | ||||
| font-kit = { version = "0.13.0", features = ["loader-freetype-default", "loader-freetype"], default-features = false } | ||||
| pathfinder_geometry = "0.5.1" | ||||
| yeslogic-fontconfig-sys = { version = "5.0", features = ["dlopen"] } | ||||
| servicepoint2 = { git = "https://github.com/kaesaecracker/servicepoint.git" } | ||||
|  |  | |||
|  | @ -8,3 +8,9 @@ Use cases: | |||
| - getting error messages for invalid packages | ||||
| - test your project when outside of CCCB | ||||
| - test your project while other people are using the display | ||||
| 
 | ||||
| ## Legal stuff | ||||
| 
 | ||||
| The included font is https://int10h.org/oldschool-pc-fonts/fontlist/font?ibm_bios (included in the download from https://int10h.org/oldschool-pc-fonts/download/). The font is CC BY-SA 4.0. | ||||
| 
 | ||||
| For everything else see the LICENSE file. | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								Web437_IBM_BIOS.woff
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Web437_IBM_BIOS.woff
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										56
									
								
								src/font.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/font.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| use font_kit::canvas::*; | ||||
| use font_kit::hinting::HintingOptions; | ||||
| use pathfinder_geometry::transform2d::Transform2F; | ||||
| use pathfinder_geometry::vector::{vec2f, vec2i}; | ||||
| use servicepoint2::{PixelGrid, TILE_SIZE}; | ||||
| 
 | ||||
| pub struct BitmapFont { | ||||
|     bitmaps: [PixelGrid; u8::MAX as usize], | ||||
| } | ||||
| 
 | ||||
| impl BitmapFont { | ||||
|     pub fn load_file(file: &str) -> BitmapFont { | ||||
|         let font = font_kit::font::Font::from_path(file, 0).expect("could not load font"); | ||||
| 
 | ||||
|         let mut bitmaps = | ||||
|             core::array::from_fn(|_| PixelGrid::new(TILE_SIZE as usize, TILE_SIZE as usize)); | ||||
| 
 | ||||
|         for char_code in u8::MIN..u8::MAX { | ||||
|             let char = char_code as char; | ||||
|             let glyph_id = match font.glyph_for_char(char) { | ||||
|                 None => continue, | ||||
|                 Some(val) => val, | ||||
|             }; | ||||
| 
 | ||||
|             let size = 8f32; | ||||
|             let transform = Transform2F::default(); | ||||
|             let mut canvas = Canvas::new(vec2i(size as i32, size as i32), Format::A8); | ||||
|             font.rasterize_glyph( | ||||
|                 &mut canvas, | ||||
|                 glyph_id, | ||||
|                 size, | ||||
|                 Transform2F::from_translation(vec2f(0f32, size)) * transform, | ||||
|                 HintingOptions::None, | ||||
|                 RasterizationOptions::GrayscaleAa, | ||||
|             ) | ||||
|             .unwrap(); | ||||
| 
 | ||||
|             assert_eq!(canvas.pixels.len(), 64); | ||||
|             assert_eq!(canvas.stride, 8); | ||||
| 
 | ||||
|             for y in 0..TILE_SIZE as usize { | ||||
|                 for x in 0..TILE_SIZE as usize { | ||||
|                     let index = x + y * TILE_SIZE as usize; | ||||
|                     let canvas_val = canvas.pixels[index] != 0; | ||||
|                     bitmaps[char_code as usize].set(x, y, canvas_val); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return BitmapFont { bitmaps }; | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_bitmap(&self, char_code: u8) -> &PixelGrid { | ||||
|         &self.bitmaps[char_code as usize] | ||||
|     } | ||||
| } | ||||
|  | @ -1,8 +1,8 @@ | |||
| use crate::protocol::{PIXEL_HEIGHT, PIXEL_WIDTH}; | ||||
| use crate::DISPLAY; | ||||
| use log::{trace, warn}; | ||||
| use pixels::wgpu::TextureFormat; | ||||
| use pixels::{Pixels, PixelsBuilder, SurfaceTexture}; | ||||
| use servicepoint2::{PIXEL_HEIGHT, PIXEL_WIDTH}; | ||||
| use winit::application::ApplicationHandler; | ||||
| use winit::dpi::{LogicalSize, Size}; | ||||
| use winit::event::WindowEvent; | ||||
|  |  | |||
							
								
								
									
										14
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								src/main.rs
									
										
									
									
									
								
							|  | @ -1,17 +1,17 @@ | |||
| #![deny(clippy::all)] | ||||
| 
 | ||||
| mod font; | ||||
| mod gui; | ||||
| mod upd_loop; | ||||
| mod protocol; | ||||
| mod upd_loop; | ||||
| 
 | ||||
| use std::default::Default; | ||||
| use std::sync::mpsc; | ||||
| 
 | ||||
| use crate::gui::App; | ||||
| use crate::upd_loop::start_udp_thread; | ||||
| use crate::upd_loop::UdpThread; | ||||
| use clap::Parser; | ||||
| use log::info; | ||||
| use protocol::PIXEL_COUNT; | ||||
| use servicepoint2::PIXEL_COUNT; | ||||
| use winit::event_loop::{ControlFlow, EventLoop}; | ||||
| 
 | ||||
| #[derive(Parser, Debug)] | ||||
|  | @ -28,8 +28,7 @@ fn main() { | |||
|     let cli = Cli::parse(); | ||||
|     info!("starting with args: {:?}", &cli); | ||||
| 
 | ||||
|     let (stop_udp_tx, stop_udp_rx) = mpsc::channel(); | ||||
|     let thread = start_udp_thread(cli.bind, stop_udp_rx); | ||||
|     let thread = UdpThread::start_new(cli.bind); | ||||
| 
 | ||||
|     let event_loop = EventLoop::new().expect("could not create event loop"); | ||||
|     event_loop.set_control_flow(ControlFlow::Poll); | ||||
|  | @ -39,6 +38,5 @@ fn main() { | |||
|         .run_app(&mut app) | ||||
|         .expect("could not run event loop"); | ||||
| 
 | ||||
|     stop_udp_tx.send(()).expect("could not cancel thread"); | ||||
|     thread.join().expect("could not join threads"); | ||||
|     thread.stop_and_wait(); | ||||
| } | ||||
|  |  | |||
|  | @ -1,27 +1,38 @@ | |||
| use num_derive::{FromPrimitive, ToPrimitive}; | ||||
| use std::mem::size_of; | ||||
| 
 | ||||
| use num_derive::{FromPrimitive, ToPrimitive}; | ||||
| #[repr(u16)] | ||||
| #[derive(Debug, FromPrimitive, ToPrimitive, Default)] | ||||
| pub enum DisplayCommand { | ||||
| pub enum DisplayCommandCode { | ||||
|     #[default] | ||||
|     CmdClear = 0x0002, | ||||
|     CmdCp437data = 0x0003, | ||||
|     CmdCharBrightness = 0x0005, | ||||
|     CmdBrightness = 0x0007, | ||||
|     CmdHardReset = 0x000b, | ||||
|     CmdFadeOut = 0x000d, | ||||
|     CmdBitmapLegacy = 0x0010, | ||||
|     CmdBitmapLinear = 0x0012, | ||||
|     CmdBitmapLinearWin = 0x0013, | ||||
|     CmdBitmapLinearAnd = 0x0014, | ||||
|     CmdBitmapLinearOr = 0x0015, | ||||
|     CmdBitmapLinearXor = 0x0016, | ||||
|     Clear = 0x0002, | ||||
|     Cp437data = 0x0003, | ||||
|     CharBrightness = 0x0005, | ||||
|     Brightness = 0x0007, | ||||
|     HardReset = 0x000b, | ||||
|     FadeOut = 0x000d, | ||||
|     BitmapLegacy = 0x0010, | ||||
|     BitmapLinear = 0x0012, | ||||
|     BitmapLinearWin = 0x0013, | ||||
|     BitmapLinearAnd = 0x0014, | ||||
|     BitmapLinearOr = 0x0015, | ||||
|     BitmapLinearXor = 0x0016, | ||||
| } | ||||
| 
 | ||||
| impl DisplayCommandCode { | ||||
|     pub fn from_primitive(value: u16) -> Option<Self> { | ||||
|         num::FromPrimitive::from_u16(value) | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_primitive(&self) -> u16 { | ||||
|         num::ToPrimitive::to_u16(self).unwrap() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[repr(C)] | ||||
| #[derive(Debug, Default)] | ||||
| pub struct HdrWindow { | ||||
|     pub command: DisplayCommand, | ||||
|     pub command: DisplayCommandCode, | ||||
|     pub x: u16, | ||||
|     pub y: u16, | ||||
|     pub w: u16, | ||||
|  | @ -49,27 +60,22 @@ pub enum DisplaySubcommand { | |||
|     SubCmdBitmapCompressZs = 0x7a73, | ||||
| } | ||||
| 
 | ||||
| pub const TILE_SIZE: u16 = 8; | ||||
| pub const TILE_WIDTH: u16 = 56; | ||||
| pub const TILE_HEIGHT: u16 = 20; | ||||
| pub const PIXEL_WIDTH: u16 = TILE_WIDTH * TILE_SIZE; | ||||
| pub const PIXEL_HEIGHT: u16 = TILE_HEIGHT * TILE_SIZE; | ||||
| pub const PIXEL_COUNT: usize = PIXEL_WIDTH as usize * PIXEL_HEIGHT as usize; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub enum ReadHeaderError { | ||||
|     BufferTooSmall, | ||||
|     WrongCommandEndianness(u16, DisplayCommand), | ||||
|     WrongCommandEndianness(u16, DisplayCommandCode), | ||||
|     InvalidCommand(u16), | ||||
| } | ||||
| 
 | ||||
| pub fn read_header(buffer: &[u8]) -> Result<HdrWindow, ReadHeaderError> { | ||||
|     assert_eq!(size_of::<HdrWindow>(), 10, "invalid struct size"); | ||||
| 
 | ||||
|     if buffer.len() < size_of::<HdrWindow>() { | ||||
|         return Err(ReadHeaderError::BufferTooSmall); | ||||
|     } | ||||
| 
 | ||||
|     let command_u16 = read_beu16(&buffer[0..=1]); | ||||
|     return match num::FromPrimitive::from_u16(command_u16) { | ||||
|     return match DisplayCommandCode::from_primitive(command_u16) { | ||||
|         Some(command) => Ok(HdrWindow { | ||||
|             command, | ||||
|             x: read_beu16(&buffer[2..=3]), | ||||
|  | @ -78,8 +84,7 @@ pub fn read_header(buffer: &[u8]) -> Result<HdrWindow, ReadHeaderError> { | |||
|             h: read_beu16(&buffer[8..=9]), | ||||
|         }), | ||||
|         None => { | ||||
|             let maybe_command: Option<DisplayCommand> = | ||||
|                 num::FromPrimitive::from_u16(u16::swap_bytes(command_u16)); | ||||
|             let maybe_command = DisplayCommandCode::from_primitive(u16::swap_bytes(command_u16)); | ||||
|             return match maybe_command { | ||||
|                 None => Err(ReadHeaderError::InvalidCommand(command_u16)), | ||||
|                 Some(command) => Err(ReadHeaderError::WrongCommandEndianness( | ||||
|  |  | |||
							
								
								
									
										263
									
								
								src/upd_loop.rs
									
										
									
									
									
								
							
							
						
						
									
										263
									
								
								src/upd_loop.rs
									
										
									
									
									
								
							|  | @ -1,164 +1,179 @@ | |||
| use crate::protocol::{ | ||||
|     read_header, DisplayCommand, HdrWindow, ReadHeaderError, PIXEL_WIDTH, TILE_SIZE, | ||||
| }; | ||||
| use crate::font::BitmapFont; | ||||
| use crate::protocol::{read_header, DisplayCommandCode, HdrWindow, ReadHeaderError}; | ||||
| use crate::DISPLAY; | ||||
| use log::{error, info, warn}; | ||||
| use log::{debug, error, info, warn}; | ||||
| use servicepoint2::{PixelGrid, PIXEL_WIDTH, TILE_SIZE}; | ||||
| use std::io::ErrorKind; | ||||
| use std::mem::size_of; | ||||
| use std::net::UdpSocket; | ||||
| use std::sync::mpsc::Receiver; | ||||
| use std::net::{ToSocketAddrs, UdpSocket}; | ||||
| use std::sync::mpsc; | ||||
| use std::sync::mpsc::Sender; | ||||
| use std::thread; | ||||
| use std::thread::JoinHandle; | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| pub fn start_udp_thread(bind: String, stop_receiver: Receiver<()>) -> JoinHandle<()> { | ||||
|     assert_eq!(size_of::<HdrWindow>(), 10, "invalid struct size"); | ||||
| pub struct UdpThread { | ||||
|     thread: JoinHandle<()>, | ||||
|     stop_tx: Sender<()>, | ||||
| } | ||||
| 
 | ||||
| impl UdpThread { | ||||
|     pub fn start_new(bind: impl ToSocketAddrs) -> Self { | ||||
|         let (stop_tx, stop_rx) = mpsc::channel(); | ||||
| 
 | ||||
|     return thread::spawn(move || { | ||||
|         let socket = UdpSocket::bind(bind).expect("could not bind socket"); | ||||
|         socket | ||||
|             .set_nonblocking(true) | ||||
|             .expect("could not enter non blocking mode"); | ||||
| 
 | ||||
|         let mut buf = [0; 8985]; | ||||
|         let font = BitmapFont::load_file("Web437_IBM_BIOS.woff"); | ||||
| 
 | ||||
|         while stop_receiver.try_recv().is_err() { | ||||
|             let (amount, _) = match socket.recv_from(&mut buf) { | ||||
|                 Err(err) if err.kind() == ErrorKind::WouldBlock => { | ||||
|                     thread::sleep(Duration::from_millis(1)); | ||||
|                     continue; | ||||
|         let thread = thread::spawn(move || { | ||||
|             let mut buf = [0; 8985]; | ||||
| 
 | ||||
|             while stop_rx.try_recv().is_err() { | ||||
|                 let (amount, _) = match socket.recv_from(&mut buf) { | ||||
|                     Err(err) if err.kind() == ErrorKind::WouldBlock => { | ||||
|                         thread::sleep(Duration::from_millis(1)); | ||||
|                         continue; | ||||
|                     } | ||||
|                     Ok(result) => result, | ||||
|                     other => other.unwrap(), | ||||
|                 }; | ||||
| 
 | ||||
|                 if amount == buf.len() { | ||||
|                     warn!( | ||||
|                         "the received package may have been truncated to a length of {}", | ||||
|                         amount | ||||
|                     ); | ||||
|                 } | ||||
|                 Ok(result) => result, | ||||
|                 other => other.unwrap(), | ||||
|             }; | ||||
| 
 | ||||
|             if amount == buf.len() { | ||||
|                 warn!( | ||||
|                     "the received package may have been truncated to a length of {}", | ||||
|                     amount | ||||
|                 ); | ||||
|                 Self::handle_package(&mut buf[..amount], &font); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|             handle_package(&mut buf[..amount]); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|         return Self { stop_tx, thread }; | ||||
|     } | ||||
| 
 | ||||
| fn handle_package(received: &mut [u8]) { | ||||
|     let header = match read_header(&received[..10]) { | ||||
|         Err(ReadHeaderError::BufferTooSmall) => { | ||||
|             error!("received a packet that is too small"); | ||||
|             return; | ||||
|         } | ||||
|         Err(ReadHeaderError::InvalidCommand(command_u16)) => { | ||||
|             error!("received invalid command {}", command_u16); | ||||
|             return; | ||||
|         } | ||||
|         Err(ReadHeaderError::WrongCommandEndianness(command_u16, command_swapped)) => { | ||||
|             error!( | ||||
|     pub fn stop_and_wait(self) { | ||||
|         self.stop_tx.send(()).expect("could not send stop packet"); | ||||
|         self.thread.join().expect("could not wait on udp thread"); | ||||
|     } | ||||
| 
 | ||||
|     fn handle_package(received: &mut [u8], font: &BitmapFont) { | ||||
|         let header = match read_header(&received[..10]) { | ||||
|             Err(ReadHeaderError::BufferTooSmall) => { | ||||
|                 error!("received a packet that is too small"); | ||||
|                 return; | ||||
|             } | ||||
|             Err(ReadHeaderError::InvalidCommand(command_u16)) => { | ||||
|                 error!("received invalid command {}", command_u16); | ||||
|                 return; | ||||
|             } | ||||
|             Err(ReadHeaderError::WrongCommandEndianness(command_u16, command_swapped)) => { | ||||
|                 error!( | ||||
|                 "The reversed byte order of {} matches command {:?}, you are probably sending the wrong endianness", | ||||
|                 command_u16, command_swapped | ||||
|             ); | ||||
|             return; | ||||
|         } | ||||
|         Ok(value) => value, | ||||
|     }; | ||||
|                 return; | ||||
|             } | ||||
|             Ok(value) => value, | ||||
|         }; | ||||
| 
 | ||||
|     let payload = &received[10..]; | ||||
|         let payload = &received[10..]; | ||||
| 
 | ||||
|     info!( | ||||
|         "received from {:?} (and {} bytes of payload)", | ||||
|         header, | ||||
|         payload.len() | ||||
|     ); | ||||
|         info!( | ||||
|             "received from {:?} (and {} bytes of payload)", | ||||
|             header, | ||||
|             payload.len() | ||||
|         ); | ||||
| 
 | ||||
|     match header.command { | ||||
|         DisplayCommand::CmdClear => { | ||||
|             info!("clearing display"); | ||||
|             for v in unsafe { DISPLAY.iter_mut() } { | ||||
|                 *v = false; | ||||
|         match header.command { | ||||
|             DisplayCommandCode::Clear => { | ||||
|                 info!("clearing display"); | ||||
|                 for v in unsafe { DISPLAY.iter_mut() } { | ||||
|                     *v = false; | ||||
|                 } | ||||
|             } | ||||
|             DisplayCommandCode::HardReset => { | ||||
|                 warn!("display shutting down"); | ||||
|                 return; | ||||
|             } | ||||
|             DisplayCommandCode::BitmapLinearWin => { | ||||
|                 Self::print_bitmap_linear_win(&header, payload); | ||||
|             } | ||||
|             DisplayCommandCode::Cp437data => { | ||||
|                 Self::print_cp437_data(&header, payload, font); | ||||
|             } | ||||
|             _ => { | ||||
|                 error!("command {:?} not implemented yet", header.command); | ||||
|             } | ||||
|         } | ||||
|         DisplayCommand::CmdHardReset => { | ||||
|             warn!("display shutting down"); | ||||
|     } | ||||
| 
 | ||||
|     fn check_payload_size(buf: &[u8], expected: usize) -> bool { | ||||
|         let actual = buf.len(); | ||||
|         if actual == expected { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         error!( | ||||
|             "expected a payload length of {} but got {}", | ||||
|             expected, actual | ||||
|         ); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     fn print_bitmap_linear_win(header: &HdrWindow, payload: &[u8]) { | ||||
|         if !Self::check_payload_size(payload, header.w as usize * header.h as usize) { | ||||
|             return; | ||||
|         } | ||||
|         DisplayCommand::CmdBitmapLinearWin => { | ||||
|             print_bitmap_linear_win(&header, payload); | ||||
|         } | ||||
|         DisplayCommand::CmdCp437data => { | ||||
|             print_cp437_data(&header, payload); | ||||
|         } | ||||
|         _ => { | ||||
|             error!("command {:?} not implemented yet", header.command); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn check_payload_size(buf: &[u8], expected: usize) -> bool { | ||||
|     let actual = buf.len(); | ||||
|     if actual == expected { | ||||
|         return true; | ||||
|         let pixel_grid = PixelGrid::load( | ||||
|             header.w as usize * TILE_SIZE as usize, | ||||
|             header.h as usize, | ||||
|             payload, | ||||
|         ); | ||||
| 
 | ||||
|         Self::print_pixel_grid( | ||||
|             header.x as usize * TILE_SIZE as usize, | ||||
|             header.y as usize, | ||||
|             &pixel_grid, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     error!( | ||||
|         "expected a payload length of {} but got {}", | ||||
|         expected, actual | ||||
|     ); | ||||
|     return false; | ||||
| } | ||||
|     fn print_cp437_data(header: &HdrWindow, payload: &[u8], font: &BitmapFont) { | ||||
|         if !UdpThread::check_payload_size(payload, (header.w * header.h) as usize) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| fn print_bitmap_linear_win(header: &HdrWindow, payload: &[u8]) { | ||||
|     if !check_payload_size(payload, header.w as usize * header.h as usize) { | ||||
|         return; | ||||
|         for char_y in 0usize..header.h as usize { | ||||
|             for char_x in 0usize..header.w as usize { | ||||
|                 let char_code = payload[char_y * header.w as usize + char_x]; | ||||
| 
 | ||||
|                 let tile_x = char_x + header.x as usize; | ||||
|                 let tile_y = char_y + header.y as usize; | ||||
| 
 | ||||
|                 let bitmap = font.get_bitmap(char_code); | ||||
|                 Self::print_pixel_grid( | ||||
|                     tile_x * TILE_SIZE as usize, | ||||
|                     tile_y * TILE_SIZE as usize, | ||||
|                     bitmap, | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     info!( | ||||
|         "top left is offset {} tiles in x-direction and {} pixels in y-direction", | ||||
|         header.x, header.y | ||||
|     ); | ||||
| 
 | ||||
|     for y in 0..header.h { | ||||
|         for byte_x in 0..header.w { | ||||
|             let byte_index = (y * header.w + byte_x) as usize; | ||||
|             let byte = payload[byte_index]; | ||||
| 
 | ||||
|             for pixel_x in 0u8..8u8 { | ||||
|                 let bit_index = 7 - pixel_x; | ||||
|                 let bitmask = 1 << bit_index; | ||||
|                 let is_set = byte & bitmask != 0; | ||||
| 
 | ||||
|                 let x = byte_x * TILE_SIZE + pixel_x as u16; | ||||
| 
 | ||||
|                 let translated_x = (x + header.x) as usize; | ||||
|                 let translated_y = (y + header.y) as usize; | ||||
|                 let index = translated_y * PIXEL_WIDTH as usize + translated_x; | ||||
| 
 | ||||
|     fn print_pixel_grid(offset_x: usize, offset_y: usize, pixels: &PixelGrid) { | ||||
|         debug!("printing {}x{} grid at {offset_x} {offset_y}", pixels.width, pixels.height); | ||||
|         for inner_y in 0..pixels.height { | ||||
|             for inner_x in 0..pixels.width { | ||||
|                 let is_set = pixels.get(inner_x, inner_y); | ||||
|                 let display_index = | ||||
|                     (offset_x + inner_x) + ((offset_y + inner_y) * PIXEL_WIDTH as usize); | ||||
|                 unsafe { | ||||
|                     DISPLAY[index] = is_set; | ||||
|                     DISPLAY[display_index] = is_set; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // TODO: actually convert from CP437
 | ||||
| fn print_cp437_data(header: &HdrWindow, payload: &[u8]) { | ||||
|     if !check_payload_size(payload, (header.w * header.h) as usize) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     info!("top left is offset by ({} | {}) tiles", header.x, header.y); | ||||
| 
 | ||||
|     let mut str = String::new(); | ||||
|     for y in 0..header.h { | ||||
|         for x in 0..header.w { | ||||
|             let byte_index = (y * header.w + x) as usize; | ||||
|             str.push(payload[byte_index] as char); | ||||
|         } | ||||
| 
 | ||||
|         str.push('\n'); | ||||
|     } | ||||
| 
 | ||||
|     info!("{}", str); | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter