@@ -0,0 +1,153 @@
1+-- | This module provides parsers for mouse events for both "normal" and
2+-- "extended" modes. This implementation was informed by
4+-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
5+module Graphics.Vty.Input.Mouse
12+import Graphics.Vty.Input.Events
13+import Graphics.Vty.Input.Classify.Types
14+import Graphics.Vty.Input.Classify.Parse
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 ((.&.))
22+-- A mouse event in SGR extended mode is
24+-- '\ESC' '[' '<' B ';' X ';' Y ';' ('M'|'m')
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
33+-- | These sequences set xterm-based terminals to send mouse event
35+requestMouseEvents :: String
36+requestMouseEvents = "\ESC[?1000h\ESC[?1002h\ESC[?1006h\ESC[?1005h"
38+-- | These sequences disable mouse events.
39+disableMouseEvents :: String
40+disableMouseEvents = "\ESC[?1000l\ESC[?1002l\ESC[?1006l\ESC[?1005l"
42+-- | Does the specified string begin with a mouse event?
43+isMouseEvent :: String -> Bool
44+isMouseEvent s = isSGREvent s || isNormalEvent s
46+isSGREvent :: String -> Bool
47+isSGREvent = isPrefixOf sgrPrefix
52+isNormalEvent :: String -> Bool
53+isNormalEvent = isPrefixOf normalPrefix
55+normalPrefix :: String
56+normalPrefix = "\ESC[<"
68+-- These bits indicate the buttons involved:
81+hasBitSet :: Int -> Int -> Bool
82+hasBitSet val bit = val .&. bit > 0
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
93+ '<' -> classifySGRMouseEvent
94+ 'M' -> classifyNormalMouseEvent
97+-- Given a modifer/button value, determine which button was indicated
98+getSGRButton :: Int -> Parser Button
100+ let buttonMap = [ (leftButton, BLeft)
101+ , (middleButton, BMiddle)
102+ , (rightButton, BRight)
104+ in case lookup (mods .&. buttonMask) buttonMap of
105+ Nothing -> failParse
108+getModifiers :: Int -> [Modifier]
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
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
124+ let xCoord = fromEnum xCoordChar - 32
125+ yCoord = fromEnum yCoordChar - 32
126+ status = fromEnum statusChar
127+ modifiers = getModifiers status
129+ let press = status .&. buttonMask /= 3
132+ button <- getSGRButton status
133+ return $ EvMouseDown xCoord yCoord button modifiers
134+ False -> return $ EvMouseUp xCoord yCoord Nothing
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
148+ let modifiers = getModifiers mods
149+ button <- getSGRButton mods
151+ 'M' -> return $ EvMouseDown xCoord yCoord button modifiers
152+ 'm' -> return $ EvMouseUp xCoord yCoord (Just button)