Present Servo BGRA hardware surfaces

This commit is contained in:
2026-05-13 00:22:00 -04:00
parent bf1ebfb6fe
commit 05a7a0d67f
154 changed files with 84157 additions and 115 deletions
@@ -0,0 +1,28 @@
float color_brightness(float3 color) {
// REC. 601 luminance coefficients for perceived brightness
return dot(color, float3(0.30f, 0.59f, 0.11f));
}
float light_on_dark_contrast(float enhancedContrast, float3 color) {
float brightness = color_brightness(color);
float multiplier = saturate(4.0f * (0.75f - brightness));
return enhancedContrast * multiplier;
}
float enhance_contrast(float alpha, float k) {
return alpha * (k + 1.0f) / (alpha * k + 1.0f);
}
float apply_alpha_correction(float a, float b, float4 g) {
float brightness_adjustment = g.x * b + g.y;
float correction = brightness_adjustment * a + (g.z * b + g.w);
return a + a * (1.0f - a) * correction;
}
float apply_contrast_and_gamma_correction(float sample, float3 color, float enhanced_contrast_factor, float4 gamma_ratios) {
float enhanced_contrast = light_on_dark_contrast(enhanced_contrast_factor, color);
float brightness = color_brightness(color);
float contrasted = enhance_contrast(sample, enhanced_contrast);
return apply_alpha_correction(contrasted, brightness, gamma_ratios);
}
+388
View File
@@ -0,0 +1,388 @@
use std::sync::LazyLock;
use anyhow::Result;
use collections::{FxHashMap, FxHashSet};
use itertools::Itertools;
use windows::Win32::{
Foundation::{HANDLE, HGLOBAL},
System::{
DataExchange::{
CloseClipboard, CountClipboardFormats, EmptyClipboard, EnumClipboardFormats,
GetClipboardData, GetClipboardFormatNameW, IsClipboardFormatAvailable, OpenClipboard,
RegisterClipboardFormatW, SetClipboardData,
},
Memory::{GMEM_MOVEABLE, GlobalAlloc, GlobalLock, GlobalSize, GlobalUnlock},
Ole::{CF_HDROP, CF_UNICODETEXT},
},
UI::Shell::{DragQueryFileW, HDROP},
};
use windows_core::PCWSTR;
use crate::{ClipboardEntry, ClipboardItem, ClipboardString, Image, ImageFormat, hash};
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
// Clipboard formats
static CLIPBOARD_HASH_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("GPUI internal text hash")));
static CLIPBOARD_METADATA_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("GPUI internal metadata")));
static CLIPBOARD_SVG_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("image/svg+xml")));
static CLIPBOARD_GIF_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("GIF")));
static CLIPBOARD_PNG_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("PNG")));
static CLIPBOARD_JPG_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("JFIF")));
// Helper maps and sets
static FORMATS_MAP: LazyLock<FxHashMap<u32, ClipboardFormatType>> = LazyLock::new(|| {
let mut formats_map = FxHashMap::default();
formats_map.insert(CF_UNICODETEXT.0 as u32, ClipboardFormatType::Text);
formats_map.insert(*CLIPBOARD_PNG_FORMAT, ClipboardFormatType::Image);
formats_map.insert(*CLIPBOARD_GIF_FORMAT, ClipboardFormatType::Image);
formats_map.insert(*CLIPBOARD_JPG_FORMAT, ClipboardFormatType::Image);
formats_map.insert(*CLIPBOARD_SVG_FORMAT, ClipboardFormatType::Image);
formats_map.insert(CF_HDROP.0 as u32, ClipboardFormatType::Files);
formats_map
});
static FORMATS_SET: LazyLock<FxHashSet<u32>> = LazyLock::new(|| {
let mut formats_map = FxHashSet::default();
formats_map.insert(CF_UNICODETEXT.0 as u32);
formats_map.insert(*CLIPBOARD_PNG_FORMAT);
formats_map.insert(*CLIPBOARD_GIF_FORMAT);
formats_map.insert(*CLIPBOARD_JPG_FORMAT);
formats_map.insert(*CLIPBOARD_SVG_FORMAT);
formats_map.insert(CF_HDROP.0 as u32);
formats_map
});
static IMAGE_FORMATS_MAP: LazyLock<FxHashMap<u32, ImageFormat>> = LazyLock::new(|| {
let mut formats_map = FxHashMap::default();
formats_map.insert(*CLIPBOARD_PNG_FORMAT, ImageFormat::Png);
formats_map.insert(*CLIPBOARD_GIF_FORMAT, ImageFormat::Gif);
formats_map.insert(*CLIPBOARD_JPG_FORMAT, ImageFormat::Jpeg);
formats_map.insert(*CLIPBOARD_SVG_FORMAT, ImageFormat::Svg);
formats_map
});
#[derive(Debug, Clone, Copy)]
enum ClipboardFormatType {
Text,
Image,
Files,
}
pub(crate) fn write_to_clipboard(item: ClipboardItem) {
with_clipboard(|| write_to_clipboard_inner(item));
}
pub(crate) fn read_from_clipboard() -> Option<ClipboardItem> {
with_clipboard(|| {
with_best_match_format(|item_format| match format_to_type(item_format) {
ClipboardFormatType::Text => read_string_from_clipboard(),
ClipboardFormatType::Image => read_image_from_clipboard(item_format),
ClipboardFormatType::Files => read_files_from_clipboard(),
})
})
.flatten()
}
pub(crate) fn with_file_names<F>(hdrop: HDROP, mut f: F)
where
F: FnMut(String),
{
let file_count = unsafe { DragQueryFileW(hdrop, DRAGDROP_GET_FILES_COUNT, None) };
for file_index in 0..file_count {
let filename_length = unsafe { DragQueryFileW(hdrop, file_index, None) } as usize;
let mut buffer = vec![0u16; filename_length + 1];
let ret = unsafe { DragQueryFileW(hdrop, file_index, Some(buffer.as_mut_slice())) };
if ret == 0 {
log::error!("unable to read file name of dragged file");
continue;
}
match String::from_utf16(&buffer[0..filename_length]) {
Ok(file_name) => f(file_name),
Err(e) => {
log::error!("dragged file name is not UTF-16: {}", e)
}
}
}
}
fn with_clipboard<F, T>(f: F) -> Option<T>
where
F: FnOnce() -> T,
{
match unsafe { OpenClipboard(None) } {
Ok(()) => {
let result = f();
if let Err(e) = unsafe { CloseClipboard() } {
log::error!("Failed to close clipboard: {e}",);
}
Some(result)
}
Err(e) => {
log::error!("Failed to open clipboard: {e}",);
None
}
}
}
fn register_clipboard_format(format: PCWSTR) -> u32 {
let ret = unsafe { RegisterClipboardFormatW(format) };
if ret == 0 {
panic!(
"Error when registering clipboard format: {}",
std::io::Error::last_os_error()
);
}
ret
}
#[inline]
fn format_to_type(item_format: u32) -> &'static ClipboardFormatType {
FORMATS_MAP.get(&item_format).unwrap()
}
// Currently, we only write the first item.
fn write_to_clipboard_inner(item: ClipboardItem) -> Result<()> {
unsafe {
EmptyClipboard()?;
}
match item.entries().first() {
Some(entry) => match entry {
ClipboardEntry::String(string) => {
write_string_to_clipboard(string)?;
}
ClipboardEntry::Image(image) => {
write_image_to_clipboard(image)?;
}
},
None => {
// Writing an empty list of entries just clears the clipboard.
}
}
Ok(())
}
fn write_string_to_clipboard(item: &ClipboardString) -> Result<()> {
let encode_wide = item.text.encode_utf16().chain(Some(0)).collect_vec();
set_data_to_clipboard(&encode_wide, CF_UNICODETEXT.0 as u32)?;
if let Some(metadata) = item.metadata.as_ref() {
let hash_result = {
let hash = ClipboardString::text_hash(&item.text);
hash.to_ne_bytes()
};
let encode_wide =
unsafe { std::slice::from_raw_parts(hash_result.as_ptr().cast::<u16>(), 4) };
set_data_to_clipboard(encode_wide, *CLIPBOARD_HASH_FORMAT)?;
let metadata_wide = metadata.encode_utf16().chain(Some(0)).collect_vec();
set_data_to_clipboard(&metadata_wide, *CLIPBOARD_METADATA_FORMAT)?;
}
Ok(())
}
fn set_data_to_clipboard<T>(data: &[T], format: u32) -> Result<()> {
unsafe {
let global = GlobalAlloc(GMEM_MOVEABLE, std::mem::size_of_val(data))?;
let handle = GlobalLock(global);
std::ptr::copy_nonoverlapping(data.as_ptr(), handle as _, data.len());
let _ = GlobalUnlock(global);
SetClipboardData(format, Some(HANDLE(global.0)))?;
}
Ok(())
}
// Here writing PNG to the clipboard to better support other apps. For more info, please ref to
// the PR.
fn write_image_to_clipboard(item: &Image) -> Result<()> {
match item.format {
ImageFormat::Svg => set_data_to_clipboard(item.bytes(), *CLIPBOARD_SVG_FORMAT)?,
ImageFormat::Gif => {
set_data_to_clipboard(item.bytes(), *CLIPBOARD_GIF_FORMAT)?;
let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Gif)?;
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
}
ImageFormat::Png => {
set_data_to_clipboard(item.bytes(), *CLIPBOARD_PNG_FORMAT)?;
let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Png)?;
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
}
ImageFormat::Jpeg => {
set_data_to_clipboard(item.bytes(), *CLIPBOARD_JPG_FORMAT)?;
let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Jpeg)?;
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
}
other => {
log::warn!(
"Clipboard unsupported image format: {:?}, convert to PNG instead.",
item.format
);
let png_bytes = convert_image_to_png_format(item.bytes(), other)?;
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
}
}
Ok(())
}
fn convert_image_to_png_format(bytes: &[u8], image_format: ImageFormat) -> Result<Vec<u8>> {
let image = image::load_from_memory_with_format(bytes, image_format.into())?;
let mut output_buf = Vec::new();
image.write_to(
&mut std::io::Cursor::new(&mut output_buf),
image::ImageFormat::Png,
)?;
Ok(output_buf)
}
// Here, we enumerate all formats on the clipboard and find the first one that we can process.
// The reason we don't use `GetPriorityClipboardFormat` is that it sometimes returns the
// wrong format.
// For instance, when copying a JPEG image from Microsoft Word, there may be several formats
// on the clipboard: Jpeg, Png, Svg.
// If we use `GetPriorityClipboardFormat`, it will return Svg, which is not what we want.
fn with_best_match_format<F>(f: F) -> Option<ClipboardItem>
where
F: Fn(u32) -> Option<ClipboardEntry>,
{
let count = unsafe { CountClipboardFormats() };
let mut clipboard_format = 0;
for _ in 0..count {
clipboard_format = unsafe { EnumClipboardFormats(clipboard_format) };
let Some(item_format) = FORMATS_SET.get(&clipboard_format) else {
continue;
};
if let Some(entry) = f(*item_format) {
return Some(ClipboardItem {
entries: vec![entry],
});
}
}
// log the formats that we don't support yet.
{
clipboard_format = 0;
for _ in 0..count {
clipboard_format = unsafe { EnumClipboardFormats(clipboard_format) };
let mut buffer = [0u16; 64];
unsafe { GetClipboardFormatNameW(clipboard_format, &mut buffer) };
let format_name = String::from_utf16_lossy(&buffer);
log::warn!(
"Try to paste with unsupported clipboard format: {}, {}.",
clipboard_format,
format_name
);
}
}
None
}
fn read_string_from_clipboard() -> Option<ClipboardEntry> {
let text = with_clipboard_data(CF_UNICODETEXT.0 as u32, |data_ptr, _| {
let pcwstr = PCWSTR(data_ptr as *const u16);
String::from_utf16_lossy(unsafe { pcwstr.as_wide() })
})?;
let Some(hash) = read_hash_from_clipboard() else {
return Some(ClipboardEntry::String(ClipboardString::new(text)));
};
let Some(metadata) = read_metadata_from_clipboard() else {
return Some(ClipboardEntry::String(ClipboardString::new(text)));
};
if hash == ClipboardString::text_hash(&text) {
Some(ClipboardEntry::String(ClipboardString {
text,
metadata: Some(metadata),
}))
} else {
Some(ClipboardEntry::String(ClipboardString::new(text)))
}
}
fn read_hash_from_clipboard() -> Option<u64> {
if unsafe { IsClipboardFormatAvailable(*CLIPBOARD_HASH_FORMAT).is_err() } {
return None;
}
with_clipboard_data(*CLIPBOARD_HASH_FORMAT, |data_ptr, size| {
if size < 8 {
return None;
}
let hash_bytes: [u8; 8] = unsafe {
std::slice::from_raw_parts(data_ptr.cast::<u8>(), 8)
.try_into()
.ok()
}?;
Some(u64::from_ne_bytes(hash_bytes))
})?
}
fn read_metadata_from_clipboard() -> Option<String> {
unsafe { IsClipboardFormatAvailable(*CLIPBOARD_METADATA_FORMAT).ok()? };
with_clipboard_data(*CLIPBOARD_METADATA_FORMAT, |data_ptr, _size| {
let pcwstr = PCWSTR(data_ptr as *const u16);
String::from_utf16_lossy(unsafe { pcwstr.as_wide() })
})
}
fn read_image_from_clipboard(format: u32) -> Option<ClipboardEntry> {
let image_format = format_number_to_image_format(format)?;
read_image_for_type(format, *image_format)
}
#[inline]
fn format_number_to_image_format(format_number: u32) -> Option<&'static ImageFormat> {
IMAGE_FORMATS_MAP.get(&format_number)
}
fn read_image_for_type(format_number: u32, format: ImageFormat) -> Option<ClipboardEntry> {
let (bytes, id) = with_clipboard_data(format_number, |data_ptr, size| {
let bytes = unsafe { std::slice::from_raw_parts(data_ptr as *mut u8 as _, size).to_vec() };
let id = hash(&bytes);
(bytes, id)
})?;
Some(ClipboardEntry::Image(Image { format, bytes, id }))
}
fn read_files_from_clipboard() -> Option<ClipboardEntry> {
let text = with_clipboard_data(CF_HDROP.0 as u32, |data_ptr, _size| {
let hdrop = HDROP(data_ptr);
let mut filenames = String::new();
with_file_names(hdrop, |file_name| {
filenames.push_str(&file_name);
});
filenames
})?;
Some(ClipboardEntry::String(ClipboardString {
text,
metadata: None,
}))
}
fn with_clipboard_data<F, R>(format: u32, f: F) -> Option<R>
where
F: FnOnce(*mut std::ffi::c_void, usize) -> R,
{
let global = HGLOBAL(unsafe { GetClipboardData(format).ok() }?.0);
let size = unsafe { GlobalSize(global) };
let data_ptr = unsafe { GlobalLock(global) };
let result = f(data_ptr, size);
unsafe { GlobalUnlock(global).ok() };
Some(result)
}
impl From<ImageFormat> for image::ImageFormat {
fn from(value: ImageFormat) -> Self {
match value {
ImageFormat::Png => image::ImageFormat::Png,
ImageFormat::Jpeg => image::ImageFormat::Jpeg,
ImageFormat::Webp => image::ImageFormat::WebP,
ImageFormat::Gif => image::ImageFormat::Gif,
// TODO: ImageFormat::Svg
ImageFormat::Bmp => image::ImageFormat::Bmp,
ImageFormat::Tiff => image::ImageFormat::Tiff,
_ => unreachable!(),
}
}
}
@@ -0,0 +1,44 @@
#include "alpha_correction.hlsl"
struct RasterVertexOutput {
float4 position : SV_Position;
float2 texcoord : TEXCOORD0;
};
RasterVertexOutput emoji_rasterization_vertex(uint vertexID : SV_VERTEXID)
{
RasterVertexOutput output;
output.texcoord = float2((vertexID << 1) & 2, vertexID & 2);
output.position = float4(output.texcoord * 2.0f - 1.0f, 0.0f, 1.0f);
output.position.y = -output.position.y;
return output;
}
struct PixelInput {
float4 position: SV_Position;
float2 texcoord : TEXCOORD0;
};
struct Bounds {
int2 origin;
int2 size;
};
Texture2D<float> t_layer : register(t0);
SamplerState s_layer : register(s0);
cbuffer GlyphLayerTextureParams : register(b0) {
Bounds bounds;
float4 run_color;
float4 gamma_ratios;
float grayscale_enhanced_contrast;
float3 _pad;
};
float4 emoji_rasterization_fragment(PixelInput input): SV_Target {
float sample = t_layer.Sample(s_layer, input.texcoord.xy).r;
float alpha_corrected = apply_contrast_and_gamma_correction(sample, run_color.rgb, grayscale_enhanced_contrast, gamma_ratios);
float alpha = alpha_corrected * run_color.a;
return float4(run_color.rgb * alpha, alpha);
}
@@ -0,0 +1,201 @@
use std::path::PathBuf;
use itertools::Itertools;
use smallvec::SmallVec;
use windows::{
Win32::{
Foundation::PROPERTYKEY,
Globalization::u_strlen,
System::Com::{CLSCTX_INPROC_SERVER, CoCreateInstance, StructuredStorage::PROPVARIANT},
UI::{
Controls::INFOTIPSIZE,
Shell::{
Common::{IObjectArray, IObjectCollection},
DestinationList, EnumerableObjectCollection, ICustomDestinationList, IShellLinkW,
PropertiesSystem::IPropertyStore,
ShellLink,
},
},
},
core::{GUID, HSTRING, Interface},
};
use crate::{Action, MenuItem};
pub(crate) struct JumpList {
pub(crate) dock_menus: Vec<DockMenuItem>,
pub(crate) recent_workspaces: Vec<SmallVec<[PathBuf; 2]>>,
}
impl JumpList {
pub(crate) fn new() -> Self {
Self {
dock_menus: Vec::new(),
recent_workspaces: Vec::new(),
}
}
}
pub(crate) struct DockMenuItem {
pub(crate) name: String,
pub(crate) description: String,
pub(crate) action: Box<dyn Action>,
}
impl DockMenuItem {
pub(crate) fn new(item: MenuItem) -> anyhow::Result<Self> {
match item {
MenuItem::Action { name, action, .. } => Ok(Self {
name: name.clone().into(),
description: if name == "New Window" {
"Opens a new window".to_string()
} else {
name.into()
},
action,
}),
_ => anyhow::bail!("Only `MenuItem::Action` is supported for dock menu on Windows."),
}
}
}
// This code is based on the example from Microsoft:
// https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/winui/shell/appshellintegration/RecipePropertyHandler/RecipePropertyHandler.cpp
pub(crate) fn update_jump_list(
jump_list: &JumpList,
) -> anyhow::Result<Vec<SmallVec<[PathBuf; 2]>>> {
let (list, removed) = create_destination_list()?;
add_recent_folders(&list, &jump_list.recent_workspaces, removed.as_ref())?;
add_dock_menu(&list, &jump_list.dock_menus)?;
unsafe { list.CommitList() }?;
Ok(removed)
}
// Copied from:
// https://github.com/microsoft/windows-rs/blob/0fc3c2e5a13d4316d242bdeb0a52af611eba8bd4/crates/libs/windows/src/Windows/Win32/Storage/EnhancedStorage/mod.rs#L1881
const PKEY_TITLE: PROPERTYKEY = PROPERTYKEY {
fmtid: GUID::from_u128(0xf29f85e0_4ff9_1068_ab91_08002b27b3d9),
pid: 2,
};
fn create_destination_list() -> anyhow::Result<(ICustomDestinationList, Vec<SmallVec<[PathBuf; 2]>>)>
{
let list: ICustomDestinationList =
unsafe { CoCreateInstance(&DestinationList, None, CLSCTX_INPROC_SERVER) }?;
let mut slots = 0;
let user_removed: IObjectArray = unsafe { list.BeginList(&mut slots) }?;
let count = unsafe { user_removed.GetCount() }?;
if count == 0 {
return Ok((list, Vec::new()));
}
let mut removed = Vec::with_capacity(count as usize);
for i in 0..count {
let shell_link: IShellLinkW = unsafe { user_removed.GetAt(i)? };
let description = {
// INFOTIPSIZE is the maximum size of the buffer
// see https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishelllinkw-getdescription
let mut buffer = [0u16; INFOTIPSIZE as usize];
unsafe { shell_link.GetDescription(&mut buffer)? };
let len = unsafe { u_strlen(buffer.as_ptr()) };
String::from_utf16_lossy(&buffer[..len as usize])
};
let args = description.split('\n').map(PathBuf::from).collect();
removed.push(args);
}
Ok((list, removed))
}
fn add_dock_menu(list: &ICustomDestinationList, dock_menus: &[DockMenuItem]) -> anyhow::Result<()> {
unsafe {
let tasks: IObjectCollection =
CoCreateInstance(&EnumerableObjectCollection, None, CLSCTX_INPROC_SERVER)?;
for (idx, dock_menu) in dock_menus.iter().enumerate() {
let argument = HSTRING::from(format!("--dock-action {}", idx));
let description = HSTRING::from(dock_menu.description.as_str());
let display = dock_menu.name.as_str();
let task = create_shell_link(argument, description, None, display)?;
tasks.AddObject(&task)?;
}
list.AddUserTasks(&tasks)?;
Ok(())
}
}
fn add_recent_folders(
list: &ICustomDestinationList,
entries: &[SmallVec<[PathBuf; 2]>],
removed: &Vec<SmallVec<[PathBuf; 2]>>,
) -> anyhow::Result<()> {
unsafe {
let tasks: IObjectCollection =
CoCreateInstance(&EnumerableObjectCollection, None, CLSCTX_INPROC_SERVER)?;
for folder_path in entries.iter().filter(|path| !removed.contains(path)) {
let argument = HSTRING::from(
folder_path
.iter()
.map(|path| format!("\"{}\"", path.display()))
.join(" "),
);
let description = HSTRING::from(
folder_path
.iter()
.map(|path| path.to_string_lossy())
.collect::<Vec<_>>()
.join("\n"),
);
// simulate folder icon
// https://github.com/microsoft/vscode/blob/7a5dc239516a8953105da34f84bae152421a8886/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts#L380
let icon = HSTRING::from("explorer.exe");
let display = folder_path
.iter()
.map(|p| {
p.file_name()
.map(|name| name.to_string_lossy())
.unwrap_or_else(|| p.to_string_lossy())
})
.join(", ");
tasks.AddObject(&create_shell_link(
argument,
description,
Some(icon),
&display,
)?)?;
}
list.AppendCategory(&HSTRING::from("Recent Folders"), &tasks)?;
Ok(())
}
}
fn create_shell_link(
argument: HSTRING,
description: HSTRING,
icon: Option<HSTRING>,
display: &str,
) -> anyhow::Result<IShellLinkW> {
unsafe {
let link: IShellLinkW = CoCreateInstance(&ShellLink, None, CLSCTX_INPROC_SERVER)?;
let exe_path = HSTRING::from(std::env::current_exe()?.as_os_str());
link.SetPath(&exe_path)?;
link.SetArguments(&argument)?;
link.SetDescription(&description)?;
if let Some(icon) = icon {
link.SetIconLocation(&icon, 0)?;
}
let store: IPropertyStore = link.cast()?;
let title = PROPVARIANT::from(display);
store.SetValue(&PKEY_TITLE, &title)?;
store.Commit()?;
Ok(link)
}
}
File diff suppressed because it is too large Load Diff
+308
View File
@@ -0,0 +1,308 @@
use collections::FxHashMap;
use etagere::BucketedAtlasAllocator;
use parking_lot::Mutex;
use windows::Win32::Graphics::{
Direct3D11::{
D3D11_BIND_SHADER_RESOURCE, D3D11_BOX, D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT,
ID3D11Device, ID3D11DeviceContext, ID3D11ShaderResourceView, ID3D11Texture2D,
},
Dxgi::Common::*,
};
use crate::{
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
Point, Size, platform::AtlasTextureList,
};
pub(crate) struct DirectXAtlas(Mutex<DirectXAtlasState>);
struct DirectXAtlasState {
device: ID3D11Device,
device_context: ID3D11DeviceContext,
monochrome_textures: AtlasTextureList<DirectXAtlasTexture>,
polychrome_textures: AtlasTextureList<DirectXAtlasTexture>,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
}
struct DirectXAtlasTexture {
id: AtlasTextureId,
bytes_per_pixel: u32,
allocator: BucketedAtlasAllocator,
texture: ID3D11Texture2D,
view: [Option<ID3D11ShaderResourceView>; 1],
live_atlas_keys: u32,
}
impl DirectXAtlas {
pub(crate) fn new(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Self {
DirectXAtlas(Mutex::new(DirectXAtlasState {
device: device.clone(),
device_context: device_context.clone(),
monochrome_textures: Default::default(),
polychrome_textures: Default::default(),
tiles_by_key: Default::default(),
}))
}
pub(crate) fn get_texture_view(
&self,
id: AtlasTextureId,
) -> [Option<ID3D11ShaderResourceView>; 1] {
let lock = self.0.lock();
let tex = lock.texture(id);
tex.view.clone()
}
pub(crate) fn handle_device_lost(
&self,
device: &ID3D11Device,
device_context: &ID3D11DeviceContext,
) {
let mut lock = self.0.lock();
lock.device = device.clone();
lock.device_context = device_context.clone();
lock.monochrome_textures = AtlasTextureList::default();
lock.polychrome_textures = AtlasTextureList::default();
lock.tiles_by_key.clear();
}
}
impl PlatformAtlas for DirectXAtlas {
fn get_or_insert_with<'a>(
&self,
key: &AtlasKey,
build: &mut dyn FnMut() -> anyhow::Result<
Option<(Size<DevicePixels>, std::borrow::Cow<'a, [u8]>)>,
>,
) -> anyhow::Result<Option<AtlasTile>> {
let mut lock = self.0.lock();
if let Some(tile) = lock.tiles_by_key.get(key) {
Ok(Some(tile.clone()))
} else {
let Some((size, bytes)) = build()? else {
return Ok(None);
};
let tile = lock
.allocate(size, key.texture_kind())
.ok_or_else(|| anyhow::anyhow!("failed to allocate"))?;
let texture = lock.texture(tile.texture_id);
texture.upload(&lock.device_context, tile.bounds, &bytes);
lock.tiles_by_key.insert(key.clone(), tile.clone());
Ok(Some(tile))
}
}
fn remove(&self, key: &AtlasKey) {
let mut lock = self.0.lock();
let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
return;
};
let textures = match id.kind {
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
};
let Some(texture_slot) = textures.textures.get_mut(id.index as usize) else {
return;
};
if let Some(mut texture) = texture_slot.take() {
texture.decrement_ref_count();
if texture.is_unreferenced() {
textures.free_list.push(texture.id.index as usize);
lock.tiles_by_key.remove(key);
} else {
*texture_slot = Some(texture);
}
}
}
}
impl DirectXAtlasState {
fn allocate(
&mut self,
size: Size<DevicePixels>,
texture_kind: AtlasTextureKind,
) -> Option<AtlasTile> {
{
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
};
if let Some(tile) = textures
.iter_mut()
.rev()
.find_map(|texture| texture.allocate(size))
{
return Some(tile);
}
}
let texture = self.push_texture(size, texture_kind)?;
texture.allocate(size)
}
fn push_texture(
&mut self,
min_size: Size<DevicePixels>,
kind: AtlasTextureKind,
) -> Option<&mut DirectXAtlasTexture> {
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
width: DevicePixels(1024),
height: DevicePixels(1024),
};
// Max texture size for DirectX. See:
// https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits
const MAX_ATLAS_SIZE: Size<DevicePixels> = Size {
width: DevicePixels(16384),
height: DevicePixels(16384),
};
let size = min_size.min(&MAX_ATLAS_SIZE).max(&DEFAULT_ATLAS_SIZE);
let pixel_format;
let bind_flag;
let bytes_per_pixel;
match kind {
AtlasTextureKind::Monochrome => {
pixel_format = DXGI_FORMAT_R8_UNORM;
bind_flag = D3D11_BIND_SHADER_RESOURCE;
bytes_per_pixel = 1;
}
AtlasTextureKind::Polychrome => {
pixel_format = DXGI_FORMAT_B8G8R8A8_UNORM;
bind_flag = D3D11_BIND_SHADER_RESOURCE;
bytes_per_pixel = 4;
}
}
let texture_desc = D3D11_TEXTURE2D_DESC {
Width: size.width.0 as u32,
Height: size.height.0 as u32,
MipLevels: 1,
ArraySize: 1,
Format: pixel_format,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_DEFAULT,
BindFlags: bind_flag.0 as u32,
CPUAccessFlags: 0,
MiscFlags: 0,
};
let mut texture: Option<ID3D11Texture2D> = None;
unsafe {
// This only returns None if the device is lost, which we will recreate later.
// So it's ok to return None here.
self.device
.CreateTexture2D(&texture_desc, None, Some(&mut texture))
.ok()?;
}
let texture = texture.unwrap();
let texture_list = match kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
};
let index = texture_list.free_list.pop();
let view = unsafe {
let mut view = None;
self.device
.CreateShaderResourceView(&texture, None, Some(&mut view))
.ok()?;
[view]
};
let atlas_texture = DirectXAtlasTexture {
id: AtlasTextureId {
index: index.unwrap_or(texture_list.textures.len()) as u32,
kind,
},
bytes_per_pixel,
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
texture,
view,
live_atlas_keys: 0,
};
if let Some(ix) = index {
texture_list.textures[ix] = Some(atlas_texture);
texture_list.textures.get_mut(ix).unwrap().as_mut()
} else {
texture_list.textures.push(Some(atlas_texture));
texture_list.textures.last_mut().unwrap().as_mut()
}
}
fn texture(&self, id: AtlasTextureId) -> &DirectXAtlasTexture {
let textures = match id.kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
};
textures[id.index as usize].as_ref().unwrap()
}
}
impl DirectXAtlasTexture {
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
let allocation = self.allocator.allocate(size.into())?;
let tile = AtlasTile {
texture_id: self.id,
tile_id: allocation.id.into(),
bounds: Bounds {
origin: allocation.rectangle.min.into(),
size,
},
padding: 0,
};
self.live_atlas_keys += 1;
Some(tile)
}
fn upload(
&self,
device_context: &ID3D11DeviceContext,
bounds: Bounds<DevicePixels>,
bytes: &[u8],
) {
unsafe {
device_context.UpdateSubresource(
&self.texture,
0,
Some(&D3D11_BOX {
left: bounds.left().0 as u32,
top: bounds.top().0 as u32,
front: 0,
right: bounds.right().0 as u32,
bottom: bounds.bottom().0 as u32,
back: 1,
}),
bytes.as_ptr() as _,
bounds.size.width.to_bytes(self.bytes_per_pixel as u8),
0,
);
}
}
fn decrement_ref_count(&mut self) {
self.live_atlas_keys -= 1;
}
fn is_unreferenced(&mut self) -> bool {
self.live_atlas_keys == 0
}
}
impl From<Size<DevicePixels>> for etagere::Size {
fn from(size: Size<DevicePixels>) -> Self {
etagere::Size::new(size.width.into(), size.height.into())
}
}
impl From<etagere::Point> for Point<DevicePixels> {
fn from(value: etagere::Point) -> Self {
Point {
x: DevicePixels::from(value.x),
y: DevicePixels::from(value.y),
}
}
}
+197
View File
@@ -0,0 +1,197 @@
use anyhow::{Context, Result};
use util::ResultExt;
use windows::Win32::{
Foundation::HMODULE,
Graphics::{
Direct3D::{
D3D_DRIVER_TYPE_UNKNOWN, D3D_FEATURE_LEVEL, D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1,
},
Direct3D11::{
D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_CREATE_DEVICE_DEBUG,
D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS,
D3D11_SDK_VERSION, D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext,
},
Dxgi::{
CreateDXGIFactory2, DXGI_CREATE_FACTORY_DEBUG, DXGI_CREATE_FACTORY_FLAGS,
IDXGIAdapter1, IDXGIFactory6,
},
},
};
use windows::core::Interface;
pub(crate) fn try_to_recover_from_device_lost<T>(
mut f: impl FnMut() -> Result<T>,
on_success: impl FnOnce(T),
on_error: impl FnOnce(),
) {
let result = (0..5).find_map(|i| {
if i > 0 {
// Add a small delay before retrying
std::thread::sleep(std::time::Duration::from_millis(100));
}
f().log_err()
});
if let Some(result) = result {
on_success(result);
} else {
on_error();
}
}
#[derive(Clone)]
pub(crate) struct DirectXDevices {
pub(crate) adapter: IDXGIAdapter1,
pub(crate) dxgi_factory: IDXGIFactory6,
pub(crate) device: ID3D11Device,
pub(crate) device_context: ID3D11DeviceContext,
}
impl DirectXDevices {
pub(crate) fn new() -> Result<Self> {
let debug_layer_available = check_debug_layer_available();
let dxgi_factory =
get_dxgi_factory(debug_layer_available).context("Creating DXGI factory")?;
let adapter =
get_adapter(&dxgi_factory, debug_layer_available).context("Getting DXGI adapter")?;
let (device, device_context) = {
let mut context: Option<ID3D11DeviceContext> = None;
let mut feature_level = D3D_FEATURE_LEVEL::default();
let device = get_device(
&adapter,
Some(&mut context),
Some(&mut feature_level),
debug_layer_available,
)
.context("Creating Direct3D device")?;
match feature_level {
D3D_FEATURE_LEVEL_11_1 => {
log::info!("Created device with Direct3D 11.1 feature level.")
}
D3D_FEATURE_LEVEL_11_0 => {
log::info!("Created device with Direct3D 11.0 feature level.")
}
D3D_FEATURE_LEVEL_10_1 => {
log::info!("Created device with Direct3D 10.1 feature level.")
}
_ => unreachable!(),
}
(device, context.unwrap())
};
Ok(Self {
adapter,
dxgi_factory,
device,
device_context,
})
}
}
#[inline]
fn check_debug_layer_available() -> bool {
#[cfg(debug_assertions)]
{
use windows::Win32::Graphics::Dxgi::{DXGIGetDebugInterface1, IDXGIInfoQueue};
unsafe { DXGIGetDebugInterface1::<IDXGIInfoQueue>(0) }
.log_err()
.is_some()
}
#[cfg(not(debug_assertions))]
{
false
}
}
#[inline]
fn get_dxgi_factory(debug_layer_available: bool) -> Result<IDXGIFactory6> {
let factory_flag = if debug_layer_available {
DXGI_CREATE_FACTORY_DEBUG
} else {
#[cfg(debug_assertions)]
log::warn!(
"Failed to get DXGI debug interface. DirectX debugging features will be disabled."
);
DXGI_CREATE_FACTORY_FLAGS::default()
};
unsafe { Ok(CreateDXGIFactory2(factory_flag)?) }
}
#[inline]
fn get_adapter(dxgi_factory: &IDXGIFactory6, debug_layer_available: bool) -> Result<IDXGIAdapter1> {
for adapter_index in 0.. {
let adapter: IDXGIAdapter1 = unsafe { dxgi_factory.EnumAdapters(adapter_index)?.cast()? };
if let Ok(desc) = unsafe { adapter.GetDesc1() } {
let gpu_name = String::from_utf16_lossy(&desc.Description)
.trim_matches(char::from(0))
.to_string();
log::info!("Using GPU: {}", gpu_name);
}
// Check to see whether the adapter supports Direct3D 11, but don't
// create the actual device yet.
if get_device(&adapter, None, None, debug_layer_available)
.log_err()
.is_some()
{
return Ok(adapter);
}
}
unreachable!()
}
#[inline]
fn get_device(
adapter: &IDXGIAdapter1,
context: Option<*mut Option<ID3D11DeviceContext>>,
feature_level: Option<*mut D3D_FEATURE_LEVEL>,
debug_layer_available: bool,
) -> Result<ID3D11Device> {
let mut device: Option<ID3D11Device> = None;
let device_flags = if debug_layer_available {
D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG
} else {
D3D11_CREATE_DEVICE_BGRA_SUPPORT
};
unsafe {
D3D11CreateDevice(
adapter,
D3D_DRIVER_TYPE_UNKNOWN,
HMODULE::default(),
device_flags,
// 4x MSAA is required for Direct3D Feature Level 10.1 or better
Some(&[
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
]),
D3D11_SDK_VERSION,
Some(&mut device),
feature_level,
context,
)?;
}
let device = device.unwrap();
let mut data = D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS::default();
unsafe {
device
.CheckFeatureSupport(
D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS,
&mut data as *mut _ as _,
std::mem::size_of::<D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS>() as u32,
)
.context("Checking GPU device feature support")?;
}
if data
.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x
.as_bool()
{
Ok(device)
} else {
Err(anyhow::anyhow!(
"Required feature StructuredBuffer is not supported by GPU/driver"
))
}
}
File diff suppressed because it is too large Load Diff
+110
View File
@@ -0,0 +1,110 @@
use std::{
thread::{ThreadId, current},
time::Duration,
};
use async_task::Runnable;
use flume::Sender;
use util::ResultExt;
use windows::{
System::Threading::{
ThreadPool, ThreadPoolTimer, TimerElapsedHandler, WorkItemHandler, WorkItemPriority,
},
Win32::{
Foundation::{LPARAM, WPARAM},
UI::WindowsAndMessaging::PostMessageW,
},
};
use crate::{
HWND, PlatformDispatcher, SafeHwnd, TaskLabel, WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD,
};
pub(crate) struct WindowsDispatcher {
main_sender: Sender<Runnable>,
main_thread_id: ThreadId,
platform_window_handle: SafeHwnd,
validation_number: usize,
}
impl WindowsDispatcher {
pub(crate) fn new(
main_sender: Sender<Runnable>,
platform_window_handle: HWND,
validation_number: usize,
) -> Self {
let main_thread_id = current().id();
let platform_window_handle = platform_window_handle.into();
WindowsDispatcher {
main_sender,
main_thread_id,
platform_window_handle,
validation_number,
}
}
fn dispatch_on_threadpool(&self, runnable: Runnable) {
let handler = {
let mut task_wrapper = Some(runnable);
WorkItemHandler::new(move |_| {
task_wrapper.take().unwrap().run();
Ok(())
})
};
ThreadPool::RunWithPriorityAsync(&handler, WorkItemPriority::High).log_err();
}
fn dispatch_on_threadpool_after(&self, runnable: Runnable, duration: Duration) {
let handler = {
let mut task_wrapper = Some(runnable);
TimerElapsedHandler::new(move |_| {
task_wrapper.take().unwrap().run();
Ok(())
})
};
ThreadPoolTimer::CreateTimer(&handler, duration.into()).log_err();
}
}
impl PlatformDispatcher for WindowsDispatcher {
fn is_main_thread(&self) -> bool {
current().id() == self.main_thread_id
}
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>) {
self.dispatch_on_threadpool(runnable);
if let Some(label) = label {
log::debug!("TaskLabel: {label:?}");
}
}
fn dispatch_on_main_thread(&self, runnable: Runnable) {
match self.main_sender.send(runnable) {
Ok(_) => unsafe {
PostMessageW(
Some(self.platform_window_handle.as_raw()),
WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD,
WPARAM(self.validation_number),
LPARAM(0),
)
.log_err();
},
Err(runnable) => {
// NOTE: Runnable may wrap a Future that is !Send.
//
// This is usually safe because we only poll it on the main thread.
// However if the send fails, we know that:
// 1. main_receiver has been dropped (which implies the app is shutting down)
// 2. we are on a background thread.
// It is not safe to drop something !Send on the wrong thread, and
// the app will exit soon anyway, so we must forget the runnable.
std::mem::forget(runnable);
}
}
}
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
self.dispatch_on_threadpool_after(runnable, duration);
}
}
+255
View File
@@ -0,0 +1,255 @@
use itertools::Itertools;
use smallvec::SmallVec;
use std::rc::Rc;
use util::ResultExt;
use uuid::Uuid;
use windows::{
Win32::{
Foundation::*,
Graphics::Gdi::*,
UI::{
HiDpi::{GetDpiForMonitor, MDT_EFFECTIVE_DPI},
WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI,
},
},
core::*,
};
use crate::{Bounds, DevicePixels, DisplayId, Pixels, PlatformDisplay, logical_point, point, size};
#[derive(Debug, Clone, Copy)]
pub(crate) struct WindowsDisplay {
pub handle: HMONITOR,
pub display_id: DisplayId,
scale_factor: f32,
bounds: Bounds<Pixels>,
physical_bounds: Bounds<DevicePixels>,
uuid: Uuid,
}
// The `HMONITOR` is thread-safe.
unsafe impl Send for WindowsDisplay {}
unsafe impl Sync for WindowsDisplay {}
impl WindowsDisplay {
pub(crate) fn new(display_id: DisplayId) -> Option<Self> {
let screen = available_monitors().into_iter().nth(display_id.0 as _)?;
let info = get_monitor_info(screen).log_err()?;
let monitor_size = info.monitorInfo.rcMonitor;
let uuid = generate_uuid(&info.szDevice);
let scale_factor = get_scale_factor_for_monitor(screen).log_err()?;
let physical_size = size(
(monitor_size.right - monitor_size.left).into(),
(monitor_size.bottom - monitor_size.top).into(),
);
Some(WindowsDisplay {
handle: screen,
display_id,
scale_factor,
bounds: Bounds {
origin: logical_point(
monitor_size.left as f32,
monitor_size.top as f32,
scale_factor,
),
size: physical_size.to_pixels(scale_factor),
},
physical_bounds: Bounds {
origin: point(monitor_size.left.into(), monitor_size.top.into()),
size: physical_size,
},
uuid,
})
}
pub fn new_with_handle(monitor: HMONITOR) -> Self {
let info = get_monitor_info(monitor).expect("unable to get monitor info");
let monitor_size = info.monitorInfo.rcMonitor;
let uuid = generate_uuid(&info.szDevice);
let display_id = available_monitors()
.iter()
.position(|handle| handle.0 == monitor.0)
.unwrap();
let scale_factor =
get_scale_factor_for_monitor(monitor).expect("unable to get scale factor for monitor");
let physical_size = size(
(monitor_size.right - monitor_size.left).into(),
(monitor_size.bottom - monitor_size.top).into(),
);
WindowsDisplay {
handle: monitor,
display_id: DisplayId(display_id as _),
scale_factor,
bounds: Bounds {
origin: logical_point(
monitor_size.left as f32,
monitor_size.top as f32,
scale_factor,
),
size: physical_size.to_pixels(scale_factor),
},
physical_bounds: Bounds {
origin: point(monitor_size.left.into(), monitor_size.top.into()),
size: physical_size,
},
uuid,
}
}
fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> Self {
let info = get_monitor_info(handle).expect("unable to get monitor info");
let monitor_size = info.monitorInfo.rcMonitor;
let uuid = generate_uuid(&info.szDevice);
let scale_factor =
get_scale_factor_for_monitor(handle).expect("unable to get scale factor for monitor");
let physical_size = size(
(monitor_size.right - monitor_size.left).into(),
(monitor_size.bottom - monitor_size.top).into(),
);
WindowsDisplay {
handle,
display_id,
scale_factor,
bounds: Bounds {
origin: logical_point(
monitor_size.left as f32,
monitor_size.top as f32,
scale_factor,
),
size: physical_size.to_pixels(scale_factor),
},
physical_bounds: Bounds {
origin: point(monitor_size.left.into(), monitor_size.top.into()),
size: physical_size,
},
uuid,
}
}
pub fn primary_monitor() -> Option<Self> {
// https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643
const POINT_ZERO: POINT = POINT { x: 0, y: 0 };
let monitor = unsafe { MonitorFromPoint(POINT_ZERO, MONITOR_DEFAULTTOPRIMARY) };
if monitor.is_invalid() {
log::error!(
"can not find the primary monitor: {}",
std::io::Error::last_os_error()
);
return None;
}
Some(WindowsDisplay::new_with_handle(monitor))
}
/// Check if the center point of given bounds is inside this monitor
pub fn check_given_bounds(&self, bounds: Bounds<Pixels>) -> bool {
let center = bounds.center();
let center = POINT {
x: (center.x.0 * self.scale_factor) as i32,
y: (center.y.0 * self.scale_factor) as i32,
};
let monitor = unsafe { MonitorFromPoint(center, MONITOR_DEFAULTTONULL) };
if monitor.is_invalid() {
false
} else {
let display = WindowsDisplay::new_with_handle(monitor);
display.uuid == self.uuid
}
}
pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
available_monitors()
.into_iter()
.enumerate()
.map(|(id, handle)| {
Rc::new(WindowsDisplay::new_with_handle_and_id(
handle,
DisplayId(id as _),
)) as Rc<dyn PlatformDisplay>
})
.collect()
}
/// Check if this monitor is still online
pub fn is_connected(hmonitor: HMONITOR) -> bool {
available_monitors().iter().contains(&hmonitor)
}
pub fn physical_bounds(&self) -> Bounds<DevicePixels> {
self.physical_bounds
}
}
impl PlatformDisplay for WindowsDisplay {
fn id(&self) -> DisplayId {
self.display_id
}
fn uuid(&self) -> anyhow::Result<Uuid> {
Ok(self.uuid)
}
fn bounds(&self) -> Bounds<Pixels> {
self.bounds
}
}
fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
unsafe {
EnumDisplayMonitors(
None,
None,
Some(monitor_enum_proc),
LPARAM(&mut monitors as *mut _ as _),
)
.ok()
.log_err();
}
monitors
}
unsafe extern "system" fn monitor_enum_proc(
hmonitor: HMONITOR,
_hdc: HDC,
_place: *mut RECT,
data: LPARAM,
) -> BOOL {
let monitors = data.0 as *mut SmallVec<[HMONITOR; 4]>;
unsafe { (*monitors).push(hmonitor) };
BOOL(1)
}
fn get_monitor_info(hmonitor: HMONITOR) -> anyhow::Result<MONITORINFOEXW> {
let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() };
monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
let status = unsafe {
GetMonitorInfoW(
hmonitor,
&mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO,
)
};
if status.as_bool() {
Ok(monitor_info)
} else {
Err(anyhow::anyhow!(std::io::Error::last_os_error()))
}
}
fn generate_uuid(device_name: &[u16]) -> Uuid {
let name = device_name
.iter()
.flat_map(|&a| a.to_be_bytes())
.collect_vec();
Uuid::new_v5(&Uuid::NAMESPACE_DNS, &name)
}
fn get_scale_factor_for_monitor(monitor: HMONITOR) -> Result<f32> {
let mut dpi_x = 0;
let mut dpi_y = 0;
unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) }?;
assert_eq!(dpi_x, dpi_y);
Ok(dpi_x as f32 / USER_DEFAULT_SCREEN_DPI as f32)
}
File diff suppressed because it is too large Load Diff
+404
View File
@@ -0,0 +1,404 @@
use anyhow::Result;
use collections::HashMap;
use windows::Win32::UI::{
Input::KeyboardAndMouse::{
GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VK_TO_VSC, MapVirtualKeyW, ToUnicode,
VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1,
VK_CONTROL, VK_MENU, VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7,
VK_OEM_8, VK_OEM_102, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT,
},
WindowsAndMessaging::KL_NAMELENGTH,
};
use windows_core::HSTRING;
use crate::{
KeybindingKeystroke, Keystroke, Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper,
};
pub(crate) struct WindowsKeyboardLayout {
id: String,
name: String,
}
pub(crate) struct WindowsKeyboardMapper {
key_to_vkey: HashMap<String, (u16, bool)>,
vkey_to_key: HashMap<u16, String>,
vkey_to_shifted: HashMap<u16, String>,
}
impl PlatformKeyboardLayout for WindowsKeyboardLayout {
fn id(&self) -> &str {
&self.id
}
fn name(&self) -> &str {
&self.name
}
}
impl PlatformKeyboardMapper for WindowsKeyboardMapper {
fn map_key_equivalent(
&self,
mut keystroke: Keystroke,
use_key_equivalents: bool,
) -> KeybindingKeystroke {
let Some((vkey, shifted_key)) = self.get_vkey_from_key(&keystroke.key, use_key_equivalents)
else {
return KeybindingKeystroke::from_keystroke(keystroke);
};
if shifted_key && keystroke.modifiers.shift {
log::warn!(
"Keystroke '{}' has both shift and a shifted key, this is likely a bug",
keystroke.key
);
}
let shift = shifted_key || keystroke.modifiers.shift;
keystroke.modifiers.shift = false;
let Some(key) = self.vkey_to_key.get(&vkey).cloned() else {
log::error!(
"Failed to map key equivalent '{:?}' to a valid key",
keystroke
);
return KeybindingKeystroke::from_keystroke(keystroke);
};
keystroke.key = if shift {
let Some(shifted_key) = self.vkey_to_shifted.get(&vkey).cloned() else {
log::error!(
"Failed to map keystroke {:?} with virtual key '{:?}' to a shifted key",
keystroke,
vkey
);
return KeybindingKeystroke::from_keystroke(keystroke);
};
shifted_key
} else {
key.clone()
};
let modifiers = Modifiers {
shift,
..keystroke.modifiers
};
KeybindingKeystroke::new(keystroke, modifiers, key)
}
fn get_key_equivalents(&self) -> Option<&HashMap<char, char>> {
None
}
}
impl WindowsKeyboardLayout {
pub(crate) fn new() -> Result<Self> {
let mut buffer = [0u16; KL_NAMELENGTH as usize];
unsafe { GetKeyboardLayoutNameW(&mut buffer)? };
let id = HSTRING::from_wide(&buffer).to_string();
let entry = windows_registry::LOCAL_MACHINE.open(format!(
"System\\CurrentControlSet\\Control\\Keyboard Layouts\\{}",
id
))?;
let name = entry.get_hstring("Layout Text")?.to_string();
Ok(Self { id, name })
}
pub(crate) fn unknown() -> Self {
Self {
id: "unknown".to_string(),
name: "unknown".to_string(),
}
}
pub(crate) fn uses_altgr(&self) -> bool {
// Check if this is a known AltGr layout by examining the layout ID
// The layout ID is a hex string like "00000409" (US) or "00000407" (German)
// Extract the language ID (last 4 bytes)
let id_bytes = self.id.as_bytes();
if id_bytes.len() >= 4 {
let lang_id = &id_bytes[id_bytes.len() - 4..];
// List of keyboard layouts that use AltGr (non-exhaustive)
matches!(
lang_id,
b"0407" | // German
b"040C" | // French
b"040A" | // Spanish
b"0415" | // Polish
b"0413" | // Dutch
b"0816" | // Portuguese
b"041D" | // Swedish
b"0414" | // Norwegian
b"040B" | // Finnish
b"041F" | // Turkish
b"0419" | // Russian
b"0405" | // Czech
b"040E" | // Hungarian
b"0424" | // Slovenian
b"041B" | // Slovak
b"0418" // Romanian
)
} else {
false
}
}
}
impl WindowsKeyboardMapper {
pub(crate) fn new() -> Self {
let mut key_to_vkey = HashMap::default();
let mut vkey_to_key = HashMap::default();
let mut vkey_to_shifted = HashMap::default();
for vkey in CANDIDATE_VKEYS {
if let Some(key) = get_key_from_vkey(*vkey) {
key_to_vkey.insert(key.clone(), (vkey.0, false));
vkey_to_key.insert(vkey.0, key);
}
let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) };
if scan_code == 0 {
continue;
}
if let Some(shifted_key) = get_shifted_key(*vkey, scan_code) {
key_to_vkey.insert(shifted_key.clone(), (vkey.0, true));
vkey_to_shifted.insert(vkey.0, shifted_key);
}
}
Self {
key_to_vkey,
vkey_to_key,
vkey_to_shifted,
}
}
fn get_vkey_from_key(&self, key: &str, use_key_equivalents: bool) -> Option<(u16, bool)> {
if use_key_equivalents {
get_vkey_from_key_with_us_layout(key)
} else {
self.key_to_vkey.get(key).cloned()
}
}
}
pub(crate) fn get_keystroke_key(
vkey: VIRTUAL_KEY,
scan_code: u32,
modifiers: &mut Modifiers,
) -> Option<String> {
if modifiers.shift && need_to_convert_to_shifted_key(vkey) {
get_shifted_key(vkey, scan_code).inspect(|_| {
modifiers.shift = false;
})
} else {
get_key_from_vkey(vkey)
}
}
fn get_key_from_vkey(vkey: VIRTUAL_KEY) -> Option<String> {
let key_data = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_CHAR) };
if key_data == 0 {
return None;
}
// The high word contains dead key flag, the low word contains the character
let key = char::from_u32(key_data & 0xFFFF)?;
Some(key.to_ascii_lowercase().to_string())
}
#[inline]
fn need_to_convert_to_shifted_key(vkey: VIRTUAL_KEY) -> bool {
matches!(
vkey,
VK_OEM_3
| VK_OEM_MINUS
| VK_OEM_PLUS
| VK_OEM_4
| VK_OEM_5
| VK_OEM_6
| VK_OEM_1
| VK_OEM_7
| VK_OEM_COMMA
| VK_OEM_PERIOD
| VK_OEM_2
| VK_OEM_102
| VK_OEM_8
| VK_ABNT_C1
| VK_0
| VK_1
| VK_2
| VK_3
| VK_4
| VK_5
| VK_6
| VK_7
| VK_8
| VK_9
)
}
fn get_shifted_key(vkey: VIRTUAL_KEY, scan_code: u32) -> Option<String> {
generate_key_char(vkey, scan_code, false, true, false)
}
pub(crate) fn generate_key_char(
vkey: VIRTUAL_KEY,
scan_code: u32,
control: bool,
shift: bool,
alt: bool,
) -> Option<String> {
let mut state = [0; 256];
if control {
state[VK_CONTROL.0 as usize] = 0x80;
}
if shift {
state[VK_SHIFT.0 as usize] = 0x80;
}
if alt {
state[VK_MENU.0 as usize] = 0x80;
}
let mut buffer = [0; 8];
let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 1 << 2) };
match len {
len if len > 0 => String::from_utf16(&buffer[..len as usize])
.ok()
.filter(|candidate| {
!candidate.is_empty() && !candidate.chars().next().unwrap().is_control()
}),
len if len < 0 => String::from_utf16(&buffer[..(-len as usize)]).ok(),
_ => None,
}
}
fn get_vkey_from_key_with_us_layout(key: &str) -> Option<(u16, bool)> {
match key {
// ` => VK_OEM_3
"`" => Some((VK_OEM_3.0, false)),
"~" => Some((VK_OEM_3.0, true)),
"1" => Some((VK_1.0, false)),
"!" => Some((VK_1.0, true)),
"2" => Some((VK_2.0, false)),
"@" => Some((VK_2.0, true)),
"3" => Some((VK_3.0, false)),
"#" => Some((VK_3.0, true)),
"4" => Some((VK_4.0, false)),
"$" => Some((VK_4.0, true)),
"5" => Some((VK_5.0, false)),
"%" => Some((VK_5.0, true)),
"6" => Some((VK_6.0, false)),
"^" => Some((VK_6.0, true)),
"7" => Some((VK_7.0, false)),
"&" => Some((VK_7.0, true)),
"8" => Some((VK_8.0, false)),
"*" => Some((VK_8.0, true)),
"9" => Some((VK_9.0, false)),
"(" => Some((VK_9.0, true)),
"0" => Some((VK_0.0, false)),
")" => Some((VK_0.0, true)),
"-" => Some((VK_OEM_MINUS.0, false)),
"_" => Some((VK_OEM_MINUS.0, true)),
"=" => Some((VK_OEM_PLUS.0, false)),
"+" => Some((VK_OEM_PLUS.0, true)),
"[" => Some((VK_OEM_4.0, false)),
"{" => Some((VK_OEM_4.0, true)),
"]" => Some((VK_OEM_6.0, false)),
"}" => Some((VK_OEM_6.0, true)),
"\\" => Some((VK_OEM_5.0, false)),
"|" => Some((VK_OEM_5.0, true)),
";" => Some((VK_OEM_1.0, false)),
":" => Some((VK_OEM_1.0, true)),
"'" => Some((VK_OEM_7.0, false)),
"\"" => Some((VK_OEM_7.0, true)),
"," => Some((VK_OEM_COMMA.0, false)),
"<" => Some((VK_OEM_COMMA.0, true)),
"." => Some((VK_OEM_PERIOD.0, false)),
">" => Some((VK_OEM_PERIOD.0, true)),
"/" => Some((VK_OEM_2.0, false)),
"?" => Some((VK_OEM_2.0, true)),
_ => None,
}
}
const CANDIDATE_VKEYS: &[VIRTUAL_KEY] = &[
VK_OEM_3,
VK_OEM_MINUS,
VK_OEM_PLUS,
VK_OEM_4,
VK_OEM_5,
VK_OEM_6,
VK_OEM_1,
VK_OEM_7,
VK_OEM_COMMA,
VK_OEM_PERIOD,
VK_OEM_2,
VK_OEM_102,
VK_OEM_8,
VK_ABNT_C1,
VK_0,
VK_1,
VK_2,
VK_3,
VK_4,
VK_5,
VK_6,
VK_7,
VK_8,
VK_9,
];
#[cfg(test)]
mod tests {
use crate::{Keystroke, Modifiers, PlatformKeyboardMapper, WindowsKeyboardMapper};
#[test]
fn test_keyboard_mapper() {
let mapper = WindowsKeyboardMapper::new();
// Normal case
let keystroke = Keystroke {
modifiers: Modifiers::control(),
key: "a".to_string(),
key_char: None,
};
let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
assert_eq!(*mapped.inner(), keystroke);
assert_eq!(mapped.key(), "a");
assert_eq!(*mapped.modifiers(), Modifiers::control());
// Shifted case, ctrl-$
let keystroke = Keystroke {
modifiers: Modifiers::control(),
key: "$".to_string(),
key_char: None,
};
let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
assert_eq!(*mapped.inner(), keystroke);
assert_eq!(mapped.key(), "4");
assert_eq!(*mapped.modifiers(), Modifiers::control_shift());
// Shifted case, but shift is true
let keystroke = Keystroke {
modifiers: Modifiers::control_shift(),
key: "$".to_string(),
key_char: None,
};
let mapped = mapper.map_key_equivalent(keystroke, true);
assert_eq!(mapped.inner().modifiers, Modifiers::control());
assert_eq!(mapped.key(), "4");
assert_eq!(*mapped.modifiers(), Modifiers::control_shift());
// Windows style
let keystroke = Keystroke {
modifiers: Modifiers::control_shift(),
key: "4".to_string(),
key_char: None,
};
let mapped = mapper.map_key_equivalent(keystroke, true);
assert_eq!(mapped.inner().modifiers, Modifiers::control());
assert_eq!(mapped.inner().key, "$");
assert_eq!(mapped.key(), "4");
assert_eq!(*mapped.modifiers(), Modifiers::control_shift());
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+197
View File
@@ -0,0 +1,197 @@
use std::ffi::{c_uint, c_void};
use ::util::ResultExt;
use windows::Win32::UI::{
Shell::{ABM_GETSTATE, ABM_GETTASKBARPOS, ABS_AUTOHIDE, APPBARDATA, SHAppBarMessage},
WindowsAndMessaging::{
SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,
SystemParametersInfoW,
},
};
use crate::*;
use super::WindowsDisplay;
/// Windows settings pulled from SystemParametersInfo
/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow
#[derive(Default, Debug, Clone, Copy)]
pub(crate) struct WindowsSystemSettings {
pub(crate) mouse_wheel_settings: MouseWheelSettings,
pub(crate) auto_hide_taskbar_position: Option<AutoHideTaskbarPosition>,
}
#[derive(Default, Debug, Clone, Copy)]
pub(crate) struct MouseWheelSettings {
/// SEE: SPI_GETWHEELSCROLLCHARS
pub(crate) wheel_scroll_chars: u32,
/// SEE: SPI_GETWHEELSCROLLLINES
pub(crate) wheel_scroll_lines: u32,
}
impl WindowsSystemSettings {
pub(crate) fn new(display: WindowsDisplay) -> Self {
let mut settings = Self::default();
settings.init(display);
settings
}
fn init(&mut self, display: WindowsDisplay) {
self.mouse_wheel_settings.update();
self.auto_hide_taskbar_position = AutoHideTaskbarPosition::new(display).log_err().flatten();
}
pub(crate) fn update(&mut self, display: WindowsDisplay, wparam: usize) {
match wparam {
// SPI_SETWORKAREA
47 => self.update_taskbar_position(display),
// SPI_GETWHEELSCROLLLINES, SPI_GETWHEELSCROLLCHARS
104 | 108 => self.update_mouse_wheel_settings(),
_ => {}
}
}
fn update_mouse_wheel_settings(&mut self) {
self.mouse_wheel_settings.update();
}
fn update_taskbar_position(&mut self, display: WindowsDisplay) {
self.auto_hide_taskbar_position = AutoHideTaskbarPosition::new(display).log_err().flatten();
}
}
impl MouseWheelSettings {
fn update(&mut self) {
self.update_wheel_scroll_chars();
self.update_wheel_scroll_lines();
}
fn update_wheel_scroll_chars(&mut self) {
let mut value = c_uint::default();
let result = unsafe {
SystemParametersInfoW(
SPI_GETWHEELSCROLLCHARS,
0,
Some((&mut value) as *mut c_uint as *mut c_void),
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
)
};
if result.log_err() != None && self.wheel_scroll_chars != value {
self.wheel_scroll_chars = value;
}
}
fn update_wheel_scroll_lines(&mut self) {
let mut value = c_uint::default();
let result = unsafe {
SystemParametersInfoW(
SPI_GETWHEELSCROLLLINES,
0,
Some((&mut value) as *mut c_uint as *mut c_void),
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
)
};
if result.log_err() != None && self.wheel_scroll_lines != value {
self.wheel_scroll_lines = value;
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) enum AutoHideTaskbarPosition {
Left,
Right,
Top,
#[default]
Bottom,
}
impl AutoHideTaskbarPosition {
fn new(display: WindowsDisplay) -> anyhow::Result<Option<Self>> {
if !check_auto_hide_taskbar_enable() {
// If auto hide taskbar is not enable, we do nothing in this case.
return Ok(None);
}
let mut info = APPBARDATA {
cbSize: std::mem::size_of::<APPBARDATA>() as u32,
..Default::default()
};
let ret = unsafe { SHAppBarMessage(ABM_GETTASKBARPOS, &mut info) };
if ret == 0 {
anyhow::bail!(
"Unable to retrieve taskbar position: {}",
std::io::Error::last_os_error()
);
}
let taskbar_bounds: Bounds<DevicePixels> = Bounds::new(
point(info.rc.left.into(), info.rc.top.into()),
size(
(info.rc.right - info.rc.left).into(),
(info.rc.bottom - info.rc.top).into(),
),
);
let display_bounds = display.physical_bounds();
if display_bounds.intersect(&taskbar_bounds) != taskbar_bounds {
// This case indicates that taskbar is not on the current monitor.
return Ok(None);
}
if taskbar_bounds.bottom() == display_bounds.bottom()
&& taskbar_bounds.right() == display_bounds.right()
{
if taskbar_bounds.size.height < display_bounds.size.height
&& taskbar_bounds.size.width == display_bounds.size.width
{
return Ok(Some(Self::Bottom));
}
if taskbar_bounds.size.width < display_bounds.size.width
&& taskbar_bounds.size.height == display_bounds.size.height
{
return Ok(Some(Self::Right));
}
log::error!(
"Unrecognized taskbar bounds {:?} give display bounds {:?}",
taskbar_bounds,
display_bounds
);
return Ok(None);
}
if taskbar_bounds.top() == display_bounds.top()
&& taskbar_bounds.left() == display_bounds.left()
{
if taskbar_bounds.size.height < display_bounds.size.height
&& taskbar_bounds.size.width == display_bounds.size.width
{
return Ok(Some(Self::Top));
}
if taskbar_bounds.size.width < display_bounds.size.width
&& taskbar_bounds.size.height == display_bounds.size.height
{
return Ok(Some(Self::Left));
}
log::error!(
"Unrecognized taskbar bounds {:?} give display bounds {:?}",
taskbar_bounds,
display_bounds
);
return Ok(None);
}
log::error!(
"Unrecognized taskbar bounds {:?} give display bounds {:?}",
taskbar_bounds,
display_bounds
);
Ok(None)
}
}
/// Check if auto hide taskbar is enable or not.
fn check_auto_hide_taskbar_enable() -> bool {
let mut info = APPBARDATA {
cbSize: std::mem::size_of::<APPBARDATA>() as u32,
..Default::default()
};
let ret = unsafe { SHAppBarMessage(ABM_GETSTATE, &mut info) } as u32;
ret == ABS_AUTOHIDE
}
+219
View File
@@ -0,0 +1,219 @@
use std::sync::OnceLock;
use ::util::ResultExt;
use anyhow::Context;
use windows::{
UI::{
Color,
ViewManagement::{UIColorType, UISettings},
},
Wdk::System::SystemServices::RtlGetVersion,
Win32::{
Foundation::*, Graphics::Dwm::*, System::LibraryLoader::LoadLibraryA,
UI::WindowsAndMessaging::*,
},
core::{BOOL, HSTRING, PCSTR},
};
use crate::*;
#[derive(Debug, Clone, Copy)]
pub(crate) enum WindowsVersion {
Win10,
Win11,
}
impl WindowsVersion {
pub(crate) fn new() -> anyhow::Result<Self> {
let mut version = unsafe { std::mem::zeroed() };
let status = unsafe { RtlGetVersion(&mut version) };
status.ok()?;
if version.dwBuildNumber >= 22000 {
Ok(WindowsVersion::Win11)
} else {
Ok(WindowsVersion::Win10)
}
}
}
pub(crate) trait HiLoWord {
fn hiword(&self) -> u16;
fn loword(&self) -> u16;
fn signed_hiword(&self) -> i16;
fn signed_loword(&self) -> i16;
}
impl HiLoWord for WPARAM {
fn hiword(&self) -> u16 {
((self.0 >> 16) & 0xFFFF) as u16
}
fn loword(&self) -> u16 {
(self.0 & 0xFFFF) as u16
}
fn signed_hiword(&self) -> i16 {
((self.0 >> 16) & 0xFFFF) as i16
}
fn signed_loword(&self) -> i16 {
(self.0 & 0xFFFF) as i16
}
}
impl HiLoWord for LPARAM {
fn hiword(&self) -> u16 {
((self.0 >> 16) & 0xFFFF) as u16
}
fn loword(&self) -> u16 {
(self.0 & 0xFFFF) as u16
}
fn signed_hiword(&self) -> i16 {
((self.0 >> 16) & 0xFFFF) as i16
}
fn signed_loword(&self) -> i16 {
(self.0 & 0xFFFF) as i16
}
}
pub(crate) unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize {
#[cfg(target_pointer_width = "64")]
unsafe {
GetWindowLongPtrW(hwnd, nindex)
}
#[cfg(target_pointer_width = "32")]
unsafe {
GetWindowLongW(hwnd, nindex) as isize
}
}
pub(crate) unsafe fn set_window_long(
hwnd: HWND,
nindex: WINDOW_LONG_PTR_INDEX,
dwnewlong: isize,
) -> isize {
#[cfg(target_pointer_width = "64")]
unsafe {
SetWindowLongPtrW(hwnd, nindex, dwnewlong)
}
#[cfg(target_pointer_width = "32")]
unsafe {
SetWindowLongW(hwnd, nindex, dwnewlong as i32) as isize
}
}
pub(crate) fn windows_credentials_target_name(url: &str) -> String {
format!("zed:url={}", url)
}
pub(crate) fn load_cursor(style: CursorStyle) -> Option<HCURSOR> {
static ARROW: OnceLock<SafeCursor> = OnceLock::new();
static IBEAM: OnceLock<SafeCursor> = OnceLock::new();
static CROSS: OnceLock<SafeCursor> = OnceLock::new();
static HAND: OnceLock<SafeCursor> = OnceLock::new();
static SIZEWE: OnceLock<SafeCursor> = OnceLock::new();
static SIZENS: OnceLock<SafeCursor> = OnceLock::new();
static NO: OnceLock<SafeCursor> = OnceLock::new();
let (lock, name) = match style {
CursorStyle::IBeam | CursorStyle::IBeamCursorForVerticalLayout => (&IBEAM, IDC_IBEAM),
CursorStyle::Crosshair => (&CROSS, IDC_CROSS),
CursorStyle::PointingHand | CursorStyle::DragLink => (&HAND, IDC_HAND),
CursorStyle::ResizeLeft
| CursorStyle::ResizeRight
| CursorStyle::ResizeLeftRight
| CursorStyle::ResizeColumn => (&SIZEWE, IDC_SIZEWE),
CursorStyle::ResizeUp
| CursorStyle::ResizeDown
| CursorStyle::ResizeUpDown
| CursorStyle::ResizeRow => (&SIZENS, IDC_SIZENS),
CursorStyle::OperationNotAllowed => (&NO, IDC_NO),
CursorStyle::None => return None,
_ => (&ARROW, IDC_ARROW),
};
Some(
*(*lock.get_or_init(|| {
HCURSOR(
unsafe { LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED) }
.log_err()
.unwrap_or_default()
.0,
)
.into()
})),
)
}
/// This function is used to configure the dark mode for the window built-in title bar.
pub(crate) fn configure_dwm_dark_mode(hwnd: HWND, appearance: WindowAppearance) {
let dark_mode_enabled: BOOL = match appearance {
WindowAppearance::Dark | WindowAppearance::VibrantDark => true.into(),
WindowAppearance::Light | WindowAppearance::VibrantLight => false.into(),
};
unsafe {
DwmSetWindowAttribute(
hwnd,
DWMWA_USE_IMMERSIVE_DARK_MODE,
&dark_mode_enabled as *const _ as _,
std::mem::size_of::<BOOL>() as u32,
)
.log_err();
}
}
#[inline]
pub(crate) fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels> {
Point {
x: px(x / scale_factor),
y: px(y / scale_factor),
}
}
// https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes
#[inline]
pub(crate) fn system_appearance() -> Result<WindowAppearance> {
let ui_settings = UISettings::new()?;
let foreground_color = ui_settings.GetColorValue(UIColorType::Foreground)?;
// If the foreground is light, then is_color_light will evaluate to true,
// meaning Dark mode is enabled.
if is_color_light(&foreground_color) {
Ok(WindowAppearance::Dark)
} else {
Ok(WindowAppearance::Light)
}
}
#[inline(always)]
fn is_color_light(color: &Color) -> bool {
((5 * color.G as u32) + (2 * color.R as u32) + color.B as u32) > (8 * 128)
}
pub(crate) fn show_error(title: &str, content: String) {
let _ = unsafe {
MessageBoxW(
None,
&HSTRING::from(content),
&HSTRING::from(title),
MB_ICONERROR | MB_SYSTEMMODAL,
)
};
}
pub(crate) fn with_dll_library<R, F>(dll_name: PCSTR, f: F) -> Result<R>
where
F: FnOnce(HMODULE) -> Result<R>,
{
let library = unsafe {
LoadLibraryA(dll_name).with_context(|| format!("Loading dll: {}", dll_name.display()))?
};
let result = f(library);
unsafe {
FreeLibrary(library)
.with_context(|| format!("Freeing dll: {}", dll_name.display()))
.log_err();
}
result
}
+81
View File
@@ -0,0 +1,81 @@
use std::{
sync::LazyLock,
time::{Duration, Instant},
};
use anyhow::{Context, Result};
use util::ResultExt;
use windows::Win32::{
Foundation::HWND,
Graphics::Dwm::{DWM_TIMING_INFO, DwmFlush, DwmGetCompositionTimingInfo},
System::Performance::QueryPerformanceFrequency,
};
static QPC_TICKS_PER_SECOND: LazyLock<u64> = LazyLock::new(|| {
let mut frequency = 0;
// On systems that run Windows XP or later, the function will always succeed and
// will thus never return zero.
unsafe { QueryPerformanceFrequency(&mut frequency).unwrap() };
frequency as u64
});
const VSYNC_INTERVAL_THRESHOLD: Duration = Duration::from_millis(1);
const DEFAULT_VSYNC_INTERVAL: Duration = Duration::from_micros(16_666); // ~60Hz
pub(crate) struct VSyncProvider {
interval: Duration,
f: Box<dyn Fn() -> bool>,
}
impl VSyncProvider {
pub(crate) fn new() -> Self {
let interval = get_dwm_interval()
.context("Failed to get DWM interval")
.log_err()
.unwrap_or(DEFAULT_VSYNC_INTERVAL);
let f = Box::new(|| unsafe { DwmFlush().is_ok() });
Self { interval, f }
}
pub(crate) fn wait_for_vsync(&self) {
let vsync_start = Instant::now();
let wait_succeeded = (self.f)();
let elapsed = vsync_start.elapsed();
// DwmFlush and DCompositionWaitForCompositorClock returns very early
// instead of waiting until vblank when the monitor goes to sleep or is
// unplugged (nothing to present due to desktop occlusion). We use 1ms as
// a threshold for the duration of the wait functions and fallback to
// Sleep() if it returns before that. This could happen during normal
// operation for the first call after the vsync thread becomes non-idle,
// but it shouldn't happen often.
if !wait_succeeded || elapsed < VSYNC_INTERVAL_THRESHOLD {
log::trace!("VSyncProvider::wait_for_vsync() took less time than expected");
std::thread::sleep(self.interval);
}
}
}
fn get_dwm_interval() -> Result<Duration> {
let mut timing_info = DWM_TIMING_INFO {
cbSize: std::mem::size_of::<DWM_TIMING_INFO>() as u32,
..Default::default()
};
unsafe { DwmGetCompositionTimingInfo(HWND::default(), &mut timing_info) }?;
let interval = retrieve_duration(timing_info.qpcRefreshPeriod, *QPC_TICKS_PER_SECOND);
// Check for interval values that are impossibly low. A 29 microsecond
// interval was seen (from a qpcRefreshPeriod of 60).
if interval < VSYNC_INTERVAL_THRESHOLD {
Ok(retrieve_duration(
timing_info.rateRefresh.uiDenominator as u64,
timing_info.rateRefresh.uiNumerator as u64,
))
} else {
Ok(interval)
}
}
#[inline]
fn retrieve_duration(counts: u64, ticks_per_second: u64) -> Duration {
let ticks_per_microsecond = ticks_per_second / 1_000_000;
Duration::from_micros(counts / ticks_per_microsecond)
}
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
use std::ops::Deref;
use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::HCURSOR};
#[derive(Debug, Clone, Copy)]
pub(crate) struct SafeCursor {
raw: HCURSOR,
}
unsafe impl Send for SafeCursor {}
unsafe impl Sync for SafeCursor {}
impl From<HCURSOR> for SafeCursor {
fn from(value: HCURSOR) -> Self {
SafeCursor { raw: value }
}
}
impl Deref for SafeCursor {
type Target = HCURSOR;
fn deref(&self) -> &Self::Target {
&self.raw
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SafeHwnd {
raw: HWND,
}
impl SafeHwnd {
pub(crate) fn as_raw(&self) -> HWND {
self.raw
}
}
unsafe impl Send for SafeHwnd {}
unsafe impl Sync for SafeHwnd {}
impl From<HWND> for SafeHwnd {
fn from(value: HWND) -> Self {
SafeHwnd { raw: value }
}
}
impl Deref for SafeHwnd {
type Target = HWND;
fn deref(&self) -> &Self::Target {
&self.raw
}
}