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