on ebee723 help sign in
vty/src/Graphics/Vty/Input.hs
218 lines 8.4 kB history raw
1{-# LANGUAGE RecordWildCards #-}
3-- | The input layer for VTY. This provides methods for initializing an
4-- 'Input' structure which can then be used to read 'Event's from the
5-- terminal.
6--
7-- The history of terminals has resulted in a broken input process. Some
8-- keys and combinations will not reliably map to the expected events
9-- by any terminal program. Even those not using vty. There is no 1:1
10-- mapping from key events to bytes read from the terminal input device.
11-- In very limited cases the terminal and vty's input process can be
12-- customized to resolve these issues.
13--
14-- See "Graphics.Vty.Config" for how to configure vty's input
15-- processing. Customizing terminfo and the terminal is beyond the scope
16-- of this documentation.
17--
18-- = VTY's Implementation
19--
20-- There are two input modes:
21--
22-- 1. 7 bit
23--
24-- 2. 8 bit
25--
26-- 7 bit input is the default and the expected in most use cases. This
27-- is what vty uses.
28--
29-- == 7 bit input encoding
30--
31-- Control key combinations are represented by masking the two high bits
32-- of the 7bit input. Back in the day the control key actually grounded
33-- the two high bit wires: 6 and 7. This is why control key combos map
34-- to single character events: The input bytes are identical. The input
35-- byte is the bit encoding of the character with bits 6 and 7 masked.
36-- Bit 6 is set by shift. Bit 6 and 7 are masked by control. EG:
37--
38-- * Control-I is 'i', `01101001`, has bit 6 and 7 masked to become
39-- `00001001`. Which is the ASCII and UTF-8 encoding of the tab key.
40--
41-- * Control+Shift-C is 'C', `01000011`, with bit 6 and 7 set to zero
42-- which makes `0000011` and is the "End of Text" code.
43--
44-- * Hypothesis: This is why capital-A, 'A', has value 65 in ASCII: This
45-- is the value 1 with bit 7 set and 6 unset.
46--
47-- * Hypothesis: Bit 6 is unset by upper case letters because,
48-- initially, there were only upper case letters used and a 5 bit
49-- encoding.
50--
51-- == 8 bit encoding
52--
53-- The 8th bit was originally used for parity checking. Useless for
54-- emulators. Some terminal emulators support a 8 bit input encoding.
55-- While this provides some advantages the actual usage is low. Most
56-- systems use 7bit mode but recognize 8bit control characters when
57-- escaped. This is what vty does.
58--
59-- == Escaped Control Keys
60--
61-- Using 7 bit input encoding the @ESC@ byte can signal the start of
62-- an encoded control key. To differentiate a single @ESC@ eventfrom a
63-- control key the timing of the input is used.
64--
65-- 1. @ESC@ individually: @ESC@ byte; no bytes for 'singleEscPeriod'.
66--
67-- 2. control keys that contain @ESC@ in their encoding: The @ESC byte;
68-- followed by more bytes read within 'singleEscPeriod'. All bytes up
69-- until the next valid input block are passed to the classifier.
70--
71-- If the current runtime is the threaded runtime then the terminal's
72-- @VMIN@ and @VTIME@ behavior reliably implement the above rules. If
73-- the current runtime does not support forkOS then there is currently
74-- no implementation.
75--
76-- Vty used to emulate @VMIN@ and @VTIME@. This was a input loop
77-- which did tricky things with non-blocking reads and timers. The
78-- implementation was not reliable. A reliable implementation is
79-- possible, but there are no plans to implement this.
80--
81-- == Unicode Input and Escaped Control Key Sequences
82--
83-- The input encoding determines how UTF-8 encoded characters are
84-- recognize.
85--
86-- * 7 bit mode: UTF-8 can be input unambiguiously. UTF-8 input is
87-- a superset of ASCII. UTF-8 does not overlap escaped control key
88-- sequences. However, the escape key must be differentiated from
89-- escaped control key sequences by the timing of the input bytes.
90--
91-- * 8 bit mode: UTF-8 cannot be input unambiguously. This does not
92-- require using the timing of input bytes to differentiate the escape
93-- key. Many terminals do not support 8 bit mode.
94--
95-- == Terminfo
96--
97-- The terminfo system is used to determine how some keys are encoded.
98-- Terminfo is incomplete. In some cases terminfo is incorrect. Vty
99-- assumes terminfo is correct but provides a mechanism to override
100-- terminfo. See "Graphics.Vty.Config" specifically 'inputOverrides'.
101--
102-- == Terminal Input is Broken
103--
104-- Clearly terminal input has fundemental issues. There is no easy way
105-- to reliably resolve these issues.
106--
107-- One resolution would be to ditch standard terminal interfaces
108-- entirely and just go directly to scancodes. A reasonable option for
109-- vty if everybody used the linux kernel console. I hear GUIs are
110-- popular these days. Sadly, GUI terminal emulators don't provide
111-- access to scancodes AFAIK.
112--
113-- All is lost? Not really. "Graphics.Vty.Config" supports customizing
114-- the input byte to event mapping and xterm supports customizing the
115-- scancode to input byte mapping. With a lot of work a user's system
116-- can be set up to encode all the key combos in an almost-sane manner.
117--
118-- There are other tricky work arounds that can be done. I have no
119-- interest in implementing most of these. They are not really worth the
120-- time.
121--
122-- == Terminal Output is Also Broken
123--
124-- This isn't the only odd aspect of terminals due to historical
125-- aspects that no longer apply. EG: Some terminfo capabilities specify
126-- millisecond delays. (Capabilities are how terminfo describes the
127-- control sequence to output red, for instance) This is to account for
128-- the slow speed of hardcopy teletype interfaces. Cause, uh, we totally
129-- still use those.
130--
131-- The output encoding of colors and attributes are also rife with
132-- issues.
133--
134-- == See also
135--
136-- * http://www.leonerd.org.uk/hacks/fixterms/
137--
138-- In my experience this cannot resolve the issues without changes to
139-- the terminal emulator and device.
140module Graphics.Vty.Input
141 ( Key(..)
142 , Modifier(..)
143 , Button(..)
144 , Event(..)
145 , Input(..)
146 , inputForConfig
147 )
148where
150import Graphics.Vty.Config
151import Graphics.Vty.Input.Events
152import Graphics.Vty.Input.Loop
153import Graphics.Vty.Input.Terminfo
155import Control.Concurrent.STM
156import Lens.Micro
158import qualified System.Console.Terminfo as Terminfo
159import System.Posix.Signals.Exts
161import Data.Monoid ((<>))
163-- | Set up the terminal with file descriptor `inputFd` for input.
164-- Returns a 'Input'.
165--
166-- The table used to determine the 'Events' to produce for the input
167-- bytes comes from 'classifyMapForTerm'. Which is then overridden by
168-- the the applicable entries from 'inputMap'.
169--
170-- The terminal device is configured with the attributes:
171--
172-- * IXON disabled
173-- - disables software flow control on outgoing data. This stops
174-- the process from being suspended if the output terminal cannot
175-- keep up. I presume this has little effect these days. I hope
176-- this means that output will be buffered if the terminal cannot
177-- keep up. In the old days the output might of been dropped?
178--
179-- "raw" mode is used for input.
180--
181-- * ISIG disabled
182-- - enables keyboard combinations that result in signals.
183--
184-- * ECHO disabled
185-- - input is not echoed to the output.
186--
187-- * ICANON disabled
188-- - canonical mode (line mode) input is not used.
189--
190-- * IEXTEN disabled
191-- - extended functions are disabled.
192--
193inputForConfig :: Config -> IO Input
194inputForConfig config@Config{ termName = Just termName
195 , inputFd = Just termFd
196 , vmin = Just _
197 , vtime = Just _
198 , .. } = do
199 terminal <- Terminfo.setupTerm termName
200 let inputOverrides = [(s,e) | (t,s,e) <- inputMap, t == Nothing || t == Just termName]
201 activeInputMap = classifyMapForTerm termName terminal `mappend` inputOverrides
202 (setAttrs,unsetAttrs) <- attributeControl termFd
203 setAttrs
204 input <- initInput config activeInputMap
205 let pokeIO = Catch $ do
206 let e = error "vty internal failure: this value should not propagate to users"
207 setAttrs
208 atomically $ writeTChan (input^.eventChannel) (EvResize e e)
209 _ <- installHandler windowChange pokeIO Nothing
210 _ <- installHandler continueProcess pokeIO Nothing
211 return $ input
212 { shutdownInput = do
213 shutdownInput input
214 _ <- installHandler windowChange Ignore Nothing
215 _ <- installHandler continueProcess Ignore Nothing
216 unsetAttrs
217 }
218inputForConfig config = (<> config) <$> standardIOConfig >>= inputForConfig