logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled

Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree
with a 3-file patch applied (no upstream history).

Patch (crates/gpui_linux/src/linux/wayland/):
  - serial.rs: add SerialKind::KeyboardEnter
  - client.rs: store wl_keyboard.enter serial; add latest_serial_of()
  - window.rs: activate() uses keyboard-enter serial (Mutter focus gate)

Mutter honors window activation only when the token carries the keyboard-
focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial.
See docs/tray-window-focus-wayland.md in logiguard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mohamad Khani
2026-07-14 02:22:17 +03:30
commit b72a46db68
3984 changed files with 1583326 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
[package]
name = "gpui_windows"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/gpui_windows.rs"
[features]
default = ["gpui/default"]
test-support = ["gpui/test-support"]
screen-capture = ["gpui/screen-capture", "scap"]
[dependencies]
gpui.workspace = true
[target.'cfg(target_os = "windows")'.dependencies]
anyhow.workspace = true
collections.workspace = true
etagere = "0.2"
futures.workspace = true
image.workspace = true
itertools.workspace = true
log.workspace = true
parking_lot.workspace = true
rand.workspace = true
raw-window-handle = "0.6"
smallvec.workspace = true
util.workspace = true
uuid.workspace = true
windows.workspace = true
windows-core.workspace = true
windows-numerics = "0.2"
windows-registry = "0.5"
[target.'cfg(target_os = "windows")'.dependencies.scap]
workspace = true
optional = true
[target.'cfg(target_os = "windows")'.build-dependencies]
windows-registry = "0.5"

View File

@@ -0,0 +1 @@
../../LICENSE-APACHE

View File

@@ -0,0 +1,242 @@
#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
fn main() {
#[cfg(target_os = "windows")]
{
// Compile HLSL shaders
#[cfg(not(debug_assertions))]
compile_shaders();
}
}
#[cfg(all(target_os = "windows", not(debug_assertions)))]
mod shader_compilation {
use std::{
fs,
io::Write,
path::{Path, PathBuf},
process::{self, Command},
};
pub fn compile_shaders() {
let shader_path =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("src/shaders.hlsl");
let out_dir = std::env::var("OUT_DIR").unwrap();
println!("cargo:rerun-if-changed={}", shader_path.display());
// Check if fxc.exe is available
let fxc_path = find_fxc_compiler();
// Define all modules
let modules = [
"quad",
"shadow",
"path_rasterization",
"path_sprite",
"underline",
"monochrome_sprite",
"subpixel_sprite",
"polychrome_sprite",
];
let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
if Path::new(&rust_binding_path).exists() {
fs::remove_file(&rust_binding_path)
.expect("Failed to remove existing Rust binding file");
}
for module in modules {
compile_shader_for_module(
module,
&out_dir,
&fxc_path,
shader_path.to_str().unwrap(),
&rust_binding_path,
);
}
{
let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("src/color_text_raster.hlsl");
compile_shader_for_module(
"emoji_rasterization",
&out_dir,
&fxc_path,
shader_path.to_str().unwrap(),
&rust_binding_path,
);
}
}
/// Locate `binary` in the newest installed Windows SDK.
pub fn find_latest_windows_sdk_binary(
binary: &str,
) -> Result<Option<PathBuf>, Box<dyn std::error::Error>> {
let key = windows_registry::LOCAL_MACHINE
.open("SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0")?;
let install_folder: String = key.get_string("InstallationFolder")?; // "C:\Program Files (x86)\Windows Kits\10\"
let install_folder_bin = Path::new(&install_folder).join("bin");
let mut versions: Vec<_> = std::fs::read_dir(&install_folder_bin)?
.flatten()
.filter(|entry| entry.path().is_dir())
.filter_map(|entry| entry.file_name().into_string().ok())
.collect();
versions.sort_by_key(|s| {
s.split('.')
.filter_map(|p| p.parse().ok())
.collect::<Vec<u32>>()
});
let arch = match std::env::consts::ARCH {
"x86_64" => "x64",
"aarch64" => "arm64",
_ => Err(format!(
"Unsupported architecture: {}",
std::env::consts::ARCH
))?,
};
if let Some(highest_version) = versions.last() {
return Ok(Some(
install_folder_bin
.join(highest_version)
.join(arch)
.join(binary),
));
}
Ok(None)
}
/// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
fn find_fxc_compiler() -> String {
// Check environment variable
if let Ok(path) = std::env::var("GPUI_FXC_PATH")
&& Path::new(&path).exists()
{
return path;
}
// Try to find in PATH
// NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
if let Ok(output) = std::process::Command::new("where.exe")
.arg("fxc.exe")
.output()
&& output.status.success()
{
let path = String::from_utf8_lossy(&output.stdout);
return path.trim().to_string();
}
if let Ok(Some(path)) = find_latest_windows_sdk_binary("fxc.exe") {
return path.to_string_lossy().into_owned();
}
panic!("Failed to find fxc.exe");
}
fn compile_shader_for_module(
module: &str,
out_dir: &str,
fxc_path: &str,
shader_path: &str,
rust_binding_path: &str,
) {
// Compile vertex shader
let output_file = format!("{}/{}_vs.h", out_dir, module);
let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
compile_shader_impl(
fxc_path,
&format!("{module}_vertex"),
&output_file,
&const_name,
shader_path,
"vs_4_1",
);
generate_rust_binding(&const_name, &output_file, rust_binding_path);
// Compile fragment shader
let output_file = format!("{}/{}_ps.h", out_dir, module);
let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
compile_shader_impl(
fxc_path,
&format!("{module}_fragment"),
&output_file,
&const_name,
shader_path,
"ps_4_1",
);
generate_rust_binding(&const_name, &output_file, rust_binding_path);
}
fn compile_shader_impl(
fxc_path: &str,
entry_point: &str,
output_path: &str,
var_name: &str,
shader_path: &str,
target: &str,
) {
let output = Command::new(fxc_path)
.args([
"/T",
target,
"/E",
entry_point,
"/Fh",
output_path,
"/Vn",
var_name,
"/O3",
shader_path,
])
.output();
match output {
Ok(result) => {
if result.status.success() {
return;
}
println!(
"cargo::error=Shader compilation failed for {}:\n{}",
entry_point,
String::from_utf8_lossy(&result.stderr)
);
process::exit(1);
}
Err(e) => {
println!("cargo::error=Failed to run fxc for {}: {}", entry_point, e);
process::exit(1);
}
}
}
fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
let const_definition = {
let global_var_start = header_content.find("const BYTE").unwrap();
let global_var = &header_content[global_var_start..];
let equal = global_var.find('=').unwrap();
global_var[equal + 1..].trim()
};
let rust_binding = format!(
"const {}: &[u8] = &{}\n",
const_name,
const_definition.replace('{', "[").replace('}', "]")
);
let mut options = fs::OpenOptions::new()
.create(true)
.append(true)
.open(output_path)
.expect("Failed to open Rust binding file");
options
.write_all(rust_binding.as_bytes())
.expect("Failed to write Rust binding file");
}
}
#[cfg(all(target_os = "windows", not(debug_assertions)))]
use shader_compilation::compile_shaders;

