text: // Copyright 2013 The Flutter Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "flutter/shell/platform/windows/win32_window.h"
  
  #include <cstring>
  
  #include "win32_dpi_utils.h"
  
  namespace flutter {
  
  namespace {
  char32_t CodePointFromSurrogatePair(wchar_t high, wchar_t low) {
    return 0x10000 + ((static_cast<char32_t>(high) & 0x000003FF) << 10) +
           (low & 0x3FF);
  }
  }  // namespace
  
  Win32Window::Win32Window() {
    // Get the DPI of the primary monitor as the initial DPI. If Per-Monitor V2 is
    // supported, |current_dpi_| should be updated in the
    // kWmDpiChangedBeforeParent message.
    current_dpi_ = GetDpiForHWND(nullptr);
  }
  
  Win32Window::~Win32Window() {
    Destroy();
  }
  
  void Win32Window::InitializeChild(const char* title,
                                    unsigned int width,
                                    unsigned int height) {
    Destroy();
    std::wstring converted_title = NarrowToWide(title);
  
    WNDCLASS window_class = RegisterWindowClass(converted_title);
  
    auto* result = CreateWindowEx(
        0, window_class.lpszClassName, converted_title.c_str(),
        WS_CHILD | WS_VISIBLE, CW_DEFAULT, CW_DEFAULT, width, height,
        HWND_MESSAGE, nullptr, window_class.hInstance, this);
  
    if (result == nullptr) {
      auto error = GetLastError();
      LPWSTR message = nullptr;
      size_t size = FormatMessageW(
          FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
              FORMAT_MESSAGE_IGNORE_INSERTS,
          NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
          reinterpret_cast<LPWSTR>(&message), 0, NULL);
      OutputDebugString(message);
      LocalFree(message);
    }
  }
  
  std::wstring Win32Window::NarrowToWide(const char* source) {
    size_t length = strlen(source);
    size_t outlen = 0;
    std::wstring wideTitle(length, L'#');
    mbstowcs_s(&outlen, &wideTitle[0], length + 1, source, length);
    return wideTitle;
  }
  
  WNDCLASS Win32Window::RegisterWindowClass(std::wstring& title) {
    window_class_name_ = title;
  
    WNDCLASS window_class{};
    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
    window_class.lpszClassName = title.c_str();
    window_class.style = CS_HREDRAW | CS_VREDRAW;
    window_class.cbClsExtra = 0;
    window_class.cbWndExtra = 0;
    window_class.hInstance = GetModuleHandle(nullptr);
    window_class.hIcon = nullptr;
    window_class.hbrBackground = 0;
    window_class.lpszMenuName = nullptr;
    window_class.lpfnWndProc = WndProc;
    RegisterClass(&window_class);
    return window_class;
  }
  
  LRESULT CALLBACK Win32Window::WndProc(HWND const window,
                                        UINT const message,
                                        WPARAM const wparam,
                                        LPARAM const lparam) noexcept {
    if (message == WM_NCCREATE) {
      auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);
      SetWindowLongPtr(window, GWLP_USERDATA,
                       reinterpret_cast<LONG_PTR>(cs->lpCreateParams));
  
      auto that = static_cast<Win32Window*>(cs->lpCreateParams);
      that->window_handle_ = window;
    } else if (Win32Window* that = GetThisFromHandle(window)) {
      return that->HandleMessage(message, wparam, lparam);
    }
  
    return DefWindowProc(window, message, wparam, lparam);
  }
  
  void Win32Window::TrackMouseLeaveEvent(HWND hwnd) {
    if (!tracking_mouse_leave_) {
      TRACKMOUSEEVENT tme;
      tme.cbSize = sizeof(tme);
      tme.hwndTrack = hwnd;
      tme.dwFlags = TME_LEAVE;
      TrackMouseEvent(&tme);
      tracking_mouse_leave_ = true;
    }
  }
  
  LRESULT
  Win32Window::HandleMessage(UINT const message,
                             WPARAM const wparam,
                             LPARAM const lparam) noexcept {
    int xPos = 0, yPos = 0;
    UINT width = 0, height = 0;
    UINT button_pressed = 0;
  
    switch (message) {
      case kWmDpiChangedBeforeParent:
        current_dpi_ = GetDpiForHWND(window_handle_);
        OnDpiScale(current_dpi_);
        return 0;
      case WM_SIZE:
        width = LOWORD(lparam);
        height = HIWORD(lparam);
  
        current_width_ = width;
        current_height_ = height;
        HandleResize(width, height);
        break;
      case WM_FONTCHANGE:
        OnFontChange();
        break;
      case WM_MOUSEMOVE:
        TrackMouseLeaveEvent(window_handle_);
  
        xPos = GET_X_LPARAM(lparam);
        yPos = GET_Y_LPARAM(lparam);
        OnPointerMove(static_cast<double>(xPos), static_cast<double>(yPos));
        break;
      case WM_MOUSELEAVE:;
        OnPointerLeave();
        // Once the tracked event is received, the TrackMouseEvent function
        // resets. Set to false to make sure it's called once mouse movement is
        // detected again.
        tracking_mouse_leave_ = false;
        break;
      case WM_SETCURSOR: {
        UINT hit_test_result = LOWORD(lparam);
        if (hit_test_result == HTCLIENT) {
          OnSetCursor();
          return TRUE;
        }
        break;
      }
      case WM_LBUTTONDOWN:
      case WM_RBUTTONDOWN:
      case WM_MBUTTONDOWN:
      case WM_XBUTTONDOWN:
        if (message == WM_LBUTTONDOWN) {
          // Capture the pointer in case the user drags outside the client area.
          // In this case, the "mouse leave" event is delayed until the user
          // releases the button. It's only activated on left click given that
          // it's more common for apps to handle dragging with only the left
          // button.
          SetCapture(window_handle_);
        }
        button_pressed = message;
        if (message == WM_XBUTTONDOWN) {
          button_pressed = GET_XBUTTON_WPARAM(wparam);
        }
        xPos = GET_X_LPARAM(lparam);
        yPos = GET_Y_LPARAM(lparam);
        OnPointerDown(static_cast<double>(xPos), static_cast<double>(yPos),
                      button_pressed);
        break;
      case WM_LBUTTONUP:
      case WM_RBUTTONUP:
      case WM_MBUTTONUP:
      case WM_XBUTTONUP:
        if (message == WM_LBUTTONUP) {
          ReleaseCapture();
        }
        button_pressed = message;
        if (message == WM_XBUTTONUP) {
          button_pressed = GET_XBUTTON_WPARAM(wparam);
        }
        xPos = GET_X_LPARAM(lparam);
        yPos = GET_Y_LPARAM(lparam);
        OnPointerUp(static_cast<double>(xPos), static_cast<double>(yPos),
                    button_pressed);
        break;
      case WM_MOUSEWHEEL:
        OnScroll(0.0, -(static_cast<short>(HIWORD(wparam)) /
                        static_cast<double>(WHEEL_DELTA)));
        break;
      case WM_MOUSEHWHEEL:
        OnScroll((static_cast<short>(HIWORD(wparam)) /
                  static_cast<double>(WHEEL_DELTA)),
                 0.0);
        break;
      case WM_UNICHAR: {
        // Tell third-pary app, we can support Unicode.
        if (wparam == UNICODE_NOCHAR)
          return TRUE;
        // DefWindowProc will send WM_CHAR for this WM_UNICHAR.
        break;
      }
      case WM_DEADCHAR:
      case WM_SYSDEADCHAR:
      case WM_CHAR:
      case WM_SYSCHAR: {
        static wchar_t s_pending_high_surrogate = 0;
  
        wchar_t character = static_cast<wchar_t>(wparam);
        std::u16string text({character});
        char32_t code_point = character;
        if (IS_HIGH_SURROGATE(character)) {
          // Save to send later with the trailing surrogate.
          s_pending_high_surrogate = character;
        } else if (IS_LOW_SURROGATE(character) && s_pending_high_surrogate != 0) {
          text.insert(text.begin(), s_pending_high_surrogate);
          // Merge the surrogate pairs for the key event.
          code_point =
              CodePointFromSurrogatePair(s_pending_high_surrogate, character);
          s_pending_high_surrogate = 0;
        }
  
        // Of the messages handled here, only WM_CHAR should be treated as
        // characters. WM_SYS*CHAR are not part of text input, and WM_DEADCHAR
        // will be incorporated into a later WM_CHAR with the full character.
        // Also filter out:
        // - Lead surrogates, which like dead keys will be send once combined.
        // - ASCII control characters, which are sent as WM_CHAR events for all
        //   control key shortcuts.
        if (message == WM_CHAR && s_pending_high_surrogate == 0 &&
            character >= u' ') {
          OnText(text);
        }
  
        // All key presses that generate a character should be sent from
        // WM_CHAR. In order to send the full key press information, the keycode
        // is persisted in keycode_for_char_message_ obtained from WM_KEYDOWN.
        if (keycode_for_char_message_ != 0) {
          const unsigned int scancode = (lparam >> 16) & 0xff;
          OnKey(keycode_for_char_message_, scancode, WM_KEYDOWN, code_point);
          keycode_for_char_message_ = 0;
        }
        break;
      }
      case WM_KEYDOWN:
      case WM_SYSKEYDOWN:
      case WM_KEYUP:
      case WM_SYSKEYUP:
        const bool is_keydown_message =
            (message == WM_KEYDOWN || message == WM_SYSKEYDOWN);
        // Check if this key produces a character. If so, the key press should
        // be sent with the character produced at WM_CHAR. Store the produced
        // keycode (it's not accessible from WM_CHAR) to be used in WM_CHAR.
        const unsigned int character = MapVirtualKey(wparam, MAPVK_VK_TO_CHAR);
        if (character > 0 && is_keydown_message) {
          keycode_for_char_message_ = wparam;
          break;
        }
        unsigned int keyCode(wparam);
        const unsigned int scancode = (lparam >> 16) & 0xff;
        // If the key is a modifier, get its side.
        if (keyCode == VK_SHIFT || keyCode == VK_MENU || keyCode == VK_CONTROL) {
          keyCode = MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX);
        }
        const int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP;
        OnKey(keyCode, scancode, action, 0);
        break;
    }
  
    return DefWindowProc(window_handle_, message, wparam, lparam);
  }
  
  UINT Win32Window::GetCurrentDPI() {
    return current_dpi_;
  }
  
  UINT Win32Window::GetCurrentWidth() {
    return current_width_;
  }
  
  UINT Win32Window::GetCurrentHeight() {
    return current_height_;
  }
  
  HWND Win32Window::GetWindowHandle() {
    return window_handle_;
  }
  
  void Win32Window::Destroy() {
    if (window_handle_) {
      DestroyWindow(window_handle_);
      window_handle_ = nullptr;
    }
  
    UnregisterClass(window_class_name_.c_str(), nullptr);
  }
  
  void Win32Window::HandleResize(UINT width, UINT height) {
    current_width_ = width;
    current_height_ = height;
    OnResize(width, height);
  }
  
  Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
    return reinterpret_cast<Win32Window*>(
        GetWindowLongPtr(window, GWLP_USERDATA));
  }
  
  }  // namespace flutter
  