URI: 
       Started a skypix parser. - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
  HTML git clone https://git.drkhsh.at/icy_draw.git
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
   DIR commit 80686885bb865c42e087666b89ff99b3369f927c
   DIR parent fc7b5bf795c92df3f66b7e81b7f8ba1af733e04e
  HTML Author: Mike Krüger <mkrueger@posteo.de>
       Date:   Tue, 21 May 2024 17:05:05 +0200
       
       Started a skypix parser.
       
       Skypix looks easy however I need to find a solution for the fonts.
       
       Diffstat:
         M crates/icy_engine/src/formats/colo… |       8 ++++----
         M crates/icy_engine/src/lib.rs        |       1 +
         M crates/icy_engine/src/palette_hand… |      55 +++++++++++++++++++++++++++++--
         M crates/icy_engine/src/parsers/ansi… |       6 +++---
         M crates/icy_engine/src/parsers/mod.… |       2 ++
         M crates/icy_engine/src/parsers/rip/… |      29 +++++++++++++++++++++++------
         M crates/icy_engine/src/parsers/rip/… |       3 ++-
         A crates/icy_engine/src/parsers/skyp… |     375 +++++++++++++++++++++++++++++++
         M crates/icy_term/Cargo.toml          |       5 ++---
         M crates/icy_term/src/data/addresses… |      24 +++++++++++++++++++-----
         M crates/icy_term/src/ui/terminal_th… |       5 +++++
         M crates/icy_term/src/ui/util/screen… |      27 ++++++++++++++++++++++-----
       
       12 files changed, 510 insertions(+), 30 deletions(-)
       ---
   DIR diff --git a/crates/icy_engine/src/formats/color_optimization.rs b/crates/icy_engine/src/formats/color_optimization.rs
       @@ -40,17 +40,17 @@ impl ColorOptimizer {
                            let map = self.shape_map.get(&attr_ch.get_font_page()).unwrap();
                            let mut ch = attr_ch.ch;
                            let mut attribute = attr_ch.attribute;
       -                    match *map.get(&attr_ch.ch).unwrap() {
       -                        GlyphShape::Whitespace => {
       +                    match map.get(&attr_ch.ch) {
       +                        Some(&GlyphShape::Whitespace) => {
                                    attribute.set_foreground(cur_attr.get_foreground());
                                    if self.normalize_whitespace && map.contains_key(&' ') {
                                        ch = ' ';
                                    }
                                }
       -                        GlyphShape::Block => {
       +                        Some(&GlyphShape::Block) => {
                                    attribute.set_background(cur_attr.get_background());
                                }
       -                        GlyphShape::Mixed => {}
       +                        _ => {}
                            }
                            layer.set_char((x, y), crate::AttributedChar { ch, attribute });
                            cur_attr = attribute;
   DIR diff --git a/crates/icy_engine/src/lib.rs b/crates/icy_engine/src/lib.rs
       @@ -34,6 +34,7 @@ pub use position::*;
        mod buffers;
        pub use buffers::*;
        
       +#[macro_use]
        mod palette_handling;
        pub use palette_handling::*;
        
   DIR diff --git a/crates/icy_engine/src/palette_handling.rs b/crates/icy_engine/src/palette_handling.rs
       @@ -30,9 +30,9 @@ lazy_static::lazy_static! {
        pub struct Color {
            #[serde(skip_serializing)]
            pub name: Option<String>,
       -    r: u8,
       -    g: u8,
       -    b: u8,
       +    pub(crate) r: u8,
       +    pub(crate) g: u8,
       +    pub(crate) b: u8,
        }
        
        impl Display for Color {
       @@ -3999,6 +3999,55 @@ pub const VIEWDATA_PALETTE: [Color; 16] = [
            }, // white
        ];
        
       +macro_rules! amiga_color {
       +    ($r:expr, $g:expr, $b:expr) => {
       +        Color {
       +            name: None,
       +            r: (($r as usize * 255) / 15) as u8,
       +            g: (($g as usize * 255) / 15) as u8,
       +            b: (($b as usize * 255) / 15) as u8,
       +        }
       +    };
       +}
       +
       +pub const AMIGA_PALETTE: [Color; 16] = [
       +    amiga_color!(00, 00, 00), // Black
       +    amiga_color!(10, 00, 00), // Red
       +    amiga_color!(00, 10, 00), // Green
       +    amiga_color!(10, 06, 00), // Orange
       +    amiga_color!(00, 00, 10), // Dark Blue
       +    amiga_color!(10, 00, 10), // Violet
       +    amiga_color!(00, 10, 10), // Cyan
       +    amiga_color!(11, 11, 11), // Light Gray
       +    amiga_color!(06, 06, 06), // Dark Gray
       +    amiga_color!(15, 00, 00), // Bright Red
       +    amiga_color!(00, 15, 00), // Bright Green
       +    amiga_color!(15, 15, 00), // Yellow
       +    amiga_color!(00, 00, 15), // Bright Blue
       +    amiga_color!(15, 00, 15), // Bright Violet
       +    amiga_color!(00, 15, 00), // Bright Cyan
       +    amiga_color!(15, 15, 15), // White
       +];
       +
       +pub const SKYPIX_PALETTE: [Color; 16] = [
       +    amiga_color!(00, 00, 00),
       +    amiga_color!(01, 01, 15),
       +    amiga_color!(13, 13, 13),
       +    amiga_color!(15, 00, 00),
       +    amiga_color!(00, 15, 01),
       +    amiga_color!(03, 10, 15),
       +    amiga_color!(15, 15, 02),
       +    amiga_color!(12, 00, 14),
       +    amiga_color!(00, 11, 06),
       +    amiga_color!(00, 13, 13),
       +    amiga_color!(00, 10, 15),
       +    amiga_color!(00, 07, 12),
       +    amiga_color!(00, 00, 15),
       +    amiga_color!(07, 00, 15),
       +    amiga_color!(12, 00, 14),
       +    amiga_color!(12, 00, 08),
       +];
       +
        fn convert_vector(temp2: f32, temp1: f32, mut x: f32) -> u8 {
            if x < 0.0 {
                x += 1.0;
   DIR diff --git a/crates/icy_engine/src/parsers/ansi/mod.rs b/crates/icy_engine/src/parsers/ansi/mod.rs
       @@ -143,6 +143,7 @@ pub struct Parser {
            pub parse_string: String,
            pub macro_dcs: String,
            pub bs_is_ctrl_char: bool,
       +    pub got_skypix_sequence: bool,
        }
        
        impl Default for Parser {
       @@ -165,6 +166,7 @@ impl Default for Parser {
                    last_char: '\0',
                    hyper_links: Vec::new(),
                    bs_is_ctrl_char: false,
       +            got_skypix_sequence: false,
                }
            }
        }
       @@ -1132,9 +1134,7 @@ impl BufferParser for Parser {
                            '!' => {
                                if !is_start {
                                    self.state = EngineState::Default;
       -                            return Err(ParserError::UnsupportedEscapeSequence(
       -                                self.current_escape_sequence.clone(),
       -                            ).into());
       +                            return Ok(CallbackAction::RunSkypixSequence(self.parsed_numbers.clone()));
                                }
                                // read custom command
                                self.state = EngineState::ReadRIPSupportRequest;
   DIR diff --git a/crates/icy_engine/src/parsers/mod.rs b/crates/icy_engine/src/parsers/mod.rs
       @@ -19,6 +19,7 @@ pub mod pcboard;
        pub mod petscii;
        pub mod renegade;
        pub mod rip;
       +pub mod skypix;
        pub mod viewdata;
        
        pub const BEL: char = '\x07';
       @@ -32,6 +33,7 @@ pub enum CallbackAction {
            Update,
            NoUpdate,
            Beep,
       +    RunSkypixSequence(Vec<i32>),
            SendString(String),
            PlayMusic(AnsiMusic),
            ChangeBaudEmulation(ansi::BaudEmulation),
   DIR diff --git a/crates/icy_engine/src/parsers/rip/bgi/mod.rs b/crates/icy_engine/src/parsers/rip/bgi/mod.rs
       @@ -255,8 +255,6 @@ impl FontType {
            }
        }
        
       -const SCREEN_SIZE: Size = Size { width: 640, height: 350 };
       -
        const DEFAULT_USER_PATTERN: [u8; 8] = [0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55];
        const RAD2DEG: f64 = 180.0 / consts::PI;
        const DEG2RAD: f64 = consts::PI / 180.0;
       @@ -520,7 +518,7 @@ impl MouseField {
        }
        
        impl Bgi {
       -    pub fn new(file_path: PathBuf) -> Bgi {
       +    pub fn new(screen_size: Size, file_path: PathBuf) -> Bgi {
                Bgi {
                    color: 7,
                    bkcolor: 0,
       @@ -532,11 +530,11 @@ impl Bgi {
                    fill_color: 0,
                    direction: Direction::Horizontal,
                    font: FontType::Default,
       -            window: SCREEN_SIZE,
       -            viewport: Rectangle::from(0, 0, SCREEN_SIZE.width, SCREEN_SIZE.height),
       +            window: screen_size,
       +            viewport: Rectangle::from(0, 0, screen_size.width, screen_size.height),
                    palette: Palette::dos_default(),
                    line_thickness: 1,
       -            screen: vec![0; (SCREEN_SIZE.width * SCREEN_SIZE.height) as usize],
       +            screen: vec![0; (screen_size.width * screen_size.height) as usize],
                    current_pos: Position::new(0, 0),
                    char_size: 4,
                    rip_image: None,
       @@ -1716,6 +1714,25 @@ impl Bgi {
        
                self.set_write_mode(old_wm);
            }
       +    pub fn put_image2(&mut self, src_x: i32, src_y: i32, width: i32, height: i32, x: i32, y: i32, image: &Image, op: WriteMode) {
       +        let old_wm = self.get_write_mode();
       +        self.set_write_mode(op);
       +
       +        for iy in src_y..src_y + height {
       +            for ix in src_x..src_x + width {
       +                let col = image.data[ix as usize + (iy * image.width) as usize];
       +
       +                let x = x + ix;
       +                let y = y + iy;
       +                if !self.viewport.contains(x, y) {
       +                    continue;
       +                }
       +                self.put_pixel(x, y, col);
       +            }
       +        }
       +
       +        self.set_write_mode(old_wm);
       +    }
        
            pub fn set_text_window(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, wrap: bool) {
                self.text_window = Some(Rectangle::from(x1, y1, x2 - x1, y2 - y1));
   DIR diff --git a/crates/icy_engine/src/parsers/rip/mod.rs b/crates/icy_engine/src/parsers/rip/mod.rs
       @@ -69,6 +69,7 @@ pub struct Parser {
            pub bgi: Bgi,
            pub record_rip_commands: bool,
        }
       +const RIP_SCREEN_SIZE: Size = Size { width: 640, height: 350 };
        
        impl Parser {
            pub fn new(fallback_parser: Box<ansi::Parser>, file_path: PathBuf) -> Self {
       @@ -82,7 +83,7 @@ impl Parser {
                    _current_write_mode: WriteMode::Normal,
                    rip_commands: Vec::new(),
                    command: None,
       -            bgi: Bgi::new(file_path),
       +            bgi: Bgi::new(RIP_SCREEN_SIZE, file_path),
                    last_rip_update: 0,
                    record_rip_commands: false,
                    rip_counter: 0,
   DIR diff --git a/crates/icy_engine/src/parsers/skypix/mod.rs b/crates/icy_engine/src/parsers/skypix/mod.rs
       @@ -0,0 +1,375 @@
       +use std::path::PathBuf;
       +
       +use super::{ansi, BufferParser};
       +use crate::{
       +    rip::bgi::{Bgi, Image, WriteMode},
       +    Buffer, CallbackAction, Caret, Color, EngineResult, Palette, Size, SKYPIX_PALETTE,
       +};
       +const SKYPIX_SCREEN_SIZE: Size = Size { width: 640, height: 200 };
       +
       +pub enum SkypixParseMode {
       +    Default,
       +    ParseFont(i32),
       +    ParseXModemTransfer(i32, i32, i32),
       +}
       +
       +#[derive(Clone, Copy, PartialEq)]
       +pub enum DisplayMode {
       +    BitPlanes3,
       +    BitPlanes4,
       +}
       +
       +pub struct Parser {
       +    fallback_parser: Box<ansi::Parser>,
       +    pub bgi: Bgi,
       +    display_mode: DisplayMode,
       +    brush: Image,
       +    parse_mode: SkypixParseMode,
       +    parameter: String,
       +    last_cmd_update: i32,
       +    cmd_counter: i32,
       +}
       +
       +impl Parser {
       +    pub fn new(fallback_parser: Box<ansi::Parser>, file_path: PathBuf) -> Self {
       +        Self {
       +            fallback_parser,
       +            bgi: Bgi::new(SKYPIX_SCREEN_SIZE, file_path),
       +            display_mode: DisplayMode::BitPlanes4,
       +            parse_mode: SkypixParseMode::Default,
       +            parameter: String::new(),
       +            last_cmd_update: -1,
       +            cmd_counter: 0,
       +            brush: Image {
       +                width: 0,
       +                height: 0,
       +                data: Vec::new(),
       +            },
       +        }
       +    }
       +
       +    fn run_skypix_sequence(&mut self, cmd: i32, parameters: &[i32], buf: &mut Buffer, caret: &mut Caret) -> EngineResult<CallbackAction> {
       +        self.cmd_counter += 1;
       +        println!(" run cmd {cmd} = par {parameters:?}");
       +        match cmd {
       +            1 => {
       +                // SET_PIXEL
       +                if parameters.len() != 2 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command SET_PIXEL"));
       +                }
       +                let x = parameters[0];
       +                let y = parameters[1];
       +                self.bgi.put_pixel(x, y, self.bgi.get_color());
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            2 => {
       +                // DRAW_LINE
       +                if parameters.len() != 2 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command DRAW_LINE"));
       +                }
       +                let x = parameters[0];
       +                let y = parameters[1];
       +                self.bgi.line_to(x, y);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            3 => {
       +                // AREA_FILL
       +                if parameters.len() != 3 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command AREA_FILL"));
       +                }
       +                // TODO: mode
       +                let _mode = parameters[0];
       +                let x = parameters[1];
       +                let y = parameters[2];
       +                self.bgi.flood_fill(x, y, self.bgi.get_color());
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            4 => {
       +                // RECTANGLE_FILL
       +                if parameters.len() != 4 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command RECTANGLE_FILL"));
       +                }
       +                let x1 = parameters[0];
       +                let y1 = parameters[1];
       +                let x2 = parameters[2];
       +                let y2 = parameters[3];
       +                self.bgi.bar(x1, y1, x2, y2);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            5 => {
       +                // ELLIPSE
       +                if parameters.len() != 4 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command ELLIPSE"));
       +                }
       +                let x1 = parameters[0];
       +                let y1 = parameters[1];
       +                let a = parameters[2];
       +                let b = parameters[3];
       +                self.bgi.ellipse(x1, y1, 0, 360, a, b);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            6 => {
       +                // GRAB_BRUSH
       +                if parameters.len() != 4 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command GRAB_BRUSH"));
       +                }
       +                let x1 = parameters[0];
       +                let y1 = parameters[1];
       +                let x2 = parameters[2];
       +                let y2 = parameters[3];
       +                self.brush = self.bgi.get_image(x1, y1, x2, y2);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            7 => {
       +                // USE_BRUSH
       +                if parameters.len() != 8 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command USE_BRUSH"));
       +                }
       +                let src_x = parameters[0];
       +                let src_y = parameters[1];
       +                let dst_x = parameters[2];
       +                let dst_y = parameters[3];
       +                let width = parameters[4];
       +                let height = parameters[5];
       +                let _minterm = parameters[6];
       +                let _mask = parameters[7];
       +                self.bgi.put_image2(src_x, src_y, width, height, dst_x, dst_y, &self.brush, WriteMode::Copy);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            8 => {
       +                // MOVE_PEN
       +                if parameters.len() != 2 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command MOVE_PEN"));
       +                }
       +                let x = parameters[0];
       +                let y = parameters[1];
       +                self.bgi.move_to(x, y);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            9 => {
       +                // PLAY_SAMPLE
       +                if parameters.len() != 4 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command PLAY_SAMPLE"));
       +                }
       +                // not implemented originally, so we just ignore it
       +                log::info!("todo: SKYPIX_PLAY_SAMPLE");
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            10 => {
       +                // SET_FONT
       +                if parameters.len() != 1 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command SET_FONT"));
       +                }
       +                let size = parameters[0];
       +                self.parameter.clear();
       +                self.parse_mode = SkypixParseMode::ParseFont(size);
       +
       +                log::warn!("todo: SET_FONT");
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            11 => {
       +                // NEW_PALETTE
       +                if parameters.len() != 16 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command NEW_PALETTE"));
       +                }
       +                let mut palette = Palette::new();
       +                for i in 0..16 {
       +                    let r = parameters[i] & 0xF;
       +                    let g = (parameters[i] >> 4) & 0xF;
       +                    let b = (parameters[i] >> 8) & 0xF;
       +
       +                    palette.set_color(i as u32, amiga_color!(r, g, b));
       +                }
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +
       +            12 => {
       +                // RESET_PALETTE
       +                buf.palette = Palette::from_slice(&SKYPIX_PALETTE);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +
       +            13 => {
       +                // FILLED_ELLIPSE
       +                if parameters.len() != 4 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command FILLED_ELLIPSE"));
       +                }
       +                let x1 = parameters[0];
       +                let y1 = parameters[1];
       +                let a = parameters[2];
       +                let b = parameters[3];
       +                self.bgi.fill_ellipse(x1, y1, 0, 360, a, b);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +
       +            14 => {
       +                // DELAY
       +                if parameters.len() != 1 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command DELAY"));
       +                }
       +                let t = parameters[0];
       +                std::thread::sleep(std::time::Duration::from_millis((1000 * t as u64) / 60));
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +
       +            15 => {
       +                // SET COLOUR OF PEN A
       +                if parameters.len() != 1 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command SET COLOUR OF PEN A"));
       +                }
       +                let col = parameters[0] as u8;
       +                self.bgi.set_color(col);
       +                caret.set_foreground(col as u32);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +
       +            16 => {
       +                // XMODEM TRANSFER
       +                if parameters.len() != 3 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command XMODEM_TRANSFER"));
       +                }
       +                let m = parameters[0];
       +                let a = parameters[1];
       +                let b = parameters[2];
       +                self.parameter.clear();
       +                self.parse_mode = SkypixParseMode::ParseXModemTransfer(m, a, b);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +
       +            17 => {
       +                // SELECT DISPLAY MODE
       +                if parameters.len() != 1 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command SELECT DISPLAY MODE"));
       +                }
       +                let m = parameters[0] as u8;
       +                match m {
       +                    1 => self.display_mode = DisplayMode::BitPlanes3,
       +                    2 => self.display_mode = DisplayMode::BitPlanes4,
       +                    _ => {
       +                        log::warn!("Unknown display mode: {}", m);
       +                    }
       +                }
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +
       +            18 => {
       +                // SET COLOUR OF PEN B
       +                if parameters.len() != 1 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command SET COLOUR OF PEN B"));
       +                }
       +                let col = parameters[0] as u8;
       +                self.bgi.set_bk_color(col);
       +                caret.set_background(col as u32);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +
       +            19 => {
       +                // POSITION CURSOR
       +                if parameters.len() != 2 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command POSITION CURSOR"));
       +                }
       +                let x = (parameters[0] * 80) / SKYPIX_SCREEN_SIZE.width;
       +                let y = (parameters[1] * 25) / SKYPIX_SCREEN_SIZE.height;
       +                caret.set_position_xy(x, y);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +
       +            21 => {
       +                // CONTROLLER RETURN
       +                if parameters.len() != 3 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command POSITION CURSOR"));
       +                }
       +                let _c = parameters[0];
       +                let _x = parameters[1];
       +                let _y = parameters[2];
       +                log::warn!("todo: CONTROLLER RETURN");
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +
       +            22 => {
       +                // DEFINE A SKYPIX GADGET
       +                if parameters.len() != 6 {
       +                    return Err(anyhow::Error::msg("Invalid number of parameters for skypix command DEFINE A SKYPIX GADGET"));
       +                }
       +                let _n = parameters[0];
       +                let _c = parameters[1];
       +                let _x1 = parameters[2];
       +                let _y1 = parameters[3];
       +                let _x2 = parameters[4];
       +                let _y2 = parameters[5];
       +                log::warn!("todo: SKYPIX GADGET");
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            _ => {
       +                return Err(anyhow::Error::msg(format!("unknown skypix command {cmd}")));
       +            }
       +        }
       +    }
       +
       +    fn load_font(&self, parameter: &str, size: i32) {
       +        log::warn!("todo: load_font {parameter} with size {size}");
       +    }
       +}
       +
       +impl Parser {}
       +
       +impl BufferParser for Parser {
       +    fn print_char(&mut self, buf: &mut Buffer, current_layer: usize, caret: &mut Caret, ch: char) -> EngineResult<CallbackAction> {
       +        match self.parse_mode {
       +            SkypixParseMode::ParseFont(size) => {
       +                if ch == '!' || self.parameter.len() > 32 {
       +                    self.load_font(&self.parameter, size);
       +                    self.parse_mode = SkypixParseMode::Default;
       +                    return Ok(CallbackAction::NoUpdate);
       +                }
       +                self.parameter.push(ch);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            SkypixParseMode::ParseXModemTransfer(m, a, b) => {
       +                if ch == '!' || self.parameter.len() > 32 {
       +                    self.parse_mode = SkypixParseMode::Default;
       +                    // For what exactly is this?
       +                    // Skypix looks like a half baked standard
       +                    // As long as it's not defined/unclear I'm not implementing transferring unknown files to the users system
       +                    log::warn!("todo: SKYPIX_XMODEM_TRANSFER {m} {a} {b} {}", self.parameter);
       +                    return Ok(CallbackAction::NoUpdate);
       +                }
       +                self.parameter.push(ch);
       +                return Ok(CallbackAction::NoUpdate);
       +            }
       +            _ => match self.fallback_parser.print_char(buf, current_layer, caret, ch) {
       +                Ok(CallbackAction::RunSkypixSequence(sequence)) => {
       +                    if sequence.len() == 0 {
       +                        return Err(anyhow::Error::msg("Empty sequence"));
       +                    }
       +                    return self.run_skypix_sequence(sequence[0], &sequence[1..], buf, caret);
       +                }
       +                x => x,
       +            },
       +        }
       +    }
       +
       +    fn get_picture_data(&mut self) -> Option<(Size, Vec<u8>)> {
       +        if self.last_cmd_update == self.cmd_counter {
       +            return None;
       +        }
       +        self.last_cmd_update = self.cmd_counter;
       +        let mut pixels = Vec::new();
       +        let pal = self.bgi.get_palette().clone();
       +        for i in &self.bgi.screen {
       +            if *i == 0 {
       +                pixels.push(0);
       +                pixels.push(0);
       +                pixels.push(0);
       +                pixels.push(0);
       +                continue;
       +            }
       +            let (r, g, b) = pal.get_rgb(*i as u32);
       +            pixels.push(r);
       +            pixels.push(g);
       +            pixels.push(b);
       +            pixels.push(255);
       +        }
       +        Some((self.bgi.window, pixels))
       +    }
       +}
   DIR diff --git a/crates/icy_term/Cargo.toml b/crates/icy_term/Cargo.toml
       @@ -8,8 +8,8 @@ description = "A terminal program supporting CP437, PetScii and ViewData"
        repository = "https://github.com/mkrueger/icy_tools"
        
        [dependencies]
       -icy_net = { git ="https://github.com/mkrueger/icy_board" }
       -#icy_net = { path ="../../../icy_board/crates/icy_net" }
       +#icy_net = { git ="https://github.com/mkrueger/icy_board" }
       +icy_net = { path ="../../../icy_board/crates/icy_net" }
        
        egui = { workspace = true }
        eframe = { workspace = true }
       @@ -73,7 +73,6 @@ directories = { workspace = true }
        open = "5.0.0"
        i18n-embed = { version = "0.14.0", features = ["fluent-system", "desktop-requester"]}
        tracing-subscriber = "0.3"
       -libssh-rs =  { version = "0.2.0", features = ["vendored", "vendored-openssl"] }
        
        # web:
        [target.'cfg(target_arch = "wasm32")'.dependencies]
   DIR diff --git a/crates/icy_term/src/data/addresses.rs b/crates/icy_term/src/data/addresses.rs
       @@ -3,7 +3,7 @@ use crate::TerminalResult;
        use chrono::{Duration, Utc};
        use icy_engine::ansi::{BaudEmulation, MusicOption};
        use icy_engine::igs::CommandExecutor;
       -use icy_engine::{ansi, ascii, atascii, avatar, mode7, petscii, rip, viewdata, BufferParser, UnicodeConverter};
       +use icy_engine::{ansi, ascii, atascii, avatar, mode7, petscii, rip, skypix, viewdata, BufferParser, UnicodeConverter};
        use icy_net::telnet::TerminalEmulation;
        use icy_net::ConnectionType;
        use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
       @@ -19,7 +19,7 @@ use std::{
        use toml::Value;
        use versions::Versioning;
        
       -pub const ALL_TERMINALS: [TerminalEmulation; 9] = [
       +pub const ALL_TERMINALS: [TerminalEmulation; 10] = [
            TerminalEmulation::Ansi,
            TerminalEmulation::Avatar,
            TerminalEmulation::Ascii,
       @@ -27,6 +27,7 @@ pub const ALL_TERMINALS: [TerminalEmulation; 9] = [
            TerminalEmulation::ATAscii,
            TerminalEmulation::ViewData,
            TerminalEmulation::Rip,
       +    TerminalEmulation::Skypix,
            TerminalEmulation::IGS,
            TerminalEmulation::Mode7,
        ];
       @@ -41,6 +42,7 @@ pub fn fmt_terminal_emulation(emulator: &TerminalEmulation) -> &str {
                TerminalEmulation::ViewData => "VIEWDATA",
                TerminalEmulation::Mode7 => "Mode7",
                TerminalEmulation::Rip => "RIPscrip",
       +        TerminalEmulation::Skypix => "Skypix",
                TerminalEmulation::IGS => "IGS (Experimental)",
            }
        }
       @@ -67,6 +69,13 @@ pub fn get_parser(emulator: &TerminalEmulation, use_ansi_music: MusicOption, cac
                    let parser = rip::Parser::new(Box::new(parser), cache_directory);
                    Box::new(parser)
                }
       +        TerminalEmulation::Skypix => {
       +            let mut parser = ansi::Parser::default();
       +            parser.ansi_music = use_ansi_music;
       +            parser.bs_is_ctrl_char = true;
       +            let parser = skypix::Parser::new(Box::new(parser), cache_directory);
       +            Box::new(parser)
       +        }
                TerminalEmulation::IGS => {
                    let ig_executor: Arc<std::sync::Mutex<Box<dyn CommandExecutor>>> =
                        Arc::new(std::sync::Mutex::new(Box::<icy_engine::parsers::igs::DrawExecutor>::default()));
       @@ -78,9 +87,12 @@ pub fn get_parser(emulator: &TerminalEmulation, use_ansi_music: MusicOption, cac
        #[must_use]
        pub fn get_unicode_converter(emulator: &TerminalEmulation) -> Box<dyn UnicodeConverter> {
            match emulator {
       -        TerminalEmulation::Ansi | TerminalEmulation::Avatar | TerminalEmulation::Ascii | TerminalEmulation::Rip | TerminalEmulation::IGS => {
       -            Box::<ascii::CP437Converter>::default()
       -        }
       +        TerminalEmulation::Ansi
       +        | TerminalEmulation::Avatar
       +        | TerminalEmulation::Ascii
       +        | TerminalEmulation::Rip
       +        | TerminalEmulation::Skypix
       +        | TerminalEmulation::IGS => Box::<ascii::CP437Converter>::default(),
                TerminalEmulation::PETscii => Box::<petscii::CharConverter>::default(),
                TerminalEmulation::ATAscii => Box::<atascii::CharConverter>::default(),
                TerminalEmulation::ViewData => Box::<viewdata::CharConverter>::default(),
       @@ -572,6 +584,7 @@ fn parse_address(value: &Value) -> Address {
                        "atascii" => result.terminal_type = TerminalEmulation::ATAscii,
                        "viewdata" => result.terminal_type = TerminalEmulation::ViewData,
                        "rip" => result.terminal_type = TerminalEmulation::Rip,
       +                "skypix" => result.terminal_type = TerminalEmulation::Skypix,
                        "igs" => result.terminal_type = TerminalEmulation::IGS,
                        "mode7" => result.terminal_type = TerminalEmulation::Mode7,
                        _ => {}
       @@ -596,6 +609,7 @@ fn parse_address(value: &Value) -> Address {
                        "antic" => result.screen_mode = ScreenMode::Antic,
                        "videotex" => result.screen_mode = ScreenMode::Videotex,
                        "rip" => result.screen_mode = ScreenMode::Rip,
       +                "skypix" => result.screen_mode = ScreenMode::SkyPix,
                        "igs" => result.screen_mode = ScreenMode::Igs,
                        "mode7" => result.screen_mode = ScreenMode::Mode7,
                        _ => {
   DIR diff --git a/crates/icy_term/src/ui/terminal_thread.rs b/crates/icy_term/src/ui/terminal_thread.rs
       @@ -118,6 +118,11 @@ impl TerminalThread {
                        return (true, 0);
                    }
        
       +            icy_engine::CallbackAction::RunSkypixSequence(seq) => {
       +                log::error!("unsupported skypix sequence: {:?}", seq);
       +                return (false, 0);
       +            }
       +
                    icy_engine::CallbackAction::NoUpdate => {
                        return (false, 0);
                    }
   DIR diff --git a/crates/icy_term/src/ui/util/screen_modes.rs b/crates/icy_term/src/ui/util/screen_modes.rs
       @@ -1,7 +1,8 @@
        use std::fmt::Display;
        
        use icy_engine::{
       -    BitFont, Color, Palette, Size, ATARI, ATARI_DEFAULT_PALETTE, C64_DEFAULT_PALETTE, C64_LOWER, C64_UPPER, CP437, IBM_VGA50_SAUCE, VIEWDATA, VIEWDATA_PALETTE,
       +    BitFont, Color, Palette, Size, ATARI, ATARI_DEFAULT_PALETTE, C64_DEFAULT_PALETTE, C64_LOWER, C64_UPPER, CP437, IBM_VGA50_SAUCE, SKYPIX_PALETTE, VIEWDATA,
       +    VIEWDATA_PALETTE,
        };
        use icy_engine_gui::BufferInputMode;
        
       @@ -20,6 +21,7 @@ pub enum ScreenMode {
            Videotex,
            Mode7,
            Rip,
       +    SkyPix,
            Igs,
        }
        
       @@ -38,7 +40,7 @@ impl ScreenMode {
            }
        }
        
       -pub const DEFAULT_MODES: [ScreenMode; 14] = [
       +pub const DEFAULT_MODES: [ScreenMode; 15] = [
            ScreenMode::Vga(80, 25),
            ScreenMode::Vga(80, 50),
            ScreenMode::Vga(132, 37),
       @@ -51,6 +53,7 @@ pub const DEFAULT_MODES: [ScreenMode; 14] = [
            ScreenMode::Default,
            ScreenMode::Rip,
            ScreenMode::Videotex,
       +    ScreenMode::SkyPix,
            ScreenMode::Igs,
            ScreenMode::Mode7,
        ];
       @@ -72,6 +75,7 @@ impl Display for ScreenMode {
                    ScreenMode::Videotex => write!(f, "VIDEOTEX"),
                    ScreenMode::Default => write!(f, "Default"),
                    ScreenMode::Rip => write!(f, "RIPscrip"),
       +            ScreenMode::SkyPix => write!(f, "SkyPix"),
                    ScreenMode::Igs => write!(f, "Igs"),
                    ScreenMode::Mode7 => write!(f, "Mode7"),
                }
       @@ -82,7 +86,7 @@ impl ScreenMode {
            pub fn get_input_mode(&self) -> BufferInputMode {
                match self {
                    //ScreenMode::Cga(_, _) | ScreenMode::Ega(_, _) |
       -            ScreenMode::Default | ScreenMode::Vga(_, _) | ScreenMode::Rip | ScreenMode::Igs => BufferInputMode::CP437,
       +            ScreenMode::Default | ScreenMode::Vga(_, _) | ScreenMode::Rip | ScreenMode::SkyPix | ScreenMode::Igs => BufferInputMode::CP437,
                    ScreenMode::Vic => BufferInputMode::PETscii,
                    ScreenMode::Antic => BufferInputMode::ATAscii,
                    ScreenMode::Videotex => BufferInputMode::ViewData,
       @@ -98,6 +102,7 @@ impl ScreenMode {
                    ScreenMode::Antic | ScreenMode::Videotex => Size::new(40, 24),
                    ScreenMode::Default => Size::new(80, 25),
                    ScreenMode::Rip => Size::new(80, 44),
       +            ScreenMode::SkyPix => Size::new(80, 25),
                }
            }
        
       @@ -168,6 +173,18 @@ impl ScreenMode {
                            .get_buffer_mut()
                            .set_font(0, BitFont::from_sauce_name("IBM VGA50").unwrap());
                        main_window.buffer_view.lock().get_buffer_mut().palette = Palette::dos_default();
       +                main_window.buffer_view.lock().get_buffer_mut().is_terminal_buffer = true;
       +            }
       +
       +            ScreenMode::SkyPix => {
       +                main_window.buffer_view.lock().get_buffer_mut().clear_font_table();
       +                main_window
       +                    .buffer_view
       +                    .lock()
       +                    .get_buffer_mut()
       +                    .set_font(0, BitFont::from_sauce_name("IBM VGA50").unwrap());
       +                main_window.buffer_view.lock().get_buffer_mut().palette = Palette::from_slice(&SKYPIX_PALETTE);
       +                main_window.buffer_view.lock().get_buffer_mut().is_terminal_buffer = true;
                    }
        
                    ScreenMode::Igs => {
       @@ -188,7 +205,7 @@ impl ScreenMode {
            #[allow(clippy::match_same_arms)]
            pub(crate) fn get_selection_fg(&self) -> Color {
                match self {
       -            ScreenMode::Default | ScreenMode::Vga(_, _) | ScreenMode::Rip => Color::new(0xAA, 0x00, 0xAA),
       +            ScreenMode::Default | ScreenMode::Vga(_, _) | ScreenMode::Rip | ScreenMode::SkyPix => Color::new(0xAA, 0x00, 0xAA),
                    ScreenMode::Vic => Color::new(0x37, 0x39, 0xC4),
                    ScreenMode::Antic => Color::new(0x09, 0x51, 0x83),
                    ScreenMode::Videotex | ScreenMode::Mode7 => Color::new(0, 0, 0),
       @@ -199,7 +216,7 @@ impl ScreenMode {
            #[allow(clippy::match_same_arms)]
            pub(crate) fn get_selection_bg(&self) -> Color {
                match self {
       -            ScreenMode::Default | ScreenMode::Vga(_, _) | ScreenMode::Rip => Color::new(0xAA, 0xAA, 0xAA),
       +            ScreenMode::Default | ScreenMode::Vga(_, _) | ScreenMode::Rip | ScreenMode::SkyPix => Color::new(0xAA, 0xAA, 0xAA),
                    ScreenMode::Vic => Color::new(0xB0, 0x3F, 0xB6),
                    ScreenMode::Antic => Color::new(0xFF, 0xFF, 0xFF),
                    ScreenMode::Videotex | ScreenMode::Mode7 => Color::new(0xFF, 0xFF, 0xFF),