{-# language StandaloneDeriving, FlexibleInstances, MagicHash, OverloadedStrings,PatternSynonyms, BlockArguments, ViewPatterns #-} module Keyboard (module Keyboard, XenoException(..)) where import Text.Printf import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE import qualified Data.Map as Map import Data.Map (Map, (!)) import Data.Word (Word8) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as BS import qualified Data.Text as Text import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import Data.Char (toLower) import XML import Data.String newtype Id a = Id {unId :: ByteString} instance IsString (Id a) where fromString = Id . BS.pack newtype Ix a = Ix {unIx :: Word} newtype Group = Group {unGroup :: Word} parse :: ByteString -> Either XenoException Keyboard parse = fmap fromXML . XML.parse export :: Keyboard -> ByteString export = BS.pack . toXML data Keyboard = Keyboard {keyboard_group :: Group ,keyboard_id :: Word ,keyboard_name :: ByteString ,keyboard_maxout :: Maybe Word ,keyboard_layouts :: Layouts ,keyboard_modifierMap :: ModifierMap ,keyboard_keyMapSets :: NonEmpty KeyMapSet ,keyboard_actions :: [Action] ,keyboard_terminators :: [When_Terminator] } instance XML Keyboard where fromXML (XML "keyboard" as cs) = Keyboard {keyboard_group = Map.findWithDefault (Group 126) "group" $ fmap (Group . readBS) as ,keyboard_id = Map.findWithDefault 1 "id" $ fmap (readBS) as ,keyboard_name = as ! "name" ,keyboard_maxout = (read . BS.unpack) <$> Map.lookup "maxount" as ,keyboard_layouts = find# "layouts" cs ,keyboard_modifierMap = find# "modifierMap" cs ,keyboard_keyMapSets = NE.fromList $ find' "keyMapSet" cs ,keyboard_actions = case cs & find \ a -> name a == "actions" of Nothing -> [] Just (XML "actions" _ cs) -> fmap fromXML cs ,keyboard_terminators = case cs & find \ a -> name a == "terminators" of Nothing -> [] Just (XML "terminators" _ cs) -> fmap fromXML cs} toXML k = printf "\n%s\n%s\n%s%s%s\n" (unGroup $ keyboard_group k) (keyboard_id k) (show $ keyboard_name k) (case keyboard_maxout k of Nothing -> ("" :: String) Just i -> printf "maxout=\"%s\"" (show i) :: String) (toXML $ keyboard_layouts k) (toXML $ keyboard_modifierMap k) (unlines . fmap toXML . NE.toList $ keyboard_keyMapSets k) (case keyboard_actions k of [] -> ("" :: String) as -> printf "\n\t\n%s\t" (unlines . fmap toXML $ as) :: String) (case keyboard_terminators k of [] -> ("" :: String) ts -> printf "\n\t\n%s\t" (unlines . fmap toXML $ ts) :: String) newtype Layouts = Layouts {layouts_layouts :: NonEmpty Layout} instance XML Layouts where fromXML (XML "layouts" as cs) = Layouts . NE.fromList $ find' "layout" cs toXML (Layouts ls) = printf "\t\n%s\t" . unlines . fmap toXML $ NE.toList ls data Layout = Layout {layout_first :: Word, layout_last :: Word ,layout_modifiers :: Id ModifierMap ,layout_mapSet :: KeyMapSet_Id} instance XML Layout where fromXML (XML "layout" as cs) = Layout {layout_first = readBS $ as ! "first" ,layout_last = readBS $ as ! "last" ,layout_modifiers = Id $ as ! "modifiers" ,layout_mapSet = readBS $ as ! "mapSet"} toXML l = printf "\t\t" (layout_first l) (layout_last l) (BS.unpack . unId $ layout_modifiers l) (show $ layout_mapSet l) data ModifierMap = ModifierMap {modifierMap_id :: Id ModifierMap ,modifierMap_defaultIndex :: Ix KeyMap ,modifierMap_keyMapSelects :: NonEmpty KeyMapSelect} instance XML ModifierMap where fromXML (XML "modifierMap" as cs) = ModifierMap {modifierMap_id = Id $ as ! "id" ,modifierMap_defaultIndex = Ix . readBS $ as ! "defaultIndex" ,modifierMap_keyMapSelects = NE.fromList $ find' "keyMapSelect" cs} toXML m = printf "\t\n%s\t" (BS.unpack . unId $ modifierMap_id m) (unIx $ modifierMap_defaultIndex m) (unlines . fmap toXML . NE.toList $ modifierMap_keyMapSelects m) data KeyMapSelect = KeyMapSelect {keyMapSelect_mapIndex :: Ix KeyMap ,keyMapSelect_modifiers :: NonEmpty Modifier } instance XML KeyMapSelect where fromXML (XML "keyMapSelect" as cs) = KeyMapSelect {keyMapSelect_mapIndex = Ix . readBS $ as ! "mapIndex" ,keyMapSelect_modifiers = NE.fromList $ find' "modifier" cs } toXML kms = printf "\t\t\n%s\t\t" (unIx $ keyMapSelect_mapIndex kms) (unlines . fmap toXML . NE.toList $ keyMapSelect_modifiers kms) newtype Modifier = Modifier {modifier_keys :: ModifierChord} instance XML Modifier where fromXML (XML "modifier" as _) = Modifier $ parseChord $ as ! "keys" where parseChord :: ByteString -> ModifierChord parseChord (BS.words -> c) = ModifierChord $ Map.fromList $ fmap parseModifier c where parseModifier :: ByteString -> (ModifierKey, ModifierStatus) parseModifier = \case (BS.stripPrefix "command" -> Just (status -> s)) -> (Command, s) (BS.stripPrefix "caps" -> Just (status -> s)) -> (Caps, s) (BS.stripPrefix "any" -> Just (modifier -> (m,s)) ) -> (Mod m Any, s) (BS.stripPrefix "right" -> Just (modifier -> (m,s)) ) -> (Mod m R, s) (modifier -> (m , s)) -> (Mod m L, s) _ -> error "parseModifier" status = \case {"" -> (:!); "?" -> (:?); _ -> error "status"} modifier (BS.map toLower -> m) = case m of (BS.stripPrefix "shift" -> Just (status -> s)) -> (Shift , s) (BS.stripPrefix "option" -> Just (status -> s)) -> (Option , s) (BS.stripPrefix "control" -> Just (status -> s)) -> (Option , s) x -> error $ "modifier " ++ show x toXML (Modifier ks) = printf "\t\t\t" $ printChord ks where printChord :: ModifierChord -> String printChord (ModifierChord (Map.toList -> m)) = unwords $ fmap (\(mk,s) -> printKey mk ++ printStatus s) m printKey = \case Caps -> "caps" Command -> "command" Mod (show -> md) s -> case s of {L -> uncap md; R -> "right" ++ md; Any -> "any" ++ md} uncap (a:as) = toLower a : as printStatus = \case {(:!) -> ""; (:?) -> "?"} newtype ModifierChord = ModifierChord (Map ModifierKey ModifierStatus) data Mod = Shift | Option | Control deriving (Eq, Ord) data Side = L | R | Any deriving (Eq,Ord) instance Semigroup Side where L <> L = L; R <> R = R; _ <> _ = Any data ModifierKey = Mod Mod Side | Command | Caps deriving (Eq,Ord) data ModifierStatus = (:!) {- ^ Required -} | (:?) {- ^ Irrelevant -} deriving Eq instance Semigroup ModifierChord where ModifierChord m <> n = Map.foldrWithKey (\k v r -> include k v r) n m where include :: ModifierKey -> ModifierStatus -> ModifierChord -> ModifierChord include k v (ModifierChord m) = case k of Mod mod Any -> ModifierChord $ Map.insert k v $ Map.delete (Mod mod L) $ Map.delete (Mod mod R) m Mod mod s | Map.lookup (Mod mod $ swap s) m == Just v -> include (Mod mod Any) v (ModifierChord m) _ -> ModifierChord $ Map.insert k v m where swap L = R; swap R = L; swap Any = Any instance Monoid ModifierChord where mempty = ModifierChord Map.empty data KeyMapSet_Id = ANSI | JIS {- ^ Japan -} deriving (Eq, Read) data KeyMapSet = KeyMapSet {keyMapSet_id :: KeyMapSet_Id, keyMapSet_keyMaps :: NonEmpty KeyMap} instance XML KeyMapSet where fromXML (XML "keyMapSet" (readBS . (! "id") -> i) cs) = KeyMapSet {keyMapSet_id = i ,keyMapSet_keyMaps = NE.fromList $ find' "keyMap" cs} toXML kms = printf "\t\n%s\t" (show $ keyMapSet_id kms) (unlines . fmap toXML . NE.toList $ keyMapSet_keyMaps kms) data KeyMap = KeyMap {keyMap_index :: Ix KeyMap ,keyMap_base :: Maybe (KeyMapSet_Id, Ix KeyMap) ,keyMap_keys :: NonEmpty Key } instance XML KeyMap where fromXML (XML "keyMap" as cs) = KeyMap {keyMap_index = Ix . readBS $ as ! "index" ,keyMap_base = (,) <$> ( readBS <$> Map.lookup "baseMapSet" as) <*> (Ix . readBS <$> Map.lookup "baseIndex" as) ,keyMap_keys = NE.fromList $ find' "key" cs} toXML km = printf "\t\t\n%s\t\t" (unIx $ keyMap_index km) (case keyMap_base km of Nothing -> "" :: String Just (bms,Ix bi) -> printf " baseMapSet=\"%s\" baseIndex=\"%d\"" (show bms) bi) (unlines $ fmap toXML $ NE.toList $ keyMap_keys km) data KeyCode = Virtual ByteString data Key = KeyOutput {key_code :: KeyCode , key_output :: ByteString} | KeyNamedAction {key_code :: KeyCode, key_namedAction :: Id Action} | KeyAction {key_code :: KeyCode, key_action :: NonEmpty When} instance XML Key where fromXML (XML "key" as cs) = let (Just (Virtual -> code), as') = Map.updateLookupWithKey (\_ _ -> Nothing) "code" as in case (Map.toList as',cs) of ([("output",o)], []) -> KeyOutput code o ([("action",a)], []) -> KeyNamedAction code (Id a) ([], [a]) -> KeyAction code $ fromXML a _ -> error "fromXML Key" toXML = \case KeyOutput (Virtual (BS.unpack -> c)) (BS.unpack -> o) -> printf "\t\t\t" c o KeyNamedAction (Virtual (BS.unpack -> c)) (Id (BS.unpack -> a)) -> printf "\t\t\t" c a KeyAction (Virtual (BS.unpack -> c)) ws -> printf "\t\t\t\n%s\t\t\t" c $ toXML ws -- -- | Single Action within 'Key' instance XML (NonEmpty When) where fromXML (XML "action" _ (fmap fromXML -> ws)) = NE.fromList ws toXML = printf "\n%s" . unlines . fmap toXML . NE.toList data Action = Action {action_id :: Id Action, action_whens :: NonEmpty When} instance XML Action where fromXML (XML "action" ((Id . (! "id")) -> i) cs) = Action i . NE.fromList $ fmap fromXML cs toXML a = printf "\t\t\n%s\t\t" (BS.unpack . unId $ action_id a) (unlines . fmap toXML . NE.toList $ action_whens a) type StateNum = Word data State = State ByteString | None data When = WhenRange {when_range :: (StateNum,StateNum) ,when_range_next :: Maybe StateNum ,when_range_output :: Maybe Char ,when_multiplier :: Maybe Word8 -- ^ The difference between the input state and the start of the range -- is multiplied by this number, then added to the next state number -- and/or the output UTF-16 value. } | When {when_state :: State ,when_next :: Maybe State ,when_output :: Maybe ByteString } instance XML When where fromXML (XML "when" as cs) = let state = as ! "state" in case (Map.lookup "through" as, Map.lookup "next" as, Map.lookup "output" as, Map.lookup "multiplier" as) of (Just t,n,o,m) -> WhenRange {when_range = (readBS state,readBS t) ,when_range_next = readBS <$> n ,when_range_output = (Text.head . Text.decodeUtf16LE) <$> o ,when_multiplier = readBS <$> m} (Nothing,n,o,Nothing) -> When (parseState state) (parseState <$> n) o _ -> error "fromXML When" where parseState = \case "none" -> None; s -> State s toXML = \case WhenRange (a,b) n o m -> printf "\t\t\t" a b (case n of Nothing -> "" :: String Just a -> printf " next=\"%d\"" a) (case o of Nothing -> "" :: String Just a -> printf " output=\"%s\"" [a]) (case m of Nothing -> "" :: String Just a -> printf " multiplier=\"%d\"" a) When s n o -> printf "\t\t\t" (printState s) (case n of Nothing -> "" :: String Just a -> printf " next=\"%s\"" $ printState a) (case o of Nothing -> "" :: String Just a -> printf " output=\"%s\"" $ BS.unpack a) where printState = \case {None -> "none"; State s -> BS.unpack s} data When_Terminator = When_Terminator {when_terminator_state :: State, when_terminator_output :: ByteString} instance XML When_Terminator where fromXML (XML "when" as _) = When_Terminator (State $ as ! "state") (as ! "output") toXML (When_Terminator s o) = printf "\t\t" (case s of None -> "none"; State a -> BS.unpack a) (BS.unpack o) deriving instance Show Keyboard deriving instance Show Group deriving instance Show Layouts deriving instance Show ModifierMap deriving instance Show KeyMapSet deriving instance Show Action deriving instance Show When_Terminator deriving instance Show Layout deriving instance Show (Id a) deriving instance Show (Ix a) deriving instance Show KeyMapSelect deriving instance Show KeyMapSet_Id deriving instance Show KeyMap deriving instance Show When deriving instance Show State deriving instance Show Modifier deriving instance Show Key deriving instance Show ModifierChord deriving instance Show KeyCode deriving instance Show ModifierKey deriving instance Show ModifierStatus deriving instance Show Mod deriving instance Show Side