Basic functionality seems to work!
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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<InitializeIndication>),
|
||||
/// 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<Model>),
|
||||
/// Change in the state of the Operator Information Area
|
||||
Oia(Oia),
|
||||
Oia(OiaField),
|
||||
/// Set of supported prefixes
|
||||
Prefixes {
|
||||
value: String,
|
||||
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
},
|
||||
// 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<String>,
|
||||
// },
|
||||
/// Reverse input mode
|
||||
ReverseInput {
|
||||
value: bool,
|
||||
},
|
||||
/// Screen trace count
|
||||
ScreenTrace {
|
||||
value: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
value: Option<usize>,
|
||||
},
|
||||
/// Host command timer (minutes:seconds)
|
||||
Script {
|
||||
value: bool,
|
||||
},
|
||||
/// Host command timer (minutes:seconds)
|
||||
Timing {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
value: Option<String>,
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Vec<CharCell>>,
|
||||
oia: HashMap<OiaFieldName, Oia>,
|
||||
oia: HashMap<OiaFieldName, OiaField>,
|
||||
screen_mode: ScreenMode,
|
||||
erase: Erase,
|
||||
thumb: Thumb,
|
||||
@@ -26,6 +26,7 @@ pub struct Tracker {
|
||||
trace_file: Option<String>,
|
||||
tls: Option<Tls>,
|
||||
|
||||
oia_tracker: OiaTracker,
|
||||
// These never change, but need to be represented in an initialize message
|
||||
static_init: Vec<InitializeIndication>,
|
||||
}
|
||||
@@ -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<Vec<CharCell>> {
|
||||
&self.screen
|
||||
}
|
||||
|
||||
pub fn get_oia(&self) -> &HashMap<OiaFieldName, OiaField> {
|
||||
&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<String>,
|
||||
/// terminal, printer
|
||||
pub lu: Option<String>,
|
||||
pub not_undera: bool,
|
||||
pub printer_lu: Option<String>,
|
||||
pub reverse_input: bool,
|
||||
pub screen_trace: Option<usize>,
|
||||
pub script: bool,
|
||||
pub timing: Option<String>,
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user