View File

@@ -0,0 +1,49 @@
// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.hlsl
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
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);
}
float3 enhance_contrast3(float3 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;
}
float3 apply_alpha_correction3(float3 a, float3 b, float4 g) {
float3 brightness_adjustment = g.x * b + g.y;
float3 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);
}
float3 apply_contrast_and_gamma_correction3(float3 sample, float3 color, float enhanced_contrast_factor, float4 gamma_ratios) {
float enhanced_contrast = light_on_dark_contrast(enhanced_contrast_factor, color);
float3 contrasted = enhance_contrast3(sample, enhanced_contrast);
return apply_alpha_correction3(contrasted, color, gamma_ratios);
}

View File

@@ -0,0 +1,388 @@
use std::sync::LazyLock;
use anyhow::Result;
use collections::FxHashMap;
use itertools::Itertools;
use windows::Win32::{
Foundation::{HANDLE, HGLOBAL},
System::{
DataExchange::{
CloseClipboard, CountClipboardFormats, EmptyClipboard, EnumClipboardFormats,
GetClipboardData, GetClipboardFormatNameW, OpenClipboard, RegisterClipboardFormatW,
SetClipboardData,
},
Memory::{GMEM_MOVEABLE, GlobalAlloc, GlobalLock, GlobalSize, GlobalUnlock},
Ole::{CF_DIB, CF_HDROP, CF_UNICODETEXT},
},
UI::Shell::{DragQueryFileW, HDROP},
};
use windows::core::{Owned, PCWSTR};
use gpui::{
ClipboardEntry, ClipboardItem, ClipboardString, ExternalPaths, Image, ImageFormat, hash,
};
const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
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")));
static IMAGE_FORMATS_MAP: LazyLock<FxHashMap<u32, ImageFormat>> = LazyLock::new(|| {
let mut map = FxHashMap::default();
map.insert(*CLIPBOARD_PNG_FORMAT, ImageFormat::Png);
map.insert(*CLIPBOARD_GIF_FORMAT, ImageFormat::Gif);
map.insert(*CLIPBOARD_JPG_FORMAT, ImageFormat::Jpeg);
map.insert(*CLIPBOARD_SVG_FORMAT, ImageFormat::Svg);
map
});
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()
);
}
log::debug!(
"Registered clipboard format {} as {}",
unsafe { format.display() },
ret
);
ret
}
fn get_clipboard_data(format: u32) -> Option<LockedGlobal> {
let global = HGLOBAL(unsafe { GetClipboardData(format).ok() }?.0);
LockedGlobal::lock(global)
}
pub(crate) fn write_to_clipboard(item: ClipboardItem) {
let Some(_clip) = ClipboardGuard::open() else {
return;
};
let result: Result<()> = (|| {
unsafe { EmptyClipboard()? };
for entry in item.entries() {
match entry {
ClipboardEntry::String(string) => write_string(string)?,
ClipboardEntry::Image(image) => write_image(image)?,
ClipboardEntry::ExternalPaths(_) => {}
}
}
Ok(())
})();
if let Err(e) = result {
log::error!("Failed to write to clipboard: {e}");
}
}
pub(crate) fn read_from_clipboard() -> Option<ClipboardItem> {
let _clip = ClipboardGuard::open()?;
let mut entries = Vec::new();
let mut have_text = false;
let mut have_image = false;
let mut have_files = false;
let count = unsafe { CountClipboardFormats() };
let mut format = 0;
for _ in 0..count {
format = unsafe { EnumClipboardFormats(format) };
if !have_text && format == CF_UNICODETEXT.0 as u32 {
if let Some(entry) = read_string() {
entries.push(entry);
have_text = true;
}
} else if !have_image && is_image_format(format) {
if let Some(entry) = read_image(format) {
entries.push(entry);
have_image = true;
}
} else if !have_files && format == CF_HDROP.0 as u32 {
if let Some(entry) = read_files() {
entries.push(entry);
have_files = true;
}
}
}
if entries.is_empty() {
log_unsupported_clipboard_formats();
return None;
}
Some(ClipboardItem { entries })
}
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 set_clipboard_bytes<T>(data: &[T], format: u32) -> Result<()> {
unsafe {
let global = Owned::new(GlobalAlloc(GMEM_MOVEABLE, std::mem::size_of_val(data))?);
let ptr = GlobalLock(*global);
anyhow::ensure!(!ptr.is_null(), "GlobalLock returned null");
std::ptr::copy_nonoverlapping(data.as_ptr(), ptr as _, data.len());
GlobalUnlock(*global).ok();
SetClipboardData(format, Some(HANDLE(global.0)))?;
// SetClipboardData succeeded — the system now owns the memory.
std::mem::forget(global);
}
Ok(())
}
fn get_clipboard_string(format: u32) -> Option<String> {
let locked = get_clipboard_data(format)?;
let bytes = locked.as_bytes();
let words_len = bytes.len() / std::mem::size_of::<u16>();
if words_len == 0 {
return Some(String::new());
}
let slice = unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const u16, words_len) };
let actual_len = slice.iter().position(|&c| c == 0).unwrap_or(words_len);
Some(String::from_utf16_lossy(&slice[..actual_len]))
}
fn is_image_format(format: u32) -> bool {
IMAGE_FORMATS_MAP.contains_key(&format) || format == CF_DIB.0 as u32
}
fn write_string(item: &ClipboardString) -> Result<()> {
let wide: Vec<u16> = item.text.encode_utf16().chain(Some(0)).collect_vec();
set_clipboard_bytes(&wide, CF_UNICODETEXT.0 as u32)?;
if let Some(metadata) = item.metadata.as_ref() {
let hash_bytes = ClipboardString::text_hash(&item.text).to_ne_bytes();
set_clipboard_bytes(&hash_bytes, *CLIPBOARD_HASH_FORMAT)?;
let wide: Vec<u16> = metadata.encode_utf16().chain(Some(0)).collect_vec();
set_clipboard_bytes(&wide, *CLIPBOARD_METADATA_FORMAT)?;
}
Ok(())
}
fn write_image(item: &Image) -> Result<()> {
let native_format = match item.format {
ImageFormat::Svg => Some(*CLIPBOARD_SVG_FORMAT),
ImageFormat::Gif => Some(*CLIPBOARD_GIF_FORMAT),
ImageFormat::Png => Some(*CLIPBOARD_PNG_FORMAT),
ImageFormat::Jpeg => Some(*CLIPBOARD_JPG_FORMAT),
_ => None,
};
if let Some(format) = native_format {
set_clipboard_bytes(item.bytes(), format)?;
}
// Also provide a PNG copy for broad compatibility.
// SVG can't be rasterized by the image crate, so skip it.
if item.format != ImageFormat::Svg && native_format != Some(*CLIPBOARD_PNG_FORMAT) {
if let Some(png_bytes) = convert_to_png(item.bytes(), item.format) {
set_clipboard_bytes(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
}
}
Ok(())
}
fn convert_to_png(bytes: &[u8], format: ImageFormat) -> Option<Vec<u8>> {
let img_format = gpui_to_image_format(format)?;
let image = image::load_from_memory_with_format(bytes, img_format)
.map_err(|e| log::warn!("Failed to decode image for PNG conversion: {e}"))
.ok()?;
let mut buf = Vec::new();
image
.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png)
.map_err(|e| log::warn!("Failed to encode PNG: {e}"))
.ok()?;
Some(buf)
}
fn read_string() -> Option<ClipboardEntry> {
let text = get_clipboard_string(CF_UNICODETEXT.0 as u32)?;
let metadata = read_clipboard_metadata(&text);
Some(ClipboardEntry::String(ClipboardString { text, metadata }))
}
fn read_clipboard_metadata(text: &str) -> Option<String> {
let locked = get_clipboard_data(*CLIPBOARD_HASH_FORMAT)?;
let hash_bytes: [u8; 8] = locked.as_bytes().get(..8)?.try_into().ok()?;
let hash = u64::from_ne_bytes(hash_bytes);
if hash != ClipboardString::text_hash(text) {
return None;
}
get_clipboard_string(*CLIPBOARD_METADATA_FORMAT)
}
fn read_image(format: u32) -> Option<ClipboardEntry> {
let locked = get_clipboard_data(format)?;
let (bytes, image_format) = if format == CF_DIB.0 as u32 {
(convert_dib_to_bmp(locked.as_bytes())?, ImageFormat::Bmp)
} else {
let image_format = *IMAGE_FORMATS_MAP.get(&format)?;
(locked.as_bytes().to_vec(), image_format)
};
let id = hash(&bytes);
Some(ClipboardEntry::Image(Image {
format: image_format,
bytes,
id,
}))
}
fn read_files() -> Option<ClipboardEntry> {
let locked = get_clipboard_data(CF_HDROP.0 as u32)?;
let hdrop = HDROP(locked.ptr as *mut _);
let mut filenames = Vec::new();
with_file_names(hdrop, |name| filenames.push(std::path::PathBuf::from(name)));
Some(ClipboardEntry::ExternalPaths(ExternalPaths(
filenames.into(),
)))
}
/// DIB is BMP without the 14-byte BITMAPFILEHEADER. Prepend one.
fn convert_dib_to_bmp(dib: &[u8]) -> Option<Vec<u8>> {
if dib.len() < 40 {
return None;
}
let header_size = u32::from_le_bytes(dib[0..4].try_into().ok()?);
let bit_count = u16::from_le_bytes(dib[14..16].try_into().ok()?);
let compression = u32::from_le_bytes(dib[16..20].try_into().ok()?);
let color_table_size = if bit_count <= 8 {
let colors_used = u32::from_le_bytes(dib[32..36].try_into().ok()?);
(if colors_used == 0 {
1u32 << bit_count
} else {
colors_used
}) * 4
} else if compression == 3 {
12 // BI_BITFIELDS
} else {
0
};
let pixel_offset = 14 + header_size + color_table_size;
let file_size = 14 + dib.len() as u32;
let mut bmp = Vec::with_capacity(file_size as usize);
bmp.extend_from_slice(b"BM");
bmp.extend_from_slice(&file_size.to_le_bytes());
bmp.extend_from_slice(&[0u8; 4]); // reserved
bmp.extend_from_slice(&pixel_offset.to_le_bytes());
bmp.extend_from_slice(dib);
Some(bmp)
}
fn log_unsupported_clipboard_formats() {
let count = unsafe { CountClipboardFormats() };
let mut format = 0;
for _ in 0..count {
format = unsafe { EnumClipboardFormats(format) };
let mut buffer = [0u16; 64];
unsafe { GetClipboardFormatNameW(format, &mut buffer) };
let format_name = String::from_utf16_lossy(&buffer);
log::warn!(
"Try to paste with unsupported clipboard format: {}, {}.",
format,
format_name
);
}
}
fn gpui_to_image_format(value: ImageFormat) -> Option<image::ImageFormat> {
match value {
ImageFormat::Png => Some(image::ImageFormat::Png),
ImageFormat::Jpeg => Some(image::ImageFormat::Jpeg),
ImageFormat::Webp => Some(image::ImageFormat::WebP),
ImageFormat::Gif => Some(image::ImageFormat::Gif),
ImageFormat::Bmp => Some(image::ImageFormat::Bmp),
ImageFormat::Tiff => Some(image::ImageFormat::Tiff),
other => {
log::warn!("No image crate equivalent for format: {other:?}");
None
}
}
}
struct ClipboardGuard;
impl ClipboardGuard {
fn open() -> Option<Self> {
match unsafe { OpenClipboard(None) } {
Ok(()) => Some(Self),
Err(e) => {
log::error!("Failed to open clipboard: {e}");
None
}
}
}
}
impl Drop for ClipboardGuard {
fn drop(&mut self) {
if let Err(e) = unsafe { CloseClipboard() } {
log::error!("Failed to close clipboard: {e}");
}
}
}
struct LockedGlobal {
global: HGLOBAL,
ptr: *const u8,
size: usize,
}
impl LockedGlobal {
fn lock(global: HGLOBAL) -> Option<Self> {
let size = unsafe { GlobalSize(global) };
let ptr = unsafe { GlobalLock(global) };
if ptr.is_null() {
return None;
}
Some(Self {
global,
ptr: ptr as *const u8,
size,
})
}
fn as_bytes(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr, self.size) }
}
}
impl Drop for LockedGlobal {
fn drop(&mut self) {
unsafe { GlobalUnlock(self.global).ok() };
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,207 @@
use std::{path::PathBuf, sync::Arc};
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 gpui::{Action, MenuItem, SharedString};
pub(crate) struct JumpList {
pub(crate) dock_menus: Vec<DockMenuItem>,
pub(crate) recent_workspaces: Arc<[SmallVec<[PathBuf; 2]>]>,
}
impl JumpList {
pub(crate) fn new() -> Self {
Self {
dock_menus: Vec::default(),
recent_workspaces: Arc::default(),
}
}
}
pub(crate) struct DockMenuItem {
pub(crate) name: SharedString,
pub(crate) description: SharedString,
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(),
description: if name == "New Window" {
"Opens a new window".into()
} else {
name
},
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(
recent_workspaces: &[SmallVec<[PathBuf; 2]>],
dock_menus: &[(SharedString, SharedString)],
) -> anyhow::Result<Vec<SmallVec<[PathBuf; 2]>>> {
let (list, removed) = create_destination_list()?;
add_recent_folders(&list, recent_workspaces, removed.as_ref())?;
add_dock_menu(&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: &[(SharedString, SharedString)],
) -> anyhow::Result<()> {
unsafe {
let tasks: IObjectCollection =
CoCreateInstance(&EnumerableObjectCollection, None, CLSCTX_INPROC_SERVER)?;
for (idx, (name, description)) in dock_menus.iter().enumerate() {
let argument = HSTRING::from(format!("--dock-action {}", idx));
let description = HSTRING::from(description.as_str());
let display = 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,
)?)?;
}
if tasks.GetCount().unwrap_or(0) > 0 {
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)
}
}

View File

@@ -0,0 +1,359 @@
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use ::util::ResultExt;
use anyhow::Result;
use gpui::*;
use windows::Win32::{
Foundation::*,
Graphics::{DirectManipulation::*, Gdi::*},
System::Com::*,
UI::{Input::Pointer::*, WindowsAndMessaging::*},
};
use crate::*;
/// Default viewport size in pixels. The actual content size doesn't matter
/// because we're using the viewport only for gesture recognition, not for
/// visual output.
const DEFAULT_VIEWPORT_SIZE: i32 = 1000;
pub(crate) struct DirectManipulationHandler {
manager: IDirectManipulationManager,
update_manager: IDirectManipulationUpdateManager,
viewport: IDirectManipulationViewport,
_handler_cookie: u32,
window: HWND,
scale_factor: Rc<Cell<f32>>,
pending_events: Rc<RefCell<Vec<PlatformInput>>>,
}
impl DirectManipulationHandler {
pub fn new(window: HWND, scale_factor: f32) -> Result<Self> {
unsafe {
let manager: IDirectManipulationManager =
CoCreateInstance(&DirectManipulationManager, None, CLSCTX_INPROC_SERVER)?;
let update_manager: IDirectManipulationUpdateManager = manager.GetUpdateManager()?;
let viewport: IDirectManipulationViewport = manager.CreateViewport(None, window)?;
let configuration = DIRECTMANIPULATION_CONFIGURATION_INTERACTION
| DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X
| DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y
| DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA
| DIRECTMANIPULATION_CONFIGURATION_RAILS_X
| DIRECTMANIPULATION_CONFIGURATION_RAILS_Y
| DIRECTMANIPULATION_CONFIGURATION_SCALING;
viewport.ActivateConfiguration(configuration)?;
viewport.SetViewportOptions(
DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE
| DIRECTMANIPULATION_VIEWPORT_OPTIONS_DISABLEPIXELSNAPPING,
)?;
let mut rect = RECT {
left: 0,
top: 0,
right: DEFAULT_VIEWPORT_SIZE,
bottom: DEFAULT_VIEWPORT_SIZE,
};
viewport.SetViewportRect(&mut rect)?;
manager.Activate(window)?;
viewport.Enable()?;
let scale_factor = Rc::new(Cell::new(scale_factor));
let pending_events = Rc::new(RefCell::new(Vec::new()));
let event_handler: IDirectManipulationViewportEventHandler =
DirectManipulationEventHandler::new(
window,
Rc::clone(&scale_factor),
Rc::clone(&pending_events),
)
.into();
let handler_cookie = viewport.AddEventHandler(Some(window), &event_handler)?;
update_manager.Update(None)?;
Ok(Self {
manager,
update_manager,
viewport,
_handler_cookie: handler_cookie,
window,
scale_factor,
pending_events,
})
}
}
pub fn set_scale_factor(&self, scale_factor: f32) {
self.scale_factor.set(scale_factor);
}
pub fn on_pointer_hit_test(&self, wparam: WPARAM) {
unsafe {
let pointer_id = wparam.loword() as u32;
let mut pointer_type = POINTER_INPUT_TYPE::default();
if GetPointerType(pointer_id, &mut pointer_type).is_ok() && pointer_type == PT_TOUCHPAD
{
self.viewport.SetContact(pointer_id).log_err();
}
}
}
pub fn update(&self) {
unsafe {
self.update_manager.Update(None).log_err();
}
}
pub fn drain_events(&self) -> Vec<PlatformInput> {
std::mem::take(&mut *self.pending_events.borrow_mut())
}
}
impl Drop for DirectManipulationHandler {
fn drop(&mut self) {
unsafe {
self.viewport.Stop().log_err();
self.viewport.Abandon().log_err();
self.manager.Deactivate(self.window).log_err();
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum GestureKind {
None,
Scroll,
Pinch,
}
#[windows_core::implement(IDirectManipulationViewportEventHandler)]
struct DirectManipulationEventHandler {
window: HWND,
scale_factor: Rc<Cell<f32>>,
gesture_kind: Cell<GestureKind>,
last_scale: Cell<f32>,
last_x_offset: Cell<f32>,
last_y_offset: Cell<f32>,
scroll_phase: Cell<TouchPhase>,
pending_events: Rc<RefCell<Vec<PlatformInput>>>,
}
impl DirectManipulationEventHandler {
fn new(
window: HWND,
scale_factor: Rc<Cell<f32>>,
pending_events: Rc<RefCell<Vec<PlatformInput>>>,
) -> Self {
Self {
window,
scale_factor,
gesture_kind: Cell::new(GestureKind::None),
last_scale: Cell::new(1.0),
last_x_offset: Cell::new(0.0),
last_y_offset: Cell::new(0.0),
scroll_phase: Cell::new(TouchPhase::Started),
pending_events,
}
}
fn end_gesture(&self) {
let position = self.mouse_position();
let modifiers = current_modifiers();
match self.gesture_kind.get() {
GestureKind::Scroll => {
self.pending_events
.borrow_mut()
.push(PlatformInput::ScrollWheel(ScrollWheelEvent {
position,
delta: ScrollDelta::Pixels(point(px(0.0), px(0.0))),
modifiers,
touch_phase: TouchPhase::Ended,
}));
}
GestureKind::Pinch => {
self.pending_events
.borrow_mut()
.push(PlatformInput::Pinch(PinchEvent {
position,
delta: 0.0,
modifiers,
phase: TouchPhase::Ended,
}));
}
GestureKind::None => {}
}
self.gesture_kind.set(GestureKind::None);
}
fn mouse_position(&self) -> Point<Pixels> {
let scale_factor = self.scale_factor.get();
unsafe {
let mut point: POINT = std::mem::zeroed();
let _ = GetCursorPos(&mut point);
let _ = ScreenToClient(self.window, &mut point);
logical_point(point.x as f32, point.y as f32, scale_factor)
}
}
}
impl IDirectManipulationViewportEventHandler_Impl for DirectManipulationEventHandler_Impl {
fn OnViewportStatusChanged(
&self,
viewport: windows_core::Ref<'_, IDirectManipulationViewport>,
current: DIRECTMANIPULATION_STATUS,
previous: DIRECTMANIPULATION_STATUS,
) -> windows_core::Result<()> {
if current == previous {
return Ok(());
}
// A new gesture interrupted inertia, so end the old sequence.
if current == DIRECTMANIPULATION_RUNNING && previous == DIRECTMANIPULATION_INERTIA {
self.end_gesture();
}
if current == DIRECTMANIPULATION_READY {
self.end_gesture();
// Reset the content transform so the viewport is ready for the next gesture.
// ZoomToRect triggers a second RUNNING -> READY cycle, so prevent an infinite loop here.
if self.last_scale.get() != 1.0
|| self.last_x_offset.get() != 0.0
|| self.last_y_offset.get() != 0.0
{
if let Some(viewport) = viewport.as_ref() {
unsafe {
viewport
.ZoomToRect(
0.0,
0.0,
DEFAULT_VIEWPORT_SIZE as f32,
DEFAULT_VIEWPORT_SIZE as f32,
false,
)
.log_err();
}
}
}
self.last_scale.set(1.0);
self.last_x_offset.set(0.0);
self.last_y_offset.set(0.0);
}
Ok(())
}
fn OnViewportUpdated(
&self,
_viewport: windows_core::Ref<'_, IDirectManipulationViewport>,
) -> windows_core::Result<()> {
Ok(())
}
fn OnContentUpdated(
&self,
_viewport: windows_core::Ref<'_, IDirectManipulationViewport>,
content: windows_core::Ref<'_, IDirectManipulationContent>,
) -> windows_core::Result<()> {
let content = content.as_ref().ok_or(E_POINTER)?;
// Get the 6-element content transform: [scale, 0, 0, scale, tx, ty]
let mut xform = [0.0f32; 6];
unsafe {
content.GetContentTransform(&mut xform)?;
}
let scale = xform[0];
let scale_factor = self.scale_factor.get();
let x_offset = xform[4] / scale_factor;
let y_offset = xform[5] / scale_factor;
if scale == 0.0 {
return Ok(());
}
let last_scale = self.last_scale.get();
let last_x = self.last_x_offset.get();
let last_y = self.last_y_offset.get();
if float_equals(scale, last_scale)
&& float_equals(x_offset, last_x)
&& float_equals(y_offset, last_y)
{
return Ok(());
}
let position = self.mouse_position();
let modifiers = current_modifiers();
// Direct Manipulation reports both translation and scale in every content update.
// Translation values can shift during a pinch due to the zoom center shifting.
// We classify each gesture as either scroll or pinch and only emit one type of event.
// We allow Scroll -> Pinch (a pinch can start with a small pan) but not the reverse.
if !float_equals(scale, 1.0) {
if self.gesture_kind.get() != GestureKind::Pinch {
self.end_gesture();
self.gesture_kind.set(GestureKind::Pinch);
self.pending_events
.borrow_mut()
.push(PlatformInput::Pinch(PinchEvent {
position,
delta: 0.0,
modifiers,
phase: TouchPhase::Started,
}));
}
} else if self.gesture_kind.get() == GestureKind::None {
self.gesture_kind.set(GestureKind::Scroll);
self.scroll_phase.set(TouchPhase::Started);
}
match self.gesture_kind.get() {
GestureKind::Scroll => {
let dx = x_offset - last_x;
let dy = y_offset - last_y;
let touch_phase = self.scroll_phase.get();
self.scroll_phase.set(TouchPhase::Moved);
self.pending_events
.borrow_mut()
.push(PlatformInput::ScrollWheel(ScrollWheelEvent {
position,
delta: ScrollDelta::Pixels(point(px(dx), px(dy))),
modifiers,
touch_phase,
}));
}
GestureKind::Pinch => {
let scale_delta = scale / last_scale;
self.pending_events
.borrow_mut()
.push(PlatformInput::Pinch(PinchEvent {
position,
delta: scale_delta - 1.0,
modifiers,
phase: TouchPhase::Moved,
}));
}
GestureKind::None => {}
}
self.last_scale.set(scale);
self.last_x_offset.set(x_offset);
self.last_y_offset.set(y_offset);
Ok(())
}
}
fn float_equals(f1: f32, f2: f32) -> bool {
const EPSILON_SCALE: f32 = 0.00001;
(f1 - f2).abs() < EPSILON_SCALE * f1.abs().max(f2.abs()).max(EPSILON_SCALE)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,320 @@
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 gpui::{
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTextureList, AtlasTile, Bounds, DevicePixels,
PlatformAtlas, Point, Size,
};
pub(crate) struct DirectXAtlas(Mutex<DirectXAtlasState>);
struct DirectXAtlasState {
device: ID3D11Device,
device_context: ID3D11DeviceContext,
monochrome_textures: AtlasTextureList<DirectXAtlasTexture>,
polychrome_textures: AtlasTextureList<DirectXAtlasTexture>,
subpixel_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(),
subpixel_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.subpixel_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))
} 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);
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,
AtlasTextureKind::Subpixel => &mut lock.subpixel_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);
} 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,
AtlasTextureKind::Subpixel => &mut self.subpixel_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;
}
AtlasTextureKind::Subpixel => {
pixel_format = DXGI_FORMAT_R8G8B8A8_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,
AtlasTextureKind::Subpixel => &mut self.subpixel_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(device_size_to_etagere(size)),
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 {
match id.kind {
AtlasTextureKind::Monochrome => &self.monochrome_textures[id.index as usize]
.as_ref()
.unwrap(),
AtlasTextureKind::Polychrome => &self.polychrome_textures[id.index as usize]
.as_ref()
.unwrap(),
AtlasTextureKind::Subpixel => {
&self.subpixel_textures[id.index as usize].as_ref().unwrap()
}
}
}
}
impl DirectXAtlasTexture {
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
let allocation = self.allocator.allocate(device_size_to_etagere(size))?;
let tile = AtlasTile {
texture_id: self.id,
tile_id: allocation.id.into(),
bounds: Bounds {
origin: etagere_point_to_device(allocation.rectangle.min),
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
}
}
fn device_size_to_etagere(size: Size<DevicePixels>) -> etagere::Size {
etagere::Size::new(size.width.into(), size.height.into())
}
fn etagere_point_to_device(value: etagere::Point) -> Point<DevicePixels> {
Point {
x: DevicePixels::from(value.x),
y: DevicePixels::from(value.y),
}
}

View File

@@ -0,0 +1,194 @@
use anyhow::{Context, Result};
use itertools::Itertools;
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>) -> Result<T> {
(0..5)
.map(|i| {
if i > 0 {
// Add a small delay before retrying
std::thread::sleep(std::time::Duration::from_millis(100 + i * 10));
}
f()
})
.find_or_last(Result::is_ok)
.unwrap()
.context("DirectXRenderer failed to recover from lost device after multiple attempts")
}
#[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, device, device_context, feature_level) =
get_adapter(&dxgi_factory, debug_layer_available).context("Getting DXGI adapter")?;
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!(),
}
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,
ID3D11Device,
ID3D11DeviceContext,
D3D_FEATURE_LEVEL,
)> {
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 and create
// the device if it does.
let mut context: Option<ID3D11DeviceContext> = None;
let mut feature_level = D3D_FEATURE_LEVEL::default();
if let Some(device) = get_device(
&adapter,
Some(&mut context),
Some(&mut feature_level),
debug_layer_available,
)
.log_err()
{
return Ok((adapter, device, context.unwrap(), feature_level));
}
}
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

