chromium/ui/events/win/events_win_utils.cc

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

#include "ui/events/win/events_win_utils.h"

#include <stdint.h>

#include "base/check.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/win/windowsx_shim.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
#include "ui/events/keycodes/platform_key_map_win.h"
#include "ui/events/types/event_type.h"
#include "ui/events/win/system_event_state_lookup.h"
#include "ui/gfx/geometry/point.h"

namespace ui {

namespace {

// From MSDN: "Mouse" events are flagged with 0xFF515700 if they come
// from a touch or stylus device.  In Vista or later, the eighth bit,
// masked by 0x80, is used to differentiate touch input from pen input
// (0 = pen, 1 = touch).
#define MOUSEEVENTF_FROMTOUCHPEN 0xFF515700
#define MOUSEEVENTF_FROMTOUCH (MOUSEEVENTF_FROMTOUCHPEN | 0x80)
#define SIGNATURE_MASK 0xFFFFFF00

// Get the native mouse key state from the native event message type.
int GetNativeMouseKey(const CHROME_MSG& native_event) {
  switch (native_event.message) {
    case WM_LBUTTONDBLCLK:
    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_NCLBUTTONDBLCLK:
    case WM_NCLBUTTONDOWN:
    case WM_NCLBUTTONUP:
      return MK_LBUTTON;
    case WM_MBUTTONDBLCLK:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONUP:
    case WM_NCMBUTTONDBLCLK:
    case WM_NCMBUTTONDOWN:
    case WM_NCMBUTTONUP:
      return MK_MBUTTON;
    case WM_RBUTTONDBLCLK:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_NCRBUTTONDBLCLK:
    case WM_NCRBUTTONDOWN:
    case WM_NCRBUTTONUP:
      return MK_RBUTTON;
    case WM_NCXBUTTONDBLCLK:
    case WM_NCXBUTTONDOWN:
    case WM_NCXBUTTONUP:
    case WM_XBUTTONDBLCLK:
    case WM_XBUTTONDOWN:
    case WM_XBUTTONUP:
      return MK_XBUTTON1;
  }
  return 0;
}

bool IsButtonDown(const CHROME_MSG& native_event) {
  return ((MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2) &
          native_event.wParam) != 0;
}

bool IsClientMouseMessage(UINT message) {
  return message == WM_MOUSELEAVE || message == WM_MOUSEHOVER ||
         (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST);
}

bool IsClientMouseEvent(const CHROME_MSG& native_event) {
  return IsClientMouseMessage(native_event.message);
}

bool IsNonClientMouseMessage(UINT message) {
  return message == WM_NCMOUSELEAVE || message == WM_NCMOUSEHOVER ||
         (message >= WM_NCMOUSEMOVE && message <= WM_NCXBUTTONDBLCLK);
}

bool IsNonClientMouseEvent(const CHROME_MSG& native_event) {
  return IsNonClientMouseMessage(native_event.message);
}

bool IsMouseMessage(UINT message) {
  return IsClientMouseMessage(message) || IsNonClientMouseMessage(message);
}

bool IsMouseEvent(const CHROME_MSG& native_event) {
  return IsClientMouseEvent(native_event) ||
         IsNonClientMouseEvent(native_event);
}

bool IsMouseWheelEvent(const CHROME_MSG& native_event) {
  return native_event.message == WM_MOUSEWHEEL ||
         native_event.message == WM_MOUSEHWHEEL;
}

bool IsKeyEvent(const CHROME_MSG& native_event) {
  return native_event.message == WM_KEYDOWN ||
         native_event.message == WM_SYSKEYDOWN ||
         native_event.message == WM_CHAR ||
         native_event.message == WM_SYSCHAR ||
         native_event.message == WM_KEYUP ||
         native_event.message == WM_SYSKEYUP;
}

bool IsScrollEvent(const CHROME_MSG& native_event) {
  return native_event.message == WM_VSCROLL ||
         native_event.message == WM_HSCROLL;
}

// Returns a mask corresponding to the set of pressed modifier keys.
// Checks the current global state and the state sent by client mouse messages.
int KeyStateFlags(const CHROME_MSG& native_event) {
  int flags = GetModifiersFromKeyState();

  // Check key messages for the extended key flag.
  if (IsKeyEvent(native_event) && (HIWORD(native_event.lParam) & KF_EXTENDED))
    flags |= EF_IS_EXTENDED_KEY;

  // Most client mouse messages include key state information.
  if (IsClientMouseEvent(native_event)) {
    int win_flags = GET_KEYSTATE_WPARAM(native_event.wParam);
    flags |= (win_flags & MK_SHIFT) ? EF_SHIFT_DOWN : 0;
    flags |= (win_flags & MK_CONTROL) ? EF_CONTROL_DOWN : 0;
  }

  return flags;
}

// Returns a mask corresponding to the set of pressed mouse buttons.
// This includes the button of the given message, even if it is being released.
int MouseStateFlags(const CHROME_MSG& native_event) {
  int win_flags = GetNativeMouseKey(native_event);

  // Client mouse messages provide key states in their WPARAMs.
  if (IsClientMouseEvent(native_event))
    win_flags |= GET_KEYSTATE_WPARAM(native_event.wParam);

  int flags = 0;
  flags |= (win_flags & MK_LBUTTON) ? EF_LEFT_MOUSE_BUTTON : 0;
  flags |= (win_flags & MK_MBUTTON) ? EF_MIDDLE_MOUSE_BUTTON : 0;
  flags |= (win_flags & MK_RBUTTON) ? EF_RIGHT_MOUSE_BUTTON : 0;
  flags |= IsNonClientMouseEvent(native_event) ? EF_IS_NON_CLIENT : 0;
  return flags;
}

class GetTickCountClock : public base::TickClock {
 public:
  GetTickCountClock() = default;
  ~GetTickCountClock() override = default;

