on master help sign in
Merge mouse support
commit405ca96eae8e4f346ebbcb54d50a064ff81c14f0 authorJonathan Daugherty <cygnus@foobox.com> dateSat 25 Jun 2016, 13:15 (10 years ago) parentf68bb32b74a8b8
9 files changed+256−16
README.md+3 src/Graphics/Vty/Input/Classify.hs+3−6 src/Graphics/Vty/Input/Classify/Parse.hs+57 src/Graphics/Vty/Input/Classify/Types.hs+18 src/Graphics/Vty/Input/Events.hs+13−7 src/Graphics/Vty/Input/Mouse.hs+153 src/Graphics/Vty/Output.hs+3−3 src/Graphics/Vty/Output/XTermColor.hs+3 vty.cabal+3
README.md+3
@@ -68,6 +68,9 @@ cabal install vty
6868* Uses the TIOCGWINSZ ioctl to find the current window size, which appears to be
6969 limited to Linux and BSD.
7070
71+* Supports "normal" and "extended" (SGR) mouse modes as described at
72+ http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
73+
7174# Platform Support
7275
7376## Posix Terminals
src/Graphics/Vty/Input/Classify.hs+3−6
@@ -12,6 +12,8 @@ module Graphics.Vty.Input.Classify
1212 ) where
1313
1414import Graphics.Vty.Input.Events
15+import Graphics.Vty.Input.Mouse
16+import Graphics.Vty.Input.Classify.Types
1517
1618import Codec.Binary.UTF8.Generic (decode)
1719
@@ -23,12 +25,6 @@ import qualified Data.Set as S( fromList, member )
2325import Data.Char
2426import Data.Word
2527
26−data KClass
27− = Valid Event [Char]
28− | Invalid
29− | Prefix
30− deriving(Show, Eq)
31−
3228compile :: ClassifyMap -> [Char] -> KClass
3329compile table = cl' where
3430 -- take all prefixes and create a set of these
@@ -64,6 +60,7 @@ classify table =
6460 if bracketedPasteFinished s
6561 then parseBracketedPaste s
6662 else Prefix
63+ _ | isMouseEvent s -> classifyMouseEvent s
6764 c:cs | ord c >= 0xC2 -> classifyUtf8 c cs
6865 _ -> standardClassifier s
6966
src/Graphics/Vty/Input/Classify/Parse.hsnew file+57
@@ -0,0 +1,57 @@
1+-- | This module provides a simple parser for parsing input event
2+-- control sequences.
3+module Graphics.Vty.Input.Classify.Parse
4+ ( Parser
5+ , runParser
6+ , failParse
7+ , readInt
8+ , readChar
9+ , expectChar
10+ ) where
11+
12+import Graphics.Vty.Input.Events
13+import Graphics.Vty.Input.Classify.Types
14+
15+import Control.Monad.Trans.Maybe
16+import Control.Monad.State
17+
18+type Parser a = MaybeT (State String) a
19+
20+-- | Run a parser on a given input string. If the parser fails, return
21+-- 'Invalid'. Otherwise return the valid event ('Valid') and the
22+-- remaining unparsed characters.
23+runParser :: String -> Parser Event -> KClass
24+runParser s parser =
25+ case runState (runMaybeT parser) s of
26+ (Nothing, _) -> Invalid
27+ (Just e, remaining) -> Valid e remaining
28+
29+-- | Fail a parsing operation.
30+failParse :: Parser a
31+failParse = fail "invalid parse"
32+
33+-- | Read an integer from the input stream. If an integer cannot be
34+-- read, fail parsing. E.g. calling readInt on an input of "123abc" will
35+-- return '123' and consume those characters.
36+readInt :: Parser Int
37+readInt = do
38+ s <- get
39+ case (reads :: ReadS Int) s of
40+ [(i, rest)] -> put rest >> return i
41+ _ -> failParse
42+
43+-- | Read a character from the input stream. If one cannot be read (e.g.
44+-- we are out of characters), fail parsing.
45+readChar :: Parser Char
46+readChar = do
47+ s <- get
48+ case s of
49+ c:rest -> put rest >> return c
50+ _ -> failParse
51+
52+-- | Read a character from the input stream and fail parsing if it is
53+-- not the specified character.
54+expectChar :: Char -> Parser ()
55+expectChar c = do
56+ c' <- readChar
57+ if c' == c then return () else failParse
src/Graphics/Vty/Input/Classify/Types.hsnew file+18
@@ -0,0 +1,18 @@
1+-- | This module exports the input classification type to avoid import
2+-- cycles between other modules that need this.
3+module Graphics.Vty.Input.Classify.Types
4+ ( KClass(..)
5+ ) where
6+
7+import Graphics.Vty.Input.Events
8+
9+data KClass
10+ = Valid Event [Char]
11+ -- ^ A valid event was parsed. Any unused characters from the input
12+ -- stream are also provided.
13+ | Invalid
14+ -- ^ The input characters did not represent a valid event.
15+ | Prefix
16+ -- ^ The input characters form the prefix of a valid event character
17+ -- sequence.
18+ deriving(Show, Eq)
src/Graphics/Vty/Input/Events.hs+13−7
@@ -22,20 +22,26 @@ data Modifier = MShift | MCtrl | MMeta | MAlt
2222 deriving (Eq,Show,Read,Ord)
2323
2424-- | Mouse buttons.
25−--
26−-- \todo not supported.
2725data Button = BLeft | BMiddle | BRight
2826 deriving (Eq,Show,Read,Ord)
2927
3028-- | Events.
3129data Event
3230 = EvKey Key [Modifier]
33− -- | \todo mouse events are not supported
34− | EvMouse Int Int Button [Modifier]
35− -- | if read from 'eventChannel' this is the size at the time of the signal. If read from
36− -- 'nextEvent' this is the size at the time the event was processed by Vty. Typically these are
37− -- the same, but if somebody is resizing the terminal quickly they can be different.
31+ -- ^ A keyboard key was pressed with the specified modifiers.
32+ | EvMouseDown Int Int Button [Modifier]
33+ -- ^ A mouse button was pressed at the specified column and row. Any
34+ -- modifiers available in the event are also provided.
35+ | EvMouseUp Int Int (Maybe Button)
36+ -- ^ A mouse button was released at the specified column and
37+ -- row. Some terminals report only that a button was released
38+ -- without specifying which one; in that case, Nothing is provided.
39+ -- Otherwise Just the button released is included in the event.
3840 | EvResize Int Int
41+ -- ^ If read from 'eventChannel' this is the size at the time of the
42+ -- signal. If read from 'nextEvent' this is the size at the time the
43+ -- event was processed by Vty. Typically these are the same, but if
44+ -- somebody is resizing the terminal quickly they can be different.
3945 | EvPaste String
4046 -- ^ A paste event occurs when a bracketed paste input sequence is
4147 -- received. For terminals that support bracketed paste mode, these
src/Graphics/Vty/Input/Mouse.hsnew file+153
@@ -0,0 +1,153 @@
1+-- | This module provides parsers for mouse events for both "normal" and
2+-- "extended" modes. This implementation was informed by
3+--
4+-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
5+module Graphics.Vty.Input.Mouse
6+ ( requestMouseEvents
7+ , disableMouseEvents
8+ , isMouseEvent
9+ , classifyMouseEvent
10+ ) where
11+
12+import Graphics.Vty.Input.Events
13+import Graphics.Vty.Input.Classify.Types
14+import Graphics.Vty.Input.Classify.Parse
15+
16+import Control.Monad.Trans.Maybe
17+import Control.Monad.State
18+import Data.List (isPrefixOf)
19+import Data.Maybe (catMaybes)
20+import Data.Bits ((.&.))
21+
22+-- A mouse event in SGR extended mode is
23+--
24+-- '\ESC' '[' '<' B ';' X ';' Y ';' ('M'|'m')
25+--
26+-- where
27+--
28+-- * B is the number with button and modifier bits set,
29+-- * X is the X coordinate of the event starting at 1
30+-- * Y is the Y coordinate of the event starting at 1
31+-- * the final character is 'M' for a press, 'm' for a release
32+
33+-- | These sequences set xterm-based terminals to send mouse event
34+-- sequences.
35+requestMouseEvents :: String
36+requestMouseEvents = "\ESC[?1000h\ESC[?1002h\ESC[?1006h\ESC[?1005h"
37+
38+-- | These sequences disable mouse events.
39+disableMouseEvents :: String
40+disableMouseEvents = "\ESC[?1000l\ESC[?1002l\ESC[?1006l\ESC[?1005l"
41+
42+-- | Does the specified string begin with a mouse event?
43+isMouseEvent :: String -> Bool
44+isMouseEvent s = isSGREvent s || isNormalEvent s
45+
46+isSGREvent :: String -> Bool
47+isSGREvent = isPrefixOf sgrPrefix
48+
49+sgrPrefix :: String
50+sgrPrefix = "\ESC[M"
51+
52+isNormalEvent :: String -> Bool
53+isNormalEvent = isPrefixOf normalPrefix
54+
55+normalPrefix :: String
56+normalPrefix = "\ESC[<"
57+
58+-- Modifier bits:
59+shiftBit :: Int
60+shiftBit = 4
61+
62+metaBit :: Int
63+metaBit = 8
64+
65+ctrlBit :: Int
66+ctrlBit = 16
67+
68+-- These bits indicate the buttons involved:
69+buttonMask :: Int
70+buttonMask = 3
71+
72+leftButton :: Int
73+leftButton = 0
74+
75+middleButton :: Int
76+middleButton = 1
77+
78+rightButton :: Int
79+rightButton = 2
80+
81+hasBitSet :: Int -> Int -> Bool
82+hasBitSet val bit = val .&. bit > 0
83+
84+-- | Attempt to lassify an input string as a mouse event.
85+classifyMouseEvent :: String -> KClass
86+classifyMouseEvent s = runParser s $ do
87+ when (not $ isMouseEvent s) failParse
88+
89+ expectChar '\ESC'
90+ expectChar '['
91+ ty <- readChar
92+ case ty of
93+ '<' -> classifySGRMouseEvent
94+ 'M' -> classifyNormalMouseEvent
95+ _ -> failParse
96+
97+-- Given a modifer/button value, determine which button was indicated
98+getSGRButton :: Int -> Parser Button
99+getSGRButton mods =
100+ let buttonMap = [ (leftButton, BLeft)
101+ , (middleButton, BMiddle)
102+ , (rightButton, BRight)
103+ ]
104+ in case lookup (mods .&. buttonMask) buttonMap of
105+ Nothing -> failParse
106+ Just b -> return b
107+
108+getModifiers :: Int -> [Modifier]
109+getModifiers mods =
110+ catMaybes [ if mods `hasBitSet` shiftBit then Just MShift else Nothing
111+ , if mods `hasBitSet` metaBit then Just MMeta else Nothing
112+ , if mods `hasBitSet` ctrlBit then Just MCtrl else Nothing
113+ ]
114+
115+-- Attempt to classify a control sequence as a "normal" mouse event. To
116+-- get here we should have already read "\ESC[M" so that will not be
117+-- included in the string to be parsed.
118+classifyNormalMouseEvent :: Parser Event
119+classifyNormalMouseEvent = do
120+ statusChar <- readChar
121+ xCoordChar <- readChar
122+ yCoordChar <- readChar
123+
124+ let xCoord = fromEnum xCoordChar - 32
125+ yCoord = fromEnum yCoordChar - 32
126+ status = fromEnum statusChar
127+ modifiers = getModifiers status
128+
129+ let press = status .&. buttonMask /= 3
130+ case press of
131+ True -> do
132+ button <- getSGRButton status
133+ return $ EvMouseDown xCoord yCoord button modifiers
134+ False -> return $ EvMouseUp xCoord yCoord Nothing
135+
136+-- Attempt to classify a control sequence as an SGR mouse event. To
137+-- get here we should have already read "\ESC[<" so that will not be
138+-- included in the string to be parsed.
139+classifySGRMouseEvent :: Parser Event
140+classifySGRMouseEvent = do
141+ mods <- readInt
142+ expectChar ';'
143+ xCoord <- readInt
144+ expectChar ';'
145+ yCoord <- readInt
146+ final <- readChar
147+
148+ let modifiers = getModifiers mods
149+ button <- getSGRButton mods
150+ case final of
151+ 'M' -> return $ EvMouseDown xCoord yCoord button modifiers
152+ 'm' -> return $ EvMouseUp xCoord yCoord (Just button)
153+ _ -> failParse
src/Graphics/Vty/Output.hs+3−3
@@ -54,14 +54,14 @@ import Data.Monoid ((<>))
5454--
5555-- Selection of a terminal is done as follows:
5656--
57−-- * If TERM == xterm use XTermColor.
58−-- * for any other TERM value TerminfoBased is used.
57+-- * If TERM contains "xterm" or "screen", use XTermColor.
58+-- * otherwise use the TerminfoBased driver.
5959--
6060-- \todo add an implementation for windows that does not depend on terminfo. Should be installable
6161-- with only what is provided in the haskell platform. Use ansi-terminal
6262outputForConfig :: Config -> IO Output
6363outputForConfig Config{ outputFd = Just fd, termName = Just termName, .. } = do
64− t <- if "xterm" `isPrefixOf` termName
64+ t <- if "xterm" `isPrefixOf` termName || "screen" `isPrefixOf` termName
6565 then XTermColor.reserveTerminal termName fd
6666 -- Not an xterm-like terminal. try for generic terminfo.
6767 else TerminfoBased.reserveTerminal termName fd
src/Graphics/Vty/Output/XTermColor.hs+3
@@ -9,6 +9,7 @@ module Graphics.Vty.Output.XTermColor ( reserveTerminal )
99 where
1010
1111import Graphics.Vty.Output.Interface
12+import Graphics.Vty.Input.Mouse
1213import qualified Graphics.Vty.Output.TerminfoBased as TerminfoBased
1314
1415import Blaze.ByteString.Builder (writeToByteString)
@@ -34,12 +35,14 @@ reserveTerminal variant outFd = liftIO $ do
3435 let variant' = if variant == "xterm-color" then "xterm" else variant
3536 flushedPut setUtf8CharSet
3637 flushedPut enableBracketedPastes
38+ flushedPut requestMouseEvents
3739 t <- TerminfoBased.reserveTerminal variant' outFd
3840 let t' = t
3941 { terminalID = terminalID t ++ " (xterm-color)"
4042 , releaseTerminal = do
4143 liftIO $ flushedPut setDefaultCharSet
4244 liftIO $ flushedPut disableBracketedPastes
45+ liftIO $ flushedPut disableMouseEvents
4346 releaseTerminal t
4447 , mkDisplayContext = \tActual r -> do
4548 dc <- mkDisplayContext t tActual r
vty.cabal+3
@@ -94,7 +94,10 @@ library
9494 Graphics.Vty.DisplayAttributes
9595 Graphics.Vty.Image.Internal
9696 Graphics.Vty.Input.Classify
97+ Graphics.Vty.Input.Classify.Types
98+ Graphics.Vty.Input.Classify.Parse
9799 Graphics.Vty.Input.Loop
100+ Graphics.Vty.Input.Mouse
98101 Graphics.Vty.Input.Terminfo
99102 Graphics.Vty.PictureToSpans
100103 Graphics.Vty.Span