#![allow(unsafe_code)]
use std::cell::RefCell;
use std::ffi::c_void;
use crate::geometry::{PhysicalSize, ScaleFactor, Size};
use crate::platform::ControlFlow;
use crate::platform::error::PlatformError;
use crate::platform::event::{ButtonState, Event, KeyCode, Modifiers, PointerButton, WindowId};
use crate::platform::input;
use crate::platform::menu::{MenuPopup, NativeMenu, NativeMenuBar, NativeMenuItem, NativeTray};
use crate::platform::window::{CursorIcon, Window, WindowAttributes, fit_icon};
use crate::render::{Pixmap, Surface};
mod dnd;
mod ime;
mod tray;
mod vsync;
pub(crate) use tray::notify;
type Handle = *mut c_void;
type Hwnd = Handle;
type Wparam = usize;
type Lparam = isize;
type Lresult = isize;
type WndProc = unsafe extern "system" fn(Hwnd, u32, Wparam, Lparam) -> Lresult;
const WS_OVERLAPPEDWINDOW: u32 = 0x00CF_0000;
const CW_USEDEFAULT: i32 = -2147483648; const SW_SHOW: i32 = 5;
const SW_MAXIMIZE: i32 = 3;
const SW_RESTORE: i32 = 9;
const WM_SETCURSOR: u32 = 0x0020;
const WM_GETMINMAXINFO: u32 = 0x0024;
const WM_SETICON: u32 = 0x0080;
const HTCLIENT: u16 = 1;
const ICON_SMALL: usize = 0;
const ICON_BIG: usize = 1;
const GWL_STYLE: i32 = -16;
const SWP_NOMOVE: u32 = 0x0002;
const SWP_FRAMECHANGED: u32 = 0x0020;
const SWP_NOOWNERZORDER: u32 = 0x0200;
const MONITOR_DEFAULTTONEAREST: u32 = 2;
const WM_DESTROY: u32 = 0x0002;
const WM_SIZE: u32 = 0x0005;
const WM_PAINT: u32 = 0x000F;
const WM_CLOSE: u32 = 0x0010;
const WM_KEYDOWN: u32 = 0x0100;
const WM_KEYUP: u32 = 0x0101;
const WM_CHAR: u32 = 0x0102;
const WM_SYSKEYDOWN: u32 = 0x0104;
const WM_SYSKEYUP: u32 = 0x0105;
const WM_MOUSEWHEEL: u32 = 0x020A;
const WM_MOUSEHWHEEL: u32 = 0x020E;
const WM_MOUSEMOVE: u32 = 0x0200;
const WM_LBUTTONDOWN: u32 = 0x0201;
const WM_LBUTTONUP: u32 = 0x0202;
const WM_RBUTTONDOWN: u32 = 0x0204;
const WM_RBUTTONUP: u32 = 0x0205;
const WM_MBUTTONDOWN: u32 = 0x0207;
const WM_MBUTTONUP: u32 = 0x0208;
const WM_GETOBJECT: u32 = 0x003D;
const WM_SETTINGCHANGE: u32 = 0x001A;
const WM_THEMECHANGED: u32 = 0x031A;
const WM_DWMCOLORIZATIONCOLORCHANGED: u32 = 0x0320;
const WM_DPICHANGED: u32 = 0x02E0;
const WM_QUIT: u32 = 0x0012;
const WM_COMMAND: u32 = 0x0111;
const MF_STRING: u32 = 0x0000;
const MF_GRAYED: u32 = 0x0001;
const MF_CHECKED: u32 = 0x0008;
const MF_POPUP: u32 = 0x0010;
const MF_SEPARATOR: u32 = 0x0800;
const TPM_RETURNCMD: u32 = 0x0100;
const TPM_NONOTIFY: u32 = 0x0080;
const WM_NULL: u32 = 0x0000;
const WM_APP_WAKE: u32 = 0x8000 + 1;
const PM_REMOVE: u32 = 0x0001;
const QS_ALLINPUT: u32 = 0x04FF;
const MWMO_INPUTAVAILABLE: u32 = 0x0004;
const INFINITE: u32 = u32::MAX;
const SWP_NOZORDER: u32 = 0x0004;
const SWP_NOACTIVATE: u32 = 0x0010;
const SWP_NOSIZE: u32 = 0x0001;
const PER_MONITOR_AWARE_V2: isize = -4;
const UIA_ROOT_OBJECT_ID: i32 = -25;
const VK_SHIFT: i32 = 0x10;
const VK_CONTROL: i32 = 0x11;
const VK_MENU: i32 = 0x12;
const VK_LWIN: i32 = 0x5B;
const VK_RWIN: i32 = 0x5C;
const BI_RGB: u32 = 0;
const DIB_RGB_COLORS: u32 = 0;
const SRCCOPY: u32 = 0x00CC_0020;
const IDC_ARROW: usize = 32512;
fn cursor_id(icon: CursorIcon) -> usize {
match icon {
CursorIcon::Default => IDC_ARROW,
CursorIcon::Text => 32513, CursorIcon::Crosshair => 32515, CursorIcon::NwseResize => 32642, CursorIcon::NeswResize => 32643, CursorIcon::EwResize => 32644, CursorIcon::NsResize => 32645, CursorIcon::Move | CursorIcon::Grabbing => 32646, CursorIcon::NotAllowed => 32648, CursorIcon::Pointer | CursorIcon::Grab => 32649, }
}
#[repr(C)]
struct WndClassW {
style: u32,
lpfn_wnd_proc: Option<WndProc>,
cb_cls_extra: i32,
cb_wnd_extra: i32,
h_instance: Handle,
h_icon: Handle,
h_cursor: Handle,
hbr_background: Handle,
lpsz_menu_name: *const u16,
lpsz_class_name: *const u16,
}
#[repr(C)]
struct Point {
x: i32,
y: i32,
}
#[repr(C)]
struct Msg {
hwnd: Hwnd,
message: u32,
w_param: Wparam,
l_param: Lparam,
time: u32,
pt: Point,
private: u32,
}
#[repr(C)]
struct Rect {
left: i32,
top: i32,
right: i32,
bottom: i32,
}
#[repr(C)]
struct MinMaxInfo {
reserved: Point,
max_size: Point,
max_position: Point,
min_track_size: Point,
max_track_size: Point,
}
#[repr(C)]
struct MonitorInfo {
cb_size: u32,
rc_monitor: Rect,
rc_work: Rect,
flags: u32,
}
#[repr(C)]
struct PaintStruct {
hdc: Handle,
f_erase: i32,
rc_paint: Rect,
f_restore: i32,
f_inc_update: i32,
rgb_reserved: [u8; 32],
}
#[repr(C)]
struct BitmapInfoHeader {
bi_size: u32,
bi_width: i32,
bi_height: i32,
bi_planes: u16,
bi_bit_count: u16,
bi_compression: u32,
bi_size_image: u32,
bi_x_pels_per_meter: i32,
bi_y_pels_per_meter: i32,
bi_clr_used: u32,
bi_clr_important: u32,
}
#[repr(C)]
struct BitmapInfo {
header: BitmapInfoHeader,
colors: [u32; 1],
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetModuleHandleW(name: *const u16) -> Handle;
}
#[link(name = "uiautomationcore")]
unsafe extern "system" {
fn UiaReturnRawElementProvider(hwnd: Hwnd, wp: Wparam, lp: Lparam, el: *mut c_void) -> Lresult;
}
const DWMWA_USE_IMMERSIVE_DARK_MODE: u32 = 20;
#[link(name = "dwmapi")]
unsafe extern "system" {
fn DwmSetWindowAttribute(hwnd: Hwnd, attr: u32, value: *const c_void, size: u32) -> i32;
}
#[link(name = "user32")]
unsafe extern "system" {
fn RegisterClassW(class: *const WndClassW) -> u16;
fn LoadCursorW(instance: Handle, name: usize) -> Handle;
#[allow(clippy::too_many_arguments)]
fn CreateWindowExW(
ex_style: u32,
class_name: *const u16,
window_name: *const u16,
style: u32,
x: i32,
y: i32,
w: i32,
h: i32,
parent: Hwnd,
menu: Handle,
instance: Handle,
param: *mut c_void,
) -> Hwnd;
fn SetProcessDpiAwarenessContext(context: isize) -> i32;
fn GetDpiForSystem() -> u32;
fn GetDpiForWindow(hwnd: Hwnd) -> u32;
fn SetWindowPos(hwnd: Hwnd, after: Hwnd, x: i32, y: i32, w: i32, h: i32, flags: u32) -> i32;
fn ShowWindow(hwnd: Hwnd, cmd: i32) -> i32;
fn UpdateWindow(hwnd: Hwnd) -> i32;
fn DefWindowProcW(hwnd: Hwnd, msg: u32, wp: Wparam, lp: Lparam) -> Lresult;
fn PeekMessageW(msg: *mut Msg, hwnd: Hwnd, min: u32, max: u32, remove: u32) -> i32;
fn PostMessageW(hwnd: Hwnd, msg: u32, wp: Wparam, lp: Lparam) -> i32;
fn MsgWaitForMultipleObjectsEx(
count: u32,
handles: *const Handle,
timeout_ms: u32,
wake_mask: u32,
flags: u32,
) -> u32;
fn TranslateMessage(msg: *const Msg) -> i32;
fn DispatchMessageW(msg: *const Msg) -> Lresult;
fn PostQuitMessage(code: i32);
fn DestroyWindow(hwnd: Hwnd) -> i32;
fn GetClientRect(hwnd: Hwnd, rect: *mut Rect) -> i32;
fn InvalidateRect(hwnd: Hwnd, rect: *const Rect, erase: i32) -> i32;
fn BeginPaint(hwnd: Hwnd, ps: *mut PaintStruct) -> Handle;
fn EndPaint(hwnd: Hwnd, ps: *const PaintStruct) -> i32;
fn GetKeyState(vk: i32) -> i16;
fn SetCursor(cursor: Handle) -> Handle;
fn SendMessageW(hwnd: Hwnd, msg: u32, wp: Wparam, lp: Lparam) -> Lresult;
fn CreateIcon(
instance: Handle,
width: i32,
height: i32,
planes: u8,
bits_per_pixel: u8,
and_bits: *const u8,
xor_bits: *const u8,
) -> Handle;
fn DestroyIcon(icon: Handle) -> i32;
fn GetWindowLongW(hwnd: Hwnd, index: i32) -> i32;
fn SetWindowLongW(hwnd: Hwnd, index: i32, value: i32) -> i32;
fn GetWindowRect(hwnd: Hwnd, rect: *mut Rect) -> i32;
fn CreateMenu() -> Handle;
fn CreatePopupMenu() -> Handle;
fn AppendMenuW(menu: Handle, flags: u32, id: usize, text: *const u16) -> i32;
fn SetMenu(hwnd: Hwnd, menu: Handle) -> i32;
fn DestroyMenu(menu: Handle) -> i32;
fn DrawMenuBar(hwnd: Hwnd) -> i32;
fn TrackPopupMenuEx(
menu: Handle,
flags: u32,
x: i32,
y: i32,
hwnd: Hwnd,
params: *const c_void,
) -> i32;
fn ClientToScreen(hwnd: Hwnd, point: *mut Point) -> i32;
fn SetForegroundWindow(hwnd: Hwnd) -> i32;
fn MonitorFromWindow(hwnd: Hwnd, flags: u32) -> Handle;
fn GetMonitorInfoW(monitor: Handle, info: *mut MonitorInfo) -> i32;
fn AdjustWindowRectExForDpi(
rect: *mut Rect,
style: u32,
menu: i32,
ex_style: u32,
dpi: u32,
) -> i32;
}
#[link(name = "gdi32")]
unsafe extern "system" {
#[allow(clippy::too_many_arguments)]
fn StretchDIBits(
hdc: Handle,
x_dest: i32,
y_dest: i32,
w_dest: i32,
h_dest: i32,
x_src: i32,
y_src: i32,
w_src: i32,
h_src: i32,
bits: *const c_void,
bmi: *const BitmapInfo,
usage: u32,
rop: u32,
) -> i32;
}
#[derive(Default)]
struct WinCtx {
fb: Vec<u8>,
fb_w: i32,
fb_h: i32,
queue: Vec<Event>,
uia: Option<crate::platform::uia::UiaTree>,
dpi: u32,
cursor: CursorIcon,
min_size: Option<Size>,
max_size: Option<Size>,
restore: Option<(i32, [i32; 4])>,
icon: usize,
ime_area: Option<Rect>,
utf16: input::Utf16Joiner,
menu: usize,
tray: tray::TrayState,
drop_accepted: bool,
drag_last: Option<crate::geometry::Point>,
}
fn current_scale() -> f64 {
CTX.with(|c| match c.borrow().dpi {
0 => 1.0,
dpi => dpi as f64 / 96.0,
})
}
fn push_event(ev: Event) {
CTX.with(|c| c.borrow_mut().queue.push(ev));
}
fn mouse_xy(lp: Lparam) -> crate::geometry::Point {
let x = (lp & 0xffff) as u16 as i16 as f64;
let y = ((lp >> 16) & 0xffff) as u16 as i16 as f64;
let scale = current_scale();
crate::geometry::Point::new(x / scale, y / scale)
}
fn modifiers() -> Modifiers {
let down = |vk| unsafe { GetKeyState(vk) } < 0;
Modifiers {
shift: down(VK_SHIFT),
ctrl: down(VK_CONTROL),
alt: down(VK_MENU),
meta: down(VK_LWIN) || down(VK_RWIN),
}
}
fn key_event(vk: usize, state: ButtonState) -> Option<Event> {
let modifiers = modifiers();
let shortcut = modifiers.ctrl != modifiers.alt || modifiers.meta;
let code = input::win32_vk(vk).or_else(|| {
shortcut
.then(|| input::win32_shortcut_char(vk, modifiers.shift))
.flatten()
.map(KeyCode::Char)
})?;
Some(Event::Key {
code,
state,
modifiers,
})
}
thread_local! {
static CTX: RefCell<WinCtx> = RefCell::new(WinCtx::default());
}
fn wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
fn fill_menu(menu: Handle, items: &[NativeMenuItem]) {
for item in items {
match item {
NativeMenuItem::Action {
id,
label,
shortcut,
enabled,
checked,
} => {
let mut text = label.clone();
if let Some(s) = shortcut {
text.push('\t');
text.push_str(&s.label_for(false));
}
let mut flags = MF_STRING;
if !enabled {
flags |= MF_GRAYED;
}
if *checked == Some(true) {
flags |= MF_CHECKED;
}
let text = wide(&text);
unsafe { AppendMenuW(menu, flags, *id as usize, text.as_ptr()) };
}
NativeMenuItem::Separator => unsafe {
AppendMenuW(menu, MF_SEPARATOR, 0, std::ptr::null());
},
NativeMenuItem::Submenu(sub) => {
let popup = unsafe { CreatePopupMenu() };
fill_menu(popup, &sub.items);
let text = wide(&sub.label);
unsafe { AppendMenuW(menu, MF_POPUP, popup as usize, text.as_ptr()) };
}
}
}
}
fn popup_menu(hwnd: Hwnd, menu: &NativeMenu, at: crate::geometry::Point) -> MenuPopup {
let popup = unsafe { CreatePopupMenu() };
if popup.is_null() {
return MenuPopup::Unsupported;
}
fill_menu(popup, &menu.items);
let scale = current_scale();
let mut p = Point {
x: (at.x * scale).round() as i32,
y: (at.y * scale).round() as i32,
};
let picked = unsafe {
ClientToScreen(hwnd, &mut p);
SetForegroundWindow(hwnd);
let id = TrackPopupMenuEx(
popup,
TPM_RETURNCMD | TPM_NONOTIFY,
p.x,
p.y,
hwnd,
std::ptr::null(),
);
PostMessageW(hwnd, WM_NULL, 0, 0);
DestroyMenu(popup);
id
};
if picked > 0 {
MenuPopup::Picked(picked as u32)
} else {
MenuPopup::Dismissed
}
}
fn set_menu_bar(hwnd: Hwnd, bar: &NativeMenuBar) {
let menu = unsafe { CreateMenu() };
for m in &bar.menus {
let popup = unsafe { CreatePopupMenu() };
fill_menu(popup, &m.items);
let text = wide(&m.label);
unsafe { AppendMenuW(menu, MF_POPUP, popup as usize, text.as_ptr()) };
}
let client = |hwnd| {
let mut r = Rect {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
unsafe { GetClientRect(hwnd, &mut r) };
r.bottom - r.top
};
let before = client(hwnd);
unsafe { SetMenu(hwnd, menu) };
let old = CTX.with(|c| std::mem::replace(&mut c.borrow_mut().menu, menu as usize));
if old != 0 {
unsafe { DestroyMenu(old as Handle) };
} else {
let lost = before - client(hwnd);
let mut w = Rect {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
if lost > 0 && unsafe { GetWindowRect(hwnd, &mut w) } != 0 {
unsafe {
SetWindowPos(
hwnd,
std::ptr::null_mut(),
0,
0,
w.right - w.left,
w.bottom - w.top + lost,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE,
)
};
}
}
unsafe { DrawMenuBar(hwnd) };
}
unsafe fn names_setting(lp: Lparam, name: &str) -> bool {
let p = lp as *const u16;
if p.is_null() {
return false;
}
let mut want = name.encode_utf16();
let mut i = 0;
loop {
let c = unsafe { *p.add(i) };
match want.next() {
Some(w) if w == c => i += 1,
Some(_) => return false,
None => return c == 0,
}
}
}
fn set_dark_title(hwnd: Hwnd, dark: bool) {
let value = dark as i32;
unsafe {
DwmSetWindowAttribute(
hwnd,
DWMWA_USE_IMMERSIVE_DARK_MODE,
&value as *const i32 as *const c_void,
4,
);
}
}
fn outer_size(hwnd: Hwnd, size: Size) -> Point {
let dpi = match unsafe { GetDpiForWindow(hwnd) } {
0 => 96,
dpi => dpi,
};
let inner = ScaleFactor::new(dpi as f64 / 96.0).to_physical(size);
let mut r = Rect {
left: 0,
top: 0,
right: inner.width as i32,
bottom: inner.height as i32,
};
let style = unsafe { GetWindowLongW(hwnd, GWL_STYLE) } as u32;
unsafe { AdjustWindowRectExForDpi(&mut r, style, 0, 0, dpi) };
Point {
x: r.right - r.left,
y: r.bottom - r.top,
}
}
fn reapply_limits(hwnd: Hwnd) {
let mut r = Rect {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
unsafe {
GetWindowRect(hwnd, &mut r);
SetWindowPos(
hwnd,
std::ptr::null_mut(),
0,
0,
r.right - r.left,
r.bottom - r.top,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE,
);
}
}
fn set_fullscreen(hwnd: Hwnd, fullscreen: bool) {
let saved = CTX.with(|c| c.borrow().restore);
match (fullscreen, saved) {
(true, None) => {
let style = unsafe { GetWindowLongW(hwnd, GWL_STYLE) };
let mut r = Rect {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
let mut info = MonitorInfo {
cb_size: core::mem::size_of::<MonitorInfo>() as u32,
rc_monitor: Rect {
left: 0,
top: 0,
right: 0,
bottom: 0,
},
rc_work: Rect {
left: 0,
top: 0,
right: 0,
bottom: 0,
},
flags: 0,
};
let monitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) };
if unsafe { GetWindowRect(hwnd, &mut r) } == 0
|| unsafe { GetMonitorInfoW(monitor, &mut info) } == 0
{
return;
}
CTX.with(|c| {
c.borrow_mut().restore = Some((style, [r.left, r.top, r.right, r.bottom]));
});
let m = info.rc_monitor;
unsafe {
SetWindowLongW(hwnd, GWL_STYLE, style & !(WS_OVERLAPPEDWINDOW as i32));
SetWindowPos(
hwnd,
std::ptr::null_mut(), m.left,
m.top,
m.right - m.left,
m.bottom - m.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED,
);
}
}
(false, Some((style, [left, top, right, bottom]))) => {
CTX.with(|c| c.borrow_mut().restore = None);
unsafe {
SetWindowLongW(hwnd, GWL_STYLE, style);
SetWindowPos(
hwnd,
std::ptr::null_mut(),
left,
top,
right - left,
bottom - top,
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED,
);
}
}
_ => {}
}
}
fn create_icon(icon: &crate::render::Pixmap) -> Handle {
let icon = fit_icon(icon, 256);
let size = icon.size();
let (w, h) = (size.width as usize, size.height as usize);
let (bgra, mask) = icon_bits(icon.as_bytes(), w, h);
unsafe {
CreateIcon(
GetModuleHandleW(std::ptr::null()),
w as i32,
h as i32,
1,
32,
mask.as_ptr(),
bgra.as_ptr(),
)
}
}
fn icon_bits(rgba: &[u8], w: usize, h: usize) -> (Vec<u8>, Vec<u8>) {
let stride = w.div_ceil(16) * 2;
let mut mask = vec![0u8; stride * h];
let mut bgra = Vec::with_capacity(rgba.len());
for (i, p) in rgba.as_chunks::<4>().0.iter().enumerate() {
bgra.extend_from_slice(&[p[2], p[1], p[0], p[3]]);
if p[3] == 0 {
let (x, y) = (i % w, i / w);
mask[y * stride + x / 8] |= 0x80 >> (x % 8);
}
}
(bgra, mask)
}
unsafe extern "system" fn wnd_proc(hwnd: Hwnd, msg: u32, wp: Wparam, lp: Lparam) -> Lresult {
if tray::taskbar_created(hwnd, msg) {
return 0;
}
match msg {
WM_SETCURSOR if (lp & 0xffff) as u16 == HTCLIENT => {
let id = CTX.with(|c| cursor_id(c.borrow().cursor));
unsafe { SetCursor(LoadCursorW(std::ptr::null_mut(), id)) };
1
}
WM_GETMINMAXINFO => {
let (min, max, fullscreen) = CTX.with(|c| {
let c = c.borrow();
(c.min_size, c.max_size, c.restore.is_some())
});
let info = unsafe { &mut *(lp as *mut MinMaxInfo) };
if let Some(min) = min {
info.min_track_size = outer_size(hwnd, min);
}
if let Some(max) = max.filter(|_| !fullscreen) {
info.max_track_size = outer_size(hwnd, max);
info.max_size = outer_size(hwnd, max);
}
0
}
WM_PAINT => {
let mut ps: PaintStruct = unsafe { std::mem::zeroed() };
let hdc = unsafe { BeginPaint(hwnd, &mut ps) };
CTX.with(|c| {
let c = c.borrow();
if !c.fb.is_empty() {
let bmi = BitmapInfo {
header: BitmapInfoHeader {
bi_size: core::mem::size_of::<BitmapInfoHeader>() as u32,
bi_width: c.fb_w,
bi_height: -c.fb_h,
bi_planes: 1,
bi_bit_count: 32,
bi_compression: BI_RGB,
bi_size_image: 0,
bi_x_pels_per_meter: 0,
bi_y_pels_per_meter: 0,
bi_clr_used: 0,
bi_clr_important: 0,
},
colors: [0],
};
unsafe {
StretchDIBits(
hdc,
0,
0,
c.fb_w,
c.fb_h,
0,
0,
c.fb_w,
c.fb_h,
c.fb.as_ptr() as *const c_void,
&bmi,
DIB_RGB_COLORS,
SRCCOPY,
);
}
}
});
unsafe { EndPaint(hwnd, &ps) };
0
}
WM_SIZE => {
let w = (lp & 0xffff) as u32;
let h = ((lp >> 16) & 0xffff) as u32;
if w > 0 && h > 0 {
push_event(Event::Resized(PhysicalSize::new(w, h)));
}
0
}
WM_DPICHANGED => {
let dpi = (wp & 0xffff) as u32;
CTX.with(|c| c.borrow_mut().dpi = dpi);
let r = unsafe { &*(lp as *const Rect) };
unsafe {
SetWindowPos(
hwnd,
std::ptr::null_mut(),
r.left,
r.top,
r.right - r.left,
r.bottom - r.top,
SWP_NOZORDER | SWP_NOACTIVATE,
);
}
0
}
WM_MOUSEMOVE => {
push_event(Event::PointerMoved {
position: mouse_xy(lp),
});
0
}
WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN | WM_LBUTTONUP | WM_RBUTTONUP
| WM_MBUTTONUP => {
let button = match msg {
WM_LBUTTONDOWN | WM_LBUTTONUP => PointerButton::Left,
WM_RBUTTONDOWN | WM_RBUTTONUP => PointerButton::Right,
_ => PointerButton::Middle,
};
let state = match msg {
WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN => ButtonState::Pressed,
_ => ButtonState::Released,
};
crate::platform::set_pointer_modifiers(modifiers());
push_event(Event::PointerButton {
button,
state,
position: mouse_xy(lp),
});
0
}
WM_MOUSEWHEEL | WM_MOUSEHWHEEL => {
push_event(Event::Scroll {
delta: input::win32_wheel(wp, msg == WM_MOUSEHWHEEL),
});
0
}
WM_KEYDOWN | WM_KEYUP => {
let state = if msg == WM_KEYDOWN {
ButtonState::Pressed
} else {
ButtonState::Released
};
if let Some(ev) = key_event(wp, state) {
push_event(ev);
}
0
}
WM_SYSKEYDOWN | WM_SYSKEYUP => {
let state = if msg == WM_SYSKEYDOWN {
ButtonState::Pressed
} else {
ButtonState::Released
};
if let Some(ev) = key_event(wp, state) {
push_event(ev);
}
unsafe { DefWindowProcW(hwnd, msg, wp, lp) }
}
WM_CHAR => {
let m = modifiers();
let shortcut = (m.ctrl && !m.alt) || m.meta;
let ch = CTX.with(|c| c.borrow_mut().utf16.push(wp as u16));
if let Some(ch) = ch
&& !ch.is_control()
&& !shortcut
{
push_event(Event::Text(ch.to_string()));
}
0
}
ime::WM_IME_SETCONTEXT => unsafe {
DefWindowProcW(hwnd, msg, wp, lp & !ime::ISC_SHOWUICOMPOSITIONWINDOW)
},
ime::WM_IME_STARTCOMPOSITION => {
ime::place_windows(hwnd);
0
}
ime::WM_IME_COMPOSITION => {
ime::composition(hwnd, lp);
0
}
ime::WM_IME_ENDCOMPOSITION => {
ime::end_composition();
0
}
WM_GETOBJECT => {
if lp as i32 == UIA_ROOT_OBJECT_ID {
let root = CTX.with(|c| {
c.borrow()
.uia
.as_ref()
.map(|t| t.root_provider())
.unwrap_or(std::ptr::null_mut())
});
if !root.is_null() {
return unsafe { UiaReturnRawElementProvider(hwnd, wp, lp, root) };
}
}
unsafe { DefWindowProcW(hwnd, msg, wp, lp) }
}
WM_SETTINGCHANGE | WM_THEMECHANGED | WM_DWMCOLORIZATIONCOLORCHANGED => {
if msg != WM_SETTINGCHANGE || unsafe { names_setting(lp, "ImmersiveColorSet") } {
crate::platform::native::note_changed();
}
unsafe { DefWindowProcW(hwnd, msg, wp, lp) }
}
WM_COMMAND if (wp >> 16) & 0xffff == 0 && lp == 0 => {
push_event(Event::MenuCommand((wp & 0xffff) as u32));
0
}
tray::WM_APP_TRAY => {
tray::callback(hwnd, wp, lp);
0
}
dnd::WM_DROPFILES => {
dnd::drop_files(wp as *mut c_void);
0
}
WM_CLOSE => {
push_event(Event::CloseRequested);
0
}
WM_DESTROY => {
tray::remove_all(hwnd);
unsafe { PostQuitMessage(0) };
0
}
_ => unsafe { DefWindowProcW(hwnd, msg, wp, lp) },
}
}
#[derive(Debug)]
struct WinWindow {
hwnd: Hwnd,
size: PhysicalSize,
}
impl Window for WinWindow {
fn id(&self) -> WindowId {
WindowId(self.hwnd as u64)
}
fn inner_size(&self) -> PhysicalSize {
self.size
}
fn scale_factor(&self) -> ScaleFactor {
ScaleFactor::new(current_scale())
}
fn request_redraw(&self) {
unsafe { InvalidateRect(self.hwnd, std::ptr::null(), 0) };
}
fn set_title(&self, _title: &str) {}
fn clipboard(&self) -> Option<String> {
crate::platform::clipboard::text()
}
fn set_clipboard(&self, text: &str) {
crate::platform::clipboard::set_text(text);
}
fn set_dark(&self, dark: bool) {
set_dark_title(self.hwnd, dark);
unsafe {
SetWindowPos(
self.hwnd,
std::ptr::null_mut(),
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED,
)
};
}
fn create_surface(&self) -> Box<dyn Surface> {
Box::new(WinSurface {
hwnd: self.hwnd,
size: self.size,
})
}
fn set_accessibility_tree(&self, root: &crate::platform::access::A11yNode) {
let tree = crate::platform::uia::UiaTree::build(root);
CTX.with(|c| c.borrow_mut().uia = Some(tree));
}
fn set_ime(&self, state: Option<&crate::platform::ImeState>) {
ime::set_state(self.hwnd, state);
}
fn set_drop_accepted(&self, accepted: bool) {
CTX.with(|c| c.borrow_mut().drop_accepted = accepted);
}
fn set_cursor(&self, cursor: CursorIcon) {
CTX.with(|c| c.borrow_mut().cursor = cursor);
unsafe { SetCursor(LoadCursorW(std::ptr::null_mut(), cursor_id(cursor))) };
}
fn set_min_size(&self, size: Option<Size>) {
CTX.with(|c| c.borrow_mut().min_size = size);
reapply_limits(self.hwnd);
}
fn set_max_size(&self, size: Option<Size>) {
CTX.with(|c| c.borrow_mut().max_size = size);
reapply_limits(self.hwnd);
}
fn set_maximized(&self, maximized: bool) {
let cmd = if maximized { SW_MAXIMIZE } else { SW_RESTORE };
unsafe { ShowWindow(self.hwnd, cmd) };
}
fn set_fullscreen(&self, fullscreen: bool) {
set_fullscreen(self.hwnd, fullscreen);
}
fn set_menu_bar(&self, menu: &NativeMenuBar) -> bool {
set_menu_bar(self.hwnd, menu);
true
}
fn popup_menu(&self, menu: &NativeMenu, at: crate::geometry::Point) -> MenuPopup {
popup_menu(self.hwnd, menu, at)
}
fn set_tray(&self, tray: Option<&NativeTray>) -> bool {
tray::set_tray(self.hwnd, tray)
}
fn set_icon(&self, icon: Option<&crate::render::Pixmap>) {
let new = icon.map_or(std::ptr::null_mut(), create_icon);
unsafe {
SendMessageW(self.hwnd, WM_SETICON, ICON_BIG, new as Lparam);
SendMessageW(self.hwnd, WM_SETICON, ICON_SMALL, new as Lparam);
}
tray::set_app_icon(new);
let old = CTX.with(|c| std::mem::replace(&mut c.borrow_mut().icon, new as usize));
if old != 0 {
unsafe { DestroyIcon(old as Handle) };
}
}
}
#[derive(Debug)]
struct WinSurface {
hwnd: Hwnd,
size: PhysicalSize,
}
impl Surface for WinSurface {
fn resize(&mut self, size: PhysicalSize) {
self.size = size;
}
fn size(&self) -> PhysicalSize {
self.size
}
fn present(&mut self, pixmap: &Pixmap, _damage: &[crate::geometry::Rect]) {
let size = pixmap.size();
let src = pixmap.as_bytes();
let mut fb = vec![0u8; src.len()];
for (d, s) in fb
.as_chunks_mut::<4>()
.0
.iter_mut()
.zip(src.as_chunks::<4>().0)
{
d[0] = s[2];
d[1] = s[1];
d[2] = s[0];
d[3] = s[3];
}
CTX.with(|c| {
let mut c = c.borrow_mut();
c.fb = fb;
c.fb_w = size.width as i32;
c.fb_h = size.height as i32;
});
unsafe { InvalidateRect(self.hwnd, std::ptr::null(), 0) };
}
}
pub fn run<H>(attrs: WindowAttributes, mut handler: H) -> Result<(), PlatformError>
where
H: FnMut(Event, &dyn Window) -> ControlFlow,
{
let class_name = wide("StippleWindowClass");
let title = wide(&attrs.title);
unsafe { SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2) };
let system_dpi = unsafe { GetDpiForSystem() };
let size = ScaleFactor::new(system_dpi as f64 / 96.0).to_physical(attrs.logical_size);
let hinstance = unsafe { GetModuleHandleW(std::ptr::null()) };
let cursor = unsafe { LoadCursorW(std::ptr::null_mut(), IDC_ARROW) };
CTX.with(|c| {
let mut c = c.borrow_mut();
c.min_size = attrs.min_size;
c.max_size = attrs.max_size;
});
let class = WndClassW {
style: 0,
lpfn_wnd_proc: Some(wnd_proc),
cb_cls_extra: 0,
cb_wnd_extra: 0,
h_instance: hinstance,
h_icon: std::ptr::null_mut(),
h_cursor: cursor,
hbr_background: std::ptr::null_mut(),
lpsz_menu_name: std::ptr::null(),
lpsz_class_name: class_name.as_ptr(),
};
let atom = unsafe { RegisterClassW(&class) };
if atom == 0 {
return Err(PlatformError::Os("RegisterClassW failed".into()));
}
let hwnd = unsafe {
CreateWindowExW(
0,
class_name.as_ptr(),
title.as_ptr(),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
size.width as i32,
size.height as i32,
std::ptr::null_mut(),
std::ptr::null_mut(),
hinstance,
std::ptr::null_mut(),
)
};
if hwnd.is_null() {
return Err(PlatformError::Os("CreateWindowExW failed".into()));
}
tray::install(hwnd);
CTX.with(|c| c.borrow_mut().dpi = unsafe { GetDpiForWindow(hwnd) });
if let Some(dark) = attrs.dark {
set_dark_title(hwnd, dark);
}
let mut rect = Rect {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
unsafe { GetClientRect(hwnd, &mut rect) };
let client = PhysicalSize::new(
(rect.right - rect.left).max(1) as u32,
(rect.bottom - rect.top).max(1) as u32,
);
let mut win = WinWindow { hwnd, size: client };
if !dnd::register(hwnd) {
eprintln!("stipple: RegisterDragDrop failed; only WM_DROPFILES drops work");
}
if let Some(icon) = attrs.icon.as_ref() {
win.set_icon(Some(icon));
}
handler(Event::RedrawRequested, &win);
let show = if attrs.maximized {
SW_MAXIMIZE
} else {
SW_SHOW
};
unsafe {
ShowWindow(hwnd, show);
UpdateWindow(hwnd);
}
if attrs.fullscreen {
set_fullscreen(hwnd, true);
}
let target = hwnd as usize;
crate::platform::wake::install(
std::sync::Arc::new(move || unsafe {
PostMessageW(target as Hwnd, WM_APP_WAKE, 0, 0);
}),
None,
);
vsync::install();
let mut msg: Msg = unsafe { std::mem::zeroed() };
'outer: loop {
let timeout = match crate::platform::wake::timeout(std::time::Instant::now()) {
Some(t) => t.as_micros().div_ceil(1000).min(INFINITE as u128 - 1) as u32,
None => INFINITE,
};
unsafe {
MsgWaitForMultipleObjectsEx(
0,
std::ptr::null(),
timeout,
QS_ALLINPUT,
MWMO_INPUTAVAILABLE,
)
};
while unsafe { PeekMessageW(&mut msg, std::ptr::null_mut(), 0, 0, PM_REMOVE) } != 0 {
if msg.message == WM_QUIT {
break 'outer;
}
unsafe {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
let events: Vec<Event> = CTX.with(|c| std::mem::take(&mut c.borrow_mut().queue));
for event in events {
if let Event::Resized(size) = event {
win.size = size;
}
if handler(event, &win) == ControlFlow::Exit {
unsafe { DestroyWindow(hwnd) };
}
}
}
if crate::platform::wake::take(std::time::Instant::now())
&& handler(Event::Wake, &win) == ControlFlow::Exit
{
unsafe { DestroyWindow(hwnd) };
}
}
Ok(())
}