flutter_flutter/shell/platform/windows/win32_flutter_window.cc
stuartmorgan ed58844e8a
Use UTF-16 for C++ text input model (#17831)
The C++ text input model used by Windows and Linux currently uses UTF-32. The intention was to facilitate handling of arrow keys, backspace/delete, etc., however since part of what is synchronized with the engine is cursor+selection offsets, and those offsets are defined in terms of UTF-16 code units, this causes very bad interactions with the framework-side model.

This converts to using UTF-16, rather than UTF-32, so that the offsets align with the framework. It also adds surrogate pair handling to the operations that adjust indexes, to avoid breaking surrogate pairs. (Arbitrary grapheme cluster handling is out of scope for this PR; while definitely desirable in the long term, surrogate pair handling is much more critical since improper handling yields invalid UTF-16, which breaks the text field).

This partially fixes https://github.com/flutter/flutter/issues/55014. A framework-side fix is also necessary (since currently both the engine and the framework attempt to handle arrow keys, which is another out-of-scope-for-this-PR issue), but even without the framework fix this dramatically improves the cursor behavior on Windows when there are surrogate pairs somewhere in the string since at least the two sides agree on what indexes mean.

Includes minor plumbing changes to the text input plumbing on Windows so that we're not pointlessly converting from UTF-16 to UTF-32 and then back to UTF-16.
2020-04-21 11:01:01 -07:00

366 lines
12 KiB
C++

#include "flutter/shell/platform/windows/win32_flutter_window.h"
#include <chrono>
namespace flutter {
// The Windows DPI system is based on this
// constant for machines running at 100% scaling.
constexpr int base_dpi = 96;
Win32FlutterWindow::Win32FlutterWindow(int width, int height) {
surface_manager = std::make_unique<AngleSurfaceManager>();
Win32Window::InitializeChild("FLUTTERVIEW", width, height);
}
Win32FlutterWindow::~Win32FlutterWindow() {
DestroyRenderSurface();
if (plugin_registrar_ && plugin_registrar_->destruction_handler) {
plugin_registrar_->destruction_handler(plugin_registrar_.get());
}
}
FlutterDesktopViewControllerRef Win32FlutterWindow::CreateWin32FlutterWindow(
const int width,
const int height) {
auto state = std::make_unique<FlutterDesktopViewControllerState>();
state->view = std::make_unique<flutter::Win32FlutterWindow>(width, height);
// a window wrapper for the state block, distinct from the
// window_wrapper handed to plugin_registrar.
state->view_wrapper = std::make_unique<FlutterDesktopView>();
state->view_wrapper->window = state->view.get();
return state.release();
}
void Win32FlutterWindow::SetState(FLUTTER_API_SYMBOL(FlutterEngine) eng) {
engine_ = eng;
auto messenger = std::make_unique<FlutterDesktopMessenger>();
message_dispatcher_ =
std::make_unique<flutter::IncomingMessageDispatcher>(messenger.get());
messenger->engine = engine_;
messenger->dispatcher = message_dispatcher_.get();
window_wrapper_ = std::make_unique<FlutterDesktopView>();
window_wrapper_->window = this;
plugin_registrar_ = std::make_unique<FlutterDesktopPluginRegistrar>();
plugin_registrar_->messenger = std::move(messenger);
plugin_registrar_->window = window_wrapper_.get();
internal_plugin_registrar_ =
std::make_unique<flutter::PluginRegistrar>(plugin_registrar_.get());
// Set up the keyboard handlers.
auto internal_plugin_messenger = internal_plugin_registrar_->messenger();
keyboard_hook_handlers_.push_back(
std::make_unique<flutter::KeyEventHandler>(internal_plugin_messenger));
keyboard_hook_handlers_.push_back(
std::make_unique<flutter::TextInputPlugin>(internal_plugin_messenger));
platform_handler_ = std::make_unique<flutter::PlatformHandler>(
internal_plugin_messenger, this);
process_events_ = true;
}
FlutterDesktopPluginRegistrarRef Win32FlutterWindow::GetRegistrar() {
return plugin_registrar_.get();
}
// Converts a FlutterPlatformMessage to an equivalent FlutterDesktopMessage.
static FlutterDesktopMessage ConvertToDesktopMessage(
const FlutterPlatformMessage& engine_message) {
FlutterDesktopMessage message = {};
message.struct_size = sizeof(message);
message.channel = engine_message.channel;
message.message = engine_message.message;
message.message_size = engine_message.message_size;
message.response_handle = engine_message.response_handle;
return message;
}
// Translates button codes from Win32 API to FlutterPointerMouseButtons.
static uint64_t ConvertWinButtonToFlutterButton(UINT button) {
switch (button) {
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
return kFlutterPointerButtonMousePrimary;
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
return kFlutterPointerButtonMouseSecondary;
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
return kFlutterPointerButtonMouseMiddle;
case XBUTTON1:
return kFlutterPointerButtonMouseBack;
case XBUTTON2:
return kFlutterPointerButtonMouseForward;
}
std::cerr << "Mouse button not recognized: " << button << std::endl;
return 0;
}
// The Flutter Engine calls out to this function when new platform messages
// are available.
void Win32FlutterWindow::HandlePlatformMessage(
const FlutterPlatformMessage* engine_message) {
if (engine_message->struct_size != sizeof(FlutterPlatformMessage)) {
std::cerr << "Invalid message size received. Expected: "
<< sizeof(FlutterPlatformMessage) << " but received "
<< engine_message->struct_size << std::endl;
return;
}
auto message = ConvertToDesktopMessage(*engine_message);
message_dispatcher_->HandleMessage(
message, [this] { this->process_events_ = false; },
[this] { this->process_events_ = true; });
}
void Win32FlutterWindow::OnDpiScale(unsigned int dpi){};
// When DesktopWindow notifies that a WM_Size message has come in
// lets FlutterEngine know about the new size.
void Win32FlutterWindow::OnResize(unsigned int width, unsigned int height) {
SendWindowMetrics();
}
void Win32FlutterWindow::OnPointerMove(double x, double y) {
if (process_events_) {
SendPointerMove(x, y);
}
}
void Win32FlutterWindow::OnPointerDown(double x, double y, UINT button) {
if (process_events_) {
uint64_t flutter_button = ConvertWinButtonToFlutterButton(button);
if (flutter_button != 0) {
uint64_t mouse_buttons = GetMouseState().buttons | flutter_button;
SetMouseButtons(mouse_buttons);
SendPointerDown(x, y);
}
}
}
void Win32FlutterWindow::OnPointerUp(double x, double y, UINT button) {
if (process_events_) {
uint64_t flutter_button = ConvertWinButtonToFlutterButton(button);
if (flutter_button != 0) {
uint64_t mouse_buttons = GetMouseState().buttons & ~flutter_button;
SetMouseButtons(mouse_buttons);
SendPointerUp(x, y);
}
}
}
void Win32FlutterWindow::OnPointerLeave() {
if (process_events_) {
SendPointerLeave();
}
}
void Win32FlutterWindow::OnText(const std::u16string& text) {
if (process_events_) {
SendText(text);
}
}
void Win32FlutterWindow::OnKey(int key,
int scancode,
int action,
char32_t character) {
if (process_events_) {
SendKey(key, scancode, action, character);
}
}
void Win32FlutterWindow::OnScroll(double delta_x, double delta_y) {
if (process_events_) {
SendScroll(delta_x, delta_y);
}
}
void Win32FlutterWindow::OnFontChange() {
if (engine_ == nullptr) {
return;
}
FlutterEngineReloadSystemFonts(engine_);
}
// Sends new size information to FlutterEngine.
void Win32FlutterWindow::SendWindowMetrics() {
if (engine_ == nullptr) {
return;
}
FlutterWindowMetricsEvent event = {};
event.struct_size = sizeof(event);
event.width = GetCurrentWidth();
event.height = GetCurrentHeight();
event.pixel_ratio = static_cast<double>(GetCurrentDPI()) / base_dpi;
auto result = FlutterEngineSendWindowMetricsEvent(engine_, &event);
}
// Updates |event_data| with the current location of the mouse cursor.
void Win32FlutterWindow::SetEventLocationFromCursorPosition(
FlutterPointerEvent* event_data) {
POINT point;
GetCursorPos(&point);
ScreenToClient(GetWindowHandle(), &point);
event_data->x = point.x;
event_data->y = point.y;
}
// Set's |event_data|'s phase to either kMove or kHover depending on the current
// primary mouse button state.
void Win32FlutterWindow::SetEventPhaseFromCursorButtonState(
FlutterPointerEvent* event_data) {
MouseState state = GetMouseState();
// For details about this logic, see FlutterPointerPhase in the embedder.h
// file.
event_data->phase = state.buttons == 0 ? state.flutter_state_is_down
? FlutterPointerPhase::kUp
: FlutterPointerPhase::kHover
: state.flutter_state_is_down
? FlutterPointerPhase::kMove
: FlutterPointerPhase::kDown;
}
void Win32FlutterWindow::SendPointerMove(double x, double y) {
FlutterPointerEvent event = {};
event.x = x;
event.y = y;
SetEventPhaseFromCursorButtonState(&event);
SendPointerEventWithData(event);
}
void Win32FlutterWindow::SendPointerDown(double x, double y) {
FlutterPointerEvent event = {};
SetEventPhaseFromCursorButtonState(&event);
event.x = x;
event.y = y;
SendPointerEventWithData(event);
SetMouseFlutterStateDown(true);
}
void Win32FlutterWindow::SendPointerUp(double x, double y) {
FlutterPointerEvent event = {};
SetEventPhaseFromCursorButtonState(&event);
event.x = x;
event.y = y;
SendPointerEventWithData(event);
if (event.phase == FlutterPointerPhase::kUp) {
SetMouseFlutterStateDown(false);
}
}
void Win32FlutterWindow::SendPointerLeave() {
FlutterPointerEvent event = {};
event.phase = FlutterPointerPhase::kRemove;
SendPointerEventWithData(event);
}
void Win32FlutterWindow::SendText(const std::u16string& text) {
for (const auto& handler : keyboard_hook_handlers_) {
handler->TextHook(this, text);
}
}
void Win32FlutterWindow::SendKey(int key,
int scancode,
int action,
char32_t character) {
for (const auto& handler : keyboard_hook_handlers_) {
handler->KeyboardHook(this, key, scancode, action, character);
}
}
void Win32FlutterWindow::SendScroll(double delta_x, double delta_y) {
FlutterPointerEvent event = {};
SetEventLocationFromCursorPosition(&event);
SetEventPhaseFromCursorButtonState(&event);
event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll;
// TODO: See if this can be queried from the OS; this value is chosen
// arbitrarily to get something that feels reasonable.
const int kScrollOffsetMultiplier = 20;
event.scroll_delta_x = delta_x * kScrollOffsetMultiplier;
event.scroll_delta_y = delta_y * kScrollOffsetMultiplier;
SendPointerEventWithData(event);
}
void Win32FlutterWindow::SendPointerEventWithData(
const FlutterPointerEvent& event_data) {
MouseState mouse_state = GetMouseState();
// If sending anything other than an add, and the pointer isn't already added,
// synthesize an add to satisfy Flutter's expectations about events.
if (!mouse_state.flutter_state_is_added &&
event_data.phase != FlutterPointerPhase::kAdd) {
FlutterPointerEvent event = {};
event.phase = FlutterPointerPhase::kAdd;
event.x = event_data.x;
event.y = event_data.y;
event.buttons = 0;
SendPointerEventWithData(event);
}
// Don't double-add (e.g., if events are delivered out of order, so an add has
// already been synthesized).
if (mouse_state.flutter_state_is_added &&
event_data.phase == FlutterPointerPhase::kAdd) {
return;
}
FlutterPointerEvent event = event_data;
event.device_kind = kFlutterPointerDeviceKindMouse;
event.buttons = mouse_state.buttons;
// Set metadata that's always the same regardless of the event.
event.struct_size = sizeof(event);
event.timestamp =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
FlutterEngineSendPointerEvent(engine_, &event, 1);
if (event_data.phase == FlutterPointerPhase::kAdd) {
SetMouseFlutterStateAdded(true);
} else if (event_data.phase == FlutterPointerPhase::kRemove) {
SetMouseFlutterStateAdded(false);
ResetMouseState();
}
}
bool Win32FlutterWindow::MakeCurrent() {
return surface_manager->MakeCurrent(render_surface);
}
bool Win32FlutterWindow::MakeResourceCurrent() {
return surface_manager->MakeResourceCurrent();
}
bool Win32FlutterWindow::ClearContext() {
return surface_manager->MakeCurrent(nullptr);
}
bool Win32FlutterWindow::SwapBuffers() {
return surface_manager->SwapBuffers(render_surface);
}
void Win32FlutterWindow::CreateRenderSurface() {
if (surface_manager && render_surface == EGL_NO_SURFACE) {
render_surface = surface_manager->CreateSurface(GetWindowHandle());
}
}
void Win32FlutterWindow::DestroyRenderSurface() {
if (surface_manager) {
surface_manager->DestroySurface(render_surface);
}
render_surface = EGL_NO_SURFACE;
}
} // namespace flutter