1#![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 inner_size: Cell<PhysicalSize<u32>>,
69 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 rendering_context: Rc<OffscreenRenderingContext>,
80 window_rendering_context: Rc<WindowRenderingContext>,
84 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 .with_visible(false);
106
107 #[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 {
155 let details = window_rendering_context.surfman_details();
156 setup_gl_accelerated_media(details.0, details.1);
157 }
158
159 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 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 return;
202 } else {
203 (KeyboardEvent::default(), None)
206 };
207 event.event.key = Key::Character(character.to_string());
208
209 if event.event.state == KeyState::Down {
210 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 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 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 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 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 }
275
276 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 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 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 .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 .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 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 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 if dy.abs() >= dx.abs() {
672 dx = 0.0;
673 } else {
674 dy = 0.0;
675 }
676
677 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 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 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}