From 736b5b0ca75ac481ea56529beb894177cc0db924 Mon Sep 17 00:00:00 2001 From: TQ Hirsch Date: Fri, 12 May 2023 00:23:39 +0200 Subject: [PATCH] Basic functionality seems to work! --- Cargo.lock | 181 ++++++++++++++++++++++++++- Cargo.toml | 3 +- d3270-common/Cargo.toml | 1 + d3270-common/src/b3270.rs | 7 +- d3270-common/src/b3270/indication.rs | 39 +++--- d3270-common/src/b3270/types.rs | 7 +- d3270-common/src/tracker.rs | 122 ++++++++++++++---- 7 files changed, 307 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39fcdfe..3a72d89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.71" @@ -312,6 +321,17 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -463,6 +483,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term 0.12.1", + "atty", + "bitflags 1.3.2", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -541,6 +576,32 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "crossterm" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "futures-core", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-mac" version = "0.8.0" @@ -632,6 +693,20 @@ dependencies = [ "bitflags 2.2.1", "serde", "serde_json", + "tracing", +] + +[[package]] +name = "d3270console" +version = "0.1.0" +dependencies = [ + "anyhow", + "crossterm", + "d3270-common", + "futures", + "serde_json", + "structopt", + "tokio", ] [[package]] @@ -905,6 +980,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -1356,6 +1449,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -1681,6 +1798,17 @@ dependencies = [ "signal-hook-registry", ] +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1797,6 +1925,36 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "subtle" version = "2.5.0" @@ -1843,6 +2001,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.40" @@ -2092,7 +2259,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "192ca16595cdd0661ce319e8eede9c975f227cdaabc4faaefdc256f43d852e45" dependencies = [ - "ansi_term", + "ansi_term 0.11.0", "chrono", "lazy_static", "matchers 0.0.1", @@ -2168,6 +2335,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.10" @@ -2222,6 +2395,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index c7a9b09..aec87c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,6 @@ members = [ "d3270-common", "d3270d", - "qt3270" + "qt3270", + "d3270console", ] diff --git a/d3270-common/Cargo.toml b/d3270-common/Cargo.toml index 79bfbc2..22a9434 100644 --- a/d3270-common/Cargo.toml +++ b/d3270-common/Cargo.toml @@ -11,6 +11,7 @@ serde_json = "1.0.96" #serde_cbor = "0.11.2" anyhow = "1.0.71" bitflags = "2.2.1" +tracing = "0.1.37" # deps for bins #structopt = "0.3.26" diff --git a/d3270-common/src/b3270.rs b/d3270-common/src/b3270.rs index 9997680..4675012 100644 --- a/d3270-common/src/b3270.rs +++ b/d3270-common/src/b3270.rs @@ -1,10 +1,11 @@ use indication::{ - CodePage, ConnectAttempt, Connection, Erase, FileTransfer, Hello, Model, Oia, Passthru, Popup, + CodePage, ConnectAttempt, Connection, Erase, FileTransfer, Hello, Model, Passthru, Popup, Proxy, RunResult, Screen, ScreenMode, Scroll, Setting, Stats, TerminalName, Thumb, Tls, TlsHello, TraceFile, UiError, }; use operation::{Fail, Register, Run, Succeed}; use serde::{Deserialize, Serialize}; +use crate::b3270::indication::OiaField; pub mod indication; pub mod operation; @@ -44,7 +45,7 @@ pub enum Indication { /// The first message sent Initialize(Vec), /// Change in the state of the Operator Information Area - Oia(Oia), + Oia(OiaField), /// A passthru action has been invoked. /// Clients must respond with a succeed or fail operation Passthru(Passthru), @@ -89,7 +90,7 @@ pub enum InitializeIndication { /// Indicates which 3270 models are supported Models(Vec), /// Change in the state of the Operator Information Area - Oia(Oia), + Oia(OiaField), /// Set of supported prefixes Prefixes { value: String, diff --git a/d3270-common/src/b3270/indication.rs b/d3270-common/src/b3270/indication.rs index 0ad23d0..feb90ad 100644 --- a/d3270-common/src/b3270/indication.rs +++ b/d3270-common/src/b3270/indication.rs @@ -96,15 +96,6 @@ pub struct Model { pub columns: u8, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -// This could be more typesafe, probably ¯\_(ツ)_/¯ -pub struct Oia { - #[serde(flatten)] - pub field: OiaField, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub lu: Option, -} - #[derive(Serialize, Deserialize)] #[serde(rename_all = "kebab-case", tag = "field")] #[derive(Debug, PartialEq, Clone)] @@ -137,25 +128,26 @@ pub enum OiaField { NotUndera { value: bool, }, - PrinterSession { - value: bool, - /// Printer session LU name - // TODO: determine if this is sent with this message or with Lu - #[serde(default, skip_serializing_if = "Option::is_none")] - lu: Option, - }, + // PrinterSession { + // value: bool, + // /// Printer session LU name + // // TODO: determine if this is sent with this message or with Lu + // #[serde(default, skip_serializing_if = "Option::is_none")] + // lu: Option, + // }, /// Reverse input mode ReverseInput { value: bool, }, /// Screen trace count ScreenTrace { - value: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + value: Option, }, - /// Host command timer (minutes:seconds) Script { value: bool, }, + /// Host command timer (minutes:seconds) Timing { #[serde(default, skip_serializing_if = "Option::is_none")] value: Option, @@ -188,7 +180,7 @@ impl OiaField { OiaField::Lock { .. } => OiaFieldName::Lock, OiaField::Lu { .. } => OiaFieldName::Lu, OiaField::NotUndera { .. } => OiaFieldName::NotUndera, - OiaField::PrinterSession { .. } => OiaFieldName::PrinterSession, + // OiaField::PrinterSession { .. } => OiaFieldName::PrinterSession, OiaField::ReverseInput { .. } => OiaFieldName::ReverseInput, OiaField::ScreenTrace { .. } => OiaFieldName::ScreenTrace, OiaField::Script { .. } => OiaFieldName::Script, @@ -336,6 +328,15 @@ pub enum CountOrText { Text(String), } +impl CountOrText { + pub fn len(&self) -> usize { + match self { + CountOrText::Count(n) => *n, + CountOrText::Text(text) => text.chars().count(), + } + } +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct Change { pub column: u8, diff --git a/d3270-common/src/b3270/types.rs b/d3270-common/src/b3270/types.rs index 491304d..2487da1 100644 --- a/d3270-common/src/b3270/types.rs +++ b/d3270-common/src/b3270/types.rs @@ -36,6 +36,9 @@ static FLAG_NAMES: &'static [(GraphicRendition, &'static str)] = &[ impl Display for GraphicRendition { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if *self == Self::empty() { + return f.write_str("default"); + } let flag_names = FLAG_NAMES .iter() .filter_map(|(val, name)| self.contains(*val).then_some(*name)); @@ -106,7 +109,7 @@ impl FromStr for GraphicRendition { .iter() .find(|(_, name)| *name == attr) .map(|x| x.0) - .ok_or_else(|| format!("Invalid attr name {attr}")) + .ok_or_else(|| format!("Invalid GR attr name {attr}")) }) .collect() } @@ -241,7 +244,7 @@ impl PackedAttr for u32 { } fn c_setbg(self, bg: Color) -> Self { - self & !0xF0000 | (u8::from(bg) as u32) << 20 + self & !0xF00000 | (u8::from(bg) as u32) << 20 } fn c_pack(fg: Color, bg: Color, gr: GraphicRendition) -> Self { diff --git a/d3270-common/src/tracker.rs b/d3270-common/src/tracker.rs index 402730a..3f6d208 100644 --- a/d3270-common/src/tracker.rs +++ b/d3270-common/src/tracker.rs @@ -1,19 +1,19 @@ -use crate::b3270::indication::{ - Change, Connection, ConnectionState, CountOrText, Cursor, Erase, Oia, OiaFieldName, Row, - RunResult, Screen, ScreenMode, Scroll, Setting, Thumb, Tls, TraceFile, -}; +use std::collections::HashMap; +use tracing::warn; + +use crate::b3270::indication::{Change, ComposeType, Connection, ConnectionState, CountOrText, Cursor, Erase, OiaField, OiaFieldName, Row, RunResult, Screen, ScreenMode, Scroll, Setting, Thumb, Tls, TraceFile}; use crate::b3270::types::{Color, GraphicRendition, PackedAttr}; use crate::b3270::{Indication, InitializeIndication}; -use std::collections::HashMap; +use crate::b3270::types::Color::{NeutralBlack, NeutralWhite}; #[derive(Copy, Clone, Debug)] -struct CharCell { +pub struct CharCell { pub ch: char, pub attr: u32, } pub struct Tracker { screen: Vec>, - oia: HashMap, + oia: HashMap, screen_mode: ScreenMode, erase: Erase, thumb: Thumb, @@ -26,6 +26,7 @@ pub struct Tracker { trace_file: Option, tls: Option, + oia_tracker: OiaTracker, // These never change, but need to be represented in an initialize message static_init: Vec, } @@ -55,13 +56,17 @@ impl Tracker { self.connection = conn.clone(); } Indication::Erase(erase) => { - self.erase.logical_cols = erase.logical_cols.or(self.erase.logical_cols); - self.erase.logical_rows = erase.logical_rows.or(self.erase.logical_rows); - self.erase.fg = erase.fg.or(self.erase.fg); - self.erase.bg = erase.bg.or(self.erase.bg); + erase.fg = erase.fg.or(self.erase.fg).or(Some(NeutralWhite)); + erase.bg = erase.bg.or(self.erase.bg).or(Some(NeutralBlack)); + erase.logical_rows = erase.logical_rows.or(self.erase.logical_rows).or(Some(self.screen_mode.rows)); + erase.logical_cols = erase.logical_cols.or(self.erase.logical_cols).or(Some(self.screen_mode.columns)); + self.erase.logical_cols = erase.logical_cols; + self.erase.logical_rows = erase.logical_rows; + self.erase.fg = erase.fg; + self.erase.bg = erase.bg; - let rows = self.erase.logical_rows.unwrap_or(self.screen_mode.rows) as usize; - let cols = self.erase.logical_cols.unwrap_or(self.screen_mode.columns) as usize; + let rows = self.erase.logical_rows.unwrap() as usize; + let cols = self.erase.logical_cols.unwrap() as usize; self.screen = vec![ vec![ @@ -119,7 +124,8 @@ impl Tracker { } } Indication::Oia(oia) => { - self.oia.insert(oia.field.field_name(), oia.clone()); + self.oia.insert(oia.field_name(), oia.clone()); + self.oia_tracker.notice(oia.clone()); } Indication::Screen(screen) => { if let Some(cursor) = screen.cursor { @@ -132,19 +138,21 @@ impl Tracker { // update screen contents let cols = self.screen[row_idx].iter_mut().skip(col_idx); match change.change { - CountOrText::Count(n) => cols.take(n).for_each(|cell| { - let mut attr = cell.attr; - if let Some(fg) = change.fg { - attr = attr.c_setfg(fg); - } - if let Some(bg) = change.bg { - attr = attr.c_setbg(bg); - } - if let Some(gr) = change.gr { - attr = attr.c_setgr(gr); - } - cell.attr = attr; - }), + CountOrText::Count(n) => { + cols.take(n).for_each(|cell| { + let mut attr = cell.attr; + if let Some(fg) = change.fg { + attr = attr.c_setfg(fg); + } + if let Some(bg) = change.bg { + attr = attr.c_setbg(bg); + } + if let Some(gr) = change.gr { + attr = attr.c_setgr(gr); + } + cell.attr = attr; + }); + }, CountOrText::Text(ref text) => { cols.zip(text.chars()).for_each(|(cell, ch)| { let mut attr = cell.attr; @@ -283,6 +291,65 @@ impl Tracker { .collect(), } } + + pub fn get_screen(&self) -> &Vec> { + &self.screen + } + + pub fn get_oia(&self) -> &HashMap { + &self.oia + } + + pub fn get_cursor(&self) -> &Cursor { + &self.cursor + } + + pub fn get_oia_state(&self) -> &OiaTracker { + &self.oia_tracker + } + + pub fn get_connection(&self) -> &Connection { &self.connection } +} + +#[derive(Default)] +pub struct OiaTracker { + pub compose: Option<(ComposeType, String)>, + pub insert: bool, + pub lock: Option, + /// terminal, printer + pub lu: Option, + pub not_undera: bool, + pub printer_lu: Option, + pub reverse_input: bool, + pub screen_trace: Option, + pub script: bool, + pub timing: Option, + pub typeahead: bool, +} + +impl OiaTracker { + pub fn notice(&mut self, oia: OiaField) { + match oia { + OiaField::Compose { value: true, type_: Some(typ), char:Some(ch_str) } => { + self.compose = Some((typ, ch_str)) + } + OiaField::Compose { value: false, ..} => self.compose = None, + OiaField::Compose { .. } => warn!(?oia, "Unexpected OIA compose setting"), + OiaField::Insert { value } => self.insert = value, + OiaField::Lock { value } => self.lock = value, + OiaField::Lu { value, lu } => { + self.lu = Some(value); + self.printer_lu = lu; + } + OiaField::NotUndera { value } => self.not_undera = value, + OiaField::ReverseInput { value } => self.reverse_input = value, + OiaField::ScreenTrace { value } => self.screen_trace = value, + OiaField::Script { value } => self.script = value, + OiaField::Timing { value } => self.timing = value, + OiaField::Typeahead { value } => self.typeahead = value, + }; + } + } impl Default for Tracker { @@ -326,6 +393,7 @@ impl Default for Tracker { trace_file: None, tls: None, static_init: vec![], + oia_tracker: OiaTracker::default(), }; ret }