Present Servo BGRA hardware surfaces
This commit is contained in:
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
+1923
File diff suppressed because it is too large
Load Diff
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
+1563
File diff suppressed because it is too large
Load Diff
+404
@@ -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());
|
||||
}
|
||||
}
|
||||
+1169
File diff suppressed because it is too large
Load Diff
+1182
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
+1413
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user