on master help sign in
Add support for SGR (extended) mouse events for XTermColor terminals

- Moves the KClass type from Classify to a new module, Classify.Types, so we can avoid import cycles - Adds a new module, Graphics.Vty.Input.Mouse, that provides functions to request SGR mouse events and parse them - Modifies the previously dormant mouse event constructors to include information supplied by SGR mouse events - Integrates the new SGR mouse event functionality into the XTermColor driver

commit7b075ce382b0db2fbaaaa0fc08363e4fa1af9534 authorJonathan Daugherty <cygnus@foobox.com> dateFri 17 Jun 2016, 12:32 (10 years ago) parent89b904f
6 files changed+147−7
src/Graphics/Vty/Input/Classify.hs+3−6 src/Graphics/Vty/Input/Classify/Types.hs+11 src/Graphics/Vty/Input/Events.hs+2−1 src/Graphics/Vty/Input/Mouse.hs+127 src/Graphics/Vty/Output/XTermColor.hs+2 vty.cabal+2
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
@@ -60,6 +56,7 @@ classify :: ClassifyMap -> [Char] -> KClass
6056classify table =
6157 let standardClassifier = compile table
6258 in \s -> case s of
59+ ('\ESC':'[':'<':cs) | length cs >= 6 -> classifySGRMouseEvent cs
6360 c:cs | ord c >= 0xC2 -> classifyUtf8 c cs
6461 _ -> standardClassifier s
6562
src/Graphics/Vty/Input/Classify/Types.hsnew file+11
@@ -0,0 +1,11 @@
1+module Graphics.Vty.Input.Classify.Types
2+ ( KClass(..)
3+ ) where
4+
5+import Graphics.Vty.Input.Events
6+
7+data KClass
8+ = Valid Event [Char]
9+ | Invalid
10+ | Prefix
11+ deriving(Show, Eq)
src/Graphics/Vty/Input/Events.hs+2−1
@@ -31,7 +31,8 @@ data Button = BLeft | BMiddle | BRight
3131data Event
3232 = EvKey Key [Modifier]
3333 -- | \todo mouse events are not supported
34− | EvMouse Int Int Button [Modifier]
34+ | EvMouseDown Int Int Button [Modifier]
35+ | EvMouseUp Int Int Button
3536 -- | if read from 'eventChannel' this is the size at the time of the signal. If read from
3637 -- 'nextEvent' this is the size at the time the event was processed by Vty. Typically these are
3738 -- the same, but if somebody is resizing the terminal quickly they can be different.
src/Graphics/Vty/Input/Mouse.hsnew file+127
@@ -0,0 +1,127 @@
1+module Graphics.Vty.Input.Mouse
2+ ( requestSGRMouseEvents
3+ , classifySGRMouseEvent
4+ ) where
5+
6+import Graphics.Vty.Input.Events
7+import Graphics.Vty.Input.Classify.Types
8+
9+import Control.Monad.Trans.Maybe
10+import Control.Monad.State
11+import Data.Maybe (catMaybes)
12+import Data.Bits ((.&.))
13+
14+-- This implementation was informed by
15+--
16+-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
17+
18+-- A mouse event in SGR extended mode is
19+--
20+-- '\ESC' '[' '<' B ';' X ';' Y ';' ('M'|'m')
21+--
22+-- where
23+--
24+-- * B is the number with button and modifier bits set,
25+-- * X is the X coordinate of the event starting at 1
26+-- * Y is the Y coordinate of the event starting at 1
27+-- * the final character is 'M' for a press, 'm' for a release
28+
29+-- | These sequences set xterm-based terminals to send mouse event
30+-- sequences. This requests SGR extended mouse events.
31+requestSGRMouseEvents :: String
32+requestSGRMouseEvents = "\ESC[?1000h\ESC[?1006h"
33+
34+-- Modifier bits:
35+shiftBit :: Int
36+shiftBit = 4
37+
38+metaBit :: Int
39+metaBit = 8
40+
41+ctrlBit :: Int
42+ctrlBit = 16
43+
44+-- These bits indicate the buttons involved:
45+buttonMask :: Int
46+buttonMask = 3
47+
48+leftButton :: Int
49+leftButton = 0
50+
51+middleButton :: Int
52+middleButton = 1
53+
54+rightButton :: Int
55+rightButton = 2
56+
57+hasBitSet :: Int -> Int -> Bool
58+hasBitSet val bit = val .&. bit > 0
59+
60+-- Given a modifer/button value, determine which button was indicated
61+getButton :: Int -> Parser Button
62+getButton mods =
63+ let buttonMap = [ (leftButton, BLeft)
64+ , (middleButton, BMiddle)
65+ , (rightButton, BRight)
66+ ]
67+ in case lookup (mods .&. buttonMask) buttonMap of
68+ Nothing -> failParse
69+ Just b -> return b
70+
71+getModifiers :: Int -> [Modifier]
72+getModifiers mods =
73+ catMaybes [ if mods `hasBitSet` shiftBit then Just MShift else Nothing
74+ , if mods `hasBitSet` metaBit then Just MMeta else Nothing
75+ , if mods `hasBitSet` ctrlBit then Just MCtrl else Nothing
76+ ]
77+
78+type Parser a = MaybeT (State String) a
79+
80+runParser :: String -> Parser Event -> KClass
81+runParser s parser =
82+ case runState (runMaybeT parser) s of
83+ (Nothing, _) -> Invalid
84+ (Just e, remaining) -> Valid e remaining
85+
86+failParse :: Parser a
87+failParse = fail "invalid parse"
88+
89+readInt :: Parser Int
90+readInt = do
91+ s <- get
92+ case (reads :: ReadS Int) s of
93+ [(i, rest)] -> do
94+ put rest
95+ return i
96+ _ -> failParse
97+
98+readChar :: Parser Char
99+readChar = do
100+ s <- get
101+ case s of
102+ c:rest -> put rest >> return c
103+ _ -> failParse
104+
105+expectChar :: Char -> Parser ()
106+expectChar c = do
107+ c' <- readChar
108+ if c' == c then return () else failParse
109+
110+-- Attempt to classify a control sequence as an SGR mouse event. To
111+-- get here we should have already read "\ESC[<" so that will not be
112+-- included in the string to be parsed.
113+classifySGRMouseEvent :: [Char] -> KClass
114+classifySGRMouseEvent s = runParser s $ do
115+ mods <- readInt
116+ expectChar ';'
117+ xCoord <- readInt
118+ expectChar ';'
119+ yCoord <- readInt
120+ final <- readChar
121+
122+ let modifiers = getModifiers mods
123+ button <- getButton mods
124+ case final of
125+ 'M' -> return $ EvMouseDown xCoord yCoord button modifiers
126+ 'm' -> return $ EvMouseUp xCoord yCoord button
127+ _ -> failParse
src/Graphics/Vty/Output/XTermColor.hs+2
@@ -9,6 +9,7 @@ module Graphics.Vty.Output.XTermColor ( reserveTerminal )
99 where
1010
1111import Graphics.Vty.Output.Interface
12+import Graphics.Vty.Input.Mouse (requestSGRMouseEvents)
1213import qualified Graphics.Vty.Output.TerminfoBased as TerminfoBased
1314
1415import Blaze.ByteString.Builder (writeToByteString)
@@ -33,6 +34,7 @@ reserveTerminal variant outFd = liftIO $ do
3334 -- xterm-color is broken.
3435 let variant' = if variant == "xterm-color" then "xterm" else variant
3536 flushedPut setUtf8CharSet
37+ flushedPut requestSGRMouseEvents
3638 t <- TerminfoBased.reserveTerminal variant' outFd
3739 let t' = t
3840 { terminalID = terminalID t ++ " (xterm-color)"
vty.cabal+2
@@ -94,7 +94,9 @@ library
9494 Graphics.Vty.DisplayAttributes
9595 Graphics.Vty.Image.Internal
9696 Graphics.Vty.Input.Classify
97+ Graphics.Vty.Input.Classify.Types
9798 Graphics.Vty.Input.Loop
99+ Graphics.Vty.Input.Mouse
98100 Graphics.Vty.Input.Terminfo
99101 Graphics.Vty.PictureToSpans
100102 Graphics.Vty.Span