use crate::core::{ObjectId, PlatformFamily};
use crate::platform::accessibility::AccessibilityBridge;
use crate::platform::clipboard::RichClipboardBackend;
use crate::platform::ime::ImeBridge;
use crate::platform::{
Platform, PlatformCapabilities, WidgetTriggerEvent, WidgetTriggerKind, WindowStateFlag,
};
use crate::compat::{String, Vec};
use crate::platform::types::{windows_shell_spawnable, Display, DisplayId, SystemFont};
use crate::platform::windows::notify;
use crate::platform::windows::types::*;
use crate::platform::DropEvent;
use std::sync::atomic::Ordering;
#[cfg(target_os = "windows")]
fn cached_environment_fact<T: Copy>(
cache: &'static EnvironmentFactCache<T>,
read: impl FnOnce() -> T,
) -> T {
const ENVIRONMENT_TTL: std::time::Duration = std::time::Duration::from_secs(1);
let slot = cache.get_or_init(|| std::sync::Mutex::new(None));
if let Ok(guard) = slot.lock() {
if let Some((sampled_at, value)) = *guard {
if sampled_at.elapsed() < ENVIRONMENT_TTL {
return value;
}
}
}
let value = read();
if let Ok(mut guard) = slot.lock() {
*guard = Some((std::time::Instant::now(), value));
}
value
}
#[cfg(target_os = "windows")]
type EnvironmentFactCache<T> =
std::sync::OnceLock<std::sync::Mutex<Option<(std::time::Instant, T)>>>;
#[cfg(target_os = "windows")]
static TEXT_SCALE_CACHE: EnvironmentFactCache<f32> = std::sync::OnceLock::new();
#[cfg(target_os = "windows")]
static SYSTEM_THEME_CACHE: EnvironmentFactCache<Option<bool>> = std::sync::OnceLock::new();
#[cfg(target_os = "windows")]
static DPI_SCALE_CACHE: EnvironmentFactCache<f32> = std::sync::OnceLock::new();
#[cfg(target_os = "windows")]
static HIGH_CONTRAST_CACHE: EnvironmentFactCache<Option<bool>> = std::sync::OnceLock::new();
#[cfg(target_os = "windows")]
static REDUCE_MOTION_CACHE: EnvironmentFactCache<Option<bool>> = std::sync::OnceLock::new();
#[cfg(target_os = "windows")]
static SYSTEM_LOCALE_CACHE: EnvironmentFactCache<Option<&'static str>> = std::sync::OnceLock::new();
#[cfg(all(target_os = "windows", widgets_unstripped))]
use winapi::um::winuser::{SW_HIDE, SW_SHOW};
const FRAME_INTERVAL_MS: u64 = crate::platform::types::FRAME_INTERVAL_MS;
#[cfg(target_os = "windows")]
fn read_clipboard_text() -> Option<String> {
use winapi::um::winbase::GlobalLock;
use winapi::um::winuser::{CloseClipboard, GetClipboardData, OpenClipboard, CF_UNICODETEXT};
unsafe {
if OpenClipboard(std::ptr::null_mut()) == 0 {
return None;
}
let h_mem = GetClipboardData(CF_UNICODETEXT);
if h_mem.is_null() {
CloseClipboard();
return None;
}
let p_src = GlobalLock(h_mem) as *const u16;
if p_src.is_null() {
CloseClipboard();
return None;
}
let mut len = 0;
while *p_src.add(len) != 0 {
len += 1;
}
let text = String::from_utf16_lossy(std::slice::from_raw_parts(p_src, len));
CloseClipboard();
Some(text)
}
}
#[cfg(target_os = "windows")]
unsafe fn dwrite_localized_string(
strings: &windows::Win32::Graphics::DirectWrite::IDWriteLocalizedStrings,
index: u32,
) -> String {
if index >= strings.GetCount() {
return String::new();
}
let Ok(length) = strings.GetStringLength(index) else {
return String::new();
};
let mut buffer = vec![0u16; length as usize + 1];
if strings.GetString(index, &mut buffer).is_err() {
return String::new();
}
String::from_utf16_lossy(&buffer[..length as usize])
}
#[cfg(target_os = "windows")]
unsafe fn dwrite_font_path(
font: &windows::Win32::Graphics::DirectWrite::IDWriteFont,
) -> Option<std::path::PathBuf> {
use windows::core::Interface;
use windows::Win32::Graphics::DirectWrite::{IDWriteFontFile, IDWriteLocalFontFileLoader};
let face = font.CreateFontFace().ok()?;
let mut count = 0u32;
face.GetFiles(&mut count, None).ok()?;
if count == 0 {
return None;
}
let mut files: Vec<Option<IDWriteFontFile>> = vec![None; count as usize];
face.GetFiles(&mut count, Some(files.as_mut_ptr())).ok()?;
files.truncate(count as usize);
for file in files.into_iter().flatten() {
let Ok(loader) = file.GetLoader() else {
continue;
};
let Ok(local) = loader.cast::<IDWriteLocalFontFileLoader>() else {
continue;
};
let mut key: *mut core::ffi::c_void = core::ptr::null_mut();
let mut key_size = 0u32;
if file.GetReferenceKey(&mut key, &mut key_size).is_err() || key.is_null() || key_size == 0
{
continue;
}
let Ok(length) = local.GetFilePathLengthFromKey(key, key_size) else {
continue;
};
let mut buffer = vec![0u16; length as usize + 1];
if local.GetFilePathFromKey(key, key_size, &mut buffer).is_err() {
continue;
}
let path = String::from_utf16_lossy(&buffer[..length as usize]);
if !path.is_empty() {
return Some(std::path::PathBuf::from(path));
}
}
None
}
#[cfg(target_os = "windows")]
fn dwrite_system_fonts() -> Vec<SystemFont> {
use windows::Win32::System::Com::{CoInitializeEx, CoUninitialize, COINIT_APARTMENTTHREADED};
unsafe {
let com_ready = CoInitializeEx(None, COINIT_APARTMENTTHREADED).is_ok();
let fonts = enumerate_directwrite();
if com_ready {
CoUninitialize();
}
fonts.unwrap_or_default()
}
}
#[cfg(target_os = "windows")]
unsafe fn enumerate_directwrite() -> Option<Vec<SystemFont>> {
use windows::Win32::Graphics::DirectWrite::{
DWriteCreateFactory, IDWriteFactory, DWRITE_FACTORY_TYPE_SHARED,
DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_WEIGHT_NORMAL,
};
let factory: IDWriteFactory = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).ok()?;
let mut collection = None;
factory.GetSystemFontCollection(&mut collection, false).ok()?;
let collection = collection?;
let mut out = Vec::new();
for family_index in 0..collection.GetFontFamilyCount() {
let Ok(family) = collection.GetFontFamily(family_index) else {
continue;
};
let family_name = match family.GetFamilyNames() {
Ok(names) => dwrite_localized_string(&names, 0),
Err(_) => continue,
};
if family_name.trim().is_empty() {
continue;
}
let mut best: Option<(SystemFont, u8)> = None;
for font_index in 0..family.GetFontCount() {
let Ok(font) = family.GetFont(font_index) else {
continue;
};
let style = match font.GetFaceNames() {
Ok(names) => dwrite_localized_string(&names, 0),
Err(_) => String::new(),
};
let source = dwrite_font_path(&font);
let regular = font.GetWeight() == DWRITE_FONT_WEIGHT_NORMAL
&& font.GetStyle() == DWRITE_FONT_STYLE_NORMAL
&& font.GetStretch() == DWRITE_FONT_STRETCH_NORMAL;
let score = u8::from(regular) + u8::from(source.is_some()) * 2;
let candidate = SystemFont { family: family_name.clone(), style, source };
match &best {
Some((_, best_score)) if *best_score >= score => {}
_ => best = Some((candidate, score)),
}
}
if let Some((font, _)) = best {
out.push(font);
}
}
Some(out)
}
impl Platform for WindowsPlatform {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn set_window_state(&self, widget_id: ObjectId, flag: WindowStateFlag, on: bool) -> bool {
let Some(kind) = self.state.kind_of(widget_id) else {
return false;
};
if !matches!(kind, super::types::WindowsHandleKind::Window) {
return false;
}
self.state.set_window_state(widget_id, flag, on);
#[cfg(target_os = "windows")]
{
use winapi::um::winuser::{
GetWindowLongW, SetWindowLongW, ShowWindow, GWL_STYLE, SW_MAXIMIZE, SW_MINIMIZE,
SW_RESTORE, WS_CAPTION, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_THICKFRAME,
};
if let Some(hwnd) = self.get_native_handle(widget_id) {
match flag {
WindowStateFlag::Maximized => unsafe {
let cmd = if on { SW_MAXIMIZE } else { SW_RESTORE };
ShowWindow(hwnd, cmd);
},
WindowStateFlag::Minimized => unsafe {
let cmd = if on { SW_MINIMIZE } else { SW_RESTORE };
ShowWindow(hwnd, cmd);
},
WindowStateFlag::Fullscreen => {
unsafe {
let style = GetWindowLongW(hwnd, GWL_STYLE) as u32;
let frame =
WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
let new_style = if on { style & !frame } else { style | frame };
SetWindowLongW(hwnd, GWL_STYLE, new_style as i32);
ShowWindow(hwnd, SW_MAXIMIZE);
}
}
WindowStateFlag::Resizable => unsafe {
let style = GetWindowLongW(hwnd, GWL_STYLE) as u32;
let new_style = if on {
style | WS_THICKFRAME | WS_MAXIMIZEBOX
} else {
style & !(WS_THICKFRAME | WS_MAXIMIZEBOX)
};
SetWindowLongW(hwnd, GWL_STYLE, new_style as i32);
},
WindowStateFlag::Decorated => unsafe {
let style = GetWindowLongW(hwnd, GWL_STYLE) as u32;
let decorative = WS_CAPTION | WS_THICKFRAME;
let new_style = if on { style | decorative } else { style & !decorative };
SetWindowLongW(hwnd, GWL_STYLE, new_style as i32);
},
}
}
}
true
}
fn is_window_in_state(&self, widget_id: ObjectId, flag: WindowStateFlag) -> Option<bool> {
let kind = self.state.kind_of(widget_id)?;
if !matches!(kind, super::types::WindowsHandleKind::Window) {
return None;
}
#[cfg(target_os = "windows")]
{
use winapi::um::winuser::{
GetWindowLongW, IsIconic, IsZoomed, GWL_STYLE, WS_CAPTION, WS_MAXIMIZEBOX,
WS_MINIMIZEBOX, WS_THICKFRAME,
};
if let Some(hwnd) = self.get_native_handle(widget_id) {
let value = match flag {
WindowStateFlag::Maximized => unsafe { IsZoomed(hwnd) != 0 },
WindowStateFlag::Minimized => unsafe { IsIconic(hwnd) != 0 },
WindowStateFlag::Fullscreen => unsafe {
let style = GetWindowLongW(hwnd, GWL_STYLE) as u32;
style & (WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX) == 0
},
WindowStateFlag::Resizable => unsafe {
let style = GetWindowLongW(hwnd, GWL_STYLE) as u32;
style & (WS_THICKFRAME | WS_MAXIMIZEBOX) != 0
},
WindowStateFlag::Decorated => unsafe {
let style = GetWindowLongW(hwnd, GWL_STYLE) as u32;
style & (WS_CAPTION | WS_THICKFRAME) != 0
},
};
return Some(value);
}
}
self.state.window_state(widget_id, flag)
}
fn set_window_min_size(&self, widget_id: ObjectId, width: u32, height: u32) -> bool {
let Some(kind) = self.state.kind_of(widget_id) else {
return false;
};
if !matches!(kind, super::types::WindowsHandleKind::Window) {
return false;
}
self.state.set_window_min_size(widget_id, width, height);
#[cfg(target_os = "windows")]
{
use winapi::um::winuser::{SetWindowPos, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOZORDER};
if let Some(hwnd) = self.get_native_handle(widget_id) {
unsafe {
SetWindowPos(
hwnd,
std::ptr::null_mut(),
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE,
);
}
}
}
true
}
fn window_min_size(&self, widget_id: ObjectId) -> Option<(u32, u32)> {
let kind = self.state.kind_of(widget_id)?;
if !matches!(kind, super::types::WindowsHandleKind::Window) {
return None;
}
self.state.window_min_size(widget_id)
}
fn set_window_icon(&self, widget_id: ObjectId, path: &str) -> bool {
let Some(kind) = self.state.kind_of(widget_id) else {
return false;
};
if !matches!(kind, super::types::WindowsHandleKind::Window) {
return false;
}
#[cfg(target_os = "windows")]
{
use winapi::um::winuser::{
LoadImageW, SendMessageW, ICON_BIG, ICON_SMALL, IMAGE_ICON, LR_DEFAULTSIZE,
LR_LOADFROMFILE, WM_SETICON,
};
if let Some(hwnd) = self.get_native_handle(widget_id) {
let wide = Self::to_wide(path);
let icon = unsafe {
LoadImageW(
std::ptr::null_mut(),
wide.as_ptr(),
IMAGE_ICON,
0,
0,
LR_LOADFROMFILE | LR_DEFAULTSIZE,
)
};
if icon.is_null() {
log::warn!("[rust_widgets][windows] set_window_icon: could not load '{path}'");
return false;
}
unsafe {
SendMessageW(hwnd, WM_SETICON, ICON_SMALL as usize, icon as isize);
SendMessageW(hwnd, WM_SETICON, ICON_BIG as usize, icon as isize);
}
}
}
self.state.set_window_icon(widget_id, path);
true
}
fn window_icon(&self, widget_id: ObjectId) -> Option<String> {
let kind = self.state.kind_of(widget_id)?;
if !matches!(kind, super::types::WindowsHandleKind::Window) {
return None;
}
self.state.window_icon(widget_id)
}
fn backend_name(&self) -> &'static str {
"WindowsPlatform"
}
#[cfg(target_os = "windows")]
fn text_scale(&self) -> f32 {
use winapi::um::winnt::{KEY_READ, REG_DWORD};
use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER};
cached_environment_fact(&TEXT_SCALE_CACHE, || {
unsafe {
let subkey: Vec<u16> = "Software\\Microsoft\\Accessibility"
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let value_name: Vec<u16> =
"TextScaleFactor".encode_utf16().chain(std::iter::once(0)).collect();
let mut key = std::ptr::null_mut();
if RegOpenKeyExW(HKEY_CURRENT_USER, subkey.as_ptr(), 0, KEY_READ, &mut key) != 0 {
return 1.0;
}
let mut data: u32 = 0;
let mut size = std::mem::size_of::<u32>() as u32;
let mut kind: u32 = 0;
let status = RegQueryValueExW(
key,
value_name.as_ptr(),
std::ptr::null_mut(),
&mut kind,
&mut data as *mut u32 as *mut u8,
&mut size,
);
RegCloseKey(key);
if status != 0 || kind != REG_DWORD || data == 0 {
return 1.0;
}
data as f32 / 100.0
}
})
}
#[cfg(not(target_os = "windows"))]
fn text_scale(&self) -> f32 {
1.0
}
#[cfg(target_os = "windows")]
fn clipboard_generation(&self) -> u64 {
use winapi::um::winuser::GetClipboardSequenceNumber;
u64::from(unsafe { GetClipboardSequenceNumber() })
}
#[cfg(not(target_os = "windows"))]
fn clipboard_generation(&self) -> u64 {
0
}
#[cfg(target_os = "windows")]
fn system_theme(&self) -> Option<bool> {
use winapi::um::winnt::{KEY_READ, REG_DWORD};
use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER};
cached_environment_fact(&SYSTEM_THEME_CACHE, || {
unsafe {
let subkey: Vec<u16> =
"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let value_name: Vec<u16> =
"AppsUseLightTheme".encode_utf16().chain(std::iter::once(0)).collect();
let mut key = std::ptr::null_mut();
if RegOpenKeyExW(HKEY_CURRENT_USER, subkey.as_ptr(), 0, KEY_READ, &mut key) != 0 {
return None;
}
let mut data: u32 = 0;
let mut size = std::mem::size_of::<u32>() as u32;
let mut kind: u32 = 0;
let status = RegQueryValueExW(
key,
value_name.as_ptr(),
std::ptr::null_mut(),
&mut kind,
&mut data as *mut u32 as *mut u8,
&mut size,
);
RegCloseKey(key);
if status != 0 || kind != REG_DWORD {
return None;
}
match data {
0 => Some(true),
1 => Some(false),
_ => None,
}
}
})
}
#[cfg(not(target_os = "windows"))]
fn system_theme(&self) -> Option<bool> {
None
}
#[cfg(target_os = "windows")]
fn high_contrast(&self) -> Option<bool> {
use winapi::shared::minwindef::DWORD;
use winapi::um::winuser::{
SystemParametersInfoW, HCF_HIGHCONTRASTON, HIGHCONTRASTW, SPI_GETHIGHCONTRAST,
};
cached_environment_fact(&HIGH_CONTRAST_CACHE, || {
unsafe {
let mut contrast: HIGHCONTRASTW = std::mem::zeroed();
contrast.cbSize = std::mem::size_of::<HIGHCONTRASTW>() as u32;
let ok = SystemParametersInfoW(
SPI_GETHIGHCONTRAST,
contrast.cbSize,
&mut contrast as *mut HIGHCONTRASTW as *mut _,
0,
);
if ok == 0 {
return None;
}
let flags: DWORD = contrast.dwFlags;
Some(flags & HCF_HIGHCONTRASTON != 0)
}
})
}
#[cfg(not(target_os = "windows"))]
fn high_contrast(&self) -> Option<bool> {
None
}
#[cfg(target_os = "windows")]
fn reduce_motion(&self) -> Option<bool> {
use winapi::shared::minwindef::BOOL;
use winapi::um::winuser::{SystemParametersInfoW, SPI_GETCLIENTAREAANIMATION};
cached_environment_fact(&REDUCE_MOTION_CACHE, || {
unsafe {
let mut animations: BOOL = 1;
let ok = SystemParametersInfoW(
SPI_GETCLIENTAREAANIMATION,
0,
&mut animations as *mut BOOL as *mut _,
0,
);
if ok == 0 {
return None;
}
Some(animations == 0)
}
})
}
#[cfg(not(target_os = "windows"))]
fn reduce_motion(&self) -> Option<bool> {
None
}
#[cfg(target_os = "windows")]
fn system_locale(&self) -> Option<&'static str> {
use winapi::um::winnls::GetUserDefaultLocaleName;
const LOCALE_NAME_MAX_LENGTH: usize = 85;
cached_environment_fact(&SYSTEM_LOCALE_CACHE, || {
unsafe {
let mut buffer = [0u16; LOCALE_NAME_MAX_LENGTH];
let written = GetUserDefaultLocaleName(buffer.as_mut_ptr(), buffer.len() as i32);
if written <= 0 {
return None;
}
let length = (written as usize - 1).min(buffer.len());
let tag = String::from_utf16_lossy(&buffer[..length]);
crate::platform::locale::intern(Some(tag))
}
})
}
#[cfg(not(target_os = "windows"))]
fn system_locale(&self) -> Option<&'static str> {
None
}
fn environment_generation(&self) -> u64 {
crate::platform::locale::environment_revision(self)
}
#[cfg(target_os = "windows")]
fn system_fonts(&self) -> Vec<SystemFont> {
dwrite_system_fonts()
}
#[cfg(not(target_os = "windows"))]
fn system_fonts(&self) -> Vec<SystemFont> {
Vec::new()
}
#[cfg(target_os = "windows")]
fn set_cursor(&self, _window: ObjectId, icon: crate::platform::CursorIcon) -> bool {
use crate::platform::CursorIcon;
use winapi::um::winuser::{
LoadCursorW, SetCursor, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, IDC_NO,
IDC_SIZEALL, IDC_SIZENS, IDC_SIZEWE, IDC_WAIT,
};
let name = match icon {
CursorIcon::Default => IDC_ARROW,
CursorIcon::Pointer => IDC_HAND,
CursorIcon::Text => IDC_IBEAM,
CursorIcon::Crosshair | CursorIcon::ZoomIn | CursorIcon::ZoomOut => IDC_CROSS,
CursorIcon::Move | CursorIcon::Grab | CursorIcon::Grabbing => IDC_SIZEALL,
CursorIcon::NotAllowed => IDC_NO,
CursorIcon::ResizeHorizontal | CursorIcon::ResizeNwSe | CursorIcon::ResizeNeSw => {
IDC_SIZEWE
}
CursorIcon::ResizeVertical => IDC_SIZENS,
CursorIcon::Wait | CursorIcon::Progress => IDC_WAIT,
CursorIcon::Help => IDC_HELP,
};
unsafe {
let cursor = LoadCursorW(std::ptr::null_mut(), name);
if cursor.is_null() {
return false;
}
let _ = SetCursor(cursor);
}
true
}
fn total_memory_mb(&self) -> Option<u64> {
#[cfg(target_os = "windows")]
{
use winapi::um::sysinfoapi::{GlobalMemoryStatusEx, MEMORYSTATUSEX};
let mut status: MEMORYSTATUSEX = unsafe { std::mem::zeroed() };
status.dwLength = std::mem::size_of::<MEMORYSTATUSEX>() as u32;
let ok = unsafe { GlobalMemoryStatusEx(&mut status) };
if ok != 0 {
return Some(status.ullTotalPhys / (1024 * 1024));
}
}
None
}
fn is_on_battery(&self) -> bool {
#[cfg(target_os = "windows")]
{
use winapi::um::winbase::{GetSystemPowerStatus, SYSTEM_POWER_STATUS};
let mut status: SYSTEM_POWER_STATUS = unsafe { std::mem::zeroed() };
let ok = unsafe { GetSystemPowerStatus(&mut status) };
if ok != 0 {
return status.ACLineStatus == 0;
}
}
false
}
fn process_memory_utilization(&self) -> Option<f32> {
#[cfg(target_os = "windows")]
{
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::psapi::{GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS};
let mut counters: PROCESS_MEMORY_COUNTERS = unsafe { std::mem::zeroed() };
counters.cb = std::mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32;
let ok =
unsafe { GetProcessMemoryInfo(GetCurrentProcess(), &mut counters, counters.cb) };
if ok != 0 {
let total = self.total_memory_mb()? as f64 * 1024.0 * 1024.0;
if total > 0.0 {
let ratio = (counters.WorkingSetSize as f64 / total) as f32;
return Some(ratio.clamp(0.0, 1.0));
}
}
}
None
}
fn process_cpu_utilization(&self) -> Option<f32> {
None
}
fn spawn_print_job(&self, job_file: &std::path::Path) -> Result<(), String> {
std::process::Command::new("powershell")
.arg("-NoProfile")
.arg("-Command")
.arg("Start-Process -FilePath $args[0] -Verb Print -PassThru | Out-Null")
.arg(job_file)
.spawn()
.map(|_child| ())
.map_err(|error| {
format!("could not launch the Windows print verb for {job_file:?}: {error}")
})
}
fn has_print_support(&self) -> bool {
windows_shell_spawnable()
}
#[cfg(widgets_unstripped)]
fn mount_surface(&self, parent: ObjectId, id: ObjectId, rect: crate::core::Rect) -> bool {
let Some(parent_hwnd) = self.get_native_handle(parent) else {
log::error!("[windows] mount_surface: unknown parent window {parent}");
return false;
};
let Some(hwnd) = super::canvas::mount_canvas(parent_hwnd, id, rect) else {
return false;
};
unsafe { self.bind_native_handle(id, hwnd) };
crate::widget::runtime::set_geometry(id, rect);
true
}
#[cfg(widgets_unstripped)]
fn resize_surface(&self, id: ObjectId, rect: crate::core::Rect) -> bool {
let Some(hwnd) = super::canvas::hwnd_for_widget(id) else {
log::error!("[windows] resize_surface: id={id} is not mounted");
return false;
};
if !super::canvas::resize_canvas(hwnd, rect) {
return false;
}
crate::widget::runtime::set_geometry(id, rect);
true
}
#[cfg(widgets_unstripped)]
fn unmount_surface(&self, id: ObjectId) -> bool {
let Some(hwnd) = super::canvas::hwnd_for_widget(id) else {
log::error!("[windows] unmount_surface: id={id} is not mounted");
return false;
};
unsafe { self.unbind_native_handle(id, hwnd) };
super::canvas::unmount_canvas(hwnd)
}
#[cfg(widgets_unstripped)]
fn supports_surfaces(&self) -> bool {
true
}
fn set_widget_visible(&self, widget_id: ObjectId, visible: bool) -> bool {
#[cfg(widgets_unstripped)]
{
let Some(hwnd) = super::window_hwnd_for_widget_id(self, widget_id) else {
return false;
};
if !unsafe { winapi::um::winuser::GetParent(hwnd) }.is_null() {
return false;
}
unsafe {
let command = if visible { SW_SHOW } else { SW_HIDE };
winapi::um::winuser::ShowWindow(hwnd, command);
}
super::canvas::invalidate_for_handle(hwnd);
true
}
#[cfg(not(widgets_unstripped))]
{
let _ = (widget_id, visible);
false
}
}
#[cfg(target_os = "windows")]
fn window_client_size(&self, window_id: ObjectId) -> Option<(u32, u32)> {
use winapi::um::winuser::GetClientRect;
if let Some(hwnd) = self.get_native_handle(window_id) {
let mut rect = winapi::shared::windef::RECT { left: 0, top: 0, right: 0, bottom: 0 };
if unsafe { GetClientRect(hwnd, &mut rect) } != 0 {
let width = (rect.right - rect.left).max(0) as u32;
let height = (rect.bottom - rect.top).max(0) as u32;
if width > 0 && height > 0 {
return Some((width, height));
}
}
}
crate::window_client_size(window_id).or_else(|| self.state.window_size(window_id))
}
fn queue_resize_trigger(&self, window_id: ObjectId, width: u32, height: u32) -> bool {
crate::queue_resize_trigger(window_id, width, height)
}
#[cfg(widgets_unstripped)]
fn invalidate_surface(&self, id: ObjectId) -> bool {
if let Some(hwnd) = super::window_hwnd_for_widget_id(self, id) {
super::canvas::invalidate_for_handle(hwnd);
return true;
}
if let Some(hwnd) = super::canvas::hwnd_for_widget(id) {
super::canvas::invalidate_canvas(hwnd);
return true;
}
false
}
#[cfg(widgets_unstripped)]
fn invalidate_surface_rect(&self, id: ObjectId, rect: crate::core::Rect) -> bool {
match super::canvas::hwnd_for_widget(id) {
Some(hwnd) => super::canvas::invalidate_canvas_rect(hwnd, rect),
None => false,
}
}
fn family(&self) -> PlatformFamily {
PlatformFamily::Desktop
}
fn get_native_handle(&self, widget: ObjectId) -> Option<usize> {
#[cfg(target_os = "windows")]
{
WindowsPlatform::get_native_handle(self, widget).map(|hwnd| hwnd as usize)
}
#[cfg(not(target_os = "windows"))]
{
let _ = widget;
None
}
}
#[cfg(feature = "raw-window-handle")]
fn raw_window_handle(&self, window: ObjectId) -> Option<raw_window_handle::RawWindowHandle> {
#[cfg(target_os = "windows")]
{
let hwnd = WindowsPlatform::get_native_handle(self, window)?;
let hwnd = core::num::NonZeroIsize::new(hwnd as isize)?;
Some(raw_window_handle::RawWindowHandle::Win32(
raw_window_handle::Win32WindowHandle::new(hwnd),
))
}
#[cfg(not(target_os = "windows"))]
{
let _ = window;
None
}
}
#[cfg(all(feature = "raw-window-handle", target_os = "windows"))]
fn raw_display_handle(&self) -> Option<raw_window_handle::RawDisplayHandle> {
Some(raw_window_handle::RawDisplayHandle::Windows(
raw_window_handle::WindowsDisplayHandle::new(),
))
}
fn capabilities(&self) -> PlatformCapabilities {
PlatformCapabilities {
dpi_scaling: true,
ime: self.ime_bridge.has_native_ime(),
accessibility: true,
native_menu: false,
typed_widget_trigger: true,
clipboard: true,
open_external: true,
file_dialog: true,
system_fonts: true,
}
}
fn choose_path(
&self,
purpose: crate::platform::PathPurpose,
title: &str,
) -> Result<Option<std::path::PathBuf>, crate::platform::PathPickerError> {
super::file_picker::choose_path(purpose, title)
}
fn open_external(&self, target: &str) -> Result<(), crate::platform::types::OpenError> {
crate::platform::external::spawn_handler("cmd", &["/C", "start", ""], target)
}
fn has_external_handler(&self) -> bool {
crate::platform::external::program_is_on_path("cmd")
}
fn dpi_scale_factor(&self) -> f32 {
#[cfg(target_os = "windows")]
{
cached_environment_fact(&DPI_SCALE_CACHE, || {
unsafe {
let hdc = winapi::um::winuser::GetDC(std::ptr::null_mut());
if hdc.is_null() {
return 1.0;
}
let dpi =
winapi::um::wingdi::GetDeviceCaps(hdc, winapi::um::wingdi::LOGPIXELSX);
winapi::um::winuser::ReleaseDC(std::ptr::null_mut(), hdc);
dpi as f32 / 96.0
}
})
}
#[cfg(not(target_os = "windows"))]
{
1.0
}
}
fn displays(&self) -> Vec<Display> {
super::displays::displays()
}
#[cfg(all(target_os = "windows", widgets_unstripped))]
fn display_of_window(&self, window_id: ObjectId) -> Option<DisplayId> {
super::window_hwnd_for_widget_id(self, window_id)
.and_then(super::displays::display_of_window_handle)
}
#[cfg(all(target_os = "windows", not(widgets_unstripped)))]
fn display_of_window(&self, window_id: ObjectId) -> Option<DisplayId> {
self.get_native_handle(window_id).and_then(super::displays::display_of_window_handle)
}
fn display_generation(&self) -> u64 {
super::displays::generation()
}
fn safe_area_insets(
&self,
_window_id: ObjectId,
) -> Option<crate::platform::types::SafeAreaInsets> {
Some(crate::platform::types::SafeAreaInsets { top: 0, bottom: 0, left: 0, right: 0 })
}
fn init(&self) {
if self.runtime_initialized.swap(true, Ordering::SeqCst) {
return;
}
#[cfg(target_os = "windows")]
{
let static_self: &'static WindowsPlatform =
unsafe { std::mem::transmute::<&WindowsPlatform, &'static WindowsPlatform>(self) };
notify::register_active_platform(static_self);
}
#[cfg(target_os = "windows")]
unsafe {
use winapi::um::commctrl::InitCommonControls;
InitCommonControls();
}
}
fn run(&self) {
#[cfg(target_os = "windows")]
unsafe {
use std::ptr::null_mut;
use winapi::um::winuser::{
DispatchMessageW, PeekMessageW, TranslateMessage, MSG, PM_REMOVE, WM_QUIT,
};
self.runtime_running.store(true, Ordering::SeqCst);
while self.runtime_running.load(Ordering::SeqCst) {
let mut msg: MSG = std::mem::zeroed();
while PeekMessageW(&mut msg, null_mut(), 0, 0, PM_REMOVE) != 0 {
if msg.message == WM_QUIT {
self.runtime_running.store(false, Ordering::SeqCst);
break;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
let outcome = crate::drive_frame(FRAME_INTERVAL_MS as u32);
if self.runtime_running.load(Ordering::SeqCst) {
crate::platform::frame_wait::pause_between_frames(outcome.needs_another_frame);
}
}
self.runtime_running.store(false, Ordering::SeqCst);
}
}
fn quit(&self) -> bool {
self.runtime_running.store(false, Ordering::SeqCst);
crate::platform::frame_wait::wake();
#[cfg(target_os = "windows")]
unsafe {
use winapi::um::winuser::PostQuitMessage;
PostQuitMessage(0);
}
true
}
fn destroy_widget(&self, widget_id: ObjectId) -> bool {
#[cfg(target_os = "windows")]
{
self.menu_state.purge(widget_id);
self.a11y_bridge.unregister_handle(widget_id);
}
self.state.destroy_widget(widget_id)
}
fn poll_widget_trigger_event(&self) -> Option<WidgetTriggerEvent> {
#[cfg(target_os = "windows")]
{
match self.menu_state.pending_widget_events.lock() {
Ok(mut events) => events.pop_front(),
Err(_) => {
log::error!(
"[rust_widgets][windows] poll_widget_trigger_event: trigger queue \
poisoned; queued activations cannot be delivered"
);
None
}
}
}
#[cfg(not(target_os = "windows"))]
{
None
}
}
fn poll_widget_triggered(&self) -> Option<ObjectId> {
self.poll_widget_trigger_event().map(|event| event.widget_id)
}
fn pop_widget_trigger_event_for(&self, widget_id: ObjectId) -> Option<WidgetTriggerEvent> {
#[cfg(target_os = "windows")]
{
if !self.state.contains_widget(widget_id) {
return None;
}
match self.menu_state.pending_widget_events.lock() {
Ok(mut events) => events
.iter()
.position(|event| event.widget_id == widget_id)
.and_then(|index| events.remove(index)),
Err(_) => {
log::error!(
"[rust_widgets][windows] pop_widget_trigger_event_for: trigger queue \
poisoned; the activation for widget {widget_id} cannot be delivered"
);
None
}
}
}
#[cfg(not(target_os = "windows"))]
{
let _ = widget_id;
None
}
}
fn inject_widget_trigger_event(&self, widget_id: ObjectId, kind: WidgetTriggerKind) -> bool {
#[cfg(target_os = "windows")]
{
if !self.state.contains_widget(widget_id) {
return false;
}
match self.menu_state.pending_widget_events.lock() {
Ok(mut events) => {
events.push_back(WidgetTriggerEvent { widget_id, kind });
true
}
Err(_) => {
log::error!(
"[rust_widgets][windows] inject_widget_trigger_event: trigger queue \
poisoned; the activation for widget {widget_id} was not queued"
);
false
}
}
}
#[cfg(not(target_os = "windows"))]
{
let _ = (widget_id, kind);
false
}
}
fn create_window(&self, title: &str, x: i32, y: i32, width: u32, height: u32) -> ObjectId {
#[cfg(target_os = "windows")]
{
unsafe extern "system" {
fn GetModuleHandleW(lpModuleName: *const u16) -> *mut std::ffi::c_void;
}
use std::ptr::null_mut;
use winapi::um::winuser::{
CreateWindowExW, ShowWindow, UpdateWindow, SW_SHOW, WS_OVERLAPPEDWINDOW, WS_VISIBLE,
};
notify::ensure_window_class_registered();
let class_name = Self::to_wide("RustWidgetsWindowClass");
let title_wide = Self::to_wide(title);
let hinstance = unsafe { GetModuleHandleW(std::ptr::null()) };
let mut frame = winapi::shared::windef::RECT {
left: 0,
top: 0,
right: width as i32,
bottom: height as i32,
};
unsafe {
winapi::um::winuser::AdjustWindowRect(&mut frame, WS_OVERLAPPEDWINDOW, 0);
}
let outer_width = frame.right - frame.left;
let outer_height = frame.bottom - frame.top;
let hwnd = unsafe {
CreateWindowExW(
0,
class_name.as_ptr(),
title_wide.as_ptr(),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
x,
y,
outer_width,
outer_height,
null_mut(),
null_mut(),
hinstance as _,
null_mut(),
)
};
if hwnd.is_null() {
log::error!(
"[rust_widgets][windows] create_window failed for title='{}' (GetLastError={})",
title,
unsafe { winapi::um::errhandlingapi::GetLastError() }
);
return 0;
}
let widget_id =
self.state.create_widget(WindowsHandleKind::Window, title, x, y, width, height);
super::types::register_library_window();
unsafe { self.bind_native_handle(widget_id, hwnd) };
self.state.init_window_state(
widget_id,
crate::platform::state::WindowStateRecord::new_window(),
);
unsafe {
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
}
widget_id
}
#[cfg(not(target_os = "windows"))]
{
let widget_id =
self.state.create_widget(WindowsHandleKind::Window, title, x, y, width, height);
self.state.init_window_state(
widget_id,
crate::platform::state::WindowStateRecord::new_window(),
);
widget_id
}
}
fn set_clipboard_text(&self, _text: &str) -> bool {
#[cfg(target_os = "windows")]
{
use winapi::um::winbase::GlobalAlloc;
use winapi::um::winbase::{GlobalLock, GlobalUnlock, GHND};
use winapi::um::winuser::{
CloseClipboard, EmptyClipboard, OpenClipboard, SetClipboardData, CF_UNICODETEXT,
};
let text_utf16: Vec<u16> = _text.encode_utf16().chain(std::iter::once(0)).collect();
let byte_size = text_utf16.len() * 2;
unsafe {
if OpenClipboard(std::ptr::null_mut()) == 0 {
return false;
}
if EmptyClipboard() == 0 {
CloseClipboard();
return false;
}
let h_mem = GlobalAlloc(GHND, byte_size);
if h_mem.is_null() {
CloseClipboard();
return false;
}
let p_dest = GlobalLock(h_mem) as *mut u16;
if p_dest.is_null() {
GlobalUnlock(h_mem);
CloseClipboard();
return false;
}
std::ptr::copy_nonoverlapping(text_utf16.as_ptr(), p_dest, text_utf16.len());
GlobalUnlock(h_mem);
let ret = SetClipboardData(CF_UNICODETEXT, h_mem as _);
CloseClipboard();
ret as isize != 0
}
}
#[cfg(not(target_os = "windows"))]
{
let _ = _text;
false
}
}
fn get_clipboard_text(&self) -> String {
#[cfg(target_os = "windows")]
{
read_clipboard_text().unwrap_or_default()
}
#[cfg(not(target_os = "windows"))]
{
String::new()
}
}
fn clipboard_text(&self) -> Option<String> {
#[cfg(target_os = "windows")]
{
read_clipboard_text()
}
#[cfg(not(target_os = "windows"))]
{
None
}
}
fn begin_drag(&self, source_widget_id: ObjectId, mime: &str, payload: &[u8]) -> bool {
#[cfg(target_os = "windows")]
{
self.state.begin_drag(source_widget_id, mime, payload)
}
#[cfg(not(target_os = "windows"))]
{
let _ = (source_widget_id, mime, payload);
false
}
}
fn poll_drop_event(&self) -> Option<DropEvent> {
#[cfg(target_os = "windows")]
{
self.state.pop_drop_event()
}
#[cfg(not(target_os = "windows"))]
{
None
}
}
fn inject_drop_event(&self, event: DropEvent) -> bool {
#[cfg(target_os = "windows")]
{
self.state.inject_drop_event(event)
}
#[cfg(not(target_os = "windows"))]
{
let _ = event;
false
}
}
fn set_widget_ime_enabled(&self, widget_id: ObjectId, enabled: bool) -> bool {
self.state.set_ime_enabled(widget_id, enabled)
}
fn is_widget_ime_enabled(&self, widget_id: ObjectId) -> bool {
self.state.ime_enabled(widget_id)
}
fn set_widget_accessibility_name(&self, widget_id: ObjectId, name: &str) -> bool {
let stored = self.state.set_accessibility_name(widget_id, name);
#[cfg(target_os = "windows")]
{
self.a11y_bridge.set_accessibility_name(widget_id, name);
}
stored
}
fn get_widget_accessibility_name(&self, widget_id: ObjectId) -> String {
self.state.accessibility_name(widget_id)
}
fn ime_bridge(&self) -> Option<&dyn ImeBridge> {
Some(&self.ime_bridge)
}
fn clipboard_backend(&self) -> Option<&dyn RichClipboardBackend> {
Some(&self.clipboard)
}
#[cfg(target_os = "windows")]
fn accessibility_bridge(&self) -> Option<&dyn AccessibilityBridge> {
Some(&self.a11y_bridge)
}
}