root/remoting/client/plugin/normalizing_input_filter_cros.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. IsOsKey
  2. IsRewrittenFunctionKey
  3. IsRewrittenExtendedKey
  4. IsRewrittenKey
  5. modifying_key_
  6. InjectKeyEvent
  7. InjectMouseEvent
  8. ProcessKeyDown
  9. ProcessKeyUp
  10. SwitchRewritingKeyToModifying
  11. CreateNormalizingInputFilter

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// NormalizingInputFilterCros addresses the problems generated by key rewritings
// such as Down->PageDown, 1->F1, etc, when keys are pressed in combination with
// the OSKey (aka Search). Rewriting OSKey+Down, for example, causes us to
// receive the following:
//
// keydown OSKey
// keydown PageDown
// keyup PageDown
// keyup OSKey
//
// The host system will therefore behave as if OSKey+PageDown were pressed,
// rather than PageDown alone.
//
// This file must be kept up-to-date with changes to
// chrome/browser/ui/ash/event_rewriter.cc

#include "remoting/client/plugin/normalizing_input_filter.h"

#include "base/logging.h"
#include "remoting/proto/event.pb.h"

namespace remoting {

namespace {

// Returns true for OSKey codes.
static bool IsOsKey(unsigned int code) {
  const unsigned int kUsbLeftOsKey      = 0x0700e3;
  const unsigned int kUsbRightOsKey     = 0x0700e7;
  return code == kUsbLeftOsKey || code == kUsbRightOsKey;
}

// Returns true for codes generated by EventRewriter::RewriteFunctionKeys().
static bool IsRewrittenFunctionKey(unsigned int code) {
  const unsigned int kUsbFunctionKeyMin = 0x07003a;
  const unsigned int kUsbFunctionKeyMax = 0x070045;
  return code >= kUsbFunctionKeyMin && code <= kUsbFunctionKeyMax;
}

// Returns true for codes generated by EventRewriter::RewriteExtendedKeys().
static bool IsRewrittenExtendedKey(unsigned int code) {
  const unsigned int kUsbExtendedKeyMin = 0x070049;
  const unsigned int kUsbExtendedKeyMax = 0x07004e;
  return code >= kUsbExtendedKeyMin && code <= kUsbExtendedKeyMax;
}

// Returns true for codes generated by EventRewriter::Rewrite().
static bool IsRewrittenKey(unsigned int code) {
  return IsRewrittenExtendedKey(code) || IsRewrittenFunctionKey(code);
}

// The input filter tries to avoid sending keydown/keyup events for OSKey
// (aka Search, WinKey, Cmd, Super) when it is used to rewrite other key events.
// Rewriting via other combinations is not currently handled.
//
// OSKey events can be categorised as one of three kinds:
// - Modifying - Holding the key down while executing other input modifies the
//     effect of that input, e.g. OSKey+L causes the workstation to lock, e.g.
//     OSKey + mouse-move performs an extended selection.
// - Rewriting (ChromeOS only) - Holding the key down while pressing certain
//     keys causes them to be treated as different keys, e.g. OSKey causes the
//     Down key to behave as PageDown.
// - Normal - Press & release of the key trigger an action, e.g. showing the
//     Start menu.
//
// The input filter has four states:
// 1. No OSKey has been pressed.
//    - When an OSKey keydown is received, the event is deferred, and we move to
//      State #2.
// 2. An OSKey is pressed, but may be Normal, Rewriting or Modifying.
//    - If the OSKey keyup is received, the key is Normal, both events are sent
//      and we return to State #1.
//    - If a Rewritten event is received we move to State #3.
//    - If a Modified event is received the OSKey keydown is sent and we enter
//      State #4.
// 3. An OSKey is pressed, and is being used to Rewrite other key events.
//    - If the OSKey keyup is received then it is suppressed, and we move to
//      State #1.
//    - If a Modified event is received the OSKey keydown is sent and we enter
//      State #4.
//    - If a Rewritten event is received then we stay in State #3.
// 4. An OSKey is pressed, and is Modifying.
//    - If the OSKey keyup is received then we send it and we move to State #1.
//    - All other key event pass through the filter unchanged.

class NormalizingInputFilterCros : public protocol::InputFilter {
 public:
  explicit NormalizingInputFilterCros(protocol::InputStub* input_stub)
      : protocol::InputFilter(input_stub),
        deferred_key_is_rewriting_(false),
        modifying_key_(0) {
  }
  virtual ~NormalizingInputFilterCros() {}

