| // 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/flutter_windows_view.h" | |
| #include <chrono> | |
| #include "flutter/common/constants.h" | |
| #include "flutter/fml/make_copyable.h" | |
| #include "flutter/fml/platform/win/wstring_conversion.h" | |
| #include "flutter/fml/synchronization/waitable_event.h" | |
| #include "flutter/shell/platform/common/accessibility_bridge.h" | |
| #include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" | |
| #include "flutter/shell/platform/windows/text_input_plugin.h" | |
| #include "flutter/third_party/accessibility/ax/platform/ax_platform_node_win.h" | |
| namespace flutter { | |
| namespace { | |
| // The maximum duration to block the platform thread for while waiting | |
| // for a window resize operation to complete. | |
| constexpr std::chrono::milliseconds kWindowResizeTimeout{100}; | |
| /// Returns true if the surface will be updated as part of the resize process. | |
| /// | |
| /// This is called on window resize to determine if the platform thread needs | |
| /// to be blocked until the frame with the right size has been rendered. It | |
| /// should be kept in-sync with how the engine deals with a new surface request | |
| /// as seen in `CreateOrUpdateSurface` in `GPUSurfaceGL`. | |
| bool SurfaceWillUpdate(size_t cur_width, | |
| size_t cur_height, | |
| size_t target_width, | |
| size_t target_height) { | |
| // TODO (https://github.com/flutter/flutter/issues/65061) : Avoid special | |
| // handling for zero dimensions. | |
| bool non_zero_target_dims = target_height > 0 && target_width > 0; | |
| bool not_same_size = | |
| (cur_height != target_height) || (cur_width != target_width); | |
| return non_zero_target_dims && not_same_size; | |
| } | |
| /// Update the surface's swap interval to block until the v-blank iff | |
| /// the system compositor is disabled. | |
| void UpdateVsync(const FlutterWindowsEngine& engine, | |
| egl::WindowSurface* surface, | |
| bool needs_vsync) { | |
| egl::Manager* egl_manager = engine.egl_manager(); | |
| if (!egl_manager) { | |
| return; | |
| } | |
| auto update_vsync = [egl_manager, surface, needs_vsync]() { | |
| if (!surface || !surface->IsValid()) { | |
| return; | |
| } | |
| if (!surface->MakeCurrent()) { | |
| FML_LOG(ERROR) << "Unable to make the render surface current to update " | |
| "the swap interval"; | |
| return; | |
| } | |
| if (!surface->SetVSyncEnabled(needs_vsync)) { | |
| FML_LOG(ERROR) << "Unable to update the render surface's swap interval"; | |
| } | |
| if (!egl_manager->render_context()->ClearCurrent()) { | |
| FML_LOG(ERROR) << "Unable to clear current surface after updating " | |
| "the swap interval"; | |
| } | |
| }; | |
| // Updating the vsync makes the EGL context and render surface current. | |
| // If the engine is running, the render surface should only be made current on | |
| // the raster thread. If the engine is initializing, the raster thread doesn't | |
| // exist yet and the render surface can be made current on the platform | |
| // thread. | |
| if (engine.running()) { | |
| engine.PostRasterThreadTask(update_vsync); | |
| } else { | |
| update_vsync(); | |
| } | |
| } | |
| /// Destroys a rendering surface that backs a Flutter view. | |
| void DestroyWindowSurface(const FlutterWindowsEngine& engine, | |
| std::unique_ptr<egl::WindowSurface> surface) { | |
| // EGL surfaces are used on the raster thread if the engine is running. | |
| // There may be pending raster tasks that use this surface. Destroy the | |
| // surface on the raster thread to avoid concurrent uses. | |
| if (engine.running()) { | |
| engine.PostRasterThreadTask(fml::MakeCopyable( | |
| [surface = std::move(surface)] { surface->Destroy(); })); | |
| } else { | |
| // There's no raster thread if engine isn't running. The surface can be | |
| // destroyed on the platform thread. | |
| surface->Destroy(); | |
| } | |
| } | |
| } // namespace | |
| FlutterWindowsView::FlutterWindowsView( | |
| FlutterViewId view_id, | |
| FlutterWindowsEngine* engine, | |
| std::unique_ptr<WindowBindingHandler> window_binding, | |
| std::shared_ptr<WindowsProcTable> windows_proc_table) | |
| : view_id_(view_id), | |
| engine_(engine), | |
| windows_proc_table_(std::move(windows_proc_table)) { | |
| if (windows_proc_table_ == nullptr) { | |
| windows_proc_table_ = std::make_shared<WindowsProcTable>(); | |
| } | |
| // Take the binding handler, and give it a pointer back to self. | |
| binding_handler_ = std::move(window_binding); | |
| binding_handler_->SetView(this); | |
| } | |
| FlutterWindowsView::~FlutterWindowsView() { | |
| // The view owns the child window. | |
| // Notify the engine the view's child window will no longer be visible. | |
| engine_->OnWindowStateEvent(GetWindowHandle(), WindowStateEvent::kHide); | |
| if (surface_) { | |
| DestroyWindowSurface(*engine_, std::move(surface_)); | |
| } | |
| } | |
| bool FlutterWindowsView::OnEmptyFrameGenerated() { | |
| // Called on the raster thread. | |
| std::unique_lock<std::mutex> lock(resize_mutex_); | |
| if (surface_ == nullptr || !surface_->IsValid()) { | |
| return false; | |
| } | |
| if (resize_status_ != ResizeState::kResizeStarted) { | |
| return true; | |
| } | |
| if (!ResizeRenderSurface(resize_target_height_, resize_target_width_)) { | |
| return false; | |
| } | |
| // Platform thread is blocked for the entire duration until the | |
| // resize_status_ is set to kDone by |OnFramePresented|. | |
| resize_status_ = ResizeState::kFrameGenerated; | |
| return true; | |
| } | |
| bool FlutterWindowsView::OnFrameGenerated(size_t width, size_t height) { | |
| // Called on the raster thread. | |
| std::unique_lock<std::mutex> lock(resize_mutex_); | |
| if (surface_ == nullptr || !surface_->IsValid()) { | |
| return false; | |
| } | |
| if (resize_status_ != ResizeState::kResizeStarted) { | |
| return true; | |
| } | |
| if (resize_target_width_ != width || resize_target_height_ != height) { | |
| return false; | |
| } | |
| if (!ResizeRenderSurface(resize_target_width_, resize_target_height_)) { | |
| return false; | |
| } | |
| // Platform thread is blocked for the entire duration until the | |
| // resize_status_ is set to kDone by |OnFramePresented|. | |
| resize_status_ = ResizeState::kFrameGenerated; | |
| return true; | |
| } | |
| void FlutterWindowsView::UpdateFlutterCursor(const std::string& cursor_name) { | |
| binding_handler_->UpdateFlutterCursor(cursor_name); | |
| } | |
| void FlutterWindowsView::SetFlutterCursor(HCURSOR cursor) { | |
| binding_handler_->SetFlutterCursor(cursor); | |
| } | |
| void FlutterWindowsView::ForceRedraw() { | |
| if (resize_status_ == ResizeState::kDone) { | |
| // Request new frame. | |
| engine_->ScheduleFrame(); | |
| } | |
| } | |
| bool FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) { | |
| // Called on the platform thread. | |
| std::unique_lock<std::mutex> lock(resize_mutex_); | |
| if (!engine_->egl_manager()) { | |
| SendWindowMetrics(width, height, binding_handler_->GetDpiScale()); | |
| return true; | |
| } | |
| if (!surface_ || !surface_->IsValid()) { | |
| SendWindowMetrics(width, height, binding_handler_->GetDpiScale()); | |
| return true; | |
| } | |
| // We're using OpenGL rendering. Resizing the surface must happen on the | |
| // raster thread. | |
| bool surface_will_update = | |
| SurfaceWillUpdate(surface_->width(), surface_->height(), width, height); | |
| if (!surface_will_update) { | |
| SendWindowMetrics(width, height, binding_handler_->GetDpiScale()); | |
| return true; | |
| } | |
| resize_status_ = ResizeState::kResizeStarted; | |
| resize_target_width_ = width; | |
| resize_target_height_ = height; | |
| SendWindowMetrics(width, height, binding_handler_->GetDpiScale()); | |
| // Block the platform thread until a frame is presented with the target | |
| // size. See |OnFrameGenerated|, |OnEmptyFrameGenerated|, and | |
| // |OnFramePresented|. | |
| return resize_cv_.wait_for(lock, kWindowResizeTimeout, | |
| [&resize_status = resize_status_] { | |
| return resize_status == ResizeState::kDone; | |
| }); | |
| } | |
| void FlutterWindowsView::OnWindowRepaint() { | |
| ForceRedraw(); | |
| } | |
| void FlutterWindowsView::OnPointerMove(double x, | |
| double y, | |
| FlutterPointerDeviceKind device_kind, | |
| int32_t device_id, | |
| int modifiers_state) { | |
| engine_->keyboard_key_handler()->SyncModifiersIfNeeded(modifiers_state); | |
| SendPointerMove(x, y, GetOrCreatePointerState(device_kind, device_id)); | |
| } | |
| void FlutterWindowsView::OnPointerDown( | |
| double x, | |
| double y, | |
| FlutterPointerDeviceKind device_kind, | |
| int32_t device_id, | |
| FlutterPointerMouseButtons flutter_button) { | |
| if (flutter_button != 0) { | |
| auto state = GetOrCreatePointerState(device_kind, device_id); | |
| state->buttons |= flutter_button; | |
| SendPointerDown(x, y, state); | |
| } | |
| } | |
| void FlutterWindowsView::OnPointerUp( | |
| double x, | |
| double y, | |
| FlutterPointerDeviceKind device_kind, | |
| int32_t device_id, | |
| FlutterPointerMouseButtons flutter_button) { | |
| if (flutter_button != 0) { | |
| auto state = GetOrCreatePointerState(device_kind, device_id); | |
| state->buttons &= ~flutter_button; | |
| SendPointerUp(x, y, state); | |
| } | |
| } | |
| void FlutterWindowsView::OnPointerLeave(double x, | |
| double y, | |
| FlutterPointerDeviceKind device_kind, | |
| int32_t device_id) { | |
| SendPointerLeave(x, y, GetOrCreatePointerState(device_kind, device_id)); | |
| } | |
| void FlutterWindowsView::OnPointerPanZoomStart(int32_t device_id) { | |
| PointerLocation point = binding_handler_->GetPrimaryPointerLocation(); | |
| SendPointerPanZoomStart(device_id, point.x, point.y); | |
| } | |
| void FlutterWindowsView::OnPointerPanZoomUpdate(int32_t device_id, | |
| double pan_x, | |
| double pan_y, | |
| double scale, | |
| double rotation) { | |
| SendPointerPanZoomUpdate(device_id, pan_x, pan_y, scale, rotation); | |
| } | |
| void FlutterWindowsView::OnPointerPanZoomEnd(int32_t device_id) { | |
| SendPointerPanZoomEnd(device_id); | |
| } | |
| void FlutterWindowsView::OnText(const std::u16string& text) { | |
| SendText(text); | |
| } | |
| void FlutterWindowsView::OnKey(int key, | |
| int scancode, | |
| int action, | |
| char32_t character, | |
| bool extended, | |
| bool was_down, | |
| KeyEventCallback callback) { | |
| SendKey(key, scancode, action, character, extended, was_down, callback); | |
| } | |
| void FlutterWindowsView::OnComposeBegin() { | |
| SendComposeBegin(); | |
| } | |
| void FlutterWindowsView::OnComposeCommit() { | |
| SendComposeCommit(); | |
| } | |
| void FlutterWindowsView::OnComposeEnd() { | |
| SendComposeEnd(); | |
| } | |
| void FlutterWindowsView::OnComposeChange(const std::u16string& text, | |
| int cursor_pos) { | |
| SendComposeChange(text, cursor_pos); | |
| } | |
| void FlutterWindowsView::OnScroll(double x, | |
| double y, | |
| double delta_x, | |
| double delta_y, | |
| int scroll_offset_multiplier, | |
| FlutterPointerDeviceKind device_kind, | |
| int32_t device_id) { | |
| SendScroll(x, y, delta_x, delta_y, scroll_offset_multiplier, device_kind, | |
| device_id); | |
| } | |
| void FlutterWindowsView::OnScrollInertiaCancel(int32_t device_id) { | |
| PointerLocation point = binding_handler_->GetPrimaryPointerLocation(); | |
| SendScrollInertiaCancel(device_id, point.x, point.y); | |
| } | |
| void FlutterWindowsView::OnUpdateSemanticsEnabled(bool enabled) { | |
| engine_->UpdateSemanticsEnabled(enabled); | |
| } | |
| gfx::NativeViewAccessible FlutterWindowsView::GetNativeViewAccessible() { | |
| if (!accessibility_bridge_) { | |
| return nullptr; | |
| } | |
| return accessibility_bridge_->GetChildOfAXFragmentRoot(); | |
| } | |
| void FlutterWindowsView::OnCursorRectUpdated(const Rect& rect) { | |
| binding_handler_->OnCursorRectUpdated(rect); | |
| } | |
| void FlutterWindowsView::OnResetImeComposing() { | |
| binding_handler_->OnResetImeComposing(); | |
| } | |
| // Sends new size information to FlutterEngine. | |
| void FlutterWindowsView::SendWindowMetrics(size_t width, | |
| size_t height, | |
| double pixel_ratio) const { | |
| FlutterWindowMetricsEvent event = {}; | |
| event.struct_size = sizeof(event); | |
| event.width = width; | |
| event.height = height; | |
| event.pixel_ratio = pixel_ratio; | |
| event.view_id = view_id_; | |
| engine_->SendWindowMetricsEvent(event); | |
| } | |
| FlutterWindowMetricsEvent FlutterWindowsView::CreateWindowMetricsEvent() const { | |
| PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds(); | |
| double pixel_ratio = binding_handler_->GetDpiScale(); | |
| FlutterWindowMetricsEvent event = {}; | |
| event.struct_size = sizeof(event); | |
| event.width = bounds.width; | |
| event.height = bounds.height; | |
| event.pixel_ratio = pixel_ratio; | |
| event.view_id = view_id_; | |
| return event; | |
| } | |
| void FlutterWindowsView::SendInitialBounds() { | |
| // Non-implicit views' initial window metrics are sent when the view is added | |
| // to the engine. | |
| if (!IsImplicitView()) { | |
| return; | |
| } | |
| engine_->SendWindowMetricsEvent(CreateWindowMetricsEvent()); | |
| } | |
| FlutterWindowsView::PointerState* FlutterWindowsView::GetOrCreatePointerState( | |
| FlutterPointerDeviceKind device_kind, | |
| int32_t device_id) { | |
| // Create a virtual pointer ID that is unique across all device types | |
| // to prevent pointers from clashing in the engine's converter | |
| // (lib/ui/window/pointer_data_packet_converter.cc) | |
| int32_t pointer_id = (static_cast<int32_t>(device_kind) << 28) | device_id; | |
| auto [it, added] = pointer_states_.try_emplace(pointer_id, nullptr); | |
| if (added) { | |
| auto state = std::make_unique<PointerState>(); | |
| state->device_kind = device_kind; | |
| state->pointer_id = pointer_id; | |
| it->second = std::move(state); | |
| } | |
| return it->second.get(); | |
| } | |
| // Set's |event_data|'s phase to either kMove or kHover depending on the current | |
| // primary mouse button state. | |
| void FlutterWindowsView::SetEventPhaseFromCursorButtonState( | |
| FlutterPointerEvent* event_data, | |
| const PointerState* state) const { | |
| // For details about this logic, see FlutterPointerPhase in the embedder.h | |
| // file. | |
| if (state->buttons == 0) { | |
| event_data->phase = state->flutter_state_is_down | |
| ? FlutterPointerPhase::kUp | |
| : FlutterPointerPhase::kHover; | |
| } else { | |
| event_data->phase = state->flutter_state_is_down | |
| ? FlutterPointerPhase::kMove | |
| : FlutterPointerPhase::kDown; | |
| } | |
| } | |
| void FlutterWindowsView::SendPointerMove(double x, | |
| double y, | |
| PointerState* state) { | |
| FlutterPointerEvent event = {}; | |
| event.x = x; | |
| event.y = y; | |
| SetEventPhaseFromCursorButtonState(&event, state); | |
| SendPointerEventWithData(event, state); | |
| } | |
| void FlutterWindowsView::SendPointerDown(double x, | |
| double y, | |
| PointerState* state) { | |
| FlutterPointerEvent event = {}; | |
| event.x = x; | |
| event.y = y; | |
| SetEventPhaseFromCursorButtonState(&event, state); | |
| SendPointerEventWithData(event, state); | |
| state->flutter_state_is_down = true; | |
| } | |
| void FlutterWindowsView::SendPointerUp(double x, | |
| double y, | |
| PointerState* state) { | |
| FlutterPointerEvent event = {}; | |
| event.x = x; | |
| event.y = y; | |
| SetEventPhaseFromCursorButtonState(&event, state); | |
| SendPointerEventWithData(event, state); | |
| if (event.phase == FlutterPointerPhase::kUp) { | |
| state->flutter_state_is_down = false; | |
| } | |
| } | |
| void FlutterWindowsView::SendPointerLeave(double x, | |
| double y, | |
| PointerState* state) { | |
| FlutterPointerEvent event = {}; | |
| event.x = x; | |
| event.y = y; | |
| event.phase = FlutterPointerPhase::kRemove; | |
| SendPointerEventWithData(event, state); | |
| } | |
| void FlutterWindowsView::SendPointerPanZoomStart(int32_t device_id, | |
| double x, | |
| double y) { | |
| auto state = | |
| GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id); | |
| state->pan_zoom_start_x = x; | |
| state->pan_zoom_start_y = y; | |
| FlutterPointerEvent event = {}; | |
| event.x = x; | |
| event.y = y; | |
| event.phase = FlutterPointerPhase::kPanZoomStart; | |
| SendPointerEventWithData(event, state); | |
| } | |
| void FlutterWindowsView::SendPointerPanZoomUpdate(int32_t device_id, | |
| double pan_x, | |
| double pan_y, | |
| double scale, | |
| double rotation) { | |
| auto state = | |
| GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id); | |
| FlutterPointerEvent event = {}; | |
| event.x = state->pan_zoom_start_x; | |
| event.y = state->pan_zoom_start_y; | |
| event.pan_x = pan_x; | |
| event.pan_y = pan_y; | |
| event.scale = scale; | |
| event.rotation = rotation; | |
| event.phase = FlutterPointerPhase::kPanZoomUpdate; | |
| SendPointerEventWithData(event, state); | |
| } | |
| void FlutterWindowsView::SendPointerPanZoomEnd(int32_t device_id) { | |
| auto state = | |
| GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id); | |
| FlutterPointerEvent event = {}; | |
| event.x = state->pan_zoom_start_x; | |
| event.y = state->pan_zoom_start_y; | |
| event.phase = FlutterPointerPhase::kPanZoomEnd; | |
| SendPointerEventWithData(event, state); | |
| } | |
| void FlutterWindowsView::SendText(const std::u16string& text) { | |
| engine_->text_input_plugin()->TextHook(text); | |
| } | |
| void FlutterWindowsView::SendKey(int key, | |
| int scancode, | |
| int action, | |
| char32_t character, | |
| bool extended, | |
| bool was_down, | |
| KeyEventCallback callback) { | |
| engine_->keyboard_key_handler()->KeyboardHook( | |
| key, scancode, action, character, extended, was_down, | |
| [=, callback = std::move(callback)](bool handled) { | |
| if (!handled) { | |
| engine_->text_input_plugin()->KeyboardHook( | |
| key, scancode, action, character, extended, was_down); | |
| } | |
| callback(handled); | |
| }); | |
| } | |
| void FlutterWindowsView::SendComposeBegin() { | |
| engine_->text_input_plugin()->ComposeBeginHook(); | |
| } | |
| void FlutterWindowsView::SendComposeCommit() { | |
| engine_->text_input_plugin()->ComposeCommitHook(); | |
| } | |
| void FlutterWindowsView::SendComposeEnd() { | |
| engine_->text_input_plugin()->ComposeEndHook(); | |
| } | |
| void FlutterWindowsView::SendComposeChange(const std::u16string& text, | |
| int cursor_pos) { | |
| engine_->text_input_plugin()->ComposeChangeHook(text, cursor_pos); | |
| } | |
| void FlutterWindowsView::SendScroll(double x, | |
| double y, | |
| double delta_x, | |
| double delta_y, | |
| int scroll_offset_multiplier, | |
| FlutterPointerDeviceKind device_kind, | |
| int32_t device_id) { | |
| auto state = GetOrCreatePointerState(device_kind, device_id); | |
| FlutterPointerEvent event = {}; | |
| event.x = x; | |
| event.y = y; | |
| event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll; | |
| event.scroll_delta_x = delta_x * scroll_offset_multiplier; | |
| event.scroll_delta_y = delta_y * scroll_offset_multiplier; | |
| SetEventPhaseFromCursorButtonState(&event, state); | |
| SendPointerEventWithData(event, state); | |
| } | |
| void FlutterWindowsView::SendScrollInertiaCancel(int32_t device_id, | |
| double x, | |
| double y) { | |
| auto state = | |
| GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id); | |
| FlutterPointerEvent event = {}; | |
| event.x = x; | |
| event.y = y; | |
| event.signal_kind = | |
| FlutterPointerSignalKind::kFlutterPointerSignalKindScrollInertiaCancel; | |
| SetEventPhaseFromCursorButtonState(&event, state); | |
| SendPointerEventWithData(event, state); | |
| } | |
| void FlutterWindowsView::SendPointerEventWithData( | |
| const FlutterPointerEvent& event_data, | |
| PointerState* state) { | |
| // 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 (!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, state); | |
| } | |
| // Don't double-add (e.g., if events are delivered out of order, so an add has | |
| // already been synthesized). | |
| if (state->flutter_state_is_added && | |
| event_data.phase == FlutterPointerPhase::kAdd) { | |
| return; | |
| } | |
| FlutterPointerEvent event = event_data; | |
| event.device_kind = state->device_kind; | |
| event.device = state->pointer_id; | |
| event.buttons = state->buttons; | |
| event.view_id = view_id_; | |
| // 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(); | |
| engine_->SendPointerEvent(event); | |
| if (event_data.phase == FlutterPointerPhase::kAdd) { | |
| state->flutter_state_is_added = true; | |
| } else if (event_data.phase == FlutterPointerPhase::kRemove) { | |
| auto it = pointer_states_.find(state->pointer_id); | |
| if (it != pointer_states_.end()) { | |
| pointer_states_.erase(it); | |
| } | |
| } | |
| } | |
| void FlutterWindowsView::OnFramePresented() { | |
| // Called on the engine's raster thread. | |
| std::unique_lock<std::mutex> lock(resize_mutex_); | |
| switch (resize_status_) { | |
| case ResizeState::kResizeStarted: | |
| // The caller must first call |OnFrameGenerated| or | |
| // |OnEmptyFrameGenerated| before calling this method. This | |
| // indicates one of the following: | |
| // | |
| // 1. The caller did not call these methods. | |
| // 2. The caller ignored these methods' result. | |
| // 3. The platform thread started a resize after the caller called these | |
| // methods. We might have presented a frame of the wrong size to the | |
| // view. | |
| return; | |
| case ResizeState::kFrameGenerated: { | |
| // A frame was generated for a pending resize. | |
| // Unblock the platform thread. | |
| resize_status_ = ResizeState::kDone; | |
| lock.unlock(); | |
| resize_cv_.notify_all(); | |
| // Blocking the raster thread until DWM flushes alleviates glitches where | |
| // previous size surface is stretched over current size view. | |
| windows_proc_table_->DwmFlush(); | |
| } | |
| case ResizeState::kDone: | |
| return; | |
| } | |
| } | |
| bool FlutterWindowsView::ClearSoftwareBitmap() { | |
| return binding_handler_->OnBitmapSurfaceCleared(); | |
| } | |
| bool FlutterWindowsView::PresentSoftwareBitmap(const void* allocation, | |
| size_t row_bytes, | |
| size_t height) { | |
| return binding_handler_->OnBitmapSurfaceUpdated(allocation, row_bytes, | |
| height); | |
| } | |
| FlutterViewId FlutterWindowsView::view_id() const { | |
| return view_id_; | |
| } | |
| bool FlutterWindowsView::IsImplicitView() const { | |
| return view_id_ == kImplicitViewId; | |
| } | |
| void FlutterWindowsView::CreateRenderSurface() { | |
| FML_DCHECK(surface_ == nullptr); | |
| if (engine_->egl_manager()) { | |
| PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds(); | |
| surface_ = engine_->egl_manager()->CreateWindowSurface( | |
| GetWindowHandle(), bounds.width, bounds.height); | |
| UpdateVsync(*engine_, surface_.get(), NeedsVsync()); | |
| resize_target_width_ = bounds.width; | |
| resize_target_height_ = bounds.height; | |
| } | |
| } | |
| bool FlutterWindowsView::ResizeRenderSurface(size_t width, size_t height) { | |
| FML_DCHECK(surface_ != nullptr); | |
| // No-op if the surface is already the desired size. | |
| if (width == surface_->width() && height == surface_->height()) { | |
| return true; | |
| } | |
| auto const existing_vsync = surface_->vsync_enabled(); | |
| // TODO: Destroying the surface and re-creating it is expensive. | |
| // Ideally this would use ANGLE's automatic surface sizing instead. | |
| // See: https://github.com/flutter/flutter/issues/79427 | |
| if (!surface_->Destroy()) { | |
| FML_LOG(ERROR) << "View resize failed to destroy surface"; | |
| return false; | |
| } | |
| std::unique_ptr<egl::WindowSurface> resized_surface = | |
| engine_->egl_manager()->CreateWindowSurface(GetWindowHandle(), width, | |
| height); | |
| if (!resized_surface) { | |
| FML_LOG(ERROR) << "View resize failed to create surface"; | |
| return false; | |
| } | |
| if (!resized_surface->MakeCurrent() || | |
| !resized_surface->SetVSyncEnabled(existing_vsync)) { | |
| // Surfaces block until the v-blank by default. | |
| // Failing to update the vsync might result in unnecessary blocking. | |
| // This regresses performance but not correctness. | |
| FML_LOG(ERROR) << "View resize failed to set vsync"; | |
| } | |
| surface_ = std::move(resized_surface); | |
| return true; | |
| } | |
| egl::WindowSurface* FlutterWindowsView::surface() const { | |
| return surface_.get(); | |
| } | |
| void FlutterWindowsView::OnHighContrastChanged() { | |
| engine_->UpdateHighContrastMode(); | |
| } | |
| HWND FlutterWindowsView::GetWindowHandle() const { | |
| return binding_handler_->GetWindowHandle(); | |
| } | |
| FlutterWindowsEngine* FlutterWindowsView::GetEngine() const { | |
| return engine_; | |
| } | |
| void FlutterWindowsView::AnnounceAlert(const std::wstring& text) { | |
| auto alert_delegate = binding_handler_->GetAlertDelegate(); | |
| if (!alert_delegate) { | |
| return; | |
| } | |
| alert_delegate->SetText(fml::WideStringToUtf16(text)); | |
| ui::AXPlatformNodeWin* alert_node = binding_handler_->GetAlert(); | |
| NotifyWinEventWrapper(alert_node, ax::mojom::Event::kAlert); | |
| } | |
| void FlutterWindowsView::NotifyWinEventWrapper(ui::AXPlatformNodeWin* node, | |
| ax::mojom::Event event) { | |
| if (node) { | |
| node->NotifyAccessibilityEvent(event); | |
| } | |
| } | |
| ui::AXFragmentRootDelegateWin* FlutterWindowsView::GetAxFragmentRootDelegate() { | |
| return accessibility_bridge_.get(); | |
| } | |
| ui::AXPlatformNodeWin* FlutterWindowsView::AlertNode() const { | |
| return binding_handler_->GetAlert(); | |
| } | |
| std::shared_ptr<AccessibilityBridgeWindows> | |
| FlutterWindowsView::CreateAccessibilityBridge() { | |
| return std::make_shared<AccessibilityBridgeWindows>(this); | |
| } | |
| void FlutterWindowsView::UpdateSemanticsEnabled(bool enabled) { | |
| if (semantics_enabled_ != enabled) { | |
| semantics_enabled_ = enabled; | |
| if (!semantics_enabled_ && accessibility_bridge_) { | |
| accessibility_bridge_.reset(); | |
| } else if (semantics_enabled_ && !accessibility_bridge_) { | |
| accessibility_bridge_ = CreateAccessibilityBridge(); | |
| } | |
| } | |
| } | |
| void FlutterWindowsView::OnDwmCompositionChanged() { | |
| UpdateVsync(*engine_, surface_.get(), NeedsVsync()); | |
| } | |
| void FlutterWindowsView::OnWindowStateEvent(HWND hwnd, WindowStateEvent event) { | |
| engine_->OnWindowStateEvent(hwnd, event); | |
| } | |
| bool FlutterWindowsView::NeedsVsync() const { | |
| // If the Desktop Window Manager composition is enabled, | |
| // the system itself synchronizes with vsync. | |
| // See: https://learn.microsoft.com/windows/win32/dwm/composition-ovw | |
| return !windows_proc_table_->DwmIsCompositionEnabled(); | |
| } | |
| } // namespace flutter |