vty-5.11.1: A simple terminal UI library

Safe HaskellNone
LanguageHaskell2010

Graphics.Vty.Input

Description

The input layer for VTY. This provides methods for initializing an Input structure which can then be used to read Events from the terminal.

The history of terminals has resulted in a broken input process. Some keys and combinations will not reliably map to the expected events by any terminal program. Even those not using vty. There is no 1:1 mapping from key events to bytes read from the terminal input device. In very limited cases the terminal and vty's input process can be customized to resolve these issues.

See Graphics.Vty.Config for how to configure vty's input processing. Customizing terminfo and the terminal is beyond the scope of this documentation.

VTY's Implementation

One can get the brain rot trying to understand all this. So, as far as I can care...

There are two input modes:

  1. 7 bit
  2. 8 bit

7 bit input is the default and the expected in most use cases. This is what vty uses.

7 bit input encoding

Control key combinations are represented by masking the two high bits of the 7bit input. Back in the day the control key actually grounded the two high bit wires: 6 and 7. This is why control key combos map to single character events: The input bytes are identical. The input byte is the bit encoding of the character with bits 6 and 7 masked. Bit 6 is set by shift. Bit 6 and 7 are masked by control. EG:

  • Control-I is i, `01101001`, has bit 6 and 7 masked to become `00001001`. Which is the ASCII and UTF-8 encoding of the tab key.
  • Control+Shift-C is C, `01000011`, with bit 6 and 7 set to zero which makes `0000011` and is the "End of Text" code.
  • Hypothesis: This is why capital-A, A, has value 65 in ASCII: This is the value 1 with bit 7 set and 6 unset.
  • Hypothesis: Bit 6 is unset by upper case letters because, initially, there were only upper case letters used and a 5 bit encoding.

8 bit encoding

The 8th bit was originally used for parity checking. Useless for emulators. Some terminal emulators support a 8 bit input encoding. While this provides some advantages the actual usage is low. Most systems use 7bit mode but recognize 8bit control characters when escaped. This is what vty does.

Escaped Control Keys

Using 7 bit input encoding the ESC byte can signal the start of an encoded control key. To differentiate a single ESC eventfrom a control key the timing of the input is used.

  1. ESC individually: ESC byte; no bytes for singleEscPeriod.
  2. control keys that contain ESC in their encoding: The @ESC byte; followed by more bytes read within singleEscPeriod. All bytes up until the next valid input block are passed to the classifier.

If the current runtime is the threaded runtime then the terminal's VMIN and VTIME behavior reliably implement the above rules. If the current runtime does not support forkOS then there is currently no implementation.

Vty used to emulate VMIN and VTIME. This was a input loop which did tricky things with non-blocking reads and timers. The implementation was not reliable. A reliable implementation is possible, but there are no plans to implement this.

Unicode Input and Escaped Control Key Sequences

The input encoding determines how UTF-8 encoded characters are recognize.

  • 7 bit mode: UTF-8 can be input unambiguiously. UTF-8 input is a superset of ASCII. UTF-8 does not overlap escaped control key sequences. However, the escape key must be differentiated from escaped control key sequences by the timing of the input bytes.
  • 8 bit mode: UTF-8 cannot be input unambiguously. This does not require using the timing of input bytes to differentiate the escape key. Many terminals do not support 8 bit mode.

Terminfo

The terminfo system is used to determine how some keys are encoded. Terminfo is incomplete. In some cases terminfo is incorrect. Vty assumes terminfo is correct but provides a mechanism to override terminfo. See Graphics.Vty.Config specifically inputOverrides.

Terminal Input is Broken

Clearly terminal input has fundemental issues. There is no easy way to reliably resolve these issues.

One resolution would be to ditch standard terminal interfaces entirely and just go directly to scancodes. A reasonable option for vty if everybody used the linux kernel console. I hear GUIs are popular these days. Sadly, GUI terminal emulators don't provide access to scancodes AFAIK.

All is lost? Not really. Graphics.Vty.Config supports customizing the input byte to event mapping and xterm supports customizing the scancode to input byte mapping. With a lot of work a user's system can be set up to encode all the key combos in an almost-sane manner.

There are other tricky work arounds that can be done. I have no interest in implementing most of these. They are not really worth the time.

Terminal Output is Also Broken

This isn't the only odd aspect of terminals due to historical aspects that no longer apply. EG: Some terminfo capabilities specify millisecond delays. (Capabilities are how terminfo describes the control sequence to output red, for instance) This is to account for the slow speed of hardcopy teletype interfaces. Cause, uh, we totally still use those.

The output encoding of colors and attributes are also rife with issues.

See also