View File

@@ -0,0 +1,180 @@
use std::{
sync::atomic::{AtomicBool, Ordering},
thread::{ThreadId, current},
time::{Duration, Instant},
};
use anyhow::Context;
use util::ResultExt;
use windows::{
System::Threading::{
ThreadPool, ThreadPoolTimer, TimerElapsedHandler, WorkItemHandler, WorkItemPriority,
},
Win32::{
Foundation::{LPARAM, WPARAM},
Media::{timeBeginPeriod, timeEndPeriod},
System::Threading::{GetCurrentThread, SetThreadPriority, THREAD_PRIORITY_TIME_CRITICAL},
UI::WindowsAndMessaging::PostMessageW,
},
};
use crate::{HWND, SafeHwnd, WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD};
use gpui::{
GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, PriorityQueueSender, RunnableVariant,
TaskTiming, ThreadTaskTimings, TimerResolutionGuard,
};
pub(crate) struct WindowsDispatcher {
pub(crate) wake_posted: AtomicBool,
main_sender: PriorityQueueSender<RunnableVariant>,
main_thread_id: ThreadId,
pub(crate) platform_window_handle: SafeHwnd,
validation_number: usize,
}
impl WindowsDispatcher {
pub(crate) fn new(
main_sender: PriorityQueueSender<RunnableVariant>,
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,
wake_posted: AtomicBool::new(false),
}
}
fn dispatch_on_threadpool(&self, priority: WorkItemPriority, runnable: RunnableVariant) {
let handler = {
let mut task_wrapper = Some(runnable);
WorkItemHandler::new(move |_| {
let runnable = task_wrapper.take().unwrap();
Self::execute_runnable(runnable);
Ok(())
})
};
ThreadPool::RunWithPriorityAsync(&handler, priority).log_err();
}
fn dispatch_on_threadpool_after(&self, runnable: RunnableVariant, duration: Duration) {
let handler = {
let mut task_wrapper = Some(runnable);
TimerElapsedHandler::new(move |_| {
let runnable = task_wrapper.take().unwrap();
Self::execute_runnable(runnable);
Ok(())
})
};
ThreadPoolTimer::CreateTimer(&handler, duration.into()).log_err();
}
#[inline(always)]
pub(crate) fn execute_runnable(runnable: RunnableVariant) {
let start = Instant::now();
let location = runnable.metadata().location;
let mut timing = TaskTiming {
location,
start,
end: None,
};
gpui::profiler::add_task_timing(timing);
runnable.run();
let end = Instant::now();
timing.end = Some(end);
gpui::profiler::add_task_timing(timing);
}
}
impl PlatformDispatcher for WindowsDispatcher {
fn get_all_timings(&self) -> Vec<ThreadTaskTimings> {
let global_thread_timings = GLOBAL_THREAD_TIMINGS.lock();
ThreadTaskTimings::convert(&global_thread_timings)
}
fn get_current_thread_timings(&self) -> gpui::ThreadTaskTimings {
gpui::profiler::get_current_thread_task_timings()
}
fn is_main_thread(&self) -> bool {
current().id() == self.main_thread_id
}
fn dispatch(&self, runnable: RunnableVariant, priority: Priority) {
let priority = match priority {
Priority::RealtimeAudio => {
panic!("RealtimeAudio priority should use spawn_realtime, not dispatch")
}
Priority::High => WorkItemPriority::High,
Priority::Medium => WorkItemPriority::Normal,
Priority::Low => WorkItemPriority::Low,
};
self.dispatch_on_threadpool(priority, runnable);
}
fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority) {
match self.main_sender.send(priority, runnable) {
Ok(_) => {
if !self.wake_posted.swap(true, Ordering::AcqRel) {
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: RunnableVariant) {
self.dispatch_on_threadpool_after(runnable, duration);
}
fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>) {
std::thread::spawn(move || {
// SAFETY: always safe to call
let thread_handle = unsafe { GetCurrentThread() };
// SAFETY: thread_handle is a valid handle to the current thread
unsafe { SetThreadPriority(thread_handle, THREAD_PRIORITY_TIME_CRITICAL) }
.context("thread priority")
.log_err();
f();
});
}
fn increase_timer_resolution(&self) -> TimerResolutionGuard {
unsafe {
timeBeginPeriod(1);
}
util::defer(Box::new(|| unsafe {
timeEndPeriod(1);
}))
}
}

View File

@@ -0,0 +1,204 @@
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::logical_point;
use gpui::{Bounds, DevicePixels, DisplayId, Pixels, PlatformDisplay, point, size};
#[derive(Debug, Clone, Copy)]
pub(crate) struct WindowsDisplay {
pub handle: HMONITOR,
pub display_id: DisplayId,
scale_factor: f32,
bounds: Bounds<Pixels>,
visible_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 handle = HMONITOR(u64::from(display_id) as _);
let info = get_monitor_info(handle).log_err()?;
let monitor_size = info.monitorInfo.rcMonitor;
let work_area = info.monitorInfo.rcWork;
let uuid = generate_uuid(&info.szDevice);
let scale_factor = get_scale_factor_for_monitor(handle).log_err()?;
let physical_size = size(
(monitor_size.right - monitor_size.left).into(),
(monitor_size.bottom - monitor_size.top).into(),
);
Some(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),
},
visible_bounds: Bounds {
origin: logical_point(work_area.left as f32, work_area.top as f32, scale_factor),
size: size(
(work_area.right - work_area.left) as f32 / scale_factor,
(work_area.bottom - work_area.top) as f32 / scale_factor,
)
.map(gpui::px),
},
physical_bounds: Bounds {
origin: point(monitor_size.left.into(), monitor_size.top.into()),
size: physical_size,
},
uuid,
})
}
pub(crate) fn display_id_for_monitor(monitor: HMONITOR) -> DisplayId {
DisplayId::new(monitor.0 as u64)
}
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;
}
WindowsDisplay::new(Self::display_id_for_monitor(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.as_f32() * self.scale_factor) as i32,
y: (center.y.as_f32() * self.scale_factor) as i32,
};
let monitor = unsafe { MonitorFromPoint(center, MONITOR_DEFAULTTONULL) };
if monitor.is_invalid() {
false
} else {
let Some(display) = WindowsDisplay::new(Self::display_id_for_monitor(monitor)) else {
return false;
};
display.uuid == self.uuid
}
}
pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
available_monitors()
.into_iter()
.filter_map(|handle| {
Some(
Rc::new(WindowsDisplay::new(Self::display_id_for_monitor(handle))?)
as Rc<dyn PlatformDisplay>,
)
})
.collect()
}
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 visible_bounds(&self) -> Bounds<Pixels> {
self.visible_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

View File

@@ -0,0 +1,40 @@
#![cfg(target_os = "windows")]
mod clipboard;
mod destination_list;
mod direct_manipulation;
mod direct_write;
mod directx_atlas;
mod directx_devices;
mod directx_renderer;
mod dispatcher;
mod display;
mod events;
mod keyboard;
mod platform;
mod system_settings;
mod util;
mod vsync;
mod window;
mod wrapper;
pub(crate) use clipboard::*;
pub(crate) use destination_list::*;
pub(crate) use direct_write::*;
pub(crate) use directx_atlas::*;
pub(crate) use directx_devices::*;
pub(crate) use directx_renderer::*;
pub(crate) use dispatcher::*;
pub(crate) use display::*;
pub(crate) use events::*;
pub(crate) use keyboard::*;
pub(crate) use platform::*;
pub(crate) use system_settings::*;
pub(crate) use util::*;
pub(crate) use vsync::*;
pub(crate) use window::*;
pub(crate) use wrapper::*;
pub use platform::WindowsPlatform;
pub(crate) use windows::Win32::Foundation::HWND;

View File

@@ -0,0 +1,371 @@
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 gpui::{
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]; // KL_NAMELENGTH includes the null terminator
unsafe { GetKeyboardLayoutNameW(&mut buffer)? };
let id = String::from_utf16_lossy(&buffer[..buffer.len() - 1]); // Remove the null terminator
let entry = windows_registry::LOCAL_MACHINE.open(format!(
"System\\CurrentControlSet\\Control\\Keyboard Layouts\\{id}"
))?;
let name = entry.get_string("Layout Text")?;
Ok(Self { id, name })
}
pub(crate) fn unknown() -> Self {
Self {
id: "unknown".to_string(),
name: "unknown".to_string(),
}
}
}
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, 0x5) };
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::WindowsKeyboardMapper;
use gpui::{Keystroke, Modifiers, PlatformKeyboardMapper};
#[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

