packages feed

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

{-# LANGUAGE OverloadedStrings #-}

module System.Wacom.Daemon
  where

import Control.Monad

import Data.List

import Control.Concurrent

import System.UDev hiding (isEmpty)
import System.Posix.IO.Select
import System.Posix.IO.Select.Types hiding (Result)

import System.Wacom.Types
import System.Wacom.Internal
import System.Wacom.Util
import System.Wacom.Profiles
import System.Wacom.Config

-- | Detect tablet devices by using udev
detectAtStartup :: Config -> UDev -> IO (Maybe TabletDevice)
detectAtStartup cfg udev = do
    list <- initEnumeration
    deviceNames <- enumerateDevices list
    let wacomDevices = filter isWacom $ map fromBS deviceNames
    if null wacomDevices
      then return Nothing
      else do
           let names = map (trim . trimQuotes) wacomDevices
               stylus = pick (tStylusKey cfg) names
               pad    = pick (tPadKey cfg) names
               touch  = pick (tTouchKey cfg) names
               dev = TabletDevice stylus pad touch
           return $ Just dev
  where
    initEnumeration = do 
      enum <- newEnumerate udev
      addMatchSubsystem enum "input"
      scanDevices enum
      list <- getListEntry enum
      return list

    enumerateDevices list = iter list

    iter (Just list) = do
      path <- getName list
      dev <- newFromSysPath udev path
      mbName <- getPropertyValue dev "NAME"
      x <- getNext list
      rest <- iter x
      case mbName of
        Nothing -> return rest
        Just new -> do
            -- C8.putStrLn $ "Device: " `B.append` new
            return (new : rest)
    iter Nothing = do
      putStrLn "End."
      return []

    pick _ [] = Nothing
    pick suffix (name : names)
      | suffix `isSuffixOf` name = Just name
      | otherwise = pick suffix names

-- | To be called before starting udevMonitor.
initUdevMonitor :: WacomHandle -> IO ()
initUdevMonitor wh@(WacomHandle tvar) = do
    putStrLn "Udev monitor initing..."
    return ()

-- | UDev device monitor daemon.
-- To be called in separate thread (by forkIO).
-- This daemon implements the following functionality:
--
--  * Detects attached tablet at startup (via udev).
--
--  * Detects tablet plugging while running (via udev).
--
--  * Tracks selected settings profile and ring mode.
--
--  * Applies selected settings by calling xsetwacom automatically
--    after tablet is attached.
--
udevMonitor :: WacomHandle -> IO ()
udevMonitor wh@(WacomHandle tvar) = withUDev $ \udev -> do
    putStrLn "Udev monitor starting..."

    st <- readMVar tvar
    let cfg = msConfig st
    mbDev <- detectAtStartup cfg udev
    case mbDev of
      Just dev -> do
                  modifyMVar_ tvar $ \st ->
                             return st {msDevice = dev}
      _ -> return ()

    st <- readMVar tvar
    onPlug (msConfig st)
    monitor <- newFromNetlink udev UDevId
    filterAddMatchSubsystemDevtype monitor "input" Nothing
    enableReceiving monitor
    fd <- getFd monitor
    forever $ do
      res <- select' [fd] [] [] Never
      case res of
        Just ([_], [], []) -> do
          dev <- receiveDevice monitor
          st <- readMVar tvar
          handleDevice (msConfig st) dev
        Nothing -> return ()
    return ()
  where
    handleDevice :: Config -> Device -> IO ()
    handleDevice cfg dev = do
      mbName <- getPropertyValue dev "NAME"
      let mbAction = getAction dev
      case (mbName, mbAction) of
        (Just name, Just action) -> handle cfg action (trimQuotes $ trim $ fromBS name)
        _ -> return ()

    handle :: Config -> Action -> String -> IO ()
    handle cfg Add name
      | isWacom name && (tStylusKey cfg) `isInfixOf` name = setStylus $ Just name
      | isWacom name && (tPadKey cfg) `isInfixOf` name = setPad $ Just name
      | isWacom name && (tTouchKey cfg) `isInfixOf` name = setTouch $ Just name
      | otherwise = putStrLn $ "Add " ++ name
    handle cfg Remove name
      | isWacom name && (tStylusKey cfg) `isInfixOf` name = setStylus Nothing
      | isWacom name && (tPadKey cfg) `isInfixOf` name = setPad Nothing
      | isWacom name && (tTouchKey cfg) `isInfixOf` name = setTouch Nothing
      | otherwise = putStrLn $ "Remove " ++ name
    handle _ action name =
      putStrLn $ show action ++ " " ++ name

    setStylus :: Maybe String -> IO ()
    setStylus x = do
      putStrLn $ "Detected stylus: " ++ show x
      modifyMVar_ tvar $ \st -> return $ st {msDevice = (msDevice st) {dStylus = x}}
      st <- readMVar tvar
      when (isFull $ msDevice st) $ onPlug (msConfig st)
      when (isEmpty $ msDevice st) onUnplug

    setPad :: Maybe String -> IO ()
    setPad x = do
      putStrLn $ "Detected pad: " ++ show x
      modifyMVar_ tvar $ \st -> return $ st {msDevice = (msDevice st) {dPad = x}}
      st <- readMVar tvar
      when (isFull $ msDevice st) $ onPlug (msConfig st)
      when (isEmpty $ msDevice st) onUnplug

    setTouch :: Maybe String -> IO ()
    setTouch x = do
      putStrLn $ "Detected touch: " ++ show x
      modifyMVar_ tvar $ \st -> return $ st {msDevice = (msDevice st) {dTouch = x}}
      st <- readMVar tvar
      when (isFull $ msDevice st) $ onPlug (msConfig st)
      when (isEmpty $ msDevice st) onUnplug

    onPlug :: Config -> IO ()
    onPlug cfg = do
      st <- readMVar tvar
      let dev = msDevice st
      putStrLn $ "Plugged " ++ show dev
      let profile = msProfile st
          rmode = msRingMode st
      initRingControlFile wh
      runProfile dev cfg profile (snd `fmap` rmode)
      return ()

    onUnplug :: IO ()
    onUnplug = do
      d <- readMVar tvar
      putStrLn $ "Unplugged " ++ show (msDevice d)