Skip to main content

servoshell/desktop/
headed_window.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! A winit window implementation.
6
7#![deny(clippy::panic)]
8#![deny(clippy::unwrap_used)]
9
10use std::cell::{Cell, RefCell};
11use std::collections::HashMap;
12use std::rc::Rc;
13
14use euclid::{Angle, Length, Point2D, Rect, Rotation3D, Scale, Size2D, UnknownUnit, Vector3D};
15use keyboard_types::ShortcutMatcher;
16use log::{debug, info};
17use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};
18use servo::{
19    AuthenticationRequest, BluetoothDeviceSelectionRequest, Cursor, DeviceIndependentIntRect,
20    DeviceIndependentPixel, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel, DevicePoint,
21    EmbedderControl, EmbedderControlId, ImeEvent, InputEvent, InputEventId, InputEventResult,
22    InputMethodControl, Key, KeyState, KeyboardEvent, Modifiers, MouseButton as ServoMouseButton,
23    MouseButtonAction, MouseButtonEvent, MouseLeftViewportEvent, MouseMoveEvent, NamedKey,
24    OffscreenRenderingContext, PermissionRequest, RenderingContext, ScreenGeometry, Theme,
25    TouchEvent, TouchEventType, TouchId, TouchPointerType, WebRenderDebugOption, WebView,
26    WebViewId, WheelDelta, WheelEvent, WheelMode, WindowRenderingContext,
27    convert_rect_to_css_pixel,
28};
29use url::Url;
30use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
31use winit::event::{
32    ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
33};
34use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
35use winit::keyboard::{Key as LogicalKey, ModifiersState, NamedKey as WinitNamedKey};
36#[cfg(target_os = "linux")]
37use winit::platform::wayland::WindowAttributesExtWayland;
38#[cfg(any(target_os = "linux", target_os = "windows"))]
39use winit::window::Icon;
40#[cfg(target_os = "macos")]
41use {
42    objc2_app_kit::{NSColorSpace, NSView},
43    objc2_foundation::MainThreadMarker,
44};
45
46use super::geometry::{winit_position_to_euclid_point, winit_size_to_euclid_size};
47use super::keyutils::{CMD_OR_ALT, keyboard_event_from_winit};
48use crate::desktop::accelerated_gl_media::setup_gl_accelerated_media;
49use crate::desktop::dialog::Dialog;
50use crate::desktop::event_loop::AppEvent;
51use crate::desktop::gui::Gui;
52use crate::desktop::keyutils::CMD_OR_CONTROL;
53use crate::prefs::ServoShellPreferences;
54use crate::running_app_state::{RunningAppState, UserInterfaceCommand};
55use crate::window::{
56    LINE_HEIGHT, LINE_WIDTH, MIN_WINDOW_INNER_SIZE, PlatformWindow, ServoShellWindow,
57    ServoShellWindowId, TopLevelWebViewCreationRequest,
58};
59
60pub(crate) const INITIAL_WINDOW_TITLE: &str = "Servo";
61
62pub struct HeadedWindow {
63    /// The egui interface that is responsible for showing the user interface elements of
64    /// this headed `Window`.
65    gui: RefCell<Gui>,
66    screen_size: Size2D<u32, DeviceIndependentPixel>,
67    webview_relative_mouse_point: Cell<Point2D<f32, DevicePixel>>,
68    /// The inner size of the window in physical pixels which excludes OS decorations.
69    /// It equals viewport size + (0, toolbar height).
70    inner_size: Cell<PhysicalSize<u32>>,
71    fullscreen: Cell<bool>,
72    fullscreen_from_document: Cell<bool>,
73    device_pixel_ratio_override: Option<f32>,
74    xr_window_poses: RefCell<Vec<Rc<XRWindowPose>>>,
75    modifiers_state: Cell<ModifiersState>,
76    /// The `RenderingContext` of Servo itself. This is used to render Servo results
77    /// temporarily until they can be blitted into the egui scene.
78    rendering_context: Rc<OffscreenRenderingContext>,
79    /// The RenderingContext that renders directly onto the Window. This is used as
80    /// the target of egui rendering and also where Servo rendering results are finally
81    /// blitted.
82    window_rendering_context: Rc<WindowRenderingContext>,
83    /// A helper that simulates touch events when the `--simulate-touch-events` flag
84    /// is enabled.
85    touch_event_simulator: Option<TouchEventSimulator>,
86    /// Keyboard events that have been sent to Servo that have still not been handled yet.
87    /// When these are handled, they will optionally be used to trigger keybindings that
88    /// are overridable by web content.
89    pending_keyboard_events: RefCell<HashMap<InputEventId, KeyboardEvent>>,
90    // Keep this as the last field of the struct to ensure that the rendering context is
91    // dropped first.
92    // (https://github.com/servo/servo/issues/36711)
93    winit_window: winit::window::Window,
94    /// The last title set on this window. We need to store this value here, as `winit::Window::title`
95    /// is not supported very many platforms.
96    last_title: RefCell<String>,
97    /// The current set of open dialogs.
98    dialogs: RefCell<HashMap<WebViewId, Vec<Dialog>>>,
99    /// The [`EmbedderControlId`] of the currently showing [`InputMethod`] interfaces,
100    /// if one is showing.
101    visible_input_method: Cell<Option<EmbedderControlId>>,
102    /// The position of the mouse cursor after the most recent `MouseMove` event.
103    last_mouse_position: Cell<Option<Point2D<f32, DeviceIndependentPixel>>>,
104}
105
106impl HeadedWindow {
107    #[servo::servo_tracing::instrument(level = "debug", name = "HeadedWindow::new", skip_all)]
108    pub(crate) fn new(
109        servoshell_preferences: &ServoShellPreferences,
110        event_loop: &ActiveEventLoop,
111        event_loop_proxy: EventLoopProxy<AppEvent>,
112        initial_url: Url,
113    ) -> Rc<Self> {
114        let no_native_titlebar = servoshell_preferences.no_native_titlebar;
115        let inner_size = servoshell_preferences.initial_window_size;
116        let window_attr = winit::window::Window::default_attributes()
117            .with_title(INITIAL_WINDOW_TITLE.to_string())
118            .with_decorations(!no_native_titlebar)
119            .with_transparent(no_native_titlebar)
120            .with_inner_size(LogicalSize::new(inner_size.width, inner_size.height))
121            .with_min_inner_size(LogicalSize::new(
122                MIN_WINDOW_INNER_SIZE.width,
123                MIN_WINDOW_INNER_SIZE.height,
124            ))
125            // Must be invisible at startup; accesskit_winit setup needs to
126            // happen before the window is shown for the first time.
127            .with_visible(false);
128
129        // Set a name so it can be pinned to taskbars in Linux.
130        #[cfg(target_os = "linux")]
131        let window_attr = window_attr.with_name("org.servo.Servo", "Servo");
132
133        #[allow(deprecated)]
134        let winit_window = event_loop
135            .create_window(window_attr)
136            .expect("Failed to create window.");
137
138        #[cfg(any(target_os = "linux", target_os = "windows"))]
139        {
140            let icon_bytes = include_bytes!("../../../resources/servo_64.png");
141            winit_window.set_window_icon(Some(load_icon(icon_bytes)));
142        }
143
144        let window_handle = winit_window
145            .window_handle()
146            .expect("winit window did not have a window handle");
147        HeadedWindow::force_srgb_color_space(window_handle.as_raw());
148
149        let monitor = winit_window
150            .current_monitor()
151            .or_else(|| winit_window.available_monitors().nth(0))
152            .expect("No monitor detected");
153
154        let (screen_size, screen_scale) = servoshell_preferences.screen_size_override.map_or_else(
155            || (monitor.size(), winit_window.scale_factor()),
156            |size| (PhysicalSize::new(size.width, size.height), 1.0),
157        );
158        let screen_scale: Scale<f64, DeviceIndependentPixel, DevicePixel> =
159            Scale::new(screen_scale);
160        let screen_size = (winit_size_to_euclid_size(screen_size).to_f64() / screen_scale).to_u32();
161        let inner_size = winit_window.inner_size();
162
163        let display_handle = event_loop
164            .display_handle()
165            .expect("could not get display handle from window");
166        let window_handle = winit_window
167            .window_handle()
168            .expect("could not get window handle from window");
169        let window_rendering_context = Rc::new(
170            WindowRenderingContext::new(display_handle, window_handle, inner_size)
171                .expect("Could not create RenderingContext for Window"),
172        );
173
174        // Setup for GL accelerated media handling. This is only active on certain Linux platforms
175        // and Windows.
176        {
177            let details = window_rendering_context.surfman_details();
178            setup_gl_accelerated_media(details.0, details.1);
179        }
180
181        // Make sure the gl context is made current.
182        window_rendering_context
183            .make_current()
184            .expect("Could not make window RenderingContext current");
185
186        let rendering_context = Rc::new(window_rendering_context.offscreen_context(inner_size));
187        let gui = RefCell::new(Gui::new(
188            &winit_window,
189            event_loop,
190            event_loop_proxy,
191            rendering_context.clone(),
192            initial_url,
193        ));
194
195        debug!("Created window {:?}", winit_window.id());
196        Rc::new(HeadedWindow {
197            gui,
198            winit_window,
199            webview_relative_mouse_point: Cell::new(Point2D::zero()),
200            fullscreen: Cell::new(false),
201            fullscreen_from_document: Cell::new(false),
202            inner_size: Cell::new(inner_size),
203            screen_size,
204            device_pixel_ratio_override: servoshell_preferences.device_pixel_ratio_override,
205            xr_window_poses: RefCell::new(vec![]),
206            modifiers_state: Cell::new(ModifiersState::empty()),
207            window_rendering_context,
208            touch_event_simulator: servoshell_preferences
209                .simulate_touch_events
210                .then(Default::default),
211            pending_keyboard_events: Default::default(),
212            rendering_context,
213            last_title: RefCell::new(String::from(INITIAL_WINDOW_TITLE)),
214            dialogs: Default::default(),
215            visible_input_method: Default::default(),
216            last_mouse_position: Default::default(),
217        })
218    }
219
220    pub(crate) fn winit_window(&self) -> &winit::window::Window {
221        &self.winit_window
222    }
223
224    fn handle_keyboard_input(
225        &self,
226        state: Rc<RunningAppState>,
227        window: &Rc<ServoShellWindow>,
228        winit_event: KeyEvent,
229    ) {
230        // First, handle servoshell key bindings that are not overridable by, or visible to, the page.
231        let keyboard_event = keyboard_event_from_winit(&winit_event, self.modifiers_state.get());
232        if self.handle_intercepted_key_bindings(state, window, &keyboard_event) {
233            return;
234        }
235
236        // Then we deliver character and keyboard events to the page in the active webview.
237        let Some(webview) = window.active_webview() else {
238            return;
239        };
240
241        for xr_window_pose in self.xr_window_poses.borrow().iter() {
242            xr_window_pose.handle_xr_rotation(&winit_event, self.modifiers_state.get());
243            xr_window_pose.handle_xr_translation(&keyboard_event);
244        }
245
246        let id = webview.notify_input_event(InputEvent::Keyboard(keyboard_event.clone()));
247        self.pending_keyboard_events
248            .borrow_mut()
249            .insert(id, keyboard_event);
250    }
251
252    /// Helper function to handle a click
253    fn handle_mouse_button_event(
254        &self,
255        webview: &WebView,
256        button: MouseButton,
257        action: ElementState,
258    ) {
259        // `point` can be outside viewport, such as at toolbar with negative y-coordinate.
260        let point = self.webview_relative_mouse_point.get();
261        let webview_rect: Rect<_, _> = webview.size().into();
262        if !webview_rect.contains(point) {
263            return;
264        }
265
266        if self
267            .touch_event_simulator
268            .as_ref()
269            .is_some_and(|touch_event_simulator| {
270                touch_event_simulator
271                    .maybe_consume_move_button_event(webview, button, action, point)
272            })
273        {
274            return;
275        }
276
277        let mouse_button = match &button {
278            MouseButton::Left => ServoMouseButton::Primary,
279            MouseButton::Right => ServoMouseButton::Secondary,
280            MouseButton::Middle => ServoMouseButton::Auxiliary,
281            MouseButton::Back => ServoMouseButton::Back,
282            MouseButton::Forward => ServoMouseButton::Forward,
283            MouseButton::Other(value) => ServoMouseButton::Other(*value),
284        };
285
286        let action = match action {
287            ElementState::Pressed => MouseButtonAction::Down,
288            ElementState::Released => MouseButtonAction::Up,
289        };
290
291        webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
292            action,
293            mouse_button,
294            point.into(),
295        )));
296    }
297
298    /// Helper function to handle mouse move events.
299    fn handle_mouse_move_event(&self, webview: &WebView, position: PhysicalPosition<f64>) {
300        let mut point = winit_position_to_euclid_point(position).to_f32();
301        point.y -= (self.toolbar_height() * self.hidpi_scale_factor()).0;
302
303        let previous_point = self.webview_relative_mouse_point.get();
304        self.webview_relative_mouse_point.set(point);
305
306        let webview_rect: Rect<_, _> = webview.size().into();
307        if !webview_rect.contains(point) {
308            if webview_rect.contains(previous_point) {
309                webview.notify_input_event(InputEvent::MouseLeftViewport(
310                    MouseLeftViewportEvent::default(),
311                ));
312            }
313            return;
314        }
315
316        if self
317            .touch_event_simulator
318            .as_ref()
319            .is_some_and(|touch_event_simulator| {
320                touch_event_simulator.maybe_consume_mouse_move_event(webview, point)
321            })
322        {
323            return;
324        }
325
326        webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point.into())));
327    }
328
329    /// Handle key events before sending them to Servo.
330    fn handle_intercepted_key_bindings(
331        &self,
332        state: Rc<RunningAppState>,
333        window: &Rc<ServoShellWindow>,
334        key_event: &KeyboardEvent,
335    ) -> bool {
336        let Some(active_webview) = window.active_webview() else {
337            return false;
338        };
339
340        let mut handled = true;
341        ShortcutMatcher::from_event(key_event.event.clone())
342            .shortcut(CMD_OR_CONTROL, 'W', || {
343                window.close_webview(active_webview.id());
344            })
345            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F9), || {
346                active_webview.capture_webrender();
347            })
348            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F10), || {
349                active_webview.toggle_webrender_debugging(WebRenderDebugOption::RenderTargetDebug);
350            })
351            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F11), || {
352                active_webview.toggle_webrender_debugging(WebRenderDebugOption::TextureCacheDebug);
353            })
354            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F12), || {
355                active_webview.toggle_webrender_debugging(WebRenderDebugOption::Profiler);
356            })
357            .shortcut(CMD_OR_ALT, Key::Named(NamedKey::ArrowRight), || {
358                active_webview.go_forward(1);
359            })
360            .optional_shortcut(
361                cfg!(not(target_os = "windows")),
362                CMD_OR_CONTROL,
363                ']',
364                || {
365                    active_webview.go_forward(1);
366                },
367            )
368            .shortcut(CMD_OR_ALT, Key::Named(NamedKey::ArrowLeft), || {
369                active_webview.go_back(1);
370            })
371            .optional_shortcut(
372                cfg!(not(target_os = "windows")),
373                CMD_OR_CONTROL,
374                '[',
375                || {
376                    active_webview.go_back(1);
377                },
378            )
379            .optional_shortcut(
380                self.get_fullscreen(),
381                Modifiers::empty(),
382                Key::Named(NamedKey::Escape),
383                || active_webview.exit_fullscreen(),
384            )
385            // Select the first 8 tabs via shortcuts
386            .shortcut(CMD_OR_CONTROL, '1', || window.activate_webview_by_index(0))
387            .shortcut(CMD_OR_CONTROL, '2', || window.activate_webview_by_index(1))
388            .shortcut(CMD_OR_CONTROL, '3', || window.activate_webview_by_index(2))
389            .shortcut(CMD_OR_CONTROL, '4', || window.activate_webview_by_index(3))
390            .shortcut(CMD_OR_CONTROL, '5', || window.activate_webview_by_index(4))
391            .shortcut(CMD_OR_CONTROL, '6', || window.activate_webview_by_index(5))
392            .shortcut(CMD_OR_CONTROL, '7', || window.activate_webview_by_index(6))
393            .shortcut(CMD_OR_CONTROL, '8', || window.activate_webview_by_index(7))
394            // Cmd/Ctrl 9 is a bit different in that it focuses the last tab instead of the 9th
395            .shortcut(CMD_OR_CONTROL, '9', || {
396                let len = window.webviews().len();
397                if len > 0 {
398                    window.activate_webview_by_index(len - 1)
399                }
400            })
401            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::PageDown), || {
402                if let Some(index) = window.get_active_webview_index() {
403                    window.activate_webview_by_index((index + 1) % window.webviews().len())
404                }
405            })
406            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::PageUp), || {
407                if let Some(index) = window.get_active_webview_index() {
408                    let len = window.webviews().len();
409                    window.activate_webview_by_index((index + len - 1) % len);
410                }
411            })
412            .shortcut(CMD_OR_CONTROL, 'T', || {
413                let url = Url::parse("servo:newtab")
414                    .expect("Should be able to unconditionally parse 'servo:newtab' as URL");
415                window.create_and_activate_toplevel_webview(
416                    state.clone(),
417                    TopLevelWebViewCreationRequest::WithUrl(url),
418                );
419            })
420            .shortcut(CMD_OR_CONTROL, 'Q', || state.schedule_exit())
421            .otherwise(|| handled = false);
422        handled
423    }
424
425    #[cfg_attr(not(target_os = "macos"), expect(unused_variables))]
426    fn force_srgb_color_space(window_handle: RawWindowHandle) {
427        #[cfg(target_os = "macos")]
428        {
429            if let RawWindowHandle::AppKit(handle) = window_handle {
430                assert!(MainThreadMarker::new().is_some());
431                unsafe {
432                    let view = handle.ns_view.cast::<NSView>().as_ref();
433                    view.window()
434                        .expect("Should have a window")
435                        .setColorSpace(Some(&NSColorSpace::sRGBColorSpace()));
436                }
437            }
438        }
439    }
440
441    fn show_ime(&self, control_id: EmbedderControlId, input_method: InputMethodControl) {
442        self.visible_input_method.set(Some(control_id));
443
444        let position = input_method.position();
445        self.winit_window.set_ime_allowed(true);
446        self.winit_window.set_ime_cursor_area(
447            LogicalPosition::new(
448                position.min.x,
449                position.min.y + (self.toolbar_height().0 as i32),
450            ),
451            LogicalSize::new(
452                position.max.x - position.min.x,
453                position.max.y - position.min.y,
454            ),
455        );
456    }
457
458    pub(crate) fn for_each_active_dialog(
459        &self,
460        window: &ServoShellWindow,
461        callback: impl Fn(&mut Dialog) -> bool,
462    ) {
463        let Some(active_webview) = window.active_webview() else {
464            return;
465        };
466        let mut dialogs = self.dialogs.borrow_mut();
467        let Some(dialogs) = dialogs.get_mut(&active_webview.id()) else {
468            return;
469        };
470        if dialogs.is_empty() {
471            return;
472        }
473
474        // If a dialog is open, clear any Servo cursor. TODO: This should restore the
475        // cursor too, when all dialogs close. In general, we need a better cursor
476        // management strategy.
477        self.set_cursor(Cursor::Default);
478        dialogs.retain_mut(callback);
479    }
480
481    fn add_dialog(&self, webview_id: WebViewId, dialog: Dialog) {
482        self.dialogs
483            .borrow_mut()
484            .entry(webview_id)
485            .or_default()
486            .push(dialog)
487    }
488
489    fn remove_dialog(&self, webview_id: WebViewId, embedder_control_id: EmbedderControlId) {
490        let mut dialogs = self.dialogs.borrow_mut();
491        if let Some(dialogs) = dialogs.get_mut(&webview_id) {
492            dialogs.retain(|dialog| dialog.embedder_control_id() != Some(embedder_control_id));
493        }
494        dialogs.retain(|_, dialogs| !dialogs.is_empty());
495    }
496
497    fn has_active_dialog_for_webview(&self, webview_id: WebViewId) -> bool {
498        // First lazily clean up any empty dialog vectors.
499        let mut dialogs = self.dialogs.borrow_mut();
500        dialogs.retain(|_, dialogs| !dialogs.is_empty());
501        dialogs.contains_key(&webview_id)
502    }
503
504    fn toolbar_height(&self) -> Length<f32, DeviceIndependentPixel> {
505        self.gui.borrow().toolbar_height()
506    }
507
508    pub(crate) fn handle_winit_window_event(
509        &self,
510        state: Rc<RunningAppState>,
511        window: Rc<ServoShellWindow>,
512        event: WindowEvent,
513    ) {
514        // Handle resize events first, so that any subsequent redrawing draws onto a buffer of the
515        // correct size.
516        let mut resized = false;
517        if let WindowEvent::Resized(new_inner_size) = event &&
518            self.inner_size.get() != new_inner_size
519        {
520            self.inner_size.set(new_inner_size);
521            self.window_rendering_context.resize(new_inner_size);
522            resized = true;
523        }
524
525        // If requested to redraw or resized, repaint as soon as possible, so that new buffer
526        // contents are available to the window manager.
527        if event == WindowEvent::RedrawRequested || resized {
528            let mut gui = self.gui.borrow_mut();
529            gui.update(&state, &window, self);
530            gui.paint(&self.winit_window);
531        }
532
533        if let WindowEvent::CursorMoved { position, .. } = event {
534            self.last_mouse_position.set(Some(
535                winit_position_to_euclid_point(position).to_f32() / self.hidpi_scale_factor(),
536            ));
537        }
538        let should_forward_mouse_event_to_egui = || {
539            // If a dialog is showing, it always captures all mouse events.
540            if window
541                .active_webview()
542                .is_some_and(|webview| self.has_active_dialog_for_webview(webview.id()))
543            {
544                return true;
545            }
546            // Otherwise, if the cursor is over the egui interface, forward the event.
547            self.last_mouse_position
548                .get()
549                .is_none_or(|point| self.gui.borrow().is_in_egui_toolbar_rect(point))
550        };
551
552        // Handle the event
553        let mut consumed = false;
554        match event {
555            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
556                // Intercept any ScaleFactorChanged events away from EguiGlow::on_window_event, so
557                // we can use our own logic for calculating the scale factor and set egui’s
558                // scale factor to that value manually.
559                let desired_scale_factor = self.hidpi_scale_factor().get();
560                let effective_egui_zoom_factor = desired_scale_factor / scale_factor as f32;
561
562                info!(
563                    "window scale factor changed to {}, setting egui zoom factor to {}",
564                    scale_factor, effective_egui_zoom_factor
565                );
566
567                self.gui
568                    .borrow()
569                    .set_zoom_factor(effective_egui_zoom_factor);
570
571                window.hidpi_scale_factor_changed();
572
573                // Request a winit redraw event, so we can recomposite, update and paint
574                // the GUI, and present the new frame.
575                self.winit_window.request_redraw();
576            },
577            WindowEvent::MouseInput {
578                state: ElementState::Pressed,
579                button: MouseButton::Forward,
580                ..
581            } => {
582                window.queue_user_interface_command(UserInterfaceCommand::Forward);
583                consumed = true;
584            },
585            WindowEvent::MouseInput {
586                state: ElementState::Pressed,
587                button: MouseButton::Back,
588                ..
589            } => {
590                window.queue_user_interface_command(UserInterfaceCommand::Back);
591                consumed = true;
592            },
593            WindowEvent::MouseWheel { .. } | WindowEvent::MouseInput { .. }
594                if !should_forward_mouse_event_to_egui() =>
595            {
596                self.gui.borrow().surrender_focus();
597            },
598            WindowEvent::KeyboardInput { .. } if !self.gui.borrow().has_keyboard_focus() => {
599                // Keyboard events should go to the WebView unless some other GUI
600                // component has keyboard focus.
601            },
602            ref event => {
603                let response = self
604                    .gui
605                    .borrow_mut()
606                    .on_window_event(&self.winit_window, event);
607
608                if response.repaint && *event != WindowEvent::RedrawRequested {
609                    self.winit_window.request_redraw();
610                }
611
612                if let WindowEvent::Focused(focused) = event {
613                    state.set_window_has_focus(window.clone(), *focused);
614                }
615
616                // All CursorMoved events, even when forwarded to the WebView, are also
617                // forwarded to Gui (above). This is because egui needs to know when
618                // the mouse is moving in other parts of the view in order to properly
619                // hide tooltips.
620                if let WindowEvent::CursorMoved { .. } = event &&
621                    !should_forward_mouse_event_to_egui()
622                {
623                    consumed = false;
624                } else {
625                    // TODO how do we handle the tab key? (see doc for consumed)
626                    // Note that servo doesn’t yet support tabbing through links and inputs
627                    consumed = response.consumed;
628                }
629            },
630        }
631
632        if !consumed && let Some(webview) = window.active_webview() {
633            match event {
634                WindowEvent::KeyboardInput { event, .. } => {
635                    self.handle_keyboard_input(state, &window, event)
636                },
637                WindowEvent::ModifiersChanged(modifiers) => {
638                    self.modifiers_state.set(modifiers.state())
639                },
640                WindowEvent::MouseInput { state, button, .. } => {
641                    self.handle_mouse_button_event(&webview, button, state);
642                },
643                WindowEvent::CursorMoved { position, .. } => {
644                    self.handle_mouse_move_event(&webview, position);
645                },
646                WindowEvent::CursorLeft { .. } => {
647                    let webview_rect: Rect<_, _> = webview.size().into();
648                    if webview_rect.contains(self.webview_relative_mouse_point.get()) {
649                        webview.notify_input_event(InputEvent::MouseLeftViewport(
650                            MouseLeftViewportEvent::default(),
651                        ));
652                    }
653                },
654                WindowEvent::MouseWheel { delta, .. } => {
655                    let (delta_x, delta_y, mode) = match delta {
656                        MouseScrollDelta::LineDelta(delta_x, delta_y) => (
657                            (delta_x * LINE_WIDTH) as f64,
658                            (delta_y * LINE_HEIGHT) as f64,
659                            WheelMode::DeltaPixel,
660                        ),
661                        MouseScrollDelta::PixelDelta(delta) => {
662                            (delta.x, delta.y, WheelMode::DeltaPixel)
663                        },
664                    };
665
666                    // Create wheel event before snapping to the major axis of movement
667                    let delta = WheelDelta {
668                        x: delta_x,
669                        y: delta_y,
670                        z: 0.0,
671                        mode,
672                    };
673                    let point = self.webview_relative_mouse_point.get();
674                    webview.notify_input_event(InputEvent::Wheel(WheelEvent::new(
675                        delta,
676                        point.into(),
677                    )));
678                },
679                WindowEvent::Touch(touch) => {
680                    webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
681                        winit_phase_to_touch_event_type(touch.phase),
682                        TouchId(touch.id as i32),
683                        DevicePoint::new(touch.location.x as f32, touch.location.y as f32).into(),
684                        TouchPointerType::Touch,
685                    )));
686                },
687                WindowEvent::PinchGesture { delta, .. } => {
688                    webview.adjust_pinch_zoom(
689                        delta as f32 + 1.0,
690                        self.webview_relative_mouse_point.get(),
691                    );
692                },
693                WindowEvent::CloseRequested => {
694                    window.schedule_close();
695                },
696                WindowEvent::ThemeChanged(theme) => {
697                    webview.notify_theme_change(match theme {
698                        winit::window::Theme::Light => Theme::Light,
699                        winit::window::Theme::Dark => Theme::Dark,
700                    });
701                },
702                WindowEvent::Ime(ime) => match ime {
703                    Ime::Enabled => {
704                        webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
705                            servo::CompositionEvent {
706                                state: servo::CompositionState::Start,
707                                data: String::new(),
708                            },
709                        )));
710                    },
711                    Ime::Preedit(text, _) => {
712                        webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
713                            servo::CompositionEvent {
714                                state: servo::CompositionState::Update,
715                                data: text,
716                            },
717                        )));
718                    },
719                    Ime::Commit(text) => {
720                        webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
721                            servo::CompositionEvent {
722                                state: servo::CompositionState::End,
723                                data: text,
724                            },
725                        )));
726                    },
727                    Ime::Disabled => {
728                        // There are two reasons we receive this message from winit:
729                        //
730                        // 1. The user dismissed the IME. In that case we want to inform Servo
731                        //    so it can unfocus the current editable element.
732                        // 2. Servo changed focus and requested that we dismiss the IME, which
733                        //    in turn triggers this message. We know this is the case when we don't
734                        //    expect any IME to be open and shouldn't send any more messages to
735                        //    Servo as it might cause unexpected blurring of the newly focused
736                        //    element.
737                        if self.visible_input_method.take().is_some() {
738                            webview.notify_input_event(InputEvent::Ime(ImeEvent::Dismissed));
739                        }
740                    },
741                },
742                WindowEvent::DroppedFile(dropped_file) => {
743                    if let Ok(url) = Url::from_file_path(&dropped_file) {
744                        webview.load(url);
745                    } else {
746                        log::error!(
747                            "Failed to create URL for dropped file ({})",
748                            dropped_file.display()
749                        );
750                    }
751                },
752                _ => {},
753            }
754        }
755    }
756
757    pub(crate) fn handle_winit_app_event(&self, state: Rc<RunningAppState>, app_event: AppEvent) {
758        if let AppEvent::Accessibility(ref event) = app_event {
759            match &event.window_event {
760                egui_winit::accesskit_winit::WindowEvent::InitialTreeRequested => {
761                    state.set_accessibility_active(true);
762                },
763                egui_winit::accesskit_winit::WindowEvent::ActionRequested(req) => {
764                    if req.target_tree != accesskit::TreeId::ROOT {
765                        // TODO(#4344): Forward action to Servo
766                    }
767                },
768                egui_winit::accesskit_winit::WindowEvent::AccessibilityDeactivated => {
769                    state.set_accessibility_active(false);
770                },
771            }
772
773            if self
774                .gui
775                .borrow_mut()
776                .handle_accesskit_event(&event.window_event)
777            {
778                self.winit_window.request_redraw();
779            }
780        }
781    }
782
783    pub(crate) fn is_fullscreen_from_document(&self) -> bool {
784        self.fullscreen_from_document.get()
785    }
786}
787
788impl PlatformWindow for HeadedWindow {
789    fn platform_manages_focus(&self) -> bool {
790        true
791    }
792
793    fn as_headed_window(&self) -> Option<&Self> {
794        Some(self)
795    }
796
797    fn screen_geometry(&self) -> ScreenGeometry {
798        let hidpi_factor = self.hidpi_scale_factor();
799        let toolbar_size = Size2D::new(0.0, (self.toolbar_height() * self.hidpi_scale_factor()).0);
800        let screen_size = self.screen_size.to_f32() * hidpi_factor;
801
802        // FIXME: In reality, this should subtract screen space used by the system interface
803        // elements, but it is difficult to get this value with `winit` currently. See:
804        // See https://github.com/rust-windowing/winit/issues/2494
805        let available_screen_size = screen_size - toolbar_size;
806
807        let window_rect = DeviceIntRect::from_origin_and_size(
808            winit_position_to_euclid_point(self.winit_window.outer_position().unwrap_or_default()),
809            winit_size_to_euclid_size(self.winit_window.outer_size()).to_i32(),
810        );
811
812        ScreenGeometry {
813            size: screen_size.to_i32(),
814            available_size: available_screen_size.to_i32(),
815            window_rect,
816        }
817    }
818
819    fn device_hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
820        Scale::new(self.winit_window.scale_factor() as f32)
821    }
822
823    fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
824        self.device_pixel_ratio_override
825            .map(Scale::new)
826            .unwrap_or_else(|| self.device_hidpi_scale_factor())
827    }
828
829    fn update_user_interface_state(&self, _: &RunningAppState, window: &ServoShellWindow) -> bool {
830        let title = window
831            .active_webview()
832            .and_then(|webview| {
833                webview
834                    .page_title()
835                    .filter(|title| !title.is_empty())
836                    .or_else(|| webview.url().map(|url| url.to_string()))
837            })
838            .unwrap_or_else(|| INITIAL_WINDOW_TITLE.to_string());
839        if title != *self.last_title.borrow() {
840            self.winit_window.set_title(&title);
841            *self.last_title.borrow_mut() = title;
842        }
843
844        self.gui.borrow_mut().update_webview_data(window)
845    }
846
847    fn request_repaint(&self, _: &ServoShellWindow) {
848        self.winit_window.request_redraw();
849    }
850
851    fn request_resize(&self, _: &WebView, new_outer_size: DeviceIntSize) -> Option<DeviceIntSize> {
852        // Allocate space for the window deocrations, but do not let the inner size get
853        // smaller than `MIN_WINDOW_INNER_SIZE` or larger than twice the screen size.
854        let inner_size = self.winit_window.inner_size();
855        let outer_size = self.winit_window.outer_size();
856        let decoration_size: DeviceIntSize = Size2D::new(
857            outer_size.width - inner_size.width,
858            outer_size.height - inner_size.height,
859        )
860        .cast();
861
862        let screen_size = (self.screen_size.to_f32() * self.hidpi_scale_factor()).to_i32();
863        let new_outer_size =
864            new_outer_size.clamp(MIN_WINDOW_INNER_SIZE + decoration_size, screen_size * 2);
865
866        if outer_size.width == new_outer_size.width as u32 &&
867            outer_size.height == new_outer_size.height as u32
868        {
869            return Some(new_outer_size);
870        }
871
872        let new_inner_size = new_outer_size - decoration_size;
873        self.winit_window
874            .request_inner_size(PhysicalSize::new(
875                new_inner_size.width,
876                new_inner_size.height,
877            ))
878            .map(|resulting_size| {
879                // `Some` means that winit applied the resize synchronously, in which case it may
880                // not emit a subsequent `WindowEvent::Resized`.
881                if self.inner_size.get() != resulting_size {
882                    self.inner_size.set(resulting_size);
883                    self.window_rendering_context.resize(resulting_size);
884                }
885
886                DeviceIntSize::new(
887                    resulting_size.width as i32 + decoration_size.width,
888                    resulting_size.height as i32 + decoration_size.height,
889                )
890            })
891    }
892
893    fn window_rect(&self) -> DeviceIndependentIntRect {
894        let outer_size = self.winit_window.outer_size();
895        let scale = self.hidpi_scale_factor();
896
897        let outer_size = winit_size_to_euclid_size(outer_size).to_i32();
898
899        let origin = self
900            .winit_window
901            .outer_position()
902            .map(winit_position_to_euclid_point)
903            .unwrap_or_default();
904        convert_rect_to_css_pixel(
905            DeviceIntRect::from_origin_and_size(origin, outer_size),
906            scale,
907        )
908    }
909
910    fn set_position(&self, point: DeviceIntPoint) {
911        self.winit_window
912            .set_outer_position::<PhysicalPosition<i32>>(PhysicalPosition::new(point.x, point.y))
913    }
914
915    fn set_fullscreen(&self, state: bool) {
916        let monitor = self
917            .winit_window()
918            .current_monitor()
919            .or_else(|| self.winit_window.available_monitors().nth(0))
920            .expect("No monitor detected");
921        if self.fullscreen.get() != state {
922            self.winit_window.set_fullscreen(if state {
923                Some(winit::window::Fullscreen::Borderless(Some(monitor)))
924            } else {
925                None
926            });
927        }
928        self.fullscreen.set(state);
929        self.fullscreen_from_document.set(state);
930    }
931
932    fn get_fullscreen(&self) -> bool {
933        self.fullscreen.get()
934    }
935
936    fn set_cursor(&self, cursor: Cursor) {
937        use winit::window::CursorIcon;
938
939        let winit_cursor = match cursor {
940            Cursor::Default => CursorIcon::Default,
941            Cursor::Pointer => CursorIcon::Pointer,
942            Cursor::ContextMenu => CursorIcon::ContextMenu,
943            Cursor::Help => CursorIcon::Help,
944            Cursor::Progress => CursorIcon::Progress,
945            Cursor::Wait => CursorIcon::Wait,
946            Cursor::Cell => CursorIcon::Cell,
947            Cursor::Crosshair => CursorIcon::Crosshair,
948            Cursor::Text => CursorIcon::Text,
949            Cursor::VerticalText => CursorIcon::VerticalText,
950            Cursor::Alias => CursorIcon::Alias,
951            Cursor::Copy => CursorIcon::Copy,
952            Cursor::Move => CursorIcon::Move,
953            Cursor::NoDrop => CursorIcon::NoDrop,
954            Cursor::NotAllowed => CursorIcon::NotAllowed,
955            Cursor::Grab => CursorIcon::Grab,
956            Cursor::Grabbing => CursorIcon::Grabbing,
957            Cursor::EResize => CursorIcon::EResize,
958            Cursor::NResize => CursorIcon::NResize,
959            Cursor::NeResize => CursorIcon::NeResize,
960            Cursor::NwResize => CursorIcon::NwResize,
961            Cursor::SResize => CursorIcon::SResize,
962            Cursor::SeResize => CursorIcon::SeResize,
963            Cursor::SwResize => CursorIcon::SwResize,
964            Cursor::WResize => CursorIcon::WResize,
965            Cursor::EwResize => CursorIcon::EwResize,
966            Cursor::NsResize => CursorIcon::NsResize,
967            Cursor::NeswResize => CursorIcon::NeswResize,
968            Cursor::NwseResize => CursorIcon::NwseResize,
969            Cursor::ColResize => CursorIcon::ColResize,
970            Cursor::RowResize => CursorIcon::RowResize,
971            Cursor::AllScroll => CursorIcon::AllScroll,
972            Cursor::ZoomIn => CursorIcon::ZoomIn,
973            Cursor::ZoomOut => CursorIcon::ZoomOut,
974            Cursor::None => {
975                self.winit_window.set_cursor_visible(false);
976                return;
977            },
978        };
979        self.winit_window.set_cursor(winit_cursor);
980        self.winit_window.set_cursor_visible(true);
981    }
982
983    fn id(&self) -> ServoShellWindowId {
984        let id: u64 = self.winit_window.id().into();
985        id.into()
986    }
987
988    #[cfg(feature = "webxr")]
989    fn new_glwindow(&self, event_loop: &ActiveEventLoop) -> Rc<dyn servo::webxr::GlWindow> {
990        let size = self.winit_window.outer_size();
991
992        let window_attr = winit::window::Window::default_attributes()
993            .with_title("Servo XR".to_string())
994            .with_inner_size(size)
995            .with_visible(false);
996
997        let winit_window = event_loop
998            .create_window(window_attr)
999            .expect("Failed to create window.");
1000
1001        let pose = Rc::new(XRWindowPose {
1002            xr_rotation: Cell::new(Rotation3D::identity()),
1003            xr_translation: Cell::new(Vector3D::zero()),
1004        });
1005        self.xr_window_poses.borrow_mut().push(pose.clone());
1006        Rc::new(XRWindow { winit_window, pose })
1007    }
1008
1009    fn rendering_context(&self) -> Rc<dyn RenderingContext> {
1010        self.rendering_context.clone()
1011    }
1012
1013    fn theme(&self) -> servo::Theme {
1014        match self.winit_window.theme() {
1015            Some(winit::window::Theme::Dark) => servo::Theme::Dark,
1016            Some(winit::window::Theme::Light) | None => servo::Theme::Light,
1017        }
1018    }
1019
1020    fn maximize(&self, _webview: &WebView) {
1021        self.winit_window.set_maximized(true);
1022    }
1023
1024    /// Handle servoshell key bindings that may have been prevented by the page in the active webview.
1025    fn notify_input_event_handled(
1026        &self,
1027        webview: &WebView,
1028        id: InputEventId,
1029        result: InputEventResult,
1030    ) {
1031        let Some(keyboard_event) = self.pending_keyboard_events.borrow_mut().remove(&id) else {
1032            return;
1033        };
1034        if result.intersects(InputEventResult::DefaultPrevented | InputEventResult::Consumed) {
1035            return;
1036        }
1037
1038        ShortcutMatcher::from_event(keyboard_event.event)
1039            .shortcut(CMD_OR_CONTROL, '=', || {
1040                webview.set_page_zoom(webview.page_zoom() + 0.1);
1041            })
1042            .shortcut(CMD_OR_CONTROL, '+', || {
1043                webview.set_page_zoom(webview.page_zoom() + 0.1);
1044            })
1045            .shortcut(CMD_OR_CONTROL, '-', || {
1046                webview.set_page_zoom(webview.page_zoom() - 0.1);
1047            })
1048            .shortcut(CMD_OR_CONTROL, '0', || {
1049                webview.set_page_zoom(1.0);
1050            })
1051            .shortcut(CMD_OR_CONTROL, 'R', || webview.reload())
1052            .shortcut(Modifiers::empty(), Key::Named(NamedKey::F5), || {
1053                webview.reload()
1054            });
1055    }
1056
1057    fn has_platform_focus(&self) -> bool {
1058        self.winit_window.has_focus()
1059    }
1060
1061    fn show_embedder_control(&self, webview_id: WebViewId, embedder_control: EmbedderControl) {
1062        let control_id = embedder_control.id();
1063        match embedder_control {
1064            EmbedderControl::SelectElement(prompt) => {
1065                // FIXME: Reading the toolbar height is needed here to properly position the select dialog.
1066                // But if the toolbar height changes while the dialog is open then the position won't be updated
1067                let offset = self.gui.borrow().toolbar_height();
1068                self.add_dialog(
1069                    webview_id,
1070                    Dialog::new_select_element_dialog(prompt, offset),
1071                );
1072            },
1073            EmbedderControl::ColorPicker(color_picker) => {
1074                // FIXME: Reading the toolbar height is needed here to properly position the select dialog.
1075                // But if the toolbar height changes while the dialog is open then the position won't be updated
1076                let offset = self.gui.borrow().toolbar_height();
1077                self.add_dialog(
1078                    webview_id,
1079                    Dialog::new_color_picker_dialog(color_picker, offset),
1080                );
1081            },
1082            EmbedderControl::InputMethod(input_method_control) => {
1083                self.show_ime(control_id, input_method_control);
1084            },
1085            EmbedderControl::FilePicker(file_picker) => {
1086                self.add_dialog(webview_id, Dialog::new_file_dialog(file_picker));
1087            },
1088            EmbedderControl::SimpleDialog(simple_dialog) => {
1089                self.add_dialog(webview_id, Dialog::new_simple_dialog(simple_dialog));
1090            },
1091            EmbedderControl::ContextMenu(prompt) => {
1092                let offset = self.gui.borrow().toolbar_height();
1093                self.add_dialog(webview_id, Dialog::new_context_menu(prompt, offset));
1094            },
1095        }
1096    }
1097
1098    fn hide_embedder_control(&self, webview_id: WebViewId, embedder_control_id: EmbedderControlId) {
1099        if self.visible_input_method.get() == Some(embedder_control_id) {
1100            self.visible_input_method.set(None);
1101            self.winit_window.set_ime_allowed(false);
1102            return;
1103        }
1104        self.remove_dialog(webview_id, embedder_control_id);
1105    }
1106
1107    fn show_bluetooth_device_dialog(
1108        &self,
1109        webview_id: WebViewId,
1110        request: BluetoothDeviceSelectionRequest,
1111    ) {
1112        self.add_dialog(webview_id, Dialog::new_device_selection_dialog(request));
1113    }
1114
1115    fn show_permission_dialog(&self, webview_id: WebViewId, permission_request: PermissionRequest) {
1116        self.add_dialog(
1117            webview_id,
1118            Dialog::new_permission_request_dialog(permission_request),
1119        );
1120    }
1121
1122    fn show_http_authentication_dialog(
1123        &self,
1124        webview_id: WebViewId,
1125        authentication_request: AuthenticationRequest,
1126    ) {
1127        self.add_dialog(
1128            webview_id,
1129            Dialog::new_authentication_dialog(authentication_request),
1130        );
1131    }
1132
1133    fn dismiss_embedder_controls_for_webview(&self, webview_id: WebViewId) {
1134        self.dialogs.borrow_mut().remove(&webview_id);
1135    }
1136
1137    fn show_console_message(&self, level: servo::ConsoleLogLevel, message: &str) {
1138        println!("{message}");
1139        log::log!(level.into(), "{message}");
1140    }
1141
1142    fn notify_accessibility_tree_update(
1143        &self,
1144        _webview: WebView,
1145        tree_update: accesskit::TreeUpdate,
1146    ) {
1147        self.gui
1148            .borrow_mut()
1149            .notify_accessibility_tree_update(tree_update);
1150    }
1151}
1152
1153fn winit_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType {
1154    match phase {
1155        TouchPhase::Started => TouchEventType::Down,
1156        TouchPhase::Moved => TouchEventType::Move,
1157        TouchPhase::Ended => TouchEventType::Up,
1158        TouchPhase::Cancelled => TouchEventType::Cancel,
1159    }
1160}
1161
1162#[cfg(any(target_os = "linux", target_os = "windows"))]
1163fn load_icon(icon_bytes: &[u8]) -> Icon {
1164    let (icon_rgba, icon_width, icon_height) = {
1165        use image::{GenericImageView, Pixel};
1166        let image = image::load_from_memory(icon_bytes).expect("Failed to load icon");
1167        let (width, height) = image.dimensions();
1168        let mut rgba = Vec::with_capacity((width * height) as usize * 4);
1169        for (_, _, pixel) in image.pixels() {
1170            rgba.extend_from_slice(&pixel.to_rgba().0);
1171        }
1172        (rgba, width, height)
1173    };
1174    Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to load icon")
1175}
1176
1177#[cfg(feature = "webxr")]
1178struct XRWindow {
1179    winit_window: winit::window::Window,
1180    pose: Rc<XRWindowPose>,
1181}
1182
1183struct XRWindowPose {
1184    xr_rotation: Cell<Rotation3D<f32, UnknownUnit, UnknownUnit>>,
1185    xr_translation: Cell<Vector3D<f32, UnknownUnit>>,
1186}
1187
1188#[cfg(feature = "webxr")]
1189impl servo::webxr::GlWindow for XRWindow {
1190    fn get_render_target(
1191        &self,
1192        device: &mut surfman::Device,
1193        _context: &mut surfman::Context,
1194    ) -> servo::webxr::GlWindowRenderTarget {
1195        self.winit_window.set_visible(true);
1196        let window_handle = self
1197            .winit_window
1198            .window_handle()
1199            .expect("could not get window handle from window");
1200        let size = self.winit_window.inner_size();
1201        let size = Size2D::new(size.width as i32, size.height as i32);
1202        let native_widget = device
1203            .connection()
1204            .create_native_widget_from_window_handle(window_handle, size)
1205            .expect("Failed to create native widget");
1206        servo::webxr::GlWindowRenderTarget::NativeWidget(native_widget)
1207    }
1208
1209    fn get_rotation(&self) -> Rotation3D<f32, UnknownUnit, UnknownUnit> {
1210        self.pose.xr_rotation.get()
1211    }
1212
1213    fn get_translation(&self) -> Vector3D<f32, UnknownUnit> {
1214        self.pose.xr_translation.get()
1215    }
1216
1217    fn get_mode(&self) -> servo::webxr::GlWindowMode {
1218        use servo::pref;
1219        if pref!(dom_webxr_glwindow_red_cyan) {
1220            servo::webxr::GlWindowMode::StereoRedCyan
1221        } else if pref!(dom_webxr_glwindow_left_right) {
1222            servo::webxr::GlWindowMode::StereoLeftRight
1223        } else if pref!(dom_webxr_glwindow_spherical) {
1224            servo::webxr::GlWindowMode::Spherical
1225        } else if pref!(dom_webxr_glwindow_cubemap) {
1226            servo::webxr::GlWindowMode::Cubemap
1227        } else {
1228            servo::webxr::GlWindowMode::Blit
1229        }
1230    }
1231
1232    fn display_handle(&self) -> raw_window_handle::DisplayHandle<'_> {
1233        self.winit_window
1234            .display_handle()
1235            .expect("Every window should have a display handle")
1236    }
1237}
1238
1239impl XRWindowPose {
1240    fn handle_xr_translation(&self, input: &KeyboardEvent) {
1241        if input.event.state != KeyState::Down {
1242            return;
1243        }
1244        const NORMAL_TRANSLATE: f32 = 0.1;
1245        const QUICK_TRANSLATE: f32 = 1.0;
1246        let mut x = 0.0;
1247        let mut z = 0.0;
1248        match input.event.key {
1249            Key::Character(ref k) => match &**k {
1250                "w" => z = -NORMAL_TRANSLATE,
1251                "W" => z = -QUICK_TRANSLATE,
1252                "s" => z = NORMAL_TRANSLATE,
1253                "S" => z = QUICK_TRANSLATE,
1254                "a" => x = -NORMAL_TRANSLATE,
1255                "A" => x = -QUICK_TRANSLATE,
1256                "d" => x = NORMAL_TRANSLATE,
1257                "D" => x = QUICK_TRANSLATE,
1258                _ => return,
1259            },
1260            _ => return,
1261        };
1262        let (old_x, old_y, old_z) = self.xr_translation.get().to_tuple();
1263        let vec = Vector3D::new(x + old_x, old_y, z + old_z);
1264        self.xr_translation.set(vec);
1265    }
1266
1267    fn handle_xr_rotation(&self, input: &KeyEvent, modifiers: ModifiersState) {
1268        if input.state != ElementState::Pressed {
1269            return;
1270        }
1271        let mut x = 0.0;
1272        let mut y = 0.0;
1273        match input.logical_key {
1274            LogicalKey::Named(WinitNamedKey::ArrowUp) => x = 1.0,
1275            LogicalKey::Named(WinitNamedKey::ArrowDown) => x = -1.0,
1276            LogicalKey::Named(WinitNamedKey::ArrowLeft) => y = 1.0,
1277            LogicalKey::Named(WinitNamedKey::ArrowRight) => y = -1.0,
1278            _ => return,
1279        };
1280        if modifiers.shift_key() {
1281            x *= 10.0;
1282            y *= 10.0;
1283        }
1284        let x: Rotation3D<_, UnknownUnit, UnknownUnit> = Rotation3D::around_x(Angle::degrees(x));
1285        let y: Rotation3D<_, UnknownUnit, UnknownUnit> = Rotation3D::around_y(Angle::degrees(y));
1286        let rotation = self.xr_rotation.get().then(&x).then(&y);
1287        self.xr_rotation.set(rotation);
1288    }
1289}
1290
1291#[derive(Default)]
1292pub struct TouchEventSimulator {
1293    pub left_mouse_button_down: Cell<bool>,
1294}
1295
1296impl TouchEventSimulator {
1297    fn maybe_consume_move_button_event(
1298        &self,
1299        webview: &WebView,
1300        button: MouseButton,
1301        action: ElementState,
1302        point: DevicePoint,
1303    ) -> bool {
1304        if button != MouseButton::Left {
1305            return false;
1306        }
1307
1308        if action == ElementState::Pressed && !self.left_mouse_button_down.get() {
1309            webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
1310                TouchEventType::Down,
1311                TouchId(0),
1312                point.into(),
1313                TouchPointerType::Touch,
1314            )));
1315            self.left_mouse_button_down.set(true);
1316        } else if action == ElementState::Released {
1317            webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
1318                TouchEventType::Up,
1319                TouchId(0),
1320                point.into(),
1321                TouchPointerType::Touch,
1322            )));
1323            self.left_mouse_button_down.set(false);
1324        }
1325
1326        true
1327    }
1328
1329    fn maybe_consume_mouse_move_event(
1330        &self,
1331        webview: &WebView,
1332        point: Point2D<f32, DevicePixel>,
1333    ) -> bool {
1334        if !self.left_mouse_button_down.get() {
1335            return false;
1336        }
1337
1338        webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
1339            TouchEventType::Move,
1340            TouchId(0),
1341            point.into(),
1342            TouchPointerType::Touch,
1343        )));
1344        true
1345    }
1346}