packages feed

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

module System.Wacom.Profiles
  (
    WacomHandle,

    newWacomHandle,
    setProfile,
    getProfileName,
    setRingMode,
    toggleRingMode,
    getRingMode,
    getRingModeName,
    syncRingMode,
    initRingControlFile,
    setMapArea,
    getMapArea,

    emptyState,

    runProfile,
    xsetwacom
  )
  where

import Control.Monad
import qualified Data.Map as M
import Text.Printf
import System.Process

import Control.Concurrent

import System.Wacom.Types
import System.Wacom.Config
import System.Wacom.Internal
import qualified System.Wacom.Ring as Ring

emptyState :: Config -> MonState
emptyState cfg = MonState noDevice cfg Nothing Nothing Nothing Nothing

-- | Create new handle to communicate with daemon.
newWacomHandle :: Config -> IO WacomHandle
newWacomHandle cfg = do
  tvar <- newMVar $ emptyState cfg
  let wh = WacomHandle tvar
  return wh

renderTouch :: TabletDevice -> Config -> [String]
renderTouch td cfg =
  case dTouch td of
    Just touch -> [printf "\"%s touch\" Touch %s" touch on]
    Nothing -> []
  where
    on | tTouch cfg = "on"
       | otherwise  = "off"

renderRingMode :: TabletDevice -> RingMode -> [String]
renderRingMode td r = 
  case dPad td of
    Just pad ->
        [printf "\"%s pad\" AbsWheelDown \"%s\"" pad (show $ ringDown r),
         printf "\"%s pad\" AbsWheelUp \"%s\"" pad (show $ ringUp r)]
    Nothing -> []

renderButtons :: TabletDevice -> M.Map Int TabletAction -> [String]
renderButtons td m =
  case dPad td of
    Nothing -> []
    Just pad ->
      [printf "\"%s pad\" Button %d \"%s\"" pad idx (show action)
          | (idx,action) <- M.assocs m]

renderMapArea :: TabletDevice -> String -> [String]
renderMapArea td area =
  case dStylus td of
    Nothing -> []
    Just stylus ->
      [printf "\"%s stylus\" MapToOutput %s" stylus area]

-- | Run xsetwacom to apply selected profile
runProfile :: TabletDevice -> Config -> Maybe Profile -> Maybe RingMode -> IO ()
runProfile td cfg mbProfile mbMode = do
  let rmode = case mbMode of
                Nothing -> []
                Just ringMode -> renderRingMode td ringMode
      prof = case mbProfile of
                Nothing -> []
                Just profile -> renderButtons td (pButtons profile)
  xsetwacom $ 
    renderTouch td cfg ++
    rmode ++
    prof

-- | Run xsetwacom command with specified arguments
xsetwacom :: [String] -> IO ()
xsetwacom cmds = do
  forM_ cmds $ \cmd -> do
    let command = "xsetwacom set " ++ cmd
    putStrLn command
    spawnCommand command

-- | Set profile by name
setProfile :: WacomHandle -> String -> IO (Result String)
setProfile wh@(WacomHandle tvar) profileName = do
  st <- readMVar tvar
  let cfg = msConfig st
      mbMode = msRingMode st
      td = msDevice st
  case lookup profileName (tProfiles cfg) of
    Nothing -> return $ Left $ "Unknown profile " ++ profileName
    Just profile -> do
        putStrLn $ "Setting profile: " ++ profileName
        runProfile td cfg (Just profile) (snd `fmap` mbMode)
        modifyMVar_ tvar $ \st -> return $ st {msProfile = Just profile}
        setRingMode wh 0
        return $ Right profileName

-- | Set ring mode by index
setRingMode :: WacomHandle -> Int -> IO (Result String)
setRingMode (WacomHandle tvar) idx = do
  st <- readMVar tvar
  let cfg = msConfig st
      td = msDevice st
  case msProfile st of
    Nothing -> return $ Left "No current profile, can't set ring mode"
    Just profile ->
      if (idx >= 0) && (idx < length (pRing profile))
        then do
             let rmode = pRing profile !! idx
             runProfile td cfg Nothing (Just rmode)
             case msRingControl st of
               Nothing -> putStrLn "No ring control file"
               Just file -> Ring.setMode file idx
             modifyMVar_ tvar $ \st -> return $ st {msRingMode = Just (idx, rmode)}
             return $ Right $ show idx
        else return $ Left $ "Invalid ring mode index"

-- | Toggle ring mode: 0 -> 1 -> 2 -> 3 -> 0...
toggleRingMode :: WacomHandle -> IO (Result String)
toggleRingMode wh@(WacomHandle tvar) = do
  st <- readMVar tvar
  let mbMode = msRingMode st
  case msProfile st of
    Nothing -> return $ Left "No current profile, can't set ring mode"
    Just profile ->
      case mbMode of
        Nothing -> return $ Left $ "No current ring mode, cannot toggle"
        Just (idx, rmode) -> do
          let n = length (pRing profile)
              idx' = (idx + 1) `mod` n
          setRingMode wh idx'

-- | Obtain current ring mode index
getRingMode :: WacomHandle -> IO (Result String)
getRingMode (WacomHandle tvar) = do
  st <- readMVar tvar
  case msRingMode st of
    Nothing -> return $ Left "No ring mode"
    Just rmode -> return $ Right $ show $ fst rmode

-- | Obtain current ring mode name
getRingModeName :: WacomHandle -> IO (Result String)
getRingModeName (WacomHandle tvar) = do
  st <- readMVar tvar
  case msRingMode st of
    Nothing -> return $ Left "No ring mode"
    Just rmode -> return $ Right $ rName $ snd rmode

-- | Sync ring mode used by daemon with the mode indicated by tablet itself
syncRingMode :: WacomHandle -> IO (Result String)
syncRingMode wh@(WacomHandle tvar) = do
  st <- readMVar tvar
  case msRingControl st of
    Nothing -> return $ Left "No ring control file"
    Just file -> do
      idx <- Ring.readMode file
      setRingMode wh idx

-- | Obtain currently selected profile name
getProfileName :: WacomHandle -> IO (Result String)
getProfileName (WacomHandle tvar) = do
  st <- readMVar tvar
  case msProfile st of
    Nothing -> return $ Left "No current profile"
    Just profile -> return $ Right $ pName profile

-- | Return path of ring control file
initRingControlFile :: WacomHandle -> IO (Result String)
initRingControlFile (WacomHandle tvar) = do
  r <- Ring.getControlFile
  modifyMVar_ tvar $ \st -> return $ st {msRingControl = r}
  case r of
    Nothing -> return $ Left "No ring control file"
    Just file -> return $ Right file

-- | Set tablet mapping area by index
setMapArea :: WacomHandle -> Int -> IO (Result String)
setMapArea (WacomHandle tvar) idx = do
  st <- readMVar tvar
  let td = msDevice st
      cfg = msConfig st
  case tMapAreas cfg of
    [] -> return $ Left $ "No map areas defined"
    areas ->
      if (idx >= 0) && (idx < length areas)
        then do
             let area = areas !! idx
             xsetwacom $ renderMapArea td area
             modifyMVar_ tvar $ \st -> return $ st {msArea = Just area}
             return $ Right area
        else return $ Left "Invalid map area index"

-- | Return currenly selected mapping area
getMapArea :: WacomHandle -> IO (Result String)
getMapArea (WacomHandle tvar) = do
  st <- readMVar tvar
  case msArea st of
    Nothing -> return $ Left "No current map area"
    Just area -> return $ Right area