blob: 5cf1c5f3e8d73fc72e38d0235550b90fab9c5905 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::rc::Rc;
use std::time::Duration;
use anyhow::Context;
use anyhow::Result;
use base::error;
use base::warn;
use base::Tube;
use linux_input_sys::virtio_input_event;
#[cfg(feature = "kiwi")]
use vm_control::ServiceSendToGpu;
use winapi::shared::minwindef::LPARAM;
use winapi::shared::minwindef::LRESULT;
use winapi::shared::minwindef::TRUE;
use winapi::shared::minwindef::UINT;
use winapi::shared::minwindef::WPARAM;
use winapi::um::winuser::*;
use super::window::MessagePacket;
use super::window::Window;
use super::window_message_dispatcher::DisplayEventDispatcher;
use super::ObjectId;
use crate::EventDevice;
use crate::EventDeviceKind;
// Once a window message is added to the message queue, if it is not retrieved within 5 seconds,
// Windows will mark the application as "Not Responding", so we'd better finish processing any
// message within this timeout and retrieve the next one.
// https://docs.microsoft.com/en-us/windows/win32/win7appqual/preventing-hangs-in-windows-applications
#[allow(dead_code)]
pub(crate) const HANDLE_WINDOW_MESSAGE_TIMEOUT: Duration = Duration::from_secs(5);
/// Thread message for killing the window during a `WndProcThread` drop. This indicates an error
/// within crosvm that internally causes the WndProc thread to be dropped, rather than when the
/// user/service initiates the window to be killed.
/// TODO(b/238678893): During such an abnormal event, window messages might not be reliable anymore.
/// Relying on destructors might be a safer choice.
pub(crate) const WM_USER_WNDPROC_THREAD_DROP_KILL_WINDOW_INTERNAL: UINT = WM_USER;
// Message for handling the change in host viewport. This is sent when the host window size changes
// and we need to render to a different part of the window. The new width and height are sent as the
// low/high word of lParam.
pub(crate) const WM_USER_HOST_VIEWPORT_CHANGE_INTERNAL: UINT = WM_USER + 1;
/// Thread message for handling the message sent from the GPU worker thread. A pointer to enum
/// `DisplaySendToWndProc` is sent as the lParam. Note that the receiver is responsible for
/// destructing the message.
pub(crate) const WM_USER_HANDLE_DISPLAY_MESSAGE_INTERNAL: UINT = WM_USER + 2;
/// Struct for resources used for window message handler creation.
pub struct MessageHandlerResources {
pub display_event_dispatcher: DisplayEventDispatcher,
pub gpu_main_display_tube: Option<Rc<Tube>>,
}
pub type CreateMessageHandlerFunction<T> =
Box<dyn FnOnce(&Window, MessageHandlerResources) -> Result<T>>;
/// Called after the handler creation finishes. The argument indicates whether that was successful.
pub type CreateMessageHandlerCallback = Box<dyn FnOnce(bool)>;
/// Messages sent from the GPU worker thread to the WndProc thread.
pub enum DisplaySendToWndProc<T: HandleWindowMessage> {
CreateSurface {
function: CreateMessageHandlerFunction<T>,
callback: CreateMessageHandlerCallback,
},
ImportEventDevice {
event_device_id: ObjectId,
event_device: EventDevice,
},
/// Handle a guest -> host input_event.
HandleEventDevice(ObjectId),
}
/// A trait for processing messages retrieved from the window message queue. All messages routed to
/// a trait object would target the same window.
pub trait HandleWindowMessage {
/// Called once when it is safe to assume all future messages targeting this window will be
/// dispatched to this handler.
fn on_message_dispatcher_attached(&mut self, _window: &Window) {}
/// Called when processing `WM_ACTIVATE`.
fn on_activate(&mut self, _window: &Window, _w_param: WPARAM) {}
/// Called when processing `WM_MOUSEACTIVATE`. See possible return values:
/// https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mouseactivate#return-value
fn on_mouse_activate(&self, _l_param: LPARAM) -> i32 {
MA_NOACTIVATE as i32
}
/// Called when processing `WM_SETFOCUS`.
fn on_set_focus(&mut self) {}
/// Called when processing `WM_INPUT`.
fn on_raw_input(&mut self, _window: &Window, _l_param: LPARAM) {}
/// Called when processing `WM_MOUSEMOVE`.
fn on_mouse_move(&mut self, _w_param: WPARAM, _l_param: LPARAM) {}
/// Called when processing `WM_LBUTTONDOWN` and `WM_LBUTTONUP`.
fn on_mouse_button_left(&mut self, _window: &Window, _is_down: bool, _l_param: LPARAM) {}
/// Called when processing `WM_RBUTTONDOWN` and `WM_RBUTTONUP`.
fn on_mouse_button_right(&mut self, _is_down: bool) {}
/// Called when processing `WM_MOUSEWHEEL`.
fn on_mouse_wheel(&mut self, _window: &Window, _w_param: WPARAM, _l_param: LPARAM) {}
/// Called when processing `WM_SETCURSOR`. It should return true if the cursor has been handled
/// and the default processing should be skipped.
fn on_set_cursor(&mut self, _window: &Window) -> bool {
false
}
/// Called when processing `WM_KEYDOWN`, `WM_KEYUP`, `WM_SYSKEYDOWN` and `WM_SYSKEYUP`.
fn on_key(&mut self, _window: &Window, _key_down: bool, _w_param: WPARAM, _l_param: LPARAM) {}
/// Called when processing `WM_WINDOWPOSCHANGING`.
fn on_window_pos_changing(&mut self, _window: &Window, _l_param: LPARAM) {}
/// Called when processing `WM_SIZING`.
fn on_window_size_changing(&mut self, _window: &Window, _w_param: WPARAM, _l_param: LPARAM) {}
/// Called when processing `WM_WINDOWPOSCHANGED`. It should return true if it is intended to
/// skip default processing, in which case `WM_SIZE` and `WM_MOVE` won't be sent to the window.
fn on_window_pos_changed(&mut self, _window: &Window, _l_param: LPARAM) -> bool {
false
}
/// Called when processing `WM_SIZE`.
fn on_window_size_changed(&mut self, _window: &Window, _w_param: WPARAM, _l_param: LPARAM) {}
/// Called when processing `WM_ENTERSIZEMOVE`.
fn on_enter_size_move(&mut self) {}
/// Called when processing `WM_EXITSIZEMOVE`.
fn on_exit_size_move(&mut self, _window: &Window) {}
/// Called when processing `WM_DISPLAYCHANGE`.
fn on_display_change(&mut self, _window: &Window) {}
/// Called when processing requests from the service.
#[cfg(feature = "kiwi")]
fn on_handle_service_message(&mut self, _window: &Window, _message: &ServiceSendToGpu) {}
/// Called when processing inbound events from event devices.
fn on_handle_event_device(
&mut self,
_window: &Window,
_event_device_kind: EventDeviceKind,
_event: virtio_input_event,
) {
}
/// Called when processing `WM_USER_HOST_VIEWPORT_CHANGE_INTERNAL`.
fn on_host_viewport_change(&mut self, _window: &Window, _l_param: LPARAM) {}
/// Called when processing `WM_CLOSE`. It should return true if the window should be destroyed
/// immediately.
fn on_close(&mut self) -> bool {
true
}
/// Called when processing `WM_DESTROY`.
fn on_destroy(&mut self) {}
}
/// This class drives the underlying `HandleWindowMessage` trait object to process window messages
/// retrieved from the message pump. Note that we rely on the owner of `WindowMessageProcessor`
/// object to drop it before the underlying window is completely gone.
pub(crate) struct WindowMessageProcessor<T: HandleWindowMessage> {
window: Window,
message_handler: Option<T>,
}
impl<T: HandleWindowMessage> WindowMessageProcessor<T> {
/// # Safety
/// The owner of `WindowMessageProcessor` object is responsible for dropping it before we finish
/// processing `WM_NCDESTROY`, because the window handle will become invalid afterwards.
pub unsafe fn new(window: Window) -> Self {
Self {
window,
message_handler: None,
}
}
pub fn create_message_handler(
&mut self,
create_handler_func: CreateMessageHandlerFunction<T>,
handler_resources: MessageHandlerResources,
) -> Result<()> {
create_handler_func(&self.window, handler_resources)
.map(|handler| {
self.message_handler.replace(handler);
if let Some(handler) = &mut self.message_handler {
handler.on_message_dispatcher_attached(&self.window);
}
})
.context("When creating window message handler")
}
pub fn handle_event_device(
&mut self,
event_device_kind: EventDeviceKind,
event: virtio_input_event,
) {
match &mut self.message_handler {
Some(handler) => handler.on_handle_event_device(&self.window, event_device_kind, event),
None => error!(
"Cannot handle event device because window message handler has not been created!",
),
}
}
#[cfg(feature = "kiwi")]
pub fn handle_service_message(&mut self, message: &ServiceSendToGpu) {
match &mut self.message_handler {
Some(handler) => handler.on_handle_service_message(&self.window, message),
None => error!(
"Cannot handle {:?} because window message handler has not been created!",
message
),
}
}
pub fn process_message(&mut self, packet: &MessagePacket) -> LRESULT {
let MessagePacket {
msg,
w_param,
l_param,
} = *packet;
// The handler may read window states so we should update them first.
self.window.update_states(msg, w_param);
let handler = match &mut self.message_handler {
Some(handler) => handler,
None => {
warn!(
"Skipping processing {:?} because message handler has not been created!",
packet
);
return self.window.default_process_message(packet);
}
};
match msg {
WM_ACTIVATE => {
handler.on_activate(&self.window, w_param);
0
}
WM_MOUSEACTIVATE => handler.on_mouse_activate(l_param) as LRESULT,
WM_SETFOCUS => {
handler.on_set_focus();
0
}
WM_INPUT => {
handler.on_raw_input(&self.window, l_param);
self.window.default_process_message(packet)
}
WM_MOUSEMOVE => {
handler.on_mouse_move(w_param, l_param);
0
}
WM_LBUTTONDOWN | WM_LBUTTONUP => {
let is_down = msg == WM_LBUTTONDOWN;
handler.on_mouse_button_left(&self.window, is_down, l_param);
0
}
WM_RBUTTONDOWN | WM_RBUTTONUP => {
handler.on_mouse_button_right(/* is_down= */ msg == WM_RBUTTONDOWN);
0
}
WM_MOUSEWHEEL => {
handler.on_mouse_wheel(&self.window, w_param, l_param);
0
}
WM_SETCURSOR => {
if handler.on_set_cursor(&self.window) {
return 0;
}
self.window.default_process_message(packet)
}
WM_KEYDOWN | WM_KEYUP | WM_SYSKEYDOWN | WM_SYSKEYUP => {
let key_down = msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN;
handler.on_key(&self.window, key_down, w_param, l_param);
0
}
WM_WINDOWPOSCHANGING => {
handler.on_window_pos_changing(&self.window, l_param);
0
}
WM_SIZING => {
handler.on_window_size_changing(&self.window, w_param, l_param);
TRUE as LRESULT
}
WM_WINDOWPOSCHANGED => {
if handler.on_window_pos_changed(&self.window, l_param) {
return 0;
}
self.window.default_process_message(packet)
}
WM_SIZE => {
handler.on_window_size_changed(&self.window, w_param, l_param);
0
}
WM_ENTERSIZEMOVE => {
handler.on_enter_size_move();
0
}
WM_EXITSIZEMOVE => {
handler.on_exit_size_move(&self.window);
0
}
WM_DISPLAYCHANGE => {
handler.on_display_change(&self.window);
0
}
WM_USER_HOST_VIEWPORT_CHANGE_INTERNAL => {
handler.on_host_viewport_change(&self.window, l_param);
0
}
WM_CLOSE => {
if handler.on_close() {
if let Err(e) = self.window.destroy() {
error!("Failed to destroy window on WM_CLOSE: {:?}", e);
}
}
0
}
WM_DESTROY => {
handler.on_destroy();
0
}
_ => self.window.default_process_message(packet),
}
}
pub fn window(&self) -> &Window {
&self.window
}
}