packages feed

workflow-osx-0.0.0: sources/Workflow/OSX/Execute.hs

{-# LANGUAGE LambdaCase #-}
module Workflow.OSX.Execute where
import Workflow.OSX.Bindings as ObjC
import Workflow.OSX.Types

import Control.Monad.Free
import Control.Monad.Trans.State

import Control.Concurrent             (threadDelay)

import Data.Foldable                  (traverse_)
import Data.List                      (intercalate)
import Data.Monoid                    ((<>))


runWorkflow :: Workflow a -> IO a
runWorkflow = iterM $ \case
 -- iterM :: (Monad m, Functor f) => (f (m a) -> m a) -> Free f a -> m a

 SendKeyChord    flags key k      -> ObjC.pressKey flags key >> k
 SendText        s k              -> runWorkflow (sendTextAsKeypresses s) >> k
 -- terminates because sendTextAsKeypresses is exclusively a sequence of SendKeyChord'es

 -- TODO SendMouseClick  flags n button k -> ObjC.clickMouse flags n button >> k

 GetClipboard    f                -> ObjC.getClipboard >>= f
 SetClipboard    s k              -> ObjC.setClipboard s >> k

 CurrentApplication f             -> ObjC.currentApplication >>= f
 OpenApplication app k            -> ObjC.openApplication app >> k
 OpenURL         url k            -> ObjC.openURL url >> k

 Delay           t k              -> threadDelay (t*1000) >> k
 -- 1,000 µs is 1ms

-- | returns a sequence of 'SendKeyChord'es.
sendTextAsKeypresses :: String -> Workflow ()
sendTextAsKeypresses
 = traverse_ (\(modifiers, key) -> liftF $ SendKeyChord modifiers key ())
 . concatMap char2keypress
 -- liftF :: WorkflowF () -> Free WorkflowF ()

runWorkflowWithDelay :: Int -> Workflow a -> IO a
runWorkflowWithDelay t = iterM $ \case
 -- iterM :: (Monad m, Functor f) => (f (m a) -> m a) -> Free f a -> m a

 SendKeyChord    flags key k      -> threadDelay (t*1000) >> ObjC.pressKey flags key             >> k
 SendText        s k              -> runWorkflow (sendTextAsKeypressesWithDelay t s)              >> k

 GetClipboard    f                -> threadDelay (t*1000) >> ObjC.getClipboard                   >>= f
 SetClipboard    s k              -> threadDelay (t*1000) >> ObjC.setClipboard s                 >> k

 CurrentApplication f             -> threadDelay (t*1000) >> ObjC.currentApplication             >>= f
 OpenApplication app k            -> threadDelay (t*1000) >> ObjC.openApplication app            >> k
 OpenURL         url k            -> threadDelay (t*1000) >> ObjC.openURL url                    >> k

 Delay           t_ k              -> threadDelay (t_*1000)                                      >> k
 -- 1,000 µs is 1ms

-- | returns a sequence of 'SendKeyChord'es.
sendTextAsKeypressesWithDelay :: Int -> String -> Workflow ()
sendTextAsKeypressesWithDelay t
 = traverse_ (\(modifiers, key) -> do
    liftF $ Delay t                    ()
    liftF $ SendKeyChord modifiers key ())
 . concatMap char2keypress
 -- liftF :: WorkflowF () -> Free WorkflowF ()

{- | shows the "static" data flow of some 'Workflow', by showing its primitive operations, in @do-notation@.

e.g.

>>> :{
putStrLn . showWorkflow $ do
 sendKeyChord [Command, Shift] BKey
 delay 1000
 sendKeyChord [Command] DownArrowKey
 x1 <- currentApplication
 x2 <- getClipboard
 openURL $ "https://www.google.com/search?q=" <> x2
 setClipboard x1
 getClipboard
:}
do
 sendKeyChord ([Command,Shift]) (BKey)
 delay (1000)
 sendKeyChord ([Command]) (DownArrowKey)
 x1 <- currentApplication
 x2 <- getClipboard
 openURL ("https://www.google.com/search?q={x2}")
 setClipboard ("{x1}")
 x3 <- getClipboard
 return "{x3}"

(note: doesn't print variables as raw strings (cf. 'print' versus 'putStrLn'), as it doesn't "crystallize" all operations into "symbols", but gives you an idea of the data flow. however, it does correctly track the control flow, even when the variables are used non-sequentially.)

(note: the variables in the code were named to be consistent with
'gensym', for readability. but of course the bindings aren't reified,
and they could have been named anything)

basically, the monadically-bound variable @x1@ is shown as if it were literally @"{x1}"@ (rather than, the current clipboard contents). a more complicated alternative could be to purely model the state: e.g. a clipboard, with 'SetClipboard' and 'GetClipboard' working together, etc.).

-}
showWorkflow :: (Show x) => Workflow x -> String
showWorkflow as = "do\n" <> evalState (showWorkflow_ as) 1

 where
 showWorkflow_ :: (Show x) => Workflow x -> State Gensym String
 showWorkflow_ (Pure x) = return $ " return " <> show x <> "\n"
 showWorkflow_ (Free a) = showWorkflowF a

 showWorkflowF :: (Show x) => WorkflowF (Workflow x) -> State Gensym String
 showWorkflowF = \case
  SendKeyChord    flags key k -> ((" sendKeyChord "    <> showArgs [show flags, show key]) <>)       <$> showWorkflow_ k
  -- TODO SendMouseClick  flags n b k -> ((" sendMouseClick "  <> showArgs [show flags, show n, show b]) <>) <$> showWorkflow_ k
  SendText        s k         -> ((" sendText "        <> showArgs [show s]) <>)                     <$> showWorkflow_ k

  SetClipboard    s k         -> ((" setClipboard "    <> showArgs [show s]) <>)                     <$> showWorkflow_ k
  OpenApplication app k       -> ((" openApplication " <> showArgs [show app]) <>)                   <$> showWorkflow_ k
  OpenURL         url k       -> ((" openURL "         <> showArgs [show url]) <>)                   <$> showWorkflow_ k
  Delay           t k         -> ((" delay "           <> showArgs [show t]) <>)                     <$> showWorkflow_ k

 -- TODO distinguish between strings and variables to avoid:
 -- x2 <- getClipboard
 -- sendText ("x2")

  GetClipboard f -> do
   x <- gensym
   rest <- showWorkflow_ (f ("{"<>x<>"}"))
   return $ " " <> x <> " <- getClipboard" <> showArgs [] <> rest

  CurrentApplication f -> do
   x <- gensym
   rest <- showWorkflow_ (f ("{"<>x<>"}"))
   return $ " " <> x <> " <- currentApplication" <> showArgs [] <> rest

 showArgs :: [String] -> String
 showArgs xs = intercalate " " (fmap (("(" <>) . (<> ")")) xs) <> "\n"

type Gensym = Int

gensym :: State Gensym String
gensym = do
 i <- get
 put $ i + 1
 return $ "x" <> show i