1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use crate::*;
use once_cell::sync::OnceCell;
use std::os::windows::prelude::*;
use std::sync::{mpsc, Mutex};
use windows::Win32::{
    Foundation::{BOOL, HANDLE, HWND, LPARAM, WPARAM},
    System::Threading::GetThreadId,
    UI::HiDpi::{
        SetProcessDpiAwareness, SetProcessDpiAwarenessContext,
        DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
        PROCESS_PER_MONITOR_DPI_AWARE,
    },
    UI::WindowsAndMessaging::{
        DispatchMessageW, GetMessageW, IsGUIThread, PostThreadMessageW, TranslateMessage, MSG,
        WM_APP,
    },
};

fn enable_dpi_awareness() {
    unsafe {
        if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2).is_err() {
            return;
        };
        if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE).is_err() {
            return;
        };
        SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE).unwrap_or(());
    }
}

pub(crate) const WM_SEND_TASK: u32 = WM_APP;

struct Task(Box<dyn FnOnce() + Send>);

struct Thread {
    th: Option<std::thread::JoinHandle<()>>,
    tx_task: mpsc::Sender<Task>,
}

impl Thread {
    fn new() -> Self {
        let (tx, rx) = mpsc::channel::<Task>();
        let (tmp_tx, tmp_rx) = mpsc::channel::<()>();
        let th = std::thread::Builder::new()
            .name("witas UiThread".into())
            .spawn(move || unsafe {
                #[cfg(feature = "coinit")]
                let _coinit =
                    coinit::init(coinit::APARTMENTTHREADED | coinit::DISABLE_OLE1DDE).unwrap();
                IsGUIThread(true);
                window::register_class();
                {
                    tmp_tx.send(()).unwrap_or(());
                }
                let mut msg = MSG::default();
                loop {
                    let ret = GetMessageW(&mut msg, HWND(0), 0, 0);
                    if ret == BOOL(0) || ret == BOOL(-1) {
                        break;
                    }
                    match msg.message {
                        WM_SEND_TASK => {
                            let ret =
                                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                                    if let Ok(task) = rx.recv() {
                                        task.0();
                                    }
                                }));
                            if let Err(e) = ret {
                                Context::quit();
                                std::panic::resume_unwind(e);
                            }
                        }
                        _ => {
                            TranslateMessage(&msg);
                            DispatchMessageW(&msg);
                        }
                    }
                    if let Some(e) = procedure::get_unwind() {
                        Context::quit();
                        std::panic::resume_unwind(e);
                    }
                }
            })
            .unwrap();
        tmp_rx.recv().unwrap_or(());
        Self {
            th: Some(th),
            tx_task: tx,
        }
    }

    fn post_message(&self, msg: u32) {
        unsafe {
            let th = GetThreadId(HANDLE(self.th.as_ref().unwrap().as_raw_handle() as _));
            PostThreadMessageW(th, msg, WPARAM(0), LPARAM(0)).ok();
        }
    }

    fn send_task(&self, f: impl FnOnce() + Send + 'static) {
        self.tx_task.send(Task(Box::new(f))).unwrap_or(());
        self.post_message(WM_SEND_TASK);
    }
}

static THREAD: OnceCell<Mutex<Thread>> = OnceCell::new();

/// The thread for running window loop.
pub struct UiThread;

impl UiThread {
    pub(crate) fn init() {
        THREAD.get_or_init(|| {
            enable_dpi_awareness();
            Mutex::new(Thread::new())
        });
    }

    #[inline]
    pub fn send_task(f: impl FnOnce() + Send + 'static) {
        let thread = THREAD.get().unwrap().lock().unwrap();
        thread.send_task(f);
    }
}