#include "pch.h" #include "ServoControl.h" #include "ServoControl.g.cpp" #include using namespace std::placeholders; using namespace winrt::Windows::Graphics::Display; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::System; using namespace concurrency; using namespace winrt::servo; namespace winrt::ServoApp::implementation { ServoControl::ServoControl() { mDPI = (float)DisplayInformation::GetForCurrentView().ResolutionScale() / 100; DefaultStyleKey(winrt::box_value(L"ServoApp.ServoControl")); Loaded(std::bind(&ServoControl::OnLoaded, this, _1, _2)); } void ServoControl::Shutdown() { if (mServo != nullptr) { if (!mLooping) { // FIXME: this should not happen. In that case, we can't send the // shutdown event to Servo. } else { RunOnGLThread([=] { mServo->RequestShutdown(); }); mLoopTask->wait(); mLoopTask.reset(); mServo.reset(); } } } void ServoControl::OnLoaded(IInspectable const &, RoutedEventArgs const &) { auto panel = Panel(); panel.Tapped(std::bind(&ServoControl::OnSurfaceTapped, this, _1, _2)); panel.PointerPressed( std::bind(&ServoControl::OnSurfacePointerPressed, this, _1, _2)); panel.PointerReleased( std::bind(&ServoControl::OnSurfacePointerPressed, this, _1, _2)); panel.PointerCanceled( std::bind(&ServoControl::OnSurfacePointerCanceled, this, _1, _2)); panel.PointerCaptureLost( std::bind(&ServoControl::OnSurfacePointerCanceled, this, _1, _2)); panel.PointerMoved( std::bind(&ServoControl::OnSurfacePointerMoved, this, _1, _2)); panel.PointerWheelChanged( std::bind(&ServoControl::OnSurfaceWheelChanged, this, _1, _2)); panel.ManipulationStarted( [=](IInspectable const &, Input::ManipulationStartedRoutedEventArgs const &e) { mOnCaptureGesturesStartedEvent(); e.Handled(true); }); panel.ManipulationCompleted( [=](IInspectable const &, Input::ManipulationCompletedRoutedEventArgs const &e) { mOnCaptureGesturesEndedEvent(); e.Handled(true); }); panel.ManipulationDelta( std::bind(&ServoControl::OnSurfaceManipulationDelta, this, _1, _2)); Panel().SizeChanged(std::bind(&ServoControl::OnSurfaceResized, this, _1, _2)); InitializeConditionVariable(&mGLCondVar); InitializeCriticalSection(&mGLLock); CreateRenderSurface(); StartRenderLoop(); } Controls::SwapChainPanel ServoControl::Panel() { return GetTemplateChild(L"swapChainPanel").as(); } void ServoControl::CreateRenderSurface() { if (mRenderSurface == EGL_NO_SURFACE) { mRenderSurface = mOpenGLES.CreateSurface(Panel(), mDPI); } } void ServoControl::DestroyRenderSurface() { mOpenGLES.DestroySurface(mRenderSurface); mRenderSurface = EGL_NO_SURFACE; } void ServoControl::RecoverFromLostDevice() { StopRenderLoop(); DestroyRenderSurface(); mOpenGLES.Reset(); CreateRenderSurface(); StartRenderLoop(); } void ServoControl::OnSurfaceManipulationDelta( IInspectable const &, Input::ManipulationDeltaRoutedEventArgs const &e) { auto x = e.Position().X * mDPI; auto y = e.Position().Y * mDPI; auto dx = e.Delta().Translation.X * mDPI; auto dy = e.Delta().Translation.Y * mDPI; RunOnGLThread([=] { mServo->Scroll(dx, dy, x, y); }); e.Handled(true); } void ServoControl::OnSurfaceTapped(IInspectable const &, Input::TappedRoutedEventArgs const &e) { auto coords = e.GetPosition(Panel()); auto x = coords.X * mDPI; auto y = coords.Y * mDPI; RunOnGLThread([=] { mServo->Click(x, y); }); e.Handled(true); } void ServoControl::OnSurfacePointerPressed( IInspectable const &, Input::PointerRoutedEventArgs const &e) { if (e.Pointer().PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse) { auto point = e.GetCurrentPoint(Panel()); auto x = point.Position().X * mDPI; auto y = point.Position().Y * mDPI; auto props = point.Properties(); std::optional button = {}; if (props.IsLeftButtonPressed()) { button = Servo::MouseButton::Left; } else if (props.IsRightButtonPressed()) { button = Servo::MouseButton::Right; } else if (props.IsMiddleButtonPressed()) { button = Servo::MouseButton::Middle; } if (!button.has_value() && mPressedMouseButton.has_value()) { auto releasedButton = *mPressedMouseButton; mPressedMouseButton = {}; RunOnGLThread([=] { mServo->MouseUp(x, y, releasedButton); }); e.Handled(true); } if (button.has_value()) { RunOnGLThread([=] { mServo->MouseDown(x, y, *button); }); e.Handled(true); } mPressedMouseButton = button; } } void ServoControl::OnSurfacePointerCanceled( IInspectable const &, Input::PointerRoutedEventArgs const &e) { mPressedMouseButton = {}; } void ServoControl::OnSurfacePointerMoved( IInspectable const &, Input::PointerRoutedEventArgs const &e) { if (e.Pointer().PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse) { auto point = e.GetCurrentPoint(Panel()); auto x = point.Position().X * mDPI; auto y = point.Position().Y * mDPI; e.Handled(true); RunOnGLThread([=] { mServo->MouseMove(x, y); }); } } void ServoControl::OnSurfaceWheelChanged( IInspectable const &, Input::PointerRoutedEventArgs const &e) { if (e.Pointer().PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse) { auto point = e.GetCurrentPoint(Panel()); auto x = point.Position().X * mDPI; auto y = point.Position().Y * mDPI; auto delta = point.Properties().MouseWheelDelta() * mDPI; RunOnGLThread([=] { mServo->Scroll(0, (float)delta, x, y); }); } } void ServoControl::OnSurfaceResized(IInspectable const &, SizeChangedEventArgs const &e) { auto size = e.NewSize(); auto w = size.Width * mDPI; auto h = size.Height * mDPI; RunOnGLThread([=] { mServo->SetSize(w, h); }); } void ServoControl::GoBack() { RunOnGLThread([=] { mServo->GoBack(); }); } void ServoControl::GoForward() { RunOnGLThread([=] { mServo->GoForward(); }); } void ServoControl::Reload() { RunOnGLThread([=] { mServo->Reload(); }); } void ServoControl::Stop() { RunOnGLThread([=] { mServo->Stop(); }); } hstring ServoControl::LoadURIOrSearch(hstring input) { // Initial input is valid if (mServo->IsUriValid(input)) { TryLoadUri(input); return input; } // Not valid. Maybe it's just missing the scheme. hstring with_scheme = L"https://" + input; // If the user only types "mozilla" we don't want to go to // https://mozilla even though it's a valid url. bool has_dot = wcsstr(input.c_str(), L".") != nullptr; if (mServo->IsUriValid(with_scheme) && has_dot) { TryLoadUri(with_scheme); return with_scheme; } // Doesn't look like a URI. Let's search for the string. hstring searchUri = L"https://duckduckgo.com/html/?q=" + Uri::EscapeComponent(input); TryLoadUri(searchUri); return searchUri; } void ServoControl::TryLoadUri(hstring input) { if (!mLooping) { mInitialURL = input; } else { RunOnGLThread([=] { if (!mServo->LoadUri(input)) { RunOnUIThread([=] { Windows::UI::Popups::MessageDialog msg{L"URI not valid"}; msg.ShowAsync(); }); } }); } } void ServoControl::RunOnGLThread(std::function task) { EnterCriticalSection(&mGLLock); mTasks.push_back(task); LeaveCriticalSection(&mGLLock); WakeConditionVariable(&mGLCondVar); } /**** GL THREAD LOOP ****/ void ServoControl::Loop() { log("BrowserPage::Loop(). GL thread: %i", GetCurrentThreadId()); mOpenGLES.MakeCurrent(mRenderSurface); EGLint panelWidth = 0; EGLint panelHeight = 0; mOpenGLES.GetSurfaceDimensions(mRenderSurface, &panelWidth, &panelHeight); glViewport(0, 0, panelWidth, panelHeight); if (mServo == nullptr) { log("Entering loop"); ServoDelegate *sd = static_cast(this); mServo = std::make_unique(mInitialURL, mArgs, panelWidth, panelHeight, mDPI, *sd); } else { // FIXME: this will fail since create_task didn't pick the thread // where Servo was running initially. throw winrt::hresult_error(E_FAIL, L"Recovering loop unimplemented"); } mServo->SetBatchMode(true); while (true) { EnterCriticalSection(&mGLLock); while (mTasks.size() == 0 && !mAnimating && mLooping) { SleepConditionVariableCS(&mGLCondVar, &mGLLock, INFINITE); } if (!mLooping) { LeaveCriticalSection(&mGLLock); break; } for (auto &&task : mTasks) { task(); } mTasks.clear(); LeaveCriticalSection(&mGLLock); mServo->PerformUpdates(); } mServo->DeInit(); } void ServoControl::StartRenderLoop() { if (mLooping) { #if defined _DEBUG throw winrt::hresult_error(E_FAIL, L"GL thread is already looping"); #else return; #endif } mLooping = true; log("BrowserPage::StartRenderLoop(). UI thread: %i", GetCurrentThreadId()); auto task = Concurrency::create_task([=] { Loop(); }); mLoopTask = std::make_unique>(task); } void ServoControl::StopRenderLoop() { if (mLooping) { EnterCriticalSection(&mGLLock); mLooping = false; LeaveCriticalSection(&mGLLock); WakeConditionVariable(&mGLCondVar); mLoopTask->wait(); mLoopTask.reset(); } } /**** SERVO CALLBACKS ****/ void ServoControl::OnServoLoadStarted() { RunOnUIThread([=] { mOnLoadStartedEvent(); }); } void ServoControl::OnServoLoadEnded() { RunOnUIThread([=] { mOnLoadEndedEvent(); }); } void ServoControl::OnServoHistoryChanged(bool back, bool forward) { RunOnUIThread([=] { mOnHistoryChangedEvent(back, forward); }); } void ServoControl::OnServoShutdownComplete() { EnterCriticalSection(&mGLLock); mLooping = false; LeaveCriticalSection(&mGLLock); } void ServoControl::OnServoAlert(hstring message) { // FIXME: make this sync RunOnUIThread([=] { Windows::UI::Popups::MessageDialog msg{message}; msg.ShowAsync(); }); } void ServoControl::OnServoTitleChanged(hstring title) { RunOnUIThread([=] { mOnTitleChangedEvent(*this, title); }); } void ServoControl::OnServoURLChanged(hstring url) { RunOnUIThread([=] { mOnURLChangedEvent(*this, url); }); } void ServoControl::Flush() { if (mOpenGLES.SwapBuffers(mRenderSurface) != GL_TRUE) { // The call to eglSwapBuffers might not be successful (i.e. due to Device // Lost) If the call fails, then we must reinitialize EGL and the GL // resources. RunOnUIThread([=] { RecoverFromLostDevice(); }); } } void ServoControl::MakeCurrent() { mOpenGLES.MakeCurrent(mRenderSurface); } void ServoControl::WakeUp() { RunOnGLThread([=] {}); } bool ServoControl::OnServoAllowNavigation(hstring uri) { if (mTransient) { RunOnUIThread([=] { Launcher::LaunchUriAsync(Uri{uri}); }); } return !mTransient; } void ServoControl::OnServoAnimatingChanged(bool animating) { EnterCriticalSection(&mGLLock); mAnimating = animating; LeaveCriticalSection(&mGLLock); WakeConditionVariable(&mGLCondVar); } void ServoControl::OnServoIMEStateChanged(bool aShow) { // FIXME: // https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-implementingtextandtextrange } template void ServoControl::RunOnUIThread(Callable cb) { Dispatcher().RunAsync(CoreDispatcherPriority::High, cb); } } // namespace winrt::ServoApp::implementation