termini-1.0.0/.cargo_vcs_info.json0000644000000001360000000000100125010ustar { "git": { "sha1": "44564df9cb1d188c4c32e0158dbd57a36008b636" }, "path_in_vcs": "" }termini-1.0.0/.gitignore000064400000000000000000000005341046102023000132630ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # Added by cargo /target termini-1.0.0/Cargo.toml0000644000000016100000000000100104750ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "termini" version = "1.0.0" authors = ["pascalkuthe "] exclude = [ "fuzz", "tests", ] description = "Minimal terminfo libary." readme = "README.md" keywords = [ "terminal", "terminfo", "termcap", "term", ] categories = ["command-line-interface"] license = "MIT" repository = "https://github.com/pascalkuthe/termini" [dependencies.home] version = "0.5.4" termini-1.0.0/Cargo.toml.orig000064400000000000000000000007441046102023000141650ustar 00000000000000[package] name = "termini" version = "1.0.0" edition = "2021" exclude = ["fuzz", "tests"] authors = ["pascalkuthe "] license = "MIT" description = "Minimal terminfo libary." repository = "https://github.com/pascalkuthe/termini" keywords = ["terminal", "terminfo", "termcap", "term"] categories = ["command-line-interface"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] home = "0.5.4" termini-1.0.0/LICENSE000064400000000000000000000020551046102023000123000ustar 00000000000000MIT License Copyright (c) 2022 Pascal Kuthe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. termini-1.0.0/README.md000064400000000000000000000026351046102023000125560ustar 00000000000000# termini - minimal terminfo [![crates.io](https://img.shields.io/crates/v/termini?style=flat-square)](https://crates.io/crates/termini) ![crates.io](https://img.shields.io/crates/l/termini?style=flat-square) `termini` is a Rust library that provides access to the `terminfo` database. Some highlights of `termini` include: * supports extended capabilities * easy to audit (single dependency, < 1k LOC) * stability (extensively fuzzed to ensure absence of panics) * tested on a wide array of `terminfo` databases `termini`s main differentiating characteristic is that it's focused on providing a very minimal functionality. `termini` only has a single dependency (`home` to query the home directory) and has less than 1k LOC. This means that it's easy to maintain/audit, doesn't introduce additional dependencies/compiletime and has a smaller surface area for bugs. `termini`s parser has been extensively fuzzed with `cargo-fuzz` to ensure that no panics occur even for fully malformed input. Furthermore, `termini` is tested with a large array of compiled `terminifo` data to ensure it produces the correct results. ## Acknowledgements During the implementation of this crate, the following code was used as reference: * [terminfo](https://github.com/meh/rust-terminfo) * [term](https://github.com/Stebalien/term) * [cxterminfo](https://github.com/BxNiom/cx-terminfo) * [pyterminfo](https://github.com/DirectXMan12/py-terminfo) termini-1.0.0/src/capabilities.rs000064400000000000000000000612161046102023000150650ustar 00000000000000/// Known bool capabilities pub enum BoolCapability { /// cub1 wraps from column 0 to last column AutoLeftMargin = 0, /// Terminal has automatic margins AutoRightMargin = 1, /// Beehive (f1=escape, f2=ctrl C) NoEscCtlc, /// Standout not erased by overwriting (hp) CeolStandoutGlitch, /// Newline ignored after 80 columns (Concept) EatNewlineGlitch, /// Can erase overstrikes with a blank EraseOverstrike, /// Generic line type (e.g., dialup, switch) GenericType, /// Hardcopy terminal HardCopy, /// Has a meta key (shift, sets parity bit) HasMetaKey, /// Has extra 'status line' HasStatusLine, /// Insert mode distinguishes nulls InsertNullGlitch, /// Display may be retained above the screen MemoryAbove, /// Display may be retained below the screen MemoryBelow, /// Safe to move while in insert mode MoveInsertMode, /// Safe to move in standout modes MoveStandoutMode, /// Terminal overstrikes on hard-copy terminal OverStrike, /// Escape can be used on the status line StatusLineEscOk, /// Destructive tabs, magic smso char (t1061) DestTabsMagicSmso, /// Hazeltine; can't print tilde (~) TildeGlitch, /// Underline character overstrikes TransparentUnderline, /// Terminal uses xon/xoff handshaking XonXoff, /// Padding won't work, xon/xoff required NeedsXonXoff, /// Printer won't echo on screen PrtrSilent, /// Cursor is hard to see HardCursor, /// smcup does not reverse rmcup NonRevRmcup, /// Pad character doesn't exist NoPadChar, /// Scrolling region is nondestructive NonDestScrollRegion, /// Terminal can re-define existing colour CanChange, /// Screen erased with background colour BackColorErase, /// Terminal uses only HLS colour notation (Tektronix) HueLightnessSaturation, /// Only positive motion for hpa/mhpa caps ColAddrGlitch, /// Using cr turns off micro mode CrCancelsMicroMode, /// Printer needs operator to change character set HasPrintWheel, /// Only positive motion for vpa/mvpa caps RowAddrGlitch, /// Printing in last column causes cr SemiAutoRightMargin, /// Changing character pitch changes resolution CpiChangesRes, /// Changing line pitch changes resolution LpiChangesRes, } /// Known number capabilities pub enum NumberCapability { /// Number of columns in a line Columns = 0, /// Tabs initially every # spaces InitTabs = 1, /// Number of lines on a screen or a page Lines, /// Lines of memory if > lines; 0 means varies LinesOfMemory, /// Number of blank characters left by smso or rmso MagicCookieGlitch, /// Lowest baud rate where padding needed PaddingBaudRate, /// Virtual terminal number VirtualTerminal, /// Number of columns in status line WidthStatusLine, /// Number of labels on screen (start at 1) NumLabels, /// Number of rows in each label LabelHeight, /// Number of columns in each label LabelWidth, /// Maximum combined video attributes terminal can display MaxAttributes, /// Maximum number of definable windows MaximumWindows, /// Maximum number of colours on the screen MaxColors, /// Maximum number of colour-pairs on the screen MaxPairs, /// Video attributes that can't be used with colours NoColorVideo, /// Number of bytes buffered before printing BufferCapacity, /// Spacing of pins vertically in pins per inch DotVertSpacing, /// Spacing of dots horizontally in dots per inch DotHorzSpacing, /// Maximum value in micro address MaxMicroAddress, /// Maximum value in parm micro MaxMicroJump, /// Character step size when in micro mode MicroColSize, /// Line step size when in micro mode MicroLineSize, /// Number of pins in print-head NumberOfPins, /// Horizontal resolution in units per character OutputResChar, /// Vertical resolution in units per line OutputResLine, /// Horizontal resolution in units per inch OutputResHorzInch, /// Vertical resolution in units per inch OutputResVertInch, /// Print rate in characters per second PrintRate, /// Character step size when in double-wide mode WideCharSize, /// Number of buttons on the mouse Buttons, /// Number of passes for each bit-map row BitImageEntwining, /// Type of bit image device BitImageType, } /// Known string capabilities pub enum StringCapability { /// Back tab BackTab = 0, /// Audible signal (bell) Bell = 1, /// Carriage return CarriageReturn, /// Change to lines #1 through #2 (VT100) ChangeScrollRegion, /// Clear all tab stops ClearAllTabs, /// Clear screen and home cursor ClearScreen, /// Clear to end of line ClearEOL, /// Clear to end of display ClearEOS, /// Set horizontal position to absolute #1 ColumnAddress, /// Terminal settable cmd characterin prototype CommandCharacter, /// Move to row #1 col #2 CursorAddress, /// Down one line CursorDown, /// Home cursor (if no cup) CursorHome, /// Make cursor invisible CursorInvisible, /// Move left one space. CursorLeft, /// Memory relative cursor addressing CursorMemAddress, /// Make cursor appear normal (undo vs/vi) CursorNormal, /// Non-destructive space (cursor or carriage right) CursorRight, /// Last line, first column (if no cup) CursorToLastLine, /// Upline (cursor up) CursorUp, /// Make cursor very visible CursorVisible, /// Delete character DeleteCharacter, /// Delete line DeleteLine, /// Disable status line DisStatusLine, /// Half-line down (forward 1/2 linefeed) DownHalfLine, /// Start alternate character set EnterAltCharsetMode, /// Turn on blinking EnterBlinkMode, /// Turn on bold (extra bright) mode EnterBoldMode, /// String to begin programs that use cup EnterAlternativeMode, /// Delete mode (enter) EnterDeleteMode, /// Turn on half-bright mode EnterDimMode, /// Insert mode (enter) EnterInsertMode, /// Turn on blank mode (characters invisible) EnterSecureMode, /// Turn on protected mode EnterProtectedMode, /// Turn on reverse video mode EnterReverseMode, /// Begin standout mode EnterStandoutMode, /// Start underscore mode EnterUnderlineMode, /// Erase #1 characters EraseChars, /// End alternate character set ExitAltCharsetMode, /// Turn off all attributes ExitAttributeMode, /// String to end programs that use cup ExitAlternativeMode, /// End delete mode ExitDeleteMode, /// End insert mode ExitInsertMode, /// End standout mode ExitStandoutMode, /// End underscore mode ExitUnderlineMode, /// Visible bell (may move cursor) FlashScreen, /// Hardcopy terminal page eject FormFeed, /// Return from status line FromStatusLine, /// Terminal or printer initialisation string Init1String, /// Terminal or printer initialisation string Init2String, /// Terminal or printer initialisation string Init3String, /// Name of initialisation file InitFile, /// Insert character InsertCharacter, /// Add new blank line InsertLine, /// Insert pad after character inserted InsertPadding, /// sent by backspace key KeyBackspace, /// sent by clear-all-tabs key KeyClearAllTabs, /// sent by clear-screen or erase key KeyClear, /// sent by clear-tab key KeyClearTab, /// sent by delete-character key KeyDeleteCharacter, /// sent by delete-line key KeyDeleteLine, /// sent by terminal down-arrow key KeyDown, /// sent by rmir or smir in insert mode KeyEic, /// sent by clear-to-end-of-line key KeyClearEOL, /// sent by clear-to-end-of-screen key KeyClearEOS, /// sent by function key f0 KeyF0, /// sent by function key f1 KeyF1, /// sent by function key f10 KeyF10, /// sent by function key f2 KeyF2, /// sent by function key f3 KeyF3, /// sent by function key f4 KeyF4, /// sent by function key f5 KeyF5, /// sent by function key f6 KeyF6, /// sent by function key f7 KeyF7, /// sent by function key f8 KeyF8, /// sent by function key f9 KeyF9, /// sent by home key KeyHome, /// sent by ins-char/enter ins-mode key KeyInsertCharacter, /// sent by insert-line key KeyInsertLine, /// sent by terminal left-arrow key KeyLeft, /// sent by home-down key KeyLastLine, /// sent by next-page key KeyNextPage, /// sent by previous-page key KeyPreviousPage, /// sent by terminal right-arrow key KeyRight, /// sent by scroll-forward/down key KeyScrollForward, /// sent by scroll-backward/up key KeyScrollBackward, /// sent by set-tab key KeySetTab, /// sent by terminal up-arrow key KeyUp, /// Out of 'keypad-transmit' mode KeypadLocal, /// Put terminal in 'keypad-transmit' mode KeypadXmit, /// Labels on function key f0 if not f0 LabF0, /// Labels on function key f1 if not f1 LabF1, /// Labels on function key f10 if not f10 LabF10, /// Labels on function key f2 if not f2 LabF2, /// Labels on function key f3 if not f3 LabF3, /// Labels on function key f4 if not f4 LabF4, /// Labels on function key f5 if not f5 LabF5, /// Labels on function key f6 if not f6 LabF6, /// Labels on function key f7 if not f7 LabF7, /// Labels on function key f8 if not f8 LabF8, /// Labels on function key f9 if not f9 LabF9, /// Turn off 'meta mode' MetaOff, /// Turn on 'meta mode' (8th bit) MetaOn, /// Newline (behaves like cr followed by lf) Newline, /// Pad character (rather than null) PadChar, /// Delete #1 chars ParmDeleteCharacters, /// Delete #1 lines ParmDeleteLine, /// Move down #1 lines. ParmDownCursor, /// Insert #1 blank chars ParmInsertCharacters, /// Scroll forward #1 lines. ParmIndex, /// Add #1 new blank lines ParmInsertLine, /// Move cursor left #1 spaces ParmLeftCursor, /// Move right #1 spaces. ParmRightCursor, /// Scroll backward #1 lines. ParmReverseIndex, /// Move cursor up #1 lines. ParmUpCursor, /// Prog funct key #1 to type string #2 PKeyKey, /// Prog funct key #1 to execute string #2 PKeyLocal, /// Prog funct key #1 to xmit string #2 PKeyXmit, /// Print contents of the screen PrintScreen, /// Turn off the printer PrinterOff, /// Turn on the printer PrinterOn, /// Repeat char #1 #2 times RepeatChar, /// Reset terminal completely to sane modes Reset1String, /// Reset terminal completely to sane modes Reset2String, /// Reset terminal completely to sane modes Reset3String, /// Name of file containing reset string ResetFile, /// Restore cursor to position of last sc RestoreCursor, /// Set vertical position to absolute #1 RowAddress, /// Save cursor position SaveCursor, /// Scroll text up ScrollForward, /// Scroll text down ScrollReverse, /// Define first set of video attributes #1-#9 SetAttributes, /// Set a tab in all rows, current column SetTab, /// Current window is lines #1-#2 cols #3-#4 SetWindow, /// Tab to next 8-space hardware tab stop Tab, /// Go to status line, col #1 ToStatusLine, /// Underscore one char and move past it UnderlineChar, /// Half-line up (reverse 1/2 linefeed) UpHalfLine, /// Path name of program for initialisation InitProg, /// upper left of keypad KeyA1, /// upper right of keypad KeyA3, /// center of keypad KeyB2, /// lower left of keypad KeyC1, /// lower right of keypad KeyC3, /// Turn on the printer for #1 bytes PrinterOnForNBytes, /// Like ip but when in replace mode CharPadding, /// Graphic charset pairs aAbBcC AcsChars, /// Prog label #1 to show string #2 PlabNorm, /// sent by back-tab key KeyBackTab, /// Turn on xon/xoff handshaking EnterXonMode, /// Turn off xon/xoff handshaking ExitXonMode, /// Turn on automatic margins EnterAutomaticMarginsMode, /// Turn off automatic margins ExitAutomaticMarginsMode, /// X-on character XOnCharacter, /// X-off character XOffCharacter, /// Enable alternate character set EnableAlternateCharSet, /// Turn on soft labels LabelOn, /// Turn off soft labels LabelOff, /// 1 KeyBegin, /// 2 KeyCancel, /// 3 KeyClose, /// 4 KeyCommand, /// 5 KeyCopy, /// 6 KeyCreate, /// 7 KeyEnd, /// 8 KeyEnter, /// 9 KeyExit, /// 0 KeyFind, /// sent by help key KeyHelp, /// sent by mark key KeyMark, /// sent by message key KeyMessage, /// sent by move key KeyMove, /// sent by next-object key KeyNext, /// sent by open key KeyOpen, /// sent by options key KeyOptions, /// sent by previous-object key KeyPrevious, /// sent by print or copy key KeyPrint, /// sent by redo key KeyRedo, /// sent by ref(erence) key KeyReference, /// sent by refresh key KeyRefresh, /// sent by replace key KeyReplace, /// sent by restart key KeyRestart, /// sent by resume key KeyResume, /// sent by save key KeySave, /// sent by suspend key KeySuspend, /// sent by undo key KeyUndo, /// sent by shifted beginning key KeyShiftBegin, /// sent by shifted cancel key KeyShiftCancel, /// sent by shifted command key KeyShiftCommand, /// sent by shifted copy key KeyShiftCopy, /// sent by shifted create key KeyShiftCreate, /// sent by shifted delete-char key KeyShiftDeleteChar, /// sent by shifted delete-line key KeyShiftDeleteLine, /// sent by select key KeySelect, /// sent by shifted end key KeyShiftEnd, /// sent by shifted clear-line key KeyShiftEOL, /// sent by shifted exit key KeyShiftExit, /// sent by shifted find key KeyShiftFind, /// #1 sent by shifted help key KeyShiftHelp, /// #2 sent by shifted home key KeyShiftHome, /// #3 sent by shifted input key KeyShiftInputKey, /// #4 sent by shifted left-arrow key KeyShiftLeft, /// sent by shifted message key KeyShiftMessage, /// sent by shifted move key KeyShiftMove, /// sent by shifted next key KeyShiftNext, /// sent by shifted options key KeyShiftOptions, /// sent by shifted prev key KeyShiftPrevious, /// sent by shifted print key KeyShiftPrint, /// sent by shifted redo key KeyShiftRedo, /// sent by shifted replace key KeyShiftReplace, /// sent by shifted right-arrow key KeyShiftRight, /// sent by shifted resume key KeyShiftResume, /// !1 sent by shifted save key KeyShiftSave, /// !2 sent by shifted suspend key KeyShiftSuspend, /// !3 sent by shifted undo key KeyShiftUndo, /// Send next input char (for ptys) ReqForInput, /// sent by function key f11 KeyF11, /// sent by function key f12 KeyF12, /// sent by function key f13 KeyF13, /// sent by function key f14 KeyF14, /// sent by function key f15 KeyF15, /// sent by function key f16 KeyF16, /// sent by function key f17 KeyF17, /// sent by function key f18 KeyF18, /// sent by function key f19 KeyF19, /// sent by function key f20 KeyF20, /// sent by function key f21 KeyF21, /// sent by function key f22 KeyF22, /// sent by function key f23 KeyF23, /// sent by function key f24 KeyF24, /// sent by function key f25 KeyF25, /// sent by function key f26 KeyF26, /// sent by function key f27 KeyF27, /// sent by function key f28 KeyF28, /// sent by function key f29 KeyF29, /// sent by function key f30 KeyF30, /// sent by function key f31 KeyF31, /// sent by function key f32 KeyF32, /// sent by function key f33 KeyF33, /// sent by function key f34 KeyF34, /// sent by function key f35 KeyF35, /// sent by function key f36 KeyF36, /// sent by function key f37 KeyF37, /// sent by function key f38 KeyF38, /// sent by function key f39 KeyF39, /// sent by function key f40 KeyF40, /// sent by function key f41 KeyF41, /// sent by function key f42 KeyF42, /// sent by function key f43 KeyF43, /// sent by function key f44 KeyF44, /// sent by function key f45 KeyF45, /// sent by function key f46 KeyF46, /// sent by function key f47 KeyF47, /// sent by function key f48 KeyF48, /// sent by function key f49 KeyF49, /// sent by function key f50 KeyF50, /// sent by function key f51 KeyF51, /// sent by function key f52 KeyF52, /// sent by function key f53 KeyF53, /// sent by function key f54 KeyF54, /// sent by function key f55 KeyF55, /// sent by function key f56 KeyF56, /// sent by function key f57 KeyF57, /// sent by function key f58 KeyF58, /// sent by function key f59 KeyF59, /// sent by function key f60 KeyF60, /// sent by function key f61 KeyF61, /// sent by function key f62 KeyF62, /// sent by function key f63 KeyF63, /// Clear to beginning of line, inclusive ClearBOL, /// Clear all margins (top, bottom, and sides) ClearMargins, /// Set left margin at current column SetLeftMargin, /// Set right margin at current column SetRightMargin, /// Label format LabelFormat, /// Set clock to hours (#1), minutes (#2), seconds (#3) SetClock, /// Display time-of-day clock DisplayClock, /// Remove time-of-day clock RemoveClock, /// Define win #1 to go from #2,#3 to #4,#5 CreateWindow, /// Go to window #1 GotoWindow, /// Hang-up phone Hangup, /// Dial phone number #1 DialPhone, /// Dial phone number #1, without progress detection QuickDial, /// Select touch tone dialing Tone, /// Select pulse dialing Pulse, /// Flash the switch hook FlashHook, /// Pause for 2-3 seconds FixedPause, /// Wait for dial tone WaitTone, /// User string 0 User0, /// User string 1 User1, /// User string 2 User2, /// User string 3 User3, /// User string 4 User4, /// User string 5 User5, /// User string 6 User6, /// User string 7 User7, /// User string 8 User8, /// User string 9 User9, /// Set default colour-pair to the original one OrigColorPair, /// Set all colour(-pair)s to the original ones OrigColors, /// Set colour #1 to RGB #2, #3, #4 InitializeColor, /// Set colour-pair #1 to fg #2, bg #3 InitializePair, /// Set current colour pair to #1 SetColorPair, /// Set foreground colour to #1 SetForeground, /// Set background colour to #1 SetBackground, /// Change number of characters per inch ChangeCharPitch, /// Change number of lines per inch ChangeLinePitch, /// Change horizontal resolution ChangeResHorz, /// Change vertical resolution ChangeResVert, /// Define a character in a character set DefineChar, /// Enable double wide printing EnterDoublewideMode, /// Set draft quality print EnterDraftQuality, /// Enable italics EnterItalicsMode, /// Enable leftward carriage motion EnterLeftwardMode, /// Enable micro motion capabilities EnterMicroMode, /// Set near-letter quality print EnterNearLetterQuality, /// Set normal quality print EnterNormalQuality, /// Enable shadow printing EnterShadowMode, /// Enable subscript printing EnterSubscriptMode, /// Enable superscript printing EnterSuperscriptMode, /// Enable upward carriage motion EnterUpwardMode, /// Disable double wide printing ExitDoublewideMode, /// Disable italics ExitItalicsMode, /// Enable rightward (normal) carriage motion ExitLeftwardMode, /// Disable micro motion capabilities ExitMicroMode, /// Disable shadow printing ExitShadowMode, /// Disable subscript printing ExitSubscriptMode, /// Disable superscript printing ExitSuperscriptMode, /// Enable downward (normal) carriage motion ExitUpwardMode, /// Like columnaddress for micro adjustment MicroColumnAddress, /// Like cursordown for micro adjustment MicroDown, /// Like cursorleft for micro adjustment MicroLeft, /// Like cursorright for micro adjustment MicroRight, /// Like rowaddress for micro adjustment MicroRowAddress, /// Like cursorup for micro adjustment MicroUp, /// Matches software bits to print-head pins OrderOfPins, /// Like parmdowncursor for micro adjust. ParmDownMicro, /// Like parmleftcursor for micro adjust. ParmLeftMicro, /// Like parmrightcursor for micro adjust. ParmRightMicro, /// Like parmupcursor for micro adjust. ParmUpMicro, /// Select character set SelectCharSet, /// Set bottom margin at current line SetBottomMargin, /// Set bottom margin at line #1 or #2 lines from bottom SetBottomMarginParm, /// Set left (right) margin at column #1 (#2) SetLeftMarginParm, /// Set right margin at column #1 SetRightMarginParm, /// Set top margin at current line SetTopMargin, /// Set top (bottom) margin at line #1 (#2) SetTopMarginParm, /// Start printing bit image graphics StartBitImage, /// Start definition of a character set StartCharSetDef, /// End printing bit image graphics StopBitImage, /// End definition of a character set StopCharSetDef, /// List of 'subscript-able' characters SubscriptCharacters, /// List of 'superscript-able' characters SuperscriptCharacters, /// Printing any of these chars causes cr TheseCauseCr, /// No motion for the subsequent character ZeroMotion, /// Returns a list of character set names CharSetNames, /// 0631, Mouse event has occured KeyMouse, /// Mouse status information MouseInfo, /// Request mouse position report ReqMousePos, /// Curses should get button events GetMouse, /// Set foreground colour to #1 using ANSI escape SetAnsiForeground, /// Set background colour to #1 using ANSI escape SetAnsiBackground, /// Prog key #1 to xmit string #2 and show string #3 PKeyPlab, /// Indicate language/codeset support DeviceType, /// Init sequence for multiple codesets CodeSetInit, /// Shift into codeset 0 (EUC set 0, ASCII) Set0DesSeq, /// Shift into codeset 1 Set1DesSeq, /// Shift into codeset 2 Set2DesSeq, /// Shift into codeset 3 Set3DesSeq, /// Sets both left and right margins SetLrMargin, /// Sets both top and bottom margins SetTbMargin, /// Repeat bit-image cell #1 #2 times BitImageRepeat, /// Move to next row of the bit image BitImageNewline, /// Move to beginning of same row BitImageCarriageReturn, /// Give name for colour #1 ColorNames, /// Define rectangular bit-image region DefineBitImageRegion, /// End a bit-image region EndBitImageRegion, /// Change to ribbon colour #1 SetColorBand, /// Set page length to #1 lines SetPageLength, /// Display PC character DisplayPcChar, /// Enter PC character display mode EnterPcCharsetMode, /// Disable PC character display mode ExitPcCharsetMode, /// Enter PC scancode mode EnterScancodeMode, /// Disable PC scancode mode ExitScancodeMode, /// PC terminal options PcTermOptions, /// Escape for scancode emulation ScancodeEscape, /// Alternate escape for scancode emulation (default is for VT100) AltScancodeEsc, /// Turn on horizontal highlight mode EnterHorizontalHlMode, /// Turn on left highlight mode EnterLeftHlMode, /// Turn on low highlight mode EnterLowHlMode, /// Turn on right highlight mode EnterRightHlMode, /// Turn on top highlight mode EnterTopHlMode, /// Turn on vertical highlight mode EnterVerticalHlMode, /// Define second set of video attributes #1-#6 SetAAttributes, /// Set page length to #1 hundredth of an inch SetPageLenInch, } termini-1.0.0/src/lib.rs000064400000000000000000000220561046102023000132010ustar 00000000000000#![forbid(unsafe_code)] use std::collections::HashMap; use std::fs::{self, File}; use std::path::{Path, PathBuf}; use std::string::FromUtf8Error; use std::{env, io}; pub use crate::capabilities::{BoolCapability, NumberCapability, StringCapability}; mod capabilities; mod parsing; #[cfg(test)] mod tests; /// Terminfo database information #[derive(Debug, Default)] pub struct TermInfo { pub name: String, pub description: String, pub aliases: Vec, data: TermInfoData, extended: Extended, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Value<'a> { True, RawString(&'a [u8]), Utf8String(&'a str), Number(i32), } #[derive(Debug)] pub enum Error { NotFound, InvalidMagicNum(i16), Io(io::Error), NoNames, NamesMissingNull, StringMissingNull, OutOfBoundString { off: u16, table_size: u16 }, InvalidUtf8(FromUtf8Error), InvalidNames, } impl std::error::Error for Error { fn source(&self) -> std::option::Option<&(dyn std::error::Error + 'static)> { #[allow(deprecated)] match self { Error::Io(source) => Some(source as _), Error::InvalidUtf8(source) => Some(source as _), _ => None, } } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Error::NotFound {} => write!(f, "Terminfo file not found"), Error::InvalidMagicNum(num) => write!(f, "bad magic number {num} in terminfo header",), Error::Io(_) => write!(f, "reading terminfo failed",), Error::NoNames => write!(f, "no names exposed, need at least one"), Error::NamesMissingNull => write!(f, "names table missing NUL terminator"), Error::StringMissingNull => { write!(f, "string table missing NUL terminator") } Error::OutOfBoundString { off, table_size } => write!( f, "string offset {off} outside data table (size: {table_size})", ), Error::InvalidUtf8(_) => write!(f, "terminfo string is invalid ASCII/UTF-8",), Error::InvalidNames {} => write!(f, "no names exposed, need at least one"), } } } impl std::convert::From for Error { #[allow(deprecated)] fn from(source: io::Error) -> Self { Error::Io(source) } } impl std::convert::From for Error { #[allow(deprecated)] fn from(source: FromUtf8Error) -> Self { Error::InvalidUtf8(source) } } #[derive(Debug, Default)] struct Extended { capabilities: HashMap, table: Box<[u8]>, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] enum ValueStorage { True, String(u16), Number(i32), } #[derive(Debug, Default)] struct TermInfoData { bools: Box<[bool]>, numbers: Box<[i32]>, strings: Box<[u16]>, str_table: Box<[u8]>, } /// gets a string from the `str_table` starting at `start`. /// /// **panics** if start is larger than the size of string table fn get_str_with_offset(table: &[u8], start: u16, offset: u16) -> Option<&[u8]> { // // non-entry // // undocumented: `FFFE` indicates cap@, which means the capability // // is not present if matches!(start, 0xffff | 0xfffe) { return None; } let table = &table[(start + offset) as usize..]; let res = table .iter() .position(|&c| c == b'\0') .map_or(table, |end| &table[..end]); Some(res) } impl TermInfoData { /// gets a string from the `str_table` starting at `start`. /// /// **panics** if start is larger than the size of string table fn get_str_at(&self, start: u16) -> Option<&[u8]> { get_str_with_offset(&self.str_table, start, 0) } } impl TermInfo { /// Returns the string value for the capability /// /// # Arguments /// * `cap` - string capability /// /// # Example /// ``` /// use termini::TermInfo; /// use termini::StringCapability; /// /// if let Ok(info) = TermInfo::from_env() { /// println!("{:?}", info.raw_string_cap(StringCapability::Bell)); /// } /// ``` pub fn raw_string_cap(&self, cap: StringCapability) -> Option<&[u8]> { let off = *self.data.strings.get(cap as usize)?; self.data.get_str_at(off) } /// Returns the string value for the capability. /// If the capability is invalid UTF-8 (ASCII) or doesn't exists `None` is returned /// /// # Arguments /// * `cap` - string capability /// /// # Example /// ``` /// use termini::TermInfo; /// use termini::StringCapability; /// /// if let Ok(info) = TermInfo::from_env() { /// println!("{:?}", info.utf8_string_cap(StringCapability::Bell)); /// } /// ``` pub fn utf8_string_cap(&self, cap: StringCapability) -> Option<&str> { let off = *self.data.strings.get(cap as usize)?; std::str::from_utf8(self.data.get_str_at(off)?).ok() } /// Returns the number value for the capability /// /// # Arguments /// * `cap` - number capability /// /// # Example /// ``` /// use termini::TermInfo; /// use termini::NumberCapability; /// /// if let Ok(info) = TermInfo::from_env() { /// println!("{:?}", info.number_cap(NumberCapability::MaxColors)); /// } /// ``` pub fn number_cap(&self, cap: NumberCapability) -> Option { self.data .numbers .get(cap as usize) .copied() .filter(|&val| val != 0xffff) } /// Returns the bool value for the capability, if the capability is not present, /// `false` is returned /// /// # Arguments /// * `cap` - bool capability /// /// # Example /// ``` /// use termini::TermInfo; /// use termini::BoolCapability; /// /// if let Ok(info) = TermInfo::from_env() { /// println!("{:?}", info.flag_cap(BoolCapability::AutoLeftMargin)); /// } /// ``` pub fn flag_cap(&self, cap: BoolCapability) -> bool { self.data.bools.get(cap as usize).copied().unwrap_or(false) } pub fn extended_cap(&self, name: &str) -> Option { let res = match *self.extended.capabilities.get(name)? { ValueStorage::True => Value::True, ValueStorage::String(off) => { let raw = get_str_with_offset(&self.extended.table, off, 0)?; match std::str::from_utf8(raw) { Ok(res) => Value::Utf8String(res), Err(_) => Value::RawString(raw), } } ValueStorage::Number(val) => Value::Number(val), }; Some(res) } /// Create TermInfo database, using TERM environment var. pub fn from_env() -> Result { if let Ok(term) = std::env::var("TERM") { TermInfo::from_name(term.as_str()) } else { Err(Error::NotFound) } } /// Create TermInfo database for the given name pub fn from_name(name: &str) -> Result { let first = name.chars().next().ok_or(Error::NotFound)?; // See https://manpages.debian.org/buster/ncurses-bin/TermInfo.5.en.html#Fetching_Compiled_Descriptions let mut search = Vec::::new(); if let Some(dir) = env::var_os("TERMINFO") { search.push(dir.into()); } else if let Some(mut home) = home::home_dir() { home.push(".terminfo"); search.push(home); } if let Ok(dirs) = env::var("TERMINFO_DIRS") { for dir in dirs.split(':') { search.push(dir.into()); } } // handle non-FHS systems like Termux if let Ok(prefix) = env::var("PREFIX") { let path = Path::new(&prefix); search.push(path.join("etc/terminfo")); search.push(path.join("lib/terminfo")); search.push(path.join("share/terminfo")); } search.push("/etc/terminfo".into()); search.push("/lib/terminfo".into()); search.push("/usr/share/terminfo".into()); search.push("/boot/system/data/terminfo".into()); for path in search { if fs::metadata(&path).is_err() { continue; } // Check standard location. { let mut path = path.clone(); path.push(first.to_string()); path.push(name); if fs::metadata(&path).is_ok() { return Self::from_path(&path); } } // Check non-standard location. let mut path = path.clone(); path.push(format!("{:x}", first as usize)); path.push(name); if fs::metadata(&path).is_ok() { return Self::from_path(&path); } } Err(Error::NotFound) } /// Rad a TermInfo database from a given path pub fn from_path(file: impl AsRef) -> Result { TermInfo::parse(File::open(file)?) } } termini-1.0.0/src/parsing.rs000064400000000000000000000201121046102023000140650ustar 00000000000000use crate::{get_str_with_offset, Error, Extended, TermInfo, TermInfoData, ValueStorage}; use std::collections::HashMap; use std::io; use std::io::Read; /// magic number octal 0432 for legacy ncurses terminfo const MAGIC_LEGACY: i16 = 0x11A; /// magic number octal 01036 for new ncurses terminfo const MAGIC_32BIT: i16 = 0x21E; impl TermInfoData { /// Create terminfo database by parse byte-array directly pub fn parse( mut reader: R, numbers_32bit: bool, bool_cnt: u16, numbers_cnt: u16, string_cnt: u16, table_bytes: u16, aligned: bool, ) -> Result { let bools = (0..bool_cnt) .map(|_| match read_byte(&mut reader) { Err(e) => Err(e), Ok(1) => Ok(true), Ok(_) => Ok(false), }) .collect::>()?; if bool_cnt % 2 == aligned.into() { read_byte(&mut reader)?; // compensate for padding } let numbers = (0..numbers_cnt) .map(|_| { if numbers_32bit { read_i32(&mut reader) } else { read_i16(&mut reader).map(i32::from) } }) .collect::>()?; let strings: Box<[_]> = (0..string_cnt) .map(|_| read_u16(&mut reader)) .collect::>()?; for &off in &*strings { if matches!(off, 0..=0xfffd if off > table_bytes) { return Err(Error::OutOfBoundString { off, table_size: table_bytes, }); } } let mut str_table = Vec::new(); let read = reader .take(table_bytes.into()) .read_to_end(&mut str_table)?; if read != table_bytes as usize { return Err(io::Error::new(io::ErrorKind::Other, "end of file").into()); } Ok(TermInfoData { bools, numbers, strings, str_table: str_table.into_boxed_slice(), }) } } impl TermInfo { /// Create terminfo database by parse byte-array directly pub fn parse(mut reader: R) -> Result { // read the magic number. let magic = read_i16(&mut reader)?; let number_32bit = match magic { MAGIC_LEGACY => false, MAGIC_32BIT => true, num => return Err(Error::InvalidMagicNum(num)), }; let names_bytes = read_non_neg_i16(&mut reader)?; let bool_count = read_non_neg_i16(&mut reader)?; let numbers_count = read_non_neg_i16(&mut reader)?; let string_count = read_non_neg_i16(&mut reader)?; let string_table_bytes = read_non_neg_i16(&mut reader)?; if names_bytes == 0 { return Err(Error::NoNames); } let term_names = read_str(&mut reader, names_bytes - 1)?; let mut term_names = term_names.split('|').map(|it| it.trim().to_owned()); let name = term_names.next().unwrap(); let mut aliases: Vec<_> = term_names.collect(); if read_byte(&mut reader)? != b'\0' { return Err(Error::NamesMissingNull); } let data = TermInfoData::parse( &mut reader, number_32bit, bool_count, numbers_count, string_count, string_table_bytes, names_bytes % 2 == 0, )?; let extended = try_parse_ext_capabilities(reader, number_32bit, string_table_bytes % 2 == 1) .unwrap_or_default(); let res = TermInfo { name, description: aliases.pop().unwrap_or_default(), aliases, data, extended, }; Ok(res) } } fn try_parse_ext_capabilities( mut reader: impl io::Read, number_32bit: bool, unaligned: bool, ) -> Result { if unaligned { read_byte(&mut reader)?; // compensate for padding } // the term(5) manpage doesn't describe this properly // what is calls "the size of the string table" is // actually the number of strings in the table (including names), // and what it calls the "last offset in the string table" is // actually the size in bytes of the string table let bool_count = read_non_neg_i16(&mut reader)?; let num_count = read_non_neg_i16(&mut reader)?; let string_count = read_non_neg_i16(&mut reader)?; let num_strings_in_table = read_non_neg_i16(&mut reader)?; let table_bytes = read_non_neg_i16(&mut reader)?; // debug_assert!(num_strings_in_table >= bool_count + num_count + 2 * string_count); // TODO bounds checks let data = TermInfoData::parse( &mut reader, number_32bit, bool_count, num_count, num_strings_in_table, table_bytes, true, )?; if string_count as usize >= data.strings.len() { return Err(Error::InvalidNames); } let names_off = data.strings[..string_count as usize] .iter() .rev() .filter_map(|&off| Some(off + data.get_str_at(off)?.len() as u16)) .max() .unwrap_or(0) + 1; let mut names = data.strings[string_count as usize..].iter().map(|&off| { if matches!(off, 0..=0xfffd if off as usize + names_off as usize >= table_bytes as usize) { return Some(Err(Error::OutOfBoundString { off: off + names_off, table_size: table_bytes, })); } let res = get_str_with_offset(&*data.str_table, off, names_off)?.to_owned(); match String::from_utf8(res) { Ok(res) => Some(Ok(res)), Err(err) => Some(Err(err.into())), } }); let mut capabilities = HashMap::with_capacity((bool_count + num_count + string_count) as usize); for (&val, name) in data.bools.iter().zip(&mut names) { if let Some(name) = name { if val { capabilities.insert(name?, ValueStorage::True); } } } for (&val, name) in data.numbers.iter().zip(&mut names) { if let Some(name) = name { if val != 0xffff { capabilities.insert(name?, ValueStorage::Number(val)); } } } for (&val, name) in data.strings.iter().zip(&mut names) { if let Some(name) = name { if !matches!(val, 0xffff | 0xfffe) { capabilities.insert(name?, ValueStorage::String(val)); } } } let mut str_table = Vec::from(data.str_table); str_table.truncate(names_off as usize); Ok(Extended { capabilities, table: str_table.into_boxed_slice(), }) } fn read_i16(mut data: R) -> Result { let mut buf = [0; 2]; data.read_exact(&mut buf)?; Ok(i16::from_le_bytes(buf)) } fn read_u16(mut data: R) -> Result { let mut buf = [0; 2]; data.read_exact(&mut buf)?; Ok(u16::from_le_bytes(buf)) } /// According to the spec, these fields must be >= -1 where -1 means that the /// feature is not /// supported. Using 0 instead of -1 works because we skip sections with length /// 0. fn read_non_neg_i16(data: impl io::Read) -> Result { match read_i16(data)? { n @ 0.. => Ok(n as u16), -1 => Ok(0), _ => Err(Error::InvalidNames), } } fn read_i32(mut data: R) -> Result { let mut buf = [0; 4]; data.read_exact(&mut buf)?; Ok(i32::from_le_bytes(buf)) } fn read_str(data: impl io::Read, size: u16) -> Result { let mut bytes = Vec::new(); let read = data.take(size.into()).read_to_end(&mut bytes)?; if read != size as usize { return Err(io::Error::new(io::ErrorKind::Other, "end of file").into()); } let bytes = String::from_utf8(bytes)?; Ok(bytes) } fn read_byte(r: &mut impl io::Read) -> io::Result { match r.bytes().next() { Some(s) => s, None => Err(io::Error::new(io::ErrorKind::Other, "end of file")), } } termini-1.0.0/src/tests.rs000064400000000000000000000041431046102023000135720ustar 00000000000000use std::fs; use crate::{BoolCapability, NumberCapability, StringCapability, TermInfo, Value}; #[test] fn name() { let db = TermInfo::from_path("tests/cancer-256color").unwrap(); assert_eq!("cancer-256color", db.name) } #[test] fn aliases() { let db = TermInfo::from_path("tests/st-256color").unwrap(); assert_eq!(vec!["stterm-256color"], db.aliases) } #[test] fn description() { let db = TermInfo::from_path("tests/cancer-256color").unwrap(); assert_eq!("terminal cancer with 256 colors", db.description) } #[test] fn standard() { let db = TermInfo::from_path("tests/st-256color").unwrap(); assert_eq!(Some(80), db.number_cap(NumberCapability::Columns)); assert!(db.flag_cap(BoolCapability::AutoRightMargin)); assert!(!db.flag_cap(BoolCapability::AutoLeftMargin)); assert_eq!( "\r", db.utf8_string_cap(StringCapability::CarriageReturn) .unwrap() ); } #[test] fn extended() { let db = TermInfo::from_path("tests/cancer-256color").unwrap(); assert_eq!(Some(Value::True), db.extended_cap("Ts")); assert_eq!(Some(Value::True), db.extended_cap("AX")); assert_eq!(Some(Value::Utf8String("\u{1b}[2 q")), db.extended_cap("Se")); } #[test] fn bigger_numbers() { let db = TermInfo::from_path("tests/xterm-256color").unwrap(); assert_eq!("xterm-256color", db.name) } #[test] fn alacritty_extended_underculr() { let db = TermInfo::from_path("tests/alacritty").unwrap(); assert_eq!( Some(Value::Utf8String("\u{1b}[4:%p1%dm")), db.extended_cap("Smulx") ); } #[test] fn kitty_extended_underculr() { let db = TermInfo::from_path("tests/xterm-kitty").unwrap(); assert_eq!(Some(Value::True), db.extended_cap("Su")); } #[test] fn crash() { let data: &[u8] = &[ 26, 1, 29, 0, 1, 1, 0, 0, 0, 1, 43, 6, 12, 12, 244, 131, 162, 131, 124, 35, 120, 124, 0, 0, 0, 27, 0, 0, 0, 12, 27, 12, 0, 0, 0, 0, 0, 12, 27, 12, ]; let _ = TermInfo::parse(data); } #[test] fn test_parse() { for f in fs::read_dir("tests/").unwrap() { let _ = TermInfo::from_path(f.unwrap().path()).unwrap(); } }