Initial commit; wrote lots o' serializers
This commit is contained in:
2127
Cargo.lock
generated
Normal file
2127
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "d3270"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.28.0", features = ["rt", "macros", "sync", "process"] }
|
||||
tide = "0.16.0"
|
||||
tide-websockets = "0.4.0"
|
||||
serde = { version = "1.0.162", features = ["derive"]}
|
||||
serde_json = "1.0.96"
|
||||
serde_cbor = "0.11.2"
|
||||
anyhow = "1.0.71"
|
||||
bitflags = "2.2.1"
|
||||
59
flake.lock
generated
Normal file
59
flake.lock
generated
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1683353485,
|
||||
"narHash": "sha256-Skp5El3egmoXPiINWjnoW0ktVfB7PR/xc4F4bhD+BJY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "caf436a52b25164b71e0d48b671127ac2e2a5b75",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
21
flake.nix
Normal file
21
flake.nix
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
description = "A very basic flake";
|
||||
|
||||
inputs.nixpkgs.url = "nixpkgs";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
clang
|
||||
rustup
|
||||
];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
];
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
116
src/b3270.rs
Normal file
116
src/b3270.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
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 operation::{Fail, Register, Run, Succeed};
|
||||
|
||||
pub mod operation;
|
||||
pub mod indication;
|
||||
pub mod types;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub enum Indication {
|
||||
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
|
||||
ConnectAttempt(ConnectAttempt),
|
||||
/// Indicates the screen size
|
||||
Erase(Erase),
|
||||
/// Display switch between LTR and RTL
|
||||
Flipped {
|
||||
/// True if display is now in RTL mode
|
||||
value: bool,
|
||||
},
|
||||
/// An xterm escape sequence requested a new font
|
||||
Font {
|
||||
text: String,
|
||||
},
|
||||
/// The formatting state of the screen has changed.
|
||||
/// A formatted string has at least one field displayed.
|
||||
Formatted {
|
||||
state: bool,
|
||||
},
|
||||
/// File transfer state change
|
||||
#[serde(rename="ft")]
|
||||
FileTransfer(FileTransfer),
|
||||
/// An XTerm escape sequence requested a new icon name
|
||||
Icon{
|
||||
text: String,
|
||||
},
|
||||
/// The first message sent
|
||||
Initialize(Vec<InitializeIndication>),
|
||||
/// Change in the state of the Operator Information Area
|
||||
Oia(Oia),
|
||||
/// A passthru action has been invoked.
|
||||
/// Clients must respond with a succeed or fail operation
|
||||
Passthru(Passthru),
|
||||
/// Display an asynchronous message
|
||||
Popup(Popup),
|
||||
/// Result of a run operation
|
||||
RunResult(RunResult),
|
||||
/// Change to screen contents
|
||||
Screen(Screen),
|
||||
/// Screen dimensions/characteristics changed
|
||||
ScreenMode(ScreenMode),
|
||||
/// Screen was scrolled up by one row
|
||||
Scroll(Scroll),
|
||||
/// Setting changed
|
||||
Setting(Setting),
|
||||
/// I/O statistics
|
||||
Stats(Stats),
|
||||
/// Reports the terminal name sent to the host during TELNET negotiation
|
||||
TerminalName(TerminalName),
|
||||
/// Change in the scrollbar thumb
|
||||
Thumb(Thumb),
|
||||
/// Indicates the name of the trace file
|
||||
TraceFile(TraceFile),
|
||||
/// TLS state changed
|
||||
Tls(Tls),
|
||||
/// Error in b3270's input
|
||||
UiError(UiError),
|
||||
/// Xterm escape sequence requested a change to the window title
|
||||
WindowTitle{
|
||||
text: String,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub enum InitializeIndication {
|
||||
CodePages(Vec<CodePage>),
|
||||
/// Indicates that the host connection has changed state.
|
||||
Connection(Connection),
|
||||
/// Indicates the screen size
|
||||
Erase(Erase),
|
||||
/// The first element in the initialize array
|
||||
Hello(Hello),
|
||||
/// Indicates which 3270 models are supported
|
||||
Models(Vec<Model>),
|
||||
/// Change in the state of the Operator Information Area
|
||||
Oia(Oia),
|
||||
/// List of supported proxies
|
||||
Proxies(Vec<Proxy>),
|
||||
/// Set of supported prefixes
|
||||
Prefixes{value: String},
|
||||
/// Screen dimensions/characteristics changed
|
||||
ScreenMode(ScreenMode),
|
||||
/// Setting changed
|
||||
Setting(Setting),
|
||||
/// Indicates build-time TLS config
|
||||
TlsHello(TlsHello),
|
||||
/// TLS state changed
|
||||
Tls(Tls),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub enum Operation {
|
||||
/// Run an action
|
||||
Run(Run),
|
||||
/// Register a pass-thru action
|
||||
Register(Register),
|
||||
/// Tell b3270 that a passthru action failed
|
||||
Fail(Fail),
|
||||
/// Tell b3270 that a passthru action succeeded
|
||||
Succeed(Succeed),
|
||||
}
|
||||
|
||||
414
src/b3270/indication.rs
Normal file
414
src/b3270/indication.rs
Normal file
@@ -0,0 +1,414 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub enum ActionCause {
|
||||
Command,
|
||||
Default,
|
||||
FileTransfer,
|
||||
Httpd,
|
||||
Idle,
|
||||
Keymap,
|
||||
Macro,
|
||||
None,
|
||||
Password,
|
||||
Paste,
|
||||
Peek,
|
||||
ScreenRedraw,
|
||||
Script,
|
||||
Typeahead,
|
||||
Ui,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct CodePage{
|
||||
/// The canonical name of the code page
|
||||
pub name: String,
|
||||
#[serde(default, skip_serializing_if="Vec::is_empty")]
|
||||
pub aliases: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Connection{
|
||||
/// New connection state
|
||||
pub state: ConnectionState,
|
||||
/// Host name, if connected
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub host: Option<String>,
|
||||
/// Source of the connection
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub cause: Option<ActionCause>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum ComposeType {
|
||||
Std,
|
||||
Ge,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum ConnectionState {
|
||||
NotConnected,
|
||||
Reconnecting,
|
||||
Resolving,
|
||||
TcpPending,
|
||||
TlsPending,
|
||||
TelnetPending,
|
||||
ConnectedNvt,
|
||||
ConnectedNvtCharmode,
|
||||
Connected3270,
|
||||
ConnectedUnbound,
|
||||
ConnectedENvt,
|
||||
ConnectedESscp,
|
||||
#[serde(rename="connected-e-tn3270e")]
|
||||
ConnectedETn3270e,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub struct Erase {
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub logical_rows: Option<u8>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub logical_cols: Option<u8>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub fg: Option<Color>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub bg: Option<Color>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Hello {
|
||||
pub version: String,
|
||||
pub build: String,
|
||||
pub copyright: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Model {
|
||||
pub model: u8,
|
||||
pub rows: u8,
|
||||
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)]
|
||||
pub enum OiaField {
|
||||
/// Composite character in progress
|
||||
Compose{
|
||||
value: bool,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
char: Option<String>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
type_: Option<ComposeType>,
|
||||
},
|
||||
/// Insert mode
|
||||
Insert{value: bool},
|
||||
/// Keyboard is locked
|
||||
Lock{
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
value: Option<String>
|
||||
},
|
||||
Lu{
|
||||
/// Host session logical unit name
|
||||
value: String,
|
||||
/// Printer session LU name
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
lu: Option<String>,
|
||||
},
|
||||
/// Communication pending
|
||||
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>,
|
||||
},
|
||||
/// Reverse input mode
|
||||
ReverseInput {
|
||||
value: bool,
|
||||
},
|
||||
/// Screen trace count
|
||||
Screentrace {
|
||||
value: String,
|
||||
},
|
||||
/// Host command timer (minutes:seconds)
|
||||
Script {
|
||||
value: String,
|
||||
},
|
||||
Typeahead {
|
||||
value: bool,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Proxy {
|
||||
pub name: String,
|
||||
pub username: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Setting {
|
||||
pub name: String,
|
||||
/// I'd love something other than depending on serde_json for this.
|
||||
pub value: Option<serde_json::Value>,
|
||||
pub cause: ActionCause,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
pub struct ScreenMode {
|
||||
pub model: u8,
|
||||
pub rows: u8,
|
||||
pub cols: u8,
|
||||
pub color: bool,
|
||||
pub oversize: bool,
|
||||
pub extended: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
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")]
|
||||
pub options: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub struct Tls {
|
||||
pub secure: bool,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub verified: Option<bool>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub session: Option<String>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub host_cert: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub struct ConnectAttempt {
|
||||
pub host_ip: String,
|
||||
pub port: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
// TODO: change this to an enum
|
||||
pub struct Cursor {
|
||||
pub enabled: bool,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub row: Option<u8>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub column: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct FileTransfer {
|
||||
#[serde(flatten)]
|
||||
pub state: FileTransferState,
|
||||
pub cause: ActionCause,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all="lowercase", tag="state")]
|
||||
pub enum FileTransferState {
|
||||
Awaiting,
|
||||
Running{
|
||||
/// Number of bytes transferred
|
||||
bytes: usize
|
||||
},
|
||||
Aborting,
|
||||
Complete{
|
||||
/// Completion message
|
||||
text: String,
|
||||
/// Transfer succeeded
|
||||
success: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename="kebab-case")]
|
||||
pub struct Passthru {
|
||||
pub p_tag: String,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub parent_r_tag: Option<String>,
|
||||
pub action: String,
|
||||
#[serde(default, skip_serializing_if="Vec::is_empty")]
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Popup {
|
||||
#[serde(rename="type")]
|
||||
pub type_: PopupType,
|
||||
pub text: String,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub error: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
#[serde(rename="kebab-case")]
|
||||
pub enum PopupType {
|
||||
/// Error message from a connection attempt
|
||||
ConnectError,
|
||||
/// Error message
|
||||
Error,
|
||||
/// Informational message
|
||||
Info,
|
||||
/// Stray action output (should not happen)
|
||||
Result,
|
||||
/// Output from the pr3287 process
|
||||
Printer,
|
||||
/// Output from other child process
|
||||
Child,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename="kebab-case")]
|
||||
pub struct Row {
|
||||
pub row: u8,
|
||||
pub changes: Vec<Change>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename="kebab-case")]
|
||||
pub enum CountOrText {
|
||||
Count(usize),
|
||||
Text(String),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Change {
|
||||
pub column: u8,
|
||||
#[serde(flatten)]
|
||||
pub change: CountOrText,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub fg: Option<Color>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub bg: Option<Color>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
/// Graphic rendition
|
||||
// TODO: parse comma-separated list of GR strings from https://x3270.miraheze.org/wiki/B3270/Graphic_rendition
|
||||
pub gr: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Screen {
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub cursor: Option<Cursor>,
|
||||
#[serde(default, skip_serializing_if="Vec::is_empty")]
|
||||
pub rows: Vec<Row>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename="kebab-case")]
|
||||
pub struct RunResult {
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub r_tag: Option<String>,
|
||||
pub success: bool,
|
||||
#[serde(default, skip_serializing_if="Vec::is_empty")]
|
||||
pub text: Vec<String>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub abort: Option<bool>,
|
||||
/// Execution time in seconds
|
||||
pub time: f32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename="kebab-case")]
|
||||
pub struct Scroll {
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub fg: Option<Color>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub bg: Option<Color>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
#[serde(rename="camelCase")]
|
||||
pub enum Color {
|
||||
NeutralBlack,
|
||||
Blue,
|
||||
Red,
|
||||
Pink,
|
||||
Green,
|
||||
Turquoise,
|
||||
Yellow,
|
||||
NeutralWhite,
|
||||
Black,
|
||||
DeepBlue,
|
||||
Orange,
|
||||
Purple,
|
||||
PaleGreen,
|
||||
PaleTurquoise,
|
||||
Gray,
|
||||
White,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename="kebab-case")]
|
||||
pub struct Stats {
|
||||
pub bytes_received: usize,
|
||||
pub bytes_sent: usize,
|
||||
pub records_received: usize,
|
||||
pub records_sent: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct TerminalName {
|
||||
pub text: String,
|
||||
#[serde(rename="override")]
|
||||
pub override_: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
pub struct Thumb {
|
||||
/// Fraction of scrollbar to top of thumb
|
||||
pub top: f32,
|
||||
/// Fraction of scrollbar for thumb display
|
||||
pub shown: f32,
|
||||
/// Number of rows saved
|
||||
pub saved: usize,
|
||||
/// Size of a screen in rows
|
||||
pub screen: usize,
|
||||
/// Number of rows scrolled back
|
||||
pub back: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct TraceFile {
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct UiError {
|
||||
pub fatal: bool,
|
||||
pub text: String,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub operation: Option<String>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub member: Option<String>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub line: Option<usize>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub column: Option<usize>,
|
||||
}
|
||||
46
src/b3270/operation.rs
Normal file
46
src/b3270/operation.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Operations
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub struct Run {
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub r_tag: Option<String>,
|
||||
#[serde(rename="type", default, skip_serializing_if="Option::is_none")]
|
||||
pub type_: Option<String>,
|
||||
pub actions: Vec<Action>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub struct Action {
|
||||
pub action: String,
|
||||
#[serde(default, skip_serializing_if="Vec::is_empty")]
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub struct Register {
|
||||
pub name: String,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub help_text: Option<String>,
|
||||
#[serde(default, skip_serializing_if="Option::is_none")]
|
||||
pub help_params: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
|
||||
/// Completes a passthru action unsuccessfully
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub struct Fail {
|
||||
pub p_tag: String,
|
||||
pub text: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub struct Succeed {
|
||||
pub p_tag: String,
|
||||
#[serde(default, skip_serializing_if="Vec::is_empty")]
|
||||
pub text: Vec<String>,
|
||||
}
|
||||
116
src/b3270/types.rs
Normal file
116
src/b3270/types.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
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)]
|
||||
pub struct GraphicRendition: u16 {
|
||||
const UNDERLINE = 0x001;
|
||||
const BLINK = 0x002;
|
||||
const HIGHLIGHT = 0x004;
|
||||
const SELECTABLE = 0x008;
|
||||
const REVERSE = 0x010;
|
||||
const WIDE = 0x020;
|
||||
const ORDER = 0x040;
|
||||
const PRIVATE_USE = 0x080;
|
||||
const NO_COPY = 0x100;
|
||||
const WRAP = 0x200;
|
||||
}
|
||||
}
|
||||
|
||||
static FLAG_NAMES: &'static [(GraphicRendition, &'static str)] = &[
|
||||
(GraphicRendition::UNDERLINE, "underline"),
|
||||
(GraphicRendition::BLINK, "blink"),
|
||||
(GraphicRendition::HIGHLIGHT, "highlight"),
|
||||
(GraphicRendition::SELECTABLE, "selectable"),
|
||||
(GraphicRendition::REVERSE, "reverse"),
|
||||
(GraphicRendition::WIDE, "wide"),
|
||||
(GraphicRendition::ORDER, "order"),
|
||||
(GraphicRendition::PRIVATE_USE, "private-use"),
|
||||
(GraphicRendition::NO_COPY, "no-copy"),
|
||||
(GraphicRendition::WRAP, "wrap"),
|
||||
];
|
||||
|
||||
impl Display for GraphicRendition {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
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 {
|
||||
f.write_char(',')?;
|
||||
}
|
||||
f.write_str(name)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for GraphicRendition {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||
if serializer.is_human_readable() {
|
||||
serializer.serialize_str(&self.to_string())
|
||||
} else {
|
||||
serializer.serialize_u16(self.bits())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GrVisitor;
|
||||
|
||||
|
||||
impl Visitor<'_> for GrVisitor {
|
||||
type Value = GraphicRendition;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
write!(formatter, "graphic rendition string or binary value")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> where E: Error {
|
||||
self.visit_u64((v & 0xFFFF) as u64)
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: Error {
|
||||
Ok(GraphicRendition::from_bits_truncate((v & 0xFFFF) as u16))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: Error {
|
||||
GraphicRendition::from_str(v).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for GraphicRendition {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.split(",")
|
||||
.map(|attr| {
|
||||
FLAG_NAMES.iter()
|
||||
.find(|(_, name)| *name == attr)
|
||||
.map(|x| x.0)
|
||||
.ok_or_else(|| format!("Invalid attr name {attr}"))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for GraphicRendition {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
|
||||
if deserializer.is_human_readable() {
|
||||
deserializer.deserialize_str(GrVisitor)
|
||||
} else {
|
||||
deserializer.deserialize_u16(GrVisitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::str::FromStr;
|
||||
use super::GraphicRendition;
|
||||
#[test]
|
||||
fn from_str_1() {
|
||||
assert_eq!(GraphicRendition::from_str("underline,blink"), Ok(GraphicRendition::BLINK | GraphicRendition::UNDERLINE))
|
||||
}
|
||||
}
|
||||
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod b3270;
|
||||
pub mod tracker;
|
||||
0
src/proto.rs
Normal file
0
src/proto.rs
Normal file
5
src/tracker.rs
Normal file
5
src/tracker.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
pub struct Tracker {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user