robot (empty) → 1.0
raw patch · 13 files changed
+1504/−0 lines, 13 filesdep +basedep +containersdep +transformerssetup-changed
Dependencies added: base, containers, transformers, xhb
Files
- LICENSE +202/−0
- README.markdown +34/−0
- Setup.hs +2/−0
- Test/Robot.hs +89/−0
- Test/Robot/Internal.hs +101/−0
- Test/Robot/Internal/XTest.hs +115/−0
- Test/Robot/Types.hs +61/−0
- Test/Robot/Types/Core.hs +18/−0
- Test/Robot/Types/Keys.hs +569/−0
- examples/RightClick.hs +10/−0
- generate_keys.py +66/−0
- keys.txt +198/−0
- robot.cabal +39/−0
+ LICENSE view
@@ -0,0 +1,202 @@++ Apache License+ Version 2.0, January 2004+ http://www.apache.org/licenses/++ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION++ 1. Definitions.++ "License" shall mean the terms and conditions for use, reproduction,+ and distribution as defined by Sections 1 through 9 of this document.++ "Licensor" shall mean the copyright owner or entity authorized by+ the copyright owner that is granting the License.++ "Legal Entity" shall mean the union of the acting entity and all+ other entities that control, are controlled by, or are under common+ control with that entity. For the purposes of this definition,+ "control" means (i) the power, direct or indirect, to cause the+ direction or management of such entity, whether by contract or+ otherwise, or (ii) ownership of fifty percent (50%) or more of the+ outstanding shares, or (iii) beneficial ownership of such entity.++ "You" (or "Your") shall mean an individual or Legal Entity+ exercising permissions granted by this License.++ "Source" form shall mean the preferred form for making modifications,+ including but not limited to software source code, documentation+ source, and configuration files.++ "Object" form shall mean any form resulting from mechanical+ transformation or translation of a Source form, including but+ not limited to compiled object code, generated documentation,+ and conversions to other media types.++ "Work" shall mean the work of authorship, whether in Source or+ Object form, made available under the License, as indicated by a+ copyright notice that is included in or attached to the work+ (an example is provided in the Appendix below).++ "Derivative Works" shall mean any work, whether in Source or Object+ form, that is based on (or derived from) the Work and for which the+ editorial revisions, annotations, elaborations, or other modifications+ represent, as a whole, an original work of authorship. For the purposes+ of this License, Derivative Works shall not include works that remain+ separable from, or merely link (or bind by name) to the interfaces of,+ the Work and Derivative Works thereof.++ "Contribution" shall mean any work of authorship, including+ the original version of the Work and any modifications or additions+ to that Work or Derivative Works thereof, that is intentionally+ submitted to Licensor for inclusion in the Work by the copyright owner+ or by an individual or Legal Entity authorized to submit on behalf of+ the copyright owner. For the purposes of this definition, "submitted"+ means any form of electronic, verbal, or written communication sent+ to the Licensor or its representatives, including but not limited to+ communication on electronic mailing lists, source code control systems,+ and issue tracking systems that are managed by, or on behalf of, the+ Licensor for the purpose of discussing and improving the Work, but+ excluding communication that is conspicuously marked or otherwise+ designated in writing by the copyright owner as "Not a Contribution."++ "Contributor" shall mean Licensor and any individual or Legal Entity+ on behalf of whom a Contribution has been received by Licensor and+ subsequently incorporated within the Work.++ 2. Grant of Copyright License. Subject to the terms and conditions of+ this License, each Contributor hereby grants to You a perpetual,+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable+ copyright license to reproduce, prepare Derivative Works of,+ publicly display, publicly perform, sublicense, and distribute the+ Work and such Derivative Works in Source or Object form.++ 3. Grant of Patent License. Subject to the terms and conditions of+ this License, each Contributor hereby grants to You a perpetual,+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable+ (except as stated in this section) patent license to make, have made,+ use, offer to sell, sell, import, and otherwise transfer the Work,+ where such license applies only to those patent claims licensable+ by such Contributor that are necessarily infringed by their+ Contribution(s) alone or by combination of their Contribution(s)+ with the Work to which such Contribution(s) was submitted. If You+ institute patent litigation against any entity (including a+ cross-claim or counterclaim in a lawsuit) alleging that the Work+ or a Contribution incorporated within the Work constitutes direct+ or contributory patent infringement, then any patent licenses+ granted to You under this License for that Work shall terminate+ as of the date such litigation is filed.++ 4. Redistribution. You may reproduce and distribute copies of the+ Work or Derivative Works thereof in any medium, with or without+ modifications, and in Source or Object form, provided that You+ meet the following conditions:++ (a) You must give any other recipients of the Work or+ Derivative Works a copy of this License; and++ (b) You must cause any modified files to carry prominent notices+ stating that You changed the files; and++ (c) You must retain, in the Source form of any Derivative Works+ that You distribute, all copyright, patent, trademark, and+ attribution notices from the Source form of the Work,+ excluding those notices that do not pertain to any part of+ the Derivative Works; and++ (d) If the Work includes a "NOTICE" text file as part of its+ distribution, then any Derivative Works that You distribute must+ include a readable copy of the attribution notices contained+ within such NOTICE file, excluding those notices that do not+ pertain to any part of the Derivative Works, in at least one+ of the following places: within a NOTICE text file distributed+ as part of the Derivative Works; within the Source form or+ documentation, if provided along with the Derivative Works; or,+ within a display generated by the Derivative Works, if and+ wherever such third-party notices normally appear. The contents+ of the NOTICE file are for informational purposes only and+ do not modify the License. You may add Your own attribution+ notices within Derivative Works that You distribute, alongside+ or as an addendum to the NOTICE text from the Work, provided+ that such additional attribution notices cannot be construed+ as modifying the License.++ You may add Your own copyright statement to Your modifications and+ may provide additional or different license terms and conditions+ for use, reproduction, or distribution of Your modifications, or+ for any such Derivative Works as a whole, provided Your use,+ reproduction, and distribution of the Work otherwise complies with+ the conditions stated in this License.++ 5. Submission of Contributions. Unless You explicitly state otherwise,+ any Contribution intentionally submitted for inclusion in the Work+ by You to the Licensor shall be under the terms and conditions of+ this License, without any additional terms or conditions.+ Notwithstanding the above, nothing herein shall supersede or modify+ the terms of any separate license agreement you may have executed+ with Licensor regarding such Contributions.++ 6. Trademarks. This License does not grant permission to use the trade+ names, trademarks, service marks, or product names of the Licensor,+ except as required for reasonable and customary use in describing the+ origin of the Work and reproducing the content of the NOTICE file.++ 7. Disclaimer of Warranty. Unless required by applicable law or+ agreed to in writing, Licensor provides the Work (and each+ Contributor provides its Contributions) on an "AS IS" BASIS,+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or+ implied, including, without limitation, any warranties or conditions+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A+ PARTICULAR PURPOSE. You are solely responsible for determining the+ appropriateness of using or redistributing the Work and assume any+ risks associated with Your exercise of permissions under this License.++ 8. Limitation of Liability. In no event and under no legal theory,+ whether in tort (including negligence), contract, or otherwise,+ unless required by applicable law (such as deliberate and grossly+ negligent acts) or agreed to in writing, shall any Contributor be+ liable to You for damages, including any direct, indirect, special,+ incidental, or consequential damages of any character arising as a+ result of this License or out of the use or inability to use the+ Work (including but not limited to damages for loss of goodwill,+ work stoppage, computer failure or malfunction, or any and all+ other commercial damages or losses), even if such Contributor+ has been advised of the possibility of such damages.++ 9. Accepting Warranty or Additional Liability. While redistributing+ the Work or Derivative Works thereof, You may choose to offer,+ and charge a fee for, acceptance of support, warranty, indemnity,+ or other liability obligations and/or rights consistent with this+ License. However, in accepting such obligations, You may act only+ on Your own behalf and on Your sole responsibility, not on behalf+ of any other Contributor, and only if You agree to indemnify,+ defend, and hold each Contributor harmless for any liability+ incurred by, or claims asserted against, such Contributor by reason+ of your accepting any such warranty or additional liability.++ END OF TERMS AND CONDITIONS++ APPENDIX: How to apply the Apache License to your work.++ To apply the Apache License to your work, attach the following+ boilerplate notice, with the fields enclosed by brackets "[]"+ replaced with your own identifying information. (Don't include+ the brackets!) The text should be enclosed in the appropriate+ comment syntax for the file format. We also recommend that a+ file or class name and description of purpose be included on the+ same "printed page" as the copyright notice for easier+ identification within third-party archives.++ Copyright [yyyy] [name of copyright owner]++ Licensed under the Apache License, Version 2.0 (the "License");+ you may not use this file except in compliance with the License.+ You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++ Unless required by applicable law or agreed to in writing, software+ distributed under the License is distributed on an "AS IS" BASIS,+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ See the License for the specific language governing permissions and+ limitations under the License.
+ README.markdown view
@@ -0,0 +1,34 @@+The Haskell Robot+=================++**Robot** is a library for sending fake keyboard and mouse events using+[Haskell][]. Applications will respond as though the user performed the+actions themselves.++[Haskell]: http://haskell.org/++Only X11 systems are supported right now, but Windows and Mac can be+added later if anyone cares.++Here are some things you can do:++* Test graphical interfaces and websites!++* Play silly pranks on your friends!++* Finally beat those [silly Flash games where you press the same button+ over and over again really fast][blah blah blah]. I don't like those.++[blah blah blah]: http://mixermike622.deviantart.com/art/GO-FAST-287788805+++Quick start+-----------++First, download and install the library:++ git clone https://github.com/lfairy/robot.git+ cd robot+ cabal install++Now try running some of the programs in the `examples` directory.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Test/Robot.hs view
@@ -0,0 +1,89 @@+-- | The main Robot interface.++module Test.Robot+ (+ -- * Running your robot+ Robot() -- hide implementation+ , runRobot+ , runRobotWithConnection++ -- * Key and button constants+ , module Test.Robot.Types++ -- * Doing things+ -- ** Basic operations+ , Pressable(press, release)+ , moveBy+ , moveTo+ -- ** Derived operations+ , hold+ , tap++ -- * Fancy utilities+ , sleep++ ) where+++import Control.Applicative+import Control.Concurrent (threadDelay)+import Control.Monad.IO.Class++import Test.Robot.Internal+import Test.Robot.Types+++-- | Represents things that can be pressed: either a key on a keyboard+-- or a button on a mouse.+class Pressable x where++ -- | Press a key or button.+ press :: x -> Robot ()++ -- | Release a key or button.+ release :: x -> Robot ()++instance Pressable Key where+ press = keyboard True+ release = keyboard False++instance Pressable Button where+ press = button True+ release = button False+++-- | Move the pointer by an offset.+moveBy :: Int -> Int -> Robot ()+moveBy = motion True++-- | Move the pointer to a point on the screen.+moveTo :: Int -> Int -> Robot ()+moveTo = motion False+++-- | @hold x act@ holds down @x@ while executing @act@. It is equivalent+-- to the code:+--+-- @+-- press x+-- act+-- release x+-- @+--+-- For example, you type some text in ALL CAPS using:+--+-- @+-- hold _Shift $ mapM_ tap [_D, _U, _C, _K, _S]+-- @+--+hold :: Pressable x => x -> Robot a -> Robot a+hold = bracketRobot_ <$> press <*> release++-- | Press the argument, then release it.+tap :: Pressable x => x -> Robot ()+tap = (`hold` return ())+++-- | Do nothing for the specified number of seconds.+sleep :: Rational -> Robot ()+sleep = liftIO . threadDelay . round . (* 1000000)
+ Test/Robot/Internal.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- | Miscellaneous low-level functions. You should rarely need to use+-- these directly.++module Test.Robot.Internal+ (+ -- * The Robot monad+ Robot(..)+ , runRobot+ , runRobotWithConnection+ , mkRobot+ , mkRobot'++ -- * Exception safety+ , bracketRobot_++ -- * Synthesizing events+ , keyboard+ , button+ , motion+ , releaseAll++ ) where+++import Control.Applicative+import Control.Exception (bracket_, finally)+import Control.Monad.IO.Class+import Control.Monad.Trans.Reader+import Data.Map (Map)+import qualified Data.Map as M++import Graphics.XHB++import qualified Test.Robot.Internal.XTest as X+import Test.Robot.Types+++-- | The Robot monad: a reader monad over IO.+newtype Robot a = Robot { unRobot :: ReaderT (Connection, Map KEYSYM KEYCODE) IO a }+ deriving (Functor, Applicative, Monad, MonadIO)++-- | Run the robot, connecting to the display automatically.+runRobot :: Robot a -> IO a+runRobot m = do+ Just c <- connect+ runRobotWithConnection m c++-- | Run the robot using an existing connection.+runRobotWithConnection :: Robot a -> Connection -> IO a+runRobotWithConnection (Robot m) c = do+ keymap <- X.getKeysymMap c+ runReaderT m (c, keymap) `finally` X.releaseAll c++mkRobot :: ((Connection, Map KEYSYM KEYCODE) -> IO a) -> Robot a+mkRobot = Robot . ReaderT++mkRobot' :: (Connection -> IO a) -> Robot a+mkRobot' = mkRobot . (. fst)+++bracketRobot_ :: Robot a -> Robot z -> Robot r -> Robot r+bracketRobot_ (Robot before) (Robot after) (Robot middle)+ = mkRobot $ \env -> let run = flip runReaderT env+ in bracket_ (run before) (run after) (run middle)+++keyboard :: Bool -> Key -> Robot ()+keyboard press key = mkRobot $ \(c, keymap) -> do+ case M.lookup (rawKey key) keymap of+ Nothing -> error $ "Unknown keysym: " ++ show (rawKey key)+ Just keycode -> X.keyboard c press keycode++button :: Bool -> Button -> Robot ()+button press butt {- LOL -} = mkRobot' $ \c ->+ X.button c press (rawButton butt)++motion :: Bool -> Int -> Int -> Robot ()+motion press x y = mkRobot' $ \c ->+ X.motion c press (clipIntegral x, clipIntegral y)++-- | @clipIntegral x :: T@ converts the value @x@ to type @T@.+-- If the argument does not fit in @T@, it is clipped, rather than+-- wrapping around as with 'fromIntegral'.+--+-- The range of the result type must be a subset of the argument type.+-- If not, the behavior of this function is undefined.+--+clipIntegral :: (Integral a, Integral b, Ord a, Bounded b) => a -> b+clipIntegral x = result+ where+ result+ | x < fromIntegral (minBound `asTypeOf` result) = minBound+ | x > fromIntegral (maxBound `asTypeOf` result) = maxBound+ | otherwise = fromIntegral x++-- | Release all the keys and buttons, in case some were left held down.+-- This is called automatically before 'runRobot' returns.+releaseAll :: Robot ()+releaseAll = mkRobot' $ \c -> X.releaseAll c
+ Test/Robot/Internal/XTest.hs view
@@ -0,0 +1,115 @@+-- | Low-level wrappers around the XTest protocol.++module Test.Robot.Internal.XTest+ (+ -- * XTest primitives+ keyboard+ , button+ , motion+ , releaseAll++ -- * Utility functions+ , getKeysymMap++ ) where++import Control.Arrow (second)+import Control.Applicative (liftA2)+import Control.Monad (forM_, (>=>))+import Data.List (unfoldr)+import Data.Map (Map)+import qualified Data.Map as M+import Data.Tuple (swap)+import Data.Word (Word16)++import Graphics.XHB+import Graphics.XHB.Gen.Test+++keyboard :: Connection -> Bool -> KEYCODE -> IO ()+keyboard c press keycode =+ let eventType = if press then 2 else 3+ req = MkFakeInput eventType keycode 0 noWindow 0 0 0+ in fakeInput c req+++button :: Connection -> Bool -> BUTTON -> IO ()+button c press butt =+ let eventType = if press then 4 else 5+ req = MkFakeInput eventType butt 0 noWindow 0 0 0+ in fakeInput c req+++motion :: Connection -> Bool -> (Word16, Word16) -> IO ()+motion c relative (x, y) =+ let eventType = 6+ isRelative = if relative then 1 else 0+ req = MkFakeInput eventType isRelative 0 noWindow x y 0+ in fakeInput c req+++noWindow :: WINDOW+noWindow = fromXid xidNone+++-- | Release all the keys and buttons, in case some were left held down.+releaseAll :: Connection -> IO ()+releaseAll c = do+ let (low, high) = keycodeRange c+ forM_ [low..high] $ keyboard c False+ -- There's no limit to the number of buttons, afaik. So hazard a guess.+ forM_ [1..42] $ button c False+++-- | Return a structure mapping each keycode to its keysyms.+getKeyboardMap :: Connection -> IO (Map KEYCODE [KEYSYM])+getKeyboardMap c = do+ let (low, high) = keycodeRange c+ MkGetKeyboardMappingReply+ { keysyms_per_keycode_GetKeyboardMappingReply = blockSize+ , keysyms_GetKeyboardMappingReply = rawKeysyms }+ <- getKeyboardMapping c low (high-low+1) >>= getReply'+ return . M.fromList $ zip [low..high]+ (chunksOf (fromIntegral blockSize) rawKeysyms)+++-- | Like 'getReply', but calls @error@ instead of returning @Left@.+getReply' :: Receipt a -> IO a+getReply' = getReply >=> either (error . show) return+++-- | Split the input list into chunks of size @k@. The last chunk may be+-- shorter than the other chunks, depending on the length of the input.+-- Exammples:+--+-- > chunksOf 2 [1..6] == [[1, 2], [3, 4], [5, 6]]+-- > chunksOf 3 "Hello, world!" == ["Hel", "lo,", " wo", "rld", "!"]+--+chunksOf :: Int -> [a] -> [[a]]+chunksOf k = unfoldr $ \xs -> case xs of+ [] -> Nothing+ _ -> Just (splitAt k xs)+++-- | Return a structure mapping keysyms to their keycode.+getKeysymMap :: Connection -> IO (Map KEYSYM KEYCODE)+getKeysymMap = fmap flipTable . getKeyboardMap+++flipTable :: Map KEYCODE [KEYSYM] -> Map KEYSYM KEYCODE+flipTable+ = M.fromList+ . map swap+ . concatMap expandEntry+ . removeZeroes+ . M.toList+ where+ removeZeroes = map (second (filter (/= 0)))++ expandEntry :: (a, [b]) -> [(a, b)]+ expandEntry (x, ys) = zip (repeat x) ys+++keycodeRange :: Connection -> (KEYCODE, KEYCODE)+keycodeRange+ = liftA2 (,) min_keycode_Setup max_keycode_Setup . connectionSetup
+ Test/Robot/Types.hs view
@@ -0,0 +1,61 @@+-- | Defines the 'Key' and 'Button' types, along with a list of+-- constants of those types.++module Test.Robot.Types+ (+ -- * Types+ Key() -- Hide implementation+ , Button()++ -- ** Creating+ -- $custom+ , customKey+ , customButton++ -- ** Extracting+ , rawKey+ , rawButton++ -- * Constants+ -- ** Buttons+ , leftButton, middleButton, rightButton+ , scrollUp, scrollDown, scrollLeft, scrollRight+ , backButton, forwardButton++ , module Test.Robot.Types.Keys++ ) where++import Graphics.XHB++import Test.Robot.Types.Core+import Test.Robot.Types.Keys++-- $custom+-- These two functions let you define your own keys and buttons.+--+-- 'customKey' requires a magic number (/keysym/) as its argument. On+-- most Unix systems, you can look up common keysyms in the file+-- @\/usr\/include\/X11\/keysymdef.h@.+--+-- For example, the pound sign @£@ is listed as:+--+-- > #define XK_sterling 0x00a3 /* U+00A3 POUND SIGN */+--+-- so to input this key, we use @customKey 0x00a3@.+--+-- Note: this feature only works if the key actually exists on your+-- keyboard. If not, then using the result will raise an exception.++customKey :: KEYSYM -> Key+customKey = Key++customButton :: BUTTON -> Button+customButton = Button++leftButton, middleButton, rightButton :: Button+scrollUp, scrollDown, scrollLeft, scrollRight :: Button+backButton, forwardButton :: Button+[leftButton, middleButton, rightButton,+ scrollUp, scrollDown, scrollLeft, scrollRight,+ backButton, forwardButton] = map Button [1..9]
+ Test/Robot/Types/Core.hs view
@@ -0,0 +1,18 @@+module Test.Robot.Types.Core where++import Graphics.XHB (BUTTON, KEYSYM)++-- | A key on the keyboard.+newtype Key = Key+ { -- | Retrieve the internal identifier for this key.+ rawKey :: KEYSYM }+ deriving (Eq, Ord, Read, Show)++-- | A mouse button or scroll wheel.+--+-- Note that the scroll wheel is treated as two separate buttons: one+-- scrolling up, and one scrolling down.+newtype Button = Button+ { -- | Retrieve the internal identifier for this button.+ rawButton :: BUTTON }+ deriving (Eq, Ord, Read, Show)
+ Test/Robot/Types/Keys.hs view
@@ -0,0 +1,569 @@+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}++-- | This module is automatically generated from the file @keys.txt@,+-- and should not be edited directly. If you want to add a keysym, refer+-- to that file instead.++module Test.Robot.Types.Keys+ (+ -- ** Control sequences+ _Backspace+ , _Tab+ , _LineFeed+ , _Clear+ , _Return+ , _Pause+ , _ScrollLock+ , _SysReq+ , _Escape+ , _Delete++ -- ** Cursor control and motion+ , _Home+ , _Left+ , _Up+ , _Right+ , _Down+ , _Prior+ , _PageUp+ , _Next+ , _PageDown+ , _End+ , _Begin++ -- ** Miscellaneous functions+ , _Select+ , _Print+ , _Execute+ , _Insert+ , _Undo+ , _Redo+ , _Menu+ , _Find+ , _Cancel+ , _Help+ , _Break+ , _ModeSwitch+ , _ScriptSwitch+ , _NumLock++ -- ** Keypad functions+ , _KP_Space+ , _KP_Tab+ , _KP_Enter+ , _KP_F1+ , _KP_F2+ , _KP_F3+ , _KP_F4+ , _KP_Home+ , _KP_Left+ , _KP_Up+ , _KP_Right+ , _KP_Down+ , _KP_Prior+ , _KP_PageUp+ , _KP_Next+ , _KP_PageDown+ , _KP_End+ , _KP_Begin+ , _KP_Insert+ , _KP_Delete+ , _KP_Multiply+ , _KP_Add+ , _KP_Separator+ , _KP_Subtract+ , _KP_Decimal+ , _KP_Divide+ , _KP_Equal++ -- ** Keypad numbers+ , _KP_0+ , _KP_1+ , _KP_2+ , _KP_3+ , _KP_4+ , _KP_5+ , _KP_6+ , _KP_7+ , _KP_8+ , _KP_9++ -- ** Function keys+ , _F1+ , _F2+ , _F3+ , _F4+ , _F5+ , _F6+ , _F7+ , _F8+ , _F9+ , _F10+ , _F11+ , _F12+ , _F13+ , _F14+ , _F15+ , _F16+ , _F17+ , _F18+ , _F19++ -- ** Modifiers+ , _Shift+ , _Shift_L+ , _Shift_R+ , _Ctrl+ , _Control+ , _Control_L+ , _Control_R+ , _CapsLock+ , _ShiftLock++ -- ** More modifiers+ , _Meta+ , _Meta_L+ , _Meta_R+ , _Alt+ , _Alt_L+ , _Alt_R+ , _Super+ , _Super_L+ , _Super_R+ , _Hyper+ , _Hyper_L+ , _Hyper_R++ -- ** Letters, numbers and symbols+ , _Space+ , _Exclamation+ , _Quotation+ , _NumberSign+ , _Dollar+ , _Percent+ , _Ampersand+ , _Apostrophe+ , _ParenLeft+ , _ParenRight+ , _Asterisk+ , _Plus+ , _Comma+ , _Minus+ , _Period+ , _Slash+ , _0+ , _1+ , _2+ , _3+ , _4+ , _5+ , _6+ , _7+ , _8+ , _9+ , _Colon+ , _Semicolon+ , _Less+ , _Equal+ , _Greater+ , _Question+ , _At+ , _A+ , _B+ , _C+ , _D+ , _E+ , _F+ , _G+ , _H+ , _I+ , _J+ , _K+ , _L+ , _M+ , _N+ , _O+ , _P+ , _Q+ , _R+ , _S+ , _T+ , _U+ , _V+ , _W+ , _X+ , _Y+ , _Z+ , _BracketLeft+ , _Backslash+ , _BracketRight+ , _Circumflex+ , _Underscore+ , _Grave+ , _BraceLeft+ , _Bar+ , _BraceRight+ , _Tilde++ ) where++import Test.Robot.Types.Core++_Backspace :: Key+_Backspace = Key 0xff08+_Tab :: Key+_Tab = Key 0xff09+_LineFeed :: Key+_LineFeed = Key 0xff0a+_Clear :: Key+_Clear = Key 0xff0b+_Return :: Key+_Return = Key 0xff0d+_Pause :: Key+_Pause = Key 0xff13+_ScrollLock :: Key+_ScrollLock = Key 0xff14+_SysReq :: Key+_SysReq = Key 0xff15+_Escape :: Key+_Escape = Key 0xff1b+_Delete :: Key+_Delete = Key 0xffff++_Home :: Key+_Home = Key 0xff50+_Left :: Key+_Left = Key 0xff51+_Up :: Key+_Up = Key 0xff52+_Right :: Key+_Right = Key 0xff53+_Down :: Key+_Down = Key 0xff54+_Prior, _PageUp :: Key+_Prior = Key 0xff55+_PageUp = Key 0xff55+_Next, _PageDown :: Key+_Next = Key 0xff56+_PageDown = Key 0xff56+_End :: Key+_End = Key 0xff57+_Begin :: Key+_Begin = Key 0xff58++_Select :: Key+_Select = Key 0xff60+_Print :: Key+_Print = Key 0xff61+_Execute :: Key+_Execute = Key 0xff62+_Insert :: Key+_Insert = Key 0xff63+_Undo :: Key+_Undo = Key 0xff65+_Redo :: Key+_Redo = Key 0xff66+_Menu :: Key+_Menu = Key 0xff67+_Find :: Key+_Find = Key 0xff68+_Cancel :: Key+_Cancel = Key 0xff69+_Help :: Key+_Help = Key 0xff6a+_Break :: Key+_Break = Key 0xff6b+_ModeSwitch, _ScriptSwitch :: Key+_ModeSwitch = Key 0xff7e+_ScriptSwitch = Key 0xff7e+_NumLock :: Key+_NumLock = Key 0xff7f++_KP_Space :: Key+_KP_Space = Key 0xff80+_KP_Tab :: Key+_KP_Tab = Key 0xff89+_KP_Enter :: Key+_KP_Enter = Key 0xff8d+_KP_F1 :: Key+_KP_F1 = Key 0xff91+_KP_F2 :: Key+_KP_F2 = Key 0xff92+_KP_F3 :: Key+_KP_F3 = Key 0xff93+_KP_F4 :: Key+_KP_F4 = Key 0xff94+_KP_Home :: Key+_KP_Home = Key 0xff95+_KP_Left :: Key+_KP_Left = Key 0xff96+_KP_Up :: Key+_KP_Up = Key 0xff97+_KP_Right :: Key+_KP_Right = Key 0xff98+_KP_Down :: Key+_KP_Down = Key 0xff99+_KP_Prior, _KP_PageUp :: Key+_KP_Prior = Key 0xff9a+_KP_PageUp = Key 0xff9a+_KP_Next, _KP_PageDown :: Key+_KP_Next = Key 0xff9b+_KP_PageDown = Key 0xff9b+_KP_End :: Key+_KP_End = Key 0xff9c+_KP_Begin :: Key+_KP_Begin = Key 0xff9d+_KP_Insert :: Key+_KP_Insert = Key 0xff9e+_KP_Delete :: Key+_KP_Delete = Key 0xff9f+_KP_Multiply :: Key+_KP_Multiply = Key 0xffaa+_KP_Add :: Key+_KP_Add = Key 0xffab+_KP_Separator :: Key+_KP_Separator = Key 0xffac+_KP_Subtract :: Key+_KP_Subtract = Key 0xffad+_KP_Decimal :: Key+_KP_Decimal = Key 0xffae+_KP_Divide :: Key+_KP_Divide = Key 0xffaf+_KP_Equal :: Key+_KP_Equal = Key 0xffbd++_KP_0 :: Key+_KP_0 = Key 0xffb0+_KP_1 :: Key+_KP_1 = Key 0xffb1+_KP_2 :: Key+_KP_2 = Key 0xffb2+_KP_3 :: Key+_KP_3 = Key 0xffb3+_KP_4 :: Key+_KP_4 = Key 0xffb4+_KP_5 :: Key+_KP_5 = Key 0xffb5+_KP_6 :: Key+_KP_6 = Key 0xffb6+_KP_7 :: Key+_KP_7 = Key 0xffb7+_KP_8 :: Key+_KP_8 = Key 0xffb8+_KP_9 :: Key+_KP_9 = Key 0xffb9++_F1 :: Key+_F1 = Key 0xffbe+_F2 :: Key+_F2 = Key 0xffbf+_F3 :: Key+_F3 = Key 0xffc0+_F4 :: Key+_F4 = Key 0xffc1+_F5 :: Key+_F5 = Key 0xffc2+_F6 :: Key+_F6 = Key 0xffc3+_F7 :: Key+_F7 = Key 0xffc4+_F8 :: Key+_F8 = Key 0xffc5+_F9 :: Key+_F9 = Key 0xffc6+_F10 :: Key+_F10 = Key 0xffc7+_F11 :: Key+_F11 = Key 0xffc8+_F12 :: Key+_F12 = Key 0xffc9+_F13 :: Key+_F13 = Key 0xffca+_F14 :: Key+_F14 = Key 0xffcb+_F15 :: Key+_F15 = Key 0xffcc+_F16 :: Key+_F16 = Key 0xffcd+_F17 :: Key+_F17 = Key 0xffce+_F18 :: Key+_F18 = Key 0xffcf+_F19 :: Key+_F19 = Key 0xffd0++_Shift, _Shift_L :: Key+_Shift = Key 0xffe1+_Shift_L = Key 0xffe1+_Shift_R :: Key+_Shift_R = Key 0xffe2+_Ctrl, _Control, _Control_L :: Key+_Ctrl = Key 0xffe3+_Control = Key 0xffe3+_Control_L = Key 0xffe3+_Control_R :: Key+_Control_R = Key 0xffe4+_CapsLock :: Key+_CapsLock = Key 0xffe5+_ShiftLock :: Key+_ShiftLock = Key 0xffe6++_Meta, _Meta_L :: Key+_Meta = Key 0xffe7+_Meta_L = Key 0xffe7+_Meta_R :: Key+_Meta_R = Key 0xffe8+_Alt, _Alt_L :: Key+_Alt = Key 0xffe9+_Alt_L = Key 0xffe9+_Alt_R :: Key+_Alt_R = Key 0xffea+_Super, _Super_L :: Key+_Super = Key 0xffeb+_Super_L = Key 0xffeb+_Super_R :: Key+_Super_R = Key 0xffec+_Hyper, _Hyper_L :: Key+_Hyper = Key 0xffed+_Hyper_L = Key 0xffed+_Hyper_R :: Key+_Hyper_R = Key 0xffee++_Space :: Key+_Space = Key 0x0020+_Exclamation :: Key+_Exclamation = Key 0x0021+_Quotation :: Key+_Quotation = Key 0x0022+_NumberSign :: Key+_NumberSign = Key 0x0023+_Dollar :: Key+_Dollar = Key 0x0024+_Percent :: Key+_Percent = Key 0x0025+_Ampersand :: Key+_Ampersand = Key 0x0026+_Apostrophe :: Key+_Apostrophe = Key 0x0027+_ParenLeft :: Key+_ParenLeft = Key 0x0028+_ParenRight :: Key+_ParenRight = Key 0x0029+_Asterisk :: Key+_Asterisk = Key 0x002a+_Plus :: Key+_Plus = Key 0x002b+_Comma :: Key+_Comma = Key 0x002c+_Minus :: Key+_Minus = Key 0x002d+_Period :: Key+_Period = Key 0x002e+_Slash :: Key+_Slash = Key 0x002f+_0 :: Key+_0 = Key 0x0030+_1 :: Key+_1 = Key 0x0031+_2 :: Key+_2 = Key 0x0032+_3 :: Key+_3 = Key 0x0033+_4 :: Key+_4 = Key 0x0034+_5 :: Key+_5 = Key 0x0035+_6 :: Key+_6 = Key 0x0036+_7 :: Key+_7 = Key 0x0037+_8 :: Key+_8 = Key 0x0038+_9 :: Key+_9 = Key 0x0039+_Colon :: Key+_Colon = Key 0x003a+_Semicolon :: Key+_Semicolon = Key 0x003b+_Less :: Key+_Less = Key 0x003c+_Equal :: Key+_Equal = Key 0x003d+_Greater :: Key+_Greater = Key 0x003e+_Question :: Key+_Question = Key 0x003f+_At :: Key+_At = Key 0x0040+_A :: Key+_A = Key 0x0041+_B :: Key+_B = Key 0x0042+_C :: Key+_C = Key 0x0043+_D :: Key+_D = Key 0x0044+_E :: Key+_E = Key 0x0045+_F :: Key+_F = Key 0x0046+_G :: Key+_G = Key 0x0047+_H :: Key+_H = Key 0x0048+_I :: Key+_I = Key 0x0049+_J :: Key+_J = Key 0x004a+_K :: Key+_K = Key 0x004b+_L :: Key+_L = Key 0x004c+_M :: Key+_M = Key 0x004d+_N :: Key+_N = Key 0x004e+_O :: Key+_O = Key 0x004f+_P :: Key+_P = Key 0x0050+_Q :: Key+_Q = Key 0x0051+_R :: Key+_R = Key 0x0052+_S :: Key+_S = Key 0x0053+_T :: Key+_T = Key 0x0054+_U :: Key+_U = Key 0x0055+_V :: Key+_V = Key 0x0056+_W :: Key+_W = Key 0x0057+_X :: Key+_X = Key 0x0058+_Y :: Key+_Y = Key 0x0059+_Z :: Key+_Z = Key 0x005a+_BracketLeft :: Key+_BracketLeft = Key 0x005b+_Backslash :: Key+_Backslash = Key 0x005c+_BracketRight :: Key+_BracketRight = Key 0x005d+_Circumflex :: Key+_Circumflex = Key 0x005e+_Underscore :: Key+_Underscore = Key 0x005f+_Grave :: Key+_Grave = Key 0x0060+_BraceLeft :: Key+_BraceLeft = Key 0x007b+_Bar :: Key+_Bar = Key 0x007c+_BraceRight :: Key+_BraceRight = Key 0x007d+_Tilde :: Key+_Tilde = Key 0x007e
+ examples/RightClick.hs view
@@ -0,0 +1,10 @@+-- | Open the right-click menu, then use the keyboard to select the+-- first entry.++import Test.Robot++main = runRobot $ mapM_ (>> sleep 0.25)+ [tap rightButton, tap _Down, tap _Return]+ where+ _Down = customKey 0xff54+ _Return = customKey 0xff0d
+ generate_keys.py view
@@ -0,0 +1,66 @@+#!/usr/bin/env python3++from collections import defaultdict+from functools import partial++HEADER = '''+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}++-- | This module is automatically generated from the file @keys.txt@,+-- and should not be edited directly. If you want to add a keysym, refer+-- to that file instead.++module Test.Robot.Types.Keys+'''.strip()++def read_blocks(raw):+ for block in raw.split('\n\n'):+ lines = block.strip().split('\n')+ title = lines[0]++ data = defaultdict(list)+ for name, code in map(str.split, lines[1:]):+ data[code].append(name)++ yield title, data+++def dump_header(blocks, print=print):+ print(HEADER)++ print_i = partial(print, ' ')+ print_i('(', end='')++ first = True+ for title, data in blocks:+ print()+ print_i(' -- **', title)+ for code, names in sorted(data.items()):+ for name in names:+ print_i(' ' if first else ',', '_'+name)+ first = False++ print()+ print_i(') where')+ print()+ print('import Test.Robot.Types.Core')+++def dump_definitions(blocks, print=print):+ for title, data in blocks:+ print()+ for code, names in sorted(data.items()):+ names = ['_' + name for name in names]+ # Gather all the aliases into the same signature, so Haddock+ # displays them on one line+ print(', '.join(names), ':: Key')+ for name in names:+ print(name.ljust(30), '= Key', code)+++if __name__ == '__main__':+ raw = open('keys.txt').read()+ with open('Test/Robot/Types/Keys.hs', 'w', newline='\n') as out_f:+ print = partial(print, file=out_f)+ dump_header(read_blocks(raw), print)+ dump_definitions(read_blocks(raw), print)
+ keys.txt view
@@ -0,0 +1,198 @@+Control sequences+Backspace 0xff08+Tab 0xff09+LineFeed 0xff0a+Clear 0xff0b+Return 0xff0d+Pause 0xff13+ScrollLock 0xff14+SysReq 0xff15+Escape 0xff1b+Delete 0xffff++Cursor control and motion+Home 0xff50+Left 0xff51+Up 0xff52+Right 0xff53+Down 0xff54+Prior 0xff55+PageUp 0xff55+Next 0xff56+PageDown 0xff56+End 0xff57+Begin 0xff58++Miscellaneous functions+Select 0xff60+Print 0xff61+Execute 0xff62+Insert 0xff63+Undo 0xff65+Redo 0xff66+Menu 0xff67+Find 0xff68+Cancel 0xff69+Help 0xff6a+Break 0xff6b+ModeSwitch 0xff7e+ScriptSwitch 0xff7e+NumLock 0xff7f++Keypad functions+KP_Space 0xff80+KP_Tab 0xff89+KP_Enter 0xff8d+KP_F1 0xff91+KP_F2 0xff92+KP_F3 0xff93+KP_F4 0xff94+KP_Home 0xff95+KP_Left 0xff96+KP_Up 0xff97+KP_Right 0xff98+KP_Down 0xff99+KP_Prior 0xff9a+KP_PageUp 0xff9a+KP_Next 0xff9b+KP_PageDown 0xff9b+KP_End 0xff9c+KP_Begin 0xff9d+KP_Insert 0xff9e+KP_Delete 0xff9f+KP_Equal 0xffbd+KP_Multiply 0xffaa+KP_Add 0xffab+KP_Separator 0xffac+KP_Subtract 0xffad+KP_Decimal 0xffae+KP_Divide 0xffaf++Keypad numbers+KP_0 0xffb0+KP_1 0xffb1+KP_2 0xffb2+KP_3 0xffb3+KP_4 0xffb4+KP_5 0xffb5+KP_6 0xffb6+KP_7 0xffb7+KP_8 0xffb8+KP_9 0xffb9++Function keys+F1 0xffbe+F2 0xffbf+F3 0xffc0+F4 0xffc1+F5 0xffc2+F6 0xffc3+F7 0xffc4+F8 0xffc5+F9 0xffc6+F10 0xffc7+F11 0xffc8+F12 0xffc9+F13 0xffca+F14 0xffcb+F15 0xffcc+F16 0xffcd+F17 0xffce+F18 0xffcf+F19 0xffd0++Modifiers+Shift 0xffe1+Shift_L 0xffe1+Shift_R 0xffe2+Ctrl 0xffe3+Control 0xffe3+Control_L 0xffe3+Control_R 0xffe4+CapsLock 0xffe5+ShiftLock 0xffe6++More modifiers+Meta 0xffe7+Meta_L 0xffe7+Meta_R 0xffe8+Alt 0xffe9+Alt_L 0xffe9+Alt_R 0xffea+Super 0xffeb+Super_L 0xffeb+Super_R 0xffec+Hyper 0xffed+Hyper_L 0xffed+Hyper_R 0xffee++Letters, numbers and symbols+Space 0x0020+Exclamation 0x0021+Quotation 0x0022+NumberSign 0x0023+Dollar 0x0024+Percent 0x0025+Ampersand 0x0026+Apostrophe 0x0027+ParenLeft 0x0028+ParenRight 0x0029+Asterisk 0x002a+Plus 0x002b+Comma 0x002c+Minus 0x002d+Period 0x002e+Slash 0x002f+0 0x0030+1 0x0031+2 0x0032+3 0x0033+4 0x0034+5 0x0035+6 0x0036+7 0x0037+8 0x0038+9 0x0039+Colon 0x003a+Semicolon 0x003b+Less 0x003c+Equal 0x003d+Greater 0x003e+Question 0x003f+At 0x0040+A 0x0041+B 0x0042+C 0x0043+D 0x0044+E 0x0045+F 0x0046+G 0x0047+H 0x0048+I 0x0049+J 0x004a+K 0x004b+L 0x004c+M 0x004d+N 0x004e+O 0x004f+P 0x0050+Q 0x0051+R 0x0052+S 0x0053+T 0x0054+U 0x0055+V 0x0056+W 0x0057+X 0x0058+Y 0x0059+Z 0x005a+BracketLeft 0x005b+Backslash 0x005c+BracketRight 0x005d+Circumflex 0x005e+Underscore 0x005f+Grave 0x0060+BraceLeft 0x007b+Bar 0x007c+BraceRight 0x007d+Tilde 0x007e
+ robot.cabal view
@@ -0,0 +1,39 @@+Name: robot+Version: 1.0+Synopsis: Simulate keyboard and mouse events+Homepage: https://github.com/lfairy/robot+License: OtherLicense+License-File: LICENSE+Author: Chris Wong+Maintainer: lambda.fairy@gmail.com+Category: Graphics+Build-Type: Simple+Extra-Source-Files: README.markdown, examples/*.hs, generate_keys.py, keys.txt+Cabal-Version: >= 1.6++Description:+ Generate native keyboard and mouse events. Using this library,+ applications will respond as if the user performed the action+ themselves.++Library+ Exposed-Modules:+ Test.Robot+ Test.Robot.Internal+ Test.Robot.Internal.XTest+ Test.Robot.Types++ Other-Modules:+ Test.Robot.Types.Core+ Test.Robot.Types.Keys++ Build-Depends: base == 4.*+ , containers+ , transformers+ , xhb++ GHC-Options: -Wall++Source-Repository head+ Type: git+ Location: https://github.com/lfairy/robot.git