diff --git a/d3270-common/src/b3270.rs b/d3270-common/src/b3270.rs index b23fc9c..7af56b2 100644 --- a/d3270-common/src/b3270.rs +++ b/d3270-common/src/b3270.rs @@ -1,14 +1,18 @@ -use serde::{Deserialize, Serialize}; -use indication::{CodePage, ConnectAttempt, Connection, Erase, FileTransfer, Hello, Model, Oia, Passthru, Popup, Proxy, RunResult, Screen, ScreenMode, Scroll, Setting, Stats, TerminalName, Thumb, Tls, TlsHello, TraceFile, UiError}; +use indication::{ + CodePage, ConnectAttempt, Connection, Erase, FileTransfer, Hello, Model, Oia, 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}; -pub mod operation; pub mod indication; +pub mod operation; pub mod types; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub enum Indication { - Bell{}, // TODO: make sure this emits/parses {"bell": {}} + Bell {}, // TODO: make sure this emits/parses {"bell": {}} /// Indicates that the host connection has changed state. Connection(Connection), /// A new host connection is being attempted @@ -30,10 +34,10 @@ pub enum Indication { state: bool, }, /// File transfer state change - #[serde(rename="ft")] + #[serde(rename = "ft")] FileTransfer(FileTransfer), /// An XTerm escape sequence requested a new icon name - Icon{ + Icon { text: String, }, /// The first message sent @@ -68,13 +72,13 @@ pub enum Indication { /// Error in b3270's input UiError(UiError), /// Xterm escape sequence requested a change to the window title - WindowTitle{ + WindowTitle { text: String, - } + }, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub enum InitializeIndication { CodePages(Vec), /// Indicates that the host connection has changed state. @@ -88,7 +92,9 @@ pub enum InitializeIndication { /// Change in the state of the Operator Information Area Oia(Oia), /// Set of supported prefixes - Prefixes{value: String}, + Prefixes { + value: String, + }, /// List of supported proxies Proxies(Vec), /// Screen dimensions/characteristics changed @@ -106,7 +112,7 @@ pub enum InitializeIndication { } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub enum Operation { /// Run an action Run(Run), @@ -117,4 +123,3 @@ pub enum Operation { /// Tell b3270 that a passthru action succeeded Succeed(Succeed), } - diff --git a/d3270-common/src/b3270/indication.rs b/d3270-common/src/b3270/indication.rs index 4347459..f5a8a40 100644 --- a/d3270-common/src/b3270/indication.rs +++ b/d3270-common/src/b3270/indication.rs @@ -1,8 +1,8 @@ -use serde::{Deserialize, Serialize}; use crate::b3270::types::{Color, GraphicRendition}; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub enum ActionCause { Command, Default, @@ -22,27 +22,27 @@ pub enum ActionCause { } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub struct CodePage{ +pub struct CodePage { /// The canonical name of the code page pub name: String, - #[serde(default, skip_serializing_if="Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub aliases: Vec, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub struct Connection{ +pub struct Connection { /// New connection state pub state: ConnectionState, /// Host name, if connected - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub host: Option, /// Source of the connection - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub cause: Option, } #[derive(Serialize, Deserialize)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] #[derive(Debug, PartialEq, Copy, Clone)] pub enum ComposeType { Std, @@ -50,7 +50,7 @@ pub enum ComposeType { } #[derive(Serialize, Deserialize)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] #[derive(Debug, PartialEq, Copy, Clone)] pub enum ConnectionState { NotConnected, @@ -65,20 +65,20 @@ pub enum ConnectionState { ConnectedUnbound, ConnectedENvt, ConnectedESscp, - #[serde(rename="connected-e-tn3270e")] + #[serde(rename = "connected-e-tn3270e")] ConnectedETn3270e, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub struct Erase { - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub logical_rows: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub logical_cols: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub fg: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub bg: Option, } @@ -101,34 +101,36 @@ pub struct Model { pub struct Oia { #[serde(flatten)] pub field: OiaField, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub lu: Option, } #[derive(Serialize, Deserialize)] -#[serde(rename_all="kebab-case", tag="field")] +#[serde(rename_all = "kebab-case", tag = "field")] #[derive(Debug, PartialEq, Clone)] pub enum OiaField { /// Composite character in progress - Compose{ + Compose { value: bool, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] char: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] type_: Option, }, /// Insert mode - Insert{value: bool}, - /// Keyboard is locked - Lock{ - #[serde(default, skip_serializing_if="Option::is_none")] - value: Option + Insert { + value: bool, }, - Lu{ + /// Keyboard is locked + Lock { + #[serde(default, skip_serializing_if = "Option::is_none")] + value: Option, + }, + Lu { /// Host session logical unit name value: String, /// Printer session LU name - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] lu: Option, }, /// Communication pending @@ -139,7 +141,7 @@ pub enum OiaField { 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")] + #[serde(default, skip_serializing_if = "Option::is_none")] lu: Option, }, /// Reverse input mode @@ -155,7 +157,7 @@ pub enum OiaField { value: String, }, Timing { - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] value: Option, }, Typeahead { @@ -181,17 +183,17 @@ pub enum OiaFieldName { impl OiaField { pub fn field_name(&self) -> OiaFieldName { match self { - OiaField::Compose {..} => OiaFieldName::Compose, - OiaField::Insert {..} => OiaFieldName::Insert, - OiaField::Lock {..} => OiaFieldName::Lock, - OiaField::Lu {..} => OiaFieldName::Lu, - OiaField::NotUndera {..} => OiaFieldName::NotUndera, - OiaField::PrinterSession {..} => OiaFieldName::PrinterSession, - OiaField::ReverseInput {..} => OiaFieldName::ReverseInput, - OiaField::ScreenTrace {..} => OiaFieldName::ScreenTrace, - OiaField::Script {..} => OiaFieldName::Script, - OiaField::Timing {..} => OiaFieldName::Timing, - OiaField::Typeahead {..} => OiaFieldName::Typeahead, + OiaField::Compose { .. } => OiaFieldName::Compose, + OiaField::Insert { .. } => OiaFieldName::Insert, + OiaField::Lock { .. } => OiaFieldName::Lock, + OiaField::Lu { .. } => OiaFieldName::Lu, + OiaField::NotUndera { .. } => OiaFieldName::NotUndera, + OiaField::PrinterSession { .. } => OiaFieldName::PrinterSession, + OiaField::ReverseInput { .. } => OiaFieldName::ReverseInput, + OiaField::ScreenTrace { .. } => OiaFieldName::ScreenTrace, + OiaField::Script { .. } => OiaFieldName::Script, + OiaField::Timing { .. } => OiaFieldName::Timing, + OiaField::Typeahead { .. } => OiaFieldName::Typeahead, } } } @@ -224,24 +226,24 @@ pub struct ScreenMode { pub struct TlsHello { pub supported: bool, pub provider: String, // docs claim this is always set, but I'm not sure. - #[serde(default, skip_serializing_if="Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub options: Vec, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub struct Tls { pub secure: bool, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub verified: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub session: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub host_cert: Option, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub struct ConnectAttempt { pub host_ip: String, pub port: String, @@ -251,9 +253,9 @@ pub struct ConnectAttempt { // TODO: change this to an enum pub struct Cursor { pub enabled: bool, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub row: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub column: Option, } @@ -265,15 +267,15 @@ pub struct FileTransfer { } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename_all="lowercase", tag="state")] +#[serde(rename_all = "lowercase", tag = "state")] pub enum FileTransferState { Awaiting, - Running{ + Running { /// Number of bytes transferred - bytes: usize + bytes: usize, }, Aborting, - Complete{ + Complete { /// Completion message text: String, /// Transfer succeeded @@ -282,27 +284,27 @@ pub enum FileTransferState { } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename="kebab-case")] +#[serde(rename = "kebab-case")] pub struct Passthru { pub p_tag: String, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub parent_r_tag: Option, pub action: String, - #[serde(default, skip_serializing_if="Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub args: Vec, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct Popup { - #[serde(rename="type")] + #[serde(rename = "type")] pub type_: PopupType, pub text: String, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, } #[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] -#[serde(rename="kebab-case")] +#[serde(rename = "kebab-case")] pub enum PopupType { /// Error message from a connection attempt ConnectError, @@ -319,15 +321,14 @@ pub enum PopupType { } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename="kebab-case")] +#[serde(rename = "kebab-case")] pub struct Row { pub row: u8, pub changes: Vec, } - #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename="kebab-case")] +#[serde(rename = "kebab-case")] pub enum CountOrText { Count(usize), Text(String), @@ -338,48 +339,48 @@ pub struct Change { pub column: u8, #[serde(flatten)] pub change: CountOrText, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub fg: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub bg: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] /// Graphic rendition pub gr: Option, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct Screen { - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub cursor: Option, - #[serde(default, skip_serializing_if="Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub rows: Vec, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename="kebab-case")] +#[serde(rename = "kebab-case")] pub struct RunResult { - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub r_tag: Option, pub success: bool, - #[serde(default, skip_serializing_if="Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub text: Vec, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub abort: Option, /// Execution time in seconds pub time: f32, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename="kebab-case")] +#[serde(rename = "kebab-case")] pub struct Scroll { - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub fg: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub bg: Option, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -#[serde(rename="kebab-case")] +#[serde(rename = "kebab-case")] pub struct Stats { pub bytes_received: usize, pub bytes_sent: usize, @@ -390,7 +391,7 @@ pub struct Stats { #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct TerminalName { pub text: String, - #[serde(rename="override")] + #[serde(rename = "override")] pub override_: bool, } @@ -410,7 +411,7 @@ pub struct Thumb { #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct TraceFile { - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub name: Option, } @@ -418,13 +419,13 @@ pub struct TraceFile { pub struct UiError { pub fatal: bool, pub text: String, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub operation: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub member: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub line: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub column: Option, } @@ -433,7 +434,13 @@ mod test { use super::*; #[test] pub fn connection_state_serializes_as_expected() { - assert_eq!(serde_json::to_string(&ConnectionState::ConnectedETn3270e).unwrap(),r#""connected-e-tn3270e""#); - assert_eq!(serde_json::to_string(&ConnectionState::ConnectedESscp).unwrap(),r#""connected-e-sscp""#); + assert_eq!( + serde_json::to_string(&ConnectionState::ConnectedETn3270e).unwrap(), + r#""connected-e-tn3270e""# + ); + assert_eq!( + serde_json::to_string(&ConnectionState::ConnectedESscp).unwrap(), + r#""connected-e-sscp""# + ); } -} \ No newline at end of file +} diff --git a/d3270-common/src/b3270/operation.rs b/d3270-common/src/b3270/operation.rs index 431f0a3..b7a0cc4 100644 --- a/d3270-common/src/b3270/operation.rs +++ b/d3270-common/src/b3270/operation.rs @@ -4,45 +4,45 @@ use serde::{Deserialize, Serialize}; // {"run":{"actions":"Key(a)"}} // Operations #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub struct Run { - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub r_tag: Option, - #[serde(rename="type", default, skip_serializing_if="Option::is_none")] + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] pub type_: Option, pub actions: Vec, } #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub struct Action { pub action: String, - #[serde(default, skip_serializing_if="Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub args: Vec, } #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub struct Register { pub name: String, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub help_text: Option, - #[serde(default, skip_serializing_if="Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub help_params: Option, } #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] /// Completes a passthru action unsuccessfully -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub struct Fail { pub p_tag: String, pub text: Vec, } #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] pub struct Succeed { pub p_tag: String, - #[serde(default, skip_serializing_if="Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub text: Vec, } diff --git a/d3270-common/src/b3270/types.rs b/d3270-common/src/b3270/types.rs index 0e7d4d4..6c14e9a 100644 --- a/d3270-common/src/b3270/types.rs +++ b/d3270-common/src/b3270/types.rs @@ -1,8 +1,8 @@ +use bitflags::bitflags; +use serde::de::{Error, Visitor}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{Display, Formatter, Write}; use std::str::FromStr; -use bitflags::bitflags; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use serde::de::{Error, Visitor}; bitflags! { #[derive(Clone,Copy,Debug,PartialEq, Eq, Hash)] @@ -36,7 +36,8 @@ static FLAG_NAMES: &'static [(GraphicRendition, &'static str)] = &[ impl Display for GraphicRendition { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let flag_names = FLAG_NAMES.iter() + let flag_names = FLAG_NAMES + .iter() .filter_map(|(val, name)| self.contains(*val).then_some(*name)); for (n, name) in flag_names.enumerate() { if n != 0 { @@ -49,7 +50,10 @@ impl Display for GraphicRendition { } impl Serialize for GraphicRendition { - fn serialize(&self, serializer: S) -> Result where S: Serializer { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { if serializer.is_human_readable() { serializer.serialize_str(&self.to_string()) } else { @@ -60,7 +64,6 @@ impl Serialize for GraphicRendition { struct GrVisitor; - impl Visitor<'_> for GrVisitor { type Value = GraphicRendition; @@ -68,15 +71,24 @@ impl Visitor<'_> for GrVisitor { write!(formatter, "graphic rendition string or binary value") } - fn visit_i64(self, v: i64) -> Result where E: Error { + fn visit_i64(self, v: i64) -> Result + where + E: Error, + { self.visit_u64((v & 0xFFFF) as u64) } - fn visit_u64(self, v: u64) -> Result where E: Error { + fn visit_u64(self, v: u64) -> Result + where + E: Error, + { Ok(GraphicRendition::from_bits_truncate((v & 0xFFFF) as u16)) } - fn visit_str(self, v: &str) -> Result where E: Error { + fn visit_str(self, v: &str) -> Result + where + E: Error, + { GraphicRendition::from_str(v).map_err(E::custom) } } @@ -87,7 +99,8 @@ impl FromStr for GraphicRendition { fn from_str(s: &str) -> Result { s.split(",") .map(|attr| { - FLAG_NAMES.iter() + FLAG_NAMES + .iter() .find(|(_, name)| *name == attr) .map(|x| x.0) .ok_or_else(|| format!("Invalid attr name {attr}")) @@ -97,7 +110,10 @@ impl FromStr for GraphicRendition { } impl<'de> Deserialize<'de> for GraphicRendition { - fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { if deserializer.is_human_readable() { deserializer.deserialize_str(GrVisitor) } else { @@ -108,16 +124,19 @@ impl<'de> Deserialize<'de> for GraphicRendition { #[cfg(test)] mod test { - use std::str::FromStr; use super::GraphicRendition; + use std::str::FromStr; #[test] fn from_str_1() { - assert_eq!(GraphicRendition::from_str("underline,blink"), Ok(GraphicRendition::BLINK | GraphicRendition::UNDERLINE)) + assert_eq!( + GraphicRendition::from_str("underline,blink"), + Ok(GraphicRendition::BLINK | GraphicRendition::UNDERLINE) + ) } } #[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] -#[serde(rename="camelCase")] +#[serde(rename = "camelCase")] #[repr(u8)] pub enum Color { NeutralBlack, @@ -142,22 +161,22 @@ impl From for u8 { fn from(value: Color) -> Self { use Color::*; match value { - NeutralBlack => 0, - Blue => 1, - Red => 2, - Pink => 3, - Green => 4, - Turquoise => 5, - Yellow => 6, - NeutralWhite => 7, - Black => 8, - DeepBlue => 9, - Orange => 10, - Purple => 11, - PaleGreen => 12, + NeutralBlack => 0, + Blue => 1, + Red => 2, + Pink => 3, + Green => 4, + Turquoise => 5, + Yellow => 6, + NeutralWhite => 7, + Black => 8, + DeepBlue => 9, + Orange => 10, + Purple => 11, + PaleGreen => 12, PaleTurquoise => 13, - Gray => 14, - White => 15, + Gray => 14, + White => 15, } } } @@ -166,16 +185,16 @@ impl From for Color { fn from(value: u8) -> Self { use Color::*; match value & 0xF { - 0 => NeutralBlack, - 1 => Blue, - 2 => Red, - 3 => Pink, - 4 => Green, - 5 => Turquoise, - 6 => Yellow, - 7 => NeutralWhite, - 8 => Black, - 9 => DeepBlue, + 0 => NeutralBlack, + 1 => Blue, + 2 => Red, + 3 => Pink, + 4 => Green, + 5 => Turquoise, + 6 => Yellow, + 7 => NeutralWhite, + 8 => Black, + 9 => DeepBlue, 10 => Orange, 11 => Purple, 12 => PaleGreen, diff --git a/d3270-common/src/executor.rs b/d3270-common/src/executor.rs index e69de29..8b13789 100644 --- a/d3270-common/src/executor.rs +++ b/d3270-common/src/executor.rs @@ -0,0 +1 @@ + diff --git a/d3270-common/src/lib.rs b/d3270-common/src/lib.rs index 9308099..0f72918 100644 --- a/d3270-common/src/lib.rs +++ b/d3270-common/src/lib.rs @@ -1,4 +1,4 @@ pub mod b3270; pub mod tracker; -pub mod executor; \ No newline at end of file +pub mod executor; diff --git a/d3270-common/src/tracker.rs b/d3270-common/src/tracker.rs index 3a4a04c..8106863 100644 --- a/d3270-common/src/tracker.rs +++ b/d3270-common/src/tracker.rs @@ -1,7 +1,10 @@ -use std::collections::HashMap; -use crate::b3270::indication::{Change, Connection, ConnectionState, CountOrText, Cursor, Erase, Oia, OiaFieldName, Row, RunResult, Screen, ScreenMode, Scroll, Setting, TerminalName, Thumb, Tls, TraceFile}; -use crate::b3270::{Indication, InitializeIndication}; +use crate::b3270::indication::{ + Change, Connection, ConnectionState, CountOrText, Cursor, Erase, Oia, OiaFieldName, Row, + RunResult, Screen, ScreenMode, Scroll, Setting, TerminalName, Thumb, Tls, TraceFile, +}; use crate::b3270::types::{Color, GraphicRendition, PackedAttr}; +use crate::b3270::{Indication, InitializeIndication}; +use std::collections::HashMap; #[derive(Copy, Clone, Debug)] struct CharCell { @@ -48,9 +51,7 @@ impl Tracker { | Indication::Icon { .. } | Indication::Popup(_) | Indication::Stats(_) - | Indication::WindowTitle { .. } - - => (), + | Indication::WindowTitle { .. } => (), Indication::Connection(conn) => { self.connection = conn.clone(); } @@ -64,43 +65,47 @@ impl Tracker { let cols = self.erase.logical_cols.unwrap_or(self.screen_mode.cols) as usize; self.screen = vec![ - vec![CharCell{ - attr: u32::c_pack( - erase.fg.unwrap_or(Color::NeutralBlack), - erase.bg.unwrap_or(Color::Blue), - GraphicRendition::empty(), - ), - ch: ' ', - };cols] - ; rows + vec![ + CharCell { + attr: u32::c_pack( + erase.fg.unwrap_or(Color::NeutralBlack), + erase.bg.unwrap_or(Color::Blue), + GraphicRendition::empty(), + ), + ch: ' ', + }; + cols + ]; + rows ] } - Indication::Formatted { state } => {self.formatted = *state; } + Indication::Formatted { state } => { + self.formatted = *state; + } Indication::Initialize(init) => { let mut static_init = Vec::with_capacity(init.len()); for indicator in init.clone() { match indicator { - InitializeIndication::CodePages(_) | - InitializeIndication::Hello(_) | - InitializeIndication::Models(_) | - InitializeIndication::Prefixes { .. } | - InitializeIndication::Proxies(_) | - InitializeIndication::TlsHello(_) | - InitializeIndication::Tls(_) | - InitializeIndication::TraceFile(_) => - static_init.push(indicator), + InitializeIndication::CodePages(_) + | InitializeIndication::Hello(_) + | InitializeIndication::Models(_) + | InitializeIndication::Prefixes { .. } + | InitializeIndication::Proxies(_) + | InitializeIndication::TlsHello(_) + | InitializeIndication::Tls(_) + | InitializeIndication::TraceFile(_) => static_init.push(indicator), // The rest are passed through to normal processing. InitializeIndication::Thumb(thumb) => { self.handle_indication(&mut Indication::Thumb(thumb)); - }, + } InitializeIndication::Setting(setting) => { self.handle_indication(&mut Indication::Setting(setting)); } InitializeIndication::ScreenMode(mode) => { self.handle_indication(&mut Indication::ScreenMode(mode)); - }, + } InitializeIndication::Oia(oia) => { self.handle_indication(&mut Indication::Oia(oia)); } @@ -127,21 +132,19 @@ 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; @@ -164,18 +167,18 @@ impl Tracker { } Indication::ScreenMode(mode) => { self.screen_mode = *mode; - self.handle_indication(&mut Indication::Erase(Erase{ + self.handle_indication(&mut Indication::Erase(Erase { logical_rows: Some(self.screen_mode.rows), logical_cols: Some(self.screen_mode.cols), fg: None, bg: None, })); } - Indication::Scroll(Scroll{ fg, bg }) => { + Indication::Scroll(Scroll { fg, bg }) => { let fg = fg.or(self.erase.fg).unwrap_or(Color::Blue); let bg = bg.or(self.erase.bg).unwrap_or(Color::NeutralBlack); let mut row = self.screen.remove(0); - row.fill(CharCell{ + row.fill(CharCell { attr: u32::c_pack(fg, bg, GraphicRendition::empty()), ch: ' ', }); @@ -190,7 +193,7 @@ impl Tracker { Indication::Thumb(thumb) => { self.thumb = thumb.clone(); } - Indication::TraceFile(TraceFile{name}) => { + Indication::TraceFile(TraceFile { name }) => { self.trace_file = name.clone(); } Indication::Tls(tls) => { @@ -201,16 +204,15 @@ impl Tracker { Indication::UiError(_) => {} // we can assume that this came from the last sent command Indication::Passthru(_) => {} // dunno how to handle this one Indication::FileTransfer(_) => {} - Indication::RunResult(RunResult{r_tag, ..}) => { + Indication::RunResult(RunResult { r_tag, .. }) => { if let Some(dest) = r_tag { return Disposition::Direct(dest.clone()); } else { return Disposition::Drop; } } - } - return Disposition::Broadcast + return Disposition::Broadcast; } pub fn get_init_indication(&self) -> Vec { @@ -219,12 +221,13 @@ impl Tracker { contents.push(InitializeIndication::Erase(self.erase)); contents.push(InitializeIndication::Thumb(self.thumb)); - contents.extend(self.oia.values() - .cloned() - .map(InitializeIndication::Oia)); - contents.extend(self.settings.values() - .cloned() - .map(InitializeIndication::Setting)); + contents.extend(self.oia.values().cloned().map(InitializeIndication::Oia)); + contents.extend( + self.settings + .values() + .cloned() + .map(InitializeIndication::Setting), + ); contents.extend(self.tls.clone().map(InitializeIndication::Tls)); // Construct a screen snapshot @@ -232,7 +235,9 @@ impl Tracker { Indication::Initialize(contents), Indication::Connection(self.connection.clone()), Indication::Screen(self.screen_snapshot()), - Indication::Formatted {state: self.formatted}, + Indication::Formatted { + state: self.formatted, + }, ]; if let Some(terminal_name) = self.terminal_name.clone() { result.push(Indication::TerminalName(terminal_name)); @@ -255,7 +260,7 @@ impl Tracker { let (first, rest) = row.split_at(split_pt); row = rest; let content = first.iter().map(|cell| cell.ch).collect(); - result.push(Change{ + result.push(Change { column, fg: Some(cur_gr.c_fg()), bg: Some(cur_gr.c_bg()), @@ -269,16 +274,19 @@ impl Tracker { } fn screen_snapshot(&self) -> Screen { - Screen{ + Screen { cursor: Some(self.cursor), - rows: self.screen.iter() - .map(Vec::as_slice).map(Self::format_row) + rows: self + .screen + .iter() + .map(Vec::as_slice) + .map(Self::format_row) .enumerate() - .map(|(row_id, changes)| Row{ + .map(|(row_id, changes)| Row { row: row_id as u8 - 1, changes, }) - .collect() + .collect(), } } } @@ -313,7 +321,7 @@ impl Default for Tracker { cursor: Cursor { enabled: false, row: None, - column: None + column: None, }, connection: Connection { state: ConnectionState::NotConnected, @@ -328,4 +336,4 @@ impl Default for Tracker { }; ret } -} \ No newline at end of file +} diff --git a/d3270d/src/connection.rs b/d3270d/src/connection.rs index bcc27c7..de3a659 100644 --- a/d3270d/src/connection.rs +++ b/d3270d/src/connection.rs @@ -1,29 +1,29 @@ +use anyhow::anyhow; +use base64::engine::general_purpose::STANDARD as B64_STANDARD; +use base64::Engine; +use bytes::Buf; +use d3270_common::b3270::indication::RunResult; +use d3270_common::b3270::operation::Action; +use d3270_common::b3270::{operation, Indication, Operation}; +use d3270_common::tracker::{Disposition, Tracker}; +use futures::future::BoxFuture; +use futures::{FutureExt, Stream, StreamExt, TryFutureExt}; +use rand::RngCore; use std::collections::{HashMap, VecDeque}; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use anyhow::anyhow; -use base64::Engine; -use base64::engine::general_purpose::STANDARD as B64_STANDARD; -use bytes::Buf; -use futures::{FutureExt, Stream, StreamExt, TryFutureExt}; -use futures::future::BoxFuture; -use rand::RngCore; -use tokio::io::{BufReader, AsyncBufReadExt, Lines, AsyncWrite}; +use tokio::io::{AsyncBufReadExt, AsyncWrite, BufReader, Lines}; use tokio::process::{Child, ChildStdout}; -use tokio::sync::{mpsc, oneshot, broadcast}; -use tokio_stream::wrappers::{BroadcastStream, errors::BroadcastStreamRecvError}; +use tokio::sync::{broadcast, mpsc, oneshot}; +use tokio_stream::wrappers::{errors::BroadcastStreamRecvError, BroadcastStream}; use tracing::{error, info, warn}; -use d3270_common::b3270::{Indication, Operation, operation}; -use d3270_common::b3270::indication::RunResult; -use d3270_common::b3270::operation::Action; -use d3270_common::tracker::{Disposition, Tracker}; pub struct B3270 { - tracker: Tracker, // - child: Child, // - comm: mpsc::Receiver, // - ind_chan: broadcast::Sender, // + tracker: Tracker, // + child: Child, // + comm: mpsc::Receiver, // + ind_chan: broadcast::Sender, // child_reader: Lines>, // write_buf: VecDeque, @@ -38,8 +38,14 @@ pub enum B3270Request { enum HandleReceiveState { Steady(BroadcastStream), Wait(oneshot::Receiver<(Vec, broadcast::Receiver)>), - Resume(std::vec::IntoIter, broadcast::Receiver), - TryRestart(BoxFuture<'static, Result<(), ()>>, oneshot::Receiver<(Vec, broadcast::Receiver)>), + Resume( + std::vec::IntoIter, + broadcast::Receiver, + ), + TryRestart( + BoxFuture<'static, Result<(), ()>>, + oneshot::Receiver<(Vec, broadcast::Receiver)>, + ), } pub struct Handle { sender: mpsc::Sender, @@ -66,11 +72,12 @@ impl Stream for Handle { match rcvr.poll_unpin(cx) { Poll::Ready(Ok((inds, rcvr))) => { // reverse the indicators so that they can be popped. - self.receiver = Some(HandleReceiveState::Resume(inds.into_iter(), rcvr)); + self.receiver = + Some(HandleReceiveState::Resume(inds.into_iter(), rcvr)); } Poll::Ready(Err(error)) => { warn!(%error, "unable to reconnect to b3270 server"); - return Poll::Ready(None) + return Poll::Ready(None); } Poll::Pending => { self.receiver = Some(HandleReceiveState::Wait(rcvr)); @@ -78,41 +85,41 @@ impl Stream for Handle { } } } - Some(HandleReceiveState::Resume(mut inds, rcvr)) => { - match inds.next() { - Some(next) => { - self.receiver = Some(HandleReceiveState::Resume(inds, rcvr)); - return Poll::Ready(Some(next)); - } - None => { - self.receiver = Some(HandleReceiveState::Steady(BroadcastStream::new(rcvr))); - } + Some(HandleReceiveState::Resume(mut inds, rcvr)) => match inds.next() { + Some(next) => { + self.receiver = Some(HandleReceiveState::Resume(inds, rcvr)); + return Poll::Ready(Some(next)); } - } - Some(HandleReceiveState::Steady(mut rcvr)) => { - match rcvr.poll_next_unpin(cx) { - Poll::Ready(Some(Ok(msg))) => { - self.receiver = Some(HandleReceiveState::Steady(rcvr)); - return Poll::Ready(Some(msg)) - } - Poll::Ready(Some(Err(BroadcastStreamRecvError::Lagged(_)))) => { - warn!("Dropped messages from b3270 server; starting resync"); - let (os_snd, os_rcv) = oneshot::channel(); - let fut = self.sender.clone().reserve_owned() - .map_ok(move |permit| { - permit.send(B3270Request::Resync(os_snd)); - }) - .map_err(|_| ()) - .boxed(); - self.receiver = Some(HandleReceiveState::TryRestart(fut, os_rcv)); - } - Poll::Ready(None) => { - warn!("Failed to receive from b3270 server"); - return Poll::Ready(None) - }, - Poll::Pending => return Poll::Pending + None => { + self.receiver = + Some(HandleReceiveState::Steady(BroadcastStream::new(rcvr))); } - } + }, + Some(HandleReceiveState::Steady(mut rcvr)) => match rcvr.poll_next_unpin(cx) { + Poll::Ready(Some(Ok(msg))) => { + self.receiver = Some(HandleReceiveState::Steady(rcvr)); + return Poll::Ready(Some(msg)); + } + Poll::Ready(Some(Err(BroadcastStreamRecvError::Lagged(_)))) => { + warn!("Dropped messages from b3270 server; starting resync"); + let (os_snd, os_rcv) = oneshot::channel(); + let fut = self + .sender + .clone() + .reserve_owned() + .map_ok(move |permit| { + permit.send(B3270Request::Resync(os_snd)); + }) + .map_err(|_| ()) + .boxed(); + self.receiver = Some(HandleReceiveState::TryRestart(fut, os_rcv)); + } + Poll::Ready(None) => { + warn!("Failed to receive from b3270 server"); + return Poll::Ready(None); + } + Poll::Pending => return Poll::Pending, + }, None => { return Poll::Ready(None); @@ -122,11 +129,18 @@ impl Stream for Handle { } } - impl B3270 { - pub fn spawn(mut child: Child) -> (tokio::task::JoinHandle, mpsc::Sender) { + pub fn spawn( + mut child: Child, + ) -> ( + tokio::task::JoinHandle, + mpsc::Sender, + ) { let (subproc_snd, subproc_rcv) = mpsc::channel(10); - let child_reader = child.stdout.take().expect("Should always be given a child that has stdout captured"); + let child_reader = child + .stdout + .take() + .expect("Should always be given a child that has stdout captured"); let child_reader = BufReader::new(child_reader).lines(); // A single connect can result in a flurry of messages, so we need a big buffer let (ind_chan, _) = broadcast::channel(100); @@ -153,9 +167,7 @@ impl Future for B3270 { while let Poll::Ready(buf) = Pin::new(&mut self.child_reader).poll_next_line(cx) { match buf { Ok(Some(line)) => match serde_json::from_str(&line) { - Ok(ind) => { - indications.push(ind) - }, + Ok(ind) => indications.push(ind), Err(error) => { warn!(%error, msg=line, "Failed to parse indication"); } @@ -204,13 +216,15 @@ impl Future for B3270 { let mut sync_state = None; while let Poll::Ready(cmd) = self.comm.poll_recv(cx) { match cmd { - None => {}, + None => {} Some(B3270Request::Resync(sender)) => { if sync_state.is_none() { sync_state = Some(self.tracker.get_init_indication()); } // it's OK for this to fail; we just don't get a new client - sender.send((sync_state.clone().unwrap(), self.ind_chan.subscribe())).ok(); + sender + .send((sync_state.clone().unwrap(), self.ind_chan.subscribe())) + .ok(); } Some(B3270Request::Action(actions, response_chan)) => { let tag = 'find_tag: loop { @@ -225,15 +239,12 @@ impl Future for B3270 { type_: Some("keymap".to_owned()), actions, }); - let result = serde_json::to_writer( - &mut self.write_buf, - &op - ); + let result = serde_json::to_writer(&mut self.write_buf, &op); match result { Ok(()) => { self.write_buf.push_back(b'\n'); self.action_response_map.insert(tag, response_chan); - }, + } Err(error) => error!(?op, %error, "Failed to serialize op"), } } @@ -244,7 +255,13 @@ impl Future for B3270 { 'write: while !self.write_buf.is_empty() { let myself = &mut *self; let chunk = myself.write_buf.chunk(); - let stdin = Pin::new(myself.child.stdin.as_mut().expect("Should always have child stdin")); + let stdin = Pin::new( + myself + .child + .stdin + .as_mut() + .expect("Should always have child stdin"), + ); match stdin.poll_write(cx, chunk) { Poll::Pending | Poll::Ready(Ok(0)) => { break 'write; diff --git a/d3270d/src/main.rs b/d3270d/src/main.rs index 161cde5..90e515f 100644 --- a/d3270d/src/main.rs +++ b/d3270d/src/main.rs @@ -1,28 +1,29 @@ +use anyhow::anyhow; use std::ffi::OsString; use std::process::Stdio; use std::str::FromStr; -use anyhow::anyhow; #[tokio::main] async fn main() -> anyhow::Result<()> { - let mut subprocess_args = vec![ - OsString::from_str("-json").unwrap(), - ]; + let mut subprocess_args = vec![OsString::from_str("-json").unwrap()]; let mut args_iter = std::env::args_os().peekable(); let mut connect_str = None; while let Some(arg) = args_iter.next() { // we default to one of the ignored args match arg.to_str().unwrap_or("-json") { - "-json" | "-xml" | "-indent" | "--" | - "-scriptportonce" | "-nowrapperdoc" | - "-socket" | "-v" | "--version" => {} + "-json" | "-xml" | "-indent" | "--" | "-scriptportonce" | "-nowrapperdoc" + | "-socket" | "-v" | "--version" => {} "-scriptport" | "-httpd" => { args_iter.next(); } "-connect" => { - connect_str = args_iter.next() + connect_str = args_iter + .next() .ok_or_else(|| anyhow!("Arg required for -connect")) - .and_then(|arg| arg.into_string().map_err(|_| anyhow!("Invalid connect string"))) + .and_then(|arg| { + arg.into_string() + .map_err(|_| anyhow!("Invalid connect string")) + }) .map(Some)?; } "-e" => { @@ -37,7 +38,7 @@ async fn main() -> anyhow::Result<()> { } } - let _connect_str = connect_str.ok_or_else(||anyhow!("No connect string given"))?; + let _connect_str = connect_str.ok_or_else(|| anyhow!("No connect string given"))?; let subproc = tokio::process::Command::new("b3270") .args(&subprocess_args) @@ -51,5 +52,4 @@ async fn main() -> anyhow::Result<()> { Ok(()) } - -pub mod connection; \ No newline at end of file +pub mod connection; diff --git a/qt3270/src/main.rs b/qt3270/src/main.rs index bd31846..df24c5b 100644 --- a/qt3270/src/main.rs +++ b/qt3270/src/main.rs @@ -1,3 +1,3 @@ fn main() { eprintln!("Hello from d3270c"); -} \ No newline at end of file +}