  // InputFilter overrides.
  virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE {
    DCHECK(event.has_usb_keycode());
    DCHECK(event.has_pressed());

    if (event.pressed())
      ProcessKeyDown(event);
    else
      ProcessKeyUp(event);
  }

  virtual void InjectMouseEvent(const protocol::MouseEvent& event) OVERRIDE {
    if (deferred_keydown_event_.has_usb_keycode())
      SwitchRewritingKeyToModifying();
    InputFilter::InjectMouseEvent(event);
  }

 private:
  void ProcessKeyDown(const protocol::KeyEvent& event) {
    // If |event| is |deferred_keydown_event_| auto-repeat then assume
    // that the user is holding the key down rather than using it to Rewrite.
    if (deferred_keydown_event_.has_usb_keycode() &&
        deferred_keydown_event_.usb_keycode() == event.usb_keycode()) {
      SwitchRewritingKeyToModifying();
    }

    // If |event| is a |modifying_key_| repeat then let it pass through.
    if (modifying_key_ == event.usb_keycode()) {
      InputFilter::InjectKeyEvent(event);
      return;
    }

    // If |event| is for an OSKey and we don't know whether it's a Normal,
    // Rewriting or Modifying use, then hold the keydown event.
    if (IsOsKey(event.usb_keycode())) {
      deferred_keydown_event_ = event;
      deferred_key_is_rewriting_ = false;
      return;
    }

    // If |event| is for a Rewritten key then set a flag to prevent any deferred
    // OSKey keydown from being sent when keyup is received for it. Otherwise,
    // inject the deferred OSKey keydown, if any, and switch that key into
    // Modifying mode.
    if (IsRewrittenKey(event.usb_keycode())) {
      // Note that there may not be a deferred OSKey event if there is a full
      // PC keyboard connected, which can generate e.g. PageDown without
      // rewriting.
      deferred_key_is_rewriting_ = true;
    } else {
      if (deferred_keydown_event_.has_usb_keycode())
        SwitchRewritingKeyToModifying();
    }

    InputFilter::InjectKeyEvent(event);
  }

  void ProcessKeyUp(const protocol::KeyEvent& event) {
    if (deferred_keydown_event_.has_usb_keycode() &&
        deferred_keydown_event_.usb_keycode() == event.usb_keycode()) {
      if (deferred_key_is_rewriting_) {
        // If we never sent the keydown then don't send a keyup.
        deferred_keydown_event_ = protocol::KeyEvent();
        return;
      }

      // If the OSKey hasn't Rewritten anything then treat as Modifying.
      SwitchRewritingKeyToModifying();
    }

    if (modifying_key_ == event.usb_keycode())
      modifying_key_ = 0;

    InputFilter::InjectKeyEvent(event);
  }

  void SwitchRewritingKeyToModifying() {
    DCHECK(deferred_keydown_event_.has_usb_keycode());
    modifying_key_ = deferred_keydown_event_.usb_keycode();
    InputFilter::InjectKeyEvent(deferred_keydown_event_);
    deferred_keydown_event_ = protocol::KeyEvent();
  }

  // Holds the keydown event for the most recent OSKey to have been pressed,
  // while it is Rewriting, or we are not yet sure whether it is Normal,
  // Rewriting or Modifying. The event is sent on if we switch to Modifying, or
  // discarded if the OSKey is released while in Rewriting mode.
  protocol::KeyEvent deferred_keydown_event_;

  // True while the |rewrite_keydown_event_| key is Rewriting, i.e. was followed
  // by one or more Rewritten key events, and not by any Modified events.
  bool deferred_key_is_rewriting_;

  // Stores the code of the OSKey while it is pressed for use as a Modifier.
  uint32 modifying_key_;

  DISALLOW_COPY_AND_ASSIGN(NormalizingInputFilterCros);
};

}  // namespace

scoped_ptr<protocol::InputFilter> CreateNormalizingInputFilter(
    protocol::InputStub* input_stub) {
  return scoped_ptr<protocol::InputFilter>(
      new NormalizingInputFilterCros(input_stub));
}

}  // namespace remoting

/* [<][>][^][v][top][bottom][index][help] */