In my experience this cannot resolve the issues without changes to the terminal emulator and device.

Synopsis

Documentation

data Key Source #

Representations of non-modifier keys.

  • KFun is indexed from 0 to 63. Range of supported FKeys varies by terminal and keyboard.
  • KUpLeft, KUpRight, KDownLeft, KDownRight, KCenter support varies by terminal and keyboard.
  • Actually, support for most of these but KEsc, KChar, KBS, and KEnter vary by terminal and keyboard.

Instances

Eq Key Source # 

Methods

(==) :: Key -> Key -> Bool #

(/=) :: Key -> Key -> Bool #

Ord Key Source # 

Methods

compare :: Key -> Key -> Ordering #

(<) :: Key -> Key -> Bool #

(<=) :: Key -> Key -> Bool #

(>) :: Key -> Key -> Bool #

(>=) :: Key -> Key -> Bool #

max :: Key -> Key -> Key #

min :: Key -> Key -> Key #

Read Key Source # 
Show Key Source # 

Methods

showsPrec :: Int -> Key -> ShowS #

show :: Key -> String #

showList :: [Key] -> ShowS #

Generic Key Source # 

Associated Types

type Rep Key :: * -> * #

Methods

from :: Key -> Rep Key x #

to :: Rep Key x -> Key #

type Rep Key Source # 
type Rep Key = D1 (MetaData "Key" "Graphics.Vty.Input.Events" "vty-5.11.1-IOfA2hjbE0wJLwNGofp7q1" False) ((:+:) ((:+:) ((:+:) ((:+:) (C1 (MetaCons "KEsc" PrefixI False) U1) ((:+:) (C1 (MetaCons "KChar" PrefixI False) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Char))) (C1 (MetaCons "KBS" PrefixI False) U1))) ((:+:) (C1 (MetaCons "KEnter" PrefixI False) U1) ((:+:) (C1 (MetaCons "KLeft" PrefixI False) U1) (C1 (MetaCons "KRight" PrefixI False) U1)))) ((:+:) ((:+:) (C1 (MetaCons "KUp" PrefixI False) U1) ((:+:) (C1 (MetaCons "KDown" PrefixI False) U1) (C1 (MetaCons "KUpLeft" PrefixI False) U1))) ((:+:) (C1 (MetaCons "KUpRight" PrefixI False) U1) ((:+:) (C1 (MetaCons "KDownLeft" PrefixI False) U1) (C1 (MetaCons "KDownRight" PrefixI False) U1))))) ((:+:) ((:+:) ((:+:) (C1 (MetaCons "KCenter" PrefixI False) U1) ((:+:) (C1 (MetaCons "KFun" PrefixI False) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Int))) (C1 (MetaCons "KBackTab" PrefixI False) U1))) ((:+:) (C1 (MetaCons "KPrtScr" PrefixI False) U1) ((:+:) (C1 (MetaCons "KPause" PrefixI False) U1) (C1 (MetaCons "KIns" PrefixI False) U1)))) ((:+:) ((:+:) (C1 (MetaCons "KHome" PrefixI False) U1) ((:+:) (C1 (MetaCons "KPageUp" PrefixI False) U1) (C1 (MetaCons "KDel" PrefixI False) U1))) ((:+:) ((:+:) (C1 (MetaCons "KEnd" PrefixI False) U1) (C1 (MetaCons "KPageDown" PrefixI False) U1)) ((:+:) (C1 (MetaCons "KBegin" PrefixI False) U1) (C1 (MetaCons "KMenu" PrefixI False) U1))))))

data Modifier Source #

Modifier keys. Key codes are interpreted such that users are more likely to have Meta than Alt; for instance on the PC Linux console, MMeta will generally correspond to the physical Alt key.

Constructors

MShift 
MCtrl 
MMeta 
MAlt 

Instances

Eq Modifier Source # 
Ord Modifier Source # 
Read Modifier Source # 
Show Modifier Source # 
Generic Modifier Source # 

Associated Types

type Rep Modifier :: * -> * #

Methods

from :: Modifier -> Rep Modifier x #

to :: Rep Modifier x -> Modifier #

type Rep Modifier Source # 
type Rep Modifier = D1 (MetaData "Modifier" "Graphics.Vty.Input.Events" "vty-5.11.1-IOfA2hjbE0wJLwNGofp7q1" False) ((:+:) ((:+:) (C1 (MetaCons "MShift" PrefixI False) U1) (C1 (MetaCons "MCtrl" PrefixI False) U1)) ((:+:) (C1 (MetaCons "MMeta" PrefixI False) U1) (C1 (MetaCons "MAlt" PrefixI False) U1)))

