on 4e45d7c help sign in
vty/src/Graphics/Vty.hs
149 lines 6.0 kB history raw
1-- | Vty supports input and output to terminal devices. The input is provides as a sequence of
2-- 'Event's. The output is defined by a 'Picture'. Which is one or more layers of 'Image's.
3--
4-- - References
5--
6-- Good sources of documentation for terminal programming are:
7--
8-- - <https://github.com/b4winckler/vim/blob/master/src/term.c>
9-- - <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
10-- - <http://ulisse.elettra.trieste.it/services/doc/serial/config.html>
11-- - <http://www.leonerd.org.uk/hacks/hints/xterm-8bit.html>
12-- - <http://www.unixwiz.net/techtips/termios-vmin-vtime.html>
13-- - <<http://vt100.net/docs/vt100-ug/chapter3.html vt100 control sequences>
14module Graphics.Vty ( Vty(..)
15 , mkVty
16 , mkVtyEscDelay
17 , module Graphics.Vty.Output
18 , module Graphics.Vty.Picture
19 , DisplayRegion
20 , Key(..)
21 , Modifier(..)
22 , Button(..)
23 , Event(..)
24 )
25 where
27import Graphics.Vty.Prelude
29import Graphics.Vty.Input
30import Graphics.Vty.Output
31import Graphics.Vty.Picture
33import Data.IORef
35import qualified System.Console.Terminfo as Terminfo
37-- | The main object. At most one should be created.
38-- An alternative is to use unsafePerformIO to automatically create a singleton Vty instance when
39-- required.
40--
41-- This does not assure any thread safety. In theory, as long as an update action is not executed
42-- when another update action is already then it's safe to call this on multiple threads.
43data Vty = Vty
44 { -- | Outputs the given Picture. Equivalent to output_picture applied to a display context
45 -- implicitly managed by Vty.
46 update :: Picture -> IO ()
47 -- | Get one Event object, blocking if necessary.
48 , next_event :: IO Event
49 -- | The terminal device interface. See `Interface`
50 --
51 -- The use of Vty typically follows this process:
52 --
53 -- 0. initialize vty
54 --
55 -- 1. use the update equation of Vty to display a picture
56 --
57 -- 2. repeat
58 --
59 -- 3. shutdown vty.
60 --
61 -- todo: provide a similar abstraction to Graphics.Vty.Output for input. Use haskeline's
62 -- input backend for implementation.
63 --
64 -- todo: remove explicit `shutdown` requirement.
65 , terminal :: Output
66 -- | Refresh the display. Normally the library takes care of refreshing. Nonetheless, some
67 -- other program might output to the terminal and mess the display. In that case the user
68 -- might want to force a refresh.
69 , refresh :: IO ()
70 -- | Clean up after vty.
71 -- The above methods will throw an exception if executed after this is executed.
72 , shutdown :: IO ()
73 }
75-- | Set up the state object for using vty. At most one state object should be
76-- created at a time.
77mkVty :: IO Vty
78mkVty = mkVtyEscDelay defaultEscDelay
80-- | Set up the state object for using vty. At most one state object should be
81-- created at a time. The delay, in microseconds, specifies the period of time to wait for a key
82-- following reading ESC from the terminal before considering the ESC key press as a discrete event.
83-- \todo move input init into terminal interface
84mkVtyEscDelay :: Int -> IO Vty
85mkVtyEscDelay escDelay = do
86 term_info <- Terminfo.setupTermFromEnv
87 t <- current_terminal
88 reserve_display t
89 (kvar, endi) <- initTermInput escDelay term_info
90 intMkVty kvar ( endi >> release_display t >> release_terminal t ) t
92intMkVty :: IO Event -> IO () -> Output -> IO Vty
93intMkVty kvar fend t = do
94 last_pic_ref <- newIORef Nothing
95 last_update_ref <- newIORef Nothing
97 let inner_update in_pic = do
98 b@(w,h) <- display_bounds t
99 let cursor = pic_cursor in_pic
100 in_pic' = case cursor of
101 Cursor x y ->
102 let
103 x' = case x of
104 _ | x >= 0x80000000 -> 0
105 | x >= w -> w - 1
106 | otherwise -> x
107 y' = case y of
108 _ | y >= 0x80000000 -> 0
109 | y >= h -> h - 1
110 | otherwise -> y
111 in in_pic { pic_cursor = Cursor x' y' }
112 _ -> in_pic
113 mlast_update <- readIORef last_update_ref
114 update_data <- case mlast_update of
115 Nothing -> do
116 dc <- display_context t b
117 output_picture dc in_pic'
118 return (b, dc)
119 Just (last_bounds, last_context) -> do
120 if b /= last_bounds
121 then do
122 dc <- display_context t b
123 output_picture dc in_pic'
124 return (b, dc)
125 else do
126 output_picture last_context in_pic'
127 return (b, last_context)
128 writeIORef last_update_ref $ Just update_data
129 writeIORef last_pic_ref $ Just in_pic'
131 let inner_refresh
132 = writeIORef last_update_ref Nothing
133 >> readIORef last_pic_ref
134 >>= maybe ( return () ) ( \pic -> inner_update pic )
136 let gkey = do k <- kvar
137 case k of
138 (EvResize _ _) -> inner_refresh
139 >> display_bounds t
140 >>= return . (\(w,h)-> EvResize w h)
141 _ -> return k
143 return $ Vty { update = inner_update
144 , next_event = gkey
145 , terminal = t
146 , refresh = inner_refresh
147 , shutdown = fend
148 }