use core::ptr;
use std::sync::OnceLock;
use crate::{
DisplayMode, MonitorId, MonitorInfo, PhysicalSize, Shell, ShellBackend, ShellError,
WindowConfiguration,
};
use super::ffi::{self, Handle, Rect, WindowPlacement, WndClassExW, dwm, style, value};
use super::geometry::{self, Frame, Styles};
use super::proc::{self, Limits};
use super::shell::{Saved, Win32Shell, WinWindow};
const CLASS_NAME: &str = "crcbl_window";
static CLASS: OnceLock<Result<(), u32>> = OnceLock::new();
pub(super) fn class_name() -> Vec<u16> {
ffi::wide(CLASS_NAME).expect("the class name is a literal with no interior NUL")
}
pub(super) fn register_class(instance: Handle) -> Result<(), ShellError> {
let outcome = *CLASS.get_or_init(|| {
let name = class_name();
let cursor = unsafe { ffi::LoadCursorW(ptr::null_mut(), value::IDC_ARROW) };
let brush = unsafe { ffi::GetStockObject(value::BLACK_BRUSH) };
let class = WndClassExW {
cb_size: size_of::<WndClassExW>() as u32,
style: value::CS_REDRAW,
lpfn_wnd_proc: proc::window_proc,
cb_cls_extra: 0,
cb_wnd_extra: 0,
h_instance: instance,
h_icon: ptr::null_mut(),
h_cursor: cursor,
hbr_background: brush,
lpsz_menu_name: ptr::null(),
lpsz_class_name: name.as_ptr(),
h_icon_sm: ptr::null_mut(),
};
let atom = unsafe { ffi::RegisterClassExW(&raw const class) };
if atom != 0 {
return Ok(());
}
let error = unsafe { ffi::GetLastError() };
if error == value::ERROR_CLASS_ALREADY_EXISTS {
crcbl_core::log::debug!(
"the {CLASS_NAME} class was already registered in this process"
);
return Ok(());
}
Err(error)
});
outcome.map_err(|error| ShellError::Connect {
backend: ShellBackend::Win32,
detail: format!(
"RegisterClassExW failed with Win32 error {error} — a process with no usable \
window station cannot create windows"
),
})
}
#[derive(Clone, Copy, Debug)]
pub(super) struct Created {
pub hwnd: Handle,
pub dpi: u32,
pub size: PhysicalSize,
}
impl Win32Shell {
pub(super) fn frame_for(styles: Styles, dpi: u32) -> Frame {
let mut rect = Rect::default();
let ok = unsafe {
ffi::AdjustWindowRectExForDpi(
&raw mut rect,
styles.style,
0,
styles.ex_style,
dpi.max(1),
)
};
if ok == 0 {
crcbl_core::log::debug!("AdjustWindowRectExForDpi refused style {:#x}", styles.style);
return Frame::default();
}
Frame {
width: rect.width(),
height: rect.height(),
}
}
pub(super) fn dpi_of(hwnd: Handle) -> u32 {
let dpi = unsafe { ffi::GetDpiForWindow(hwnd) };
if dpi == 0 { value::DEFAULT_DPI } else { dpi }
}
pub(super) fn client_size_of(hwnd: Handle) -> PhysicalSize {
let mut rect = Rect::default();
let ok = unsafe { ffi::GetClientRect(hwnd, &raw mut rect) };
if ok == 0 {
return PhysicalSize::default();
}
geometry::client_size(rect)
}
pub(super) fn create_native_window(
&self,
desc: &crate::WindowDesc<'_>,
target: Option<&MonitorInfo>,
) -> Result<Created, ShellError> {
let title = ffi::wide(desc.title).ok_or_else(|| {
ShellError::InvalidDescriptor("title contains a NUL byte".to_string())
})?;
let styles = geometry::with_transparency(
geometry::styles(desc.mode, desc.resizable),
desc.transparent,
);
let dpi = target.map_or(value::DEFAULT_DPI, |monitor| {
(monitor.scale_factor * f64::from(value::DEFAULT_DPI)).round() as u32
});
let frame = Self::frame_for(styles, dpi);
let client = match (desc.mode, target) {
(DisplayMode::Borderless { .. }, Some(monitor)) => monitor.bounds.size(),
_ => desc.size.to_physical(geometry::scale_from_dpi(dpi)),
};
let outer = frame.window_size((
i32::try_from(client.width).unwrap_or(i32::MAX),
i32::try_from(client.height).unwrap_or(i32::MAX),
));
let (x, y) = match (desc.mode, target) {
(DisplayMode::Borderless { .. }, Some(monitor)) => (monitor.bounds.x, monitor.bounds.y),
(_, Some(monitor)) => geometry::centred(monitor.work_area, outer),
(_, None) => (value::CW_USE_DEFAULT, value::CW_USE_DEFAULT),
};
let mut window_style = styles.style;
if desc.visible {
window_style |= style::VISIBLE;
}
let name = class_name();
let hwnd = unsafe {
ffi::CreateWindowExW(
styles.ex_style,
name.as_ptr(),
title.as_ptr(),
window_style,
x,
y,
outer.0,
outer.1,
ptr::null_mut(),
ptr::null_mut(),
self.instance(),
self.shared_ptr(),
)
};
if hwnd.is_null() {
let error = unsafe { ffi::GetLastError() };
return Err(ShellError::WindowCreation(format!(
"CreateWindowExW failed with Win32 error {error}"
)));
}
if desc.accept_drops {
super::dnd::accept_files(hwnd, true);
}
if let Err(error) = super::input::enable_ime(hwnd, false) {
crcbl_core::log::warn!("{error}; the input method stays on for this window");
}
let real_dpi = Self::dpi_of(hwnd);
let real_frame = Self::frame_for(styles, real_dpi);
if real_dpi != dpi && !desc.mode.is_borderless() {
let client = desc.size.to_physical(geometry::scale_from_dpi(real_dpi));
let outer = real_frame.window_size((
i32::try_from(client.width).unwrap_or(i32::MAX),
i32::try_from(client.height).unwrap_or(i32::MAX),
));
unsafe {
ffi::SetWindowPos(
hwnd,
ptr::null_mut(),
0,
0,
outer.0,
outer.1,
value::SWP_NO_MOVE | value::SWP_NO_Z_ORDER | value::SWP_NO_ACTIVATE,
);
}
}
Ok(Created {
hwnd,
dpi: real_dpi,
size: Self::client_size_of(hwnd),
})
}
pub(super) fn refresh_limits(&self, window: &WinWindow) {
let styles = match (&window.panel, window.popup.map(|popup| popup.input)) {
(Some(panel), _) => geometry::panel_styles(panel.layer, panel.keyboard),
(None, Some(input)) => geometry::popup_styles(input, false),
(None, None) => geometry::styles(window.effective_mode, window.resizable),
};
let frame = Self::frame_for(styles, window.dpi);
self.shared().set_limits(Limits {
hwnd: window.key,
track: geometry::track_sizes(window.requested_constraints, window.scale_factor, frame),
aspect: window.requested_constraints.aspect,
frame,
});
}
pub(super) fn apply_mode(
&mut self,
window: crate::WindowId,
mode: DisplayMode,
) -> Result<(), ShellError> {
let state = self.window(window)?;
let hwnd = state.raw();
let resizable = state.resizable;
let already_saved = state.saved.is_some();
match mode {
DisplayMode::Borderless { monitor } => {
let bounds = self.borderless_bounds(hwnd, monitor)?;
if !already_saved {
let saved = Saved {
style: unsafe { ffi::GetWindowLongPtrW(hwnd, value::GWL_STYLE) } as u32,
ex_style: unsafe { ffi::GetWindowLongPtrW(hwnd, value::GWL_EX_STYLE) }
as u32,
placement: Self::placement_of(hwnd),
};
self.window_mut(window)?.saved = Some(saved);
}
let styles = geometry::styles(mode, resizable);
let visible = unsafe { ffi::GetWindowLongPtrW(hwnd, value::GWL_STYLE) } as u32
& style::VISIBLE;
Self::set_styles(hwnd, styles.style | visible, styles.ex_style);
unsafe {
ffi::SetWindowPos(
hwnd,
ptr::null_mut(),
bounds.x,
bounds.y,
i32::try_from(bounds.width).unwrap_or(i32::MAX),
i32::try_from(bounds.height).unwrap_or(i32::MAX),
value::SWP_NO_Z_ORDER
| value::SWP_NO_OWNER_Z_ORDER
| value::SWP_FRAME_CHANGED
| value::SWP_NO_ACTIVATE,
);
}
}
DisplayMode::Windowed => {
let live_visible = unsafe { ffi::GetWindowLongPtrW(hwnd, value::GWL_STYLE) } as u32
& style::VISIBLE;
if let Some(saved) = self.window_mut(window)?.saved.take() {
Self::set_styles(
hwnd,
(saved.style & !style::VISIBLE) | live_visible,
saved.ex_style,
);
unsafe {
ffi::SetWindowPos(
hwnd,
ptr::null_mut(),
0,
0,
0,
0,
value::SWP_NO_MOVE
| value::SWP_NO_SIZE
| value::SWP_NO_Z_ORDER
| value::SWP_NO_OWNER_Z_ORDER
| value::SWP_FRAME_CHANGED
| value::SWP_NO_ACTIVATE,
);
}
let mut placement = saved.placement;
if live_visible == 0 {
placement.show_cmd = value::SW_HIDE as u32;
}
unsafe { ffi::SetWindowPlacement(hwnd, &raw const placement) };
} else if self.window(window)?.effective_mode.is_borderless() {
let state = self.window(window)?;
let styles = geometry::styles(DisplayMode::Windowed, resizable);
let frame = Self::frame_for(styles, state.dpi);
let client = state.requested_size.to_physical(state.scale_factor);
let outer = frame.window_size((
i32::try_from(client.width).unwrap_or(i32::MAX),
i32::try_from(client.height).unwrap_or(i32::MAX),
));
let (x, y) = self
.monitor_of(hwnd)
.and_then(|id| self.monitors().iter().find(|info| info.id == id))
.map_or((0, 0), |info| geometry::centred(info.work_area, outer));
let visible = unsafe { ffi::GetWindowLongPtrW(hwnd, value::GWL_STYLE) } as u32
& style::VISIBLE;
Self::set_styles(hwnd, styles.style | visible, styles.ex_style);
unsafe {
ffi::SetWindowPos(
hwnd,
ptr::null_mut(),
x,
y,
outer.0,
outer.1,
value::SWP_NO_Z_ORDER
| value::SWP_NO_OWNER_Z_ORDER
| value::SWP_FRAME_CHANGED
| value::SWP_NO_ACTIVATE,
);
}
}
}
}
let effective_monitor = self.monitor_of(hwnd);
let dpi = Self::dpi_of(hwnd);
let state = self.window_mut(window)?;
state.effective_mode = match mode {
DisplayMode::Windowed => DisplayMode::Windowed,
DisplayMode::Borderless { .. } => DisplayMode::Borderless {
monitor: effective_monitor,
},
};
state.dpi = dpi;
state.scale_factor = geometry::scale_from_dpi(dpi);
let state = self.window(window)?;
self.refresh_limits(state);
if state.transparent {
Self::apply_transparent_chrome(hwnd);
}
Ok(())
}
pub(super) fn apply_transparent_chrome(hwnd: Handle) {
let styles = unsafe {
Styles {
style: ffi::GetWindowLongPtrW(hwnd, value::GWL_STYLE) as u32,
ex_style: ffi::GetWindowLongPtrW(hwnd, value::GWL_EX_STYLE) as u32,
}
};
let chrome = geometry::transparent_chrome(styles);
for (attribute, wanted) in [
(dwm::WINDOW_CORNER_PREFERENCE, chrome.corners),
(dwm::BORDER_COLOR, chrome.border),
] {
let result = unsafe {
ffi::DwmSetWindowAttribute(
hwnd,
attribute,
(&raw const wanted).cast(),
size_of::<u32>() as u32,
)
};
if result < 0 {
crcbl_core::log::debug!(
"DwmSetWindowAttribute({attribute}, {wanted:#x}) answered {result:#x}; this \
Windows draws that part of a transparent window's frame its own way"
);
}
}
}
pub(super) fn apply_input_region(
hwnd: Handle,
region: Option<&[crate::PhysicalRect]>,
) -> Result<(), ShellError> {
let Some(region) = region else {
if unsafe { ffi::SetWindowRgn(hwnd, ptr::null_mut(), 1) } == 0 {
return Err(ShellError::Backend(format!(
"SetWindowRgn refused to remove window {hwnd:?}'s input region"
)));
}
return Ok(());
};
let mut outer = Rect::default();
let mut client = Rect::default();
let mut origin = ffi::Point { x: 0, y: 0 };
let read = unsafe {
ffi::GetWindowRect(hwnd, &raw mut outer) != 0
&& ffi::GetClientRect(hwnd, &raw mut client) != 0
&& ffi::ClientToScreen(hwnd, &raw mut origin) != 0
};
if !read {
return Err(ShellError::Backend(format!(
"could not read window {hwnd:?}'s frame to build its input region"
)));
}
let rects = geometry::window_region(
region,
ffi::Point {
x: origin.x - outer.left,
y: origin.y - outer.top,
},
(client.width(), client.height()),
(outer.width(), outer.height()),
);
let combined = unsafe { ffi::CreateRectRgn(0, 0, 0, 0) };
if combined.is_null() {
return Err(ShellError::Backend(
"CreateRectRgn refused: the process is out of GDI objects".to_string(),
));
}
for rect in rects {
let united = unsafe {
let part = ffi::CreateRectRgn(rect.left, rect.top, rect.right, rect.bottom);
let united = !part.is_null()
&& ffi::CombineRgn(combined, combined, part, value::RGN_OR)
!= value::REGION_ERROR;
if !part.is_null() {
ffi::DeleteObject(part);
}
united
};
if !united {
unsafe { ffi::DeleteObject(combined) };
return Err(ShellError::Backend(
"could not build an input region: the process is out of GDI objects"
.to_string(),
));
}
}
if unsafe { ffi::SetWindowRgn(hwnd, combined, 1) } == 0 {
unsafe { ffi::DeleteObject(combined) };
return Err(ShellError::Backend(format!(
"SetWindowRgn refused window {hwnd:?}'s input region"
)));
}
Ok(())
}
fn borderless_bounds(
&self,
hwnd: Handle,
monitor: Option<MonitorId>,
) -> Result<crate::PhysicalRect, ShellError> {
if let Some(monitor) = monitor {
return self
.monitors()
.iter()
.find(|info| info.id == monitor)
.map(|info| info.bounds)
.ok_or(ShellError::NoSuchMonitor(monitor.0));
}
self.monitor_of(hwnd)
.and_then(|id| self.monitors().iter().find(|info| info.id == id))
.or_else(|| self.monitors().iter().find(|info| info.is_primary))
.or_else(|| self.monitors().first())
.map(|info| info.bounds)
.ok_or_else(|| {
ShellError::Backend(
"there is no monitor to place a borderless window on".to_string(),
)
})
}
pub(super) fn monitor_of(&self, hwnd: Handle) -> Option<MonitorId> {
let handle = unsafe { ffi::MonitorFromWindow(hwnd, value::MONITOR_DEFAULT_TO_NEAREST) };
if handle.is_null() {
return None;
}
let device = super::monitors::device_name(handle)?;
self.monitor_id_of(&device)
}
fn placement_of(hwnd: Handle) -> WindowPlacement {
let mut placement = WindowPlacement {
length: size_of::<WindowPlacement>() as u32,
..WindowPlacement::default()
};
unsafe { ffi::GetWindowPlacement(hwnd, &raw mut placement) };
placement
}
fn set_styles(hwnd: Handle, window_style: u32, ex_style: u32) {
let carried = unsafe { ffi::GetWindowLongPtrW(hwnd, value::GWL_EX_STYLE) } as u32
& (style::EX_ACCEPT_FILES | style::EX_NO_REDIRECTION_BITMAP);
unsafe {
ffi::SetWindowLongPtrW(hwnd, value::GWL_STYLE, window_style as isize);
ffi::SetWindowLongPtrW(hwnd, value::GWL_EX_STYLE, (ex_style | carried) as isize);
}
}
pub(super) fn configuration_of(window: &WinWindow) -> WindowConfiguration {
WindowConfiguration {
size: Self::client_size_of(window.raw()),
scale_factor: window.scale_factor,
mode: window.effective_mode,
}
}
}