View File

@@ -0,0 +1,87 @@
use std::{
cell::Cell,
ffi::{c_uint, c_void},
};
use ::util::ResultExt;
use windows::Win32::UI::WindowsAndMessaging::{
SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SYSTEM_PARAMETERS_INFO_ACTION,
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, SystemParametersInfoW,
};
/// Windows settings pulled from SystemParametersInfo
/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow
#[derive(Default, Debug, Clone)]
pub(crate) struct WindowsSystemSettings {
pub(crate) mouse_wheel_settings: MouseWheelSettings,
}
#[derive(Default, Debug, Clone)]
pub(crate) struct MouseWheelSettings {
/// SEE: SPI_GETWHEELSCROLLCHARS
pub(crate) wheel_scroll_chars: Cell<u32>,
/// SEE: SPI_GETWHEELSCROLLLINES
pub(crate) wheel_scroll_lines: Cell<u32>,
}
impl WindowsSystemSettings {
pub(crate) fn new() -> Self {
let mut settings = Self::default();
settings.init();
settings
}
fn init(&mut self) {
self.mouse_wheel_settings.update();
}
pub(crate) fn update(&self, wparam: usize) {
match SYSTEM_PARAMETERS_INFO_ACTION(wparam as u32) {
SPI_GETWHEELSCROLLLINES | SPI_GETWHEELSCROLLCHARS => self.update_mouse_wheel_settings(),
_ => {}
}
}
fn update_mouse_wheel_settings(&self) {
self.mouse_wheel_settings.update();
}
}
impl MouseWheelSettings {
fn update(&self) {
self.update_wheel_scroll_chars();
self.update_wheel_scroll_lines();
}
fn update_wheel_scroll_chars(&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.get() != value {
self.wheel_scroll_chars.set(value);
}
}
fn update_wheel_scroll_lines(&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.get() != value {
self.wheel_scroll_lines.set(value);
}
}
}

View File

@@ -0,0 +1,191 @@
use std::sync::OnceLock;
use ::util::ResultExt;
use anyhow::Context;
use windows::{
UI::{
Color,
ViewManagement::{UIColorType, UISettings},
},
Win32::{
Foundation::*, Graphics::Dwm::*, System::LibraryLoader::LoadLibraryA,
UI::WindowsAndMessaging::*,
},
core::{BOOL, PCSTR},
};
use crate::*;
use gpui::*;
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 SIZENWSE: OnceLock<SafeCursor> = OnceLock::new();
static SIZENESW: 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::ResizeUpLeftDownRight => (&SIZENWSE, IDC_SIZENWSE),
CursorStyle::ResizeUpRightDownLeft => (&SIZENESW, IDC_SIZENESW),
CursorStyle::OperationNotAllowed => (&NO, IDC_NO),
_ => (&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 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
}

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

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
}
}