on bef2d40 help sign in
vty/src/Graphics/Vty.hs
167 lines 6.4 kB history raw
1-- | Vty supports input and output to terminal devices.
2--
3-- - The input is provides as a sequence of 'Event's.
4--
5-- - The output is defined by a 'Picture'. Which is one or more layers of 'Image's.
6-- The constructors in "Graphics.Vty.Image.Internal" should not be used directly. The module
7-- "Graphics.Vty.Image" provides a number of constructor equations that will build correct
8-- 'Image' values. See 'string', '<|>', and '<->' for starters.
9--
10-- - 'Image's can be styled using 'Attr'. See "Graphics.Vty.Attributes".
11--
12-- Good sources of documentation for terminal programming are:
13--
14-- - <https://github.com/b4winckler/vim/blob/master/src/term.c>
15--
16-- - <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
17--
18-- - <http://ulisse.elettra.trieste.it/services/doc/serial/config.html>
19--
20-- - <http://www.leonerd.org.uk/hacks/hints/xterm-8bit.html>
21--
22-- - <http://www.unixwiz.net/techtips/termios-vmin-vtime.html>
23--
24-- - <http://vt100.net/docs/vt100-ug/chapter3.html vt100 control sequences>
25module Graphics.Vty ( Vty(..)
26 , mkVty
27 , mkVtyEscDelay
28 , module Graphics.Vty.Output
29 , module Graphics.Vty.Picture
30 , DisplayRegion
31 , Key(..)
32 , Modifier(..)
33 , Button(..)
34 , Event(..)
35 )
36 where
38import Graphics.Vty.Prelude
40import Graphics.Vty.Input
41import Graphics.Vty.Output
42import Graphics.Vty.Picture
44import Control.Concurrent
46import Data.Default
47import Data.IORef
49import qualified System.Console.Terminfo as Terminfo
51-- | The main object. At most one should be created.
52--
53-- The use of Vty typically follows this process:
54--
55-- 0. initialize vty
56--
57-- 1. use the update equation of Vty to display a picture
58--
59-- 2. repeat
60--
61-- 3. shutdown vty.
62--
63-- An alternative to tracking the Vty instance is to use 'withVty' in "Graphics.Vty.Inline.Unsafe".
64--
65-- This does not assure any thread safety. In theory, as long as an update action is not executed
66-- when another update action is already then it's safe to call this on multiple threads.
67--
68-- \todo Remove explicit `shutdown` requirement.
69data Vty = Vty
70 { -- | Outputs the given Picture. Equivalent to 'output_picture' applied to a display context
71 -- implicitly managed by Vty. The managed display context is reset on resize.
72 update :: Picture -> IO ()
73 -- | Get one Event object, blocking if necessary. This will refresh the terminal if the event
74 -- is a 'EvResize'.
75 , next_event :: IO Event
76 -- | The input interface. See 'Input'
77 , input_iface :: Input
78 -- | The output interface. See 'Output'
79 , output_iface :: Output
80 -- | Refresh the display. Normally the library takes care of refreshing. Nonetheless, some
81 -- other program might output to the terminal and mess up the display. In that case the
82 -- application might want to force a refresh.
83 , refresh :: IO ()
84 -- | Clean up after vty.
85 -- The above methods will throw an exception if executed after this is executed.
86 , shutdown :: IO ()
87 }
89-- | Set up the state object for using vty. At most one state object should be
90-- created at a time.
91mkVty :: IO Vty
92mkVty = mkVtyEscDelay def
94-- | Set up the state object for using vty. At most one state object should be
95-- created at a time. The delay, in microseconds, specifies the period of time to wait for a key
96-- following reading ESC from the terminal before considering the ESC key press as a discrete event.
97-- \todo move input init into terminal interface
98mkVtyEscDelay :: Config -> IO Vty
99mkVtyEscDelay config = do
100 input <- input_for_current_terminal config
101 out <- output_for_current_terminal
102 intMkVty input out
104intMkVty :: Input -> Output -> IO Vty
105intMkVty input out = do
106 reserve_display out
107 let shutdown_io = do
108 shutdown_input input
109 release_display out
110 release_terminal out
111 last_pic_ref <- newIORef Nothing
112 last_update_ref <- newIORef Nothing
114 let inner_update in_pic = do
115 b@(w,h) <- display_bounds out
116 let cursor = pic_cursor in_pic
117 in_pic' = case cursor of
118 Cursor x y ->
119 let
120 x' = case x of
121 _ | x < 0 -> 0
122 | x >= w -> w - 1
123 | otherwise -> x
124 y' = case y of
125 _ | y < 0 -> 0
126 | y >= h -> h - 1
127 | otherwise -> y
128 in in_pic { pic_cursor = Cursor x' y' }
129 _ -> in_pic
130 mlast_update <- readIORef last_update_ref
131 update_data <- case mlast_update of
132 Nothing -> do
133 dc <- display_context out b
134 output_picture dc in_pic'
135 return (b, dc)
136 Just (last_bounds, last_context) -> do
137 if b /= last_bounds
138 then do
139 dc <- display_context out b
140 output_picture dc in_pic'
141 return (b, dc)
142 else do
143 output_picture last_context in_pic'
144 return (b, last_context)
145 writeIORef last_update_ref $ Just update_data
146 writeIORef last_pic_ref $ Just in_pic'
148 let inner_refresh
149 = writeIORef last_update_ref Nothing
150 >> readIORef last_pic_ref
151 >>= maybe ( return () ) ( \pic -> inner_update pic )
153 let gkey = do k <- readChan $ _event_channel input
154 case k of
155 (EvResize _ _) -> inner_refresh
156 >> display_bounds out
157 >>= return . (\(w,h)-> EvResize w h)
158 _ -> return k
160 return $ Vty { update = inner_update
161 , next_event = gkey
162 , input_iface = input
163 , output_iface = out
164 , refresh = inner_refresh
165 , shutdown = shutdown_io
166 }