data Button Source #

Mouse buttons.

Constructors

BLeft 
BMiddle 
BRight 

Instances

Eq Button Source # 

Methods

(==) :: Button -> Button -> Bool #

(/=) :: Button -> Button -> Bool #

Ord Button Source # 
Read Button Source # 
Show Button Source # 
Generic Button Source # 

Associated Types

type Rep Button :: * -> * #

Methods

from :: Button -> Rep Button x #

to :: Rep Button x -> Button #

type Rep Button Source # 
type Rep Button = D1 (MetaData "Button" "Graphics.Vty.Input.Events" "vty-5.11.1-IOfA2hjbE0wJLwNGofp7q1" False) ((:+:) (C1 (MetaCons "BLeft" PrefixI False) U1) ((:+:) (C1 (MetaCons "BMiddle" PrefixI False) U1) (C1 (MetaCons "BRight" PrefixI False) U1)))

data Event Source #

Events.

Constructors

EvKey Key [Modifier]

A keyboard key was pressed with the specified modifiers.

EvMouseDown Int Int Button [Modifier]

A mouse button was pressed at the specified column and row. Any modifiers available in the event are also provided.

EvMouseUp Int Int (Maybe Button)

A mouse button was released at the specified column and row. Some terminals report only that a button was released without specifying which one; in that case, Nothing is provided. Otherwise Just the button released is included in the event.

EvResize Int Int

If read from eventChannel this is the size at the time of the signal. If read from nextEvent this is the size at the time the event was processed by Vty. Typically these are the same, but if somebody is resizing the terminal quickly they can be different.

EvPaste ByteString

A paste event occurs when a bracketed paste input sequence is received. For terminals that support bracketed paste mode, these events will be triggered on a paste event. Terminals that do not support bracketed pastes will send the paste contents as ordinary input (which is probably bad, so beware!) Note that the data is provided in raw form and you'll have to decode (e.g. as UTF-8) if that's what your application expects.

Instances

Eq Event Source # 

Methods

(==) :: Event -> Event -> Bool #

(/=) :: Event -> Event -> Bool #

Ord Event Source # 

Methods

compare :: Event -> Event -> Ordering #

(<) :: Event -> Event -> Bool #

(<=) :: Event -> Event -> Bool #

(>) :: Event -> Event -> Bool #

(>=) :: Event -> Event -> Bool #

max :: Event -> Event -> Event #

min :: Event -> Event -> Event #

Read Event Source # 
Show Event Source # 

Methods

showsPrec :: Int -> Event -> ShowS #

show :: Event -> String #

showList :: [Event] -> ShowS #

Generic Event Source # 

Associated Types

type Rep Event :: * -> * #

Methods

from :: Event -> Rep Event x #

to :: Rep Event x -> Event #

type Rep Event Source # 
type Rep Event = D1 (MetaData "Event" "Graphics.Vty.Input.Events" "vty-5.11.1-IOfA2hjbE0wJLwNGofp7q1" False) ((:+:) ((:+:) (C1 (MetaCons "EvKey" PrefixI False) ((:*:) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Key)) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 [Modifier])))) (C1 (MetaCons "EvMouseDown" PrefixI False) ((:*:) ((:*:) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Int)) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Int))) ((:*:) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Button)) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 [Modifier])))))) ((:+:) (C1 (MetaCons "EvMouseUp" PrefixI False) ((:*:) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Int)) ((:*:) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Int)) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (Maybe Button)))))) ((:+:) (C1 (MetaCons "EvResize" PrefixI False) ((:*:) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Int)) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Int)))) (C1 (MetaCons "EvPaste" PrefixI False) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 ByteString))))))

data Input Source #

Constructors

Input 

Fields

inputForConfig :: Config -> IO Input Source #

Set up the terminal with file descriptor inputFd for input. Returns a Input.

The table used to determine the Events to produce for the input bytes comes from classifyMapForTerm. Which is then overridden by the the applicable entries from inputMap.

The terminal device is configured with the attributes:

  • IXON disabled
  • disables software flow control on outgoing data. This stops the process from being suspended if the output terminal cannot keep up. I presume this has little effect these days. I hope this means that output will be buffered if the terminal cannot keep up. In the old days the output might of been dropped?

"raw" mode is used for input.

  • ISIG disabled
  • enables keyboard combinations that result in signals. TODO: should probably be a dynamic option.
  • ECHO disabled
  • input is not echod to the output. TODO: should be a dynamic option.
  • ICANON disabled
  • canonical mode (line mode) input is not used. TODO: should be a dynamic option.
  • IEXTEN disabled
  • extended functions are disabled. TODO: I don't know what those are.