@@ -0,0 +1,127 @@
1+module Graphics.Vty.Input.Mouse
2+ ( requestSGRMouseEvents
3+ , classifySGRMouseEvent
6+import Graphics.Vty.Input.Events
7+import Graphics.Vty.Input.Classify.Types
9+import Control.Monad.Trans.Maybe
10+import Control.Monad.State
11+import Data.Maybe (catMaybes)
12+import Data.Bits ((.&.))
14+-- This implementation was informed by
16+-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
18+-- A mouse event in SGR extended mode is
20+-- '\ESC' '[' '<' B ';' X ';' Y ';' ('M'|'m')
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
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"
44+-- These bits indicate the buttons involved:
57+hasBitSet :: Int -> Int -> Bool
58+hasBitSet val bit = val .&. bit > 0
60+-- Given a modifer/button value, determine which button was indicated
61+getButton :: Int -> Parser Button
63+ let buttonMap = [ (leftButton, BLeft)
64+ , (middleButton, BMiddle)
65+ , (rightButton, BRight)
67+ in case lookup (mods .&. buttonMask) buttonMap of
71+getModifiers :: Int -> [Modifier]
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
78+type Parser a = MaybeT (State String) a
80+runParser :: String -> Parser Event -> KClass
82+ case runState (runMaybeT parser) s of
83+ (Nothing, _) -> Invalid
84+ (Just e, remaining) -> Valid e remaining
87+failParse = fail "invalid parse"
92+ case (reads :: ReadS Int) s of
98+readChar :: Parser Char
102+ c:rest -> put rest >> return c
105+expectChar :: Char -> Parser ()
108+ if c' == c then return () else failParse
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
122+ let modifiers = getModifiers mods
123+ button <- getButton mods
125+ 'M' -> return $ EvMouseDown xCoord yCoord button modifiers
126+ 'm' -> return $ EvMouseUp xCoord yCoord button