text: # -*- coding: utf-8 -*-
  """
  Check that SendInput can work the way we want it to
  
  The tips and tricks at http://www.pinvoke.net/default.aspx/user32.sendinput
  is useful!
  
  """
  from __future__ import absolute_import, division, print_function, unicode_literals
  import time
  import ctypes
  
  __all__ = ['KeySequenceError', 'SendKeys']
  
  # pylint: disable-msg=R0903
  
  DEBUG = 0
  
  try:
      MapVirtualKey = ctypes.windll.user32.MapVirtualKeyW
      SendInput = ctypes.windll.user32.SendInput
      VkKeyScan = ctypes.windll.user32.VkKeyScanW
      VkKeyScan.restype = ctypes.c_short
      VkKeyScan.argtypes = [ctypes.c_wchar]
  except AttributeError:
  
      def passthrough(*args, **kwargs):
          return args
  
      MapVirtualKey = passthrough
      SendInput = passthrough
      VkKeyScan = passthrough
  
  DWORD = ctypes.c_ulong
  LONG = ctypes.c_long
  WORD = ctypes.c_ushort
  
  
  # C:/PROGRA~1/MICROS~4/VC98/Include/winuser.h 4283
  class MOUSEINPUT(ctypes.Structure):
      'Needed for complete definition of INPUT structure - not used'
      _pack_ = 2
      _fields_ = [
          # C:/PROGRA~1/MICROS~4/VC98/Include/winuser.h 4283
          ('dx', LONG),
          ('dy', LONG),
          ('mouseData', DWORD),
          ('dwFlags', DWORD),
          ('time', DWORD),
          ('dwExtraInfo', DWORD),
      ]
  
  
  assert ctypes.sizeof(MOUSEINPUT) in [24, 48], ctypes.sizeof(MOUSEINPUT)
  assert ctypes.alignment(MOUSEINPUT) == 2, ctypes.alignment(MOUSEINPUT)
  
  
  # C:/PROGRA~1/MICROS~4/VC98/Include/winuser.h 4292
  class KEYBDINPUT(ctypes.Structure):
      'A particular keyboard event'
      _pack_ = 2
      _fields_ = [
          # C:/PROGRA~1/MICROS~4/VC98/Include/winuser.h 4292
          ('wVk', WORD),
          ('wScan', WORD),
          ('dwFlags', DWORD),
          ('time', DWORD),
          ('dwExtraInfo', DWORD),
      ]
  
  
  assert ctypes.sizeof(KEYBDINPUT) in [16, 28], ctypes.sizeof(KEYBDINPUT)
  assert ctypes.alignment(KEYBDINPUT) == 2, ctypes.alignment(KEYBDINPUT)
  
  
  class HARDWAREINPUT(ctypes.Structure):
      'Needed for complete definition of INPUT structure - not used'
      _pack_ = 2
      _fields_ = [
          # C:/PROGRA~1/MICROS~4/VC98/Include/winuser.h 4300
          ('uMsg', DWORD),
          ('wParamL', WORD),
          ('wParamH', WORD),
      ]
  
  
  assert ctypes.sizeof(HARDWAREINPUT) in [8, 12], ctypes.sizeof(HARDWAREINPUT)
  assert ctypes.alignment(HARDWAREINPUT) == 2, ctypes.alignment(HARDWAREINPUT)
  
  
  # C:/PROGRA~1/MICROS~4/VC98/Include/winuser.h 4314
  class UNION_INPUT_STRUCTS(ctypes.Union):
      'The C Union type representing a single Event of any type'
      _fields_ = [
          # C:/PROGRA~1/MICROS~4/VC98/Include/winuser.h 4314
          ('mi', MOUSEINPUT),
          ('ki', KEYBDINPUT),
          ('hi', HARDWAREINPUT),
      ]
  
  
  assert ctypes.sizeof(UNION_INPUT_STRUCTS) in [24, 48], ctypes.sizeof(UNION_INPUT_STRUCTS)
  assert ctypes.alignment(UNION_INPUT_STRUCTS) == 2, ctypes.alignment(UNION_INPUT_STRUCTS)
  
  
  # C:/PROGRA~1/MICROS~4/VC98/Include/winuser.h 4310
  class INPUT(ctypes.Structure):
      'See: http://msdn.microsoft.com/en-us/library/ms646270%28VS.85%29.aspx'
      _pack_ = 2
      _fields_ = [
          # C:/PROGRA~1/MICROS~4/VC98/Include/winuser.h 4310
          ('type', DWORD),
          # Unnamed field renamed to '_'
          ('_', UNION_INPUT_STRUCTS),
      ]
  
  
  assert ctypes.sizeof(INPUT) in [28, 56], ctypes.sizeof(INPUT)
  assert ctypes.alignment(INPUT) == 2, ctypes.alignment(INPUT)
  
  
  INPUT_KEYBOARD = 1
  KEYEVENTF_EXTENDEDKEY = 1
  KEYEVENTF_KEYUP = 2
  KEYEVENTF_UNICODE = 4
  KEYEVENTF_SCANCODE = 8
  VK_SHIFT = 16
  VK_CONTROL = 17
  VK_MENU = 18
  
  # 'codes' recognized as {CODE( repeat)?}
  CODES = {
      'BACK': 8,
      'BACKSPACE': 8,
      'BKSP': 8,
      'BREAK': 3,
      'BS': 8,
      'CAP': 20,
      'CAPSLOCK': 20,
      'DEL': 46,
      'DELETE': 46,
      'DOWN': 40,
      'END': 35,
      'ENTER': 13,
      'ESC': 27,
      'F1': 112,
      'F2': 113,
      'F3': 114,
      'F4': 115,
      'F5': 116,
      'F6': 117,
      'F7': 118,
      'F8': 119,
      'F9': 120,
      'F10': 121,
      'F11': 122,
      'F12': 123,
      'F13': 124,
      'F14': 125,
      'F15': 126,
      'F16': 127,
      'F17': 128,
      'F18': 129,
      'F19': 130,
      'F20': 131,
      'F21': 132,
      'F22': 133,
      'F23': 134,
      'F24': 135,
      'HELP': 47,
      'HOME': 36,
      'INS': 45,
      'INSERT': 45,
      'LEFT': 37,
      'LWIN': 91,
      'NUMLOCK': 144,
      'PGDN': 34,
      'PGUP': 33,
      'PRTSC': 44,
      'RIGHT': 39,
      'RMENU': 165,
      'RWIN': 92,
      'SCROLLLOCK': 145,
      'SPACE': 32,
      'TAB': 9,
      'UP': 38,
      'VK_ACCEPT': 30,
      'VK_ADD': 107,
      'VK_APPS': 93,
      'VK_ATTN': 246,
      'VK_BACK': 8,
      'VK_CANCEL': 3,
      'VK_CAPITAL': 20,
      'VK_CLEAR': 12,
      'VK_CONTROL': 17,
      'VK_CONVERT': 28,
      'VK_CRSEL': 247,
      'VK_DECIMAL': 110,
      'VK_DELETE': 46,
      'VK_DIVIDE': 111,
      'VK_DOWN': 40,
      'VK_END': 35,
      'VK_EREOF': 249,
      'VK_ESCAPE': 27,
      'VK_EXECUTE': 43,
      'VK_EXSEL': 248,
      'VK_F1': 112,
      'VK_F2': 113,
      'VK_F3': 114,
      'VK_F4': 115,
      'VK_F5': 116,
      'VK_F6': 117,
      'VK_F7': 118,
      'VK_F8': 119,
      'VK_F9': 120,
      'VK_F10': 121,
      'VK_F11': 122,
      'VK_F12': 123,
      'VK_F13': 124,
      'VK_F14': 125,
      'VK_F15': 126,
      'VK_F16': 127,
      'VK_F17': 128,
      'VK_F18': 129,
      'VK_F19': 130,
      'VK_F20': 131,
      'VK_F21': 132,
      'VK_F22': 133,
      'VK_F23': 134,
      'VK_F24': 135,
      'VK_FINAL': 24,
      'VK_HANGEUL': 21,
      'VK_HANGUL': 21,
      'VK_HANJA': 25,
      'VK_HELP': 47,
      'VK_HOME': 36,
      'VK_INSERT': 45,
      'VK_JUNJA': 23,
      'VK_KANA': 21,
      'VK_KANJI': 25,
      'VK_LBUTTON': 1,
      'VK_LCONTROL': 162,
      'VK_LEFT': 37,
      'VK_LMENU': 164,
      'VK_LSHIFT': 160,
      'VK_LWIN': 91,
      'VK_MBUTTON': 4,
      'VK_MENU': 18,
      'VK_MODECHANGE': 31,
      'VK_MULTIPLY': 106,
      'VK_NEXT': 34,
      'VK_NONAME': 252,
      'VK_NONCONVERT': 29,
      'VK_NUMLOCK': 144,
      'VK_NUMPAD0': 96,
      'VK_NUMPAD1': 97,
      'VK_NUMPAD2': 98,
      'VK_NUMPAD3': 99,
      'VK_NUMPAD4': 100,
      'VK_NUMPAD5': 101,
      'VK_NUMPAD6': 102,
      'VK_NUMPAD7': 103,
      'VK_NUMPAD8': 104,
      'VK_NUMPAD9': 105,
      'VK_OEM_CLEAR': 254,
      'VK_PA1': 253,
      'VK_PAUSE': 19,
      'VK_PLAY': 250,
      'VK_PRINT': 42,
      'VK_PRIOR': 33,
      'VK_PROCESSKEY': 229,
      'VK_RBUTTON': 2,
      'VK_RCONTROL': 163,
      'VK_RETURN': 13,
      'VK_RIGHT': 39,
      'VK_RMENU': 165,
      'VK_RSHIFT': 161,
      'VK_RWIN': 92,
      'VK_SCROLL': 145,
      'VK_SELECT': 41,
      'VK_SEPARATOR': 108,
      'VK_SHIFT': 16,
      'VK_SNAPSHOT': 44,
      'VK_SPACE': 32,
      'VK_SUBTRACT': 109,
      'VK_TAB': 9,
      'VK_UP': 38,
      'ZOOM': 251,
  }
  # reverse the CODES dict to make it easy to look up a particular code name
  CODE_NAMES = dict((entry[1], entry[0]) for entry in CODES.items())
  
  # modifier keys
  MODIFIERS = {
      '+': VK_SHIFT,
      '^': VK_CONTROL,
      '%': VK_MENU,
  }
  
  
  class KeySequenceError(Exception):
      """Exception raised when a key sequence string has a syntax error"""
  
      def __str__(self):
          return ' '.join(self.args)
  
  
  class KeyAction(object):
      """Class that represents a single 'keyboard' action
  
      It represents either a PAUSE action (not reallly keyboard) or a keyboard
      action (press or release or both) of a particular key.
      """
  
      def __init__(self, key, down=True, up=True):
          self.key = key
          if isinstance(self.key, basestring):  # NOQA
              self.key = unicode(key)  # NOQA
          self.down = down
          self.up = up
  
      def _get_key_info(self):
          """Return virtual_key, scan_code, and flags for the action
  
          This is one of the methods that will be overridden by sub classes"""
          return 0, ord(self.key), KEYEVENTF_UNICODE
  
      def GetInput(self):
          'Build the INPUT structure for the action'
          actions = 1
          # if both up and down
          if self.up and self.down:
              actions = 2
  
          inputs = (INPUT * actions)()
  
          vk, scan, flags = self._get_key_info()
  
          for inp in inputs:
              inp.type = INPUT_KEYBOARD
  
              inp._.ki.wVk = vk
              inp._.ki.wScan = scan
              inp._.ki.dwFlags |= flags
  
          # if we are releasing - then let it up
          if self.up:
              inputs[-1]._.ki.dwFlags |= KEYEVENTF_KEYUP
  
          return inputs
  
      def Run(self):
          'Execute the action'
          inputs = self.GetInput()
          return SendInput(len(inputs), ctypes.byref(inputs), ctypes.sizeof(INPUT))
  
      def _get_down_up_string(self):
          """Return a string that will show whether the string is up or down
  
          return 'down' if the key is a press only
          return 'up' if the key is up only
          return '' if the key is up & down (as default)
          """
          down_up = ''
          if not (self.down and self.up):
              if self.down:
                  down_up = 'down'
              elif self.up:
                  down_up = 'up'
          return down_up
  
      def key_description(self):
          'Return a description of the key'
          vk, scan, flags = self._get_key_info()
          desc = ''
          if vk:
              if vk in CODE_NAMES:
                  desc = CODE_NAMES[vk]
              else:
                  desc = 'VK %d' % vk
          else:
              desc = '%s' % self.key
  
          return desc
  
      def __str__(self):
          parts = []
          parts.append(self.key_description())
          up_down = self._get_down_up_string()
          if up_down:
              parts.append(up_down)
  
          return '<%s>' % (' '.join(parts))
  
      __repr__ = __str__
  
  
  class VirtualKeyAction(KeyAction):
      """Represents a virtual key action e.g. F9 DOWN, etc
  
      Overrides necessary methods of KeyAction"""
  
      def _get_key_info(self):
          'Virtual keys have extended flag set'
  
          # copied more or less verbatim from
          # http://www.pinvoke.net/default.aspx/user32.sendinput
          if (self.key >= 33 and self.key <= 46) or (self.key >= 91 and self.key <= 93):
              flags = KEYEVENTF_EXTENDEDKEY
          else:
              flags = 0
          # This works for %{F4} - ALT + F4
          # return self.key, 0, 0
  
          # this works for Tic Tac Toe i.e. +{RIGHT} SHIFT + RIGHT
          return self.key, MapVirtualKey(self.key, 0), flags
  
  
  class EscapedKeyAction(KeyAction):
      """Represents an escaped key action e.g. F9 DOWN, etc
  
      Overrides necessary methods of KeyAction"""
  
      def _get_key_info(self):
          """EscapedKeyAction doesn't send it as Unicode and the vk and
          scan code are generated differently"""
          vkey_scan = LoByte(VkKeyScan(self.key))
  
          return (vkey_scan, MapVirtualKey(vkey_scan, 0), 0)
  
      def key_description(self):
          'Return a description of the key'
  
          return 'KEsc %s' % self.key
  
  
  class PauseAction(KeyAction):
      'Represents a pause action'
  
      def __init__(self, how_long):
          self.how_long = how_long
  
      def Run(self):
          'Pause for the lenght of time specified'
          time.sleep(self.how_long)
  
      def __str__(self):
          return '<PAUSE %1.2f>' % (self.how_long)
  
      __repr__ = __str__
  
      # def GetInput(self):
      #    print `self.key`
      #    keys = KeyAction.GetInput(self)
      #
      #    shift_state = HiByte(VkKeyScan(self.key))
      #
      #    shift_down = shift_state & 0x100  # 1st bit
      #    ctrl_down =  shift_state & 0x80   # 2nd bit
      #    alt_down =  shift_state & 0x40    # 3rd bit
      #
      #    print bin(shift_state), shift_down, ctrl_down, alt_down
      #
      #    print keys
      #    keys = [k for k in keys]
      #
      #    modifiers = []
      #    if shift_down:
      #        keys[0:0] = VirtualKeyAction(VK_SHIFT, up = False).GetInput()
      #        keys.append(VirtualKeyAction(VK_SHIFT, down = False).GetInput())
      #    if ctrl_down:
      #        keys[0:0] = VirtualKeyAction(VK_CONTROL, up = False).GetInput()
      #        keys.append(VirtualKeyAction(VK_CONTROL, down = False).GetInput())
      #    if alt_down:
      #        keys[0:0] = VirtualKeyAction(VK_ALT, up = False).GetInput()
      #        keys.append(VirtualKeyAction(VK_ALT, down = False).GetInput())
      #
      #    print keys
      #    new_keys = (INPUT * len(keys)) ()
      #
      #    for i, k in enumerate(keys):
      #        if hasattr(k, 'type'):
      #            new_keys[i] = k
      #        else:
      #            for sub_key in k:
      #                new_keys[i] = sub_key
      #
      #    return new_keys
      #
  
  
  def handle_code(code):
      'Handle a key or sequence of keys in braces'
  
      code_keys = []
      # it is a known code (e.g. {DOWN}, {ENTER}, etc)
      if code in CODES:
          code_keys.append(VirtualKeyAction(CODES[code]))
  
      # it is an escaped modifier e.g. {%}, {^}, {+}
      elif len(code) == 1:
          code_keys.append(KeyAction(code))
  
      # it is a repetition or a pause  {DOWN 5}, {PAUSE 1.3}
      elif ' ' in code:
          to_repeat, count = code.rsplit(None, 1)
          if to_repeat == 'PAUSE':
              try:
                  pause_time = float(count)
              except ValueError:
                  raise KeySequenceError('invalid pause time %s' % count)
              code_keys.append(PauseAction(pause_time))
  
          else:
              try:
                  count = int(count)
              except ValueError:
                  raise KeySequenceError('invalid repetition count %s' % count)
  
              # If the value in to_repeat is a VK e.g. DOWN
              # we need to add the code repeated
              if to_repeat in CODES:
                  code_keys.extend([VirtualKeyAction(CODES[to_repeat])] * count)
              # otherwise parse the keys and we get back a KeyAction
              else:
                  to_repeat = parse_keys(to_repeat)
                  if isinstance(to_repeat, list):
                      keys = to_repeat * count
                  else:
                      keys = [to_repeat] * count
                  code_keys.extend(keys)
      else:
          raise RuntimeError('Unknown code: %s' % code)
  
      return code_keys
  
  
  def parse_keys(
      string, with_spaces=False, with_tabs=False, with_newlines=False, modifiers=None
  ):
      'Return the parsed keys'
  
      keys = []
      if not modifiers:
          modifiers = []
      index = 0
      while index < len(string):
  
          c = string[index]
          index += 1
          # check if one of CTRL, SHIFT, ALT has been pressed
          if c in MODIFIERS.keys():
              modifier = MODIFIERS[c]
              # remember that we are currently modified
              modifiers.append(modifier)
              # hold down the modifier key
              keys.append(VirtualKeyAction(modifier, up=False))
              if DEBUG:
                  print('MODS+', modifiers)
              continue
  
          # Apply modifiers over a bunch of characters (not just one!)
          elif c == '(':
              # find the end of the bracketed text
              end_pos = string.find(')', index)
              if end_pos == -1:
                  raise KeySequenceError('`)` not found')
              keys.extend(parse_keys(string[index:end_pos], modifiers=modifiers))
              index = end_pos + 1
  
          # Escape or named key
          elif c == '{':
              # We start searching from index + 1 to account for the case {}}
              end_pos = string.find('}', index + 1)
              if end_pos == -1:
                  raise KeySequenceError('`}` not found')
  
              code = string[index:end_pos]
              index = end_pos + 1
              keys.extend(handle_code(code))
  
          # unmatched ")"
          elif c == ')':
              raise KeySequenceError('`)` should be preceeded by `(`')
  
          # unmatched "}"
          elif c == '}':
              raise KeySequenceError('`}` should be preceeded by `{`')
  
          # so it is a normal character
          else:
              # don't output white space unless flags to output have been set
              if (
                  c == ' '
                  and not with_spaces
                  or c == '\t'
                  and not with_tabs
                  or c == '\n'
                  and not with_newlines
              ):
                  continue
  
              # output nuewline
              if c in ('~', '\n'):
                  keys.append(VirtualKeyAction(CODES['ENTER']))
  
              # safest are the virtual keys - so if our key is a virtual key
              # use a VirtualKeyAction
              # if ord(c) in CODE_NAMES:
              #    keys.append(VirtualKeyAction(ord(c)))
  
              elif modifiers:
                  keys.append(EscapedKeyAction(c))
  
              else:
                  keys.append(KeyAction(c))
  
          # as we have handled the text - release the modifiers
          while modifiers:
              if DEBUG:
                  print('MODS-', modifiers)
              keys.append(VirtualKeyAction(modifiers.pop(), down=False))
  
      # just in case there were any modifiers left pressed - release them
      while modifiers:
          keys.append(VirtualKeyAction(modifiers.pop(), down=False))
  
      return keys
  
  
  def LoByte(val):
      'Return the low byte of the value'
      return val & 0xFF
  
  
  def HiByte(val):
      'Return the high byte of the value'
      return (val & 0xFF00) >> 8
  
  
  def SendKeys(
      keys,
      pause=0.05,
      with_spaces=False,
      with_tabs=False,
      with_newlines=False,
      turn_off_numlock=True,
  ):
      'Parse the keys and type them'
      keys = parse_keys(keys, with_spaces, with_tabs, with_newlines)
  
      for k in keys:
          k.Run()
          time.sleep(pause)
  
  
  def main():
      'Send some test strings'
  
      actions = """
          {LWIN}
          {PAUSE .25}
          r
          {PAUSE .25}
          Notepad.exe{ENTER}
          {PAUSE 1}
          Hello{SPACE}World!
          {PAUSE 1}
          %{F4}
          {PAUSE .25}
          n
          """
      SendKeys(actions, pause=0.1)
  
      keys = parse_keys(actions)
      for k in keys:
          print(k)
          k.Run()
          time.sleep(0.1)
  
      test_strings = [
          '\n' '(aa)some text\n',
          '(a)some{ }text\n',
          '(b)some{{}text\n',
          '(c)some{+}text\n',
          '(d)so%me{ab 4}text',
          '(e)so%me{LEFT 4}text',
          '(f)so%me{ENTER 4}text',
          '(g)so%me{^aa 4}text',
          '(h)some +(asdf)text',
          '(i)some %^+(asdf)text',
          '(j)some %^+a text+',
          '(k)some %^+a tex+{&}',
          '(l)some %^+a tex+(dsf)',
          '',
      ]
  
      for s in test_strings:
          print(repr(s))
          keys = parse_keys(s, with_newlines=True)
          print(keys)
  
          for k in keys:
              k.Run()
              time.sleep(0.1)
          print()
  
  
  if __name__ == '__main__':
  
      main()
  