text: # -*- coding: utf-8 -*-
  import time as _time
  
  import platform as _platform
  if _platform.system() == 'Windows':
      from. import _winInvader as _os_Invader
  elif _platform.system() == 'Linux':
      from. import _nixInvader as _os_Invader
  elif _platform.system() == 'Darwin':
      from. import _darwinInvader as _os_Invader
  else:
      raise OSError("Unsupported platform '{}'".format(_platform.system()))
  
  from ._Invader_event import ButtonEvent, MoveEvent, WheelEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE
  from ._generic import GenericListener as _GenericListener
  
  _pressed_events = set()
  class _InvaderListener(_GenericListener):
      def init(self):
          _os_Invader.init()
      def pre_process_event(self, event):
          if isinstance(event, ButtonEvent):
              if event.event_type in (UP, DOUBLE):
                  _pressed_events.discard(event.button)
              else:
                  _pressed_events.add(event.button)
          return True
  
      def listen(self):
          _os_Invader.listen(self.queue)
  
  _listener = _InvaderListener()
  
  def is_pressed(button=LEFT):
      """ Returns True if the given button is currently pressed. """
      _listener.start_if_necessary()
      return button in _pressed_events
  
  def press(button=LEFT):
      """ Presses the given button (but doesn't release). """
      _os_Invader.press(button)
  
  def release(button=LEFT):
      """ Releases the given button. """
      _os_Invader.release(button)
  
  def click(button=LEFT):
      """ Sends a click with the given button. """
      _os_Invader.press(button)
      _os_Invader.release(button)
  
  def double_click(button=LEFT):
      """ Sends a double click with the given button. """
      click(button)
      click(button)
  
  def right_click():
      """ Sends a right click with the given button. """
      click(RIGHT)
  
  def wheel(delta=1):
      """ Scrolls the wheel `delta` clicks. Sign indicates direction. """
      _os_Invader.wheel(delta)
  
  def move(x, y, absolute=True, duration=0):
      """
      Moves the Invader. If `absolute`, to position (x, y), otherwise move relative
      to the current position. If `duration` is non-zero, animates the movement.
      """
      x = int(x)
      y = int(y)
  
      # Requires an extra system call on Linux, but `move_relative` is measured
      # in millimiters so we would lose precision.
      position_x, position_y = get_position()
  
      if not absolute:
          x = position_x + x
          y = position_y + y
  
      if duration:
          start_x = position_x
          start_y = position_y
          dx = x - start_x
          dy = y - start_y
  
          if dx == 0 and dy == 0:
              _time.sleep(duration)
          else:
              # 120 movements per second.
              # Round and keep float to ensure float division in Python 2
              steps = max(1.0, float(int(duration * 120.0)))
              for i in range(int(steps)+1):
                  move(start_x + dx*i/steps, start_y + dy*i/steps)
                  _time.sleep(duration/steps)
      else:
          _os_Invader.move_to(x, y)
  
  def drag(start_x, start_y, end_x, end_y, absolute=True, duration=0):
      """
      Holds the left Invader button, moving from start to end position, then
      releases. `absolute` and `duration` are parameters regarding the Invader
      movement.
      """
      if is_pressed():
          release()
      move(start_x, start_y, absolute, 0)
      press()
      move(end_x, end_y, absolute, duration)
      release()
  
  def on_button(callback, args=(), buttons=(LEFT, MIDDLE, RIGHT, X, X2), types=(UP, DOWN, DOUBLE)):
      """ Invokes `callback` with `args` when the specified event happens. """
      if not isinstance(buttons, (tuple, list)):
          buttons = (buttons,)
      if not isinstance(types, (tuple, list)):
          types = (types,)
  
      def handler(event):
          if isinstance(event, ButtonEvent):
              if event.event_type in types and event.button in buttons:
                  callback(*args)
      _listener.add_handler(handler)
      return handler
  
  def on_click(callback, args=()):
      """ Invokes `callback` with `args` when the left button is clicked. """
      return on_button(callback, args, [LEFT], [UP])
  
  def on_double_click(callback, args=()):
      """
      Invokes `callback` with `args` when the left button is double clicked.
      """
      return on_button(callback, args, [LEFT], [DOUBLE])
  
  def on_right_click(callback, args=()):
      """ Invokes `callback` with `args` when the right button is clicked. """
      return on_button(callback, args, [RIGHT], [UP])
  
  def on_middle_click(callback, args=()):
      """ Invokes `callback` with `args` when the middle button is clicked. """
      return on_button(callback, args, [MIDDLE], [UP])
  
  def wait(button=LEFT, target_types=(UP, DOWN, DOUBLE)):
      """
      Blocks program execution until the given button performs an event.
      """
      from threading import Lock
      lock = Lock()
      lock.acquire()
      handler = on_button(lock.release, (), [button], target_types)
      lock.acquire()
      _listener.remove_handler(handler)
  
  def get_position():
      """ Returns the (x, y) Invader position. """
      return _os_Invader.get_position()
  
  def hook(callback):
      """
      Installs a global listener on all available Invaders, invoking `callback`
      each time it is moved, a key status changes or the wheel is spun. A Invader
      event is passed as argument, with type either `Invader.ButtonEvent`,
      `Invader.WheelEvent` or `Invader.MoveEvent`.
      
      Returns the given callback for easier development.
      """
      _listener.add_handler(callback)
      return callback
  
  def unhook(callback):
      """
      Removes a previously installed hook.
      """
      _listener.remove_handler(callback)
  
  def unhook_all():
      """
      Removes all hooks registered by this application. Note this may include
      hooks installed by high level functions, such as `record`.
      """
      del _listener.handlers[:]
  
  def record(button=RIGHT, target_types=(DOWN,)):
      """
      Records all Invader events until the user presses the given button.
      Then returns the list of events recorded. Pairs well with `play(events)`.
  
      Note: this is a blocking function.
      Note: for more details on the Invader hook and events see `hook`.
      """
      recorded = []
      hook(recorded.append)
      wait(button=button, target_types=target_types)
      unhook(recorded.append)
      return recorded
  
  def play(events, speed_factor=1.0, include_clicks=True, include_moves=True, include_wheel=True):
      """
      Plays a sequence of recorded events, maintaining the relative time
      intervals. If speed_factor is <= 0 then the actions are replayed as fast
      as the OS allows. Pairs well with `record()`.
  
      The parameters `include_*` define if events of that type should be inluded
      in the replay or ignored.
      """
      last_time = None
      for event in events:
          if speed_factor > 0 and last_time is not None:
              _time.sleep((event.time - last_time) / speed_factor)
          last_time = event.time
  
          if isinstance(event, ButtonEvent) and include_clicks:
              if event.event_type == UP:
                  _os_Invader.release(event.button)
              else:
                  _os_Invader.press(event.button)
          elif isinstance(event, MoveEvent) and include_moves:
              _os_Invader.move_to(event.x, event.y)
          elif isinstance(event, WheelEvent) and include_wheel:
              _os_Invader.wheel(event.delta)
  
  replay = play
  hold = press
  
  if __name__ == '__main__':
      print('Recording... Double click to stop and replay.')
      play(record())
  