packages feed

wacom-daemon-0.1.0.0: System/Wacom/X11.hs

{-# LANGUAGE RecordWildCards #-}

module System.Wacom.X11
  (
    ModeAction (..),
    KeyMap,
    getWindowInfo,
    withDisplay,
    getNumlockMask,
    cleanMask,
    parseShortcut,
    grabHotkeys,
    compileKeyMap
  ) where

import Control.Monad
import Control.Exception as E
import qualified Data.Map as M
import Data.Bits

import Graphics.X11
import Graphics.X11.Xlib.Extras

import System.Wacom.Matching

data ModeAction =
    ToggleRingMode
  | SetMappingArea Int
  deriving (Eq, Show)

type KeyMap = M.Map (KeyMask, KeySym) ModeAction

getTitle :: Display -> Window -> IO String
getTitle d w = do
    let getProp =
            (internAtom d "_NET_WM_NAME" False >>= getTextProperty d w)
                `E.catch` \(SomeException e) -> do
                                                putStrLn (show e)
                                                getTextProperty d w wM_NAME
        extract prop = do l <- wcTextPropertyToTextList d prop
                          return $ if null l then "<?>" else head l
    bracket getProp (xFree . tp_value) extract `E.catch` \(SomeException e) -> return (show e)

getWindow :: Display -> Atom -> Window -> IO (Maybe Window)
getWindow dpy atom root = do
  r <- getWindowProperty32 dpy atom root
  case r of
    Just [w] -> return $ Just $ fromIntegral w
    _ -> return Nothing

-- | Obtain basic information about currently active window
getWindowInfo :: Display
              -> Atom                 -- ^ _NET_ACTIVE_WINDOW atom
              -> Window               -- ^ X root window
              -> IO (Maybe WindowInfo)
getWindowInfo dpy atom root = do
  r <- getWindow dpy atom root
  case r of
    Nothing -> return Nothing
    Just 0 -> return Nothing
    Just window -> do
      title <- getTitle dpy window
      cls <- resName `fmap` getClassHint dpy window
      return $ Just $ WindowInfo title cls

withDisplay :: String -> (Display -> IO a) -> IO a
withDisplay str fn = do
  dpy <- openDisplay str
  res <- fn dpy
  closeDisplay dpy
  return res

-- | Detect KeyMask corresponding to NumLock modifier
getNumlockMask :: Display -> IO KeyMask
getNumlockMask dpy = do
    ms <- getModifierMapping dpy
    xs <- sequence [ do
                        ks <- keycodeToKeysym dpy kc 0
                        if ks == xK_Num_Lock
                            then return (setBit 0 (fromIntegral m))
                            else return (0 :: KeyMask)
                        | (m, kcs) <- ms, kc <- kcs, kc /= 0]
    return $ foldr (.|.) 0 xs 

-- | Strip numlock\/capslock from a mask
cleanMask :: KeyMask -> KeyMask -> KeyMask
cleanMask numlock km =
    (complement (numlock .|. lockMask) .&. km)

-- | Parse X11 KeyMask + KeySym from xsetwacom-style specification.
--
-- Supported are strings like @F11@, @ctrl alt z@.
parseShortcut :: String -> Maybe (Modifier, KeySym)
parseShortcut str = do
    let ws = words str
    case ws of
      [] -> Nothing
      [w] -> do
             k <- parseKey w
             return (noModMask, k)
      _ -> do
           mods <- parseMods (init ws)
           k <- parseKey (last ws)
           return (mods, k)
  where
    parseKey :: String -> Maybe KeySym
    parseKey key = 
      case stringToKeysym key of
               0 -> Nothing
               k -> Just k

    parseMods :: [String] -> Maybe Modifier
    parseMods mods = foldr (.|.) 0 `fmap` mapM parseMod mods

    parseMod :: String -> Maybe Modifier
    parseMod "ctrl" = Just controlMask 
    parseMod "shift" = Just shiftMask
    parseMod "alt" = Just mod1Mask
    parseMod "mod1" = Just mod1Mask
    parseMod "mod2" = Just mod2Mask
    parseMod "mod3" = Just mod3Mask
    parseMod "mod4" = Just mod4Mask
    parseMod "mod5" = Just mod5Mask
    parseMod _ = Nothing

grabHotkey :: Display -> Window -> KeyMask -> String -> IO ()
grabHotkey dpy rootw numlock keystr = do
  case parseShortcut keystr of
    Nothing -> fail $ "Unsupported shortcut: " ++ keystr
    Just (mod, keysym) -> do
        putStrLn $ "Grabbing hotkey: " ++ keystr
        hotkey <- keysymToKeycode dpy keysym
        -- Grab both mask+key and numlock+mask+key.
        grabKey dpy hotkey mod rootw True grabModeAsync grabModeAsync
        grabKey dpy hotkey (mod .|. numlock) rootw True grabModeAsync grabModeAsync

grabHotkeys :: Display -> Window -> KeyMask -> [String] -> IO ()
grabHotkeys dpy rootw numlock keys = do
    forM_ keys $ \keystr ->
        grabHotkey dpy rootw numlock keystr

compileKeyMap :: Display -> KeyMapConfig -> IO KeyMap
compileKeyMap dpy KeyMapConfig {..} = do
    ring <- case kmRingMode of
              Nothing -> return []
              Just keystr -> do
                             key <- compile keystr
                             return [(key, ToggleRingMode)]
    areas <- do
             keys <- mapM compile kmMapAreas
             return [(key, SetMappingArea i) | (i,key) <- zip [0..] keys]
    return $ M.fromList $ ring ++ areas
  where
    compile :: String -> IO (KeyMask, KeySym)
    compile keystr = do
      case parseShortcut keystr of
        Nothing -> fail $ "Unsupported shortcut: " ++ keystr
        Just (mod, keysym) -> do
          return (mod, keysym)