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, WindowStateFlag};
use crate::compat::{String, Vec};
use crate::platform::types::windows_shell_spawnable;
use crate::platform::windows::notify;
use crate::platform::windows::types::*;
use crate::platform::DropEvent;
use std::sync::atomic::Ordering;
const FRAME_INTERVAL_MS: u64 = 16;
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"
}
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> {
let status = std::process::Command::new("powershell")
.arg("-NoProfile")
.arg("-Command")
.arg("Start-Process -FilePath $args[0] -Verb Print -PassThru | Out-Null")
.arg(job_file)
.status();
if let Ok(status) = status {
if status.success() {
return Ok(());
}
}
Err(format!(
"no Windows print command could submit job file '{}'; every candidate \
PowerShell/spooler invocation failed",
job_file.display()
))
}
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;
};
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;
};
super::canvas::unmount_canvas(hwnd)
}
#[cfg(widgets_unstripped)]
fn supports_surfaces(&self) -> bool {
true
}
#[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 {
match super::canvas::hwnd_for_widget(id) {
Some(hwnd) => {
super::canvas::invalidate_canvas(hwnd);
true
}
None => 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
}
}
fn capabilities(&self) -> PlatformCapabilities {
PlatformCapabilities {
dpi_scaling: true,
ime: true,
accessibility: true,
native_menu: true,
typed_widget_trigger: true,
}
}
fn dpi_scale_factor(&self) -> f32 {
#[cfg(target_os = "windows")]
{
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 init(&self) {
self.runtime_initialized.store(true, Ordering::SeqCst);
#[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 std::thread;
use std::time::Duration;
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);
}
crate::drive_frame(FRAME_INTERVAL_MS as u32);
if self.runtime_running.load(Ordering::SeqCst) {
thread::sleep(Duration::from_millis(FRAME_INTERVAL_MS));
}
}
self.runtime_running.store(false, Ordering::SeqCst);
}
}
fn quit(&self) {
#[cfg(target_os = "windows")]
unsafe {
use winapi::um::winuser::PostQuitMessage;
self.runtime_running.store(false, Ordering::SeqCst);
PostQuitMessage(0);
}
}
fn destroy_widget(&self, widget_id: ObjectId) -> bool {
#[cfg(target_os = "windows")]
{
if let Ok(mut handles) = self.menu_state.handles.lock() {
handles.remove(&widget_id);
} else {
log::error!("[rust_widgets][windows] destroy_widget: handles mutex poisoned");
}
}
self.state.destroy_widget(widget_id)
}
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 hwnd = unsafe {
CreateWindowExW(
0,
class_name.as_ptr(),
title_wide.as_ptr(),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
x,
y,
width as i32,
height as i32,
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);
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")]
{
use winapi::um::winbase::GlobalLock;
use winapi::um::winuser::{
CloseClipboard, GetClipboardData, OpenClipboard, CF_UNICODETEXT,
};
let result = unsafe {
if OpenClipboard(std::ptr::null_mut()) == 0 {
return String::new();
}
let h_mem = GetClipboardData(CF_UNICODETEXT);
if h_mem.is_null() {
CloseClipboard();
return String::new();
}
let p_src = GlobalLock(h_mem) as *const u16;
if p_src.is_null() {
CloseClipboard();
return String::new();
}
let mut len = 0;
while *p_src.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts(p_src, len);
let text = String::from_utf16_lossy(slice);
CloseClipboard();
text
};
result
}
#[cfg(not(target_os = "windows"))]
{
String::new()
}
}
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 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)
}
}