  base::TimeTicks NowTicks() const override {
    return base::TimeTicks() + base::Milliseconds(::GetTickCount());
  }
};

const base::TickClock* g_tick_count_clock = nullptr;

}  // namespace

EventType EventTypeFromMSG(const CHROME_MSG& native_event) {
  switch (native_event.message) {
    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
    case WM_CHAR:
    case WM_SYSCHAR:
      return EventType::kKeyPressed;
    // The WM_DEADCHAR message is posted to the window with the keyboard focus
    // when a WM_KEYUP message is translated. This happens for special keyboard
    // sequences.
    case WM_DEADCHAR:
    case WM_KEYUP:
    // The WM_SYSDEADCHAR message is posted to a window with keyboard focus
    // when the WM_SYSKEYDOWN message is translated by the TranslateMessage
    // function. It specifies the character code of the system dead key.
    case WM_SYSDEADCHAR:
    case WM_SYSKEYUP:
      return EventType::kKeyReleased;
    case WM_LBUTTONDBLCLK:
    case WM_LBUTTONDOWN:
    case WM_MBUTTONDBLCLK:
    case WM_MBUTTONDOWN:
    case WM_NCLBUTTONDBLCLK:
    case WM_NCLBUTTONDOWN:
    case WM_NCMBUTTONDBLCLK:
    case WM_NCMBUTTONDOWN:
    case WM_NCRBUTTONDBLCLK:
    case WM_NCRBUTTONDOWN:
    case WM_NCXBUTTONDBLCLK:
    case WM_NCXBUTTONDOWN:
    case WM_RBUTTONDBLCLK:
    case WM_RBUTTONDOWN:
    case WM_XBUTTONDBLCLK:
    case WM_XBUTTONDOWN:
      return EventType::kMousePressed;
    case WM_LBUTTONUP:
    case WM_MBUTTONUP:
    case WM_NCLBUTTONUP:
    case WM_NCMBUTTONUP:
    case WM_NCRBUTTONUP:
    case WM_NCXBUTTONUP:
    case WM_RBUTTONUP:
    case WM_XBUTTONUP:
      return EventType::kMouseReleased;
    case WM_MOUSEMOVE:
      return IsButtonDown(native_event) ? EventType::kMouseDragged
                                        : EventType::kMouseMoved;
    case WM_NCMOUSEMOVE:
      return EventType::kMouseMoved;
    case WM_MOUSEWHEEL:
    case WM_MOUSEHWHEEL:
      return EventType::kMousewheel;
    case WM_MOUSELEAVE:
    case WM_NCMOUSELEAVE:
      return EventType::kMouseExited;
    case WM_VSCROLL:
    case WM_HSCROLL:
      return EventType::kScroll;
    default:
      // We can't NOTREACHED() here, since this function can be called for any
      // message.
      break;
  }
  return EventType::kUnknown;
}

int EventFlagsFromMSG(const CHROME_MSG& native_event) {
  int flags = KeyStateFlags(native_event);
  if (IsMouseEvent(native_event))
    flags |= MouseStateFlags(native_event);

  return flags;
}

base::TimeTicks EventTimeFromMSG(const CHROME_MSG& native_event) {
  // On Windows, the native input event timestamp (|native_event.time|) is
  // coming from |GetTickCount()| clock [1], while in platform independent code
  // path we get timestamps by calling |TimeTicks::Now()|, which, if using high-
  // resolution timer as underlying implementation, could have different time
  // origin than |GetTickCount()|. To avoid the mismatching, we use
  // |TimeTicks::Now()| for event timestamp instead of the native timestamp to
  // ensure computed input latency and web exposed timestamp is consistent with
  // other components.
  // TODO(crbug.com/342671898): Consider using the new optimistic
  // EventLatencyTimeFromTickClock() conversion here as well.
  // It is unnecessary to invoke |ValidateEventTimeClock| here because of above.
  // [1] http://blogs.msdn.com/b/oldnewthing/archive/2014/01/22/10491576.aspx
  return EventTimeForNow();
}

base::TimeTicks EventLatencyTimeFromTickClock(DWORD event_time,
                                              base::TimeTicks current_time) {
  static const base::NoDestructor<GetTickCountClock> default_tick_count_clock;
  if (!g_tick_count_clock)
    g_tick_count_clock = default_tick_count_clock.get();

  base::TimeTicks event_tick_count =
      base::TimeTicks() + base::Milliseconds(event_time);

  base::TimeTicks current_tick_count = g_tick_count_clock->NowTicks();
  // Check if the 32-bit tick count wrapped around after the event.
  if (current_tick_count < event_tick_count) {
    // ::GetTickCount returns an unsigned 32-bit value, which will fit into the
    // signed 64-bit base::TimeTicks.
    constexpr auto kAdditionalMillisecondsAfterWrap =
        static_cast<int64_t>(std::numeric_limits<DWORD>::max()) + 1;
    current_tick_count += base::Milliseconds(kAdditionalMillisecondsAfterWrap);
  }

  // The Windows ::GetTickCount() clock is simulating an old-school 64hZ clock
  // ticking every 15.625ms and reports that in ms so in practice each "tick" is
  // either 15 or 16ms.
  constexpr auto kMaxTickCountInterval = base::Milliseconds(16);

  base::TimeDelta tick_delta = current_tick_count - event_tick_count;
  // Discount one tick in order to be optimistic about the delay (we cannot know
  // how close the tick clock was to ticking when `event_tick_count` was
  // sampled nor how recently it ticked when `current_tick_count` was sampled).
  tick_delta =
      std::max(base::Microseconds(0), tick_delta - kMaxTickCountInterval);

  base::TimeTicks converted_event_time = current_time - tick_delta;
  ValidateEventTimeClock(&converted_event_time);
  return converted_event_time;
}

base::TimeTicks EventLatencyTimeFromPerformanceCounter(UINT64 event_time) {
  DCHECK(base::TimeTicks::IsHighResolution());
  base::TimeTicks time_stamp = base::TimeTicks::FromQPCValue(event_time);
  ValidateEventTimeClock(&time_stamp);
  return time_stamp;
}

gfx::Point EventLocationFromMSG(const CHROME_MSG& native_event) {
  // This code may use GetCursorPos() to get a mouse location. This may
  // fail in certain situations (see
  // https://bugs.chromium.org/p/chromium/issues/detail?id=540840#c20 for
  // details). To handle failure this code tracks the last known location so
  // that it can use a reasonable value should GetCursorPos() fail.
  static gfx::Point last_known_location;
  gfx::Point event_location = last_known_location;
  if ((native_event.message == WM_MOUSELEAVE ||
       native_event.message == WM_NCMOUSELEAVE) ||
      IsScrollEvent(native_event)) {
    // These events have no coordinates. For sanity with rest of events grab
    // coordinates from the OS.
    POINT native_point;
    if (::GetCursorPos(&native_point)) {
      ScreenToClient(native_event.hwnd, &native_point);
      event_location = gfx::Point(native_point);
    }
  } else if (IsClientMouseEvent(native_event) &&
             !IsMouseWheelEvent(native_event)) {
    // Note: Wheel events are considered client, but their position is in screen
    //       coordinates.
    // Client message. The position is contained in the LPARAM.
    event_location = gfx::Point(static_cast<DWORD>(native_event.lParam));
  } else {
    DCHECK(IsNonClientMouseEvent(native_event) ||
           IsMouseWheelEvent(native_event) || IsScrollEvent(native_event));
    // Non-client message. The position is contained in a POINTS structure in
    // LPARAM, and is in screen coordinates so we have to convert to client.
    POINT native_point;
    native_point.x = GET_X_LPARAM(native_event.lParam);
    native_point.y = GET_Y_LPARAM(native_event.lParam);
    ScreenToClient(native_event.hwnd, &native_point);
    event_location = gfx::Point(native_point);
  }

  last_known_location = event_location;
  return event_location;
}

gfx::Point EventSystemLocationFromMSG(const CHROME_MSG& native_event) {
  POINT global_point = {GET_X_LPARAM(native_event.lParam),
                        GET_Y_LPARAM(native_event.lParam)};
  // Wheel events have position in screen coordinates.
  if (!IsMouseWheelEvent(native_event))
    ClientToScreen(native_event.hwnd, &global_point);
  return gfx::Point(global_point);
}

KeyboardCode KeyboardCodeFromMSG(const CHROME_MSG& native_event) {
  return KeyboardCodeForWindowsKeyCode(static_cast<WORD>(native_event.wParam));
}

DomCode CodeFromMSG(const CHROME_MSG& native_event) {
  const uint16_t scan_code = GetScanCodeFromLParam(native_event.lParam);
  return CodeForWindowsScanCode(scan_code);
}

bool IsCharFromMSG(const CHROME_MSG& native_event) {
  return native_event.message == WM_CHAR || native_event.message == WM_SYSCHAR;
}

int GetChangedMouseButtonFlagsFromMSG(const CHROME_MSG& native_event) {
  switch (GetNativeMouseKey(native_event)) {
    case MK_LBUTTON:
      return EF_LEFT_MOUSE_BUTTON;
    case MK_MBUTTON:
      return EF_MIDDLE_MOUSE_BUTTON;
    case MK_RBUTTON:
      return EF_RIGHT_MOUSE_BUTTON;
    // TODO: add support for MK_XBUTTON1.
    default:
      break;
  }
  return 0;
}

PointerDetails GetMousePointerDetailsFromMSG(const CHROME_MSG& native_event) {
  // We should filter out all the mouse events Synthesized from touch events.
  // TODO(lanwei): Will set the pointer ID, see https://crbug.com/616771.
  if ((GetMessageExtraInfo() & SIGNATURE_MASK) != MOUSEEVENTF_FROMTOUCHPEN)
    return PointerDetails(EventPointerType::kMouse);

  return PointerDetails(EventPointerType::kPen);
}

gfx::Vector2d GetMouseWheelOffsetFromMSG(const CHROME_MSG& native_event) {
  DCHECK(native_event.message == WM_MOUSEWHEEL ||
         native_event.message == WM_MOUSEHWHEEL);
  if (native_event.message == WM_MOUSEWHEEL)
    return gfx::Vector2d(0, GET_WHEEL_DELTA_WPARAM(native_event.wParam));
  return gfx::Vector2d(GET_WHEEL_DELTA_WPARAM(native_event.wParam), 0);
}

void ClearTouchIdIfReleasedFromMSG(const CHROME_MSG& xev) {
  NOTIMPLEMENTED();
}

int GetTouchIdFromMSG(const CHROME_MSG& xev) {
  NOTIMPLEMENTED();
  return 0;
}

PointerDetails GetTouchPointerDetailsFromMSG(const CHROME_MSG& native_event) {
  NOTIMPLEMENTED();
  return PointerDetails(EventPointerType::kTouch,
                        /* pointer_id*/ 0,
                        /* radius_x */ 1.0,
                        /* radius_y */ 1.0,
                        /* force */ 0.f);
}

bool GetScrollOffsetsFromMSG(const CHROME_MSG& native_event) {
  // TODO(ananta)
  // Support retrieving the scroll offsets from the scroll event.
  if (native_event.message == WM_VSCROLL || native_event.message == WM_HSCROLL)
    return true;
  return false;
}

bool GetFlingDataFromMSG(const CHROME_MSG& native_event,
                         float* vx,
                         float* vy,
                         float* vx_ordinal,
                         float* vy_ordinal,
                         bool* is_cancel) {
  // Not supported in Windows.
  NOTIMPLEMENTED();
  return false;
}

int GetModifiersFromKeyState() {
  int modifiers = EF_NONE;
  if (ui::win::IsShiftPressed())
    modifiers |= EF_SHIFT_DOWN;
  if (ui::win::IsAltRightPressed() && PlatformKeyMap::UsesAltGraph()) {
    modifiers |= EF_ALTGR_DOWN;
  } else {
    // Note that if the platform keyboard layout uses AltGraph then these may
    // be overridden on KeyEvents for printable characters generated using
    // AltGraph simulated via Control+Alt.
    if (ui::win::IsCtrlPressed())
      modifiers |= EF_CONTROL_DOWN;
    if (ui::win::IsAltPressed())
      modifiers |= EF_ALT_DOWN;
  }
  if (ui::win::IsWindowsKeyPressed())
    modifiers |= EF_COMMAND_DOWN;
  if (ui::win::IsNumLockOn())
    modifiers |= EF_NUM_LOCK_ON;
  if (ui::win::IsCapsLockOn())
    modifiers |= EF_CAPS_LOCK_ON;
  if (ui::win::IsScrollLockOn())
    modifiers |= EF_SCROLL_LOCK_ON;
  return modifiers;
}

// Windows emulates mouse messages for touch events.
bool IsMouseEventFromTouch(UINT message) {
  return IsMouseMessage(message) &&
         (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) ==
             MOUSEEVENTF_FROMTOUCH;
}

// Conversion scan_code and LParam each other.
// uint16_t scan_code:
//     ui/events/keycodes/dom/dom_code_data.inc
// 0 - 15bits: represetns the scan code.
// 28 - 30 bits (0xE000): represents whether this is an extended key or not.
//
// LPARAM lParam:
//     http://msdn.microsoft.com/en-us/library/windows/desktop/ms644984.aspx
// 16 - 23bits: represetns the scan code.
// 24bit (0x0100): represents whether this is an extended key or not.
uint16_t GetScanCodeFromLParam(LPARAM l_param) {
  uint16_t scan_code = ((l_param >> 16) & 0x00FF);
  if (l_param & (1 << 24))
    scan_code |= 0xE000;
  return scan_code;
}

LPARAM GetLParamFromScanCode(uint16_t scan_code) {
  LPARAM l_param = static_cast<LPARAM>(scan_code & 0x00FF) << 16;
  if ((scan_code & 0xE000) == 0xE000)
    l_param |= (1 << 24);
  return l_param;
}

KeyEvent KeyEventFromMSG(const CHROME_MSG& msg) {
  DCHECK(IsKeyEvent(msg));
  EventType type = EventTypeFromMSG(msg);
  KeyboardCode key_code = KeyboardCodeFromMSG(msg);
  DomCode code = CodeFromMSG(msg);
  int flags = EventFlagsFromMSG(msg);
  DomKey key;
  base::TimeTicks time_stamp = EventTimeFromMSG(msg);

  if (IsCharFromMSG(msg)) {
    flags = PlatformKeyMap::ReplaceControlAndAltWithAltGraph(flags);
    return KeyEvent::FromCharacter(msg.wParam, key_code, code, flags,
                                   time_stamp);
  } else {
    key = PlatformKeyMap::DomKeyFromKeyboardCode(key_code, &flags);
    return KeyEvent(type, key_code, code, flags, key, time_stamp);
  }
}

CHROME_MSG MSGFromKeyEvent(KeyEvent* event, HWND hwnd) {
  if (event->HasNativeEvent())
    return event->native_event();
  uint16_t scan_code = KeycodeConverter::DomCodeToNativeKeycode(event->code());
  LPARAM l_param = GetLParamFromScanCode(scan_code);
  WPARAM w_param = event->GetConflatedWindowsKeyCode();
  UINT message;
  if (event->is_char())
    message = WM_CHAR;
  else
    message = event->type() == EventType::kKeyPressed ? WM_KEYDOWN : WM_KEYUP;
  return {hwnd, message, w_param, l_param};
}

MouseEvent MouseEventFromMSG(const CHROME_MSG& msg) {
  EventType type = EventTypeFromMSG(msg);
  gfx::Point location = EventLocationFromMSG(msg);
  gfx::Point root_location = EventSystemLocationFromMSG(msg);
  base::TimeTicks time_stamp = EventTimeFromMSG(msg);
  int flags = EventFlagsFromMSG(msg);
  int changed_button_flags = GetChangedMouseButtonFlagsFromMSG(msg);
  PointerDetails pointer_details = GetMousePointerDetailsFromMSG(msg);

  return MouseEvent(type, location, root_location, time_stamp, flags,
                    changed_button_flags, pointer_details);
}

MouseWheelEvent MouseWheelEventFromMSG(const CHROME_MSG& msg) {
  gfx::Vector2d offset = GetMouseWheelOffsetFromMSG(msg);
  gfx::Point location = EventLocationFromMSG(msg);
  gfx::Point root_location = EventSystemLocationFromMSG(msg);
  base::TimeTicks time_stamp = EventTimeFromMSG(msg);
  int flags = EventFlagsFromMSG(msg);
  int changed_button_flags = GetChangedMouseButtonFlagsFromMSG(msg);

  return MouseWheelEvent(offset, location, root_location, time_stamp, flags,
                         changed_button_flags);
}

void SetEventLatencyTickClockForTesting(const base::TickClock* clock) {
  g_tick_count_clock = clock;
}

}  // namespace ui