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::env;
13use std::rc::Rc;
14use std::time::Duration;
15
16use euclid::{Angle, Length, Point2D, Rotation3D, Scale, Size2D, UnknownUnit, Vector2D, Vector3D};
17use keyboard_types::ShortcutMatcher;
18use log::{debug, info};
19use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};
20use servo::servo_geometry::{
21    DeviceIndependentIntRect, DeviceIndependentPixel, convert_rect_to_css_pixel,
22};
23use servo::webrender_api::ScrollLocation;
24use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel};
25use servo::{
26    Cursor, ImeEvent, InputEvent, Key, KeyState, KeyboardEvent, Modifiers,
27    MouseButton as ServoMouseButton, MouseButtonAction, MouseButtonEvent, MouseLeftViewportEvent,
28    MouseMoveEvent, NamedKey, OffscreenRenderingContext, RenderingContext, ScreenGeometry, Theme,
29    TouchEvent, TouchEventType, TouchId, WebRenderDebugOption, WebView, WheelDelta, WheelEvent,
30    WheelMode, WindowRenderingContext,
31};
32use url::Url;
33use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
34use winit::event::{
35    ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
36};
37use winit::event_loop::ActiveEventLoop;
38use winit::keyboard::{Key as LogicalKey, ModifiersState, NamedKey as WinitNamedKey};
39#[cfg(target_os = "linux")]
40use winit::platform::wayland::WindowAttributesExtWayland;
41#[cfg(any(target_os = "linux", target_os = "windows"))]
42use winit::window::Icon;
43#[cfg(target_os = "macos")]
44use {
45    objc2_app_kit::{NSColorSpace, NSView},
46    objc2_foundation::MainThreadMarker,
47};
48
49use super::app_state::RunningAppState;
50use super::geometry::{winit_position_to_euclid_point, winit_size_to_euclid_size};
51use super::keyutils::{CMD_OR_ALT, keyboard_event_from_winit};
52use super::window_trait::{
53    LINE_HEIGHT, LINE_WIDTH, MIN_INNER_HEIGHT, MIN_INNER_WIDTH, PIXEL_DELTA_FACTOR,
54    WindowPortsMethods,
55};
56use crate::desktop::accelerated_gl_media::setup_gl_accelerated_media;
57use crate::desktop::keyutils::CMD_OR_CONTROL;
58use crate::prefs::ServoShellPreferences;
59
60pub struct Window {
61    screen_size: Size2D<u32, DeviceIndependentPixel>,
62    toolbar_height: Cell<Length<f32, DeviceIndependentPixel>>,
63    monitor: winit::monitor::MonitorHandle,
64    webview_relative_mouse_point: Cell<Point2D<f32, DevicePixel>>,
65    last_pressed: Cell<Option<(KeyboardEvent, Option<LogicalKey>)>>,
66    /// The inner size of the window in physical pixels which excludes OS decorations.
67    /// It equals viewport size + (0, toolbar height).
68    inner_size: Cell<PhysicalSize<u32>>,
69    /// A map of winit's key codes to key values that are interpreted from
70    /// winit's ReceivedChar events.
71    keys_down: RefCell<HashMap<LogicalKey, Key>>,
72    fullscreen: Cell<bool>,
73    device_pixel_ratio_override: Option<f32>,
74    xr_window_poses: RefCell<Vec<Rc<XRWindowPose>>>,
75    modifiers_state: Cell<ModifiersState>,
76
77    /// The `RenderingContext` of Servo itself. This is used to render Servo results
78    /// temporarily until they can be blitted into the egui scene.
79    rendering_context: Rc<OffscreenRenderingContext>,
80    /// The RenderingContext that renders directly onto the Window. This is used as
81    /// the target of egui rendering and also where Servo rendering results are finally
82    /// blitted.
83    window_rendering_context: Rc<WindowRenderingContext>,
84    // Keep this as the last field of the struct to ensure that the rendering context is
85    // dropped first.
86    // (https://github.com/servo/servo/issues/36711)
87    winit_window: winit::window::Window,
88}
89
90impl Window {
91    pub fn new(
92        servoshell_preferences: &ServoShellPreferences,
93        event_loop: &ActiveEventLoop,
94    ) -> Window {
95        let no_native_titlebar = servoshell_preferences.no_native_titlebar;
96        let inner_size = servoshell_preferences.initial_window_size;
97        let window_attr = winit::window::Window::default_attributes()
98            .with_title("Servo".to_string())
99            .with_decorations(!no_native_titlebar)
100            .with_transparent(no_native_titlebar)
101            .with_inner_size(LogicalSize::new(inner_size.width, inner_size.height))
102            .with_min_inner_size(LogicalSize::new(MIN_INNER_WIDTH, MIN_INNER_HEIGHT))
103            // Must be invisible at startup; accesskit_winit setup needs to
104            // happen before the window is shown for the first time.
105            .with_visible(false);
106
107        // Set a name so it can be pinned to taskbars in Linux.
108        #[cfg(target_os = "linux")]
109        let window_attr = window_attr.with_name("org.servo.Servo", "Servo");
110
111        #[allow(deprecated)]
112        let winit_window = event_loop
113            .create_window(window_attr)
114            .expect("Failed to create window.");
115
116        #[cfg(any(target_os = "linux", target_os = "windows"))]
117        {
118            let icon_bytes = include_bytes!("../../../resources/servo_64.png");
119            winit_window.set_window_icon(Some(load_icon(icon_bytes)));
120        }
121
122        let window_handle = winit_window
123            .window_handle()
124            .expect("winit window did not have a window handle");
125        Window::force_srgb_color_space(window_handle.as_raw());
126
127        let monitor = winit_window
128            .current_monitor()
129            .or_else(|| winit_window.available_monitors().nth(0))
130            .expect("No monitor detected");
131
132        let (screen_size, screen_scale) = servoshell_preferences.screen_size_override.map_or_else(
133            || (monitor.size(), winit_window.scale_factor()),
134            |size| (PhysicalSize::new(size.width, size.height), 1.0),
135        );
136        let screen_scale: Scale<f64, DeviceIndependentPixel, DevicePixel> =
137            Scale::new(screen_scale);
138        let screen_size = (winit_size_to_euclid_size(screen_size).to_f64() / screen_scale).to_u32();
139        let inner_size = winit_window.inner_size();
140
141        let display_handle = event_loop
142            .display_handle()
143            .expect("could not get display handle from window");
144        let window_handle = winit_window
145            .window_handle()
146            .expect("could not get window handle from window");
147        let window_rendering_context = Rc::new(
148            WindowRenderingContext::new(display_handle, window_handle, inner_size)
149                .expect("Could not create RenderingContext for Window"),
150        );
151
152        // Setup for GL accelerated media handling. This is only active on certain Linux platforms
153        // and Windows.
154        {
155            let details = window_rendering_context.surfman_details();
156            setup_gl_accelerated_media(details.0, details.1);
157        }
158
159        // Make sure the gl context is made current.
160        window_rendering_context
161            .make_current()
162            .expect("Could not make window RenderingContext current");
163
164        let rendering_context = Rc::new(window_rendering_context.offscreen_context(inner_size));
165
166        debug!("Created window {:?}", winit_window.id());
167        Window {
168            winit_window,
169            webview_relative_mouse_point: Cell::new(Point2D::zero()),
170            last_pressed: Cell::new(None),
171            keys_down: RefCell::new(HashMap::new()),
172            fullscreen: Cell::new(false),
173            inner_size: Cell::new(inner_size),
174            monitor,
175            screen_size,
176            device_pixel_ratio_override: servoshell_preferences.device_pixel_ratio_override,
177            xr_window_poses: RefCell::new(vec![]),
178            modifiers_state: Cell::new(ModifiersState::empty()),
179            toolbar_height: Cell::new(Default::default()),
180            window_rendering_context,
181            rendering_context,
182        }
183    }
184
185    fn handle_received_character(&self, webview: &WebView, mut character: char) {
186        info!("winit received character: {:?}", character);
187        if character.is_control() {
188            if character as u8 >= 32 {
189                return;
190            }
191            // shift ASCII control characters to lowercase
192            character = (character as u8 + 96) as char;
193        }
194        let (mut event, key_code) = if let Some((event, key_code)) = self.last_pressed.replace(None)
195        {
196            (event, key_code)
197        } else if character.is_ascii() {
198            // Some keys like Backspace emit a control character in winit
199            // but they are already dealt with in handle_keyboard_input
200            // so just ignore the character.
201            return;
202        } else {
203            // For combined characters like the letter e with an acute accent
204            // no keyboard event is emitted. A dummy event is created in this case.
205            (KeyboardEvent::default(), None)
206        };
207        event.event.key = Key::Character(character.to_string());
208
209        if event.event.state == KeyState::Down {
210            // Ensure that when we receive a keyup event from winit, we are able
211            // to infer that it's related to this character and set the event
212            // properties appropriately.
213            if let Some(key_code) = key_code {
214                self.keys_down
215                    .borrow_mut()
216                    .insert(key_code, event.event.key.clone());
217            }
218        }
219
220        let xr_poses = self.xr_window_poses.borrow();
221        for xr_window_pose in &*xr_poses {
222            xr_window_pose.handle_xr_translation(&event);
223        }
224        webview.notify_input_event(InputEvent::Keyboard(event));
225    }
226
227    fn handle_keyboard_input(&self, state: Rc<RunningAppState>, winit_event: KeyEvent) {
228        // First, handle servoshell key bindings that are not overridable by, or visible to, the page.
229        let mut keyboard_event =
230            keyboard_event_from_winit(&winit_event, self.modifiers_state.get());
231        if self.handle_intercepted_key_bindings(state.clone(), &keyboard_event) {
232            return;
233        }
234
235        // Then we deliver character and keyboard events to the page in the focused webview.
236        let Some(webview) = state.focused_webview() else {
237            return;
238        };
239
240        if let Some(input_text) = &winit_event.text {
241            for character in input_text.chars() {
242                self.handle_received_character(&webview, character);
243            }
244        }
245
246        if keyboard_event.event.state == KeyState::Down &&
247            keyboard_event.event.key == Key::Named(NamedKey::Unidentified)
248        {
249            // If pressed and probably printable, we expect a ReceivedCharacter event.
250            // Wait for that to be received and don't queue any event right now.
251            self.last_pressed
252                .set(Some((keyboard_event, Some(winit_event.logical_key))));
253            return;
254        } else if keyboard_event.event.state == KeyState::Up &&
255            keyboard_event.event.key == Key::Named(NamedKey::Unidentified)
256        {
257            // If release and probably printable, this is following a ReceiverCharacter event.
258            if let Some(key) = self.keys_down.borrow_mut().remove(&winit_event.logical_key) {
259                keyboard_event.event.key = key;
260            }
261        }
262
263        if keyboard_event.event.key != Key::Named(NamedKey::Unidentified) {
264            self.last_pressed.set(None);
265            let xr_poses = self.xr_window_poses.borrow();
266            for xr_window_pose in &*xr_poses {
267                xr_window_pose.handle_xr_rotation(&winit_event, self.modifiers_state.get());
268            }
269            webview.notify_input_event(InputEvent::Keyboard(keyboard_event));
270        }
271
272        // servoshell also has key bindings that are visible to, and overridable by, the page.
273        // See the handler for EmbedderMsg::Keyboard in webview.rs for those.
274    }
275
276    /// Helper function to handle a click
277    fn handle_mouse(&self, webview: &WebView, button: MouseButton, action: ElementState) {
278        let mouse_button = match &button {
279            MouseButton::Left => ServoMouseButton::Left,
280            MouseButton::Right => ServoMouseButton::Right,
281            MouseButton::Middle => ServoMouseButton::Middle,
282            MouseButton::Back => ServoMouseButton::Back,
283            MouseButton::Forward => ServoMouseButton::Forward,
284            MouseButton::Other(value) => ServoMouseButton::Other(*value),
285        };
286
287        let point = self.webview_relative_mouse_point.get();
288        // `point` can be outside viewport, such as at toolbar with negative y-coordinate.
289        if !webview.rect().contains(point) {
290            return;
291        }
292        let action = match action {
293            ElementState::Pressed => MouseButtonAction::Down,
294            ElementState::Released => MouseButtonAction::Up,
295        };
296
297        webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
298            action,
299            mouse_button,
300            point,
301        )));
302    }
303
304    /// Handle key events before sending them to Servo.
305    fn handle_intercepted_key_bindings(
306        &self,
307        state: Rc<RunningAppState>,
308        key_event: &KeyboardEvent,
309    ) -> bool {
310        let Some(focused_webview) = state.focused_webview() else {
311            return false;
312        };
313
314        let mut handled = true;
315        ShortcutMatcher::from_event(key_event.event.clone())
316            .shortcut(CMD_OR_CONTROL, 'R', || focused_webview.reload())
317            .shortcut(CMD_OR_CONTROL, 'W', || {
318                state.close_webview(focused_webview.id());
319            })
320            .shortcut(CMD_OR_CONTROL, 'P', || {
321                let rate = env::var("SAMPLING_RATE")
322                    .ok()
323                    .and_then(|s| s.parse().ok())
324                    .unwrap_or(10);
325                let duration = env::var("SAMPLING_DURATION")
326                    .ok()
327                    .and_then(|s| s.parse().ok())
328                    .unwrap_or(10);
329                focused_webview.toggle_sampling_profiler(
330                    Duration::from_millis(rate),
331                    Duration::from_secs(duration),
332                );
333            })
334            .shortcut(CMD_OR_CONTROL, 'X', || {
335                focused_webview
336                    .notify_input_event(InputEvent::EditingAction(servo::EditingActionEvent::Cut))
337            })
338            .shortcut(CMD_OR_CONTROL, 'C', || {
339                focused_webview
340                    .notify_input_event(InputEvent::EditingAction(servo::EditingActionEvent::Copy))
341            })
342            .shortcut(CMD_OR_CONTROL, 'V', || {
343                focused_webview
344                    .notify_input_event(InputEvent::EditingAction(servo::EditingActionEvent::Paste))
345            })
346            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F9), || {
347                focused_webview.capture_webrender();
348            })
349            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F10), || {
350                focused_webview.toggle_webrender_debugging(WebRenderDebugOption::RenderTargetDebug);
351            })
352            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F11), || {
353                focused_webview.toggle_webrender_debugging(WebRenderDebugOption::TextureCacheDebug);
354            })
355            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F12), || {
356                focused_webview.toggle_webrender_debugging(WebRenderDebugOption::Profiler);
357            })
358            .shortcut(CMD_OR_ALT, Key::Named(NamedKey::ArrowRight), || {
359                focused_webview.go_forward(1);
360            })
361            .optional_shortcut(
362                cfg!(not(target_os = "windows")),
363                CMD_OR_CONTROL,
364                ']',
365                || {
366                    focused_webview.go_forward(1);
367                },
368            )
369            .shortcut(CMD_OR_ALT, Key::Named(NamedKey::ArrowLeft), || {
370                focused_webview.go_back(1);
371            })
372            .optional_shortcut(
373                cfg!(not(target_os = "windows")),
374                CMD_OR_CONTROL,
375                '[',
376                || {
377                    focused_webview.go_back(1);
378                },
379            )
380            .optional_shortcut(
381                self.get_fullscreen(),
382                Modifiers::empty(),
383                Key::Named(NamedKey::Escape),
384                || focused_webview.exit_fullscreen(),
385            )
386            // Select the first 8 tabs via shortcuts
387            .shortcut(CMD_OR_CONTROL, '1', || state.focus_webview_by_index(0))
388            .shortcut(CMD_OR_CONTROL, '2', || state.focus_webview_by_index(1))
389            .shortcut(CMD_OR_CONTROL, '3', || state.focus_webview_by_index(2))
390            .shortcut(CMD_OR_CONTROL, '4', || state.focus_webview_by_index(3))
391            .shortcut(CMD_OR_CONTROL, '5', || state.focus_webview_by_index(4))
392            .shortcut(CMD_OR_CONTROL, '6', || state.focus_webview_by_index(5))
393            .shortcut(CMD_OR_CONTROL, '7', || state.focus_webview_by_index(6))
394            .shortcut(CMD_OR_CONTROL, '8', || state.focus_webview_by_index(7))
395            // Cmd/Ctrl 9 is a bit different in that it focuses the last tab instead of the 9th
396            .shortcut(CMD_OR_CONTROL, '9', || {
397                let len = state.webviews().len();
398                if len > 0 {
399                    state.focus_webview_by_index(len - 1)
400                }
401            })
402            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::PageDown), || {
403                if let Some(index) = state.get_focused_webview_index() {
404                    state.focus_webview_by_index((index + 1) % state.webviews().len())
405                }
406            })
407            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::PageUp), || {
408                if let Some(index) = state.get_focused_webview_index() {
409                    let len = state.webviews().len();
410                    state.focus_webview_by_index((index + len - 1) % len);
411                }
412            })
413            .shortcut(CMD_OR_CONTROL, 'T', || {
414                state.create_and_focus_toplevel_webview(
415                    Url::parse("servo:newtab")
416                        .expect("Should be able to unconditionally parse 'servo:newtab' as URL"),
417                );
418            })
419            .shortcut(CMD_OR_CONTROL, 'Q', || state.servo().start_shutting_down())
420            .otherwise(|| handled = false);
421        handled
422    }
423
424    pub(crate) fn offscreen_rendering_context(&self) -> Rc<OffscreenRenderingContext> {
425        self.rendering_context.clone()
426    }
427
428    #[allow(unused_variables)]
429    fn force_srgb_color_space(window_handle: RawWindowHandle) {
430        #[cfg(target_os = "macos")]
431        {
432            if let RawWindowHandle::AppKit(handle) = window_handle {
433                assert!(MainThreadMarker::new().is_some());
434                unsafe {
435                    let view = handle.ns_view.cast::<NSView>().as_ref();
436                    view.window()
437                        .expect("Should have a window")
438                        .setColorSpace(Some(&NSColorSpace::sRGBColorSpace()));
439                }
440            }
441        }
442    }
443}
444
445impl WindowPortsMethods for Window {
446    fn screen_geometry(&self) -> ScreenGeometry {
447        let hidpi_factor = self.hidpi_scale_factor();
448        let toolbar_size = Size2D::new(
449            0.0,
450            (self.toolbar_height.get() * self.hidpi_scale_factor()).0,
451        );
452        let screen_size = self.screen_size.to_f32() * hidpi_factor;
453
454        // FIXME: In reality, this should subtract screen space used by the system interface
455        // elements, but it is difficult to get this value with `winit` currently. See:
456        // See https://github.com/rust-windowing/winit/issues/2494
457        let available_screen_size = screen_size - toolbar_size;
458
459        let window_rect = DeviceIntRect::from_origin_and_size(
460            winit_position_to_euclid_point(self.winit_window.outer_position().unwrap_or_default()),
461            winit_size_to_euclid_size(self.winit_window.outer_size()).to_i32(),
462        );
463
464        ScreenGeometry {
465            size: screen_size.to_i32(),
466            available_size: available_screen_size.to_i32(),
467            window_rect,
468        }
469    }
470
471    fn device_hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
472        Scale::new(self.winit_window.scale_factor() as f32)
473    }
474
475    fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
476        self.device_pixel_ratio_override
477            .map(Scale::new)
478            .unwrap_or_else(|| self.device_hidpi_scale_factor())
479    }
480
481    fn page_height(&self) -> f32 {
482        let dpr = self.hidpi_scale_factor();
483        let size = self.winit_window.inner_size();
484        size.height as f32 * dpr.get()
485    }
486
487    fn set_title(&self, title: &str) {
488        self.winit_window.set_title(title);
489    }
490
491    fn request_resize(&self, _: &WebView, new_outer_size: DeviceIntSize) -> Option<DeviceIntSize> {
492        let outer_size = self.winit_window.outer_size();
493        if outer_size.width == new_outer_size.width as u32 &&
494            outer_size.height == new_outer_size.height as u32
495        {
496            return Some(new_outer_size);
497        }
498
499        let inner_size = self.winit_window.inner_size();
500        let decoration_height = outer_size.height - inner_size.height;
501        let decoration_width = outer_size.width - inner_size.width;
502
503        self.winit_window
504            .request_inner_size::<PhysicalSize<i32>>(PhysicalSize::new(
505                new_outer_size.width - decoration_width as i32,
506                new_outer_size.height - decoration_height as i32,
507            ))
508            .map(|resulting_size| {
509                DeviceIntSize::new(
510                    (resulting_size.width + decoration_width) as i32,
511                    (resulting_size.height + decoration_height) as i32,
512                )
513            })
514    }
515
516    fn window_rect(&self) -> DeviceIndependentIntRect {
517        let outer_size = self.winit_window.outer_size();
518        let scale = self.hidpi_scale_factor();
519
520        let outer_size = winit_size_to_euclid_size(outer_size).to_i32();
521
522        let origin = self
523            .winit_window
524            .outer_position()
525            .map(winit_position_to_euclid_point)
526            .unwrap_or_default();
527        convert_rect_to_css_pixel(
528            DeviceIntRect::from_origin_and_size(origin, outer_size),
529            scale,
530        )
531    }
532
533    fn set_position(&self, point: DeviceIntPoint) {
534        self.winit_window
535            .set_outer_position::<PhysicalPosition<i32>>(PhysicalPosition::new(point.x, point.y))
536    }
537
538    fn set_fullscreen(&self, state: bool) {
539        if self.fullscreen.get() != state {
540            self.winit_window.set_fullscreen(if state {
541                Some(winit::window::Fullscreen::Borderless(Some(
542                    self.monitor.clone(),
543                )))
544            } else {
545                None
546            });
547        }
548        self.fullscreen.set(state);
549    }
550
551    fn get_fullscreen(&self) -> bool {
552        self.fullscreen.get()
553    }
554
555    fn set_cursor(&self, cursor: Cursor) {
556        use winit::window::CursorIcon;
557
558        let winit_cursor = match cursor {
559            Cursor::Default => CursorIcon::Default,
560            Cursor::Pointer => CursorIcon::Pointer,
561            Cursor::ContextMenu => CursorIcon::ContextMenu,
562            Cursor::Help => CursorIcon::Help,
563            Cursor::Progress => CursorIcon::Progress,
564            Cursor::Wait => CursorIcon::Wait,
565            Cursor::Cell => CursorIcon::Cell,
566            Cursor::Crosshair => CursorIcon::Crosshair,
567            Cursor::Text => CursorIcon::Text,
568            Cursor::VerticalText => CursorIcon::VerticalText,
569            Cursor::Alias => CursorIcon::Alias,
570            Cursor::Copy => CursorIcon::Copy,
571            Cursor::Move => CursorIcon::Move,
572            Cursor::NoDrop => CursorIcon::NoDrop,
573            Cursor::NotAllowed => CursorIcon::NotAllowed,
574            Cursor::Grab => CursorIcon::Grab,
575            Cursor::Grabbing => CursorIcon::Grabbing,
576            Cursor::EResize => CursorIcon::EResize,
577            Cursor::NResize => CursorIcon::NResize,
578            Cursor::NeResize => CursorIcon::NeResize,
579            Cursor::NwResize => CursorIcon::NwResize,
580            Cursor::SResize => CursorIcon::SResize,
581            Cursor::SeResize => CursorIcon::SeResize,
582            Cursor::SwResize => CursorIcon::SwResize,
583            Cursor::WResize => CursorIcon::WResize,
584            Cursor::EwResize => CursorIcon::EwResize,
585            Cursor::NsResize => CursorIcon::NsResize,
586            Cursor::NeswResize => CursorIcon::NeswResize,
587            Cursor::NwseResize => CursorIcon::NwseResize,
588            Cursor::ColResize => CursorIcon::ColResize,
589            Cursor::RowResize => CursorIcon::RowResize,
590            Cursor::AllScroll => CursorIcon::AllScroll,
591            Cursor::ZoomIn => CursorIcon::ZoomIn,
592            Cursor::ZoomOut => CursorIcon::ZoomOut,
593            Cursor::None => {
594                self.winit_window.set_cursor_visible(false);
595                return;
596            },
597        };
598        self.winit_window.set_cursor(winit_cursor);
599        self.winit_window.set_cursor_visible(true);
600    }
601
602    fn id(&self) -> winit::window::WindowId {
603        self.winit_window.id()
604    }
605
606    fn handle_winit_event(&self, state: Rc<RunningAppState>, event: WindowEvent) {
607        let Some(webview) = state.focused_webview() else {
608            return;
609        };
610
611        match event {
612            WindowEvent::KeyboardInput { event, .. } => self.handle_keyboard_input(state, event),
613            WindowEvent::ModifiersChanged(modifiers) => self.modifiers_state.set(modifiers.state()),
614            WindowEvent::MouseInput { state, button, .. } => {
615                self.handle_mouse(&webview, button, state);
616            },
617            WindowEvent::CursorMoved { position, .. } => {
618                let mut point = winit_position_to_euclid_point(position).to_f32();
619                point.y -= (self.toolbar_height() * self.hidpi_scale_factor()).0;
620
621                let previous_point = self.webview_relative_mouse_point.get();
622                if webview.rect().contains(point) {
623                    webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point)));
624                } else if webview.rect().contains(previous_point) {
625                    webview.notify_input_event(InputEvent::MouseLeftViewport(
626                        MouseLeftViewportEvent::default(),
627                    ));
628                }
629
630                self.webview_relative_mouse_point.set(point);
631            },
632            WindowEvent::CursorLeft { .. } => {
633                if webview
634                    .rect()
635                    .contains(self.webview_relative_mouse_point.get())
636                {
637                    webview.notify_input_event(InputEvent::MouseLeftViewport(
638                        MouseLeftViewportEvent::default(),
639                    ));
640                }
641            },
642            WindowEvent::MouseWheel { delta, .. } => {
643                let (mut dx, mut dy, mode) = match delta {
644                    MouseScrollDelta::LineDelta(dx, dy) => (
645                        (dx * LINE_WIDTH) as f64,
646                        (dy * LINE_HEIGHT) as f64,
647                        WheelMode::DeltaLine,
648                    ),
649                    MouseScrollDelta::PixelDelta(position) => {
650                        let position: LogicalPosition<f64> =
651                            position.to_logical(self.device_hidpi_scale_factor().get() as f64);
652                        (
653                            position.x * PIXEL_DELTA_FACTOR,
654                            position.y * PIXEL_DELTA_FACTOR,
655                            WheelMode::DeltaPixel,
656                        )
657                    },
658                };
659
660                // Create wheel event before snapping to the major axis of movement
661                let delta = WheelDelta {
662                    x: dx,
663                    y: dy,
664                    z: 0.0,
665                    mode,
666                };
667                let point = self.webview_relative_mouse_point.get();
668
669                // Scroll events snap to the major axis of movement, with vertical
670                // preferred over horizontal.
671                if dy.abs() >= dx.abs() {
672                    dx = 0.0;
673                } else {
674                    dy = 0.0;
675                }
676
677                // Send events
678                webview.notify_input_event(InputEvent::Wheel(WheelEvent::new(delta, point)));
679                let scroll_location = ScrollLocation::Delta(-Vector2D::new(dx as f32, dy as f32));
680                webview.notify_scroll_event(scroll_location, point.to_i32());
681            },
682            WindowEvent::Touch(touch) => {
683                webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
684                    winit_phase_to_touch_event_type(touch.phase),
685                    TouchId(touch.id as i32),
686                    Point2D::new(touch.location.x as f32, touch.location.y as f32),
687                )));
688            },
689            WindowEvent::PinchGesture { delta, .. } => {
690                webview.set_pinch_zoom(delta as f32 + 1.0);
691            },
692            WindowEvent::CloseRequested => {
693                state.servo().start_shutting_down();
694            },
695            WindowEvent::ThemeChanged(theme) => {
696                webview.notify_theme_change(match theme {
697                    winit::window::Theme::Light => Theme::Light,
698                    winit::window::Theme::Dark => Theme::Dark,
699                });
700            },
701            WindowEvent::Resized(new_inner_size) => {
702                if self.inner_size.get() != new_inner_size {
703                    self.inner_size.set(new_inner_size);
704                    // This should always be set to inner size
705                    // because we are resizing `SurfmanRenderingContext`.
706                    // See https://github.com/servo/servo/issues/38369#issuecomment-3138378527
707                    self.window_rendering_context.resize(new_inner_size);
708                }
709            },
710            WindowEvent::Ime(ime) => match ime {
711                Ime::Enabled => {
712                    webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
713                        servo::CompositionEvent {
714                            state: servo::CompositionState::Start,
715                            data: String::new(),
716                        },
717                    )));
718                },
719                Ime::Preedit(text, _) => {
720                    webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
721                        servo::CompositionEvent {
722                            state: servo::CompositionState::Update,
723                            data: text,
724                        },
725                    )));
726                },
727                Ime::Commit(text) => {
728                    webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
729                        servo::CompositionEvent {
730                            state: servo::CompositionState::End,
731                            data: text,
732                        },
733                    )));
734                },
735                Ime::Disabled => {
736                    webview.notify_input_event(InputEvent::Ime(ImeEvent::Dismissed));
737                },
738            },
739            _ => {},
740        }
741    }
742
743    #[cfg(feature = "webxr")]
744    fn new_glwindow(
745        &self,
746        event_loop: &ActiveEventLoop,
747    ) -> Rc<dyn servo::webxr::glwindow::GlWindow> {
748        let size = self.winit_window.outer_size();
749
750        let window_attr = winit::window::Window::default_attributes()
751            .with_title("Servo XR".to_string())
752            .with_inner_size(size)
753            .with_visible(false);
754
755        let winit_window = event_loop
756            .create_window(window_attr)
757            .expect("Failed to create window.");
758
759        let pose = Rc::new(XRWindowPose {
760            xr_rotation: Cell::new(Rotation3D::identity()),
761            xr_translation: Cell::new(Vector3D::zero()),
762        });
763        self.xr_window_poses.borrow_mut().push(pose.clone());
764        Rc::new(XRWindow { winit_window, pose })
765    }
766
767    fn winit_window(&self) -> Option<&winit::window::Window> {
768        Some(&self.winit_window)
769    }
770
771    fn toolbar_height(&self) -> Length<f32, DeviceIndependentPixel> {
772        self.toolbar_height.get()
773    }
774
775    fn set_toolbar_height(&self, height: Length<f32, DeviceIndependentPixel>) {
776        if self.toolbar_height() == height {
777            return;
778        }
779        self.toolbar_height.set(height);
780        // Prevent the inner area from being 0 pixels wide or tall
781        // this prevents a crash in the compositor due to invalid surface size
782        self.winit_window.set_min_inner_size(Some(PhysicalSize::new(
783            MIN_INNER_WIDTH,
784            MIN_INNER_HEIGHT.max((self.toolbar_height() * self.hidpi_scale_factor()).0 as i32),
785        )));
786    }
787
788    fn rendering_context(&self) -> Rc<dyn RenderingContext> {
789        self.rendering_context.clone()
790    }
791
792    fn show_ime(
793        &self,
794        _input_type: servo::InputMethodType,
795        _text: Option<(String, i32)>,
796        _multiline: bool,
797        position: servo::webrender_api::units::DeviceIntRect,
798    ) {
799        self.winit_window.set_ime_allowed(true);
800        self.winit_window.set_ime_cursor_area(
801            LogicalPosition::new(
802                position.min.x,
803                position.min.y + (self.toolbar_height.get().0 as i32),
804            ),
805            LogicalSize::new(
806                position.max.x - position.min.x,
807                position.max.y - position.min.y,
808            ),
809        );
810    }
811
812    fn hide_ime(&self) {
813        self.winit_window.set_ime_allowed(false);
814    }
815
816    fn theme(&self) -> servo::Theme {
817        match self.winit_window.theme() {
818            Some(winit::window::Theme::Dark) => servo::Theme::Dark,
819            Some(winit::window::Theme::Light) | None => servo::Theme::Light,
820        }
821    }
822
823    fn maximize(&self, _webview: &WebView) {
824        self.winit_window.set_maximized(true);
825    }
826}
827
828fn winit_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType {
829    match phase {
830        TouchPhase::Started => TouchEventType::Down,
831        TouchPhase::Moved => TouchEventType::Move,
832        TouchPhase::Ended => TouchEventType::Up,
833        TouchPhase::Cancelled => TouchEventType::Cancel,
834    }
835}
836
837#[cfg(any(target_os = "linux", target_os = "windows"))]
838fn load_icon(icon_bytes: &[u8]) -> Icon {
839    let (icon_rgba, icon_width, icon_height) = {
840        use image::{GenericImageView, Pixel};
841        let image = image::load_from_memory(icon_bytes).expect("Failed to load icon");
842        let (width, height) = image.dimensions();
843        let mut rgba = Vec::with_capacity((width * height) as usize * 4);
844        for (_, _, pixel) in image.pixels() {
845            rgba.extend_from_slice(&pixel.to_rgba().0);
846        }
847        (rgba, width, height)
848    };
849    Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to load icon")
850}
851
852#[cfg(feature = "webxr")]
853struct XRWindow {
854    winit_window: winit::window::Window,
855    pose: Rc<XRWindowPose>,
856}
857
858struct XRWindowPose {
859    xr_rotation: Cell<Rotation3D<f32, UnknownUnit, UnknownUnit>>,
860    xr_translation: Cell<Vector3D<f32, UnknownUnit>>,
861}
862
863#[cfg(feature = "webxr")]
864impl servo::webxr::glwindow::GlWindow for XRWindow {
865    fn get_render_target(
866        &self,
867        device: &mut surfman::Device,
868        _context: &mut surfman::Context,
869    ) -> servo::webxr::glwindow::GlWindowRenderTarget {
870        self.winit_window.set_visible(true);
871        let window_handle = self
872            .winit_window
873            .window_handle()
874            .expect("could not get window handle from window");
875        let size = self.winit_window.inner_size();
876        let size = Size2D::new(size.width as i32, size.height as i32);
877        let native_widget = device
878            .connection()
879            .create_native_widget_from_window_handle(window_handle, size)
880            .expect("Failed to create native widget");
881        servo::webxr::glwindow::GlWindowRenderTarget::NativeWidget(native_widget)
882    }
883
884    fn get_rotation(&self) -> Rotation3D<f32, UnknownUnit, UnknownUnit> {
885        self.pose.xr_rotation.get()
886    }
887
888    fn get_translation(&self) -> Vector3D<f32, UnknownUnit> {
889        self.pose.xr_translation.get()
890    }
891
892    fn get_mode(&self) -> servo::webxr::glwindow::GlWindowMode {
893        use servo::servo_config::pref;
894        if pref!(dom_webxr_glwindow_red_cyan) {
895            servo::webxr::glwindow::GlWindowMode::StereoRedCyan
896        } else if pref!(dom_webxr_glwindow_left_right) {
897            servo::webxr::glwindow::GlWindowMode::StereoLeftRight
898        } else if pref!(dom_webxr_glwindow_spherical) {
899            servo::webxr::glwindow::GlWindowMode::Spherical
900        } else if pref!(dom_webxr_glwindow_cubemap) {
901            servo::webxr::glwindow::GlWindowMode::Cubemap
902        } else {
903            servo::webxr::glwindow::GlWindowMode::Blit
904        }
905    }
906
907    fn display_handle(&self) -> raw_window_handle::DisplayHandle<'_> {
908        self.winit_window
909            .display_handle()
910            .expect("Every window should have a display handle")
911    }
912}
913
914impl XRWindowPose {
915    fn handle_xr_translation(&self, input: &KeyboardEvent) {
916        if input.event.state != KeyState::Down {
917            return;
918        }
919        const NORMAL_TRANSLATE: f32 = 0.1;
920        const QUICK_TRANSLATE: f32 = 1.0;
921        let mut x = 0.0;
922        let mut z = 0.0;
923        match input.event.key {
924            Key::Character(ref k) => match &**k {
925                "w" => z = -NORMAL_TRANSLATE,
926                "W" => z = -QUICK_TRANSLATE,
927                "s" => z = NORMAL_TRANSLATE,
928                "S" => z = QUICK_TRANSLATE,
929                "a" => x = -NORMAL_TRANSLATE,
930                "A" => x = -QUICK_TRANSLATE,
931                "d" => x = NORMAL_TRANSLATE,
932                "D" => x = QUICK_TRANSLATE,
933                _ => return,
934            },
935            _ => return,
936        };
937        let (old_x, old_y, old_z) = self.xr_translation.get().to_tuple();
938        let vec = Vector3D::new(x + old_x, old_y, z + old_z);
939        self.xr_translation.set(vec);
940    }
941
942    fn handle_xr_rotation(&self, input: &KeyEvent, modifiers: ModifiersState) {
943        if input.state != ElementState::Pressed {
944            return;
945        }
946        let mut x = 0.0;
947        let mut y = 0.0;
948        match input.logical_key {
949            LogicalKey::Named(WinitNamedKey::ArrowUp) => x = 1.0,
950            LogicalKey::Named(WinitNamedKey::ArrowDown) => x = -1.0,
951            LogicalKey::Named(WinitNamedKey::ArrowLeft) => y = 1.0,
952            LogicalKey::Named(WinitNamedKey::ArrowRight) => y = -1.0,
953            _ => return,
954        };
955        if modifiers.shift_key() {
956            x *= 10.0;
957            y *= 10.0;
958        }
959        let x: Rotation3D<_, UnknownUnit, UnknownUnit> = Rotation3D::around_x(Angle::degrees(x));
960        let y: Rotation3D<_, UnknownUnit, UnknownUnit> = Rotation3D::around_y(Angle::degrees(y));
961        let rotation = self.xr_rotation.get().then(&x).then(&y);
962        self.xr_rotation.set(rotation);
963    }
964}