Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/gpu_display/src/gpu_display_win/window_message_dispatcher.rs
5394 views
1
// Copyright 2022 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
use std::cell::RefCell;
6
use std::collections::HashMap;
7
use std::io::ErrorKind;
8
use std::marker::PhantomPinned;
9
use std::os::raw::c_void;
10
use std::pin::Pin;
11
use std::rc::Rc;
12
13
use anyhow::bail;
14
use anyhow::Context;
15
use anyhow::Result;
16
use base::error;
17
use base::info;
18
use base::Event;
19
use base::Tube;
20
use cros_tracing::trace_event;
21
use linux_input_sys::virtio_input_event;
22
#[cfg(feature = "kiwi")]
23
use vm_control::ServiceSendToGpu;
24
use win_util::win32_wide_string;
25
use winapi::shared::minwindef::LRESULT;
26
use winapi::shared::windef::HWND;
27
use winapi::um::winuser::DefWindowProcW;
28
use winapi::um::winuser::GetForegroundWindow;
29
use winapi::um::winuser::PostQuitMessage;
30
use winapi::um::winuser::RemovePropW;
31
use winapi::um::winuser::HRAWINPUT;
32
use winapi::um::winuser::WM_CLOSE;
33
use winapi::um::winuser::WM_INPUT;
34
use winapi::um::winuser::WM_NCDESTROY;
35
36
use super::keyboard_input_manager::KeyboardInputManager;
37
use super::window::BasicWindow;
38
use super::window::GuiWindow;
39
use super::window::MessageOnlyWindow;
40
use super::window::MessagePacket;
41
use super::window_message_processor::*;
42
use super::MouseMode;
43
use super::ObjectId;
44
use crate::EventDevice;
45
use crate::EventDeviceKind;
46
47
/// The pointer to dispatcher will be stored with HWND using `SetPropW()` with the following name.
48
pub(crate) const DISPATCHER_PROPERTY_NAME: &str = "PROP_WND_MSG_DISPATCHER";
49
50
/// Tracks the ids of all event devices in one kind. For each kind, we either have one event device
51
/// shared by all scanouts (guest displays), or one per scanout.
52
enum EventDeviceIds {
53
GlobalDevice(ObjectId),
54
PerScanoutDevices(Vec<ObjectId>),
55
}
56
57
/// This class is used to dispatch input events from the display to the guest input device. It is
58
/// also used to receive events from the input device (e.g. guest) on behalf of the window so they
59
/// can be routed back to the window for processing.
60
#[derive(Clone)]
61
pub struct DisplayEventDispatcher {
62
event_devices: Rc<RefCell<HashMap<ObjectId, EventDevice>>>,
63
event_device_ids: Rc<RefCell<HashMap<EventDeviceKind, EventDeviceIds>>>,
64
}
65
66
impl DisplayEventDispatcher {
67
pub fn new() -> Self {
68
Self {
69
event_devices: Default::default(),
70
event_device_ids: Default::default(),
71
}
72
}
73
74
pub fn dispatch(
75
&self,
76
window: &GuiWindow,
77
events: &[virtio_input_event],
78
device_kind: EventDeviceKind,
79
) {
80
if let Some(event_device_id) = self.find_event_device_id(device_kind, window.scanout_id()) {
81
if let Some(event_device) = self.event_devices.borrow_mut().get_mut(&event_device_id) {
82
if let Err(e) = event_device.send_report(events.iter().cloned()) {
83
error!("Failed to send events to event device: {}", e);
84
}
85
}
86
}
87
}
88
89
pub fn read_from_device(
90
&self,
91
event_device_id: ObjectId,
92
) -> Option<(EventDeviceKind, virtio_input_event)> {
93
if let Some(device) = self.event_devices.borrow_mut().get(&event_device_id) {
94
match device.recv_event_encoded() {
95
Ok(event) => return Some((device.kind(), event)),
96
Err(e) if e.kind() == ErrorKind::WouldBlock => return None,
97
Err(e) => error!(
98
"failed to read from event device {:?} (index: {}): {:?}",
99
device.kind(),
100
event_device_id,
101
e
102
),
103
}
104
} else {
105
error!(
106
"notified to read from event device {:?} but do not have a device with that ID",
107
event_device_id
108
);
109
}
110
None
111
}
112
113
fn import_event_device(&mut self, event_device_id: ObjectId, event_device: EventDevice) {
114
info!("Importing {:?} (ID: {:?})", event_device, event_device_id);
115
let device_kind = event_device.kind();
116
let same_kind_device_ids = match device_kind {
117
EventDeviceKind::Touchscreen => {
118
// Temporarily removing from `self.event_device_ids`. Will be reinserted after
119
// adding `event_device_id`.
120
let mut per_scanout_device_ids = self
121
.event_device_ids
122
.borrow_mut()
123
.remove(&device_kind)
124
.and_then(|imported_device_ids| match imported_device_ids {
125
EventDeviceIds::PerScanoutDevices(ids) => Some(ids),
126
_ => unreachable!(),
127
})
128
.unwrap_or_default();
129
per_scanout_device_ids.push(event_device_id);
130
EventDeviceIds::PerScanoutDevices(per_scanout_device_ids)
131
}
132
_ => EventDeviceIds::GlobalDevice(event_device_id),
133
};
134
self.event_device_ids
135
.borrow_mut()
136
.insert(device_kind, same_kind_device_ids);
137
self.event_devices
138
.borrow_mut()
139
.insert(event_device_id, event_device);
140
}
141
142
fn find_event_device_id(
143
&self,
144
device_kind: EventDeviceKind,
145
scanout_id: u32,
146
) -> Option<ObjectId> {
147
self.event_device_ids
148
.borrow()
149
.get(&device_kind)
150
.and_then(|same_kind_device_ids| match same_kind_device_ids {
151
EventDeviceIds::GlobalDevice(event_device_id) => Some(*event_device_id),
152
EventDeviceIds::PerScanoutDevices(event_device_ids) => {
153
event_device_ids.get(scanout_id as usize).cloned()
154
}
155
})
156
}
157
}
158
159
impl Default for DisplayEventDispatcher {
160
fn default() -> Self {
161
Self::new()
162
}
163
}
164
165
/// This struct is used for dispatching window messages. Note that messages targeting the WndProc
166
/// thread itself shouldn't be posted using `PostThreadMessageW()`, but posted as window messages to
167
/// `message_router_window`, so that they won't get lost when the modal loop is running.
168
///
169
/// This struct should be created before the WndProc thread enters the message loop. Once all
170
/// windows tracked by it are destroyed, it will signal exiting the message loop, and then it can be
171
/// dropped.
172
pub(crate) struct WindowMessageDispatcher {
173
message_router_window: Option<MessageOnlyWindow>,
174
vacant_gui_windows: HashMap<HWND, WindowResources>,
175
in_use_gui_windows: HashMap<HWND, WindowMessageProcessor>,
176
// We have a one-to-one mapping between virtio-gpu scanouts and GUI window surfaces. The index
177
// of the GUI window in this Vec will be the same as the associated scanout's id.
178
// These handles are only used for hashmap queries. Do not directly call Windows APIs on them.
179
gui_window_handles: Vec<HWND>,
180
keyboard_input_manager: KeyboardInputManager,
181
display_event_dispatcher: DisplayEventDispatcher,
182
gpu_main_display_tube: Option<Rc<Tube>>,
183
close_requested_event: Event,
184
// The dispatcher is pinned so that its address in the memory won't change, and it is always
185
// safe to use the pointer to it stored in the window.
186
_pinned_marker: PhantomPinned,
187
}
188
189
impl WindowMessageDispatcher {
190
/// This function should only be called once from the WndProc thread. It will take the ownership
191
/// of the `GuiWindow` objects, and drop them before the underlying windows are completely gone.
192
/// TODO(b/238680252): This should be good enough for supporting multi-windowing, but we should
193
/// revisit it if we also want to manage some child windows of the crosvm window.
194
pub fn new(
195
message_router_window: MessageOnlyWindow,
196
gui_windows: Vec<GuiWindow>,
197
gpu_main_display_tube: Option<Rc<Tube>>,
198
close_requested_event: Event,
199
) -> Result<Pin<Box<Self>>> {
200
static CONTEXT_MESSAGE: &str = "When creating WindowMessageDispatcher";
201
let display_event_dispatcher = DisplayEventDispatcher::new();
202
let gui_window_handles = gui_windows
203
.iter()
204
.map(|window| {
205
// SAFETY:
206
// Safe because we will only use these handles to query hashmaps.
207
unsafe { window.handle() }
208
})
209
.collect();
210
let mut dispatcher = Box::pin(Self {
211
message_router_window: Some(message_router_window),
212
vacant_gui_windows: Default::default(), // To be updated.
213
in_use_gui_windows: Default::default(),
214
gui_window_handles,
215
keyboard_input_manager: KeyboardInputManager::new(display_event_dispatcher.clone()),
216
display_event_dispatcher,
217
gpu_main_display_tube,
218
close_requested_event,
219
_pinned_marker: PhantomPinned,
220
});
221
dispatcher
222
.as_mut()
223
.attach_thread_message_router()
224
.context(CONTEXT_MESSAGE)?;
225
dispatcher
226
.as_mut()
227
.create_window_resources(gui_windows)
228
.context(CONTEXT_MESSAGE)?;
229
Ok(dispatcher)
230
}
231
232
/// # Safety
233
/// The caller must not use the handle after the message loop terminates.
234
pub unsafe fn message_router_handle(&self) -> Option<HWND> {
235
self.message_router_window
236
.as_ref()
237
.map(|router| router.handle())
238
}
239
240
#[cfg(feature = "kiwi")]
241
pub fn process_service_message(self: Pin<&mut Self>, message: &ServiceSendToGpu) {
242
if matches!(message, ServiceSendToGpu::Shutdown) {
243
info!("Processing ShutdownRequest from service");
244
// Safe because we won't move the dispatcher out of the returned mutable reference.
245
unsafe { self.get_unchecked_mut().request_shutdown_gpu_display() };
246
return;
247
}
248
249
// TODO(b/306024335): `ServiceSendToGpu` should specify the targeted display id.
250
// Safe because we won't move the dispatcher out of the returned mutable reference.
251
let primary_window_handle = self.primary_window_handle();
252
match unsafe {
253
self.get_unchecked_mut()
254
.in_use_gui_windows
255
.get_mut(&primary_window_handle)
256
} {
257
Some(processor) => processor.handle_service_message(message),
258
None => error!("Cannot handle service message because primary window is not in-use!"),
259
}
260
}
261
262
/// Returns `Some` if the message is processed by the targeted window.
263
pub fn dispatch_window_message(
264
&mut self,
265
hwnd: HWND,
266
packet: &MessagePacket,
267
) -> Option<LRESULT> {
268
// First, check if the message is targeting the wndproc thread itself.
269
if let Some(router) = &self.message_router_window {
270
if router.is_same_window(hwnd) {
271
return if packet.msg == WM_INPUT {
272
// The message router window is the sink for all WM_INPUT messages targeting
273
// this application. We would reroute WM_INPUT to the current foreground window.
274
// SAFETY: trivially safe
275
let foreground_hwnd = unsafe { GetForegroundWindow() };
276
if let Some(processor) = self.in_use_gui_windows.get_mut(&foreground_hwnd) {
277
processor.process_general_message(
278
GeneralMessage::RawInputEvent(packet.l_param as HRAWINPUT),
279
&self.keyboard_input_manager,
280
);
281
}
282
// Always do default processing "so the system can perform cleanup".
283
// https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-input#parameters
284
router.default_process_message(packet);
285
Some(0)
286
} else {
287
Some(self.process_simulated_thread_message(hwnd, packet))
288
};
289
}
290
}
291
292
// Second, check if this message indicates any lifetime events of GUI windows.
293
if let Some(ret) = self.handle_gui_window_lifetime_message(hwnd, packet) {
294
return Some(ret);
295
}
296
297
// Third, check if the message is targeting an in-use GUI window.
298
self.in_use_gui_windows
299
.get_mut(&hwnd)
300
.map(|processor| processor.process_window_message(packet, &self.keyboard_input_manager))
301
}
302
303
// TODO(b/306407787): We won't need this once we have full support for multi-window.
304
fn primary_window_handle(&self) -> HWND {
305
self.gui_window_handles[0]
306
}
307
308
fn attach_thread_message_router(self: Pin<&mut Self>) -> Result<()> {
309
let dispatcher_ptr = &*self as *const Self;
310
// SAFETY:
311
// Safe because we won't move the dispatcher out of it.
312
match unsafe { &self.get_unchecked_mut().message_router_window } {
313
// SAFETY:
314
// Safe because we guarantee the dispatcher outlives the thread message router.
315
Some(router) => unsafe { Self::store_pointer_in_window(dispatcher_ptr, router) },
316
None => bail!("Thread message router not found, cannot associate with dispatcher!"),
317
}
318
}
319
320
fn create_window_resources(self: Pin<&mut Self>, windows: Vec<GuiWindow>) -> Result<()> {
321
// SAFETY:
322
// because we won't move the dispatcher out of it.
323
let pinned_dispatcher = unsafe { self.get_unchecked_mut() };
324
for window in windows.into_iter() {
325
if !window.is_valid() {
326
// SAFETY:
327
// Safe because we are just logging the handle value.
328
bail!("Window {:p} is invalid!", unsafe { window.handle() });
329
}
330
331
// SAFETY:
332
// because we guarantee the dispatcher outlives our GUI windows.
333
unsafe { Self::store_pointer_in_window(&*pinned_dispatcher, &window)? };
334
335
pinned_dispatcher.vacant_gui_windows.insert(
336
// SAFETY:
337
// Safe because this handle is only used as the hashmap kay.
338
unsafe { window.handle() },
339
// SAFETY:
340
// Safe the dispatcher will take care of the lifetime of the window.
341
unsafe { WindowResources::new(window) },
342
);
343
}
344
Ok(())
345
}
346
347
/// Processes messages targeting the WndProc thread itself. Note that these messages are not
348
/// posted using `PostThreadMessageW()`, but posted to `message_router_window` as window
349
/// messages (hence "simulated"), so they won't get lost if the modal loop is running.
350
fn process_simulated_thread_message(
351
&mut self,
352
message_router_hwnd: HWND,
353
packet: &MessagePacket,
354
) -> LRESULT {
355
let MessagePacket {
356
msg,
357
w_param,
358
l_param,
359
} = *packet;
360
match msg {
361
WM_USER_HANDLE_DISPLAY_MESSAGE_INTERNAL => {
362
let _trace_event =
363
trace_event!(gpu_display, "WM_USER_HANDLE_DISPLAY_MESSAGE_INTERNAL");
364
// SAFETY:
365
// Safe because the sender gives up the ownership and expects the receiver to
366
// destruct the message.
367
let message = unsafe { Box::from_raw(l_param as *mut DisplaySendToWndProc) };
368
self.handle_display_message(*message);
369
}
370
WM_USER_SHUTDOWN_WNDPROC_THREAD_INTERNAL => {
371
let _trace_event =
372
trace_event!(gpu_display, "WM_USER_SHUTDOWN_WNDPROC_THREAD_INTERNAL");
373
self.shutdown();
374
}
375
_ => {
376
let _trace_event =
377
trace_event!(gpu_display, "WM_OTHER_MESSAGE_ROUTER_WINDOW_MESSAGE");
378
// SAFETY:
379
// Safe because we are processing a message targeting the message router window.
380
return unsafe { DefWindowProcW(message_router_hwnd, msg, w_param, l_param) };
381
}
382
}
383
0
384
}
385
386
fn handle_display_message(&mut self, message: DisplaySendToWndProc) {
387
match message {
388
DisplaySendToWndProc::CreateSurface {
389
scanout_id,
390
function,
391
callback,
392
} => {
393
callback(self.create_surface(scanout_id, function));
394
}
395
DisplaySendToWndProc::ReleaseSurface { surface_id } => self.release_surface(surface_id),
396
DisplaySendToWndProc::ImportEventDevice {
397
event_device_id,
398
event_device,
399
} => {
400
self.display_event_dispatcher
401
.import_event_device(event_device_id, event_device);
402
}
403
DisplaySendToWndProc::HandleEventDevice(event_device_id) => {
404
self.handle_event_device(event_device_id)
405
}
406
DisplaySendToWndProc::SetMouseMode {
407
surface_id,
408
mouse_mode,
409
} => self.set_mouse_mode(surface_id, mouse_mode),
410
}
411
}
412
413
fn handle_event_device(&mut self, event_device_id: ObjectId) {
414
// TODO(b/306407787): Events should be routed to the correct window.
415
match self
416
.in_use_gui_windows
417
.get_mut(&self.primary_window_handle())
418
{
419
Some(processor) => {
420
if let Some((event_device_kind, event)) = self
421
.display_event_dispatcher
422
.read_from_device(event_device_id)
423
{
424
processor.process_general_message(
425
GeneralMessage::GuestEvent {
426
event_device_kind,
427
event,
428
},
429
&self.keyboard_input_manager,
430
);
431
}
432
}
433
None => {
434
error!("Cannot handle event device because primary window is not in-use!")
435
}
436
}
437
}
438
439
fn set_mouse_mode(&mut self, surface_id: u32, mouse_mode: MouseMode) {
440
match self
441
.in_use_gui_windows
442
.iter_mut()
443
.find(|(_, processor)| processor.surface_id() == surface_id)
444
{
445
Some(iter) => iter.1.process_general_message(
446
GeneralMessage::SetMouseMode(mouse_mode),
447
&self.keyboard_input_manager,
448
),
449
None => error!(
450
"Can't set mouse mode for surface {} because it is not in-use!",
451
surface_id
452
),
453
}
454
}
455
456
/// Returns true if the surface is created successfully.
457
fn create_surface(
458
&mut self,
459
scanout_id: u32,
460
create_surface_func: CreateSurfaceFunction,
461
) -> bool {
462
// virtio-gpu prefers to use the lowest available scanout id when creating a new surface, so
463
// here we implictly prefer the primary window (associated with scanout 0).
464
let hwnd = match self.gui_window_handles.get(scanout_id as usize) {
465
Some(hwnd) => *hwnd,
466
None => {
467
error!("Invalid scanout id {}!", scanout_id);
468
return false;
469
}
470
};
471
let window_resources = match self.vacant_gui_windows.remove(&hwnd) {
472
Some(resources) => resources,
473
None => {
474
error!(
475
"GUI window associated with scanout {} is not vacant!",
476
scanout_id
477
);
478
return false;
479
}
480
};
481
let surface_resources = SurfaceResources {
482
display_event_dispatcher: self.display_event_dispatcher.clone(),
483
gpu_main_display_tube: self.gpu_main_display_tube.clone(),
484
};
485
// SAFETY:
486
// Safe because the dispatcher will take care of the lifetime of the window.
487
match unsafe {
488
WindowMessageProcessor::new(create_surface_func, surface_resources, window_resources)
489
} {
490
Ok(processor) => {
491
self.in_use_gui_windows.insert(hwnd, processor);
492
self.in_use_gui_windows
493
.get_mut(&hwnd)
494
.expect("It is just inserted")
495
.process_general_message(
496
GeneralMessage::MessageDispatcherAttached,
497
&self.keyboard_input_manager,
498
);
499
true
500
}
501
Err(e) => {
502
error!("Failed to create surface: {:?}", e);
503
false
504
}
505
}
506
}
507
508
fn release_surface(&mut self, surface_id: u32) {
509
match self
510
.in_use_gui_windows
511
.iter()
512
.find(|(_, processor)| processor.surface_id() == surface_id)
513
{
514
Some(iter) => {
515
self.try_release_surface_and_dissociate_gui_window(*iter.0);
516
}
517
None => error!(
518
"Can't release surface {} because there is no window associated with it!",
519
surface_id
520
),
521
}
522
}
523
524
/// Returns true if `hwnd` points to an in-use GUI window.
525
fn try_release_surface_and_dissociate_gui_window(&mut self, hwnd: HWND) -> bool {
526
if let Some(processor) = self.in_use_gui_windows.remove(&hwnd) {
527
// SAFETY:
528
// Safe because the dispatcher will take care of the lifetime of the window.
529
self.vacant_gui_windows.insert(hwnd, unsafe {
530
processor.release_surface_and_take_window_resources()
531
});
532
return true;
533
}
534
false
535
}
536
537
/// # Safety
538
/// The caller is responsible for keeping the pointer valid until it is removed from the window.
539
unsafe fn store_pointer_in_window(
540
pointer: *const Self,
541
window: &dyn BasicWindow,
542
) -> Result<()> {
543
window
544
.set_property(DISPATCHER_PROPERTY_NAME, pointer as *mut c_void)
545
.context("When storing message dispatcher pointer")
546
}
547
548
/// Returns Some if this is a GUI window lifetime related message and if we have handled it.
549
fn handle_gui_window_lifetime_message(
550
&mut self,
551
hwnd: HWND,
552
packet: &MessagePacket,
553
) -> Option<LRESULT> {
554
// Windows calls WndProc as a subroutine when we call `DestroyWindow()`. So, when handling
555
// WM_DESTROY/WM_NCDESTROY for one window, we would avoid calling `DestroyWindow()` on
556
// another window to avoid recursively mutably borrowing self. Instead, we do
557
// `DestroyWindow()` and clean up all associated resources on WM_CLOSE. The long-term fix is
558
// tracked by b/314379499.
559
match packet.msg {
560
WM_CLOSE => {
561
// If the window is in-use, return it to the vacant window pool.
562
// TODO(b/314309389): This only frees the `Surface` in WndProc thread, while the
563
// corresponding guest display isn't unplugged. We might need to figure out a way to
564
// inform `gpu_control_tube` to remove that display.
565
if self.try_release_surface_and_dissociate_gui_window(hwnd) {
566
// If the service isn't connnected (e.g. when debugging the emulator alone), we
567
// would request shutdown if no window is in-use anymore.
568
if self.gpu_main_display_tube.is_none() && self.in_use_gui_windows.is_empty() {
569
self.request_shutdown_gpu_display();
570
}
571
return Some(0);
572
}
573
}
574
// Don't use any reference to `self` when handling WM_DESTROY/WM_NCDESTROY, since it is
575
// likely already mutably borrowed when handling WM_CLOSE on the same stack.
576
WM_NCDESTROY => {
577
info!("Window {:p} destroyed", hwnd);
578
// We don't care if removing the dispatcher pointer succeeds, since this window will
579
// be completely gone right after this function returns.
580
let property = win32_wide_string(DISPATCHER_PROPERTY_NAME);
581
// SAFETY:
582
// Safe because `hwnd` is valid, and `property` lives longer than the function call.
583
unsafe { RemovePropW(hwnd, property.as_ptr()) };
584
return Some(0);
585
}
586
_ => (),
587
}
588
None
589
}
590
591
/// Signals GpuDisplay to close. This is not going to release any resources right away, but the
592
/// closure of GpuDisplay will trigger shutting down the entire VM, and all resources will be
593
/// released by then.
594
fn request_shutdown_gpu_display(&self) {
595
if let Err(e) = self.close_requested_event.signal() {
596
error!("Failed to signal close requested event: {}", e);
597
}
598
}
599
600
/// Destroys all GUI windows and the message router window, and requests exiting message loop.
601
fn shutdown(&mut self) {
602
info!("Shutting down all windows and message loop");
603
604
// Destroy all GUI windows.
605
// Note that Windows calls WndProc as a subroutine when we call `DestroyWindow()`, we have
606
// to store window handles in a Vec and query the hashmaps every time rather than simply
607
// iterating through the hashmaps.
608
let in_use_handles: Vec<HWND> = self.in_use_gui_windows.keys().cloned().collect();
609
for hwnd in in_use_handles.iter() {
610
if let Some(processor) = self.in_use_gui_windows.remove(hwnd) {
611
// SAFETY:
612
// Safe because we are dropping the `WindowResources` before the window is gone.
613
let resources = unsafe { processor.release_surface_and_take_window_resources() };
614
if let Err(e) = resources.window().destroy() {
615
error!("Failed to destroy in-use GUI window: {:?}", e);
616
}
617
}
618
}
619
620
let vacant_handles: Vec<HWND> = self.vacant_gui_windows.keys().cloned().collect();
621
for hwnd in vacant_handles.iter() {
622
if let Some(resources) = self.vacant_gui_windows.remove(hwnd) {
623
if let Err(e) = resources.window().destroy() {
624
error!("Failed to destroy vacant GUI window: {:?}", e);
625
}
626
}
627
}
628
629
// Destroy the message router window.
630
if let Some(window) = self.message_router_window.take() {
631
if let Err(e) = window.destroy() {
632
error!("Failed to destroy thread message router: {:?}", e);
633
}
634
}
635
636
// Exit the message loop.
637
//
638
// SAFETY:
639
// Safe because this function takes in no memory managed by us, and it always succeeds.
640
unsafe {
641
PostQuitMessage(0);
642
}
643
}
644
}
645
646