on d64ff96 help sign in
vty/src/Graphics/Vty/Input.hs
185 lines 7.1 kB history raw
1{-# LANGUAGE RecordWildCards #-}
3-- | This module provides the input layer for Vty, including methods
4-- for initializing an 'Input' structure and reading 'Event's from the
5-- terminal.
6--
7-- Note that due to the evolution of terminal emulators, some keys
8-- and combinations will not reliably map to the expected events by
9-- any terminal program. There is no 1:1 mapping from key events to
10-- bytes read from the terminal input device. In very limited cases the
11-- terminal and vty's input process can be customized to resolve these
12-- issues; see "Graphics.Vty.Config" for how to configure vty's input
13-- processing.
14--
15-- = VTY's Implementation
16--
17-- There are two input modes:
18--
19-- 1. 7-bit
20--
21-- 2. 8-bit
22--
23-- The 7-bit input mode is the default and the expected mode in most use
24-- cases. This is what Vty uses.
25--
26-- == 7-bit input encoding
27--
28-- Control key combinations are represented by masking the two high bits
29-- of the 7-bit input. Historically the control key actually grounded
30-- the two high bit wires: 6 and 7. This is why control key combos
31-- map to single character events: the input bytes are identical. The
32-- input byte is the bit encoding of the character with bits 6 and 7
33-- masked. Bit 6 is set by shift. Bit 6 and 7 are masked by control. For
34-- example,
35--
36-- * Control-I is 'i', `01101001`, and has bit 6 and 7 masked to become
37-- `00001001`, which is the ASCII and UTF-8 encoding of the Tab key.
38--
39-- * Control+Shift-C is 'C', `01000011`, with bit 6 and 7 set to zero
40-- which is `0000011` and is the "End of Text" code.
41--
42-- * Hypothesis: This is why capital-A, 'A', has value 65 in ASCII: this
43-- is the value 1 with bit 7 set and 6 unset.
44--
45-- * Hypothesis: Bit 6 is unset by upper case letters because,
46-- initially, there were only upper case letters used and a 5 bit
47-- encoding.
48--
49-- == 8-bit encoding
50--
51-- The 8th bit was originally used for parity checking which is useless
52-- for terminal emulators. Some terminal emulators support an 8-bit
53-- input encoding. While this provides some advantages, the actual usage
54-- is low. Most systems use 7-bit mode but recognize 8-bit control
55-- characters when escaped. This is what Vty does.
56--
57-- == Escaped Control Keys
58--
59-- Using 7-bit input encoding, the @ESC@ byte can signal the start of
60-- an encoded control key. To differentiate a single @ESC@ event from a
61-- control key, the timing of the input is used.
62--
63-- 1. @ESC@ individually: @ESC@ byte; no bytes following for a period of
64-- 'VMIN' milliseconds.
65--
66-- 2. Control keys that contain @ESC@ in their encoding: The @ESC byte
67-- is followed by more bytes read within 'VMIN' milliseconds. All bytes
68-- up until the next valid input block are passed to the classifier.
69--
70-- If the current runtime is the threaded runtime then the terminal's
71-- @VMIN@ and @VTIME@ behavior reliably implement the above rules. If
72-- the current runtime does not support 'forkOS' then there is currently
73-- no implementation.
74--
75-- == Unicode Input and Escaped Control Key Sequences
76--
77-- The input encoding determines how UTF-8 encoded characters are
78-- recognized.
79--
80-- * 7-bit mode: UTF-8 can be input unambiguiously. UTF-8 input is
81-- a superset of ASCII. UTF-8 does not overlap escaped control key
82-- sequences. However, the escape key must be differentiated from
83-- escaped control key sequences by the timing of the input bytes.
84--
85-- * 8-bit mode: UTF-8 cannot be input unambiguously. This does not
86-- require using the timing of input bytes to differentiate the escape
87-- key. Many terminals do not support 8-bit mode.
88--
89-- == Terminfo
90--
91-- The terminfo system is used to determine how some keys are encoded.
92-- Terminfo is incomplete and in some cases terminfo is incorrect. Vty
93-- assumes terminfo is correct but provides a mechanism to override
94-- terminfo; see "Graphics.Vty.Config", specifically 'inputOverrides'.
95--
96-- == Terminal Input is Broken
97--
98-- Clearly terminal input has fundemental issues. There is no easy way
99-- to reliably resolve these issues.
100--
101-- One resolution would be to ditch standard terminal interfaces
102-- entirely and just go directly to scancodes. This would be a
103-- reasonable option for Vty if everybody used the linux kernel console
104-- but for obvious reasons this is not possible.
105--
106-- The "Graphics.Vty.Config" module supports customizing the
107-- input-byte-to-event mapping and xterm supports customizing the
108-- scancode-to-input-byte mapping. With a lot of work a user's system
109-- can be set up to encode all the key combos in an almost-sane manner.
110--
111-- == See also
112--
113-- * http://www.leonerd.org.uk/hacks/fixterms/
114module Graphics.Vty.Input
115 ( Key(..)
116 , Modifier(..)
117 , Button(..)
118 , Event(..)
119 , Input(..)
120 , inputForConfig
121 )
122where
124import Graphics.Vty.Config
125import Graphics.Vty.Input.Events
126import Graphics.Vty.Input.Loop
127import Graphics.Vty.Input.Terminfo
129import Control.Concurrent.STM
130import Lens.Micro
132import qualified System.Console.Terminfo as Terminfo
133import System.Posix.Signals.Exts
135import Data.Monoid ((<>))
137-- | Set up the terminal with file descriptor `inputFd` for input.
138-- Returns an 'Input'.
139--
140-- The table used to determine the 'Events' to produce for the input
141-- bytes comes from 'classifyMapForTerm' which is then overridden by
142-- the the applicable entries from the configuration's 'inputMap'.
143--
144-- The terminal device is configured with the attributes:
145--
146-- * IXON disabled: disables software flow control on outgoing data.
147-- This stops the process from being suspended if the output terminal
148-- cannot keep up.
149--
150-- * Raw mode is used for input.
151--
152-- * ISIG disabled (enables keyboard combinations that result in
153-- signals)
154--
155-- * ECHO disabled (input is not echoed to the output)
156--
157-- * ICANON disabled (canonical mode (line mode) input is not used)
158--
159-- * IEXTEN disabled (extended functions are disabled)
160inputForConfig :: Config -> IO Input
161inputForConfig config@Config{ termName = Just termName
162 , inputFd = Just termFd
163 , vmin = Just _
164 , vtime = Just _
165 , .. } = do
166 terminal <- Terminfo.setupTerm termName
167 let inputOverrides = [(s,e) | (t,s,e) <- inputMap, t == Nothing || t == Just termName]
168 activeInputMap = classifyMapForTerm termName terminal `mappend` inputOverrides
169 (setAttrs,unsetAttrs) <- attributeControl termFd
170 setAttrs
171 input <- initInput config activeInputMap
172 let pokeIO = Catch $ do
173 let e = error "vty internal failure: this value should not propagate to users"
174 setAttrs
175 atomically $ writeTChan (input^.eventChannel) (EvResize e e)
176 _ <- installHandler windowChange pokeIO Nothing
177 _ <- installHandler continueProcess pokeIO Nothing
178 return $ input
179 { shutdownInput = do
180 shutdownInput input
181 _ <- installHandler windowChange Ignore Nothing
182 _ <- installHandler continueProcess Ignore Nothing
183 unsetAttrs
184 }
185inputForConfig config = (<> config) <$> standardIOConfig >>= inputForConfig