Flutter Windows Embedder
flutter_windows_view.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include <chrono>
8 #include <cmath>
9 
10 #include "flutter/common/constants.h"
11 #include "flutter/fml/make_copyable.h"
12 #include "flutter/fml/platform/win/wstring_conversion.h"
13 #include "flutter/fml/synchronization/waitable_event.h"
17 #include "flutter/third_party/accessibility/ax/platform/ax_platform_node_win.h"
18 
19 namespace flutter {
20 
21 namespace {
22 // The windows API sends pressure as a normalized value between 0 and 1024
23 // See
24 // https://learn.microsoft.com/windows/win32/api/winuser/ns-winuser-pointer_pen_info
25 static const int kMaxPenPressure = 1024;
26 
27 // The maximum duration to block the Windows event loop while waiting
28 // for a window resize operation to complete.
29 constexpr std::chrono::milliseconds kWindowResizeTimeout{100};
30 
31 /// Returns true if the surface will be updated as part of the resize process.
32 ///
33 /// This is called on window resize to determine if the platform thread needs
34 /// to be blocked until the frame with the right size has been rendered. It
35 /// should be kept in-sync with how the engine deals with a new surface request
36 /// as seen in `CreateOrUpdateSurface` in `GPUSurfaceGL`.
37 bool SurfaceWillUpdate(size_t cur_width,
38  size_t cur_height,
39  size_t target_width,
40  size_t target_height) {
41  // TODO (https://github.com/flutter/flutter/issues/65061) : Avoid special
42  // handling for zero dimensions.
43  bool non_zero_target_dims = target_height > 0 && target_width > 0;
44  bool not_same_size =
45  (cur_height != target_height) || (cur_width != target_width);
46  return non_zero_target_dims && not_same_size;
47 }
48 
49 /// Update the surface's swap interval to block until the v-blank iff
50 /// the system compositor is disabled.
51 void UpdateVsync(const FlutterWindowsEngine& engine,
52  egl::WindowSurface* surface,
53  bool needs_vsync) {
54  egl::Manager* egl_manager = engine.egl_manager();
55  if (!egl_manager) {
56  return;
57  }
58 
59  auto update_vsync = [egl_manager, surface, needs_vsync]() {
60  if (!surface || !surface->IsValid()) {
61  return;
62  }
63 
64  if (!surface->MakeCurrent()) {
65  FML_LOG(ERROR) << "Unable to make the render surface current to update "
66  "the swap interval";
67  return;
68  }
69 
70  if (!surface->SetVSyncEnabled(needs_vsync)) {
71  FML_LOG(ERROR) << "Unable to update the render surface's swap interval";
72  }
73 
74  if (!egl_manager->render_context()->ClearCurrent()) {
75  FML_LOG(ERROR) << "Unable to clear current surface after updating "
76  "the swap interval";
77  }
78  };
79 
80  // Updating the vsync makes the EGL context and render surface current.
81  // If the engine is running, the render surface should only be made current on
82  // the raster thread. If the engine is initializing, the raster thread doesn't
83  // exist yet and the render surface can be made current on the platform
84  // thread.
85  if (engine.running()) {
86  engine.PostRasterThreadTask(update_vsync);
87  } else {
88  update_vsync();
89  }
90 }
91 
92 /// Destroys a rendering surface that backs a Flutter view.
93 void DestroyWindowSurface(const FlutterWindowsEngine& engine,
94  std::unique_ptr<egl::WindowSurface> surface) {
95  // EGL surfaces are used on the raster thread if the engine is running.
96  // There may be pending raster tasks that use this surface. Destroy the
97  // surface on the raster thread to avoid concurrent uses.
98  if (engine.running()) {
99  engine.PostRasterThreadTask(fml::MakeCopyable(
100  [surface = std::move(surface)] { surface->Destroy(); }));
101  } else {
102  // There's no raster thread if engine isn't running. The surface can be
103  // destroyed on the platform thread.
104  surface->Destroy();
105  }
106 }
107 
108 } // namespace
109 
111  FlutterViewId view_id,
112  FlutterWindowsEngine* engine,
113  std::unique_ptr<WindowBindingHandler> window_binding,
114  bool is_sized_to_content,
115  const BoxConstraints& box_constraints,
116  FlutterWindowsViewSizingDelegate* sizing_delegate,
117  std::shared_ptr<WindowsProcTable> windows_proc_table)
118  : view_id_(view_id),
119  engine_(engine),
120  is_sized_to_content_(is_sized_to_content),
121  box_constraints_(box_constraints),
122  sizing_delegate_(sizing_delegate),
123  windows_proc_table_(std::move(windows_proc_table)) {
124  if (windows_proc_table_ == nullptr) {
125  windows_proc_table_ = std::make_shared<WindowsProcTable>();
126  }
127 
128  // Take the binding handler, and give it a pointer back to self.
129  binding_handler_ = std::move(window_binding);
130  binding_handler_->SetView(this);
131 }
132 
134  // The view owns the child window.
135  // Notify the engine the view's child window will no longer be visible.
137 
138  if (surface_) {
139  DestroyWindowSurface(*engine_, std::move(surface_));
140  }
141 }
142 
144  // Called on the raster thread.
145  std::unique_lock<std::mutex> lock(resize_mutex_);
146 
147  if (surface_ == nullptr || !surface_->IsValid()) {
148  return false;
149  }
150 
151  if (resize_status_ != ResizeState::kResizeStarted) {
152  return true;
153  }
154 
155  if (!ResizeRenderSurface(resize_target_width_, resize_target_height_)) {
156  return false;
157  }
158 
159  // Platform thread is blocked for the entire duration until the
160  // resize_status_ is set to kDone by |OnFramePresented|.
161  resize_status_ = ResizeState::kFrameGenerated;
162  return true;
163 }
164 
165 bool FlutterWindowsView::OnFrameGenerated(size_t width, size_t height) {
166  // Called on the raster thread.
167  std::unique_lock<std::mutex> lock(resize_mutex_);
168 
169  if (IsSizedToContent()) {
170  if (!ResizeRenderSurface(width, height)) {
171  return false;
172  }
173 
174  sizing_delegate_->DidUpdateViewSize(width, height);
175  return true;
176  }
177 
178  if (surface_ == nullptr || !surface_->IsValid()) {
179  return false;
180  }
181 
182  if (resize_status_ != ResizeState::kResizeStarted) {
183  return true;
184  }
185 
186  if (resize_target_width_ != width || resize_target_height_ != height) {
187  return false;
188  }
189 
190  if (!ResizeRenderSurface(resize_target_width_, resize_target_height_)) {
191  return false;
192  }
193 
194  // Platform thread is blocked for the entire duration until the
195  // resize_status_ is set to kDone by |OnFramePresented|.
196  resize_status_ = ResizeState::kFrameGenerated;
197  return true;
198 }
199 
201  if (resize_status_ == ResizeState::kDone) {
202  // Request new frame.
203  engine_->ScheduleFrame();
204  }
205 }
206 
207 // Called on the platform thread.
208 bool FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) {
209  if (IsSizedToContent()) {
210  // No resize synchronization needed for views sized to content.
211  // Still send metrics so the engine renders at the correct viewport size
212  // (e.g. after the child window is resized to the host's client area).
213  SendWindowMetrics(width, height, binding_handler_->GetDpiScale());
214  return true;
215  }
216 
217  if (!engine_->egl_manager()) {
218  SendWindowMetrics(width, height, binding_handler_->GetDpiScale());
219  return true;
220  }
221 
222  if (!surface_ || !surface_->IsValid()) {
223  SendWindowMetrics(width, height, binding_handler_->GetDpiScale());
224  return true;
225  }
226 
227  // We're using OpenGL rendering. Resizing the surface must happen on the
228  // raster thread.
229  bool surface_will_update =
230  SurfaceWillUpdate(surface_->width(), surface_->height(), width, height);
231  if (!surface_will_update) {
232  SendWindowMetrics(width, height, binding_handler_->GetDpiScale());
233  return true;
234  }
235 
236  {
237  std::unique_lock<std::mutex> lock(resize_mutex_);
238  resize_status_ = ResizeState::kResizeStarted;
239  resize_target_width_ = width;
240  resize_target_height_ = height;
241  }
242 
243  SendWindowMetrics(width, height, binding_handler_->GetDpiScale());
244 
245  std::chrono::time_point<std::chrono::steady_clock> start_time =
246  std::chrono::steady_clock::now();
247 
248  while (true) {
249  if (std::chrono::steady_clock::now() > start_time + kWindowResizeTimeout) {
250  return false;
251  }
252  std::unique_lock<std::mutex> lock(resize_mutex_);
253  if (resize_status_ == ResizeState::kDone) {
254  break;
255  }
256  lock.unlock();
257  engine_->task_runner()->PollOnce(kWindowResizeTimeout);
258  }
259  return true;
260 }
261 
263  ForceRedraw();
264 }
265 
267  double y,
268  FlutterPointerDeviceKind device_kind,
269  int32_t device_id,
270  uint64_t buttons,
271  uint32_t rotation,
272  uint32_t pressure,
273  int modifiers_state) {
274  engine_->keyboard_key_handler()->SyncModifiersIfNeeded(modifiers_state);
275  auto state = GetOrCreatePointerState(device_kind, device_id);
276  state->buttons = buttons;
277  state->rotation = rotation;
278  state->pressure = pressure;
279  SendPointerMove(x, y, state);
280 }
281 
283  double y,
284  FlutterPointerDeviceKind device_kind,
285  int32_t device_id,
286  uint64_t buttons,
287  uint32_t rotation,
288  uint32_t pressure) {
289  if (buttons != 0) {
290  auto state = GetOrCreatePointerState(device_kind, device_id);
291  state->buttons |= buttons;
292  state->rotation = rotation;
293  state->pressure = pressure;
294  SendPointerDown(x, y, state);
295  }
296 }
297 
299  double y,
300  FlutterPointerDeviceKind device_kind,
301  int32_t device_id,
302  uint64_t buttons) {
303  auto state = GetOrCreatePointerState(device_kind, device_id);
304  const uint64_t released_buttons = buttons == 0 ? state->buttons : buttons;
305  if (released_buttons != 0) {
306  state->buttons &= ~released_buttons;
307  SendPointerUp(x, y, state);
308  }
309 }
310 
312  double y,
313  FlutterPointerDeviceKind device_kind,
314  int32_t device_id) {
315  SendPointerLeave(x, y, GetOrCreatePointerState(device_kind, device_id));
316 }
317 
319  PointerLocation point = binding_handler_->GetPrimaryPointerLocation();
320  SendPointerPanZoomStart(device_id, point.x, point.y);
321 }
322 
324  double pan_x,
325  double pan_y,
326  double scale,
327  double rotation) {
328  SendPointerPanZoomUpdate(device_id, pan_x, pan_y, scale, rotation);
329 }
330 
332  SendPointerPanZoomEnd(device_id);
333 }
334 
335 void FlutterWindowsView::OnText(const std::u16string& text) {
336  SendText(text);
337 }
338 
340  int scancode,
341  int action,
342  char32_t character,
343  bool extended,
344  bool was_down,
347 }
348 
349 void FlutterWindowsView::OnFocus(FlutterViewFocusState focus_state,
350  FlutterViewFocusDirection direction) {
351  SendFocus(focus_state, direction);
352 }
353 
355  SendComposeBegin();
356 }
357 
359  SendComposeCommit();
360 }
361 
363  SendComposeEnd();
364 }
365 
366 void FlutterWindowsView::OnComposeChange(const std::u16string& text,
367  int cursor_pos) {
368  SendComposeChange(text, cursor_pos);
369 }
370 
372  double y,
373  double delta_x,
374  double delta_y,
375  int scroll_offset_multiplier,
376  FlutterPointerDeviceKind device_kind,
377  int32_t device_id) {
378  SendScroll(x, y, delta_x, delta_y, scroll_offset_multiplier, device_kind,
379  device_id);
380 }
381 
383  PointerLocation point = binding_handler_->GetPrimaryPointerLocation();
384  SendScrollInertiaCancel(device_id, point.x, point.y);
385 }
386 
388  engine_->UpdateSemanticsEnabled(enabled);
389 }
390 
391 gfx::NativeViewAccessible FlutterWindowsView::GetNativeViewAccessible() {
392  if (!accessibility_bridge_) {
393  return nullptr;
394  }
395 
396  return accessibility_bridge_->GetChildOfAXFragmentRoot();
397 }
398 
400  binding_handler_->OnCursorRectUpdated(rect);
401 }
402 
404  binding_handler_->OnResetImeComposing();
405 }
406 
407 // Sends new size information to FlutterEngine.
408 void FlutterWindowsView::SendWindowMetrics(size_t width,
409  size_t height,
410  double pixel_ratio) const {
411  FlutterEngineDisplayId display_id = binding_handler_->GetDisplayId();
412  FlutterWindowMetricsEvent event = {};
413  event.struct_size = sizeof(event);
414  event.width = width;
415  event.height = height;
416  event.has_constraints = true;
417  auto const constraints = GetConstraints();
418  event.min_width_constraint =
419  static_cast<size_t>(constraints.smallest().width());
420  event.min_height_constraint =
421  static_cast<size_t>(constraints.smallest().height());
422  event.max_width_constraint =
423  static_cast<size_t>(constraints.biggest().width());
424  event.max_height_constraint =
425  static_cast<size_t>(constraints.biggest().height());
426  event.pixel_ratio = pixel_ratio;
427  event.display_id = display_id;
428  event.view_id = view_id_;
429  engine_->SendWindowMetricsEvent(event);
430 }
431 
432 FlutterWindowMetricsEvent FlutterWindowsView::CreateWindowMetricsEvent() const {
433  PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds();
434  double pixel_ratio = binding_handler_->GetDpiScale();
435  FlutterEngineDisplayId display_id = binding_handler_->GetDisplayId();
436 
437  FlutterWindowMetricsEvent event = {};
438  event.struct_size = sizeof(event);
439  event.width = bounds.width;
440  event.height = bounds.height;
441  auto constraints = GetConstraints();
442  event.has_constraints = true;
443  event.min_width_constraint =
444  static_cast<size_t>(constraints.smallest().width());
445  event.min_height_constraint =
446  static_cast<size_t>(constraints.smallest().height());
447  event.max_width_constraint =
448  static_cast<size_t>(constraints.biggest().width());
449  event.max_height_constraint =
450  static_cast<size_t>(constraints.biggest().height());
451  event.pixel_ratio = pixel_ratio;
452  event.display_id = display_id;
453  event.view_id = view_id_;
454 
455  return event;
456 }
457 
459  // Non-implicit views' initial window metrics are sent when the view is added
460  // to the engine.
461  if (!IsImplicitView()) {
462  return;
463  }
464 
466 }
467 
468 FlutterWindowsView::PointerState* FlutterWindowsView::GetOrCreatePointerState(
469  FlutterPointerDeviceKind device_kind,
470  int32_t device_id) {
471  // Create a virtual pointer ID that is unique across all device types
472  // to prevent pointers from clashing in the engine's converter
473  // (lib/ui/window/pointer_data_packet_converter.cc)
474  int32_t pointer_id = (static_cast<int32_t>(device_kind) << 28) | device_id;
475 
476  auto [it, added] = pointer_states_.try_emplace(pointer_id, nullptr);
477  if (added) {
478  auto state = std::make_unique<PointerState>();
479  state->device_kind = device_kind;
480  state->pointer_id = pointer_id;
481  it->second = std::move(state);
482  }
483 
484  return it->second.get();
485 }
486 
487 // Set's |event_data|'s phase to either kMove or kHover depending on the current
488 // primary mouse button state.
489 void FlutterWindowsView::SetEventPhaseFromCursorButtonState(
490  FlutterPointerEvent* event_data,
491  const PointerState* state) const {
492  // For details about this logic, see FlutterPointerPhase in the embedder.h
493  // file.
494  if (state->buttons == 0) {
495  event_data->phase = state->flutter_state_is_down
496  ? FlutterPointerPhase::kUp
497  : FlutterPointerPhase::kHover;
498  } else {
499  event_data->phase = state->flutter_state_is_down
500  ? FlutterPointerPhase::kMove
501  : FlutterPointerPhase::kDown;
502  }
503 }
504 
505 void FlutterWindowsView::SendPointerMove(double x,
506  double y,
507  PointerState* state) {
508  FlutterPointerEvent event = {};
509  event.x = x;
510  event.y = y;
511 
512  SetEventPhaseFromCursorButtonState(&event, state);
513  SendPointerEventWithData(event, state);
514 }
515 
516 void FlutterWindowsView::SendPointerDown(double x,
517  double y,
518  PointerState* state) {
519  FlutterPointerEvent event = {};
520  event.x = x;
521  event.y = y;
522 
523  SetEventPhaseFromCursorButtonState(&event, state);
524  SendPointerEventWithData(event, state);
525 
526  state->flutter_state_is_down = true;
527 }
528 
529 void FlutterWindowsView::SendPointerUp(double x,
530  double y,
531  PointerState* state) {
532  FlutterPointerEvent event = {};
533  event.x = x;
534  event.y = y;
535 
536  SetEventPhaseFromCursorButtonState(&event, state);
537  SendPointerEventWithData(event, state);
538  if (event.phase == FlutterPointerPhase::kUp) {
539  state->flutter_state_is_down = false;
540  }
541 }
542 
543 void FlutterWindowsView::SendPointerLeave(double x,
544  double y,
545  PointerState* state) {
546  FlutterPointerEvent event = {};
547  event.x = x;
548  event.y = y;
549  event.phase = FlutterPointerPhase::kRemove;
550  SendPointerEventWithData(event, state);
551 }
552 
553 void FlutterWindowsView::SendPointerPanZoomStart(int32_t device_id,
554  double x,
555  double y) {
556  auto state =
557  GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id);
558  state->pan_zoom_start_x = x;
559  state->pan_zoom_start_y = y;
560  FlutterPointerEvent event = {};
561  event.x = x;
562  event.y = y;
563  event.phase = FlutterPointerPhase::kPanZoomStart;
564  SendPointerEventWithData(event, state);
565 }
566 
567 void FlutterWindowsView::SendPointerPanZoomUpdate(int32_t device_id,
568  double pan_x,
569  double pan_y,
570  double scale,
571  double rotation) {
572  auto state =
573  GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id);
574  FlutterPointerEvent event = {};
575  event.x = state->pan_zoom_start_x;
576  event.y = state->pan_zoom_start_y;
577  event.pan_x = pan_x;
578  event.pan_y = pan_y;
579  event.scale = scale;
580  event.rotation = rotation;
581  event.phase = FlutterPointerPhase::kPanZoomUpdate;
582  SendPointerEventWithData(event, state);
583 }
584 
585 void FlutterWindowsView::SendPointerPanZoomEnd(int32_t device_id) {
586  auto state =
587  GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id);
588  FlutterPointerEvent event = {};
589  event.x = state->pan_zoom_start_x;
590  event.y = state->pan_zoom_start_y;
591  event.phase = FlutterPointerPhase::kPanZoomEnd;
592  SendPointerEventWithData(event, state);
593 }
594 
595 void FlutterWindowsView::SendText(const std::u16string& text) {
596  engine_->text_input_plugin()->TextHook(text);
597 }
598 
599 void FlutterWindowsView::SendKey(int key,
600  int scancode,
601  int action,
602  char32_t character,
603  bool extended,
604  bool was_down,
605  KeyEventCallback callback) {
608  [engine = engine_, view_id = view_id_, key, scancode, action, character,
609  extended, was_down, callback = std::move(callback)](bool handled) {
610  if (!handled) {
611  engine->text_input_plugin()->KeyboardHook(
613  }
614  if (engine->view(view_id)) {
615  callback(handled);
616  }
617  });
618 }
619 
620 void FlutterWindowsView::SendFocus(FlutterViewFocusState focus_state,
621  FlutterViewFocusDirection direction) {
622  FlutterViewFocusEvent event = {};
623  event.struct_size = sizeof(event);
624  event.view_id = view_id_;
625  event.state = focus_state;
626  event.direction = direction;
627  engine_->SendViewFocusEvent(event);
628 }
629 
630 void FlutterWindowsView::SendComposeBegin() {
631  engine_->text_input_plugin()->ComposeBeginHook();
632 }
633 
634 void FlutterWindowsView::SendComposeCommit() {
635  engine_->text_input_plugin()->ComposeCommitHook();
636 }
637 
638 void FlutterWindowsView::SendComposeEnd() {
639  engine_->text_input_plugin()->ComposeEndHook();
640 }
641 
642 void FlutterWindowsView::SendComposeChange(const std::u16string& text,
643  int cursor_pos) {
644  engine_->text_input_plugin()->ComposeChangeHook(text, cursor_pos);
645 }
646 
647 void FlutterWindowsView::SendScroll(double x,
648  double y,
649  double delta_x,
650  double delta_y,
651  int scroll_offset_multiplier,
652  FlutterPointerDeviceKind device_kind,
653  int32_t device_id) {
654  auto state = GetOrCreatePointerState(device_kind, device_id);
655 
656  FlutterPointerEvent event = {};
657  event.x = x;
658  event.y = y;
659  event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll;
660  event.scroll_delta_x = delta_x * scroll_offset_multiplier;
661  event.scroll_delta_y = delta_y * scroll_offset_multiplier;
662  SetEventPhaseFromCursorButtonState(&event, state);
663  SendPointerEventWithData(event, state);
664 }
665 
666 void FlutterWindowsView::SendScrollInertiaCancel(int32_t device_id,
667  double x,
668  double y) {
669  auto state =
670  GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id);
671 
672  FlutterPointerEvent event = {};
673  event.x = x;
674  event.y = y;
675  event.signal_kind =
676  FlutterPointerSignalKind::kFlutterPointerSignalKindScrollInertiaCancel;
677  SetEventPhaseFromCursorButtonState(&event, state);
678  SendPointerEventWithData(event, state);
679 }
680 
681 void FlutterWindowsView::SendPointerEventWithData(
682  const FlutterPointerEvent& event_data,
683  PointerState* state) {
684  // If sending anything other than an add, and the pointer isn't already added,
685  // synthesize an add to satisfy Flutter's expectations about events.
686  if (!state->flutter_state_is_added &&
687  event_data.phase != FlutterPointerPhase::kAdd) {
688  FlutterPointerEvent event = {};
689  event.phase = FlutterPointerPhase::kAdd;
690  event.x = event_data.x;
691  event.y = event_data.y;
692  event.buttons = 0;
693  SendPointerEventWithData(event, state);
694  }
695 
696  // Don't double-add (e.g., if events are delivered out of order, so an add has
697  // already been synthesized).
698  if (state->flutter_state_is_added &&
699  event_data.phase == FlutterPointerPhase::kAdd) {
700  return;
701  }
702 
703  FlutterPointerEvent event = event_data;
704  event.device_kind = state->device_kind;
705  event.device = state->pointer_id;
706  event.buttons = state->buttons;
707  event.view_id = view_id_;
708  event.rotation = (double)state->rotation * (M_PI / 180);
709  event.pressure = state->pressure;
710  // Normalized between 0 and 1024 by the windows API
711  event.pressure_min = 0;
712  event.pressure_max = kMaxPenPressure;
713 
714  // Set metadata that's always the same regardless of the event.
715  event.struct_size = sizeof(event);
716  event.timestamp =
717  std::chrono::duration_cast<std::chrono::microseconds>(
718  std::chrono::high_resolution_clock::now().time_since_epoch())
719  .count();
720 
721  engine_->SendPointerEvent(event);
722 
723  if (event_data.phase == FlutterPointerPhase::kAdd) {
724  state->flutter_state_is_added = true;
725  } else if (event_data.phase == FlutterPointerPhase::kRemove) {
726  auto it = pointer_states_.find(state->pointer_id);
727  if (it != pointer_states_.end()) {
728  pointer_states_.erase(it);
729  }
730  }
731 }
732 
734  std::scoped_lock lock(first_frame_callback_mutex_);
735  first_frame_callback_ = std::move(callback);
736 }
737 
738 void FlutterWindowsView::FireFirstFrameCallbackIfSet() {
739  std::scoped_lock lock(first_frame_callback_mutex_);
740  if (first_frame_callback_) {
741  fml::closure callback = std::move(first_frame_callback_);
742  first_frame_callback_ = nullptr;
743  engine_->task_runner()->PostTask(std::move(callback));
744  }
745 }
746 
748  // Called on the engine's raster thread.
749  FireFirstFrameCallbackIfSet();
750 
751  std::unique_lock<std::mutex> lock(resize_mutex_);
752 
753  switch (resize_status_) {
754  case ResizeState::kResizeStarted:
755  // The caller must first call |OnFrameGenerated| or
756  // |OnEmptyFrameGenerated| before calling this method. This
757  // indicates one of the following:
758  //
759  // 1. The caller did not call these methods.
760  // 2. The caller ignored these methods' result.
761  // 3. The platform thread started a resize after the caller called these
762  // methods. We might have presented a frame of the wrong size to the
763  // view.
764  return;
765  case ResizeState::kFrameGenerated: {
766  // A frame was generated for a pending resize.
767  resize_status_ = ResizeState::kDone;
768  // Unblock the platform thread.
769  engine_->task_runner()->PostTask([this] {});
770 
771  lock.unlock();
772 
773  // Blocking the raster thread until DWM flushes alleviates glitches where
774  // previous size surface is stretched over current size view.
775  windows_proc_table_->DwmFlush();
776  }
777  case ResizeState::kDone:
778  return;
779  }
780 }
781 
783  return binding_handler_->OnBitmapSurfaceCleared();
784 }
785 
786 bool FlutterWindowsView::PresentSoftwareBitmap(const void* allocation,
787  size_t row_bytes,
788  size_t height) {
789  bool result =
790  binding_handler_->OnBitmapSurfaceUpdated(allocation, row_bytes, height);
791  if (result) {
792  // The software compositor does not call OnFramePresented, so fire the
793  // first frame callback here.
794  FireFirstFrameCallbackIfSet();
795  }
796  return result;
797 }
798 
800  return view_id_;
801 }
802 
804  return view_id_ == kImplicitViewId;
805 }
806 
808  FML_DCHECK(surface_ == nullptr);
809 
810  if (engine_->egl_manager()) {
811  PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds();
812  surface_ = engine_->egl_manager()->CreateWindowSurface(
813  GetWindowHandle(), bounds.width, bounds.height);
814 
815  UpdateVsync(*engine_, surface_.get(), NeedsVsync());
816 
817  resize_target_width_ = bounds.width;
818  resize_target_height_ = bounds.height;
819  }
820 }
821 
822 bool FlutterWindowsView::ResizeRenderSurface(size_t width, size_t height) {
823  FML_DCHECK(surface_ != nullptr);
824 
825  // No-op if the surface is already the desired size.
826  if (width == surface_->width() && height == surface_->height()) {
827  return true;
828  }
829 
830  auto const existing_vsync = surface_->vsync_enabled();
831 
832  // TODO: Destroying the surface and re-creating it is expensive.
833  // Ideally this would use ANGLE's automatic surface sizing instead.
834  // See: https://github.com/flutter/flutter/issues/79427
835  if (!surface_->Destroy()) {
836  FML_LOG(ERROR) << "View resize failed to destroy surface";
837  return false;
838  }
839 
840  std::unique_ptr<egl::WindowSurface> resized_surface =
841  engine_->egl_manager()->CreateWindowSurface(GetWindowHandle(), width,
842  height);
843  if (!resized_surface) {
844  FML_LOG(ERROR) << "View resize failed to create surface";
845  return false;
846  }
847 
848  if (!resized_surface->MakeCurrent() ||
849  !resized_surface->SetVSyncEnabled(existing_vsync)) {
850  // Surfaces block until the v-blank by default.
851  // Failing to update the vsync might result in unnecessary blocking.
852  // This regresses performance but not correctness.
853  FML_LOG(ERROR) << "View resize failed to set vsync";
854  }
855 
856  surface_ = std::move(resized_surface);
857  return true;
858 }
859 
861  return surface_.get();
862 }
863 
865  engine_->UpdateHighContrastMode();
866 }
867 
869  return binding_handler_->GetWindowHandle();
870 }
871 
873  return engine_;
874 }
875 
876 void FlutterWindowsView::AnnounceAlert(const std::wstring& text) {
877  auto alert_delegate = binding_handler_->GetAlertDelegate();
878  if (!alert_delegate) {
879  return;
880  }
881  alert_delegate->SetText(fml::WideStringToUtf16(text));
882  ui::AXPlatformNodeWin* alert_node = binding_handler_->GetAlert();
883  NotifyWinEventWrapper(alert_node, ax::mojom::Event::kAlert);
884 }
885 
886 void FlutterWindowsView::NotifyWinEventWrapper(ui::AXPlatformNodeWin* node,
887  ax::mojom::Event event) {
888  if (node) {
889  node->NotifyAccessibilityEvent(event);
890  }
891 }
892 
893 ui::AXFragmentRootDelegateWin* FlutterWindowsView::GetAxFragmentRootDelegate() {
894  return accessibility_bridge_.get();
895 }
896 
897 ui::AXPlatformNodeWin* FlutterWindowsView::AlertNode() const {
898  return binding_handler_->GetAlert();
899 }
900 
901 std::shared_ptr<AccessibilityBridgeWindows>
903  return std::make_shared<AccessibilityBridgeWindows>(this);
904 }
905 
907  if (semantics_enabled_ != enabled) {
908  semantics_enabled_ = enabled;
909 
910  if (!semantics_enabled_ && accessibility_bridge_) {
911  accessibility_bridge_.reset();
912  } else if (semantics_enabled_ && !accessibility_bridge_) {
913  accessibility_bridge_ = CreateAccessibilityBridge();
914  }
915  }
916 }
917 
919  UpdateVsync(*engine_, surface_.get(), NeedsVsync());
920 }
921 
923  engine_->OnWindowStateEvent(hwnd, event);
924 }
925 
927  return binding_handler_->Focus();
928 }
929 
930 bool FlutterWindowsView::NeedsVsync() const {
931  // If the Desktop Window Manager composition is enabled,
932  // the system itself synchronizes with vsync.
933  // See: https://learn.microsoft.com/windows/win32/dwm/composition-ovw
934  return !windows_proc_table_->DwmIsCompositionEnabled();
935 }
936 
938  return is_sized_to_content_;
939 }
940 
941 void FlutterWindowsView::SetSizedToContent(bool sized_to_content) {
942  is_sized_to_content_ = sized_to_content;
944 }
945 
946 BoxConstraints FlutterWindowsView::GetConstraints() const {
947  if (!is_sized_to_content_) {
948  PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds();
949  return BoxConstraints(Size(bounds.width, bounds.height),
950  Size(bounds.width, bounds.height));
951  }
952 
953  Size smallest = box_constraints_.smallest();
954  Size biggest = box_constraints_.biggest();
955  if (sizing_delegate_) {
956  auto const work_area = sizing_delegate_->GetWorkArea();
957  double const width = std::min(static_cast<double>(work_area.width),
958  box_constraints_.biggest().width());
959  double const height = std::min(static_cast<double>(work_area.height),
960  box_constraints_.biggest().height());
961  biggest = Size(width, height);
962  }
963  return BoxConstraints(smallest, biggest);
964 }
965 
966 } // namespace flutter
void OnWindowStateEvent(HWND hwnd, WindowStateEvent event)
void SendViewFocusEvent(const FlutterViewFocusEvent &event)
KeyboardHandlerBase * keyboard_key_handler()
void SendPointerEvent(const FlutterPointerEvent &event)
void SendWindowMetricsEvent(const FlutterWindowMetricsEvent &event)
virtual void OnPointerPanZoomStart(int32_t device_id) override
virtual void UpdateSemanticsEnabled(bool enabled)
virtual ui::AXFragmentRootDelegateWin * GetAxFragmentRootDelegate() override
void OnScrollInertiaCancel(int32_t device_id) override
virtual std::shared_ptr< AccessibilityBridgeWindows > CreateAccessibilityBridge()
void SetSizedToContent(bool sized_to_content)
ui::AXPlatformNodeWin * AlertNode() const
void OnPointerDown(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, uint64_t buttons, uint32_t rotation, uint32_t pressure) override
virtual void OnUpdateSemanticsEnabled(bool enabled) override
void OnPointerLeave(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id=0) override
virtual void NotifyWinEventWrapper(ui::AXPlatformNodeWin *node, ax::mojom::Event event)
void SetFirstFrameCallback(fml::closure callback)
FlutterWindowsEngine * GetEngine() const
void OnScroll(double x, double y, double delta_x, double delta_y, int scroll_offset_multiplier, FlutterPointerDeviceKind device_kind, int32_t device_id) override
void OnWindowStateEvent(HWND hwnd, WindowStateEvent event) override
virtual void OnPointerPanZoomEnd(int32_t device_id) override
void OnPointerMove(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, uint64_t buttons, uint32_t rotation, uint32_t pressure, int modifiers_state) override
FlutterWindowMetricsEvent CreateWindowMetricsEvent() const
FlutterWindowsView(FlutterViewId view_id, FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > window_binding, bool is_sized_to_content, const BoxConstraints &box_constraints, FlutterWindowsViewSizingDelegate *sizing_delegate=nullptr, std::shared_ptr< WindowsProcTable > windows_proc_table=nullptr)
virtual void AnnounceAlert(const std::wstring &text)
void OnPointerUp(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, uint64_t buttons) override
virtual bool PresentSoftwareBitmap(const void *allocation, size_t row_bytes, size_t height)
void OnFocus(FlutterViewFocusState focus_state, FlutterViewFocusDirection direction) override
virtual HWND GetWindowHandle() const
egl::WindowSurface * surface() const
bool OnWindowSizeChanged(size_t width, size_t height) override
void OnText(const std::u16string &) override
virtual void OnPointerPanZoomUpdate(int32_t device_id, double pan_x, double pan_y, double scale, double rotation) override
void OnComposeChange(const std::u16string &text, int cursor_pos) override
virtual gfx::NativeViewAccessible GetNativeViewAccessible() override
virtual void OnCursorRectUpdated(const Rect &rect)
void OnKey(int key, int scancode, int action, char32_t character, bool extended, bool was_down, KeyEventCallback callback) override
bool OnFrameGenerated(size_t width, size_t height)
virtual WindowRect GetWorkArea() const =0
virtual void DidUpdateViewSize(int32_t width, int32_t height)=0
virtual void SyncModifiersIfNeeded(int modifiers_state)=0
virtual void KeyboardHook(int key, int scancode, int action, char32_t character, bool extended, bool was_down, KeyEventCallback callback)=0
void PollOnce(std::chrono::milliseconds timeout)
Definition: task_runner.cc:96
void PostTask(TaskClosure task)
Definition: task_runner.cc:89
virtual void ComposeChangeHook(const std::u16string &text, int cursor_pos)
virtual void TextHook(const std::u16string &text)
virtual std::unique_ptr< WindowSurface > CreateWindowSurface(HWND hwnd, size_t width, size_t height)
Definition: manager.cc:283
FlutterDesktopBinaryReply callback
std::u16string text
WindowStateEvent
An event representing a change in window state that may update the.
int64_t FlutterViewId
constexpr FlutterViewId kImplicitViewId