From 09479941ff36a1613ff5783660df76f8cbd55ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sat, 10 Oct 2026 20:12:28 -0400 Subject: [PATCH] Vendor gpui-pre-windows 0.3.8 unmodified (crates.io source) Co-Authored-By: Claude Opus 5.5 --- vendor/gpui-pre-windows/Cargo.lock | 4882 +++++++++++++++++ vendor/gpui-pre-windows/Cargo.toml | 243 + vendor/gpui-pre-windows/Cargo.toml.orig | 59 + vendor/gpui-pre-windows/LICENSE-APACHE | 222 + vendor/gpui-pre-windows/build.rs | 242 + .../src/alpha_correction.hlsl | 49 + vendor/gpui-pre-windows/src/clipboard.rs | 394 ++ .../src/color_text_raster.hlsl | 44 + .../gpui-pre-windows/src/destination_list.rs | 207 + vendor/gpui-pre-windows/src/dialog.rs | 340 ++ .../src/direct_manipulation.rs | 359 ++ vendor/gpui-pre-windows/src/direct_write.rs | 2170 ++++++++ vendor/gpui-pre-windows/src/directx_atlas.rs | 420 ++ .../gpui-pre-windows/src/directx_devices.rs | 194 + .../gpui-pre-windows/src/directx_renderer.rs | 2070 +++++++ vendor/gpui-pre-windows/src/dispatcher.rs | 191 + vendor/gpui-pre-windows/src/display.rs | 210 + vendor/gpui-pre-windows/src/events.rs | 1812 ++++++ vendor/gpui-pre-windows/src/gpui_windows.rs | 43 + vendor/gpui-pre-windows/src/keyboard.rs | 371 ++ vendor/gpui-pre-windows/src/platform.rs | 1939 +++++++ vendor/gpui-pre-windows/src/shaders.hlsl | 1268 +++++ .../src/system_notifications.rs | 198 + .../gpui-pre-windows/src/system_settings.rs | 87 + vendor/gpui-pre-windows/src/util.rs | 191 + vendor/gpui-pre-windows/src/vsync.rs | 119 + vendor/gpui-pre-windows/src/window.rs | 1735 ++++++ vendor/gpui-pre-windows/src/wrapper.rs | 53 + 28 files changed, 20112 insertions(+) create mode 100644 vendor/gpui-pre-windows/Cargo.lock create mode 100644 vendor/gpui-pre-windows/Cargo.toml create mode 100644 vendor/gpui-pre-windows/Cargo.toml.orig create mode 100644 vendor/gpui-pre-windows/LICENSE-APACHE create mode 100644 vendor/gpui-pre-windows/build.rs create mode 100644 vendor/gpui-pre-windows/src/alpha_correction.hlsl create mode 100644 vendor/gpui-pre-windows/src/clipboard.rs create mode 100644 vendor/gpui-pre-windows/src/color_text_raster.hlsl create mode 100644 vendor/gpui-pre-windows/src/destination_list.rs create mode 100644 vendor/gpui-pre-windows/src/dialog.rs create mode 100644 vendor/gpui-pre-windows/src/direct_manipulation.rs create mode 100644 vendor/gpui-pre-windows/src/direct_write.rs create mode 100644 vendor/gpui-pre-windows/src/directx_atlas.rs create mode 100644 vendor/gpui-pre-windows/src/directx_devices.rs create mode 100644 vendor/gpui-pre-windows/src/directx_renderer.rs create mode 100644 vendor/gpui-pre-windows/src/dispatcher.rs create mode 100644 vendor/gpui-pre-windows/src/display.rs create mode 100644 vendor/gpui-pre-windows/src/events.rs create mode 100644 vendor/gpui-pre-windows/src/gpui_windows.rs create mode 100644 vendor/gpui-pre-windows/src/keyboard.rs create mode 100644 vendor/gpui-pre-windows/src/platform.rs create mode 100644 vendor/gpui-pre-windows/src/shaders.hlsl create mode 100644 vendor/gpui-pre-windows/src/system_notifications.rs create mode 100644 vendor/gpui-pre-windows/src/system_settings.rs create mode 100644 vendor/gpui-pre-windows/src/util.rs create mode 100644 vendor/gpui-pre-windows/src/vsync.rs create mode 100644 vendor/gpui-pre-windows/src/window.rs create mode 100644 vendor/gpui-pre-windows/src/wrapper.rs diff --git a/vendor/gpui-pre-windows/Cargo.lock b/vendor/gpui-pre-windows/Cargo.lock new file mode 100644 index 0000000..110b0fe --- /dev/null +++ b/vendor/gpui-pre-windows/Cargo.lock @@ -0,0 +1,4882 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "accesskit" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" +dependencies = [ + "enumn", + "uuid", +] + +[[package]] +name = "accesskit_consumer" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106c2b961215864d1c2e703ee63269c25c4e80a577ffb2c1017b9c17dcdf83a1" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "static_assertions", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-core", + "futures-io", + "pin-project-lite", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.17", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.117", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bitstream-io" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +dependencies = [ + "core2", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cc" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "compression-codecs" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" +dependencies = [ + "bzip2", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-helmer-fork" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "core-graphics2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4416167a69126e617f8d0a214af0e3c1dbdeffcb100ddf72dcd1a1ac9893c146" +dependencies = [ + "bitflags 2.13.1", + "block", + "cfg-if", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "core-text" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" +dependencies = [ + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139679cc63eb9504bdbe37e37874b0247136177655f0008588781e90863afa62" +dependencies = [ + "block", + "core-foundation 0.10.0", + "core-graphics2", + "io-surface", + "libc", + "metal", +] + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "ctor" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d83cb7e7a873830708d6b02a78cd36a592c6fa14bf267b68725103b85c0d77f" +dependencies = [ + "link-section", + "linktime-proc-macro", +] + +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embed-resource" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "fastrand", + "futures-core", + "futures-sink", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project", + "smallvec", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gpui-pre" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f0bc3ebba070f8981b3f8e905a12f47d27794a30d029e59cdb9edf7a4b3360" +dependencies = [ + "accesskit", + "anyhow", + "async-channel", + "async-task", + "backtrace", + "bindgen", + "bitflags 2.13.1", + "chrono", + "core-video", + "ctor", + "derive_more", + "embed-resource", + "etagere", + "futures", + "futures-concurrency", + "getrandom 0.3.4", + "gpui-pre-collections", + "gpui-pre-http-client", + "gpui-pre-macros", + "gpui-pre-refineable", + "gpui-pre-scheduler", + "gpui-pre-shared-string", + "gpui-pre-sum-tree", + "gpui-pre-util", + "gpui-pre-util-macros", + "heapless", + "image", + "inventory", + "itertools 0.14.0", + "libc", + "log", + "lyon", + "num_cpus", + "objc2-core-foundation", + "objc2-core-video", + "parking", + "parking_lot", + "pin-project", + "pollster 0.4.0", + "postage", + "profiling", + "proptest", + "rand 0.9.4", + "raw-window-handle", + "regex", + "resvg", + "schemars", + "seahash", + "serde", + "serde_json", + "slotmap", + "smallvec", + "spin 0.10.0", + "strum", + "taffy", + "thiserror 2.0.17", + "ttf-parser", + "url", + "usvg", + "uuid", + "waker-fn", + "web-time", + "windows 0.62.2", + "zed-font-kit", + "zed-scap", +] + +[[package]] +name = "gpui-pre-collections" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c060863025dc3b8e90febad2d1f4ffd23a5d0dcedba39d3f6b7820ea2fdd3a0" +dependencies = [ + "gpui-pre-util", + "indexmap", + "rustc-hash", +] + +[[package]] +name = "gpui-pre-derive-refineable" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc834f30f5f49791e214f1fde01a009c7de91c9cd0a9f31dabfed130ba39e6cf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "gpui-pre-http-client" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2935cf82333da3f41774e5757837756408f01356197b41c817ad2666034453" +dependencies = [ + "anyhow", + "async-compression", + "bytes", + "derive_more", + "futures", + "http", + "http-body", + "log", + "parking_lot", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + +[[package]] +name = "gpui-pre-macros" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad26ca759364694cb55487bebff4c1b7dd6accab49990ff3cda6f7a818a9f4" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "gpui-pre-perf" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcb288a506a476b6cbf97e6ec1cc0118db43f0ca91087588afa1c2d8cd4f842" +dependencies = [ + "gpui-pre-collections", + "serde", + "serde_json", +] + +[[package]] +name = "gpui-pre-refineable" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4042345bb5bd3ecbc5d15b21ed128154f462a992058d7038cd397631f5462338" +dependencies = [ + "gpui-pre-derive-refineable", +] + +[[package]] +name = "gpui-pre-scheduler" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4acd73345bc3c09f1b98c1b21eb4459ad8336f89bb2edcef619e11c5a08456e" +dependencies = [ + "async-task", + "backtrace", + "chrono", + "flume", + "futures", + "parking_lot", + "rand 0.9.4", + "web-time", +] + +[[package]] +name = "gpui-pre-shared-string" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "477529fd5aa90bff969785ee9a3938484d94aa5101cdf7b84719629c0345f98a" +dependencies = [ + "schemars", + "serde", + "smol_str", +] + +[[package]] +name = "gpui-pre-sum-tree" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec0ec176d576b82aed716cf47185856b54ea9528caaf9d01787024689bfd824" +dependencies = [ + "gpui-pre-ztracing", + "heapless", + "log", + "rayon", + "tracing", +] + +[[package]] +name = "gpui-pre-util" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1635ae7d0b05400bfa211ddef6c67dc8d9f223c463022c2b3bf2c1c14c6044f" +dependencies = [ + "anyhow", + "log", + "which", +] + +[[package]] +name = "gpui-pre-util-macros" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01486fce7722e5d7d7e7bb71c719dfdcc6ed917112bdc7c56d0556fdb60d10ab" +dependencies = [ + "gpui-pre-perf", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "gpui-pre-windows" +version = "0.3.8" +dependencies = [ + "accesskit", + "accesskit_windows", + "anyhow", + "dunce", + "etagere", + "futures", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-scheduler", + "gpui-pre-util", + "image", + "itertools 0.14.0", + "log", + "parking_lot", + "rand 0.9.4", + "raw-window-handle", + "smallvec", + "uuid", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-numerics 0.3.1", + "windows-registry", + "zed-scap", +] + +[[package]] +name = "gpui-pre-zlog" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bbe5b98630ef4bfed5a7105ec1062e78d571912bc491388f07ea22f2457c18b" +dependencies = [ + "anyhow", + "chrono", + "gpui-pre-collections", + "log", +] + +[[package]] +name = "gpui-pre-ztracing" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f806400f6bda1fdd9fc627e158c0e44fb0c4b7767582a3621efcf92f4a850db7" +dependencies = [ + "gpui-pre-zlog", + "gpui-pre-ztracing-macro", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "gpui-pre-ztracing-macro" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d337c9acf058238e45ce0570d8a5958439242889492f91ae2d8e324211a61ccd" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.0", + "qoi", + "ravif", + "rayon", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error 2.0.1", +] + +[[package]] +name = "imagesize" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "inventory" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-surface" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" +dependencies = [ + "cgl", + "core-foundation 0.10.0", + "core-foundation-sys", + "leaky-cow", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +dependencies = [ + "arrayvec", + "euclid", + "polycool", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" + +[[package]] +name = "leaky-cow" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" +dependencies = [ + "leak", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.13.1", + "libc", +] + +[[package]] +name = "link-section" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee1a0d6e252afe82e7bc2db42fba60e02ddf3b1accaf8cb21d96e34ba61f3d4" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348d0075b1fc163b26d72a7f75fc5141daf2fd1bdf128d873cbaf6785d495bdf" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "serde_core", + "value-bag", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lyon" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcb7d54d54c8937364c9d41902d066656817dce1e03a44e5533afebd1ef4352" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c0829e28c4f336396f250d850c3987e16ce6db057ffe047ce0dd54aab6b647" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e16770d760c7848b0c1c2d209101e408207a65168109509f8483837a36cf2e7" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aeca86bcfd632a15984ba029b539ffb811e0a70bf55e814ef8b0f54f506fdeb" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f586142e1280335b1bc89539f7c97dd80f08fc43e9ab1b74ef0a42b04aa353" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +dependencies = [ + "libc", +] + +[[package]] +name = "metal" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-graphics-types 0.2.0", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "objc2-core-foundation", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "pollster" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic", + "crossbeam-queue", + "futures", + "log", + "parking_lot", + "pin-project", + "pollster 0.2.5", + "static_assertions", + "thiserror 1.0.69", +] + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.13.1", + "num-traits", + "proptest-macro", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "proptest-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efaa288b896cb2b345da7b7f2110ab19e51565b83495b56fcec98a62f8b1f33e" +dependencies = [ + "convert_case 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pxfm" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.4", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.17", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error 2.0.1", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "resvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" +dependencies = [ + "gif", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", + "zune-jpeg", +] + +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "indexmap", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "screencapturekit" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" +dependencies = [ + "screencapturekit-sys", +] + +[[package]] +name = "screencapturekit-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" +dependencies = [ + "block", + "dispatch", + "objc", + "objc-foundation", + "objc_id", + "once_cell", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_fmt" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "serde_core", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sval" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d94c4464e595f0284970fd9c7e9013804d035d4a61ab74b113242c874c05814d" + +[[package]] +name = "sval_buffer" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f46e34b20a39e6a2bf02b926983149b3af6609fd1ee8a6e63f6f340f3e2164" +dependencies = [ + "sval", + "sval_ref", +] + +[[package]] +name = "sval_dynamic" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d0970e53c92ab5381d3b2db1828da8af945954d4234225f6dd9c3afbcef3f5" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e5e6e1613e1e7fc2e1a9fdd709622e54c122ceb067a60d170d75efd491a839" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aec382f7bfa6e367b23c9611f129b94eb7daaf3d8fae45a8d0a0211eb4d4c8e6" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3049d0f99ce6297f8f7d9953b35a0103b7584d8f638de40e64edb7105fa578ae" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f88913e77506085c0a8bf6912bb6558591a960faf5317df6c1d9b227224ca6e1" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f579fd7254f4be6cd7b450034f856b78523404655848789c451bacc6aa8b387d" +dependencies = [ + "serde_core", + "sval", + "sval_nested", +] + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "svgtypes" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" +dependencies = [ + "kurbo", + "siphasher", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "taffy" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c034e05f6ee85a12daa63863c2245797715075c70649947aa0da54f3f2ab1d0f" +dependencies = [ + "arrayvec", + "serde", + "slotmap", + "smallvec", +] + +[[package]] +name = "tao-core-video-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "objc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.1", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error 2.0.1", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[package]] +name = "tracing" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "usvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" +dependencies = [ + "base64", + "data-url", + "flate2", + "fontdb", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree 0.21.1", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16530907bfe2999a1773ca5900a65101e092c70f642f25cc23ca0c43573262c5" +dependencies = [ + "erased-serde", + "serde_core", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00ae130edd690eaa877e4f40605d534790d1cf1d651e7685bd6a144521b251f" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.13.1", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[package]] +name = "which" +version = "8.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-capture" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" +dependencies = [ + "parking_lot", + "rayon", + "thiserror 2.0.17", + "windows 0.61.3", + "windows-future 0.2.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.13.1", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "xcb" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f07c123b796139bfe0603e654eaf08e132e52387ba95b252c78bad3640ba37ea" +dependencies = [ + "bitflags 1.3.2", + "libc", + "quick-xml", + "x11", +] + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zed-font-kit" +version = "0.14.1-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3898e450f36f852edda72e3f985c34426042c4951790b23b107f93394f9bff5" +dependencies = [ + "bitflags 2.13.1", + "byteorder", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "zed-scap" +version = "0.0.8-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b338d705ae33a43ca00287c11129303a7a0aa57b101b72a1c08c863f698ac8" +dependencies = [ + "anyhow", + "cocoa", + "core-graphics-helmer-fork", + "log", + "objc", + "rand 0.8.6", + "screencapturekit", + "screencapturekit-sys", + "sysinfo", + "tao-core-video-sys", + "windows 0.61.3", + "windows-capture", + "x11", + "xcb", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f212a141d820099d57ffafb9569be9617a6f27d3dc881fbee8fb56642f917a9" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/vendor/gpui-pre-windows/Cargo.toml b/vendor/gpui-pre-windows/Cargo.toml new file mode 100644 index 0000000..52a60ac --- /dev/null +++ b/vendor/gpui-pre-windows/Cargo.toml @@ -0,0 +1,243 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2024" +name = "gpui-pre-windows" +version = "0.3.8" +build = "build.rs" +publish = true +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Zed's `gpui_windows` crate (gpui-pre snapshot of zed@279fe07)" +readme = false +license = "Apache-2.0" +repository = "https://github.com/zed-industries/zed" +resolver = "2" + +[package.metadata.cargo-shear] +ignored = ["scap"] + +[package.metadata.gpui-pre] +zed-crate = "gpui_windows" +zed-version = "0.1.0" +zed-rev = "279fe070bb389b79652e52065b2f001edcc0b11b" + +[features] +default = ["gpui/default"] +screen-capture = [ + "gpui/screen-capture", + "scap", +] +test-support = ["gpui/test-support"] + +[lib] +name = "gpui_windows" +path = "src/gpui_windows.rs" +doctest = false + +[dependencies.gpui] +version = "=0.3.8" +default-features = false +package = "gpui-pre" + +[target.'cfg(target_os = "windows")'.dependencies.accesskit] +version = "0.24.0" +features = ["enumn"] + +[target.'cfg(target_os = "windows")'.dependencies.accesskit_windows] +version = "0.34" + +[target.'cfg(target_os = "windows")'.dependencies.anyhow] +version = "1.0.86" + +[target.'cfg(target_os = "windows")'.dependencies.collections] +version = "=0.3.8" +package = "gpui-pre-collections" + +[target.'cfg(target_os = "windows")'.dependencies.dunce] +version = "1.0" + +[target.'cfg(target_os = "windows")'.dependencies.etagere] +version = "0.2" + +[target.'cfg(target_os = "windows")'.dependencies.futures] +version = "0.3.34" + +[target.'cfg(target_os = "windows")'.dependencies.gpui_util] +version = "=0.3.8" +package = "gpui-pre-util" + +[target.'cfg(target_os = "windows")'.dependencies.image] +version = "0.25.1" +features = [ + "bmp", + "dds", + "exr", + "ff", + "gif", + "hdr", + "ico", + "jpeg", + "png", + "pnm", + "qoi", + "rayon", + "tga", + "tiff", + "webp", +] +default-features = false + +[target.'cfg(target_os = "windows")'.dependencies.itertools] +version = "0.14.0" + +[target.'cfg(target_os = "windows")'.dependencies.log] +version = "0.4.16" +features = [ + "kv_unstable_serde", + "serde", +] + +[target.'cfg(target_os = "windows")'.dependencies.parking_lot] +version = "0.12.1" + +[target.'cfg(target_os = "windows")'.dependencies.rand] +version = "0.9" + +[target.'cfg(target_os = "windows")'.dependencies.raw-window-handle] +version = "0.6" + +[target.'cfg(target_os = "windows")'.dependencies.scap] +version = "0.0.8-zed" +optional = true +default-features = false +package = "zed-scap" + +[target.'cfg(target_os = "windows")'.dependencies.scheduler] +version = "=0.3.8" +package = "gpui-pre-scheduler" + +[target.'cfg(target_os = "windows")'.dependencies.smallvec] +version = "1.6" +features = [ + "union", + "const_new", +] + +[target.'cfg(target_os = "windows")'.dependencies.uuid] +version = "1.1.2" +features = [ + "v4", + "v5", + "v7", + "serde", +] + +[target.'cfg(target_os = "windows")'.dependencies.windows] +version = "0.62" +features = [ + "Data_Xml_Dom", + "Foundation_Numerics", + "Globalization_DateTimeFormatting", + "Storage_Search", + "Storage_Streams", + "System_Threading", + "UI_Notifications", + "UI_ViewManagement", + "Wdk_System_SystemServices", + "Win32_Foundation", + "Win32_Globalization", + "Win32_Graphics_Direct3D", + "Win32_Graphics_Direct3D11", + "Win32_Graphics_Direct3D_Fxc", + "Win32_Graphics_DirectComposition", + "Win32_Graphics_DirectWrite", + "Win32_Graphics_DirectManipulation", + "Win32_Graphics_Dwm", + "Win32_Graphics_Dxgi", + "Win32_Graphics_Dxgi_Common", + "Win32_Graphics_Gdi", + "Win32_Graphics_Imaging", + "Win32_Graphics_Hlsl", + "Win32_Networking_WinSock", + "Win32_Security", + "Win32_Security_Credentials", + "Win32_Security_Cryptography", + "Win32_Storage_FileSystem", + "Win32_Storage_Packaging_Appx", + "Win32_System_Com", + "Win32_System_Com_StructuredStorage", + "Win32_System_Console", + "Win32_System_Diagnostics_Debug", + "Win32_System_DataExchange", + "Win32_System_IO", + "Win32_System_JobObjects", + "Win32_System_LibraryLoader", + "Win32_System_Memory", + "Win32_System_Ole", + "Win32_System_Performance", + "Win32_System_Pipes", + "Win32_System_Power", + "Win32_System_RestartManager", + "Win32_System_SystemInformation", + "Win32_System_SystemServices", + "Win32_System_Threading", + "Win32_System_Variant", + "Win32_System_WinRT", + "Win32_UI_Controls", + "Win32_UI_HiDpi", + "Win32_UI_Input_Ime", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_Input_Pointer", + "Win32_UI_Shell", + "Win32_UI_Shell_Common", + "Win32_UI_Shell_PropertiesSystem", + "Win32_UI_WindowsAndMessaging", + "Win32_Media", + "Win32_System_StationsAndDesktops", +] + +[target.'cfg(target_os = "windows")'.dependencies.windows-core] +version = "0.62" + +[target.'cfg(target_os = "windows")'.dependencies.windows-numerics] +version = "0.3" + +[target.'cfg(target_os = "windows")'.dependencies.windows-registry] +version = "0.6.0" + +[target.'cfg(target_os = "windows")'.build-dependencies.windows-registry] +version = "0.6.0" + +[lints.clippy] +dbg_macro = "deny" +declare_interior_mutable_const = "deny" +disallowed_methods = "deny" +large_enum_variant = "allow" +let_underscore_future = "allow" +nonminimal_bool = "allow" +redundant_clone = "deny" +single_range_in_vec_init = "allow" +todo = "deny" +too_many_arguments = "allow" +type_complexity = "allow" + +[lints.clippy.style] +level = "allow" +priority = -1 + +[lints.rust.unexpected_cfgs] +level = "allow" +priority = 0 diff --git a/vendor/gpui-pre-windows/Cargo.toml.orig b/vendor/gpui-pre-windows/Cargo.toml.orig new file mode 100644 index 0000000..8c3320e --- /dev/null +++ b/vendor/gpui-pre-windows/Cargo.toml.orig @@ -0,0 +1,59 @@ +[package] +name = "gpui-pre-windows" +version = "0.3.8" +edition = { workspace = true } +publish = true +license = "Apache-2.0" +description = "Zed's `gpui_windows` crate (gpui-pre snapshot of zed@279fe07)" +repository = "https://github.com/zed-industries/zed" + +[package.metadata.cargo-shear] +ignored = ["scap"] + +[package.metadata.gpui-pre] +zed-crate = "gpui_windows" +zed-version = "0.1.0" +zed-rev = "279fe070bb389b79652e52065b2f001edcc0b11b" + +[lib] +path = "src/gpui_windows.rs" +doctest = false +name = "gpui_windows" + +[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] +accesskit = { workspace = true } +accesskit_windows = { workspace = true } +anyhow = { workspace = true } +collections = { workspace = true } +dunce = { workspace = true } +etagere = { workspace = true } +futures = { workspace = true } +gpui_util = { workspace = true } +image = { workspace = true } +itertools = { workspace = true } +log = { workspace = true } +parking_lot = { workspace = true } +rand = { workspace = true } +raw-window-handle = { workspace = true } +scheduler = { workspace = true } +smallvec = { workspace = true } +uuid = { workspace = true } +windows = { workspace = true, features = ["Win32_System_StationsAndDesktops"] } +windows-core = { workspace = true } +windows-numerics = "0.3" +windows-registry = { workspace = true } +scap = { workspace = true, optional = true } + +[target.'cfg(target_os = "windows")'.build-dependencies] +windows-registry = { workspace = true } + +[lints] +workspace = true diff --git a/vendor/gpui-pre-windows/LICENSE-APACHE b/vendor/gpui-pre-windows/LICENSE-APACHE new file mode 100644 index 0000000..461a0fe --- /dev/null +++ b/vendor/gpui-pre-windows/LICENSE-APACHE @@ -0,0 +1,222 @@ +Copyright 2022 - 2025 Zed Industries, Inc. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + + http://www.apache.org/licenses/LICENSE-2.0 + + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + + 1. Definitions. + + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + + END OF TERMS AND CONDITIONS diff --git a/vendor/gpui-pre-windows/build.rs b/vendor/gpui-pre-windows/build.rs new file mode 100644 index 0000000..1db9571 --- /dev/null +++ b/vendor/gpui-pre-windows/build.rs @@ -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, Box> { + 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::>() + }); + + 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; diff --git a/vendor/gpui-pre-windows/src/alpha_correction.hlsl b/vendor/gpui-pre-windows/src/alpha_correction.hlsl new file mode 100644 index 0000000..5a34a4e --- /dev/null +++ b/vendor/gpui-pre-windows/src/alpha_correction.hlsl @@ -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); +} diff --git a/vendor/gpui-pre-windows/src/clipboard.rs b/vendor/gpui-pre-windows/src/clipboard.rs new file mode 100644 index 0000000..11eb2a8 --- /dev/null +++ b/vendor/gpui-pre-windows/src/clipboard.rs @@ -0,0 +1,394 @@ +use std::{borrow::Cow, 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 = + LazyLock::new(|| register_clipboard_format(windows::core::w!("GPUI internal text hash"))); +static CLIPBOARD_METADATA_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("GPUI internal metadata"))); +static CLIPBOARD_SVG_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("image/svg+xml"))); +static CLIPBOARD_GIF_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("GIF"))); +static CLIPBOARD_PNG_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("PNG"))); +static CLIPBOARD_JPG_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("JFIF"))); + +static IMAGE_FORMATS_MAP: LazyLock> = 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 { + 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 { + 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(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(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 { + let locked = get_clipboard_data(format)?; + let bytes = locked.as_bytes(); + let words_len = bytes.len() / std::mem::size_of::(); + 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<()> { + // CF_UNICODETEXT is null-terminated, so replace embedded NUL characters with spaces. + let text = if item.text.contains('\0') { + Cow::Owned(item.text.replace('\0', " ")) + } else { + Cow::Borrowed(item.text.as_str()) + }; + let wide: Vec = 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(&text).to_ne_bytes(); + set_clipboard_bytes(&hash_bytes, *CLIPBOARD_HASH_FORMAT)?; + + let wide: Vec = 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> { + 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 { + 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 { + 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 { + 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 { + 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> { + 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 { + 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 { + 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 { + 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() }; + } +} diff --git a/vendor/gpui-pre-windows/src/color_text_raster.hlsl b/vendor/gpui-pre-windows/src/color_text_raster.hlsl new file mode 100644 index 0000000..2fbc156 --- /dev/null +++ b/vendor/gpui-pre-windows/src/color_text_raster.hlsl @@ -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 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); +} diff --git a/vendor/gpui-pre-windows/src/destination_list.rs b/vendor/gpui-pre-windows/src/destination_list.rs new file mode 100644 index 0000000..d6967c0 --- /dev/null +++ b/vendor/gpui-pre-windows/src/destination_list.rs @@ -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, + 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, +} + +impl DockMenuItem { + pub(crate) fn new(item: MenuItem) -> anyhow::Result { + 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>> { + 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>)> +{ + 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>, +) -> 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::>() + .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, + display: &str, +) -> anyhow::Result { + 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) + } +} diff --git a/vendor/gpui-pre-windows/src/dialog.rs b/vendor/gpui-pre-windows/src/dialog.rs new file mode 100644 index 0000000..16866cf --- /dev/null +++ b/vendor/gpui-pre-windows/src/dialog.rs @@ -0,0 +1,340 @@ +use std::{ + cell::RefCell, + rc::Rc, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, +}; + +use anyhow::{Context as _, Result, anyhow}; +use futures::{ + channel::oneshot, + future::{Either, select}, + lock::Mutex, +}; +use gpui::ForegroundExecutor; +use gpui_util::ResultExt; +use windows::{ + Win32::{ + Foundation::*, + System::{Com::*, Threading::GetCurrentThreadId}, + UI::{ + Controls::{TASKDIALOG_NOTIFICATIONS, TDN_BUTTON_CLICKED}, + WindowsAndMessaging::*, + }, + }, + core::{BOOL, HRESULT, w}, +}; + +use crate::SafeHwnd; + +pub(crate) struct DialogOwner { + hwnd: HWND, + closed: Arc, + serialization: Mutex<()>, +} + +impl DialogOwner { + pub(crate) fn new(hwnd: HWND) -> Rc { + Rc::new(Self { + hwnd, + closed: Arc::new(AtomicBool::new(false)), + serialization: Mutex::new(()), + }) + } + + pub(crate) fn close(&self) { + self.closed.store(true, Ordering::Release); + } + + pub(crate) async fn when_idle(&self) { + // After close(), queued requests cannot start a native dialog. Only the + // active dialog can still use the HWND, and it holds this lock until exit. + let _guard = self.serialization.lock().await; + } +} + +#[derive(Clone, Default)] +struct Cancellation { + requested: Arc, + owner_closed: Option>, +} + +impl Cancellation { + fn new(owner: Option<&DialogOwner>) -> Self { + Self { + owner_closed: owner.map(|owner| owner.closed.clone()), + ..Self::default() + } + } + + fn is_requested(&self) -> bool { + self.requested.load(Ordering::Acquire) + || self + .owner_closed + .as_ref() + .is_some_and(|closed| closed.load(Ordering::Acquire)) + } +} + +/// Native modal loops may synchronously message their owner, so the foreground +/// thread must keep pumping messages rather than waiting for the dialog thread. +/// Holding the owner's lock through native completion also allows window +/// destruction to wait for the dialog without blocking the foreground thread. +pub(crate) fn show_dialog( + owner: Option>, + executor: &ForegroundExecutor, + dialog: impl FnOnce(HWND) -> Result + Send + 'static, +) -> oneshot::Receiver> { + let (mut sender, receiver) = oneshot::channel(); + let cancellation = Cancellation::new(owner.as_deref()); + if cancellation.is_requested() { + sender.send(Err(anyhow!("dialog owner has closed"))).ok(); + return receiver; + } + let hwnd = owner.as_ref().map(|owner| SafeHwnd::from(owner.hwnd)); + executor + .spawn(async move { + // Let Windows manage enabling and activating the owner. Overlapping + // modal loops could otherwise re-enable it while another dialog is open. + let serialization = if let Some(owner) = owner.as_ref() { + match select(owner.serialization.lock(), sender.cancellation()).await { + Either::Left((guard, _)) => Some(guard), + Either::Right(((), _)) => return, + } + } else { + None + }; + if sender.is_canceled() || cancellation.is_requested() { + return; + } + let (result_sender, mut result_receiver) = oneshot::channel(); + let thread = std::thread::Builder::new() + .name("windows-native-dialog".into()) + .spawn({ + let cancellation = cancellation.clone(); + move || { + let result = (|| { + let _apartment = Apartment::new()?; + let _timer = CancellationTimer::new(cancellation.clone())?; + if cancellation.is_requested() { + return Err(anyhow!("dialog cancelled")); + } + let result = dialog(hwnd.map(|hwnd| hwnd.as_raw()).unwrap_or_default()); + if cancellation.is_requested() { + return Err(anyhow!("dialog cancelled")); + } + result + })(); + result_sender.send(result).ok(); + } + }); + if let Err(error) = thread { + drop(serialization); + sender.send(Err(error.into())).ok(); + return; + } + let result = match select(&mut result_receiver, sender.cancellation()).await { + Either::Left((result, _)) => result, + Either::Right(((), _)) => { + cancellation.requested.store(true, Ordering::Release); + result_receiver.await + } + }; + // Neither launch another dialog nor destroy the owner until the + // native loop exits, even if the caller no longer wants its result. + drop(serialization); + sender + .send(result.unwrap_or_else(|error| Err(error.into()))) + .ok(); + }) + .detach(); + receiver +} + +struct Apartment; + +impl Apartment { + fn new() -> Result { + unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok()? }; + Ok(Self) + } +} + +impl Drop for Apartment { + fn drop(&mut self) { + unsafe { CoUninitialize() }; + } +} + +thread_local! { + static CANCELLATION: RefCell> = const { RefCell::new(None) }; +} + +struct CancellationTimer(usize); + +impl CancellationTimer { + fn new(cancellation: Cancellation) -> Result { + let timer = unsafe { SetTimer(None, 0, 100, Some(cancel_dialog)) }; + if timer == 0 { + return Err(windows::core::Error::from_thread()) + .context("starting native dialog cancellation timer"); + } + CANCELLATION.with(|slot| slot.replace(Some(cancellation))); + Ok(Self(timer)) + } +} + +impl Drop for CancellationTimer { + fn drop(&mut self) { + CANCELLATION.with(|slot| slot.take()); + unsafe { KillTimer(None, self.0).log_err() }; + } +} + +unsafe extern "system" fn cancel_dialog(_: HWND, _: u32, _: usize, _: u32) { + if cancellation_requested() { + unsafe { + EnumThreadWindows(GetCurrentThreadId(), Some(close_dialog), LPARAM(0)) + .ok() + .log_err(); + }; + } +} + +fn cancellation_requested() -> bool { + CANCELLATION.with(|slot| { + slot.borrow() + .as_ref() + .is_some_and(Cancellation::is_requested) + }) +} + +pub(crate) unsafe extern "system" fn task_dialog_callback( + _: HWND, + notification: TASKDIALOG_NOTIFICATIONS, + button: WPARAM, + _: LPARAM, + has_cancel_button: isize, +) -> HRESULT { + // WM_CLOSE requires TDF_ALLOW_DIALOG_CANCELLATION, which also enables Esc/X. + // Only allow those user gestures when the prompt offered a Cancel button. + if notification == TDN_BUTTON_CLICKED + && button.0 == IDCANCEL.0 as usize + && has_cancel_button == 0 + && !cancellation_requested() + { + S_FALSE + } else { + S_OK + } +} + +unsafe extern "system" fn close_dialog(hwnd: HWND, _: LPARAM) -> BOOL { + // Shell dialogs can open further modal dialogs. Close only dialog windows + // on this dedicated thread, not hidden COM infrastructure windows. + let mut class_name = [0u16; 32]; + let length = unsafe { GetClassNameW(hwnd, &mut class_name) } as usize; + let dialog_class = w!("#32770"); + if class_name.get(..length) == Some(unsafe { dialog_class.as_wide() }) { + unsafe { PostMessageW(Some(hwnd), WM_CLOSE, WPARAM(0), LPARAM(0)).log_err() }; + } + TRUE +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::FutureExt as _; + + #[test] + fn task_dialog_cancellation_requires_cancel_button_or_internal_request() { + let cancellation = Cancellation::default(); + CANCELLATION.with(|slot| slot.replace(Some(cancellation.clone()))); + for internally_cancelled in [false, true] { + cancellation + .requested + .store(internally_cancelled, Ordering::Release); + for has_cancel_button in [false, true] { + for button in [IDCANCEL.0, IDOK.0, -1] { + let result = unsafe { + task_dialog_callback( + HWND::default(), + TDN_BUTTON_CLICKED, + WPARAM(button as usize), + LPARAM(0), + has_cancel_button as isize, + ) + }; + let should_veto = + button == IDCANCEL.0 && !has_cancel_button && !internally_cancelled; + assert_eq!(result, if should_veto { S_FALSE } else { S_OK }); + } + } + } + CANCELLATION.with(|slot| slot.take()); + } + + #[test] + fn closing_owner_cancels_every_outstanding_dialog() { + let owner = DialogOwner::new(HWND::default()); + let first = Cancellation::new(Some(&owner)); + let second = Cancellation::new(Some(&owner)); + assert!(!first.is_requested()); + assert!(!second.is_requested()); + + owner.close(); + owner.close(); + + assert!(first.is_requested()); + assert!(second.is_requested()); + assert!(Cancellation::new(Some(&owner)).is_requested()); + } + + #[test] + fn closed_owner_waits_for_active_dialog_but_queued_requests_stay_cancelled() { + let owner = DialogOwner::new(HWND::default()); + let active = owner.serialization.try_lock().expect("first dialog"); + let queued = Cancellation::new(Some(&owner)); + + owner.close(); + assert!(owner.when_idle().now_or_never().is_none()); + drop(active); + assert!(owner.when_idle().now_or_never().is_some()); + + let _guard = owner.serialization.try_lock().expect("queued request"); + assert!(queued.is_requested()); + } + + #[test] + fn cancelling_one_request_does_not_cancel_its_owner_or_other_requests() { + let owner = DialogOwner::new(HWND::default()); + let first = Cancellation::new(Some(&owner)); + let second = Cancellation::new(Some(&owner)); + first.requested.store(true, Ordering::Release); + assert!(first.is_requested()); + assert!(!second.is_requested()); + assert!(!owner.closed.load(Ordering::Acquire)); + } + + #[test] + fn dialogs_serialize_per_owner() { + let owner = DialogOwner::new(HWND::default()); + let other_owner = DialogOwner::new(HWND::default()); + let guard = owner.serialization.try_lock().expect("first dialog"); + + assert!(owner.serialization.try_lock().is_none()); + assert!(other_owner.serialization.try_lock().is_some()); + drop(guard); + assert!(owner.serialization.try_lock().is_some()); + } + + #[test] + fn ownerless_request_can_be_cancelled_without_a_window() { + let cancellation = Cancellation::new(None); + assert!(!cancellation.is_requested()); + cancellation.requested.store(true, Ordering::Release); + assert!(cancellation.is_requested()); + } +} diff --git a/vendor/gpui-pre-windows/src/direct_manipulation.rs b/vendor/gpui-pre-windows/src/direct_manipulation.rs new file mode 100644 index 0000000..9adcbed --- /dev/null +++ b/vendor/gpui-pre-windows/src/direct_manipulation.rs @@ -0,0 +1,359 @@ +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +use anyhow::Result; +use gpui::*; +use gpui_util::ResultExt; +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>, + pending_events: Rc>>, +} + +impl DirectManipulationHandler { + pub fn new(window: HWND, scale_factor: f32) -> Result { + 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 { + 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>, + gesture_kind: Cell, + last_scale: Cell, + last_x_offset: Cell, + last_y_offset: Cell, + scroll_phase: Cell, + pending_events: Rc>>, +} + +impl DirectManipulationEventHandler { + fn new( + window: HWND, + scale_factor: Rc>, + pending_events: Rc>>, + ) -> 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 { + 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) +} diff --git a/vendor/gpui-pre-windows/src/direct_write.rs b/vendor/gpui-pre-windows/src/direct_write.rs new file mode 100644 index 0000000..304dc38 --- /dev/null +++ b/vendor/gpui-pre-windows/src/direct_write.rs @@ -0,0 +1,2170 @@ +use std::{ + borrow::Cow, + ffi::{c_uint, c_void}, + mem::ManuallyDrop, +}; + +use anyhow::{Context, Result}; +use collections::HashMap; +use gpui_util::{ResultExt, maybe}; +use parking_lot::{RwLock, RwLockUpgradableReadGuard}; +use windows::{ + Win32::{ + Foundation::*, + Globalization::GetUserDefaultLocaleName, + Graphics::{ + Direct3D::D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, Direct3D11::*, DirectWrite::*, + Dxgi::Common::*, Gdi::LOGFONTW, + }, + System::SystemServices::LOCALE_NAME_MAX_LENGTH, + UI::WindowsAndMessaging::*, + }, + core::*, +}; +use windows_numerics::Vector2; + +use crate::*; +use gpui::*; + +#[derive(Debug)] +struct FontInfo { + font_family_h: HSTRING, + font_face: IDWriteFontFace3, + features: IDWriteTypography, + fallbacks: Option, + font_collection: IDWriteFontCollection1, +} + +pub(crate) struct DirectWriteTextSystem { + components: DirectWriteComponents, + state: RwLock, +} + +struct DirectWriteComponents { + locale: HSTRING, + factory: IDWriteFactory5, + in_memory_loader: IDWriteInMemoryFontFileLoader, + builder: IDWriteFontSetBuilder1, + text_renderer: TextRendererWrapper, + system_ui_font_name: SharedString, + system_subpixel_rendering: bool, +} + +impl Drop for DirectWriteComponents { + fn drop(&mut self) { + unsafe { + let _ = self + .factory + .UnregisterFontFileLoader(&self.in_memory_loader); + } + } +} + +struct GPUState { + device: ID3D11Device, + device_context: ID3D11DeviceContext, + sampler: Option, + blend_state: ID3D11BlendState, + vertex_shader: ID3D11VertexShader, + pixel_shader: ID3D11PixelShader, +} + +struct DirectWriteState { + /// Used to rasterize color glyphs. `None` while the platform is headless: shaping and + /// layout don't need a GPU, and nothing is drawn. + gpu_state: Option, + system_font_collection: IDWriteFontCollection1, + custom_font_collection: IDWriteFontCollection1, + fonts: Vec, + font_to_font_id: HashMap, + font_info_cache: HashMap, + layout_line_scratch: Vec, +} + +impl GPUState { + fn new(directx_devices: &DirectXDevices) -> Result { + let device = directx_devices.device.clone(); + let device_context = directx_devices.device_context.clone(); + + let blend_state = { + let mut blend_state = None; + let desc = D3D11_BLEND_DESC { + AlphaToCoverageEnable: false.into(), + IndependentBlendEnable: false.into(), + RenderTarget: [ + D3D11_RENDER_TARGET_BLEND_DESC { + BlendEnable: true.into(), + SrcBlend: D3D11_BLEND_ONE, + DestBlend: D3D11_BLEND_INV_SRC_ALPHA, + BlendOp: D3D11_BLEND_OP_ADD, + SrcBlendAlpha: D3D11_BLEND_ONE, + DestBlendAlpha: D3D11_BLEND_INV_SRC_ALPHA, + BlendOpAlpha: D3D11_BLEND_OP_ADD, + RenderTargetWriteMask: D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8, + }, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ], + }; + unsafe { device.CreateBlendState(&desc, Some(&mut blend_state)) }?; + blend_state.unwrap() + }; + + let sampler = { + let mut sampler = None; + let desc = D3D11_SAMPLER_DESC { + Filter: D3D11_FILTER_MIN_MAG_MIP_POINT, + AddressU: D3D11_TEXTURE_ADDRESS_BORDER, + AddressV: D3D11_TEXTURE_ADDRESS_BORDER, + AddressW: D3D11_TEXTURE_ADDRESS_BORDER, + MipLODBias: 0.0, + MaxAnisotropy: 1, + ComparisonFunc: D3D11_COMPARISON_ALWAYS, + BorderColor: [0.0, 0.0, 0.0, 0.0], + MinLOD: 0.0, + MaxLOD: 0.0, + }; + unsafe { device.CreateSamplerState(&desc, Some(&mut sampler)) }?; + sampler + }; + + let vertex_shader = { + let source = shader_resources::RawShaderBytes::new( + shader_resources::ShaderModule::EmojiRasterization, + shader_resources::ShaderTarget::Vertex, + )?; + let mut shader = None; + unsafe { device.CreateVertexShader(source.as_bytes(), None, Some(&mut shader)) }?; + shader.unwrap() + }; + + let pixel_shader = { + let source = shader_resources::RawShaderBytes::new( + shader_resources::ShaderModule::EmojiRasterization, + shader_resources::ShaderTarget::Fragment, + )?; + let mut shader = None; + unsafe { device.CreatePixelShader(source.as_bytes(), None, Some(&mut shader)) }?; + shader.unwrap() + }; + + Ok(Self { + device, + device_context, + sampler, + blend_state, + vertex_shader, + pixel_shader, + }) + } +} + +impl DirectWriteTextSystem { + /// Creates the text system, able to rasterize color glyphs once it has `directx_devices`. + pub(crate) fn new(directx_devices: Option<&DirectXDevices>) -> Result { + let factory: IDWriteFactory5 = unsafe { DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)? }; + // The `IDWriteInMemoryFontFileLoader` here is supported starting from + // Windows 10 Creators Update, which consequently requires the entire + // `DirectWriteTextSystem` to run on `win10 1703`+. + let in_memory_loader = unsafe { factory.CreateInMemoryFontFileLoader()? }; + unsafe { factory.RegisterFontFileLoader(&in_memory_loader)? }; + let builder = unsafe { factory.CreateFontSetBuilder()? }; + let mut locale = [0u16; LOCALE_NAME_MAX_LENGTH as usize]; + unsafe { GetUserDefaultLocaleName(&mut locale) }; + let locale = HSTRING::from_wide(&locale); + let text_renderer = TextRendererWrapper::new(locale.clone()); + + let gpu_state = directx_devices.map(GPUState::new).transpose()?; + + let system_subpixel_rendering = get_system_subpixel_rendering(); + let system_ui_font_name = get_system_ui_font_name(); + let components = DirectWriteComponents { + locale, + factory, + in_memory_loader, + builder, + text_renderer, + system_ui_font_name, + system_subpixel_rendering, + }; + + let system_font_collection = unsafe { + let mut result = None; + components + .factory + .GetSystemFontCollection(false, &mut result, true)?; + result.context("Failed to get system font collection")? + }; + let custom_font_set = unsafe { components.builder.CreateFontSet()? }; + let custom_font_collection = unsafe { + components + .factory + .CreateFontCollectionFromFontSet(&custom_font_set)? + }; + + Ok(Self { + components, + state: RwLock::new(DirectWriteState { + gpu_state, + system_font_collection, + custom_font_collection, + fonts: Vec::new(), + font_to_font_id: HashMap::default(), + font_info_cache: HashMap::default(), + layout_line_scratch: Vec::new(), + }), + }) + } + + /// Uses `directx_devices` to rasterize color glyphs, replacing any previous devices, for + /// example after the device was lost. + pub(crate) fn handle_gpu_lost(&self, directx_devices: &DirectXDevices) -> Result<()> { + self.state.write().handle_gpu_lost(directx_devices) + } + + /// Releases the GPU state, when the platform switches to headless mode. + pub(crate) fn release_gpu(&self) { + self.state.write().gpu_state = None; + } +} + +impl PlatformTextSystem for DirectWriteTextSystem { + fn add_fonts(&self, fonts: Vec>) -> Result<()> { + self.state.write().add_fonts(&self.components, fonts) + } + + fn all_font_names(&self) -> Vec { + self.state.read().all_font_names(&self.components) + } + + fn font_id(&self, font: &Font) -> Result { + let lock = self.state.upgradable_read(); + if let Some(font_id) = lock.font_to_font_id.get(font) { + Ok(*font_id) + } else { + RwLockUpgradableReadGuard::upgrade(lock) + .select_and_cache_font(&self.components, font) + .with_context(|| format!("Failed to select font: {:?}", font)) + } + } + + fn font_metrics(&self, font_id: FontId) -> FontMetrics { + self.state.read().font_metrics(font_id) + } + + fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result> { + self.state.read().get_typographic_bounds(font_id, glyph_id) + } + + fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result> { + self.state.read().get_advance(font_id, glyph_id) + } + + fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { + self.state.read().glyph_for_char(font_id, ch) + } + + fn glyph_raster_bounds( + &self, + params: &RenderGlyphParams, + ) -> anyhow::Result> { + self.state.read().raster_bounds(&self.components, params) + } + + fn rasterize_glyph( + &self, + params: &RenderGlyphParams, + raster_bounds: Bounds, + ) -> anyhow::Result<(Size, Vec)> { + self.state + .read() + .rasterize_glyph(&self.components, params, raster_bounds) + } + + fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout { + self.state + .write() + .layout_line(&self.components, text, font_size, runs) + .log_err() + .unwrap_or(LineLayout { + font_size, + ..Default::default() + }) + } + + fn recommended_rendering_mode( + &self, + _font_id: FontId, + _font_size: Pixels, + ) -> TextRenderingMode { + if self.components.system_subpixel_rendering { + TextRenderingMode::Subpixel + } else { + TextRenderingMode::Grayscale + } + } +} + +impl DirectWriteState { + fn select_and_cache_font( + &mut self, + components: &DirectWriteComponents, + font: &Font, + ) -> Option { + let select_font = |this: &mut DirectWriteState, font: &Font| -> Option { + let info = [&this.custom_font_collection, &this.system_font_collection] + .into_iter() + .find_map(|font_collection| unsafe { + DirectWriteState::make_font_from_font_collection( + font, + font_collection, + &components.factory, + &this.system_font_collection, + &components.system_ui_font_name, + ) + })?; + + let font_id = FontId(this.fonts.len()); + let font_face_key = info.font_face.cast::().unwrap().as_raw().addr(); + this.fonts.push(info); + this.font_info_cache.insert(font_face_key, font_id); + Some(font_id) + }; + + let mut font_id = select_font(self, font); + if font_id.is_none() { + // try updating system fonts and reselect + let mut collection = None; + let font_collection_updated = unsafe { + components + .factory + .GetSystemFontCollection(false, &mut collection, true) + } + .log_err() + .is_some(); + if font_collection_updated && let Some(collection) = collection { + self.system_font_collection = collection; + } + font_id = select_font(self, font); + }; + let font_id = font_id?; + self.font_to_font_id.insert(font.clone(), font_id); + Some(font_id) + } + + fn add_fonts( + &mut self, + components: &DirectWriteComponents, + fonts: Vec>, + ) -> Result<()> { + for font_data in fonts { + match font_data { + Cow::Borrowed(data) => unsafe { + let font_file = components + .in_memory_loader + .CreateInMemoryFontFileReference( + &components.factory, + data.as_ptr().cast(), + data.len() as _, + None, + )?; + components.builder.AddFontFile(&font_file)?; + }, + Cow::Owned(data) => unsafe { + let font_file = components + .in_memory_loader + .CreateInMemoryFontFileReference( + &components.factory, + data.as_ptr().cast(), + data.len() as _, + None, + )?; + components.builder.AddFontFile(&font_file)?; + }, + } + } + let set = unsafe { components.builder.CreateFontSet()? }; + let collection = unsafe { components.factory.CreateFontCollectionFromFontSet(&set)? }; + self.custom_font_collection = collection; + + Ok(()) + } + + fn generate_font_fallbacks( + fallbacks: &FontFallbacks, + factory: &IDWriteFactory5, + system_font_collection: &IDWriteFontCollection1, + ) -> Result> { + let fallback_list = fallbacks.fallback_list(); + if fallback_list.is_empty() { + return Ok(None); + } + unsafe { + let builder = factory.CreateFontFallbackBuilder()?; + let font_set = &system_font_collection.GetFontSet()?; + let mut unicode_ranges = Vec::new(); + for family_name in fallback_list { + let family_name = HSTRING::from(family_name); + let Some(fonts) = font_set + .GetMatchingFonts( + &family_name, + DWRITE_FONT_WEIGHT_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, + DWRITE_FONT_STYLE_NORMAL, + ) + .log_err() + else { + continue; + }; + let Ok(font_face) = fonts.GetFontFaceReference(0) else { + continue; + }; + let font = font_face.CreateFontFace()?; + let mut count = 0; + font.GetUnicodeRanges(None, &mut count).ok(); + if count == 0 { + continue; + } + unicode_ranges.clear(); + unicode_ranges.resize_with(count as usize, DWRITE_UNICODE_RANGE::default); + let Some(_) = font + .GetUnicodeRanges(Some(&mut unicode_ranges), &mut count) + .log_err() + else { + continue; + }; + builder.AddMapping( + &unicode_ranges, + &[family_name.as_ptr()], + None, + None, + None, + 1.0, + )?; + } + let system_fallbacks = factory.GetSystemFontFallback()?; + builder.AddMappings(&system_fallbacks)?; + Ok(Some(builder.CreateFontFallback()?)) + } + } + + unsafe fn generate_font_features( + factory: &IDWriteFactory5, + font_features: &FontFeatures, + ) -> Result { + let direct_write_features = unsafe { factory.CreateTypography()? }; + apply_font_features(&direct_write_features, font_features)?; + Ok(direct_write_features) + } + + unsafe fn make_font_from_font_collection( + &Font { + ref family, + ref features, + ref fallbacks, + weight, + style, + }: &Font, + collection: &IDWriteFontCollection1, + factory: &IDWriteFactory5, + system_font_collection: &IDWriteFontCollection1, + system_ui_font_name: &SharedString, + ) -> Option { + const SYSTEM_UI_FONT_NAME: &str = ".SystemUIFont"; + let family = if family == SYSTEM_UI_FONT_NAME { + system_ui_font_name + } else { + gpui::font_name_with_fallbacks_shared(&family, &system_ui_font_name) + }; + let fontset = unsafe { collection.GetFontSet().log_err()? }; + let font_family_h = HSTRING::from(family.as_str()); + let font = unsafe { + fontset + .GetMatchingFonts( + &font_family_h, + font_weight_to_dwrite(weight), + DWRITE_FONT_STRETCH_NORMAL, + font_style_to_dwrite(style), + ) + .log_err()? + }; + let total_number = unsafe { font.GetFontCount() }; + for index in 0..total_number { + let res = maybe!({ + let font_face_ref = unsafe { font.GetFontFaceReference(index).log_err()? }; + let font_face = unsafe { font_face_ref.CreateFontFace().log_err()? }; + let direct_write_features = + unsafe { Self::generate_font_features(factory, features).log_err()? }; + let fallbacks = fallbacks.as_ref().and_then(|fallbacks| { + Self::generate_font_fallbacks(fallbacks, factory, system_font_collection) + .log_err() + .flatten() + }); + let font_info = FontInfo { + font_family_h: font_family_h.clone(), + font_face, + features: direct_write_features, + fallbacks, + font_collection: collection.clone(), + }; + Some(font_info) + }); + if res.is_some() { + return res; + } + } + None + } + + fn layout_line( + &mut self, + components: &DirectWriteComponents, + text: &str, + font_size: Pixels, + font_runs: &[FontRun], + ) -> Result { + if font_runs.is_empty() { + return Ok(LineLayout { + font_size, + ..Default::default() + }); + } + unsafe { + self.layout_line_scratch.clear(); + self.layout_line_scratch.extend(text.encode_utf16()); + let text_wide = &*self.layout_line_scratch; + + let mut utf8_offset = 0usize; + let mut utf16_offset = 0u32; + let text_layout = { + let first_run = &font_runs[0]; + let font_info = &self.fonts[first_run.font_id.0]; + let collection = &font_info.font_collection; + let format: IDWriteTextFormat1 = components + .factory + .CreateTextFormat( + &font_info.font_family_h, + collection, + font_info.font_face.GetWeight(), + font_info.font_face.GetStyle(), + DWRITE_FONT_STRETCH_NORMAL, + font_size.as_f32(), + &components.locale, + )? + .cast()?; + if let Some(ref fallbacks) = font_info.fallbacks { + format.SetFontFallback(fallbacks)?; + } + + let layout = components.factory.CreateTextLayout( + text_wide, + &format, + f32::INFINITY, + f32::INFINITY, + )?; + let current_text = &text[utf8_offset..(utf8_offset + first_run.len)]; + utf8_offset += first_run.len; + let current_text_utf16_length = current_text.encode_utf16().count() as u32; + let text_range = DWRITE_TEXT_RANGE { + startPosition: utf16_offset, + length: current_text_utf16_length, + }; + layout.SetTypography(&font_info.features, text_range)?; + utf16_offset += current_text_utf16_length; + + layout + }; + + let (ascent, descent) = { + let mut first_metrics = [DWRITE_LINE_METRICS::default(); 4]; + let mut line_count = 0u32; + text_layout.GetLineMetrics(Some(&mut first_metrics), &mut line_count)?; + ( + px(first_metrics[0].baseline), + px(first_metrics[0].height - first_metrics[0].baseline), + ) + }; + let mut break_ligatures = true; + for run in &font_runs[1..] { + let font_info = &self.fonts[run.font_id.0]; + let current_text = &text[utf8_offset..(utf8_offset + run.len)]; + utf8_offset += run.len; + let current_text_utf16_length = current_text.encode_utf16().count() as u32; + + let collection = &font_info.font_collection; + let text_range = DWRITE_TEXT_RANGE { + startPosition: utf16_offset, + length: current_text_utf16_length, + }; + utf16_offset += current_text_utf16_length; + text_layout.SetFontCollection(collection, text_range)?; + text_layout.SetFontFamilyName(&font_info.font_family_h, text_range)?; + let font_size = if break_ligatures { + font_size.as_f32().next_up() + } else { + font_size.as_f32() + }; + text_layout.SetFontSize(font_size, text_range)?; + text_layout.SetFontStyle(font_info.font_face.GetStyle(), text_range)?; + text_layout.SetFontWeight(font_info.font_face.GetWeight(), text_range)?; + text_layout.SetTypography(&font_info.features, text_range)?; + + break_ligatures = !break_ligatures; + } + + let mut runs = Vec::new(); + let mut renderer_context = RendererContext { + text_system: self, + components, + index_converter: StringIndexConverter::new(text), + runs: &mut runs, + width: 0.0, + }; + text_layout.Draw( + Some((&raw mut renderer_context).cast::().cast_const()), + &components.text_renderer.0, + 0.0, + 0.0, + )?; + let width = px(renderer_context.width); + + Ok(LineLayout { + font_size, + width, + ascent, + descent, + runs, + len: text.len(), + }) + } + } + + fn font_metrics(&self, font_id: FontId) -> FontMetrics { + unsafe { + let font_info = &self.fonts[font_id.0]; + let mut metrics = std::mem::zeroed(); + font_info.font_face.GetMetrics(&mut metrics); + + FontMetrics { + units_per_em: metrics.Base.designUnitsPerEm as _, + ascent: metrics.Base.ascent as _, + descent: -(metrics.Base.descent as f32), + line_gap: metrics.Base.lineGap as _, + underline_position: metrics.Base.underlinePosition as _, + underline_thickness: metrics.Base.underlineThickness as _, + cap_height: metrics.Base.capHeight as _, + x_height: metrics.Base.xHeight as _, + bounding_box: Bounds { + origin: Point { + x: metrics.glyphBoxLeft as _, + y: metrics.glyphBoxBottom as _, + }, + size: Size { + width: (metrics.glyphBoxRight - metrics.glyphBoxLeft) as _, + height: (metrics.glyphBoxTop - metrics.glyphBoxBottom) as _, + }, + }, + } + } + } + + fn create_glyph_run_analysis( + &self, + components: &DirectWriteComponents, + params: &RenderGlyphParams, + ) -> Result { + let font = &self.fonts[params.font_id.0]; + let glyph_id = [params.glyph_id.0 as u16]; + let advance = [0.0]; + let offset = [DWRITE_GLYPH_OFFSET::default()]; + let glyph_run = DWRITE_GLYPH_RUN { + fontFace: ManuallyDrop::new(Some(unsafe { std::ptr::read(&***font.font_face) })), + fontEmSize: params.font_size.as_f32(), + glyphCount: 1, + glyphIndices: glyph_id.as_ptr(), + glyphAdvances: advance.as_ptr(), + glyphOffsets: offset.as_ptr(), + isSideways: BOOL(0), + bidiLevel: 0, + }; + let transform = DWRITE_MATRIX { + m11: params.scale_factor, + m12: 0.0, + m21: 0.0, + m22: params.scale_factor, + dx: 0.0, + dy: 0.0, + }; + let baseline_origin_x = + params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor; + let baseline_origin_y = params.subpixel_variant.y as f32 + / gpui::SUBPIXEL_VARIANTS_Y as f32 + / params.scale_factor; + + let mut rendering_mode = DWRITE_RENDERING_MODE1::default(); + let mut grid_fit_mode = DWRITE_GRID_FIT_MODE::default(); + unsafe { + font.font_face.GetRecommendedRenderingMode( + params.font_size.as_f32(), + // Using 96 as scale is applied by the transform + 96.0, + 96.0, + Some(&transform), + false, + DWRITE_OUTLINE_THRESHOLD_ANTIALIASED, + DWRITE_MEASURING_MODE_NATURAL, + None, + &mut rendering_mode, + &mut grid_fit_mode, + )?; + } + let rendering_mode = match rendering_mode { + DWRITE_RENDERING_MODE1_OUTLINE => DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC, + m => m, + }; + + let antialias_mode = if params.subpixel_rendering { + DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE + } else { + DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE + }; + + let glyph_analysis = unsafe { + components.factory.CreateGlyphRunAnalysis( + &glyph_run, + Some(&transform), + rendering_mode, + DWRITE_MEASURING_MODE_NATURAL, + grid_fit_mode, + antialias_mode, + baseline_origin_x, + baseline_origin_y, + ) + }?; + Ok(glyph_analysis) + } + + fn raster_bounds( + &self, + components: &DirectWriteComponents, + params: &RenderGlyphParams, + ) -> Result> { + let glyph_analysis = self.create_glyph_run_analysis(components, params)?; + + let texture_type = if params.subpixel_rendering { + DWRITE_TEXTURE_CLEARTYPE_3x1 + } else { + DWRITE_TEXTURE_ALIASED_1x1 + }; + + let bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(texture_type)? }; + + if bounds.right < bounds.left { + Ok(Bounds { + origin: point(0.into(), 0.into()), + size: size(0.into(), 0.into()), + }) + } else { + Ok(Bounds { + origin: point(bounds.left.into(), bounds.top.into()), + size: size( + (bounds.right - bounds.left).into(), + (bounds.bottom - bounds.top).into(), + ), + }) + } + } + + fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { + let font_info = &self.fonts[font_id.0]; + let codepoints = ch as u32; + let mut glyph_indices = 0u16; + unsafe { + font_info + .font_face + .GetGlyphIndices(&raw const codepoints, 1, &raw mut glyph_indices) + .log_err() + } + .map(|_| GlyphId(glyph_indices as u32)) + } + + fn rasterize_glyph( + &self, + components: &DirectWriteComponents, + params: &RenderGlyphParams, + glyph_bounds: Bounds, + ) -> Result<(Size, Vec)> { + if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 { + anyhow::bail!("glyph bounds are empty"); + } + + let bitmap_data = if params.is_emoji { + if let Ok(color) = self.rasterize_color(components, params, glyph_bounds) { + color + } else { + let monochrome = self.rasterize_monochrome(components, params, glyph_bounds)?; + monochrome + .into_iter() + .flat_map(|pixel| [0, 0, 0, pixel]) + .collect::>() + } + } else { + self.rasterize_monochrome(components, params, glyph_bounds)? + }; + + Ok((glyph_bounds.size, bitmap_data)) + } + + fn rasterize_monochrome( + &self, + components: &DirectWriteComponents, + params: &RenderGlyphParams, + glyph_bounds: Bounds, + ) -> Result> { + let glyph_analysis = self.create_glyph_run_analysis(components, params)?; + if !params.subpixel_rendering { + let mut bitmap_data = + vec![0u8; glyph_bounds.size.width.0 as usize * glyph_bounds.size.height.0 as usize]; + unsafe { + glyph_analysis.CreateAlphaTexture( + DWRITE_TEXTURE_ALIASED_1x1, + &RECT { + left: glyph_bounds.origin.x.0, + top: glyph_bounds.origin.y.0, + right: glyph_bounds.size.width.0 + glyph_bounds.origin.x.0, + bottom: glyph_bounds.size.height.0 + glyph_bounds.origin.y.0, + }, + &mut bitmap_data, + )?; + } + + return Ok(bitmap_data); + } + + let width = glyph_bounds.size.width.0 as usize; + let height = glyph_bounds.size.height.0 as usize; + let pixel_count = width * height; + + let mut bitmap_data = vec![0u8; pixel_count * 4]; + + unsafe { + glyph_analysis.CreateAlphaTexture( + DWRITE_TEXTURE_CLEARTYPE_3x1, + &RECT { + left: glyph_bounds.origin.x.0, + top: glyph_bounds.origin.y.0, + right: glyph_bounds.size.width.0 + glyph_bounds.origin.x.0, + bottom: glyph_bounds.size.height.0 + glyph_bounds.origin.y.0, + }, + &mut bitmap_data[..pixel_count * 3], + )?; + } + + // The output buffer expects RGBA data, so pad the alpha channel with zeros. + for pixel_ix in (0..pixel_count).rev() { + let src = pixel_ix * 3; + let dst = pixel_ix * 4; + ( + bitmap_data[dst], + bitmap_data[dst + 1], + bitmap_data[dst + 2], + bitmap_data[dst + 3], + ) = ( + bitmap_data[src], + bitmap_data[src + 1], + bitmap_data[src + 2], + 0, + ); + } + + Ok(bitmap_data) + } + + fn rasterize_color( + &self, + components: &DirectWriteComponents, + params: &RenderGlyphParams, + glyph_bounds: Bounds, + ) -> Result> { + // INVARIANT: the code below drives the *shared* D3D11 immediate context + // (`Map`/`Unmap`/`Draw`/`CopyResource`), which `DirectXRenderer` and `DirectXAtlas` also + // touch. An immediate `ID3D11DeviceContext` is not thread-safe, so this must only run on + // the main UI thread (which it always is; text rasterization never leaves that thread). + let gpu_state = self + .gpu_state + .as_ref() + .context("rasterizing color glyphs needs a GPU, which headless mode has none of")?; + let bitmap_size = glyph_bounds.size; + let subpixel_shift = params + .subpixel_variant + .map(|v| v as f32 / SUBPIXEL_VARIANTS_X as f32); + let baseline_origin_x = subpixel_shift.x / params.scale_factor; + let baseline_origin_y = subpixel_shift.y / params.scale_factor; + + let transform = DWRITE_MATRIX { + m11: params.scale_factor, + m12: 0.0, + m21: 0.0, + m22: params.scale_factor, + dx: 0.0, + dy: 0.0, + }; + + let font = &self.fonts[params.font_id.0]; + let glyph_id = [params.glyph_id.0 as u16]; + let advance = [glyph_bounds.size.width.0 as f32]; + let offset = [DWRITE_GLYPH_OFFSET { + advanceOffset: -glyph_bounds.origin.x.0 as f32 / params.scale_factor, + ascenderOffset: glyph_bounds.origin.y.0 as f32 / params.scale_factor, + }]; + let glyph_run = DWRITE_GLYPH_RUN { + fontFace: ManuallyDrop::new(Some(unsafe { std::ptr::read(&***font.font_face) })), + fontEmSize: params.font_size.as_f32(), + glyphCount: 1, + glyphIndices: glyph_id.as_ptr(), + glyphAdvances: advance.as_ptr(), + glyphOffsets: offset.as_ptr(), + isSideways: BOOL(0), + bidiLevel: 0, + }; + + // todo: support formats other than COLR + let color_enumerator = unsafe { + components.factory.TranslateColorGlyphRun( + Vector2::new(baseline_origin_x, baseline_origin_y), + &glyph_run, + None, + DWRITE_GLYPH_IMAGE_FORMATS_COLR, + DWRITE_MEASURING_MODE_NATURAL, + Some(&transform), + 0, + ) + }?; + + let mut glyph_layers = Vec::new(); + let mut alpha_data = Vec::new(); + loop { + let color_run = unsafe { color_enumerator.GetCurrentRun() }?; + let color_run = unsafe { &*color_run }; + let image_format = color_run.glyphImageFormat & !DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE; + if image_format == DWRITE_GLYPH_IMAGE_FORMATS_COLR { + let color_analysis = unsafe { + components.factory.CreateGlyphRunAnalysis( + &color_run.Base.glyphRun as *const _, + Some(&transform), + DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC, + DWRITE_MEASURING_MODE_NATURAL, + DWRITE_GRID_FIT_MODE_DEFAULT, + DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE, + baseline_origin_x, + baseline_origin_y, + ) + }?; + + let color_bounds = + unsafe { color_analysis.GetAlphaTextureBounds(DWRITE_TEXTURE_ALIASED_1x1) }?; + + let color_size = size( + color_bounds.right - color_bounds.left, + color_bounds.bottom - color_bounds.top, + ); + if color_size.width > 0 && color_size.height > 0 { + alpha_data.clear(); + alpha_data.resize((color_size.width * color_size.height) as usize, 0); + unsafe { + color_analysis.CreateAlphaTexture( + DWRITE_TEXTURE_ALIASED_1x1, + &color_bounds, + &mut alpha_data, + ) + }?; + + let run_color = { + let run_color = color_run.Base.runColor; + Rgba { + r: run_color.r, + g: run_color.g, + b: run_color.b, + a: run_color.a, + } + }; + let bounds = bounds(point(color_bounds.left, color_bounds.top), color_size); + glyph_layers.push(GlyphLayerTexture::new( + gpu_state, + run_color, + bounds, + &alpha_data, + )?); + } + } + + let has_next = unsafe { color_enumerator.MoveNext() } + .map(|e| e.as_bool()) + .unwrap_or(false); + if !has_next { + break; + } + } + + let render_target_texture = { + let mut texture = None; + let desc = D3D11_TEXTURE2D_DESC { + Width: bitmap_size.width.0 as u32, + Height: bitmap_size.height.0 as u32, + MipLevels: 1, + ArraySize: 1, + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + Usage: D3D11_USAGE_DEFAULT, + BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32, + CPUAccessFlags: 0, + MiscFlags: 0, + }; + unsafe { + gpu_state + .device + .CreateTexture2D(&desc, None, Some(&mut texture)) + }?; + texture.unwrap() + }; + + let render_target_view = { + let desc = D3D11_RENDER_TARGET_VIEW_DESC { + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + ViewDimension: D3D11_RTV_DIMENSION_TEXTURE2D, + Anonymous: D3D11_RENDER_TARGET_VIEW_DESC_0 { + Texture2D: D3D11_TEX2D_RTV { MipSlice: 0 }, + }, + }; + let mut rtv = None; + unsafe { + gpu_state.device.CreateRenderTargetView( + &render_target_texture, + Some(&desc), + Some(&mut rtv), + ) + }?; + rtv + }; + + Self::composite_color_layers( + gpu_state, + &glyph_layers, + bitmap_size, + &render_target_texture, + &render_target_view, + ) + } + + fn composite_color_layers( + gpu_state: &GPUState, + glyph_layers: &[GlyphLayerTexture], + bitmap_size: Size, + render_target_texture: &ID3D11Texture2D, + render_target_view: &Option, + ) -> Result> { + let params_buffer = { + let desc = D3D11_BUFFER_DESC { + ByteWidth: std::mem::size_of::() as u32, + Usage: D3D11_USAGE_DYNAMIC, + BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32, + CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, + MiscFlags: 0, + StructureByteStride: 0, + }; + + let mut buffer = None; + unsafe { + gpu_state + .device + .CreateBuffer(&desc, None, Some(&mut buffer)) + }?; + buffer + }; + + let staging_texture = { + let mut texture = None; + let desc = D3D11_TEXTURE2D_DESC { + Width: bitmap_size.width.0 as u32, + Height: bitmap_size.height.0 as u32, + MipLevels: 1, + ArraySize: 1, + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + Usage: D3D11_USAGE_STAGING, + BindFlags: 0, + CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32, + MiscFlags: 0, + }; + unsafe { + gpu_state + .device + .CreateTexture2D(&desc, None, Some(&mut texture)) + }?; + texture.unwrap() + }; + + let device_context = &gpu_state.device_context; + unsafe { device_context.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP) }; + unsafe { device_context.VSSetShader(&gpu_state.vertex_shader, None) }; + unsafe { device_context.PSSetShader(&gpu_state.pixel_shader, None) }; + unsafe { + device_context.VSSetConstantBuffers(0, Some(std::slice::from_ref(¶ms_buffer))) + }; + unsafe { + device_context.PSSetConstantBuffers(0, Some(std::slice::from_ref(¶ms_buffer))) + }; + unsafe { + device_context.OMSetRenderTargets(Some(std::slice::from_ref(render_target_view)), None) + }; + unsafe { + if let Some(render_target_view) = render_target_view.as_ref() { + device_context.ClearRenderTargetView(render_target_view, &[0.0, 0.0, 0.0, 0.0]); + } + } + unsafe { device_context.PSSetSamplers(0, Some(std::slice::from_ref(&gpu_state.sampler))) }; + unsafe { device_context.OMSetBlendState(&gpu_state.blend_state, None, 0xffffffff) }; + + let crate::FontInfo { + gamma_ratios, + grayscale_enhanced_contrast, + .. + } = DirectXRenderer::get_font_info(); + + for layer in glyph_layers { + let params = GlyphLayerTextureParams { + run_color: layer.run_color, + bounds: layer.bounds, + gamma_ratios: *gamma_ratios, + grayscale_enhanced_contrast: *grayscale_enhanced_contrast, + _pad: [0f32; 3], + }; + unsafe { + let mut dest = std::mem::zeroed(); + gpu_state.device_context.Map( + params_buffer.as_ref().unwrap(), + 0, + D3D11_MAP_WRITE_DISCARD, + 0, + Some(&mut dest), + )?; + std::ptr::copy_nonoverlapping(¶ms as *const _, dest.pData as *mut _, 1); + gpu_state + .device_context + .Unmap(params_buffer.as_ref().unwrap(), 0); + }; + + let texture = [Some(layer.texture_view.clone())]; + unsafe { device_context.PSSetShaderResources(0, Some(&texture)) }; + + let viewport = [D3D11_VIEWPORT { + TopLeftX: layer.bounds.origin.x as f32, + TopLeftY: layer.bounds.origin.y as f32, + Width: layer.bounds.size.width as f32, + Height: layer.bounds.size.height as f32, + MinDepth: 0.0, + MaxDepth: 1.0, + }]; + unsafe { device_context.RSSetViewports(Some(&viewport)) }; + + unsafe { device_context.Draw(4, 0) }; + } + + unsafe { device_context.CopyResource(&staging_texture, render_target_texture) }; + + let mapped_data = { + let mut mapped_data = D3D11_MAPPED_SUBRESOURCE::default(); + unsafe { + device_context.Map( + &staging_texture, + 0, + D3D11_MAP_READ, + 0, + Some(&mut mapped_data), + ) + }?; + mapped_data + }; + let mut rasterized = + vec![0u8; (bitmap_size.width.0 as u32 * bitmap_size.height.0 as u32 * 4) as usize]; + + for y in 0..bitmap_size.height.0 as usize { + let width = bitmap_size.width.0 as usize; + unsafe { + std::ptr::copy_nonoverlapping::( + (mapped_data.pData as *const u8).byte_add(mapped_data.RowPitch as usize * y), + rasterized + .as_mut_ptr() + .byte_add(width * y * std::mem::size_of::()), + width * std::mem::size_of::(), + ) + }; + } + + // Release the mapping now that the rows have been copied out; leaving `staging_texture` + // mapped would leak the mapping and keep the resource pinned for later reuse. + unsafe { device_context.Unmap(&staging_texture, 0) }; + + // Convert from premultiplied to straight alpha + for chunk in rasterized.chunks_exact_mut(4) { + let b = chunk[0] as f32; + let g = chunk[1] as f32; + let r = chunk[2] as f32; + let a = chunk[3] as f32; + if a > 0.0 { + let inv_a = 255.0 / a; + chunk[0] = (b * inv_a).clamp(0.0, 255.0) as u8; + chunk[1] = (g * inv_a).clamp(0.0, 255.0) as u8; + chunk[2] = (r * inv_a).clamp(0.0, 255.0) as u8; + } + } + + Ok(rasterized) + } + + fn get_typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result> { + unsafe { + let font = &self.fonts[font_id.0].font_face; + let glyph_indices = [glyph_id.0 as u16]; + let mut metrics = [DWRITE_GLYPH_METRICS::default()]; + font.GetDesignGlyphMetrics(glyph_indices.as_ptr(), 1, metrics.as_mut_ptr(), false)?; + + let metrics = &metrics[0]; + let advance_width = metrics.advanceWidth as i32; + let advance_height = metrics.advanceHeight as i32; + let left_side_bearing = metrics.leftSideBearing; + let right_side_bearing = metrics.rightSideBearing; + let top_side_bearing = metrics.topSideBearing; + let bottom_side_bearing = metrics.bottomSideBearing; + let vertical_origin_y = metrics.verticalOriginY; + + let y_offset = vertical_origin_y + bottom_side_bearing - advance_height; + let width = advance_width - (left_side_bearing + right_side_bearing); + let height = advance_height - (top_side_bearing + bottom_side_bearing); + + Ok(Bounds { + origin: Point { + x: left_side_bearing as f32, + y: y_offset as f32, + }, + size: Size { + width: width as f32, + height: height as f32, + }, + }) + } + } + + fn get_advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result> { + unsafe { + let font = &self.fonts[font_id.0].font_face; + let glyph_indices = [glyph_id.0 as u16]; + let mut metrics = [DWRITE_GLYPH_METRICS::default()]; + font.GetDesignGlyphMetrics(glyph_indices.as_ptr(), 1, metrics.as_mut_ptr(), false)?; + + let metrics = &metrics[0]; + + Ok(Size { + width: metrics.advanceWidth as f32, + height: 0.0, + }) + } + } + + fn all_font_names(&self, components: &DirectWriteComponents) -> Vec { + let mut result = + get_font_names_from_collection(&self.system_font_collection, &components.locale); + result.extend(get_font_names_from_collection( + &self.custom_font_collection, + &components.locale, + )); + result + } + + fn handle_gpu_lost(&mut self, directx_devices: &DirectXDevices) -> Result<()> { + try_to_recover_from_device_lost(|| { + GPUState::new(directx_devices).context("Recreating GPU state for DirectWrite") + }) + .map(|gpu_state| self.gpu_state = Some(gpu_state)) + } +} + +struct GlyphLayerTexture { + run_color: Rgba, + bounds: Bounds, + texture_view: ID3D11ShaderResourceView, + // holding on to the texture to not RAII drop it + _texture: ID3D11Texture2D, +} + +impl GlyphLayerTexture { + fn new( + gpu_state: &GPUState, + run_color: Rgba, + bounds: Bounds, + alpha_data: &[u8], + ) -> Result { + let texture_size = bounds.size; + + let desc = D3D11_TEXTURE2D_DESC { + Width: texture_size.width as u32, + Height: texture_size.height as u32, + MipLevels: 1, + ArraySize: 1, + Format: DXGI_FORMAT_R8_UNORM, + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + Usage: D3D11_USAGE_DEFAULT, + BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32, + CPUAccessFlags: 0, + MiscFlags: 0, + }; + + let texture = { + let mut texture: Option = None; + unsafe { + gpu_state + .device + .CreateTexture2D(&desc, None, Some(&mut texture))? + }; + texture.unwrap() + }; + let texture_view = { + let mut view: Option = None; + unsafe { + gpu_state + .device + .CreateShaderResourceView(&texture, None, Some(&mut view))? + }; + view.unwrap() + }; + + unsafe { + gpu_state.device_context.UpdateSubresource( + &texture, + 0, + None, + alpha_data.as_ptr() as _, + texture_size.width as u32, + 0, + ) + }; + + Ok(GlyphLayerTexture { + run_color, + bounds, + texture_view, + _texture: texture, + }) + } +} + +#[repr(C)] +struct GlyphLayerTextureParams { + bounds: Bounds, + run_color: Rgba, + gamma_ratios: [f32; 4], + grayscale_enhanced_contrast: f32, + _pad: [f32; 3], +} + +struct TextRendererWrapper(IDWriteTextRenderer); + +impl TextRendererWrapper { + fn new(locale_str: HSTRING) -> Self { + let inner = TextRenderer::new(locale_str); + TextRendererWrapper(inner.into()) + } +} + +#[implement(IDWriteTextRenderer)] +struct TextRenderer { + locale: HSTRING, +} + +impl TextRenderer { + fn new(locale: HSTRING) -> Self { + TextRenderer { locale } + } +} + +struct RendererContext<'t, 'a, 'b> { + text_system: &'t mut DirectWriteState, + components: &'a DirectWriteComponents, + index_converter: StringIndexConverter<'a>, + runs: &'b mut Vec, + width: f32, +} + +#[derive(Debug)] +struct ClusterAnalyzer<'t> { + utf16_idx: usize, + glyph_idx: usize, + glyph_count: usize, + cluster_map: &'t [u16], +} + +impl<'t> ClusterAnalyzer<'t> { + fn new(cluster_map: &'t [u16], glyph_count: usize) -> Self { + ClusterAnalyzer { + utf16_idx: 0, + glyph_idx: 0, + glyph_count, + cluster_map, + } + } +} + +impl Iterator for ClusterAnalyzer<'_> { + type Item = (usize, usize); + + fn next(&mut self) -> Option<(usize, usize)> { + if self.utf16_idx >= self.cluster_map.len() { + return None; // No more clusters + } + let start_utf16_idx = self.utf16_idx; + let current_glyph = self.cluster_map[start_utf16_idx] as usize; + + // Find the end of current cluster (where glyph index changes) + let mut end_utf16_idx = start_utf16_idx + 1; + while end_utf16_idx < self.cluster_map.len() + && self.cluster_map[end_utf16_idx] as usize == current_glyph + { + end_utf16_idx += 1; + } + + let utf16_len = end_utf16_idx - start_utf16_idx; + + // Calculate glyph count for this cluster + let next_glyph = if end_utf16_idx < self.cluster_map.len() { + self.cluster_map[end_utf16_idx] as usize + } else { + self.glyph_count + }; + + let glyph_count = next_glyph - current_glyph; + + // Update state for next call + self.utf16_idx = end_utf16_idx; + self.glyph_idx = next_glyph; + + Some((utf16_len, glyph_count)) + } +} + +#[allow(non_snake_case)] +impl IDWritePixelSnapping_Impl for TextRenderer_Impl { + fn IsPixelSnappingDisabled( + &self, + _clientdrawingcontext: *const ::core::ffi::c_void, + ) -> windows::core::Result { + Ok(BOOL(0)) + } + + fn GetCurrentTransform( + &self, + _clientdrawingcontext: *const ::core::ffi::c_void, + transform: *mut DWRITE_MATRIX, + ) -> windows::core::Result<()> { + unsafe { + *transform = DWRITE_MATRIX { + m11: 1.0, + m12: 0.0, + m21: 0.0, + m22: 1.0, + dx: 0.0, + dy: 0.0, + }; + } + Ok(()) + } + + fn GetPixelsPerDip( + &self, + _clientdrawingcontext: *const ::core::ffi::c_void, + ) -> windows::core::Result { + Ok(1.0) + } +} + +#[allow(non_snake_case)] +impl IDWriteTextRenderer_Impl for TextRenderer_Impl { + fn DrawGlyphRun( + &self, + clientdrawingcontext: *const ::core::ffi::c_void, + _baselineoriginx: f32, + _baselineoriginy: f32, + _measuringmode: DWRITE_MEASURING_MODE, + glyphrun: *const DWRITE_GLYPH_RUN, + glyphrundescription: *const DWRITE_GLYPH_RUN_DESCRIPTION, + _clientdrawingeffect: windows::core::Ref, + ) -> windows::core::Result<()> { + let glyphrun = unsafe { &*glyphrun }; + let glyph_count = glyphrun.glyphCount as usize; + if glyph_count == 0 { + return Ok(()); + } + let desc = unsafe { &*glyphrundescription }; + let context = unsafe { &mut *(clientdrawingcontext.cast::().cast_mut()) }; + let Some(font_face) = glyphrun.fontFace.as_ref() else { + return Ok(()); + }; + // This `cast()` action here should never fail since we are running on Win10+, and + // `IDWriteFontFace3` requires Win10 + let Ok(font_face) = &font_face.cast::() else { + return Err(Error::new( + DWRITE_E_UNSUPPORTEDOPERATION, + "Failed to cast font face", + )); + }; + + let font_face_key = font_face.cast::().unwrap().as_raw().addr(); + let font_id = context + .text_system + .font_info_cache + .get(&font_face_key) + .copied() + // in some circumstances, we might be getting served a FontFace that we did not create ourselves + // so create a new font from it and cache it accordingly. The usual culprit here seems to be Segoe UI Symbol + .map_or_else( + || { + let font = font_face_to_font(font_face, &self.locale) + .ok_or_else(|| Error::new(DWRITE_E_NOFONT, "Failed to create font"))?; + let font_id = match context.text_system.font_to_font_id.get(&font) { + Some(&font_id) => font_id, + None => context + .text_system + .select_and_cache_font(context.components, &font) + .ok_or_else(|| Error::new(DWRITE_E_NOFONT, "Failed to create font"))?, + }; + context + .text_system + .font_info_cache + .insert(font_face_key, font_id); + windows::core::Result::Ok(font_id) + }, + Ok, + )?; + + let color_font = unsafe { font_face.IsColorFont().as_bool() }; + + let glyph_ids = unsafe { + slice_from_nullable( + glyphrun.glyphIndices, + glyph_count, + "DirectWrite returned a null glyph indices array", + )? + }; + let glyph_advances = unsafe { + slice_from_nullable( + glyphrun.glyphAdvances, + glyph_count, + "DirectWrite returned a null glyph advances array", + )? + }; + let glyph_offsets = unsafe { + slice_from_nullable( + glyphrun.glyphOffsets, + glyph_count, + "DirectWrite returned a null glyph offsets array", + )? + }; + let cluster_map = unsafe { + slice_from_nullable( + desc.clusterMap, + desc.stringLength as usize, + "DirectWrite returned a null cluster map", + )? + }; + + let cluster_analyzer = ClusterAnalyzer::new(cluster_map, glyph_count); + let mut utf16_idx = desc.textPosition as usize; + let mut glyph_idx = 0; + let mut glyphs = Vec::with_capacity(glyph_count); + for (cluster_utf16_len, cluster_glyph_count) in cluster_analyzer { + context.index_converter.advance_to_utf16_ix(utf16_idx); + utf16_idx += cluster_utf16_len; + for (cluster_glyph_idx, glyph_id) in glyph_ids + [glyph_idx..(glyph_idx + cluster_glyph_count)] + .iter() + .enumerate() + { + let id = GlyphId(*glyph_id as u32); + let is_emoji = + color_font && is_color_glyph(font_face, id, &context.components.factory); + let this_glyph_idx = glyph_idx + cluster_glyph_idx; + glyphs.push(ShapedGlyph { + id, + position: point( + px(context.width + glyph_offsets[this_glyph_idx].advanceOffset), + px(-glyph_offsets[this_glyph_idx].ascenderOffset), + ), + index: context.index_converter.utf8_ix, + is_emoji, + }); + context.width += glyph_advances[this_glyph_idx]; + } + glyph_idx += cluster_glyph_count; + } + context.runs.push(ShapedRun { font_id, glyphs }); + Ok(()) + } + + fn DrawUnderline( + &self, + _clientdrawingcontext: *const ::core::ffi::c_void, + _baselineoriginx: f32, + _baselineoriginy: f32, + _underline: *const DWRITE_UNDERLINE, + _clientdrawingeffect: windows::core::Ref, + ) -> windows::core::Result<()> { + Err(windows::core::Error::new( + E_NOTIMPL, + "DrawUnderline unimplemented", + )) + } + + fn DrawStrikethrough( + &self, + _clientdrawingcontext: *const ::core::ffi::c_void, + _baselineoriginx: f32, + _baselineoriginy: f32, + _strikethrough: *const DWRITE_STRIKETHROUGH, + _clientdrawingeffect: windows::core::Ref, + ) -> windows::core::Result<()> { + Err(windows::core::Error::new( + E_NOTIMPL, + "DrawStrikethrough unimplemented", + )) + } + + fn DrawInlineObject( + &self, + _clientdrawingcontext: *const ::core::ffi::c_void, + _originx: f32, + _originy: f32, + _inlineobject: windows::core::Ref, + _issideways: BOOL, + _isrighttoleft: BOOL, + _clientdrawingeffect: windows::core::Ref, + ) -> windows::core::Result<()> { + Err(windows::core::Error::new( + E_NOTIMPL, + "DrawInlineObject unimplemented", + )) + } +} + +/// Interprets an optional DirectWrite array pointer as a slice, treating a +/// null pointer with a zero length as an empty slice. A null pointer with a +/// nonzero length fails with `null_error_message`. +/// +/// # Safety +/// +/// When `ptr` is non-null, the caller must guarantee that it points to a valid +/// array of at least `len` elements that outlives the returned slice. +unsafe fn slice_from_nullable<'a, T>( + ptr: *const T, + len: usize, + null_error_message: &str, +) -> windows::core::Result<&'a [T]> { + if ptr.is_null() { + if len != 0 { + return Err(Error::new(E_INVALIDARG, null_error_message)); + } + Ok(&[]) + } else { + Ok(unsafe { std::slice::from_raw_parts(ptr, len) }) + } +} + +struct StringIndexConverter<'a> { + text: &'a str, + utf8_ix: usize, + utf16_ix: usize, +} + +impl<'a> StringIndexConverter<'a> { + fn new(text: &'a str) -> Self { + Self { + text, + utf8_ix: 0, + utf16_ix: 0, + } + } + + #[allow(dead_code)] + fn advance_to_utf8_ix(&mut self, utf8_target: usize) { + for (ix, c) in self.text[self.utf8_ix..].char_indices() { + if self.utf8_ix + ix >= utf8_target { + self.utf8_ix += ix; + return; + } + self.utf16_ix += c.len_utf16(); + } + self.utf8_ix = self.text.len(); + } + + fn advance_to_utf16_ix(&mut self, utf16_target: usize) { + for (ix, c) in self.text[self.utf8_ix..].char_indices() { + if self.utf16_ix >= utf16_target { + self.utf8_ix += ix; + return; + } + self.utf16_ix += c.len_utf16(); + } + self.utf8_ix = self.text.len(); + } +} + +fn font_style_to_dwrite(style: FontStyle) -> DWRITE_FONT_STYLE { + match style { + FontStyle::Normal => DWRITE_FONT_STYLE_NORMAL, + FontStyle::Italic => DWRITE_FONT_STYLE_ITALIC, + FontStyle::Oblique => DWRITE_FONT_STYLE_OBLIQUE, + } +} + +fn font_style_from_dwrite(value: DWRITE_FONT_STYLE) -> FontStyle { + match value.0 { + 0 => FontStyle::Normal, + 1 => FontStyle::Italic, + 2 => FontStyle::Oblique, + _ => unreachable!(), + } +} + +fn font_weight_to_dwrite(weight: FontWeight) -> DWRITE_FONT_WEIGHT { + DWRITE_FONT_WEIGHT(weight.0 as i32) +} + +fn font_weight_from_dwrite(value: DWRITE_FONT_WEIGHT) -> FontWeight { + FontWeight(value.0 as f32) +} + +fn get_font_names_from_collection( + collection: &IDWriteFontCollection1, + locale: &HSTRING, +) -> Vec { + unsafe { + let mut result = Vec::new(); + let family_count = collection.GetFontFamilyCount(); + for index in 0..family_count { + let Some(font_family) = collection.GetFontFamily(index).log_err() else { + continue; + }; + let Some(localized_family_name) = font_family.GetFamilyNames().log_err() else { + continue; + }; + let Some(family_name) = get_name(localized_family_name, locale).log_err() else { + continue; + }; + result.push(family_name); + } + + result + } +} + +fn font_face_to_font(font_face: &IDWriteFontFace3, locale: &HSTRING) -> Option { + let localized_family_name = unsafe { font_face.GetFamilyNames().log_err() }?; + let family_name = get_name(localized_family_name, locale).log_err()?; + let weight = unsafe { font_face.GetWeight() }; + let style = unsafe { font_face.GetStyle() }; + Some(Font { + family: family_name.into(), + features: FontFeatures::default(), + weight: font_weight_from_dwrite(weight), + style: font_style_from_dwrite(style), + fallbacks: None, + }) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_font_feature_tag +fn apply_font_features( + direct_write_features: &IDWriteTypography, + features: &FontFeatures, +) -> Result<()> { + let tag_values = features.tag_value_list(); + + // All of these features are enabled by default by DirectWrite. + // If you want to (and can) peek into the source of DirectWrite + let mut feature_liga = make_direct_write_feature("liga", 1); + let mut feature_clig = make_direct_write_feature("clig", 1); + let mut feature_calt = make_direct_write_feature("calt", 1); + + for (tag, value) in tag_values { + if tag.as_str() == "liga" && *value == 0 { + feature_liga.parameter = 0; + continue; + } + if tag.as_str() == "clig" && *value == 0 { + feature_clig.parameter = 0; + continue; + } + if tag.as_str() == "calt" && *value == 0 { + feature_calt.parameter = 0; + continue; + } + + unsafe { + direct_write_features.AddFontFeature(make_direct_write_feature(tag, *value))?; + } + } + unsafe { + direct_write_features.AddFontFeature(feature_liga)?; + direct_write_features.AddFontFeature(feature_clig)?; + direct_write_features.AddFontFeature(feature_calt)?; + } + + Ok(()) +} + +#[inline] +const fn make_direct_write_feature(feature_name: &str, parameter: u32) -> DWRITE_FONT_FEATURE { + let tag = make_direct_write_tag(feature_name); + DWRITE_FONT_FEATURE { + nameTag: tag, + parameter, + } +} + +#[inline] +const fn make_open_type_tag(tag_name: &str) -> u32 { + let bytes = tag_name.as_bytes(); + debug_assert!(bytes.len() == 4); + u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) +} + +#[inline] +const fn make_direct_write_tag(tag_name: &str) -> DWRITE_FONT_FEATURE_TAG { + DWRITE_FONT_FEATURE_TAG(make_open_type_tag(tag_name)) +} + +#[inline] +fn get_name(string: IDWriteLocalizedStrings, locale: &HSTRING) -> Result { + let mut locale_name_index = 0u32; + let mut exists = BOOL(0); + unsafe { string.FindLocaleName(locale, &mut locale_name_index, &mut exists as _)? }; + if !exists.as_bool() { + unsafe { + string.FindLocaleName( + DEFAULT_LOCALE_NAME, + &mut locale_name_index as _, + &mut exists as _, + )? + }; + anyhow::ensure!(exists.as_bool(), "No localised string for {locale}"); + } + + let name_length = unsafe { string.GetStringLength(locale_name_index) }? as usize; + let mut name_vec = vec![0u16; name_length + 1]; + unsafe { + string.GetString(locale_name_index, &mut name_vec)?; + } + + Ok(String::from_utf16_lossy(&name_vec[..name_length])) +} + +fn get_system_subpixel_rendering() -> bool { + let mut value = c_uint::default(); + let result = unsafe { + SystemParametersInfoW( + SPI_GETFONTSMOOTHINGTYPE, + 0, + Some((&mut value) as *mut c_uint as *mut c_void), + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(), + ) + }; + if result.log_err().is_some() { + value == FE_FONTSMOOTHINGCLEARTYPE + } else { + true + } +} + +fn get_system_ui_font_name() -> SharedString { + unsafe { + let mut info: LOGFONTW = std::mem::zeroed(); + let font_family = if SystemParametersInfoW( + SPI_GETICONTITLELOGFONT, + std::mem::size_of::() as u32, + Some(&mut info as *mut _ as _), + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0), + ) + .log_err() + .is_none() + { + // https://learn.microsoft.com/en-us/windows/win32/uxguide/vis-fonts + // Segoe UI is the Windows font intended for user interface text strings. + "Segoe UI".into() + } else { + let font_name = String::from_utf16_lossy(&info.lfFaceName); + font_name.trim_matches(char::from(0)).to_owned().into() + }; + log::info!("Use {} as UI font.", font_family); + font_family + } +} + +// One would think that with newer DirectWrite method: IDWriteFontFace4::GetGlyphImageFormats +// but that doesn't seem to work for some glyphs, say ❤ +fn is_color_glyph( + font_face: &IDWriteFontFace3, + glyph_id: GlyphId, + factory: &IDWriteFactory5, +) -> bool { + let glyph_run = DWRITE_GLYPH_RUN { + fontFace: ManuallyDrop::new(Some(unsafe { std::ptr::read(&****font_face) })), + fontEmSize: 14.0, + glyphCount: 1, + glyphIndices: &(glyph_id.0 as u16), + glyphAdvances: &0.0, + glyphOffsets: &DWRITE_GLYPH_OFFSET { + advanceOffset: 0.0, + ascenderOffset: 0.0, + }, + isSideways: BOOL(0), + bidiLevel: 0, + }; + unsafe { + factory.TranslateColorGlyphRun( + Vector2::default(), + &glyph_run as _, + None, + DWRITE_GLYPH_IMAGE_FORMATS_COLR + | DWRITE_GLYPH_IMAGE_FORMATS_SVG + | DWRITE_GLYPH_IMAGE_FORMATS_PNG + | DWRITE_GLYPH_IMAGE_FORMATS_JPEG + | DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8, + DWRITE_MEASURING_MODE_NATURAL, + None, + 0, + ) + } + .is_ok() +} + +const DEFAULT_LOCALE_NAME: PCWSTR = windows::core::w!("en-US"); + +#[cfg(test)] +mod tests { + use super::{DirectWriteState, DirectWriteTextSystem, GPUState, GlyphLayerTexture}; + use crate::direct_write::ClusterAnalyzer; + use crate::directx_devices::DirectXDevices; + use anyhow::Result; + use gpui::{ + DevicePixels, Font, PlatformTextSystem, RenderGlyphParams, Rgba, bounds, point, px, size, + }; + use std::ffi::c_void; + use windows::Win32::Graphics::Direct3D11::{ + D3D11_BIND_RENDER_TARGET, D3D11_RENDER_TARGET_VIEW_DESC, D3D11_RENDER_TARGET_VIEW_DESC_0, + D3D11_RTV_DIMENSION_TEXTURE2D, D3D11_SUBRESOURCE_DATA, D3D11_TEX2D_RTV, + D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT, + }; + use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC}; + + #[test] + fn test_cluster_map() { + let cluster_map = [0]; + let mut analyzer = ClusterAnalyzer::new(&cluster_map, 1); + let next = analyzer.next(); + assert_eq!(next, Some((1, 1))); + let next = analyzer.next(); + assert_eq!(next, None); + + let cluster_map = [0, 1, 2]; + let mut analyzer = ClusterAnalyzer::new(&cluster_map, 3); + let next = analyzer.next(); + assert_eq!(next, Some((1, 1))); + let next = analyzer.next(); + assert_eq!(next, Some((1, 1))); + let next = analyzer.next(); + assert_eq!(next, Some((1, 1))); + let next = analyzer.next(); + assert_eq!(next, None); + // 👨‍👩‍👧‍👦👩‍💻 + let cluster_map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4]; + let mut analyzer = ClusterAnalyzer::new(&cluster_map, 5); + let next = analyzer.next(); + assert_eq!(next, Some((11, 4))); + let next = analyzer.next(); + assert_eq!(next, Some((5, 1))); + let next = analyzer.next(); + assert_eq!(next, None); + // 👩‍💻 + let cluster_map = [0, 0, 0, 0, 0]; + let mut analyzer = ClusterAnalyzer::new(&cluster_map, 1); + let next = analyzer.next(); + assert_eq!(next, Some((5, 1))); + let next = analyzer.next(); + assert_eq!(next, None); + } + + #[test] + fn color_emoji_rasterization_is_stable_across_batches() -> Result<()> { + let devices = DirectXDevices::new()?; + let text_system = DirectWriteTextSystem::new(Some(&devices))?; + + let font = Font { + family: "Segoe UI Emoji".into(), + ..Default::default() + }; + let font_id = text_system.font_id(&font)?; + + let mut params_list = Vec::new(); + for ch in ['🫠', '🥹', '🧗', '🏋', '🚀', '🥺'] { + let Some(glyph_id) = text_system.glyph_for_char(font_id, ch) else { + log::info!("no glyph found for {ch}"); + continue; + }; + let params = RenderGlyphParams { + font_id, + glyph_id, + font_size: px(48.0), + subpixel_variant: point(0u8, 0u8), + scale_factor: 1.0, + is_emoji: true, + subpixel_rendering: false, + dilation: 0, + }; + let raster_bounds = text_system.glyph_raster_bounds(¶ms)?; + if raster_bounds.size.width.0 == 0 || raster_bounds.size.height.0 == 0 { + log::info!("raster bounds are empty for {ch}"); + continue; + } + params_list.push((params, raster_bounds)); + } + assert!(!params_list.is_empty()); + + let first: Vec<_> = params_list + .iter() + .map(|(params, bounds)| text_system.rasterize_glyph(params, *bounds)) + .collect::>()?; + + // Churn the texture heap with further rasterization passes. If the color + // compositing leaks leftover texture data (the render target is not cleared), + // the second batch can pick up different contents and differ from the first. + // With an explicit clear both batches are deterministic and identical. + for _ in 0..3 { + for (params, bounds) in ¶ms_list { + text_system.rasterize_glyph(params, *bounds)?; + } + } + let second: Vec<_> = params_list + .iter() + .map(|(params, bounds)| text_system.rasterize_glyph(params, *bounds)) + .collect::>()?; + + assert_eq!( + first, second, + "color glyph rasterization changed between batches; \ + render target contents are leaking into the glyph bitmaps" + ); + Ok(()) + } + + #[test] + fn color_emoji_composites_over_cleared_texture() -> Result<()> { + let devices = DirectXDevices::new()?; + let gpu_state = GPUState::new(&devices)?; + + const SIZE: u32 = 32; + // Seed the render target with solid red so that any texel which the + // compositing pass fails to clear/overwrite remains identifiable after + // the readback. + let poison = { + let mut v = vec![0u8; (SIZE * SIZE * 4) as usize]; + for pixel in v.chunks_exact_mut(4) { + pixel[2] = 255; + pixel[3] = 255; + } + v + }; + let desc = D3D11_TEXTURE2D_DESC { + Width: SIZE, + Height: SIZE, + MipLevels: 1, + ArraySize: 1, + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + Usage: D3D11_USAGE_DEFAULT, + BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32, + CPUAccessFlags: 0, + MiscFlags: 0, + }; + let initial_data = D3D11_SUBRESOURCE_DATA { + pSysMem: poison.as_ptr() as *const c_void, + SysMemPitch: SIZE * 4, + SysMemSlicePitch: 0, + }; + let texture = unsafe { + let mut texture = None; + gpu_state + .device + .CreateTexture2D(&desc, Some(&initial_data), Some(&mut texture))?; + texture.unwrap() + }; + let render_target_view = unsafe { + let desc = D3D11_RENDER_TARGET_VIEW_DESC { + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + ViewDimension: D3D11_RTV_DIMENSION_TEXTURE2D, + Anonymous: D3D11_RENDER_TARGET_VIEW_DESC_0 { + Texture2D: D3D11_TEX2D_RTV { MipSlice: 0 }, + }, + }; + let mut rtv = None; + gpu_state + .device + .CreateRenderTargetView(&texture, Some(&desc), Some(&mut rtv))?; + rtv.unwrap() + }; + + // A single opaque layer in the top-left corner; the bottom-right corner + // of the texture is covered by no layer at all. + let layer_alpha = vec![255u8; 4 * 4]; + let layer = GlyphLayerTexture::new( + &gpu_state, + Rgba { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }, + bounds(point(0, 0), size(4, 4)), + &layer_alpha, + )?; + + let rasterized = DirectWriteState::composite_color_layers( + &gpu_state, + std::slice::from_ref(&layer), + size(DevicePixels(SIZE as i32), DevicePixels(SIZE as i32)), + &texture, + &Some(render_target_view), + )?; + + let corner = (SIZE as usize - 1 + (SIZE as usize - 1) * SIZE as usize) * 4; + assert_eq!( + &rasterized[corner..corner + 4], + &[0, 0, 0, 0], + "uncovered texel retained the poison from the uninitialized render target" + ); + Ok(()) + } +} diff --git a/vendor/gpui-pre-windows/src/directx_atlas.rs b/vendor/gpui-pre-windows/src/directx_atlas.rs new file mode 100644 index 0000000..622c1cb --- /dev/null +++ b/vendor/gpui-pre-windows/src/directx_atlas.rs @@ -0,0 +1,420 @@ +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::{ + AtlasBackend, AtlasKey, AtlasState, AtlasTextureId, AtlasTextureKind, AtlasTextureList, + AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size, +}; + +pub(crate) struct DirectXAtlas(Mutex>); + +struct DirectXAtlasTextures { + device: ID3D11Device, + device_context: ID3D11DeviceContext, + monochrome_textures: AtlasTextureList, + polychrome_textures: AtlasTextureList, + subpixel_textures: AtlasTextureList, +} + +struct DirectXAtlasTexture { + id: AtlasTextureId, + bytes_per_pixel: u32, + allocator: BucketedAtlasAllocator, + texture: ID3D11Texture2D, + view: [Option; 1], + live_atlas_keys: u32, +} + +impl DirectXAtlas { + pub(crate) fn new(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Self { + DirectXAtlas(Mutex::new(AtlasState::new(DirectXAtlasTextures { + device: device.clone(), + device_context: device_context.clone(), + monochrome_textures: Default::default(), + polychrome_textures: Default::default(), + subpixel_textures: Default::default(), + }))) + } + + /// Returns the view backing `id`, or `None` once every tile in it has been + /// removed. A scene can still reference such a texture when a cached view + /// replays a paint from before the image was dropped, so callers must skip + /// those sprites rather than assume the texture exists. + pub(crate) fn get_texture_view( + &self, + id: AtlasTextureId, + ) -> Option<[Option; 1]> { + let lock = self.0.lock(); + let texture = lock.backend.texture(id)?; + Some(texture.view.clone()) + } + + pub(crate) fn handle_device_lost( + &self, + device: &ID3D11Device, + device_context: &ID3D11DeviceContext, + ) { + let mut lock = self.0.lock(); + lock.clear(|textures| { + textures.device = device.clone(); + textures.device_context = device_context.clone(); + textures.monochrome_textures = AtlasTextureList::default(); + textures.polychrome_textures = AtlasTextureList::default(); + textures.subpixel_textures = AtlasTextureList::default(); + }); + } +} + +impl PlatformAtlas for DirectXAtlas { + fn get_or_insert_with<'a>( + &self, + key: AtlasKey, + build: &mut dyn FnMut() -> anyhow::Result< + Option<(Size, std::borrow::Cow<'a, [u8]>)>, + >, + ) -> anyhow::Result> { + self.0.lock().get_or_insert_with(key, build) + } + + fn remove(&self, key: &AtlasKey) { + self.0.lock().remove(key); + } +} + +impl AtlasBackend for DirectXAtlasTextures { + fn insert( + &mut self, + kind: AtlasTextureKind, + size: Size, + bytes: &[u8], + ) -> anyhow::Result { + let tile = self + .allocate(size, kind) + .ok_or_else(|| anyhow::anyhow!("failed to allocate"))?; + let texture = self + .texture(tile.texture_id) + .ok_or_else(|| anyhow::anyhow!("allocated tile refers to a missing texture"))?; + texture.upload(&self.device_context, tile.bounds, bytes); + Ok(tile) + } + + fn remove(&mut self, tile: AtlasTile) { + let id = tile.texture_id; + + let textures = match id.kind { + AtlasTextureKind::Monochrome => &mut self.monochrome_textures, + AtlasTextureKind::Polychrome => &mut self.polychrome_textures, + AtlasTextureKind::Subpixel => &mut self.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.allocator.deallocate(tile.tile_id.into()); + texture.decrement_ref_count(); + if texture.is_unreferenced() { + textures.free_list.push(texture.id.index as usize); + } else { + *texture_slot = Some(texture); + } + } + } +} + +impl DirectXAtlasTextures { + fn allocate( + &mut self, + size: Size, + texture_kind: AtlasTextureKind, + ) -> Option { + { + 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, + kind: AtlasTextureKind, + ) -> Option<&mut DirectXAtlasTexture> { + const DEFAULT_ATLAS_SIZE: Size = 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 = 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 = 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) -> Option<&DirectXAtlasTexture> { + let textures = match id.kind { + AtlasTextureKind::Monochrome => &self.monochrome_textures, + AtlasTextureKind::Polychrome => &self.polychrome_textures, + AtlasTextureKind::Subpixel => &self.subpixel_textures, + }; + textures.textures.get(id.index as usize)?.as_ref() + } +} + +impl DirectXAtlasTexture { + fn allocate(&mut self, size: Size) -> Option { + 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, + bytes: &[u8], + ) { + // `UpdateSubresource` reads `row_pitch * height` bytes from `bytes` based on the + // `D3D11_BOX` below. If the caller hands us a slice shorter than that, the driver would + // over-read past the end of the source buffer (potentially by multiple megabytes), so bail + // out instead. This is a first-insert path rather than a per-frame one, so the check is + // effectively free. + let row_bytes = bounds.size.width.to_bytes(self.bytes_per_pixel as u8) as usize; + let expected = row_bytes * bounds.size.height.0.max(0) as usize; + if bytes.len() < expected { + log::error!( + "DirectXAtlasTexture::upload: source slice is {} bytes but the {}x{} region \ + requires {} bytes; skipping upload to avoid a driver over-read", + bytes.len(), + bounds.size.width.0, + bounds.size.height.0, + expected, + ); + return; + } + 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) -> etagere::Size { + etagere::Size::new(size.width.into(), size.height.into()) +} + +fn etagere_point_to_device(value: etagere::Point) -> Point { + Point { + x: DevicePixels::from(value.x), + y: DevicePixels::from(value.y), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use gpui::{ImageId, RenderImageParams}; + use std::borrow::Cow; + use windows::Win32::{ + Foundation::HMODULE, + Graphics::{ + Direct3D::D3D_DRIVER_TYPE_WARP, + Direct3D11::{D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_SDK_VERSION, D3D11CreateDevice}, + }, + }; + + fn create_atlas() -> Option { + let mut device: Option = None; + let mut device_context: Option = None; + unsafe { + D3D11CreateDevice( + None, + D3D_DRIVER_TYPE_WARP, + HMODULE::default(), + D3D11_CREATE_DEVICE_BGRA_SUPPORT, + None, + D3D11_SDK_VERSION, + Some(&mut device), + None, + Some(&mut device_context), + ) + } + .ok()?; + Some(DirectXAtlas::new(&device?, &device_context?)) + } + + fn make_image_key(image_id: usize) -> AtlasKey { + AtlasKey::Image(RenderImageParams { + image_id: ImageId(image_id), + frame_index: 0, + }) + } + + fn insert_tile(atlas: &DirectXAtlas, key: AtlasKey, size: Size) -> AtlasTile { + atlas + .get_or_insert_with(key, &mut || { + let byte_count = (size.width.0 as usize) * (size.height.0 as usize) * 4; + Ok(Some((size, Cow::Owned(vec![0u8; byte_count])))) + }) + .expect("allocation should succeed") + .expect("callback returns Some") + } + + #[test] + fn test_remove_deallocates_tile_space_for_reuse() { + let Some(atlas) = create_atlas() else { + return; + }; + + let small = Size { + width: DevicePixels(64), + height: DevicePixels(64), + }; + let big = Size { + width: DevicePixels(700), + height: DevicePixels(700), + }; + + let keeper_key = make_image_key(1); + let big_key_a = make_image_key(2); + let big_key_b = make_image_key(3); + + let keeper_tile = insert_tile(&atlas, keeper_key, small); + let tile_a = insert_tile(&atlas, big_key_a.clone(), big); + assert_eq!(keeper_tile.texture_id, tile_a.texture_id); + + atlas.remove(&big_key_a); + + let tile_b = insert_tile(&atlas, big_key_b, big); + assert_eq!(tile_b.texture_id, keeper_tile.texture_id); + } +} diff --git a/vendor/gpui-pre-windows/src/directx_devices.rs b/vendor/gpui-pre-windows/src/directx_devices.rs new file mode 100644 index 0000000..8e65e6e --- /dev/null +++ b/vendor/gpui-pre-windows/src/directx_devices.rs @@ -0,0 +1,194 @@ +use anyhow::{Context, Result}; +use gpui_util::ResultExt; +use itertools::Itertools; +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(mut f: impl FnMut() -> Result) -> Result { + (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 { + 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::(0) } + .log_err() + .is_some() + } + #[cfg(not(debug_assertions))] + { + false + } +} + +#[inline] +fn get_dxgi_factory(debug_layer_available: bool) -> Result { + 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 = 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>, + feature_level: Option<*mut D3D_FEATURE_LEVEL>, + debug_layer_available: bool, +) -> Result { + let mut device: Option = 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::() 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" + )) + } +} diff --git a/vendor/gpui-pre-windows/src/directx_renderer.rs b/vendor/gpui-pre-windows/src/directx_renderer.rs new file mode 100644 index 0000000..f8b86bc --- /dev/null +++ b/vendor/gpui-pre-windows/src/directx_renderer.rs @@ -0,0 +1,2070 @@ +use std::{ + slice, + sync::{Arc, OnceLock}, +}; + +use anyhow::{Context, Result}; +use gpui_util::ResultExt; +use windows::{ + Win32::{ + Foundation::HWND, + Graphics::{ + Direct3D::*, + Direct3D11::*, + DirectComposition::*, + DirectWrite::*, + Dxgi::{Common::*, *}, + }, + }, + core::{HSTRING, Interface}, +}; + +use crate::directx_renderer::shader_resources::{RawShaderBytes, ShaderModule, ShaderTarget}; +use crate::*; +use gpui::*; + +pub(crate) const DISABLE_DIRECT_COMPOSITION: &str = "GPUI_DISABLE_DIRECT_COMPOSITION"; +const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM; +// This configuration is used for MSAA rendering on paths only, and it's guaranteed to be supported by DirectX 11. +const PATH_MULTISAMPLE_COUNT: u32 = 4; +const MAX_INSTANCE_BUFFER_SIZE: usize = 256 * 1024 * 1024; + +pub(crate) struct FontInfo { + pub gamma_ratios: [f32; 4], + pub grayscale_enhanced_contrast: f32, + pub subpixel_enhanced_contrast: f32, + pub is_bgr: bool, +} + +pub(crate) struct DirectXRenderer { + hwnd: HWND, + atlas: Arc, + devices: Option, + resources: Option, + globals: DirectXGlobalElements, + pipelines: DirectXRenderPipelines, + direct_composition: Option, + font_info: &'static FontInfo, + + width: u32, + height: u32, + + /// Whether we want to skip drwaing due to device lost events. + /// + /// In that case we want to discard the first frame that we draw as we got reset in the middle of a frame + /// meaning we lost all the allocated gpu textures and scene resources. + skip_draws: bool, +} + +/// Direct3D objects +#[derive(Clone)] +pub(crate) struct DirectXRendererDevices { + pub(crate) adapter: IDXGIAdapter1, + pub(crate) dxgi_factory: IDXGIFactory6, + pub(crate) device: ID3D11Device, + pub(crate) device_context: ID3D11DeviceContext, + dxgi_device: Option, + annotation: Option, +} + +struct DirectXResources { + // Direct3D rendering objects + swap_chain: IDXGISwapChain1, + render_target: Option, + render_target_view: Option, + + // Path intermediate textures (with MSAA) + path_intermediate_texture: ID3D11Texture2D, + path_intermediate_srv: Option, + path_intermediate_msaa_texture: ID3D11Texture2D, + path_intermediate_msaa_view: Option, + + // Cached viewport + viewport: D3D11_VIEWPORT, +} + +struct DirectXRenderPipelines { + shadow_pipeline: PipelineState, + quad_pipeline: PipelineState, + path_rasterization_pipeline: PipelineState, + path_sprite_pipeline: PipelineState, + underline_pipeline: PipelineState, + mono_sprites: PipelineState, + subpixel_sprites: PipelineState, + poly_sprites: PipelineState, +} + +struct DirectXGlobalElements { + global_params_buffer: Option, + batch_params_buffer: Option, + sampler: Option, +} + +struct Annotation<'a>(&'a ID3DUserDefinedAnnotation); + +impl<'a> Annotation<'a> { + fn new(annotation: &'a ID3DUserDefinedAnnotation, label: HSTRING) -> Self { + unsafe { annotation.BeginEvent(&label) }; + Self(annotation) + } +} + +impl Drop for Annotation<'_> { + fn drop(&mut self) { + unsafe { self.0.EndEvent() }; + } +} + +struct DirectComposition { + comp_device: IDCompositionDevice, + comp_target: IDCompositionTarget, + comp_visual: IDCompositionVisual, +} + +impl DirectXRendererDevices { + pub(crate) fn new( + directx_devices: &DirectXDevices, + disable_direct_composition: bool, + ) -> Result { + let DirectXDevices { + adapter, + dxgi_factory, + device, + device_context, + } = directx_devices; + let dxgi_device = if disable_direct_composition { + None + } else { + Some(device.cast().context("Creating DXGI device")?) + }; + let annotation = device_context.cast().ok(); + + Ok(Self { + adapter: adapter.clone(), + dxgi_factory: dxgi_factory.clone(), + device: device.clone(), + device_context: device_context.clone(), + dxgi_device, + annotation, + }) + } +} + +impl DirectXRenderer { + pub(crate) fn new( + hwnd: HWND, + directx_devices: &DirectXDevices, + disable_direct_composition: bool, + ) -> Result { + if disable_direct_composition { + log::info!("Direct Composition is disabled."); + } + + let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition) + .context("Creating DirectX devices")?; + let atlas = Arc::new(DirectXAtlas::new(&devices.device, &devices.device_context)); + + let resources = DirectXResources::new(&devices, 1, 1, hwnd, disable_direct_composition) + .context("Creating DirectX resources")?; + let globals = DirectXGlobalElements::new(&devices.device) + .context("Creating DirectX global elements")?; + let pipelines = DirectXRenderPipelines::new(&devices.device) + .context("Creating DirectX render pipelines")?; + + let direct_composition = if disable_direct_composition { + None + } else { + let composition = DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), hwnd) + .context("Creating DirectComposition")?; + composition + .set_swap_chain(&resources.swap_chain) + .context("Setting swap chain for DirectComposition")?; + Some(composition) + }; + + Ok(DirectXRenderer { + hwnd, + atlas, + devices: Some(devices), + resources: Some(resources), + globals, + pipelines, + direct_composition, + font_info: Self::get_font_info(), + width: 1, + height: 1, + skip_draws: false, + }) + } + + pub(crate) fn sprite_atlas(&self) -> Arc { + self.atlas.clone() + } + + fn pre_draw(&self, clear_color: &[f32; 4]) -> Result<()> { + let resources = self.resources.as_ref().expect("resources missing"); + let device_context = &self + .devices + .as_ref() + .expect("devices missing") + .device_context; + update_buffer( + device_context, + self.globals.global_params_buffer.as_ref().unwrap(), + &[GlobalParams { + gamma_ratios: self.font_info.gamma_ratios, + viewport_size: [resources.viewport.Width, resources.viewport.Height], + grayscale_enhanced_contrast: self.font_info.grayscale_enhanced_contrast, + subpixel_enhanced_contrast: self.font_info.subpixel_enhanced_contrast, + is_bgr: self.font_info.is_bgr as u32, + _pad: [0; 3], + }], + )?; + unsafe { + device_context.ClearRenderTargetView( + resources + .render_target_view + .as_ref() + .context("missing render target view")?, + clear_color, + ); + device_context + .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None); + device_context.RSSetViewports(Some(slice::from_ref(&resources.viewport))); + device_context + .VSSetConstantBuffers(0, Some(slice::from_ref(&self.globals.global_params_buffer))); + device_context + .VSSetConstantBuffers(1, Some(slice::from_ref(&self.globals.batch_params_buffer))); + device_context + .PSSetConstantBuffers(0, Some(slice::from_ref(&self.globals.global_params_buffer))); + } + Ok(()) + } + + #[inline] + fn present(&mut self) -> Result<()> { + let result = unsafe { + self.resources + .as_ref() + .expect("resources missing") + .swap_chain + .Present(0, DXGI_PRESENT(0)) + }; + result.ok().context("Presenting swap chain failed") + } + + pub(crate) fn handle_device_lost(&mut self, directx_devices: &DirectXDevices) -> Result<()> { + try_to_recover_from_device_lost(|| { + self.handle_device_lost_impl(directx_devices) + .context("DirectXRenderer handling device lost") + }) + } + + fn handle_device_lost_impl(&mut self, directx_devices: &DirectXDevices) -> Result<()> { + let disable_direct_composition = self.direct_composition.is_none(); + + unsafe { + #[cfg(debug_assertions)] + if let Some(devices) = &self.devices { + report_live_objects(&devices.device) + .context("Failed to report live objects after device lost") + .log_err(); + } + + self.resources.take(); + if let Some(devices) = &self.devices { + devices.device_context.OMSetRenderTargets(None, None); + devices.device_context.ClearState(); + devices.device_context.Flush(); + #[cfg(debug_assertions)] + report_live_objects(&devices.device) + .context("Failed to report live objects after device lost") + .log_err(); + } + + self.direct_composition.take(); + self.devices.take(); + } + + let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition) + .context("Recreating DirectX devices")?; + let resources = DirectXResources::new( + &devices, + self.width, + self.height, + self.hwnd, + disable_direct_composition, + ) + .context("Creating DirectX resources")?; + let globals = DirectXGlobalElements::new(&devices.device) + .context("Creating DirectXGlobalElements")?; + let pipelines = DirectXRenderPipelines::new(&devices.device) + .context("Creating DirectXRenderPipelines")?; + + let direct_composition = if disable_direct_composition { + None + } else { + let composition = + DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), self.hwnd)?; + composition.set_swap_chain(&resources.swap_chain)?; + Some(composition) + }; + + self.atlas + .handle_device_lost(&devices.device, &devices.device_context); + + unsafe { + devices + .device_context + .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None); + } + self.devices = Some(devices); + self.resources = Some(resources); + self.globals = globals; + self.pipelines = pipelines; + self.direct_composition = direct_composition; + self.skip_draws = true; + Ok(()) + } + + pub(crate) fn draw( + &mut self, + scene: &Scene, + background_appearance: WindowBackgroundAppearance, + ) -> Result<()> { + if self.skip_draws { + // skip drawing this frame, we just recovered from a device lost event + // and so likely do not have the textures anymore that are required for drawing + return Ok(()); + } + self.render(scene, background_appearance)?; + self.present() + } + + /// Clear the render target for `background_appearance` and encode every + /// primitive batch of `scene` into it, without presenting. Shared by + /// [`draw`](Self::draw) (which then presents) and + /// [`render_to_image`](Self::render_to_image) (which reads the target back + /// instead), so the two cannot drift. + fn render( + &mut self, + scene: &Scene, + background_appearance: WindowBackgroundAppearance, + ) -> Result<()> { + self.pre_draw(&match background_appearance { + WindowBackgroundAppearance::Opaque => [1.0f32; 4], + _ => [0.0f32; 4], + })?; + self.upload_scene_buffers(scene)?; + + let annotation = self + .devices + .as_ref() + .and_then(|devices| devices.annotation.clone()) + .filter(|annotation| unsafe { annotation.GetStatus().as_bool() }); + for batch in scene.batches() { + let _annotation = annotation + .as_ref() + .map(|annotation| Annotation::new(annotation, HSTRING::from(batch.label()))); + match batch { + PrimitiveBatch::Shadows(range) => self.draw_shadows(range.start, range.len()), + PrimitiveBatch::Quads(range) => self.draw_quads(range.start, range.len()), + PrimitiveBatch::Paths(range) => { + let paths = &scene.paths[range]; + self.draw_paths_to_intermediate(paths)?; + self.draw_paths_from_intermediate(paths) + } + PrimitiveBatch::Underlines(range) => self.draw_underlines(range.start, range.len()), + PrimitiveBatch::MonochromeSprites { texture_id, range } => { + self.draw_monochrome_sprites(texture_id, range.start, range.len()) + } + PrimitiveBatch::SubpixelSprites { texture_id, range } => { + self.draw_subpixel_sprites(texture_id, range.start, range.len()) + } + PrimitiveBatch::PolychromeSprites { texture_id, range } => { + self.draw_polychrome_sprites(texture_id, range.start, range.len()) + } + PrimitiveBatch::Surfaces(range) => self.draw_surfaces(&scene.surfaces[range]), + } + .with_context(|| { + format!( + "scene too large:\ + {} paths, {} shadows, {} quads, {} underlines, {} mono, {} subpixel, {} poly, {} surfaces", + scene.paths.len(), + scene.shadows.len(), + scene.quads.len(), + scene.underlines.len(), + scene.monochrome_sprites.len(), + scene.subpixel_sprites.len(), + scene.polychrome_sprites.len(), + scene.surfaces.len(), + ) + })?; + } + Ok(()) + } + + /// Render `scene` to an offscreen CPU image **without presenting** so + /// the window need never be shown or visible (the macOS headless path + /// goes through MetalRenderer; this is the Windows analogue). Draws into + /// the existing render target, copies it into a `D3D11_USAGE_STAGING` + /// texture, maps it, and converts BGRA to RGBA. + #[cfg(any(test, feature = "test-support"))] + pub(crate) fn render_to_image( + &mut self, + scene: &Scene, + background_appearance: WindowBackgroundAppearance, + ) -> Result { + // A pending device-lost recovery (`skip_draws`) leaves the atlas holding + // tile references from the previous device; drawing before the forced + // re-render rebuilds them panics in `DirectXAtlasTextures::texture`. + anyhow::ensure!( + !self.skip_draws, + "render_to_image unavailable while recovering from a lost device" + ); + self.render(scene, background_appearance)?; + + let devices = self.devices.as_ref().context("devices missing")?; + let device = &devices.device; + let context = &devices.device_context; + let resources = self.resources.as_ref().context("resources missing")?; + let render_target = resources + .render_target + .as_ref() + .context("render target missing")?; + + // A CPU-readable copy of the render target. + let mut desc = D3D11_TEXTURE2D_DESC::default(); + unsafe { render_target.GetDesc(&mut desc) }; + let width = desc.Width; + let height = desc.Height; + let staging_desc = D3D11_TEXTURE2D_DESC { + Usage: D3D11_USAGE_STAGING, + BindFlags: 0, + CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32, + MiscFlags: 0, + MipLevels: 1, + ArraySize: 1, + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + ..desc + }; + let mut staging: Option = None; + unsafe { device.CreateTexture2D(&staging_desc, None, Some(&mut staging))? }; + let staging = staging.context("creating staging texture")?; + unsafe { context.CopyResource(&staging, render_target) }; + + let mut mapped = D3D11_MAPPED_SUBRESOURCE::default(); + unsafe { context.Map(&staging, 0, D3D11_MAP_READ, 0, Some(&mut mapped))? }; + let row_bytes = (width as usize) * 4; + let mut pixels = vec![0u8; row_bytes * height as usize]; + // SAFETY: `Map` succeeded, so `pData` points at `RowPitch * height` + // readable bytes for as long as the mapping is held, and `RowPitch >= + // row_bytes` (it only ever adds trailing padding). `pixels` is sized + // `row_bytes * height`, so every copy stays in bounds on both sides, + // and the regions cannot overlap (`pixels` is a fresh allocation). + unsafe { + let src = mapped.pData as *const u8; + for row in 0..height as usize { + let s = src.add(row * mapped.RowPitch as usize); + let d = pixels.as_mut_ptr().add(row * row_bytes); + std::ptr::copy_nonoverlapping(s, d, row_bytes); + } + context.Unmap(&staging, 0); + } + // The render target is BGRA; image::RgbaImage expects RGBA. + for px in pixels.chunks_exact_mut(4) { + px.swap(0, 2); + } + image::RgbaImage::from_raw(width, height, pixels) + .context("Failed to build RgbaImage from staging readback") + } + + pub(crate) fn resize(&mut self, new_size: Size) -> Result<()> { + let width = new_size.width.0.max(1) as u32; + let height = new_size.height.0.max(1) as u32; + if self.width == width && self.height == height { + return Ok(()); + } + self.width = width; + self.height = height; + + // Clear the render target before resizing + let devices = self.devices.as_ref().context("devices missing")?; + unsafe { devices.device_context.OMSetRenderTargets(None, None) }; + let resources = self.resources.as_mut().context("resources missing")?; + resources.render_target.take(); + resources.render_target_view.take(); + + // Resizing the swap chain requires a call to the underlying DXGI adapter, which can return the device removed error. + // The app might have moved to a monitor that's attached to a different graphics device. + // When a graphics device is removed or reset, the desktop resolution often changes, resulting in a window size change. + // But here we just return the error, because we are handling device lost scenarios elsewhere. + unsafe { + resources + .swap_chain + .ResizeBuffers( + BUFFER_COUNT as u32, + width, + height, + RENDER_TARGET_FORMAT, + DXGI_SWAP_CHAIN_FLAG(0), + ) + .context("Failed to resize swap chain")?; + } + + resources.recreate_resources(devices, width, height)?; + + unsafe { + devices + .device_context + .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None); + } + + Ok(()) + } + + fn upload_scene_buffers(&mut self, scene: &Scene) -> Result<()> { + let devices = self.devices.as_ref().context("devices missing")?; + + if !scene.shadows.is_empty() { + self.pipelines.shadow_pipeline.update_buffer( + &devices.device, + &devices.device_context, + &scene.shadows, + )?; + } + + if !scene.quads.is_empty() { + self.pipelines.quad_pipeline.update_buffer( + &devices.device, + &devices.device_context, + &scene.quads, + )?; + } + + if !scene.underlines.is_empty() { + self.pipelines.underline_pipeline.update_buffer( + &devices.device, + &devices.device_context, + &scene.underlines, + )?; + } + + if !scene.monochrome_sprites.is_empty() { + self.pipelines.mono_sprites.update_buffer( + &devices.device, + &devices.device_context, + &scene.monochrome_sprites, + )?; + } + + if !scene.subpixel_sprites.is_empty() { + self.pipelines.subpixel_sprites.update_buffer( + &devices.device, + &devices.device_context, + &scene.subpixel_sprites, + )?; + } + + if !scene.polychrome_sprites.is_empty() { + self.pipelines.poly_sprites.update_buffer( + &devices.device, + &devices.device_context, + &scene.polychrome_sprites, + )?; + } + + Ok(()) + } + + fn draw_shadows(&mut self, start: usize, len: usize) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + self.pipelines.shadow_pipeline.draw_range( + &devices.device_context, + self.globals + .batch_params_buffer + .as_ref() + .context("batch params buffer missing")?, + start as u32, + len as u32, + ) + } + + fn draw_quads(&mut self, start: usize, len: usize) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + self.pipelines.quad_pipeline.draw_range( + &devices.device_context, + self.globals + .batch_params_buffer + .as_ref() + .context("batch params buffer missing")?, + start as u32, + len as u32, + ) + } + + fn draw_paths_to_intermediate(&mut self, paths: &[Path]) -> Result<()> { + if paths.is_empty() { + return Ok(()); + } + + let devices = self.devices.as_ref().context("devices missing")?; + let resources = self.resources.as_ref().context("resources missing")?; + // Clear intermediate MSAA texture + unsafe { + devices.device_context.ClearRenderTargetView( + resources.path_intermediate_msaa_view.as_ref().unwrap(), + &[0.0; 4], + ); + // Set intermediate MSAA texture as render target + devices.device_context.OMSetRenderTargets( + Some(slice::from_ref(&resources.path_intermediate_msaa_view)), + None, + ); + } + + // Collect all vertices and sprites for a single draw call + let mut vertices = Vec::new(); + + for path in paths { + vertices.extend(path.vertices.iter().map(|v| PathRasterizationSprite { + xy_position: v.xy_position, + st_position: v.st_position, + color: path.color, + bounds: path.clipped_bounds(), + })); + } + + self.pipelines.path_rasterization_pipeline.update_buffer( + &devices.device, + &devices.device_context, + &vertices, + )?; + + self.pipelines.path_rasterization_pipeline.draw( + &devices.device_context, + D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, + vertices.len() as u32, + 1, + )?; + + // Resolve MSAA to non-MSAA intermediate texture + unsafe { + devices.device_context.ResolveSubresource( + &resources.path_intermediate_texture, + 0, + &resources.path_intermediate_msaa_texture, + 0, + RENDER_TARGET_FORMAT, + ); + // Restore main render target + devices + .device_context + .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None); + } + + Ok(()) + } + + fn draw_paths_from_intermediate(&mut self, paths: &[Path]) -> Result<()> { + let Some(first_path) = paths.first() else { + return Ok(()); + }; + + // When copying paths from the intermediate texture to the drawable, + // each pixel must only be copied once, in case of transparent paths. + // + // If all paths have the same draw order, then their bounds are all + // disjoint, so we can copy each path's bounds individually. If this + // batch combines different draw orders, we perform a single copy + // for a minimal spanning rect. + let sprites = if paths.last().unwrap().order == first_path.order { + paths + .iter() + .map(|path| PathSprite { + bounds: path.clipped_bounds(), + }) + .collect::>() + } else { + let mut bounds = first_path.clipped_bounds(); + for path in paths.iter().skip(1) { + bounds = bounds.union(&path.clipped_bounds()); + } + vec![PathSprite { bounds }] + }; + + let devices = self.devices.as_ref().context("devices missing")?; + let resources = self.resources.as_ref().context("resources missing")?; + self.pipelines.path_sprite_pipeline.update_buffer( + &devices.device, + &devices.device_context, + &sprites, + )?; + + // Draw the sprites with the path texture + self.pipelines.path_sprite_pipeline.draw_with_texture( + &devices.device_context, + slice::from_ref(&resources.path_intermediate_srv), + slice::from_ref(&self.globals.sampler), + sprites.len() as u32, + ) + } + + fn draw_underlines(&mut self, start: usize, len: usize) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + self.pipelines.underline_pipeline.draw_range( + &devices.device_context, + self.globals + .batch_params_buffer + .as_ref() + .context("batch params buffer missing")?, + start as u32, + len as u32, + ) + } + + fn draw_monochrome_sprites( + &mut self, + texture_id: AtlasTextureId, + start: usize, + len: usize, + ) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + let Some(texture_view) = self.atlas.get_texture_view(texture_id) else { + return Ok(()); + }; + self.pipelines.mono_sprites.draw_range_with_texture( + &devices.device_context, + &texture_view, + self.globals + .batch_params_buffer + .as_ref() + .context("batch params buffer missing")?, + slice::from_ref(&self.globals.sampler), + start as u32, + len as u32, + ) + } + + fn draw_subpixel_sprites( + &mut self, + texture_id: AtlasTextureId, + start: usize, + len: usize, + ) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + let Some(texture_view) = self.atlas.get_texture_view(texture_id) else { + return Ok(()); + }; + self.pipelines.subpixel_sprites.draw_range_with_texture( + &devices.device_context, + &texture_view, + self.globals + .batch_params_buffer + .as_ref() + .context("batch params buffer missing")?, + slice::from_ref(&self.globals.sampler), + start as u32, + len as u32, + ) + } + + fn draw_polychrome_sprites( + &mut self, + texture_id: AtlasTextureId, + start: usize, + len: usize, + ) -> Result<()> { + if len == 0 { + return Ok(()); + } + let devices = self.devices.as_ref().context("devices missing")?; + let Some(texture_view) = self.atlas.get_texture_view(texture_id) else { + return Ok(()); + }; + self.pipelines.poly_sprites.draw_range_with_texture( + &devices.device_context, + &texture_view, + self.globals + .batch_params_buffer + .as_ref() + .context("batch params buffer missing")?, + slice::from_ref(&self.globals.sampler), + start as u32, + len as u32, + ) + } + + fn draw_surfaces(&mut self, surfaces: &[PaintSurface]) -> Result<()> { + if surfaces.is_empty() { + return Ok(()); + } + Ok(()) + } + + pub(crate) fn gpu_specs(&self) -> Result { + let devices = self.devices.as_ref().context("devices missing")?; + let desc = unsafe { devices.adapter.GetDesc1() }?; + let is_software_emulated = (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != 0; + let device_name = String::from_utf16_lossy(&desc.Description) + .trim_matches(char::from(0)) + .to_string(); + let driver_name = match desc.VendorId { + 0x10DE => "NVIDIA Corporation".to_string(), + 0x1002 => "AMD Corporation".to_string(), + 0x8086 => "Intel Corporation".to_string(), + id => format!("Unknown Vendor (ID: {:#X})", id), + }; + let driver_version = match desc.VendorId { + 0x10DE => nvidia::get_driver_version(), + 0x1002 => amd::get_driver_version(), + // For Intel and other vendors, we use the DXGI API to get the driver version. + _ => dxgi::get_driver_version(&devices.adapter), + } + .context("Failed to get gpu driver info") + .log_err() + .unwrap_or("Unknown Driver".to_string()); + Ok(GpuSpecs { + is_software_emulated, + device_name, + driver_name, + driver_info: driver_version, + }) + } + + pub(crate) fn get_font_info() -> &'static FontInfo { + static CACHED_FONT_INFO: OnceLock = OnceLock::new(); + CACHED_FONT_INFO.get_or_init(|| unsafe { + let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).unwrap(); + let render_params: IDWriteRenderingParams1 = + factory.CreateRenderingParams().unwrap().cast().unwrap(); + FontInfo { + gamma_ratios: gpui::get_gamma_correction_ratios(render_params.GetGamma()), + grayscale_enhanced_contrast: render_params.GetGrayscaleEnhancedContrast(), + subpixel_enhanced_contrast: render_params.GetEnhancedContrast(), + is_bgr: render_params.GetPixelGeometry() == DWRITE_PIXEL_GEOMETRY_BGR, + } + }) + } + + pub(crate) fn mark_drawable(&mut self) { + self.skip_draws = false; + } +} + +impl DirectXResources { + pub fn new( + devices: &DirectXRendererDevices, + width: u32, + height: u32, + hwnd: HWND, + disable_direct_composition: bool, + ) -> Result { + let swap_chain = if disable_direct_composition { + create_swap_chain(&devices.dxgi_factory, &devices.device, hwnd, width, height)? + } else { + create_swap_chain_for_composition( + &devices.dxgi_factory, + &devices.device, + width, + height, + )? + }; + + let ( + render_target, + render_target_view, + path_intermediate_texture, + path_intermediate_srv, + path_intermediate_msaa_texture, + path_intermediate_msaa_view, + viewport, + ) = create_resources(devices, &swap_chain, width, height)?; + set_rasterizer_state(&devices.device, &devices.device_context)?; + + Ok(Self { + swap_chain, + render_target: Some(render_target), + render_target_view, + path_intermediate_texture, + path_intermediate_msaa_texture, + path_intermediate_msaa_view, + path_intermediate_srv, + viewport, + }) + } + + #[inline] + fn recreate_resources( + &mut self, + devices: &DirectXRendererDevices, + width: u32, + height: u32, + ) -> Result<()> { + let ( + render_target, + render_target_view, + path_intermediate_texture, + path_intermediate_srv, + path_intermediate_msaa_texture, + path_intermediate_msaa_view, + viewport, + ) = create_resources(devices, &self.swap_chain, width, height)?; + self.render_target = Some(render_target); + self.render_target_view = render_target_view; + self.path_intermediate_texture = path_intermediate_texture; + self.path_intermediate_msaa_texture = path_intermediate_msaa_texture; + self.path_intermediate_msaa_view = path_intermediate_msaa_view; + self.path_intermediate_srv = path_intermediate_srv; + self.viewport = viewport; + Ok(()) + } +} + +impl DirectXRenderPipelines { + pub fn new(device: &ID3D11Device) -> Result { + let shadow_pipeline = PipelineState::new( + device, + "shadow_pipeline", + ShaderModule::Shadow, + 4, + create_blend_state(device)?, + )?; + let quad_pipeline = PipelineState::new( + device, + "quad_pipeline", + ShaderModule::Quad, + 64, + create_blend_state(device)?, + )?; + let path_rasterization_pipeline = PipelineState::new( + device, + "path_rasterization_pipeline", + ShaderModule::PathRasterization, + 32, + create_blend_state_for_path_rasterization(device)?, + )?; + let path_sprite_pipeline = PipelineState::new( + device, + "path_sprite_pipeline", + ShaderModule::PathSprite, + 4, + create_blend_state_for_path_sprite(device)?, + )?; + let underline_pipeline = PipelineState::new( + device, + "underline_pipeline", + ShaderModule::Underline, + 4, + create_blend_state(device)?, + )?; + let mono_sprites = PipelineState::new( + device, + "monochrome_sprite_pipeline", + ShaderModule::MonochromeSprite, + 512, + create_blend_state(device)?, + )?; + let subpixel_sprites = PipelineState::new( + device, + "subpixel_sprite_pipeline", + ShaderModule::SubpixelSprite, + 512, + create_blend_state_for_subpixel_rendering(device)?, + )?; + let poly_sprites = PipelineState::new( + device, + "polychrome_sprite_pipeline", + ShaderModule::PolychromeSprite, + 16, + create_blend_state(device)?, + )?; + + Ok(Self { + shadow_pipeline, + quad_pipeline, + path_rasterization_pipeline, + path_sprite_pipeline, + underline_pipeline, + mono_sprites, + subpixel_sprites, + poly_sprites, + }) + } +} + +impl DirectComposition { + pub fn new(dxgi_device: &IDXGIDevice, hwnd: HWND) -> Result { + let comp_device = get_comp_device(dxgi_device)?; + let comp_target = unsafe { comp_device.CreateTargetForHwnd(hwnd, true) }?; + let comp_visual = unsafe { comp_device.CreateVisual() }?; + + Ok(Self { + comp_device, + comp_target, + comp_visual, + }) + } + + pub fn set_swap_chain(&self, swap_chain: &IDXGISwapChain1) -> Result<()> { + unsafe { + self.comp_visual.SetContent(swap_chain)?; + self.comp_target.SetRoot(&self.comp_visual)?; + self.comp_device.Commit()?; + } + Ok(()) + } +} + +impl DirectXGlobalElements { + pub fn new(device: &ID3D11Device) -> Result { + let global_params_buffer = create_constant_buffer::(device)?; + let batch_params_buffer = create_constant_buffer::(device)?; + + let sampler = unsafe { + let desc = D3D11_SAMPLER_DESC { + Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR, + AddressU: D3D11_TEXTURE_ADDRESS_WRAP, + AddressV: D3D11_TEXTURE_ADDRESS_WRAP, + AddressW: D3D11_TEXTURE_ADDRESS_WRAP, + MipLODBias: 0.0, + MaxAnisotropy: 1, + ComparisonFunc: D3D11_COMPARISON_ALWAYS, + BorderColor: [0.0; 4], + MinLOD: 0.0, + MaxLOD: D3D11_FLOAT32_MAX, + }; + let mut output = None; + device.CreateSamplerState(&desc, Some(&mut output))?; + output + }; + + Ok(Self { + global_params_buffer, + batch_params_buffer, + sampler, + }) + } +} + +#[derive(Debug, Default)] +#[repr(C)] +struct GlobalParams { + gamma_ratios: [f32; 4], + viewport_size: [f32; 2], + grayscale_enhanced_contrast: f32, + subpixel_enhanced_contrast: f32, + is_bgr: u32, + _pad: [u32; 3], +} + +#[derive(Clone, Copy, Debug, Default)] +#[repr(C, align(16))] +struct BatchParams { + start_index: u32, + _padding: [u32; 3], +} + +const _: () = assert!(std::mem::size_of::() == 16); + +struct PipelineState { + label: &'static str, + vertex: ID3D11VertexShader, + fragment: ID3D11PixelShader, + buffer: ID3D11Buffer, + buffer_size: usize, + view: Option, + blend_state: ID3D11BlendState, + _marker: std::marker::PhantomData, +} + +impl PipelineState { + fn new( + device: &ID3D11Device, + label: &'static str, + shader_module: ShaderModule, + buffer_size: usize, + blend_state: ID3D11BlendState, + ) -> Result { + let vertex = { + let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Vertex)?; + create_vertex_shader(device, raw_shader.as_bytes())? + }; + let fragment = { + let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Fragment)?; + create_fragment_shader(device, raw_shader.as_bytes())? + }; + let buffer = create_buffer(device, std::mem::size_of::(), buffer_size)?; + let view = create_buffer_view(device, &buffer)?; + + Ok(PipelineState { + label, + vertex, + fragment, + buffer, + buffer_size, + view, + blend_state, + _marker: std::marker::PhantomData, + }) + } + + fn update_buffer( + &mut self, + device: &ID3D11Device, + device_context: &ID3D11DeviceContext, + data: &[T], + ) -> Result<()> { + if self.buffer_size < data.len() { + let element_size = std::mem::size_of::(); + let required_size = std::mem::size_of_val(data); + anyhow::ensure!( + required_size <= MAX_INSTANCE_BUFFER_SIZE, + "{} buffer needs {required_size} bytes, above the maximum of {MAX_INSTANCE_BUFFER_SIZE}", + self.label + ); + let new_buffer_size = data + .len() + .next_power_of_two() + .min(MAX_INSTANCE_BUFFER_SIZE / element_size); + log::debug!( + "Updating {} buffer size from {} to {}", + self.label, + self.buffer_size, + new_buffer_size + ); + let buffer = create_buffer(device, std::mem::size_of::(), new_buffer_size)?; + let view = create_buffer_view(device, &buffer)?; + self.buffer = buffer; + self.view = view; + self.buffer_size = new_buffer_size; + } + update_buffer(device_context, &self.buffer, data) + } + + fn draw( + &self, + device_context: &ID3D11DeviceContext, + topology: D3D_PRIMITIVE_TOPOLOGY, + vertex_count: u32, + instance_count: u32, + ) -> Result<()> { + set_pipeline_state( + device_context, + slice::from_ref(&self.view), + topology, + &self.vertex, + &self.fragment, + &self.blend_state, + ); + unsafe { + device_context.DrawInstanced(vertex_count, instance_count, 0, 0); + } + Ok(()) + } + + fn draw_with_texture( + &self, + device_context: &ID3D11DeviceContext, + texture: &[Option], + sampler: &[Option], + instance_count: u32, + ) -> Result<()> { + set_pipeline_state( + device_context, + slice::from_ref(&self.view), + D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, + &self.vertex, + &self.fragment, + &self.blend_state, + ); + unsafe { + device_context.PSSetSamplers(0, Some(sampler)); + device_context.VSSetShaderResources(0, Some(texture)); + device_context.PSSetShaderResources(0, Some(texture)); + + device_context.DrawInstanced(4, instance_count, 0, 0); + } + Ok(()) + } + + fn draw_range( + &self, + device_context: &ID3D11DeviceContext, + batch_params_buffer: &ID3D11Buffer, + first_instance: u32, + instance_count: u32, + ) -> Result<()> { + anyhow::ensure!( + first_instance as usize + instance_count as usize <= self.buffer_size, + "DirectX instance range exceeds the {} buffer", + self.label + ); + update_batch_start(device_context, batch_params_buffer, first_instance)?; + set_pipeline_state( + device_context, + slice::from_ref(&self.view), + D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, + &self.vertex, + &self.fragment, + &self.blend_state, + ); + unsafe { + device_context.DrawInstanced(4, instance_count, 0, 0); + } + Ok(()) + } + + fn draw_range_with_texture( + &self, + device_context: &ID3D11DeviceContext, + texture: &[Option], + batch_params_buffer: &ID3D11Buffer, + sampler: &[Option], + first_instance: u32, + instance_count: u32, + ) -> Result<()> { + anyhow::ensure!( + first_instance as usize + instance_count as usize <= self.buffer_size, + "DirectX instance range exceeds the {} buffer", + self.label + ); + update_batch_start(device_context, batch_params_buffer, first_instance)?; + set_pipeline_state( + device_context, + slice::from_ref(&self.view), + D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, + &self.vertex, + &self.fragment, + &self.blend_state, + ); + unsafe { + device_context.PSSetSamplers(0, Some(sampler)); + device_context.VSSetShaderResources(0, Some(texture)); + device_context.PSSetShaderResources(0, Some(texture)); + device_context.DrawInstanced(4, instance_count, 0, 0); + } + Ok(()) + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +struct PathRasterizationSprite { + xy_position: Point, + st_position: Point, + color: Background, + bounds: Bounds, +} + +#[derive(Clone, Copy)] +#[repr(C)] +struct PathSprite { + bounds: Bounds, +} + +impl Drop for DirectXRenderer { + fn drop(&mut self) { + #[cfg(debug_assertions)] + if let Some(devices) = &self.devices { + report_live_objects(&devices.device).ok(); + } + } +} + +#[inline] +fn get_comp_device(dxgi_device: &IDXGIDevice) -> Result { + Ok(unsafe { DCompositionCreateDevice(dxgi_device)? }) +} + +fn create_swap_chain_for_composition( + dxgi_factory: &IDXGIFactory6, + device: &ID3D11Device, + width: u32, + height: u32, +) -> Result { + let desc = DXGI_SWAP_CHAIN_DESC1 { + Width: width, + Height: height, + Format: RENDER_TARGET_FORMAT, + Stereo: false.into(), + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, + BufferCount: BUFFER_COUNT as u32, + // Composition SwapChains only support the DXGI_SCALING_STRETCH Scaling. + Scaling: DXGI_SCALING_STRETCH, + SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED, + Flags: 0, + }; + Ok(unsafe { dxgi_factory.CreateSwapChainForComposition(device, &desc, None)? }) +} + +fn create_swap_chain( + dxgi_factory: &IDXGIFactory6, + device: &ID3D11Device, + hwnd: HWND, + width: u32, + height: u32, +) -> Result { + use windows::Win32::Graphics::Dxgi::DXGI_MWA_NO_ALT_ENTER; + + let desc = DXGI_SWAP_CHAIN_DESC1 { + Width: width, + Height: height, + Format: RENDER_TARGET_FORMAT, + Stereo: false.into(), + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, + BufferCount: BUFFER_COUNT as u32, + Scaling: DXGI_SCALING_NONE, + SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + AlphaMode: DXGI_ALPHA_MODE_IGNORE, + Flags: 0, + }; + let swap_chain = + unsafe { dxgi_factory.CreateSwapChainForHwnd(device, hwnd, &desc, None, None) }?; + unsafe { dxgi_factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) }?; + Ok(swap_chain) +} + +#[inline] +fn create_resources( + devices: &DirectXRendererDevices, + swap_chain: &IDXGISwapChain1, + width: u32, + height: u32, +) -> Result<( + ID3D11Texture2D, + Option, + ID3D11Texture2D, + Option, + ID3D11Texture2D, + Option, + D3D11_VIEWPORT, +)> { + let (render_target, render_target_view) = + create_render_target_and_its_view(swap_chain, &devices.device)?; + let (path_intermediate_texture, path_intermediate_srv) = + create_path_intermediate_texture(&devices.device, width, height)?; + let (path_intermediate_msaa_texture, path_intermediate_msaa_view) = + create_path_intermediate_msaa_texture_and_view(&devices.device, width, height)?; + let viewport = D3D11_VIEWPORT { + TopLeftX: 0.0, + TopLeftY: 0.0, + Width: width as f32, + Height: height as f32, + MinDepth: 0.0, + MaxDepth: 1.0, + }; + Ok(( + render_target, + render_target_view, + path_intermediate_texture, + path_intermediate_srv, + path_intermediate_msaa_texture, + path_intermediate_msaa_view, + viewport, + )) +} + +#[inline] +fn create_render_target_and_its_view( + swap_chain: &IDXGISwapChain1, + device: &ID3D11Device, +) -> Result<(ID3D11Texture2D, Option)> { + let render_target: ID3D11Texture2D = unsafe { swap_chain.GetBuffer(0) }?; + let mut render_target_view = None; + unsafe { device.CreateRenderTargetView(&render_target, None, Some(&mut render_target_view))? }; + Ok((render_target, render_target_view)) +} + +#[inline] +fn create_path_intermediate_texture( + device: &ID3D11Device, + width: u32, + height: u32, +) -> Result<(ID3D11Texture2D, Option)> { + let texture = unsafe { + let mut output = None; + let desc = D3D11_TEXTURE2D_DESC { + Width: width, + Height: height, + MipLevels: 1, + ArraySize: 1, + Format: RENDER_TARGET_FORMAT, + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + Usage: D3D11_USAGE_DEFAULT, + BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32, + CPUAccessFlags: 0, + MiscFlags: 0, + }; + device.CreateTexture2D(&desc, None, Some(&mut output))?; + output.unwrap() + }; + + let mut shader_resource_view = None; + unsafe { device.CreateShaderResourceView(&texture, None, Some(&mut shader_resource_view))? }; + + Ok((texture, Some(shader_resource_view.unwrap()))) +} + +#[inline] +fn create_path_intermediate_msaa_texture_and_view( + device: &ID3D11Device, + width: u32, + height: u32, +) -> Result<(ID3D11Texture2D, Option)> { + let msaa_texture = unsafe { + let mut output = None; + let desc = D3D11_TEXTURE2D_DESC { + Width: width, + Height: height, + MipLevels: 1, + ArraySize: 1, + Format: RENDER_TARGET_FORMAT, + SampleDesc: DXGI_SAMPLE_DESC { + Count: PATH_MULTISAMPLE_COUNT, + Quality: D3D11_STANDARD_MULTISAMPLE_PATTERN.0 as u32, + }, + Usage: D3D11_USAGE_DEFAULT, + BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32, + CPUAccessFlags: 0, + MiscFlags: 0, + }; + device.CreateTexture2D(&desc, None, Some(&mut output))?; + output.unwrap() + }; + let mut msaa_view = None; + unsafe { device.CreateRenderTargetView(&msaa_texture, None, Some(&mut msaa_view))? }; + Ok((msaa_texture, Some(msaa_view.unwrap()))) +} + +#[inline] +fn set_rasterizer_state(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Result<()> { + let desc = D3D11_RASTERIZER_DESC { + FillMode: D3D11_FILL_SOLID, + CullMode: D3D11_CULL_NONE, + FrontCounterClockwise: false.into(), + DepthBias: 0, + DepthBiasClamp: 0.0, + SlopeScaledDepthBias: 0.0, + DepthClipEnable: true.into(), + ScissorEnable: false.into(), + MultisampleEnable: true.into(), + AntialiasedLineEnable: false.into(), + }; + let rasterizer_state = unsafe { + let mut state = None; + device.CreateRasterizerState(&desc, Some(&mut state))?; + state.unwrap() + }; + unsafe { device_context.RSSetState(&rasterizer_state) }; + Ok(()) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_blend_desc +#[inline] +fn create_blend_state(device: &ID3D11Device) -> Result { + let mut desc = D3D11_BLEND_DESC::default(); + desc.RenderTarget[0].BlendEnable = true.into(); + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8; + unsafe { + let mut state = None; + device.CreateBlendState(&desc, Some(&mut state))?; + Ok(state.unwrap()) + } +} + +#[inline] +fn create_blend_state_for_subpixel_rendering(device: &ID3D11Device) -> Result { + let mut desc = D3D11_BLEND_DESC::default(); + desc.RenderTarget[0].BlendEnable = true.into(); + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC1_COLOR; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC1_COLOR; + // It does not make sense to draw transparent subpixel-rendered text, since it cannot be meaningfully alpha-blended onto anything else. + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; + desc.RenderTarget[0].RenderTargetWriteMask = + D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8 & !D3D11_COLOR_WRITE_ENABLE_ALPHA.0 as u8; + + unsafe { + let mut state = None; + device.CreateBlendState(&desc, Some(&mut state))?; + Ok(state.unwrap()) + } +} + +#[inline] +fn create_blend_state_for_path_rasterization(device: &ID3D11Device) -> Result { + // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display + // device performs the blend in linear space, which is ideal. + let mut desc = D3D11_BLEND_DESC::default(); + desc.RenderTarget[0].BlendEnable = true.into(); + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8; + unsafe { + let mut state = None; + device.CreateBlendState(&desc, Some(&mut state))?; + Ok(state.unwrap()) + } +} + +#[inline] +fn create_blend_state_for_path_sprite(device: &ID3D11Device) -> Result { + // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display + // device performs the blend in linear space, which is ideal. + let mut desc = D3D11_BLEND_DESC::default(); + desc.RenderTarget[0].BlendEnable = true.into(); + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8; + unsafe { + let mut state = None; + device.CreateBlendState(&desc, Some(&mut state))?; + Ok(state.unwrap()) + } +} + +#[inline] +fn create_vertex_shader(device: &ID3D11Device, bytes: &[u8]) -> Result { + unsafe { + let mut shader = None; + device.CreateVertexShader(bytes, None, Some(&mut shader))?; + Ok(shader.unwrap()) + } +} + +#[inline] +fn create_fragment_shader(device: &ID3D11Device, bytes: &[u8]) -> Result { + unsafe { + let mut shader = None; + device.CreatePixelShader(bytes, None, Some(&mut shader))?; + Ok(shader.unwrap()) + } +} + +#[inline] +fn create_constant_buffer(device: &ID3D11Device) -> Result> { + const { assert!(std::mem::size_of::() != 0 && std::mem::size_of::().is_multiple_of(16)) }; + let desc = D3D11_BUFFER_DESC { + ByteWidth: std::mem::size_of::() as u32, + Usage: D3D11_USAGE_DYNAMIC, + BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32, + CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, + MiscFlags: 0, + StructureByteStride: 0, + }; + let mut buffer = None; + unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?; + Ok(buffer) +} + +#[inline] +fn create_buffer( + device: &ID3D11Device, + element_size: usize, + buffer_size: usize, +) -> Result { + let desc = D3D11_BUFFER_DESC { + ByteWidth: (element_size * buffer_size) as u32, + Usage: D3D11_USAGE_DYNAMIC, + BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32, + CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, + MiscFlags: D3D11_RESOURCE_MISC_BUFFER_STRUCTURED.0 as u32, + StructureByteStride: element_size as u32, + }; + let mut buffer = None; + unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?; + Ok(buffer.unwrap()) +} + +#[inline] +fn create_buffer_view( + device: &ID3D11Device, + buffer: &ID3D11Buffer, +) -> Result> { + let mut view = None; + unsafe { device.CreateShaderResourceView(buffer, None, Some(&mut view)) }?; + Ok(view) +} + +#[inline] +fn update_buffer( + device_context: &ID3D11DeviceContext, + buffer: &ID3D11Buffer, + data: &[T], +) -> Result<()> { + unsafe { + let mut dest = std::mem::zeroed(); + device_context.Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut dest))?; + std::ptr::copy_nonoverlapping(data.as_ptr(), dest.pData as _, data.len()); + device_context.Unmap(buffer, 0); + } + Ok(()) +} + +#[inline] +fn update_batch_start( + device_context: &ID3D11DeviceContext, + buffer: &ID3D11Buffer, + first_instance: u32, +) -> Result<()> { + update_buffer( + device_context, + buffer, + &[BatchParams { + start_index: first_instance, + _padding: [0; 3], + }], + ) +} + +#[inline] +fn set_pipeline_state( + device_context: &ID3D11DeviceContext, + buffer_view: &[Option], + topology: D3D_PRIMITIVE_TOPOLOGY, + vertex_shader: &ID3D11VertexShader, + fragment_shader: &ID3D11PixelShader, + blend_state: &ID3D11BlendState, +) { + unsafe { + device_context.VSSetShaderResources(1, Some(buffer_view)); + device_context.PSSetShaderResources(1, Some(buffer_view)); + device_context.IASetPrimitiveTopology(topology); + device_context.VSSetShader(vertex_shader, None); + device_context.PSSetShader(fragment_shader, None); + device_context.OMSetBlendState(blend_state, None, 0xFFFFFFFF); + } +} + +#[cfg(debug_assertions)] +fn report_live_objects(device: &ID3D11Device) -> Result<()> { + let debug_device: ID3D11Debug = device.cast()?; + unsafe { + debug_device.ReportLiveDeviceObjects(D3D11_RLDO_DETAIL)?; + } + Ok(()) +} + +const BUFFER_COUNT: usize = 3; + +pub(crate) mod shader_resources { + use anyhow::Result; + + #[cfg(debug_assertions)] + use windows::{ + Win32::Graphics::Direct3D::{ + Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile}, + ID3DBlob, + }, + core::{HSTRING, PCSTR}, + }; + + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub(crate) enum ShaderModule { + Quad, + Shadow, + Underline, + PathRasterization, + PathSprite, + MonochromeSprite, + SubpixelSprite, + PolychromeSprite, + EmojiRasterization, + } + + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub(crate) enum ShaderTarget { + Vertex, + Fragment, + } + + pub(crate) struct RawShaderBytes<'t> { + inner: &'t [u8], + + #[cfg(debug_assertions)] + _blob: ID3DBlob, + } + + impl<'t> RawShaderBytes<'t> { + pub(crate) fn new(module: ShaderModule, target: ShaderTarget) -> Result { + #[cfg(not(debug_assertions))] + { + Ok(Self::from_bytes(module, target)) + } + #[cfg(debug_assertions)] + { + let blob = build_shader_blob(module, target)?; + let inner = unsafe { + std::slice::from_raw_parts( + blob.GetBufferPointer() as *const u8, + blob.GetBufferSize(), + ) + }; + Ok(Self { inner, _blob: blob }) + } + } + + pub(crate) fn as_bytes(&'t self) -> &'t [u8] { + self.inner + } + + #[cfg(not(debug_assertions))] + fn from_bytes(module: ShaderModule, target: ShaderTarget) -> Self { + let bytes = match module { + ShaderModule::Quad => match target { + ShaderTarget::Vertex => QUAD_VERTEX_BYTES, + ShaderTarget::Fragment => QUAD_FRAGMENT_BYTES, + }, + ShaderModule::Shadow => match target { + ShaderTarget::Vertex => SHADOW_VERTEX_BYTES, + ShaderTarget::Fragment => SHADOW_FRAGMENT_BYTES, + }, + ShaderModule::Underline => match target { + ShaderTarget::Vertex => UNDERLINE_VERTEX_BYTES, + ShaderTarget::Fragment => UNDERLINE_FRAGMENT_BYTES, + }, + ShaderModule::PathRasterization => match target { + ShaderTarget::Vertex => PATH_RASTERIZATION_VERTEX_BYTES, + ShaderTarget::Fragment => PATH_RASTERIZATION_FRAGMENT_BYTES, + }, + ShaderModule::PathSprite => match target { + ShaderTarget::Vertex => PATH_SPRITE_VERTEX_BYTES, + ShaderTarget::Fragment => PATH_SPRITE_FRAGMENT_BYTES, + }, + ShaderModule::MonochromeSprite => match target { + ShaderTarget::Vertex => MONOCHROME_SPRITE_VERTEX_BYTES, + ShaderTarget::Fragment => MONOCHROME_SPRITE_FRAGMENT_BYTES, + }, + ShaderModule::SubpixelSprite => match target { + ShaderTarget::Vertex => SUBPIXEL_SPRITE_VERTEX_BYTES, + ShaderTarget::Fragment => SUBPIXEL_SPRITE_FRAGMENT_BYTES, + }, + ShaderModule::PolychromeSprite => match target { + ShaderTarget::Vertex => POLYCHROME_SPRITE_VERTEX_BYTES, + ShaderTarget::Fragment => POLYCHROME_SPRITE_FRAGMENT_BYTES, + }, + ShaderModule::EmojiRasterization => match target { + ShaderTarget::Vertex => EMOJI_RASTERIZATION_VERTEX_BYTES, + ShaderTarget::Fragment => EMOJI_RASTERIZATION_FRAGMENT_BYTES, + }, + }; + Self { inner: bytes } + } + } + + #[cfg(debug_assertions)] + pub(super) fn build_shader_blob(entry: ShaderModule, target: ShaderTarget) -> Result { + unsafe { + use windows::Win32::Graphics::{ + Direct3D::ID3DInclude, Hlsl::D3D_COMPILE_STANDARD_FILE_INCLUDE, + }; + + let shader_name = if matches!(entry, ShaderModule::EmojiRasterization) { + "color_text_raster.hlsl" + } else { + "shaders.hlsl" + }; + + let entry = format!( + "{}_{}\0", + entry.as_str(), + match target { + ShaderTarget::Vertex => "vertex", + ShaderTarget::Fragment => "fragment", + } + ); + let target = match target { + ShaderTarget::Vertex => "vs_4_1\0", + ShaderTarget::Fragment => "ps_4_1\0", + }; + + let mut compile_blob = None; + let mut error_blob = None; + let shader_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join(&format!("src/{}", shader_name)) + .canonicalize()?; + + let entry_point = PCSTR::from_raw(entry.as_ptr()); + let target_cstr = PCSTR::from_raw(target.as_ptr()); + + // really dirty trick because winapi bindings are unhappy otherwise + let include_handler = &std::mem::transmute::( + D3D_COMPILE_STANDARD_FILE_INCLUDE as usize, + ); + + let ret = D3DCompileFromFile( + &HSTRING::from(shader_path.to_str().unwrap()), + None, + include_handler, + entry_point, + target_cstr, + D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, + 0, + &mut compile_blob, + Some(&mut error_blob), + ); + if ret.is_err() { + let Some(error_blob) = error_blob else { + return Err(anyhow::anyhow!("{ret:?}")); + }; + + let error_string = + std::ffi::CStr::from_ptr(error_blob.GetBufferPointer() as *const i8) + .to_string_lossy(); + log::error!("Shader compile error: {}", error_string); + return Err(anyhow::anyhow!("Compile error: {}", error_string)); + } + Ok(compile_blob.unwrap()) + } + } + + #[cfg(not(debug_assertions))] + include!(concat!(env!("OUT_DIR"), "/shaders_bytes.rs")); + + #[cfg(debug_assertions)] + impl ShaderModule { + pub fn as_str(self) -> &'static str { + match self { + ShaderModule::Quad => "quad", + ShaderModule::Shadow => "shadow", + ShaderModule::Underline => "underline", + ShaderModule::PathRasterization => "path_rasterization", + ShaderModule::PathSprite => "path_sprite", + ShaderModule::MonochromeSprite => "monochrome_sprite", + ShaderModule::SubpixelSprite => "subpixel_sprite", + ShaderModule::PolychromeSprite => "polychrome_sprite", + ShaderModule::EmojiRasterization => "emoji_rasterization", + } + } + } +} + +mod nvidia { + use std::{ + ffi::CStr, + os::raw::{c_char, c_int, c_uint}, + }; + + use anyhow::Result; + use windows::{Win32::System::LibraryLoader::GetProcAddress, core::s}; + + use crate::with_dll_library; + + // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L180 + const NVAPI_SHORT_STRING_MAX: usize = 64; + + // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L235 + #[allow(non_camel_case_types)] + type NvAPI_ShortString = [c_char; NVAPI_SHORT_STRING_MAX]; + + // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L447 + #[allow(non_camel_case_types)] + type NvAPI_SYS_GetDriverAndBranchVersion_t = unsafe extern "C" fn( + driver_version: *mut c_uint, + build_branch_string: *mut NvAPI_ShortString, + ) -> c_int; + + pub(super) fn get_driver_version() -> Result { + #[cfg(target_pointer_width = "64")] + let nvidia_dll_name = s!("nvapi64.dll"); + #[cfg(target_pointer_width = "32")] + let nvidia_dll_name = s!("nvapi.dll"); + + with_dll_library(nvidia_dll_name, |nvidia_dll| unsafe { + let nvapi_query_addr = GetProcAddress(nvidia_dll, s!("nvapi_QueryInterface")) + .ok_or_else(|| anyhow::anyhow!("Failed to get nvapi_QueryInterface address"))?; + let nvapi_query: extern "C" fn(u32) -> *mut () = std::mem::transmute(nvapi_query_addr); + + // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_interface.h#L41 + let nvapi_get_driver_version_ptr = nvapi_query(0x2926aaad); + if nvapi_get_driver_version_ptr.is_null() { + anyhow::bail!("Failed to get NVIDIA driver version function pointer"); + } + let nvapi_get_driver_version: NvAPI_SYS_GetDriverAndBranchVersion_t = + std::mem::transmute(nvapi_get_driver_version_ptr); + + let mut driver_version: c_uint = 0; + let mut build_branch_string: NvAPI_ShortString = [0; NVAPI_SHORT_STRING_MAX]; + let result = nvapi_get_driver_version( + &mut driver_version as *mut c_uint, + &mut build_branch_string as *mut NvAPI_ShortString, + ); + + if result != 0 { + anyhow::bail!( + "Failed to get NVIDIA driver version, error code: {}", + result + ); + } + let major = driver_version / 100; + let minor = driver_version % 100; + let branch_string = CStr::from_ptr(build_branch_string.as_ptr()); + Ok(format!( + "{}.{} {}", + major, + minor, + branch_string.to_string_lossy() + )) + }) + } +} + +mod amd { + use std::os::raw::{c_char, c_int, c_void}; + + use anyhow::Result; + use windows::{Win32::System::LibraryLoader::GetProcAddress, core::s}; + + use crate::with_dll_library; + + // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L145 + const AGS_CURRENT_VERSION: i32 = (6 << 22) | (3 << 12); + + // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L204 + // This is an opaque type, using struct to represent it properly for FFI + #[repr(C)] + struct AGSContext { + _private: [u8; 0], + } + + #[repr(C)] + pub struct AGSGPUInfo { + pub driver_version: *const c_char, + pub radeon_software_version: *const c_char, + pub num_devices: c_int, + pub devices: *mut c_void, + } + + // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L429 + #[allow(non_camel_case_types)] + type agsInitialize_t = unsafe extern "C" fn( + version: c_int, + config: *const c_void, + context: *mut *mut AGSContext, + gpu_info: *mut AGSGPUInfo, + ) -> c_int; + + // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L436 + #[allow(non_camel_case_types)] + type agsDeInitialize_t = unsafe extern "C" fn(context: *mut AGSContext) -> c_int; + + pub(super) fn get_driver_version() -> Result { + #[cfg(target_pointer_width = "64")] + let amd_dll_name = s!("amd_ags_x64.dll"); + #[cfg(target_pointer_width = "32")] + let amd_dll_name = s!("amd_ags_x86.dll"); + + with_dll_library(amd_dll_name, |amd_dll| unsafe { + let ags_initialize_addr = GetProcAddress(amd_dll, s!("agsInitialize")) + .ok_or_else(|| anyhow::anyhow!("Failed to get agsInitialize address"))?; + let ags_deinitialize_addr = GetProcAddress(amd_dll, s!("agsDeInitialize")) + .ok_or_else(|| anyhow::anyhow!("Failed to get agsDeInitialize address"))?; + + let ags_initialize: agsInitialize_t = std::mem::transmute(ags_initialize_addr); + let ags_deinitialize: agsDeInitialize_t = std::mem::transmute(ags_deinitialize_addr); + + let mut context: *mut AGSContext = std::ptr::null_mut(); + let mut gpu_info: AGSGPUInfo = AGSGPUInfo { + driver_version: std::ptr::null(), + radeon_software_version: std::ptr::null(), + num_devices: 0, + devices: std::ptr::null_mut(), + }; + + let result = ags_initialize( + AGS_CURRENT_VERSION, + std::ptr::null(), + &mut context, + &mut gpu_info, + ); + if result != 0 { + anyhow::bail!("Failed to initialize AMD AGS, error code: {}", result); + } + + // Vulkan actually returns this as the driver version + let software_version = if !gpu_info.radeon_software_version.is_null() { + std::ffi::CStr::from_ptr(gpu_info.radeon_software_version) + .to_string_lossy() + .into_owned() + } else { + "Unknown Radeon Software Version".to_string() + }; + + let driver_version = if !gpu_info.driver_version.is_null() { + std::ffi::CStr::from_ptr(gpu_info.driver_version) + .to_string_lossy() + .into_owned() + } else { + "Unknown Radeon Driver Version".to_string() + }; + + ags_deinitialize(context); + Ok(format!("{} ({})", software_version, driver_version)) + }) + } +} + +mod dxgi { + use windows::{ + Win32::Graphics::Dxgi::{IDXGIAdapter1, IDXGIDevice}, + core::Interface, + }; + + pub(super) fn get_driver_version(adapter: &IDXGIAdapter1) -> anyhow::Result { + let number = unsafe { adapter.CheckInterfaceSupport(&IDXGIDevice::IID as _) }?; + Ok(format!( + "{}.{}.{}.{}", + number >> 48, + (number >> 32) & 0xFFFF, + (number >> 16) & 0xFFFF, + number & 0xFFFF + )) + } +} diff --git a/vendor/gpui-pre-windows/src/dispatcher.rs b/vendor/gpui-pre-windows/src/dispatcher.rs new file mode 100644 index 0000000..55f18b5 --- /dev/null +++ b/vendor/gpui-pre-windows/src/dispatcher.rs @@ -0,0 +1,191 @@ +use std::{ + ffi::c_void, + ptr::NonNull, + sync::atomic::{AtomicBool, Ordering}, + thread::{ThreadId, current}, + time::Duration, +}; + +use anyhow::Context; +use gpui_util::ResultExt; +use windows::Win32::{ + Foundation::{FILETIME, LPARAM, WPARAM}, + Media::{timeBeginPeriod, timeEndPeriod}, + System::Threading::{ + CloseThreadpoolTimer, CreateThreadpoolTimer, GetCurrentThread, PTP_CALLBACK_INSTANCE, + PTP_TIMER, SetThreadPriority, SetThreadpoolTimer, THREAD_PRIORITY_TIME_CRITICAL, + TP_CALLBACK_ENVIRON_V3, TP_CALLBACK_PRIORITY, TP_CALLBACK_PRIORITY_HIGH, + TP_CALLBACK_PRIORITY_LOW, TP_CALLBACK_PRIORITY_NORMAL, TrySubmitThreadpoolCallback, + }, + UI::WindowsAndMessaging::PostMessageW, +}; + +use crate::{HWND, SafeHwnd, WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD}; +use gpui::{ + PlatformDispatcher, Priority, PriorityQueueSender, RunnableVariant, TimerResolutionGuard, +}; + +pub(crate) struct WindowsDispatcher { + pub(crate) wake_posted: AtomicBool, + main_sender: PriorityQueueSender, + main_thread_id: ThreadId, + pub(crate) platform_window_handle: SafeHwnd, + validation_number: usize, +} + +impl WindowsDispatcher { + pub(crate) fn new( + main_sender: PriorityQueueSender, + 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: TP_CALLBACK_PRIORITY, runnable: RunnableVariant) { + let environ = TP_CALLBACK_ENVIRON_V3 { + Version: 3, + CallbackPriority: priority, + Size: size_of::() as u32, + ..Default::default() + }; + + // If the thread pool never runs our callback, the matching `from_raw` is never called, which leaks the runnable. + // Dropping the scheduled runnable would cancel its task and make the next poll of any awaiter panic. Since we expect + // the scenario to usually happen during shutdown, this leak is acceptable. + let context = runnable.into_raw().as_ptr() as *mut c_void; + + unsafe { + TrySubmitThreadpoolCallback(Some(run_work_callback), Some(context), Some(&environ)) + .log_err(); + } + } + + fn dispatch_on_threadpool_after(&self, runnable: RunnableVariant, duration: Duration) { + let context = runnable.into_raw().as_ptr() as *mut c_void; + + unsafe { + if let Ok(timer) = CreateThreadpoolTimer(Some(run_timer_callback), Some(context), None) + { + // Negative FILETIME expresses a relative delay in 100ns ticks + let ticks = (duration.as_nanos() / 100).min(i64::MAX as u128) as i64; + let due = (-ticks) as u64; + let due_time = FILETIME { + dwLowDateTime: due as u32, + dwHighDateTime: (due >> 32) as u32, + }; + SetThreadpoolTimer(timer, Some(&due_time), 0, None); + } + } + } + + #[inline(always)] + pub(crate) fn execute_runnable(runnable: RunnableVariant) { + let location = runnable.metadata().location; + let spawned = runnable.metadata().spawned; + gpui::profiler::update_running_task(spawned, location); + runnable.run(); + gpui::profiler::save_task_timing(); + } +} + +impl PlatformDispatcher for WindowsDispatcher { + 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 => TP_CALLBACK_PRIORITY_HIGH, + Priority::Medium => TP_CALLBACK_PRIORITY_NORMAL, + Priority::Low => TP_CALLBACK_PRIORITY_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) { + 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); + } + gpui_util::defer(Box::new(|| unsafe { + timeEndPeriod(1); + })) + } +} + +unsafe extern "system" fn run_work_callback( + _instance: PTP_CALLBACK_INSTANCE, + context: *mut c_void, +) { + let runnable = unsafe { RunnableVariant::from_raw(NonNull::new_unchecked(context as *mut ())) }; + WindowsDispatcher::execute_runnable(runnable); +} + +unsafe extern "system" fn run_timer_callback( + _instance: PTP_CALLBACK_INSTANCE, + context: *mut c_void, + timer: PTP_TIMER, +) { + let runnable = unsafe { RunnableVariant::from_raw(NonNull::new_unchecked(context as *mut ())) }; + WindowsDispatcher::execute_runnable(runnable); + unsafe { CloseThreadpoolTimer(timer) }; +} diff --git a/vendor/gpui-pre-windows/src/display.rs b/vendor/gpui-pre-windows/src/display.rs new file mode 100644 index 0000000..ee02309 --- /dev/null +++ b/vendor/gpui-pre-windows/src/display.rs @@ -0,0 +1,210 @@ +use gpui_util::ResultExt; +use itertools::Itertools; +use smallvec::SmallVec; +use std::rc::Rc; +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, + visible_bounds: Bounds, + physical_bounds: Bounds, + 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 { + 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 { + // 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)) + } + + /// The DPI scale factor of this monitor, independent of whatever monitor + /// a not-yet-positioned window currently happens to be on. + pub(crate) fn scale_factor(&self) -> f32 { + self.scale_factor + } + + /// Check if the center point of given bounds is inside this monitor + pub fn check_given_bounds(&self, bounds: Bounds) -> 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> { + available_monitors() + .into_iter() + .filter_map(|handle| { + Some( + Rc::new(WindowsDisplay::new(Self::display_id_for_monitor(handle))?) + as Rc, + ) + }) + .collect() + } + + pub fn physical_bounds(&self) -> Bounds { + self.physical_bounds + } +} + +impl PlatformDisplay for WindowsDisplay { + fn id(&self) -> DisplayId { + self.display_id + } + + fn uuid(&self) -> anyhow::Result { + Ok(self.uuid) + } + + fn bounds(&self) -> Bounds { + self.bounds + } + + fn visible_bounds(&self) -> Bounds { + 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 { + let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() }; + monitor_info.monitorInfo.cbSize = std::mem::size_of::() 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 { + 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) +} diff --git a/vendor/gpui-pre-windows/src/events.rs b/vendor/gpui-pre-windows/src/events.rs new file mode 100644 index 0000000..4a9b18b --- /dev/null +++ b/vendor/gpui-pre-windows/src/events.rs @@ -0,0 +1,1812 @@ +use std::{cell::Cell, rc::Rc, sync::atomic::Ordering}; + +use anyhow::Context as _; +use gpui_util::ResultExt; +use windows::{ + Win32::{ + Foundation::*, + Graphics::Gdi::*, + System::SystemServices::*, + UI::{ + Controls::*, + HiDpi::*, + Input::{Ime::*, KeyboardAndMouse::*}, + WindowsAndMessaging::*, + }, + }, + core::PCWSTR, +}; + +use crate::*; +use gpui::*; + +pub(crate) const WM_GPUI_CURSOR_STYLE_CHANGED: u32 = WM_USER + 1; +pub(crate) const WM_GPUI_CLOSE_ONE_WINDOW: u32 = WM_USER + 2; +pub(crate) const WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD: u32 = WM_USER + 3; +pub(crate) const WM_GPUI_DOCK_MENU_ACTION: u32 = WM_USER + 4; +pub(crate) const WM_GPUI_FORCE_UPDATE_WINDOW: u32 = WM_USER + 5; +pub(crate) const WM_GPUI_KEYBOARD_LAYOUT_CHANGED: u32 = WM_USER + 6; +pub(crate) const WM_GPUI_GPU_DEVICE_LOST: u32 = WM_USER + 7; +pub(crate) const WM_GPUI_KEYDOWN: u32 = WM_USER + 8; +pub(crate) const WM_GPUI_END_SESSION: u32 = WM_USER + 9; + +const SIZE_MOVE_LOOP_TIMER_ID: usize = 1; + +/// Coordinates window draws on the UI thread. Owned by the platform and +/// shared with every window (like `WindowsPlatformState::cursor_visible`), +/// because the coordination is inherently cross-window: while window A is +/// drawing, a re-entrant paint request for window B must be deferred. +pub(crate) struct DrawCoordinator { + /// Whether some window is currently inside `draw_window`. Win32 can + /// re-enter the window procedure while a draw is in progress (e.g. + /// cross-thread `SendMessage` dispatch during message pumping, or modal + /// message loops entered by COM calls), and drawing re-entrantly would + /// nest GPUI draws. Nested draws are wasted work whose output is + /// immediately redrawn, so we defer them instead. + drawing: Cell, +} + +impl DrawCoordinator { + pub(crate) fn new() -> Self { + Self { + drawing: Cell::new(false), + } + } + + fn try_begin_draw(&self) -> Option> { + // This only covers the `draw_window` span (which extends past the GPUI + // draw, through presentation and IME updates). Requests that arrive + // re-entrantly during GPUI-initiated draws (e.g. key dispatch or + // opening a window draws synchronously) are deferred by GPUI's + // `on_request_frame` callback itself, which no-ops in that case. + if self.drawing.get() { + None + } else { + self.drawing.set(true); + Some(DrawWindowGuard { coordinator: self }) + } + } +} + +struct DrawWindowGuard<'a> { + coordinator: &'a DrawCoordinator, +} + +impl Drop for DrawWindowGuard<'_> { + fn drop(&mut self) { + self.coordinator.drawing.set(false); + } +} + +impl WindowsWindowInner { + pub(crate) fn handle_msg( + self: &Rc, + handle: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, + ) -> LRESULT { + let handled = match msg { + // `DefWindowProc` answers `MA_NOACTIVATE` for a left click on `HTCAPTION`. + // The activation is only triggered when `DefWindowProc` handles the following `WM_NCLBUTTONDOWN`. + // The GPUI event is dispatched in between, so a click handler runs while `active_window` is still + // whichever window was active before the click. If that handler consumes the + // press, `DefWindowProc` never sees it, so the window is never activated at all. + // So, let's eagerly activate the window. + WM_MOUSEACTIVATE => Some(MA_ACTIVATE as isize), + WM_ACTIVATE => self.handle_activate_msg(wparam), + WM_CREATE => self.handle_create_msg(handle), + WM_MOVE => self.handle_move_msg(handle, lparam), + WM_SIZE => self.handle_size_msg(wparam, lparam), + WM_GETMINMAXINFO => self.handle_get_min_max_info_msg(lparam), + WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => self.handle_size_move_loop(handle), + WM_EXITSIZEMOVE | WM_EXITMENULOOP => self.handle_size_move_loop_exit(handle), + WM_TIMER => self.handle_timer_msg(handle, wparam), + WM_NCCALCSIZE => self.handle_calc_client_size(handle, wparam, lparam), + WM_DPICHANGED => self.handle_dpi_changed_msg(handle, wparam, lparam), + WM_DISPLAYCHANGE => self.handle_display_change_msg(handle), + WM_NCHITTEST => self.handle_hit_test_msg(handle, lparam), + WM_PAINT => self.handle_paint_msg(handle), + WM_CLOSE => self.handle_close_msg(), + WM_DESTROY => self.handle_destroy_msg(handle), + WM_QUERYENDSESSION => Some(1), + WM_ENDSESSION => self.handle_end_session_msg(wparam), + WM_MOUSEMOVE => self.handle_mouse_move_msg(handle, lparam, wparam), + WM_MOUSELEAVE | WM_NCMOUSELEAVE => self.handle_mouse_leave_msg(), + WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(handle, lparam), + // Treat double click as a second single click, since we track the double clicks ourselves. + // If you don't interact with any elements, this will fall through to the windows default + // behavior of toggling whether the window is maximized. + WM_NCLBUTTONDBLCLK | WM_NCLBUTTONDOWN => { + self.handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam) + } + WM_NCRBUTTONDOWN => { + self.handle_nc_mouse_down_msg(handle, MouseButton::Right, wparam, lparam) + } + WM_NCMBUTTONDOWN => { + self.handle_nc_mouse_down_msg(handle, MouseButton::Middle, wparam, lparam) + } + WM_NCLBUTTONUP => { + self.handle_nc_mouse_up_msg(handle, MouseButton::Left, wparam, lparam) + } + WM_NCRBUTTONUP => { + self.handle_nc_mouse_up_msg(handle, MouseButton::Right, wparam, lparam) + } + WM_NCMBUTTONUP => { + self.handle_nc_mouse_up_msg(handle, MouseButton::Middle, wparam, lparam) + } + WM_LBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Left, lparam), + WM_RBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Right, lparam), + WM_MBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Middle, lparam), + WM_XBUTTONDOWN => { + self.handle_xbutton_msg(handle, wparam, lparam, Self::handle_mouse_down_msg) + } + WM_LBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Left, lparam), + WM_RBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Right, lparam), + WM_MBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Middle, lparam), + WM_XBUTTONUP => { + self.handle_xbutton_msg(handle, wparam, lparam, Self::handle_mouse_up_msg) + } + WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(handle, wparam, lparam), + WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(handle, wparam, lparam), + WM_SYSKEYUP => self.handle_syskeyup_msg(wparam, lparam), + WM_KEYUP => self.handle_keyup_msg(wparam, lparam), + WM_GPUI_KEYDOWN => self.handle_keydown_msg(wparam, lparam), + WM_CHAR => self.handle_char_msg(wparam), + WM_IME_STARTCOMPOSITION => self.handle_ime_position(handle), + WM_IME_COMPOSITION => self.handle_ime_composition(handle, lparam), + WM_SETCURSOR => self.handle_set_cursor(handle, lparam), + WM_SETTINGCHANGE => self.handle_system_settings_changed(handle, wparam, lparam), + WM_INPUTLANGCHANGE => self.handle_input_language_changed(), + WM_SHOWWINDOW => self.handle_window_visibility_changed(handle, wparam), + WM_GPUI_CURSOR_STYLE_CHANGED => self.handle_cursor_changed(lparam), + WM_GPUI_FORCE_UPDATE_WINDOW => self.draw_window(handle, true), + WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam), + DM_POINTERHITTEST => self.handle_dm_pointer_hit_test(wparam), + WM_GETOBJECT => self.handle_wm_getobject(wparam, lparam), + _ => None, + }; + if let Some(n) = handled { + LRESULT(n) + } else { + unsafe { DefWindowProcW(handle, msg, wparam, lparam) } + } + } + + fn handle_end_session_msg(&self, wparam: WPARAM) -> Option { + if wparam.0 != 0 { + unsafe { + SendMessageW( + self.platform_window_handle, + WM_GPUI_END_SESSION, + Some(WPARAM(self.validation_number)), + None, + ); + } + } + Some(0) + } + + fn handle_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option { + let origin = logical_point( + lparam.signed_loword() as f32, + lparam.signed_hiword() as f32, + self.state.scale_factor.get(), + ); + self.state.origin.set(origin); + let size = self.state.logical_size.get(); + let center_x = origin.x.as_f32() + size.width.as_f32() / 2.; + let center_y = origin.y.as_f32() + size.height.as_f32() / 2.; + let monitor_bounds = self.state.display.get().bounds(); + if center_x < monitor_bounds.left().as_f32() + || center_x > monitor_bounds.right().as_f32() + || center_y < monitor_bounds.top().as_f32() + || center_y > monitor_bounds.bottom().as_f32() + { + // center of the window may have moved to another monitor + let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) }; + // minimize the window can trigger this event too, in this case, + // monitor is invalid, we do nothing. + if !monitor.is_invalid() && self.state.display.get().handle != monitor { + // we will get the same monitor if we only have one + self.state.display.set(WindowsDisplay::new( + WindowsDisplay::display_id_for_monitor(monitor), + )?); + } + } + if let Some(mut callback) = self.state.callbacks.moved.take() { + callback(); + self.state.callbacks.moved.set(Some(callback)); + } + Some(0) + } + + fn handle_get_min_max_info_msg(&self, lparam: LPARAM) -> Option { + let min_size = self.state.min_size?; + let scale_factor = self.state.scale_factor.get(); + let boarder_offset = &self.state.border_offset; + + unsafe { + let minmax_info = &mut *(lparam.0 as *mut MINMAXINFO); + minmax_info.ptMinTrackSize.x = min_size.width.scale(scale_factor).as_f32() as i32 + + boarder_offset.width_offset.get(); + minmax_info.ptMinTrackSize.y = min_size.height.scale(scale_factor).as_f32() as i32 + + boarder_offset.height_offset.get(); + } + Some(0) + } + + fn handle_size_msg(self: &Rc, wparam: WPARAM, lparam: LPARAM) -> Option { + // Minimizing and restoring both arrive as `WM_SIZE`; the deferred report + // reads `IsIconic` at delivery, so one call covers both directions. + self.report_visibility(); + + // Don't resize the renderer when the window is minimized, but record that it was minimized so + // that on restore the swap chain can be recreated via `update_drawable_size_even_if_unchanged`. + if wparam.0 == SIZE_MINIMIZED as usize { + self.state + .restore_from_minimized + .set(self.state.callbacks.request_frame.take()); + return Some(0); + } + + let width = lparam.loword().max(1) as i32; + let height = lparam.hiword().max(1) as i32; + let new_size = size(DevicePixels(width), DevicePixels(height)); + + let scale_factor = self.state.scale_factor.get(); + let mut should_resize_renderer = false; + if let Some(restore_from_minimized) = self.state.restore_from_minimized.take() { + self.state + .callbacks + .request_frame + .set(Some(restore_from_minimized)); + } else { + should_resize_renderer = true; + } + + self.handle_size_change(new_size, scale_factor, should_resize_renderer); + Some(0) + } + + fn handle_size_change( + &self, + device_size: Size, + scale_factor: f32, + should_resize_renderer: bool, + ) { + let new_logical_size = device_size.to_pixels(scale_factor); + + self.state.logical_size.set(new_logical_size); + if should_resize_renderer + && let Err(e) = self.state.renderer.borrow_mut().resize(device_size) + { + log::error!("Failed to resize renderer, invalidating devices: {}", e); + self.state + .invalidate_devices + .store(true, std::sync::atomic::Ordering::Release); + } + if let Some(mut callback) = self.state.callbacks.resize.take() { + callback(new_logical_size, scale_factor); + self.state.callbacks.resize.set(Some(callback)); + } + } + + fn handle_size_move_loop(&self, handle: HWND) -> Option { + unsafe { + let ret = SetTimer( + Some(handle), + SIZE_MOVE_LOOP_TIMER_ID, + USER_TIMER_MINIMUM, + None, + ); + if ret == 0 { + log::error!( + "unable to create timer: {}", + std::io::Error::last_os_error() + ); + } + } + None + } + + fn handle_size_move_loop_exit(&self, handle: HWND) -> Option { + unsafe { + KillTimer(Some(handle), SIZE_MOVE_LOOP_TIMER_ID).log_err(); + } + None + } + + fn handle_timer_msg(&self, handle: HWND, wparam: WPARAM) -> Option { + if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID { + let mut runnables = self.main_receiver.clone().try_iter(); + while let Some(Ok(runnable)) = runnables.next() { + WindowsDispatcher::execute_runnable(runnable); + } + self.handle_paint_msg(handle) + } else { + None + } + } + + fn handle_paint_msg(&self, handle: HWND) -> Option { + self.draw_window(handle, false) + } + + fn handle_close_msg(&self) -> Option { + let mut callback = self.state.callbacks.should_close.take()?; + let should_close = callback(); + self.state.callbacks.should_close.set(Some(callback)); + if should_close { None } else { Some(0) } + } + + fn handle_destroy_msg(&self, handle: HWND) -> Option { + let callback = { self.state.callbacks.close.take() }; + // Re-enable parent window if this was a modal dialog + if let Some(parent_hwnd) = self.parent_hwnd { + unsafe { + let _ = EnableWindow(parent_hwnd, true); + let _ = SetForegroundWindow(parent_hwnd); + } + } + + if let Some(callback) = callback { + callback(); + } + unsafe { + PostMessageW( + Some(self.platform_window_handle), + WM_GPUI_CLOSE_ONE_WINDOW, + WPARAM(self.validation_number), + LPARAM(handle.0 as isize), + ) + .log_err(); + } + Some(0) + } + + fn handle_mouse_move_msg(&self, handle: HWND, lparam: LPARAM, wparam: WPARAM) -> Option { + self.start_tracking_mouse(handle, TME_LEAVE); + self.restore_cursor_after_hide(); + + let Some(mut func) = self.state.callbacks.input.take() else { + return Some(1); + }; + let scale_factor = self.state.scale_factor.get(); + + let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) { + flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left), + flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right), + flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle), + flags if flags.contains(MK_XBUTTON1) => { + Some(MouseButton::Navigate(NavigationDirection::Back)) + } + flags if flags.contains(MK_XBUTTON2) => { + Some(MouseButton::Navigate(NavigationDirection::Forward)) + } + _ => None, + }; + let x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + let input = PlatformInput::MouseMove(MouseMoveEvent { + position: logical_point(x, y, scale_factor), + pressed_button, + modifiers: current_modifiers(), + }); + let handled = !func(input).propagate; + self.state.callbacks.input.set(Some(func)); + + if handled { Some(0) } else { Some(1) } + } + + fn handle_mouse_leave_msg(&self) -> Option { + self.state.hovered.set(false); + // The next window's `WM_SETCURSOR` picks its own cursor, so we just clear + // the flag for tight `is_cursor_visible()` semantics. + self.state.cursor_visible.store(true, Ordering::Relaxed); + if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() { + callback(false); + self.state + .callbacks + .hovered_status_change + .set(Some(callback)); + } + + Some(0) + } + + fn handle_syskeyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option { + let input = handle_key_event(wparam, lparam, &self.state, |keystroke, _| { + PlatformInput::KeyUp(KeyUpEvent { keystroke }) + })?; + let mut func = self.state.callbacks.input.take()?; + + func(input); + self.state.callbacks.input.set(Some(func)); + + // Always return 0 to indicate that the message was handled, so we could properly handle `ModifiersChanged` event. + Some(0) + } + + // It's a known bug that you can't trigger `ctrl-shift-0`. See: + // https://superuser.com/questions/1455762/ctrl-shift-number-key-combination-has-stopped-working-for-a-few-numbers + fn handle_keydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option { + let Some(input) = handle_key_event( + wparam, + lparam, + &self.state, + |keystroke, prefer_character_input| { + PlatformInput::KeyDown(KeyDownEvent { + keystroke, + is_held: lparam.0 & (0x1 << 30) > 0, + prefer_character_input, + }) + }, + ) else { + return Some(1); + }; + + let Some(mut func) = self.state.callbacks.input.take() else { + return Some(1); + }; + + let handled = !func(input).propagate; + + self.state.callbacks.input.set(Some(func)); + + if handled { Some(0) } else { Some(1) } + } + + fn handle_keyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option { + let Some(input) = handle_key_event(wparam, lparam, &self.state, |keystroke, _| { + PlatformInput::KeyUp(KeyUpEvent { keystroke }) + }) else { + return Some(1); + }; + + let Some(mut func) = self.state.callbacks.input.take() else { + return Some(1); + }; + + let handled = !func(input).propagate; + self.state.callbacks.input.set(Some(func)); + + if handled { Some(0) } else { Some(1) } + } + + fn handle_char_msg(&self, wparam: WPARAM) -> Option { + let input = self.parse_char_message(wparam)?; + self.with_input_handler(|input_handler| { + input_handler.replace_text_in_range(None, &input); + }); + + Some(0) + } + + fn handle_mouse_down_msg( + &self, + handle: HWND, + button: MouseButton, + lparam: LPARAM, + ) -> Option { + unsafe { SetCapture(handle) }; + + let Some(mut func) = self.state.callbacks.input.take() else { + return Some(1); + }; + let x = lparam.signed_loword(); + let y = lparam.signed_hiword(); + let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32)); + let click_count = self.state.click_state.update(button, physical_point); + let scale_factor = self.state.scale_factor.get(); + + let input = PlatformInput::MouseDown(MouseDownEvent { + button, + position: logical_point(x as f32, y as f32, scale_factor), + modifiers: current_modifiers(), + click_count, + first_mouse: false, + }); + let handled = !func(input).propagate; + self.state.callbacks.input.set(Some(func)); + + if handled { Some(0) } else { Some(1) } + } + + fn handle_mouse_up_msg( + &self, + _handle: HWND, + button: MouseButton, + lparam: LPARAM, + ) -> Option { + unsafe { ReleaseCapture().log_err() }; + + let Some(mut func) = self.state.callbacks.input.take() else { + return Some(1); + }; + let x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + let click_count = self.state.click_state.current_count.get(); + let scale_factor = self.state.scale_factor.get(); + + let input = PlatformInput::MouseUp(MouseUpEvent { + button, + position: logical_point(x, y, scale_factor), + modifiers: current_modifiers(), + click_count, + }); + let handled = !func(input).propagate; + self.state.callbacks.input.set(Some(func)); + + if handled { Some(0) } else { Some(1) } + } + + fn handle_xbutton_msg( + &self, + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + handler: impl Fn(&Self, HWND, MouseButton, LPARAM) -> Option, + ) -> Option { + let nav_dir = match wparam.hiword() { + XBUTTON1 => NavigationDirection::Back, + XBUTTON2 => NavigationDirection::Forward, + _ => return Some(1), + }; + handler(self, handle, MouseButton::Navigate(nav_dir), lparam) + } + + fn handle_mouse_wheel_msg( + &self, + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + ) -> Option { + let modifiers = current_modifiers(); + + let Some(mut func) = self.state.callbacks.input.take() else { + return Some(1); + }; + let scale_factor = self.state.scale_factor.get(); + let wheel_scroll_amount = match modifiers.shift { + true => self + .system_settings() + .mouse_wheel_settings + .wheel_scroll_chars + .get(), + false => self + .system_settings() + .mouse_wheel_settings + .wheel_scroll_lines + .get(), + }; + + let wheel_distance = + (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_amount as f32; + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() }; + let input = PlatformInput::ScrollWheel(ScrollWheelEvent { + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), + delta: ScrollDelta::Lines(match modifiers.shift { + true => Point { + x: wheel_distance, + y: 0.0, + }, + false => Point { + y: wheel_distance, + x: 0.0, + }, + }), + modifiers, + touch_phase: TouchPhase::Moved, + }); + let handled = !func(input).propagate; + self.state.callbacks.input.set(Some(func)); + + if handled { Some(0) } else { Some(1) } + } + + fn handle_mouse_horizontal_wheel_msg( + &self, + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + ) -> Option { + let Some(mut func) = self.state.callbacks.input.take() else { + return Some(1); + }; + let scale_factor = self.state.scale_factor.get(); + let wheel_scroll_chars = self + .system_settings() + .mouse_wheel_settings + .wheel_scroll_chars + .get(); + + let wheel_distance = + (-wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32; + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() }; + let event = PlatformInput::ScrollWheel(ScrollWheelEvent { + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), + delta: ScrollDelta::Lines(Point { + x: wheel_distance, + y: 0.0, + }), + modifiers: current_modifiers(), + touch_phase: TouchPhase::Moved, + }); + let handled = !func(event).propagate; + self.state.callbacks.input.set(Some(func)); + + if handled { Some(0) } else { Some(1) } + } + + fn retrieve_caret_position(&self) -> Option { + self.with_input_handler_and_scale_factor(|input_handler, scale_factor| { + let caret_range = input_handler.selected_text_range(false)?; + let caret_position = input_handler.bounds_for_range(caret_range.range)?; + Some(POINT { + // logical to physical + x: (caret_position.origin.x.as_f32() * scale_factor) as i32, + y: (caret_position.origin.y.as_f32() * scale_factor) as i32 + + ((caret_position.size.height.as_f32() * scale_factor) as i32 / 2), + }) + }) + } + + fn handle_ime_position(&self, handle: HWND) -> Option { + if let Some(caret_position) = self.retrieve_caret_position() { + self.update_ime_position(handle, caret_position); + } + Some(0) + } + + pub(crate) fn update_ime_position(&self, handle: HWND, caret_position: POINT) { + let Some(ctx) = ImeContext::get(handle) else { + return; + }; + unsafe { + ImmSetCompositionWindow( + *ctx, + &COMPOSITIONFORM { + dwStyle: CFS_POINT, + ptCurrentPos: caret_position, + ..Default::default() + }, + ) + .ok() + .log_err(); + + ImmSetCandidateWindow( + *ctx, + &CANDIDATEFORM { + dwStyle: CFS_CANDIDATEPOS, + ptCurrentPos: caret_position, + ..Default::default() + }, + ) + .ok() + .log_err(); + } + } + + fn update_ime_enabled(&self, handle: HWND) { + let ime_enabled = self + .with_input_handler(|input_handler| input_handler.query_accepts_text_input()) + .unwrap_or(false); + if ime_enabled == self.state.ime_enabled.get() { + return; + } + self.state.ime_enabled.set(ime_enabled); + unsafe { + if ime_enabled { + ImmAssociateContextEx(handle, HIMC::default(), IACE_DEFAULT) + .ok() + .log_err(); + } else { + // The IME context is per-thread, so without this check a change in this + // window's text input state could commit an IME composition happening in another window. + if GetFocus() == handle + && let Some(ctx) = ImeContext::get(handle) + { + ImmNotifyIME(*ctx, NI_COMPOSITIONSTR, CPS_COMPLETE, 0) + .ok() + .log_err(); + } + ImmAssociateContextEx(handle, HIMC::default(), 0) + .ok() + .log_err(); + } + } + } + + fn handle_ime_composition(&self, handle: HWND, lparam: LPARAM) -> Option { + let ctx = ImeContext::get(handle)?; + self.handle_ime_composition_inner(*ctx, lparam) + } + + fn handle_ime_composition_inner(&self, ctx: HIMC, lparam: LPARAM) -> Option { + let lparam = lparam.0 as u32; + if lparam == 0 { + // Japanese IME may send this message with lparam = 0, which indicates that + // there is no composition string. + self.with_input_handler(|input_handler| { + input_handler.replace_text_in_range(None, ""); + })?; + Some(0) + } else { + if lparam & GCS_RESULTSTR.0 > 0 { + let comp_result = parse_ime_composition_string(ctx, GCS_RESULTSTR)?; + self.with_input_handler(|input_handler| { + input_handler + .replace_text_in_range(None, &String::from_utf16_lossy(&comp_result)); + })?; + } + if lparam & GCS_COMPSTR.0 > 0 { + let comp_string = parse_ime_composition_string(ctx, GCS_COMPSTR)?; + let caret_pos = + (!comp_string.is_empty() && lparam & GCS_CURSORPOS.0 > 0).then(|| { + let cursor_pos = retrieve_composition_cursor_position(ctx); + let pos = if should_use_ime_cursor_position(ctx, cursor_pos) { + cursor_pos + } else { + comp_string.len() + }; + pos..pos + }); + self.with_input_handler(|input_handler| { + input_handler.replace_and_mark_text_in_range( + None, + &String::from_utf16_lossy(&comp_string), + caret_pos, + ); + })?; + } + if lparam & (GCS_RESULTSTR.0 | GCS_COMPSTR.0) > 0 { + return Some(0); + } + + // currently, we don't care other stuff + None + } + } + + fn handle_calc_client_size( + &self, + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + ) -> Option { + if !self.hide_title_bar || self.state.is_fullscreen() || wparam.0 == 0 { + return None; + } + + unsafe { + let params = lparam.0 as *mut NCCALCSIZE_PARAMS; + let saved_top = (*params).rgrc[0].top; + let result = DefWindowProcW(handle, WM_NCCALCSIZE, wparam, lparam); + (*params).rgrc[0].top = saved_top; + if self.state.is_maximized() { + let dpi = GetDpiForWindow(handle); + (*params).rgrc[0].top += get_frame_thicknessx(dpi); + } + Some(result.0 as isize) + } + } + + fn handle_activate_msg(self: &Rc, wparam: WPARAM) -> Option { + let activated = wparam.loword() > 0; + + let events = self + .state + .a11y + .try_borrow_mut() + .ok() + .and_then(|mut a11y| a11y.as_mut()?.adapter.update_window_focus_state(activated)); + if let Some(events) = events { + events.raise(); + } + + let this = self.clone(); + + if !activated { + this.state.cursor_visible.store(true, Ordering::Relaxed); + } + + // When the window is activated (gains focus), reset the modifier tracking state. + // This fixes the issue where Alt-Tab away and back leaves stale modifier state + // (especially the Alt key) because Windows doesn't always send key-up events to + // windows that have lost focus. + if activated { + this.state.last_reported_modifiers.set(None); + this.state.last_reported_capslock.set(None); + + if let Some(mut func) = this.state.callbacks.input.take() { + let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent { + modifiers: current_modifiers(), + capslock: current_capslock(), + }); + func(input); + this.state.callbacks.input.set(Some(func)); + } + } + + self.executor + .spawn(async move { + if let Some(mut func) = this.state.callbacks.active_status_change.take() { + func(activated); + this.state.callbacks.active_status_change.set(Some(func)); + } + }) + .detach(); + + None + } + + fn handle_wm_getobject(&self, wparam: WPARAM, lparam: LPARAM) -> Option { + let result = { + let mut a11y = self.state.a11y.borrow_mut(); + let a11y = a11y.as_mut()?; + a11y.adapter.handle_wm_getobject( + accesskit_windows::WPARAM(wparam.0), + accesskit_windows::LPARAM(lparam.0), + &mut a11y.activation_handler, + )? + }; + // The borrow above must be dropped before calling `.into()`, because + // it calls `UiaReturnRawElementProvider` which may send a nested + // `WM_GETOBJECT` back into this window procedure. + let lresult: accesskit_windows::LRESULT = result.into(); + Some(lresult.0) + } + + fn handle_create_msg(&self, handle: HWND) -> Option { + if self.hide_title_bar { + notify_frame_changed(handle); + Some(0) + } else { + None + } + } + + fn handle_dpi_changed_msg( + &self, + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + ) -> Option { + let new_dpi = wparam.loword() as f32; + + let is_maximized = self.state.is_maximized(); + let new_scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32; + self.state.scale_factor.set(new_scale_factor); + self.state.border_offset.update(handle).log_err(); + + self.state + .direct_manipulation + .set_scale_factor(new_scale_factor); + + if is_maximized { + // Get the monitor and its work area at the new DPI + let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST) }; + let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() }; + monitor_info.cbSize = std::mem::size_of::() as u32; + if unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }.as_bool() { + let work_area = monitor_info.rcWork; + let width = work_area.right - work_area.left; + let height = work_area.bottom - work_area.top; + + // Update the window size to match the new monitor work area + // This will trigger WM_SIZE which will handle the size change + unsafe { + SetWindowPos( + handle, + None, + work_area.left, + work_area.top, + width, + height, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED, + ) + .context("unable to set maximized window position after dpi has changed") + .log_err(); + } + + // SetWindowPos may not send WM_SIZE for maximized windows in some cases, + // so we manually update the size to ensure proper rendering + let device_size = size(DevicePixels(width), DevicePixels(height)); + self.handle_size_change(device_size, new_scale_factor, true); + } + } else { + // For non-maximized windows, use the suggested RECT from the system + let rect = unsafe { &*(lparam.0 as *const RECT) }; + let width = rect.right - rect.left; + let height = rect.bottom - rect.top; + // this will emit `WM_SIZE` and `WM_MOVE` right here + // even before this function returns + // the new size is handled in `WM_SIZE` + unsafe { + SetWindowPos( + handle, + None, + rect.left, + rect.top, + width, + height, + SWP_NOZORDER | SWP_NOACTIVATE, + ) + .context("unable to set window position after dpi has changed") + .log_err(); + } + } + + Some(0) + } + + fn handle_display_change_msg(&self, handle: HWND) -> Option { + let new_monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) }; + if new_monitor.is_invalid() { + log::error!("No monitor detected!"); + return None; + } + let new_display = WindowsDisplay::new(WindowsDisplay::display_id_for_monitor(new_monitor))?; + self.state.display.set(new_display); + Some(0) + } + + fn handle_hit_test_msg(&self, handle: HWND, lparam: LPARAM) -> Option { + if self.state.is_fullscreen() { + return None; + } + + let callback = self.state.callbacks.hit_test_window_control.take(); + let drag_area = if let Some(mut callback) = callback { + let area = callback(); + self.state + .callbacks + .hit_test_window_control + .set(Some(callback)); + area.and_then(|area| match area { + WindowControlArea::Drag if self.is_movable => Some(HTCAPTION as _), + WindowControlArea::Drag => None, + WindowControlArea::Close => Some(HTCLOSE as _), + WindowControlArea::Max if self.is_resizable => Some(HTMAXBUTTON as _), + WindowControlArea::Max if self.is_movable => Some(HTCAPTION as _), + WindowControlArea::Max => Some(HTNOWHERE as _), + WindowControlArea::Min if self.is_minimizable => Some(HTMINBUTTON as _), + WindowControlArea::Min if self.is_movable => Some(HTCAPTION as _), + WindowControlArea::Min => Some(HTNOWHERE as _), + }) + } else { + None + }; + + if !self.hide_title_bar { + // If the OS draws the title bar, we don't need to handle hit test messages. + return drag_area; + } + + let dpi = unsafe { GetDpiForWindow(handle) }; + // We do not use the OS title bar, so the default `DefWindowProcW` will only register a 1px edge for resizes + // We need to calculate the frame thickness ourselves and do the hit test manually. + let frame_y = get_frame_thicknessx(dpi); + let frame_x = get_frame_thicknessy(dpi); + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + + unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() }; + if self.is_resizable + && !self.state.is_maximized() + && 0 <= cursor_point.y + && cursor_point.y <= frame_y + { + // x-axis actually goes from -frame_x to 0 + return Some(if cursor_point.x <= 0 { + HTTOPLEFT + } else { + let mut rect = Default::default(); + unsafe { GetWindowRect(handle, &mut rect) }.log_err(); + // right and bottom bounds of RECT are exclusive, thus `-1` + let right = rect.right - rect.left - 1; + // the bounds include the padding frames, so accommodate for both of them + if right - 2 * frame_x <= cursor_point.x { + HTTOPRIGHT + } else { + HTTOP + } + } as _); + } + + drag_area + } + + fn handle_nc_mouse_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option { + self.start_tracking_mouse(handle, TME_LEAVE | TME_NONCLIENT); + self.restore_cursor_after_hide(); + + let mut func = self.state.callbacks.input.take()?; + let scale_factor = self.state.scale_factor.get(); + + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() }; + let input = PlatformInput::MouseMove(MouseMoveEvent { + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), + pressed_button: None, + modifiers: current_modifiers(), + }); + let handled = !func(input).propagate; + self.state.callbacks.input.set(Some(func)); + + if handled { Some(0) } else { None } + } + + fn handle_nc_mouse_down_msg( + &self, + handle: HWND, + button: MouseButton, + wparam: WPARAM, + lparam: LPARAM, + ) -> Option { + if let Some(mut func) = self.state.callbacks.input.take() { + let scale_factor = self.state.scale_factor.get(); + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() }; + let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y)); + let click_count = self.state.click_state.update(button, physical_point); + + let input = PlatformInput::MouseDown(MouseDownEvent { + button, + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), + modifiers: current_modifiers(), + click_count, + first_mouse: false, + }); + let handled = !func(input).propagate; + self.state.callbacks.input.set(Some(func)); + + if handled { + return Some(0); + } + } else { + }; + + // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc + if button == MouseButton::Left { + match wparam.0 as u32 { + HTMINBUTTON => self.state.nc_button_pressed.set(Some(HTMINBUTTON)), + HTMAXBUTTON => self.state.nc_button_pressed.set(Some(HTMAXBUTTON)), + HTCLOSE => self.state.nc_button_pressed.set(Some(HTCLOSE)), + _ => return None, + }; + Some(0) + } else { + None + } + } + + fn handle_nc_mouse_up_msg( + &self, + handle: HWND, + button: MouseButton, + wparam: WPARAM, + lparam: LPARAM, + ) -> Option { + if let Some(mut func) = self.state.callbacks.input.take() { + let scale_factor = self.state.scale_factor.get(); + + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() }; + let input = PlatformInput::MouseUp(MouseUpEvent { + button, + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), + modifiers: current_modifiers(), + click_count: 1, + }); + let handled = !func(input).propagate; + self.state.callbacks.input.set(Some(func)); + + if handled { + return Some(0); + } + } else { + } + + let last_pressed = self.state.nc_button_pressed.take(); + if button == MouseButton::Left + && let Some(last_pressed) = last_pressed + { + let handled = match (wparam.0 as u32, last_pressed) { + (HTMINBUTTON, HTMINBUTTON) if self.is_minimizable => { + unsafe { ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err() }; + true + } + (HTMINBUTTON, HTMINBUTTON) => true, + (HTMAXBUTTON, HTMAXBUTTON) if self.is_resizable => { + if self.state.is_maximized() { + unsafe { ShowWindowAsync(handle, SW_NORMAL).ok().log_err() }; + } else { + unsafe { ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err() }; + } + true + } + (HTMAXBUTTON, HTMAXBUTTON) => true, + (HTCLOSE, HTCLOSE) => { + unsafe { + PostMessageW(Some(handle), WM_CLOSE, WPARAM::default(), LPARAM::default()) + .log_err() + }; + true + } + _ => false, + }; + if handled { + return Some(0); + } + } + + None + } + + fn handle_cursor_changed(&self, lparam: LPARAM) -> Option { + let had_cursor = self.state.current_cursor.get().is_some(); + + self.state.current_cursor.set(if lparam.0 == 0 { + None + } else { + Some(HCURSOR(lparam.0 as _)) + }); + + if had_cursor != self.state.current_cursor.get().is_some() { + unsafe { SetCursor(self.state.current_cursor.get()) }; + } + + Some(0) + } + + fn handle_set_cursor(&self, handle: HWND, lparam: LPARAM) -> Option { + if unsafe { !IsWindowEnabled(handle).as_bool() } + || matches!( + lparam.loword() as u32, + HTLEFT + | HTRIGHT + | HTTOP + | HTTOPLEFT + | HTTOPRIGHT + | HTBOTTOM + | HTBOTTOMLEFT + | HTBOTTOMRIGHT + ) + { + return None; + } + let cursor = if self.state.cursor_visible.load(Ordering::Relaxed) { + self.state.current_cursor.get() + } else { + None + }; + unsafe { + SetCursor(cursor); + }; + Some(0) + } + + fn handle_system_settings_changed( + &self, + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + ) -> Option { + if wparam.0 != 0 { + self.state.click_state.system_update(wparam.0); + self.state.border_offset.update(handle).log_err(); + // system settings may emit a window message which wants to take the refcell self.state, so drop it + + self.system_settings().update(wparam.0); + } else { + self.handle_system_theme_changed(handle, lparam)?; + }; + + Some(0) + } + + fn handle_system_theme_changed(&self, handle: HWND, lparam: LPARAM) -> Option { + // lParam is a pointer to a string that indicates the area containing the system parameter + // that was changed. + let parameter = PCWSTR::from_raw(lparam.0 as _); + if unsafe { !parameter.is_null() && !parameter.is_empty() } + && let Some(parameter_string) = unsafe { parameter.to_string() }.log_err() + { + log::info!("System settings changed: {}", parameter_string); + if parameter_string.as_str() == "ImmersiveColorSet" { + let new_appearance = system_appearance() + .context("unable to get system appearance when handling ImmersiveColorSet") + .log_err()?; + + if new_appearance != self.state.appearance.get() { + self.state.appearance.set(new_appearance); + let mut callback = self.state.callbacks.appearance_changed.take()?; + + callback(); + self.state.callbacks.appearance_changed.set(Some(callback)); + configure_dwm_dark_mode(handle, new_appearance); + } + } + } + Some(0) + } + + fn handle_input_language_changed(&self) -> Option { + unsafe { + PostMessageW( + Some(self.platform_window_handle), + WM_GPUI_KEYBOARD_LAYOUT_CHANGED, + WPARAM(self.validation_number), + LPARAM(0), + ) + .log_err(); + } + Some(0) + } + + fn handle_window_visibility_changed( + self: &Rc, + handle: HWND, + wparam: WPARAM, + ) -> Option { + self.report_visibility(); + if wparam.0 == 1 { + self.draw_window(handle, false); + } + None + } + + // The window procedure can run while GPUI is updating this window (e.g. + // `ShowWindow` from an action handler), so deliver observers after that + // update completes, as activation does. The state is read at delivery so + // a burst of messages collapses to the final value. + fn report_visibility(self: &Rc) { + if self.state.last_visibility.get().is_none() { + return; + } + let this = self.clone(); + self.executor + .spawn(async move { + let visibility = this.visibility(); + if this.state.last_visibility.get() == Some(visibility) { + return; + } + this.state.last_visibility.set(Some(visibility)); + if let Some(mut callback) = this.state.callbacks.visibility_change.take() { + callback(visibility); + this.state.callbacks.visibility_change.set(Some(callback)); + } + }) + .detach(); + } + + fn handle_device_lost(&self, lparam: LPARAM) -> Option { + let devices = lparam.0 as *const DirectXDevices; + let devices = unsafe { &*devices }; + if let Err(err) = self + .state + .renderer + .borrow_mut() + .handle_device_lost(&devices) + { + panic!("Device lost: {err}"); + } + // Make sure the first `draw_window` after recovery (whether it comes + // from the forced WM_GPUI_FORCE_UPDATE_WINDOW or a stray WM_PAINT in + // between) is treated as a forced render so it both clears + // `skip_draws` and bypasses the view cache. + self.state.force_render_pending.set(true); + Some(0) + } + + fn handle_dm_pointer_hit_test(&self, wparam: WPARAM) -> Option { + self.state.direct_manipulation.on_pointer_hit_test(wparam); + None + } + + #[inline] + fn draw_window(&self, handle: HWND, force_render: bool) -> Option { + let Some(_guard) = self.state.draw_coordinator.try_begin_draw() else { + log::debug!("deferring re-entrant draw of window {handle:?}"); + if force_render { + self.state.force_render_pending.set(true); + } + // Validate the region so a nested message pump doesn't keep + // re-dispatching WM_PAINT for the still-invalid region in a busy + // loop until the in-progress draw unwinds. The vsync thread + // re-invalidates every window on each vsync (see + // `begin_vsync_thread`), so the deferred frame still gets drawn, + // at most one vsync late. + unsafe { ValidateRect(Some(handle), None).ok().log_err() }; + return Some(0); + }; + let mut request_frame = self.state.callbacks.request_frame.take()?; + + self.state.direct_manipulation.update(); + + let events = self.state.direct_manipulation.drain_events(); + if !events.is_empty() { + if let Some(mut func) = self.state.callbacks.input.take() { + for event in events { + func(event); + } + self.state.callbacks.input.set(Some(func)); + } + } + + let force_render = force_render || self.state.force_render_pending.take(); + if force_render { + // Re-enable drawing after a device loss recovery. The forced render + // will rebuild the scene with fresh atlas textures. + self.state.renderer.borrow_mut().mark_drawable(); + } + let (signal_at, signal_source) = self.state.frame_signal.take().map_or( + (None, FrameRequestSource::NativeCallback), + |(at, source)| (Some(at), source), + ); + request_frame(RequestFrameOptions { + require_presentation: false, + force_render, + signal_at, + signal_source, + }); + + self.state.callbacks.request_frame.set(Some(request_frame)); + self.update_ime_enabled(handle); + unsafe { ValidateRect(Some(handle), None).ok().log_err() }; + + Some(0) + } + + #[inline] + fn parse_char_message(&self, wparam: WPARAM) -> Option { + let code_point = wparam.loword(); + + // https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/#G2630 + match code_point { + 0xD800..=0xDBFF => { + // High surrogate, wait for low surrogate + self.state.pending_surrogate.set(Some(code_point)); + None + } + 0xDC00..=0xDFFF => { + if let Some(high_surrogate) = self.state.pending_surrogate.take() { + // Low surrogate, combine with pending high surrogate + String::from_utf16(&[high_surrogate, code_point]).ok() + } else { + // Invalid low surrogate without a preceding high surrogate + log::warn!( + "Received low surrogate without a preceding high surrogate: {code_point:x}" + ); + None + } + } + _ => { + self.state.pending_surrogate.set(None); + char::from_u32(code_point as u32) + .filter(|c| !c.is_control()) + .map(|c| c.to_string()) + } + } + } + + /// Clear the hidden flag and restore the cursor immediately + fn restore_cursor_after_hide(&self) { + if !self.state.cursor_visible.swap(true, Ordering::Relaxed) { + unsafe { + SetCursor(self.state.current_cursor.get()); + } + } + } + + fn start_tracking_mouse(&self, handle: HWND, flags: TRACKMOUSEEVENT_FLAGS) { + if !self.state.hovered.get() { + self.state.hovered.set(true); + unsafe { + TrackMouseEvent(&mut TRACKMOUSEEVENT { + cbSize: std::mem::size_of::() as u32, + dwFlags: flags, + hwndTrack: handle, + dwHoverTime: HOVER_DEFAULT, + }) + .log_err() + }; + if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() { + callback(true); + self.state + .callbacks + .hovered_status_change + .set(Some(callback)); + } + } + } + + fn with_input_handler(&self, f: F) -> Option + where + F: FnOnce(&mut PlatformInputHandler) -> R, + { + let mut input_handler = self.state.input_handler.take()?; + let result = f(&mut input_handler); + self.state.input_handler.set(Some(input_handler)); + Some(result) + } + + fn with_input_handler_and_scale_factor(&self, f: F) -> Option + where + F: FnOnce(&mut PlatformInputHandler, f32) -> Option, + { + let mut input_handler = self.state.input_handler.take()?; + let scale_factor = self.state.scale_factor.get(); + + let result = f(&mut input_handler, scale_factor); + self.state.input_handler.set(Some(input_handler)); + result + } +} + +struct ImeContext { + hwnd: HWND, + himc: HIMC, +} + +impl ImeContext { + fn get(hwnd: HWND) -> Option { + let himc = unsafe { ImmGetContext(hwnd) }; + if himc.is_invalid() { + return None; + } + Some(Self { hwnd, himc }) + } +} + +impl std::ops::Deref for ImeContext { + type Target = HIMC; + fn deref(&self) -> &HIMC { + &self.himc + } +} + +impl Drop for ImeContext { + fn drop(&mut self) { + unsafe { + ImmReleaseContext(self.hwnd, self.himc).ok().log_err(); + } + } +} + +fn handle_key_event( + wparam: WPARAM, + lparam: LPARAM, + state: &WindowsWindowState, + f: F, +) -> Option +where + F: FnOnce(Keystroke, bool) -> PlatformInput, +{ + let virtual_key = VIRTUAL_KEY(wparam.loword()); + let modifiers = current_modifiers(); + + match virtual_key { + VK_SHIFT | VK_CONTROL | VK_MENU | VK_LMENU | VK_RMENU | VK_LWIN | VK_RWIN => { + if state + .last_reported_modifiers + .get() + .is_some_and(|prev_modifiers| prev_modifiers == modifiers) + { + return None; + } + state.last_reported_modifiers.set(Some(modifiers)); + Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent { + modifiers, + capslock: current_capslock(), + })) + } + VK_PACKET => None, + VK_CAPITAL => { + let capslock = current_capslock(); + if state + .last_reported_capslock + .get() + .is_some_and(|prev_capslock| prev_capslock == capslock) + { + return None; + } + state.last_reported_capslock.set(Some(capslock)); + Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent { + modifiers, + capslock, + })) + } + vkey => { + let keystroke = parse_normal_key(vkey, lparam, modifiers)?; + Some(f(keystroke.0, keystroke.1)) + } + } +} + +fn parse_immutable(vkey: VIRTUAL_KEY) -> Option { + Some( + match vkey { + VK_SPACE => "space", + VK_BACK => "backspace", + VK_RETURN => "enter", + VK_TAB => "tab", + VK_UP => "up", + VK_DOWN => "down", + VK_RIGHT => "right", + VK_LEFT => "left", + VK_HOME => "home", + VK_END => "end", + VK_PRIOR => "pageup", + VK_NEXT => "pagedown", + VK_BROWSER_BACK => "back", + VK_BROWSER_FORWARD => "forward", + VK_ESCAPE => "escape", + VK_INSERT => "insert", + VK_DELETE => "delete", + VK_APPS => "menu", + VK_F1 => "f1", + VK_F2 => "f2", + VK_F3 => "f3", + VK_F4 => "f4", + VK_F5 => "f5", + VK_F6 => "f6", + VK_F7 => "f7", + VK_F8 => "f8", + VK_F9 => "f9", + VK_F10 => "f10", + VK_F11 => "f11", + VK_F12 => "f12", + VK_F13 => "f13", + VK_F14 => "f14", + VK_F15 => "f15", + VK_F16 => "f16", + VK_F17 => "f17", + VK_F18 => "f18", + VK_F19 => "f19", + VK_F20 => "f20", + VK_F21 => "f21", + VK_F22 => "f22", + VK_F23 => "f23", + VK_F24 => "f24", + _ => return None, + } + .to_string(), + ) +} + +fn parse_normal_key( + vkey: VIRTUAL_KEY, + lparam: LPARAM, + mut modifiers: Modifiers, +) -> Option<(Keystroke, bool)> { + let (key_char, prefer_character_input) = process_key(vkey, lparam.hiword()); + + let key = parse_immutable(vkey).or_else(|| { + let scan_code = lparam.hiword() & 0xFF; + get_keystroke_key(vkey, scan_code as u32, &mut modifiers) + })?; + + Some(( + Keystroke { + modifiers, + key, + key_char, + }, + prefer_character_input, + )) +} + +fn process_key(vkey: VIRTUAL_KEY, scan_code: u16) -> (Option, bool) { + let mut keyboard_state = [0u8; 256]; + unsafe { + if GetKeyboardState(&mut keyboard_state).is_err() { + return (None, false); + } + } + + let mut buffer_c = [0u16; 8]; + let result_c = unsafe { + ToUnicode( + vkey.0 as u32, + scan_code as u32, + Some(&keyboard_state), + &mut buffer_c, + 0x4, + ) + }; + + if result_c == 0 { + return (None, false); + } + + let c = &buffer_c[..result_c.unsigned_abs() as usize]; + let key_char = String::from_utf16(c) + .ok() + .filter(|s| !s.is_empty() && !s.chars().next().unwrap().is_control()); + + if result_c < 0 { + return (key_char, true); + } + + if key_char.is_none() { + return (None, false); + } + + // Workaround for some bug that makes the compiler think keyboard_state is still zeroed out + let keyboard_state = std::hint::black_box(keyboard_state); + let ctrl_down = (keyboard_state[VK_CONTROL.0 as usize] & 0x80) != 0; + let alt_down = (keyboard_state[VK_MENU.0 as usize] & 0x80) != 0; + let win_down = (keyboard_state[VK_LWIN.0 as usize] & 0x80) != 0 + || (keyboard_state[VK_RWIN.0 as usize] & 0x80) != 0; + + let has_modifiers = ctrl_down || alt_down || win_down; + if !has_modifiers { + return (key_char, false); + } + + let mut state_no_modifiers = keyboard_state; + state_no_modifiers[VK_CONTROL.0 as usize] = 0; + state_no_modifiers[VK_LCONTROL.0 as usize] = 0; + state_no_modifiers[VK_RCONTROL.0 as usize] = 0; + state_no_modifiers[VK_MENU.0 as usize] = 0; + state_no_modifiers[VK_LMENU.0 as usize] = 0; + state_no_modifiers[VK_RMENU.0 as usize] = 0; + state_no_modifiers[VK_LWIN.0 as usize] = 0; + state_no_modifiers[VK_RWIN.0 as usize] = 0; + + let mut buffer_c_no_modifiers = [0u16; 8]; + let result_c_no_modifiers = unsafe { + ToUnicode( + vkey.0 as u32, + scan_code as u32, + Some(&state_no_modifiers), + &mut buffer_c_no_modifiers, + 0x4, + ) + }; + + let c_no_modifiers = &buffer_c_no_modifiers[..result_c_no_modifiers.unsigned_abs() as usize]; + ( + key_char, + result_c != result_c_no_modifiers || c != c_no_modifiers, + ) +} + +fn parse_ime_composition_string(ctx: HIMC, comp_type: IME_COMPOSITION_STRING) -> Option> { + unsafe { + let string_len = ImmGetCompositionStringW(ctx, comp_type, None, 0); + if string_len >= 0 { + let mut buffer = vec![0u8; string_len as usize + 2]; + ImmGetCompositionStringW( + ctx, + comp_type, + Some(buffer.as_mut_ptr() as _), + string_len as _, + ); + let wstring = std::slice::from_raw_parts::( + buffer.as_mut_ptr().cast::(), + string_len as usize / 2, + ); + Some(wstring.to_vec()) + } else { + None + } + } +} + +#[inline] +fn retrieve_composition_cursor_position(ctx: HIMC) -> usize { + unsafe { ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0) as usize } +} + +fn should_use_ime_cursor_position(ctx: HIMC, cursor_pos: usize) -> bool { + let attrs_size = unsafe { ImmGetCompositionStringW(ctx, GCS_COMPATTR, None, 0) } as usize; + if attrs_size == 0 { + return false; + } + + let mut attrs = vec![0u8; attrs_size]; + let result = unsafe { + ImmGetCompositionStringW( + ctx, + GCS_COMPATTR, + Some(attrs.as_mut_ptr() as *mut _), + attrs_size as u32, + ) + }; + if result <= 0 { + return false; + } + + // Keep the cursor adjacent to the inserted text by only using the suggested position + // if it's adjacent to unconverted text. + let at_cursor_is_input = cursor_pos < attrs.len() && attrs[cursor_pos] == (ATTR_INPUT as u8); + let before_cursor_is_input = cursor_pos > 0 + && (cursor_pos - 1) < attrs.len() + && attrs[cursor_pos - 1] == (ATTR_INPUT as u8); + + at_cursor_is_input || before_cursor_is_input +} + +#[inline] +fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool { + unsafe { GetKeyState(vkey.0 as i32) < 0 } +} + +#[inline] +pub(crate) fn current_modifiers() -> Modifiers { + Modifiers { + control: is_virtual_key_pressed(VK_CONTROL), + alt: is_virtual_key_pressed(VK_MENU), + shift: is_virtual_key_pressed(VK_SHIFT), + platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN), + function: false, + } +} + +#[inline] +pub(crate) fn current_capslock() -> Capslock { + let on = unsafe { GetKeyState(VK_CAPITAL.0 as i32) & 1 } > 0; + Capslock { on } +} + +// there is some additional non-visible space when talking about window +// borders on Windows: +// - SM_CXSIZEFRAME: The resize handle. +// - SM_CXPADDEDBORDER: Additional border space that isn't part of the resize handle. +fn get_frame_thicknessx(dpi: u32) -> i32 { + let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) }; + let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) }; + resize_frame_thickness + padding_thickness +} + +fn get_frame_thicknessy(dpi: u32) -> i32 { + let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) }; + let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) }; + resize_frame_thickness + padding_thickness +} + +fn notify_frame_changed(handle: HWND) { + unsafe { + SetWindowPos( + handle, + None, + 0, + 0, + 0, + 0, + SWP_FRAMECHANGED + | SWP_NOACTIVATE + | SWP_NOCOPYBITS + | SWP_NOMOVE + | SWP_NOOWNERZORDER + | SWP_NOREPOSITION + | SWP_NOSENDCHANGING + | SWP_NOSIZE + | SWP_NOZORDER, + ) + .log_err(); + } +} diff --git a/vendor/gpui-pre-windows/src/gpui_windows.rs b/vendor/gpui-pre-windows/src/gpui_windows.rs new file mode 100644 index 0000000..e7a45a0 --- /dev/null +++ b/vendor/gpui-pre-windows/src/gpui_windows.rs @@ -0,0 +1,43 @@ +#![cfg(target_os = "windows")] + +mod clipboard; +mod destination_list; +mod dialog; +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_notifications; +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_notifications::*; +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; diff --git a/vendor/gpui-pre-windows/src/keyboard.rs b/vendor/gpui-pre-windows/src/keyboard.rs new file mode 100644 index 0000000..8164bc1 --- /dev/null +++ b/vendor/gpui-pre-windows/src/keyboard.rs @@ -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, + vkey_to_key: HashMap, + vkey_to_shifted: HashMap, +} + +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> { + None + } +} + +impl WindowsKeyboardLayout { + pub(crate) fn new() -> Result { + 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 { + 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 { + 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 { + 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 { + 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()); + } +} diff --git a/vendor/gpui-pre-windows/src/platform.rs b/vendor/gpui-pre-windows/src/platform.rs new file mode 100644 index 0000000..995017f --- /dev/null +++ b/vendor/gpui-pre-windows/src/platform.rs @@ -0,0 +1,1939 @@ +use std::{ + cell::{Cell, RefCell}, + ffi::{OsStr, OsString}, + os::windows::ffi::{OsStrExt as _, OsStringExt as _}, + path::{Path, PathBuf}, + rc::{Rc, Weak}, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, +}; + +use anyhow::{Context as _, Result, anyhow}; +use futures::channel::oneshot::Receiver; +use gpui_util::{ResultExt, get_powershell, new_std_command}; +use itertools::Itertools; +use parking_lot::RwLock; +use smallvec::SmallVec; +use windows::{ + UI::ViewManagement::UISettings, + Win32::{ + Foundation::*, + Graphics::{Direct3D11::ID3D11Device, Gdi::*}, + Security::Credentials::*, + System::{ + Com::*, + LibraryLoader::*, + Ole::*, + Power::*, + SystemInformation::*, + SystemServices::POWER_REQUEST_CONTEXT_VERSION, + Threading::{POWER_REQUEST_CONTEXT_SIMPLE_STRING, REASON_CONTEXT, REASON_CONTEXT_0}, + }, + UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*}, + }, + core::*, +}; + +use crate::*; +use gpui::*; + +struct TrackedWindow { + handle: SafeHwnd, + frame_signal: Arc, +} + +impl TrackedWindow { + fn as_raw(&self) -> HWND { + self.handle.as_raw() + } +} + +pub struct WindowsPlatform { + inner: Rc, + raw_window_handles: Arc>>, + /// The windowing mode to start in, applied when `run` starts. Windowed when unset. + initial_windowing: RefCell>, + /// Set to stop the current `VSyncProvider` thread, which runs while windowed. + vsync_stop: RefCell>>, + // The below members will never change throughout the entire lifecycle of the app. + /// Created with `headless()`: never windowed, and without a real text system. + headless: bool, + icon: HICON, + background_executor: BackgroundExecutor, + foreground_executor: ForegroundExecutor, + text_system: Arc, + direct_write_text_system: Option>, + drop_target_helper: Option, + /// Flag to instruct the `VSyncProvider` thread to invalidate the directx devices + /// as resizing them has failed, causing us to have lost at least the render target. + invalidate_devices: Arc, + handle: HWND, + suspend_resume_notification: RefCell>, + disable_direct_composition: bool, + has_package_identity: bool, + app_identity: RefCell>, + system_notifications: RefCell, +} + +struct WindowsPlatformInner { + state: WindowsPlatformState, + raw_window_handles: std::sync::Weak>>, + // The below members will never change throughout the entire lifecycle of the app. + validation_number: usize, + main_receiver: PriorityQueueReceiver, + dispatcher: Arc, +} + +pub(crate) struct WindowsPlatformState { + callbacks: PlatformCallbacks, + menus: RefCell>, + jump_list: RefCell, + // NOTE: standard cursor handles don't need to close. + pub(crate) current_cursor: Cell>, + /// Shared with each window so `WM_SETCURSOR` can read it directly. + pub(crate) cursor_visible: Arc, + /// Shared with each window to coordinate draws across windows on the UI + /// thread; see [`DrawCoordinator`]. + pub(crate) draw_coordinator: Rc, + directx_devices: RefCell>, +} + +#[derive(Default)] +struct PlatformCallbacks { + open_urls: Cell)>>>, + quit: Cell bool>>>, + reopen: Cell>>, + app_menu_action: Cell>>, + will_open_app_menu: Cell>>, + validate_app_menu_command: Cell bool>>>, + keyboard_layout_change: Cell>>, + system_sleep: Cell>>, + system_wake: Cell>>, +} + +impl WindowsPlatformState { + fn new(directx_devices: Option) -> Self { + let callbacks = PlatformCallbacks::default(); + let jump_list = JumpList::new(); + let current_cursor = load_cursor(CursorStyle::Arrow); + + Self { + callbacks, + jump_list: RefCell::new(jump_list), + current_cursor: Cell::new(current_cursor), + cursor_visible: Arc::new(AtomicBool::new(true)), + draw_coordinator: Rc::new(DrawCoordinator::new()), + directx_devices: RefCell::new(directx_devices), + menus: RefCell::new(Vec::new()), + } + } +} + +struct PowerRequest { + handle: HANDLE, + // `PowerCreateRequest` retains a pointer into the reason string for the + // lifetime of the handle, so the UTF-16 buffer must outlive the request. + _reason: Vec, +} + +unsafe impl Send for PowerRequest {} + +impl PowerRequest { + fn prevent_idle_sleep(reason: &str) -> Result { + let mut reason = reason.encode_utf16().chain([0]).collect::>(); + let context = REASON_CONTEXT { + Version: POWER_REQUEST_CONTEXT_VERSION, + Flags: POWER_REQUEST_CONTEXT_SIMPLE_STRING, + Reason: REASON_CONTEXT_0 { + SimpleReasonString: PWSTR(reason.as_mut_ptr()), + }, + }; + let handle = unsafe { PowerCreateRequest(&context) } + .context("Failed to create a Windows power request")?; + if let Err(error) = unsafe { PowerSetRequest(handle, PowerRequestSystemRequired) } { + unsafe { CloseHandle(handle) } + .context("Failed to close the Windows power request") + .log_err(); + return Err(error).context("Failed to set the Windows power request"); + } + Ok(Self { + handle, + _reason: reason, + }) + } +} + +impl Drop for PowerRequest { + fn drop(&mut self) { + unsafe { PowerClearRequest(self.handle, PowerRequestSystemRequired) } + .context("Failed to clear the Windows power request") + .log_err(); + unsafe { CloseHandle(self.handle) } + .context("Failed to close the Windows power request") + .log_err(); + } +} + +impl WindowsPlatform { + pub fn new(headless: bool) -> Result { + unsafe { + OleInitialize(None).context("unable to initialize Windows OLE")?; + } + // A platform that can become windowed needs the real text system from the start: the app + // keeps the one it's created with. It gets DirectX devices when it becomes windowed. + let (text_system, direct_write_text_system) = if !headless { + let dw_text_system = Arc::new( + DirectWriteTextSystem::new(None).context("Error creating DirectWriteTextSystem")?, + ); + ( + dw_text_system.clone() as Arc, + Some(dw_text_system), + ) + } else { + ( + Arc::new(gpui::NoopTextSystem::new()) as Arc, + None, + ) + }; + + let (main_sender, main_receiver) = PriorityQueueReceiver::new(); + let validation_number = if usize::BITS == 64 { + rand::random::() as usize + } else { + rand::random::() as usize + }; + let raw_window_handles = Arc::new(RwLock::new(SmallVec::new())); + + register_platform_window_class(); + let mut context = PlatformWindowCreateContext { + inner: None, + raw_window_handles: Arc::downgrade(&raw_window_handles), + validation_number, + main_sender: Some(main_sender), + main_receiver: Some(main_receiver), + directx_devices: None, + dispatcher: None, + }; + let result = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE(0), + PLATFORM_WINDOW_CLASS_NAME, + None, + WINDOW_STYLE(0), + 0, + 0, + 0, + 0, + Some(HWND_MESSAGE), + None, + None, + Some(&raw const context as *const _), + ) + }; + let inner = context + .inner + .take() + .context("CreateWindowExW did not run correctly")??; + let dispatcher = context + .dispatcher + .take() + .context("CreateWindowExW did not run correctly")?; + let handle = result?; + + let disable_direct_composition = std::env::var(DISABLE_DIRECT_COMPOSITION) + .is_ok_and(|value| value == "true" || value == "1"); + let background_executor = BackgroundExecutor::new(dispatcher.clone()); + let foreground_executor = ForegroundExecutor::new(dispatcher); + + let drop_target_helper: Option = if !headless { + Some(unsafe { + CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER) + .context("Error creating drop target helper.")? + }) + } else { + None + }; + let icon = if !headless { + load_icon().unwrap_or_default() + } else { + HICON::default() + }; + + Ok(Self { + inner, + handle, + raw_window_handles, + initial_windowing: RefCell::new(None), + vsync_stop: RefCell::new(None), + headless, + icon, + background_executor, + foreground_executor, + text_system, + direct_write_text_system, + suspend_resume_notification: RefCell::new(None), + disable_direct_composition, + has_package_identity: has_package_identity(), + drop_target_helper, + invalidate_devices: Arc::new(AtomicBool::new(false)), + app_identity: RefCell::new(None), + system_notifications: RefCell::new(SystemNotificationState::new()), + }) + } + + pub(crate) fn window_from_hwnd(&self, hwnd: HWND) -> Option> { + self.raw_window_handles + .read() + .iter() + .find(|entry| entry.as_raw() == hwnd) + .and_then(|hwnd| window_from_hwnd(hwnd.as_raw())) + } + + #[inline] + fn post_message(&self, message: u32, wparam: WPARAM, lparam: LPARAM) { + self.raw_window_handles + .read() + .iter() + .for_each(|handle| unsafe { + PostMessageW(Some(handle.as_raw()), message, wparam, lparam).log_err(); + }); + } + + fn generate_creation_info(&self) -> WindowCreationInfo { + WindowCreationInfo { + icon: self.icon, + executor: self.foreground_executor.clone(), + current_cursor: self.inner.state.current_cursor.get(), + cursor_visible: self.inner.state.cursor_visible.clone(), + drop_target_helper: self.drop_target_helper.clone().unwrap(), + validation_number: self.inner.validation_number, + main_receiver: self.inner.main_receiver.clone(), + platform_window_handle: self.handle, + disable_direct_composition: self.disable_direct_composition, + directx_devices: self.inner.state.directx_devices.borrow().clone().unwrap(), + invalidate_devices: self.invalidate_devices.clone(), + draw_coordinator: self.inner.state.draw_coordinator.clone(), + } + } + + fn set_dock_menus(&self, menus: Vec) { + let mut actions = Vec::new(); + menus.into_iter().for_each(|menu| { + if let Some(dock_menu) = DockMenuItem::new(menu).log_err() { + actions.push(dock_menu); + } + }); + self.inner.state.jump_list.borrow_mut().dock_menus = actions; + let borrow = self.inner.state.jump_list.borrow(); + let dock_menus = borrow + .dock_menus + .iter() + .map(|menu| (menu.name.clone(), menu.description.clone())) + .collect::>(); + let recent_workspaces = borrow.recent_workspaces.clone(); + self.background_executor + .spawn(async move { + update_jump_list(&recent_workspaces, &dock_menus).log_err(); + }) + .detach(); + } + + fn update_jump_list( + &self, + menus: Vec, + entries: Vec>, + ) -> Task>> { + let mut actions = Vec::new(); + menus.into_iter().for_each(|menu| { + if let Some(dock_menu) = DockMenuItem::new(menu).log_err() { + actions.push(dock_menu); + } + }); + let mut jump_list = self.inner.state.jump_list.borrow_mut(); + jump_list.dock_menus = actions; + jump_list.recent_workspaces = entries.into(); + let dock_menus = jump_list + .dock_menus + .iter() + .map(|menu| (menu.name.clone(), menu.description.clone())) + .collect::>(); + let recent_workspaces = jump_list.recent_workspaces.clone(); + self.background_executor.spawn(async move { + update_jump_list(&recent_workspaces, &dock_menus) + .log_err() + .unwrap_or_default() + }) + } + + fn find_current_active_window(&self) -> Option { + let active_window_hwnd = unsafe { GetActiveWindow() }; + if active_window_hwnd.is_invalid() { + return None; + } + self.raw_window_handles + .read() + .iter() + .find(|hwnd| hwnd.as_raw() == active_window_hwnd) + .map(|hwnd| hwnd.as_raw()) + } + + fn is_windowed(&self) -> bool { + self.inner.state.directx_devices.borrow().is_some() + } + + /// Connects in the initial windowing mode, before the app finishes launching. + /// + /// # Panics + /// + /// Panics if DirectX can't be initialized, since the app can't start. Starts headless if this + /// process can't show windows, for example when started over SSH. + fn connect_initially(&self) { + let request = self + .initial_windowing + .borrow_mut() + .take() + .unwrap_or_else(|| WindowingRequest::Windowed(GraphicalEnvironment::detect())); + let WindowingRequest::Windowed(environment) = request else { + return; + }; + if let Err(error) = check_can_show_windows(&environment) { + log::info!("starting headless: {error:#}"); + return; + } + self.attach_gpu() + .unwrap_or_else(|error| panic!("failed to initialize DirectX: {error:#}")); + } + + /// Creates the DirectX devices that windows draw with, and starts vsync. + fn attach_gpu(&self) -> Result<()> { + let devices = DirectXDevices::new().context("Creating DirectX devices")?; + if let Some(text_system) = &self.direct_write_text_system { + text_system.handle_gpu_lost(&devices)?; + } + *self.inner.state.directx_devices.borrow_mut() = Some(devices); + self.begin_vsync_thread(); + Ok(()) + } + + /// Stops vsync and releases the DirectX devices. No window may be open. + fn detach_gpu(&self) { + if let Some(stop) = self.vsync_stop.borrow_mut().take() { + // Not joined: the thread can be sending this thread a message. It exits after its + // next vsync, and drops its reference to the devices then. + stop.store(true, Ordering::Release); + } + if let Some(text_system) = &self.direct_write_text_system { + text_system.release_gpu(); + } + self.inner.state.directx_devices.borrow_mut().take(); + } + + fn begin_vsync_thread(&self) { + let Some(directx_devices) = self.inner.state.directx_devices.borrow().clone() else { + return; + }; + let Some(direct_write_text_system) = &self.direct_write_text_system else { + return; + }; + let stop = Arc::new(AtomicBool::new(false)); + *self.vsync_stop.borrow_mut() = Some(stop.clone()); + let mut directx_device = directx_devices; + let platform_window: SafeHwnd = self.handle.into(); + let validation_number = self.inner.validation_number; + let all_windows = Arc::downgrade(&self.raw_window_handles); + let text_system = Arc::downgrade(direct_write_text_system); + let invalidate_devices = self.invalidate_devices.clone(); + + std::thread::Builder::new() + .name("VSyncProvider".to_owned()) + .spawn(move || { + let vsync_provider = VSyncProvider::new(); + loop { + let signal_source = vsync_provider.wait_for_vsync(); + let signal_at = PlatformFrameSignal::capture(scheduler::Instant::now); + if stop.load(Ordering::Acquire) { + break; + } + if check_device_lost(&directx_device.device) + || invalidate_devices.fetch_and(false, Ordering::Acquire) + { + if let Err(err) = handle_gpu_device_lost( + &mut directx_device, + platform_window.as_raw(), + validation_number, + &all_windows, + &text_system, + ) { + panic!("Device lost: {err}"); + } + } + let Some(all_windows) = all_windows.upgrade() else { + break; + }; + for hwnd in all_windows.read().iter() { + unsafe { + if let Some(signal_at) = signal_at { + if IsWindowVisible(hwnd.as_raw()).as_bool() + && !IsIconic(hwnd.as_raw()).as_bool() + { + hwnd.frame_signal.record(signal_at, signal_source); + } else { + // Hidden windows may not consume WM_PAINT until shown again. + hwnd.frame_signal.take(); + } + } + RedrawWindow(Some(hwnd.as_raw()), None, None, RDW_INVALIDATE) + .ok() + .log_err(); + } + } + } + }) + .unwrap(); + } +} + +fn translate_accelerator(msg: &MSG) -> Option<()> { + if msg.message != WM_KEYDOWN && msg.message != WM_SYSKEYDOWN { + return None; + } + + let result = unsafe { + SendMessageW( + msg.hwnd, + WM_GPUI_KEYDOWN, + Some(msg.wParam), + Some(msg.lParam), + ) + }; + (result.0 == 0).then_some(()) +} + +fn encode_restart_arguments(arguments: &[OsString]) -> OsString { + // `Start-Process` accepts a single native command line, so quote each argument according to + // the Windows argv parsing rules before passing the complete string through the environment. + let mut encoded = Vec::new(); + + for (index, argument) in arguments.iter().enumerate() { + if index > 0 { + encoded.push(b' ' as u16); + } + encoded.push(b'"' as u16); + + let mut backslash_count = 0; + for code_unit in argument.encode_wide() { + if code_unit == b'\\' as u16 { + backslash_count += 1; + } else { + if code_unit == b'"' as u16 { + encoded.extend(std::iter::repeat_n(b'\\' as u16, backslash_count * 2 + 1)); + } else { + encoded.extend(std::iter::repeat_n(b'\\' as u16, backslash_count)); + } + backslash_count = 0; + encoded.push(code_unit); + } + } + + encoded.extend(std::iter::repeat_n(b'\\' as u16, backslash_count * 2)); + encoded.push(b'"' as u16); + } + + OsString::from_wide(&encoded) +} + +impl Platform for WindowsPlatform { + fn background_executor(&self) -> BackgroundExecutor { + self.background_executor.clone() + } + + fn foreground_executor(&self) -> ForegroundExecutor { + self.foreground_executor.clone() + } + + fn text_system(&self) -> Arc { + self.text_system.clone() + } + + fn keyboard_layout(&self) -> Box { + Box::new( + WindowsKeyboardLayout::new() + .log_err() + .unwrap_or(WindowsKeyboardLayout::unknown()), + ) + } + + fn keyboard_mapper(&self) -> Rc { + Rc::new(WindowsKeyboardMapper::new()) + } + + fn on_keyboard_layout_change(&self, callback: Box) { + self.inner + .state + .callbacks + .keyboard_layout_change + .set(Some(callback)); + } + + fn on_thermal_state_change(&self, _callback: Box) {} + + fn thermal_state(&self) -> ThermalState { + ThermalState::Nominal + } + + fn prevent_idle_sleep(&self, reason: &str) -> Task> { + Task::ready( + PowerRequest::prevent_idle_sleep(reason) + .map(|request| ActivityGuard::new(move || drop(request))), + ) + } + + fn run(&self, on_finish_launching: Box) { + if !self.headless { + self.connect_initially(); + } + on_finish_launching(); + + let mut msg = MSG::default(); + unsafe { + while GetMessageW(&mut msg, None, 0, 0).as_bool() { + if translate_accelerator(&msg).is_none() { + _ = TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + } + + self.inner.with_callback( + |callbacks| &callbacks.quit, + |callback| { + callback(); + }, + ); + } + + fn quit(&self) { + self.foreground_executor() + .spawn(async { unsafe { PostQuitMessage(0) } }) + .detach(); + } + + fn set_initial_windowing(&self, request: WindowingRequest) { + *self.initial_windowing.borrow_mut() = Some(request); + } + + fn request_windowing(&self, request: WindowingRequest) -> Task> { + if self.headless { + return Task::ready(Err(anyhow!( + "a platform created headless can't switch windowing modes" + ))); + } + let windowed = self.is_windowed(); + Task::ready(match request { + WindowingRequest::Headless if !windowed => Err(anyhow!("already headless")), + WindowingRequest::Headless => { + self.detach_gpu(); + Ok(()) + } + WindowingRequest::Windowed(_) if windowed => Err(anyhow!("already windowed")), + WindowingRequest::Windowed(environment) => { + check_can_show_windows(&environment).and_then(|()| self.attach_gpu()) + } + }) + } + + fn graphical_environment(&self) -> Option { + self.is_windowed().then(GraphicalEnvironment::detect) + } + + fn restart(&self, binary_path: Option, arguments: Vec) { + let pid = std::process::id(); + let Some(app_path) = binary_path.or(self.app_path().log_err()) else { + return; + }; + let script = r#" + $pidToWaitFor = $env:ZED_RESTART_PID + $exePath = $env:ZED_RESTART_EXECUTABLE + $argumentList = $env:ZED_RESTART_ARGUMENTS + + [Environment]::SetEnvironmentVariable("ZED_RESTART_PID", $null) + [Environment]::SetEnvironmentVariable("ZED_RESTART_EXECUTABLE", $null) + [Environment]::SetEnvironmentVariable("ZED_RESTART_ARGUMENTS", $null) + + while ($true) { + $process = Get-Process -Id $pidToWaitFor -ErrorAction SilentlyContinue + if (-not $process) { + if ([string]::IsNullOrEmpty($argumentList)) { + Start-Process -FilePath $exePath + } else { + Start-Process -FilePath $exePath -ArgumentList $argumentList + } + break + } + Start-Sleep -Seconds 0.1 + } + "#; + + // Defer spawning to the foreground executor so it runs after the + // current `AppCell` borrow is released. On Windows, `Command::spawn()` + // can pump the Win32 message loop (via `CreateProcessW`), which + // re-enters message handling possibly resulting in another mutable + // borrow of the `AppCell` ending up with a double borrow panic + let Some(powershell) = get_powershell() else { + log::error!("failed to restart: PowerShell is unavailable"); + return; + }; + self.foreground_executor + .spawn(async move { + let mut command = new_std_command(powershell); + let arguments = encode_restart_arguments(&arguments); + command + .arg("-command") + .arg(script) + .env("ZED_RESTART_PID", pid.to_string()) + .env("ZED_RESTART_EXECUTABLE", app_path) + .env("ZED_RESTART_ARGUMENTS", arguments); + #[allow( + clippy::disallowed_methods, + reason = "We are restarting ourselves, using std command thus is fine" + )] + let restart_process = command.spawn(); + + match restart_process { + Ok(_) => unsafe { PostQuitMessage(0) }, + Err(e) => log::error!("failed to spawn restart script: {:?}", e), + } + }) + .detach(); + } + + fn activate(&self, _ignoring_other_apps: bool) {} + + fn hide(&self) {} + + // todo(windows) + fn hide_other_apps(&self) { + unimplemented!() + } + + // todo(windows) + fn unhide_other_apps(&self) { + unimplemented!() + } + + fn displays(&self) -> Vec> { + WindowsDisplay::displays() + } + + fn primary_display(&self) -> Option> { + WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc) + } + + #[cfg(feature = "screen-capture")] + fn is_screen_capture_supported(&self) -> bool { + true + } + + #[cfg(feature = "screen-capture")] + fn screen_capture_sources( + &self, + ) -> futures::channel::oneshot::Receiver>>> { + gpui::scap_screen_capture::scap_screen_sources(&self.foreground_executor) + } + + fn active_window(&self) -> Option { + let active_window_hwnd = unsafe { GetActiveWindow() }; + self.window_from_hwnd(active_window_hwnd) + .map(|inner| inner.handle) + } + + fn open_window( + &self, + handle: AnyWindowHandle, + options: WindowParams, + ) -> Result> { + if !self.is_windowed() { + return Err(anyhow!("cannot open windows while headless")); + } + let window = WindowsWindow::new(handle, options, self.generate_creation_info())?; + let handle = window.get_raw_handle(); + self.raw_window_handles.write().push(TrackedWindow { + handle: handle.into(), + frame_signal: window.state.frame_signal.clone(), + }); + + Ok(Box::new(window)) + } + + fn window_appearance(&self) -> WindowAppearance { + system_appearance().log_err().unwrap_or_default() + } + + fn open_url(&self, url: &str) { + if url.is_empty() { + return; + } + let url_string = url.to_string(); + self.background_executor() + .spawn(async move { + open_target(&url_string) + .with_context(|| format!("Opening url: {}", url_string)) + .log_err(); + }) + .detach(); + } + + fn on_open_urls(&self, callback: Box)>) { + self.inner.state.callbacks.open_urls.set(Some(callback)); + } + + fn prompt_for_paths( + &self, + options: PathPromptOptions, + ) -> Receiver>>> { + let owner = self + .find_current_active_window() + .and_then(|hwnd| self.window_from_hwnd(hwnd)) + .map(|window| window.dialog_owner.clone()); + crate::dialog::show_dialog(owner, &self.foreground_executor, move |window| { + file_open_dialog(options, Some(window)) + }) + } + + fn prompt_for_new_path( + &self, + directory: &Path, + suggested_name: Option<&str>, + ) -> Receiver>> { + let directory = directory.to_owned(); + let suggested_name = suggested_name.map(|s| s.to_owned()); + let owner = self + .find_current_active_window() + .and_then(|hwnd| self.window_from_hwnd(hwnd)) + .map(|window| window.dialog_owner.clone()); + crate::dialog::show_dialog(owner, &self.foreground_executor, move |window| { + file_save_dialog(directory, suggested_name, Some(window)) + }) + } + + fn can_select_mixed_files_and_dirs(&self) -> bool { + // The FOS_PICKFOLDERS flag toggles between "only files" and "only folders". + false + } + + fn reveal_path(&self, path: &Path) { + if path.as_os_str().is_empty() { + return; + } + let path = path.to_path_buf(); + self.background_executor() + .spawn(async move { + open_target_in_explorer(&path) + .with_context(|| format!("Revealing path {} in explorer", path.display())) + .log_err(); + }) + .detach(); + } + + fn open_with_system(&self, path: &Path) { + if path.as_os_str().is_empty() { + return; + } + let path = path.to_path_buf(); + self.background_executor() + .spawn(async move { + open_target(&path) + .with_context(|| format!("Opening {} with system", path.display())) + .log_err(); + }) + .detach(); + } + + fn on_quit(&self, callback: Box bool>) { + self.inner.state.callbacks.quit.set(Some(callback)); + } + + fn on_reopen(&self, callback: Box) { + self.inner.state.callbacks.reopen.set(Some(callback)); + } + + fn on_system_sleep(&self, callback: Box) { + self.inner.state.callbacks.system_sleep.set(Some(callback)); + } + + fn on_system_wake(&self, callback: Box) { + self.inner.state.callbacks.system_wake.set(Some(callback)); + let mut notification = self.suspend_resume_notification.borrow_mut(); + if notification.is_none() { + *notification = unsafe { + // SAFETY: self.handle is the platform window receiving WM_POWERBROADCAST. + RegisterSuspendResumeNotification( + HANDLE(self.handle.0), + DEVICE_NOTIFY_WINDOW_HANDLE, + ) + .log_err() + }; + } + } + + fn set_app_identity(&self, identifier: &str, name: &str) { + // If the process has package identity, it's automatally granted an AUMID by the system. + if self.has_package_identity { + return; + } + + let identifier_utf16 = windows::core::HSTRING::from(identifier); + // SAFETY: `identifier_utf16` outlives the call and is null-terminated. + if let Err(error) = unsafe { + windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID( + windows::core::PCWSTR(identifier_utf16.as_ptr()), + ) + } { + log::warn!("failed to set the process AppUserModelID: {error}"); + } + *self.app_identity.borrow_mut() = Some((identifier.to_string(), name.to_string())); + } + + fn show_system_notification(&self, notification: gpui::SystemNotification) { + let app_identity = self.app_identity.borrow().clone(); + self.system_notifications + .borrow_mut() + .show( + self.has_package_identity, + app_identity + .as_ref() + .map(|(identifier, name)| (identifier.as_str(), name.as_str())), + notification, + ) + .log_err(); + } + + fn dismiss_system_notification(&self, tag: &str) { + self.system_notifications.borrow_mut().dismiss(tag); + } + + fn on_system_notification_response( + &self, + callback: Box, + ) { + self.system_notifications + .borrow_mut() + .on_response(&self.foreground_executor, callback); + } + + fn set_menus(&self, menus: Vec, _keymap: &Keymap) { + *self.inner.state.menus.borrow_mut() = menus.into_iter().map(|menu| menu.owned()).collect(); + } + + fn get_menus(&self) -> Option> { + Some(self.inner.state.menus.borrow().clone()) + } + + fn set_dock_menu(&self, menus: Vec, _keymap: &Keymap) { + self.set_dock_menus(menus); + } + + fn on_app_menu_action(&self, callback: Box) { + self.inner + .state + .callbacks + .app_menu_action + .set(Some(callback)); + } + + fn on_will_open_app_menu(&self, callback: Box) { + self.inner + .state + .callbacks + .will_open_app_menu + .set(Some(callback)); + } + + fn on_validate_app_menu_command(&self, callback: Box bool>) { + self.inner + .state + .callbacks + .validate_app_menu_command + .set(Some(callback)); + } + + fn app_path(&self) -> Result { + Ok(std::env::current_exe()?) + } + + // todo(windows) + fn path_for_auxiliary_executable(&self, _name: &str) -> Result { + anyhow::bail!("not yet implemented"); + } + + fn set_cursor_style(&self, style: CursorStyle) { + let hcursor = load_cursor(style); + if self.inner.state.current_cursor.get().map(|c| c.0) != hcursor.map(|c| c.0) { + self.post_message( + WM_GPUI_CURSOR_STYLE_CHANGED, + WPARAM(0), + LPARAM(hcursor.map_or(0, |c| c.0 as isize)), + ); + self.inner.state.current_cursor.set(hcursor); + } + } + + fn hide_cursor_until_mouse_moves(&self) { + if !self + .inner + .state + .cursor_visible + .swap(false, Ordering::Relaxed) + { + return; + } + + for handle in self.raw_window_handles.read().iter() { + let Some(window) = window_from_hwnd(handle.as_raw()) else { + continue; + }; + if window.state.hovered.get() { + unsafe { SetCursor(None) }; + break; + } + } + } + + fn is_cursor_visible(&self) -> bool { + self.inner.state.cursor_visible.load(Ordering::Relaxed) + } + + fn should_auto_hide_scrollbars(&self) -> bool { + should_auto_hide_scrollbars().log_err().unwrap_or(false) + } + + fn write_to_clipboard(&self, item: ClipboardItem) { + write_to_clipboard(item); + } + + fn read_from_clipboard(&self) -> Option { + read_from_clipboard() + } + + fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task> { + // CredWriteW rejects larger blobs with the opaque RPC error + // 0x800706F7 "The stub received bad data", so fail with a clear + // message instead. + if password.len() > CRED_MAX_CREDENTIAL_BLOB_SIZE as usize { + return Task::ready(Err(anyhow!( + "credential for {url} is {} bytes, which exceeds the Windows Credential Manager limit of {CRED_MAX_CREDENTIAL_BLOB_SIZE} bytes", + password.len() + ))); + } + let password = password.to_vec(); + let mut username = username.encode_utf16().chain(Some(0)).collect_vec(); + let mut target_name = windows_credentials_target_name(url) + .encode_utf16() + .chain(Some(0)) + .collect_vec(); + self.background_executor().spawn(async move { + let credentials = CREDENTIALW { + LastWritten: unsafe { GetSystemTimeAsFileTime() }, + Flags: CRED_FLAGS(0), + Type: CRED_TYPE_GENERIC, + TargetName: PWSTR::from_raw(target_name.as_mut_ptr()), + CredentialBlobSize: password.len() as u32, + CredentialBlob: password.as_ptr() as *mut _, + Persist: CRED_PERSIST_LOCAL_MACHINE, + UserName: PWSTR::from_raw(username.as_mut_ptr()), + ..CREDENTIALW::default() + }; + unsafe { + CredWriteW(&credentials, 0).map_err(|err| { + anyhow!( + "Failed to write credentials to Windows Credential Manager: {}", + err, + ) + })?; + } + Ok(()) + }) + } + + fn read_credentials(&self, url: &str) -> Task)>>> { + let target_name = windows_credentials_target_name(url) + .encode_utf16() + .chain(Some(0)) + .collect_vec(); + self.background_executor().spawn(async move { + let mut credentials: *mut CREDENTIALW = std::ptr::null_mut(); + let result = unsafe { + CredReadW( + PCWSTR::from_raw(target_name.as_ptr()), + CRED_TYPE_GENERIC, + None, + &mut credentials, + ) + }; + + if let Err(err) = result { + // ERROR_NOT_FOUND means the credential doesn't exist. + // Return Ok(None) to match macOS and Linux behavior. + if err.code() == ERROR_NOT_FOUND.to_hresult() { + return Ok(None); + } + return Err(err.into()); + } + + if credentials.is_null() { + return Ok(None); + } + + // SAFETY: `CredReadW` succeeded, so this points to a valid `CREDENTIALW` until `CredFree` below. + let result = unsafe { username_and_password(&*credentials) }; + unsafe { CredFree(credentials as *const _ as _) }; + result.map(Some) + }) + } + + fn delete_credentials(&self, url: &str) -> Task> { + let target_name = windows_credentials_target_name(url) + .encode_utf16() + .chain(Some(0)) + .collect_vec(); + self.background_executor().spawn(async move { + unsafe { + CredDeleteW( + PCWSTR::from_raw(target_name.as_ptr()), + CRED_TYPE_GENERIC, + None, + )? + }; + Ok(()) + }) + } + + fn register_url_scheme(&self, _: &str) -> Task> { + Task::ready(Err(anyhow!("register_url_scheme unimplemented"))) + } + + fn perform_dock_menu_action(&self, action: usize) { + unsafe { + PostMessageW( + Some(self.handle), + WM_GPUI_DOCK_MENU_ACTION, + WPARAM(self.inner.validation_number), + LPARAM(action as isize), + ) + .log_err(); + } + } + + fn update_jump_list( + &self, + menus: Vec, + entries: Vec>, + ) -> Task>> { + self.update_jump_list(menus, entries) + } +} + +impl WindowsPlatformInner { + fn new(context: &mut PlatformWindowCreateContext) -> Result> { + let state = WindowsPlatformState::new(context.directx_devices.take()); + Ok(Rc::new(Self { + state, + raw_window_handles: context.raw_window_handles.clone(), + dispatcher: context + .dispatcher + .as_ref() + .context("missing dispatcher")? + .clone(), + validation_number: context.validation_number, + main_receiver: context + .main_receiver + .take() + .context("missing main receiver")?, + })) + } + + /// Calls `project` to project to the corresponding callback field, removes it from callbacks, calls `f` with the callback and then puts the callback back. + fn with_callback( + &self, + project: impl Fn(&PlatformCallbacks) -> &Cell>, + f: impl FnOnce(&mut T), + ) { + let callback = project(&self.state.callbacks).take(); + if let Some(mut callback) = callback { + f(&mut callback); + project(&self.state.callbacks).set(Some(callback)); + } + } + + fn handle_msg( + self: &Rc, + handle: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, + ) -> LRESULT { + let handled = match msg { + WM_GPUI_CLOSE_ONE_WINDOW + | WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD + | WM_GPUI_DOCK_MENU_ACTION + | WM_GPUI_KEYBOARD_LAYOUT_CHANGED + | WM_GPUI_GPU_DEVICE_LOST + | WM_GPUI_END_SESSION => self.handle_gpui_events(msg, wparam, lparam), + WM_POWERBROADCAST => self.handle_power_broadcast(wparam), + _ => None, + }; + if let Some(result) = handled { + LRESULT(result) + } else { + unsafe { DefWindowProcW(handle, msg, wparam, lparam) } + } + } + + fn handle_gpui_events(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> Option { + if wparam.0 != self.validation_number { + log::error!("Wrong validation number while processing message: {message}"); + return None; + } + match message { + WM_GPUI_CLOSE_ONE_WINDOW => { + self.close_one_window(HWND(lparam.0 as _)); + Some(0) + } + WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD => self.run_foreground_task(), + WM_GPUI_DOCK_MENU_ACTION => self.handle_dock_action_event(lparam.0 as _), + WM_GPUI_KEYBOARD_LAYOUT_CHANGED => self.handle_keyboard_layout_change(), + WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam), + WM_GPUI_END_SESSION => self.handle_end_session(), + _ => unreachable!(), + } + } + + fn handle_end_session(&self) -> Option { + let mut shutdown_completed = false; + self.with_callback( + |callbacks| &callbacks.quit, + |callback| shutdown_completed = callback(), + ); + log::logger().flush(); + if shutdown_completed { + std::process::exit(0); + } + + // Shutdown couldn't run synchronously, since the AppCell is already borrowed. + // Windows may terminate the application as soon as we return from this handler, but if we post a WM_QUIT message now, + // we may get to gracefully shut down the app before we're terminated by the OS. + unsafe { PostQuitMessage(0) }; + Some(0) + } + + fn close_one_window(&self, target_window: HWND) -> bool { + let Some(all_windows) = self.raw_window_handles.upgrade() else { + log::error!("Failed to upgrade raw window handles"); + return false; + }; + let mut lock = all_windows.write(); + let index = lock + .iter() + .position(|handle| handle.as_raw() == target_window) + .unwrap(); + lock.remove(index); + + lock.is_empty() + } + + #[inline] + fn run_foreground_task(&self) -> Option { + const MAIN_TASK_TIMEOUT: u128 = 10; + + let start = std::time::Instant::now(); + 'tasks: loop { + 'timeout_loop: loop { + if start.elapsed().as_millis() >= MAIN_TASK_TIMEOUT { + log::debug!("foreground task timeout reached"); + // we spent our budget on gpui tasks, we likely have a lot of work queued so drain system events first to stay responsive + // then quit out of foreground work to allow us to process other gpui events first before returning back to foreground task work + // if we don't we might not for example process window quit events + let mut msg = MSG::default(); + let process_message = |msg: &_| { + if translate_accelerator(msg).is_none() { + _ = unsafe { TranslateMessage(msg) }; + unsafe { DispatchMessageW(msg) }; + } + }; + let peek_msg = |msg: &mut _, msg_kind| unsafe { + PeekMessageW(msg, None, 0, 0, PM_REMOVE | msg_kind).as_bool() + }; + // We need to process a paint message here as otherwise we will re-enter `run_foreground_task` before painting if we have work remaining. + // The reason for this is that windows prefers custom application message processing over system messages. + if peek_msg(&mut msg, PM_QS_PAINT) { + process_message(&msg); + } + while peek_msg(&mut msg, PM_QS_INPUT) { + process_message(&msg); + } + // Allow the main loop to process other gpui events before going back into `run_foreground_task` + unsafe { + if let Err(_) = PostMessageW( + Some(self.dispatcher.platform_window_handle.as_raw()), + WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD, + WPARAM(self.validation_number), + LPARAM(0), + ) { + self.dispatcher.wake_posted.store(false, Ordering::Release); + }; + } + break 'tasks; + } + let mut main_receiver = self.main_receiver.clone(); + match main_receiver.try_pop() { + Ok(Some(runnable)) => WindowsDispatcher::execute_runnable(runnable), + _ => break 'timeout_loop, + } + } + + // Someone could enqueue a Runnable here. The flag is still true, so they will not PostMessage. + // We need to check for those Runnables after we clear the flag. + self.dispatcher.wake_posted.store(false, Ordering::Release); + let mut main_receiver = self.main_receiver.clone(); + match main_receiver.try_pop() { + Ok(Some(runnable)) => { + self.dispatcher.wake_posted.store(true, Ordering::Release); + + WindowsDispatcher::execute_runnable(runnable); + } + _ => break 'tasks, + } + } + + Some(0) + } + + fn handle_dock_action_event(&self, action_idx: usize) -> Option { + let Some(action) = self + .state + .jump_list + .borrow() + .dock_menus + .get(action_idx) + .map(|dock_menu| dock_menu.action.boxed_clone()) + else { + log::error!("Dock menu for index {action_idx} not found"); + return Some(1); + }; + self.with_callback( + |callbacks| &callbacks.app_menu_action, + |callback| callback(&*action), + ); + Some(0) + } + + fn handle_keyboard_layout_change(&self) -> Option { + self.with_callback( + |callbacks| &callbacks.keyboard_layout_change, + |callback| callback(), + ); + Some(0) + } + + fn handle_power_broadcast(&self, wparam: WPARAM) -> Option { + match wparam.0 as u32 { + PBT_APMSUSPEND => { + self.with_callback(|callbacks| &callbacks.system_sleep, |callback| callback()); + } + PBT_APMRESUMEAUTOMATIC => { + self.with_callback(|callbacks| &callbacks.system_wake, |callback| callback()); + } + _ => {} + } + Some(1) + } + + fn handle_device_lost(&self, lparam: LPARAM) -> Option { + let directx_devices = lparam.0 as *const DirectXDevices; + let directx_devices = unsafe { &*directx_devices }; + let mut current = self.state.directx_devices.borrow_mut(); + // The platform may have switched to headless while the device was being recovered. + if current.is_some() { + *current = Some(directx_devices.clone()); + } + + Some(0) + } +} + +impl Drop for WindowsPlatform { + fn drop(&mut self) { + if let Some(stop) = self.vsync_stop.borrow_mut().take() { + stop.store(true, Ordering::Release); + } + unsafe { + if let Some(notification) = self.suspend_resume_notification.borrow_mut().take() { + // SAFETY: notification was returned by RegisterSuspendResumeNotification. + UnregisterSuspendResumeNotification(notification).log_err(); + } + DestroyWindow(self.handle) + .context("Destroying platform window") + .log_err(); + OleUninitialize(); + } + } +} + +pub(crate) struct WindowCreationInfo { + pub(crate) icon: HICON, + pub(crate) executor: ForegroundExecutor, + pub(crate) current_cursor: Option, + pub(crate) cursor_visible: Arc, + pub(crate) drop_target_helper: IDropTargetHelper, + pub(crate) validation_number: usize, + pub(crate) main_receiver: PriorityQueueReceiver, + pub(crate) platform_window_handle: HWND, + pub(crate) disable_direct_composition: bool, + pub(crate) directx_devices: DirectXDevices, + /// Flag to instruct the `VSyncProvider` thread to invalidate the directx devices + /// as resizing them has failed, causing us to have lost at least the render target. + pub(crate) invalidate_devices: Arc, + /// Shared with [`WindowsPlatformState::draw_coordinator`] and every other window. + pub(crate) draw_coordinator: Rc, +} + +struct PlatformWindowCreateContext { + inner: Option>>, + raw_window_handles: std::sync::Weak>>, + validation_number: usize, + main_sender: Option>, + main_receiver: Option>, + directx_devices: Option, + dispatcher: Option>, +} + +fn has_package_identity() -> bool { + let mut package_full_name_length = 0; + let result = unsafe { + windows::Win32::Storage::Packaging::Appx::GetCurrentPackageFullName( + &mut package_full_name_length, + None, + ) + }; + if result == ERROR_INSUFFICIENT_BUFFER { + true + } else if result == APPMODEL_ERROR_NO_PACKAGE { + false + } else { + log::warn!("failed to determine whether the process has package identity: {result:?}"); + false + } +} + +fn open_target(target: impl AsRef) -> Result<()> { + let target = target.as_ref(); + let ret = unsafe { + ShellExecuteW( + None, + windows::core::w!("open"), + &HSTRING::from(target), + None, + None, + SW_SHOWDEFAULT, + ) + }; + if ret.0 as isize <= 32 { + Err(anyhow::anyhow!( + "Unable to open target: {}", + std::io::Error::last_os_error() + )) + } else { + Ok(()) + } +} + +fn open_target_in_explorer(target: &Path) -> Result<()> { + let dir = target.parent().context("No parent folder found")?; + let desktop = unsafe { SHGetDesktopFolder()? }; + + let mut dir_item = std::ptr::null_mut(); + unsafe { + desktop.ParseDisplayName( + HWND::default(), + None, + &HSTRING::from(dir), + None, + &mut dir_item, + std::ptr::null_mut(), + )?; + } + + let mut file_item = std::ptr::null_mut(); + unsafe { + desktop.ParseDisplayName( + HWND::default(), + None, + &HSTRING::from(target), + None, + &mut file_item, + std::ptr::null_mut(), + )?; + } + + let highlight = [file_item as *const _]; + unsafe { SHOpenFolderAndSelectItems(dir_item as _, Some(&highlight), 0) }.or_else(|err| { + if err.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 { + // On some systems, the above call mysteriously fails with "file not + // found" even though the file is there. In these cases, ShellExecute() + // seems to work as a fallback (although it won't select the file). + open_target(dir).context("Opening target parent folder") + } else { + Err(anyhow::anyhow!("Can not open target path: {}", err)) + } + }) +} + +fn file_open_dialog( + options: PathPromptOptions, + window: Option, +) -> Result>> { + let folder_dialog: IFileOpenDialog = + unsafe { CoCreateInstance(&FileOpenDialog, None, CLSCTX_ALL)? }; + + let mut dialog_options = FOS_FILEMUSTEXIST; + if options.multiple { + dialog_options |= FOS_ALLOWMULTISELECT; + } + if options.directories { + dialog_options |= FOS_PICKFOLDERS; + } + + unsafe { + folder_dialog.SetOptions(dialog_options)?; + + if let Some(prompt) = options.prompt { + let prompt: &str = &prompt; + folder_dialog.SetOkButtonLabel(&HSTRING::from(prompt))?; + } + + if let Err(error) = folder_dialog.Show(window) { + if error.code() == HRESULT::from_win32(ERROR_CANCELLED.0) { + return Ok(None); + } + return Err(error.into()); + } + } + + let results = unsafe { folder_dialog.GetResults()? }; + let file_count = unsafe { results.GetCount()? }; + if file_count == 0 { + return Ok(None); + } + + let mut paths = Vec::with_capacity(file_count as usize); + for i in 0..file_count { + let item = unsafe { results.GetItemAt(i)? }; + let path = unsafe { item.GetDisplayName(SIGDN_FILESYSPATH)?.to_string()? }; + paths.push(PathBuf::from(path)); + } + + Ok(Some(paths)) +} + +fn file_save_dialog( + directory: PathBuf, + suggested_name: Option, + window: Option, +) -> Result> { + let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? }; + if !directory.to_string_lossy().is_empty() + && let Some(full_path) = directory + .canonicalize() + .context("failed to canonicalize directory") + .log_err() + { + let full_path = dunce::simplified(&full_path); + let full_path_string = full_path.display().to_string(); + let path_item: IShellItem = + unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_string), None)? }; + unsafe { + dialog + .SetFolder(&path_item) + .context("failed to set dialog folder") + .log_err() + }; + } + + if let Some(suggested_name) = suggested_name { + unsafe { + dialog + .SetFileName(&HSTRING::from(suggested_name)) + .context("failed to set file name") + .log_err() + }; + } + + unsafe { + dialog.SetFileTypes(&[Common::COMDLG_FILTERSPEC { + pszName: windows::core::w!("All files"), + pszSpec: windows::core::w!("*.*"), + }])?; + if let Err(error) = dialog.Show(window) { + if error.code() == HRESULT::from_win32(ERROR_CANCELLED.0) { + return Ok(None); + } + return Err(error.into()); + } + } + let shell_item = unsafe { dialog.GetResult()? }; + let file_path_string = unsafe { + let pwstr = shell_item.GetDisplayName(SIGDN_FILESYSPATH)?; + let string = pwstr.to_string()?; + CoTaskMemFree(Some(pwstr.0 as _)); + string + }; + Ok(Some(PathBuf::from(file_path_string))) +} + +fn load_icon() -> Result { + let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? }; + let handle = unsafe { + LoadImageW( + Some(module.into()), + windows::core::PCWSTR(1 as _), + IMAGE_ICON, + 0, + 0, + LR_DEFAULTSIZE | LR_SHARED, + ) + .context("unable to load icon file")? + }; + Ok(HICON(handle.0)) +} + +#[inline] +fn should_auto_hide_scrollbars() -> Result { + let ui_settings = UISettings::new()?; + Ok(ui_settings.AutoHideScrollBars()?) +} + +fn check_device_lost(device: &ID3D11Device) -> bool { + let device_state = unsafe { device.GetDeviceRemovedReason() }; + match device_state { + Ok(_) => false, + Err(err) => { + log::error!("DirectX device lost detected: {:?}", err); + true + } + } +} + +/// Checks that this process can show windows in the session `environment` names. +fn check_can_show_windows(environment: &GraphicalEnvironment) -> Result<()> { + let current = GraphicalEnvironment::detect().session_id; + if let (Some(requested), Some(current)) = (environment.session_id, current) + && requested != current + { + return Err(anyhow!( + "windows can only be shown in this process's session ({current}), not session \ + {requested}" + )); + } + if !is_window_station_visible() { + return Err(anyhow!( + "this process's window station is not interactive, so it can't show windows" + )); + } + Ok(()) +} + +/// Whether this process's window station has a visible desktop. A service's, or an SSH login's, +/// doesn't. +fn is_window_station_visible() -> bool { + use windows::Win32::System::StationsAndDesktops::{ + GetProcessWindowStation, GetUserObjectInformationW, UOI_FLAGS, USEROBJECTFLAGS, + }; + + let Ok(station) = (unsafe { GetProcessWindowStation() }) else { + return false; + }; + let mut flags = USEROBJECTFLAGS::default(); + // SAFETY: `flags` is valid for writes of the size passed. + let result = unsafe { + GetUserObjectInformationW( + HANDLE(station.0), + UOI_FLAGS, + Some(&mut flags as *mut USEROBJECTFLAGS as *mut std::ffi::c_void), + std::mem::size_of::() as u32, + None, + ) + }; + result.is_ok() && flags.dwFlags & WSF_VISIBLE as u32 != 0 +} + +fn handle_gpu_device_lost( + directx_devices: &mut DirectXDevices, + platform_window: HWND, + validation_number: usize, + all_windows: &std::sync::Weak>>, + text_system: &std::sync::Weak, +) -> Result<()> { + // Here we wait a bit to ensure the system has time to recover from the device lost state. + // If we don't wait, the final drawing result will be blank. + std::thread::sleep(std::time::Duration::from_millis(350)); + + *directx_devices = try_to_recover_from_device_lost(|| { + DirectXDevices::new().context("Failed to recreate new DirectX devices after device lost") + })?; + log::info!("DirectX devices successfully recreated."); + + let lparam = LPARAM(directx_devices as *const _ as _); + unsafe { + SendMessageW( + platform_window, + WM_GPUI_GPU_DEVICE_LOST, + Some(WPARAM(validation_number)), + Some(lparam), + ); + } + + if let Some(text_system) = text_system.upgrade() { + text_system.handle_gpu_lost(&directx_devices)?; + } + if let Some(all_windows) = all_windows.upgrade() { + for window in all_windows.read().iter() { + unsafe { + SendMessageW( + window.as_raw(), + WM_GPUI_GPU_DEVICE_LOST, + Some(WPARAM(validation_number)), + Some(lparam), + ); + } + } + std::thread::sleep(std::time::Duration::from_millis(200)); + for window in all_windows.read().iter() { + unsafe { + SendMessageW( + window.as_raw(), + WM_GPUI_FORCE_UPDATE_WINDOW, + Some(WPARAM(validation_number)), + None, + ); + } + } + } + Ok(()) +} + +const PLATFORM_WINDOW_CLASS_NAME: PCWSTR = w!("Zed::PlatformWindow"); + +fn register_platform_window_class() { + let wc = WNDCLASSW { + lpfnWndProc: Some(window_procedure), + lpszClassName: PCWSTR(PLATFORM_WINDOW_CLASS_NAME.as_ptr()), + ..Default::default() + }; + unsafe { RegisterClassW(&wc) }; +} + +unsafe extern "system" fn window_procedure( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + if msg == WM_NCCREATE { + let params = unsafe { &*(lparam.0 as *const CREATESTRUCTW) }; + let creation_context = params.lpCreateParams as *mut PlatformWindowCreateContext; + let creation_context = unsafe { &mut *creation_context }; + + let Some(main_sender) = creation_context.main_sender.take() else { + creation_context.inner = Some(Err(anyhow!("missing main sender"))); + return LRESULT(0); + }; + creation_context.dispatcher = Some(Arc::new(WindowsDispatcher::new( + main_sender, + hwnd, + creation_context.validation_number, + ))); + + return match WindowsPlatformInner::new(creation_context) { + Ok(inner) => { + let weak = Box::new(Rc::downgrade(&inner)); + unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) }; + creation_context.inner = Some(Ok(inner)); + unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } + } + Err(error) => { + creation_context.inner = Some(Err(error)); + LRESULT(0) + } + }; + } + + let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; + if ptr.is_null() { + return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }; + } + let inner = unsafe { &*ptr }; + let result = if let Some(inner) = inner.upgrade() { + if cfg!(debug_assertions) { + let inner = std::panic::AssertUnwindSafe(inner); + match std::panic::catch_unwind(|| { inner }.handle_msg(hwnd, msg, wparam, lparam)) { + Ok(result) => result, + Err(_) => std::process::abort(), + } + } else { + inner.handle_msg(hwnd, msg, wparam, lparam) + } + } else { + unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } + }; + + if msg == WM_NCDESTROY { + unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) }; + unsafe { drop(Box::from_raw(ptr)) }; + } + + result +} + +/// Copies the username and secret out of a credential returned by `CredReadW`. +/// +/// Both `UserName` and `CredentialBlob` are optional in Credential Manager and +/// come back as null pointers when absent, so they are treated as empty here. +/// +/// # Safety +/// +/// A non-null `UserName` must point to a NUL-terminated wide string and a +/// non-null `CredentialBlob` must be readable for `CredentialBlobSize` bytes, +/// as is the case for credentials returned by `CredReadW`. +unsafe fn username_and_password(credential: &CREDENTIALW) -> Result<(String, Vec)> { + let username = if credential.UserName.is_null() { + String::new() + } else { + // SAFETY: guaranteed by the caller. + unsafe { credential.UserName.to_string()? } + }; + let password = if credential.CredentialBlob.is_null() { + Vec::new() + } else { + // SAFETY: guaranteed by the caller. + unsafe { + std::slice::from_raw_parts( + credential.CredentialBlob, + credential.CredentialBlobSize as usize, + ) + } + .to_vec() + }; + Ok((username, password)) +} + +#[cfg(test)] +mod tests { + use std::ffi::{OsStr, OsString}; + + use crate::{read_from_clipboard, write_to_clipboard}; + use gpui::ClipboardItem; + use windows::Win32::Security::Credentials::{ + CRED_PERSIST_SESSION, CRED_TYPE_GENERIC, CREDENTIALW, CredDeleteW, CredFree, CredReadW, + CredWriteW, + }; + use windows::core::{PCWSTR, PWSTR}; + + use super::{encode_restart_arguments, username_and_password}; + + #[test] + fn test_read_credential_with_username() { + assert_eq!( + round_trip_credential(Some("alice"), b"secret"), + ("alice".to_string(), b"secret".to_vec()) + ); + } + + #[test] + fn test_read_credential_without_username() { + assert_eq!( + round_trip_credential(None, b"secret"), + (String::new(), b"secret".to_vec()) + ); + } + + fn round_trip_credential(username: Option<&str>, secret: &[u8]) -> (String, Vec) { + let mut target_name: Vec = format!( + "zed-test-{}-{}", + std::process::id(), + username.unwrap_or_default() + ) + .encode_utf16() + .chain(Some(0)) + .collect(); + let mut username: Vec = username + .map(|username| username.encode_utf16().chain(Some(0)).collect()) + .unwrap_or_default(); + let mut secret = secret.to_vec(); + let credential = CREDENTIALW { + Type: CRED_TYPE_GENERIC, + TargetName: PWSTR::from_raw(target_name.as_mut_ptr()), + CredentialBlobSize: secret.len() as u32, + CredentialBlob: secret.as_mut_ptr(), + Persist: CRED_PERSIST_SESSION, + UserName: if username.is_empty() { + PWSTR::null() + } else { + PWSTR::from_raw(username.as_mut_ptr()) + }, + ..CREDENTIALW::default() + }; + let target_name = PCWSTR::from_raw(target_name.as_ptr()); + unsafe { CredWriteW(&credential, 0) }.unwrap(); + + let mut credentials: *mut CREDENTIALW = std::ptr::null_mut(); + unsafe { CredReadW(target_name, CRED_TYPE_GENERIC, None, &mut credentials) }.unwrap(); + let result = unsafe { username_and_password(&*credentials) }; + unsafe { CredFree(credentials as *const _ as _) }; + unsafe { CredDeleteW(target_name, CRED_TYPE_GENERIC, None) }.unwrap(); + result.unwrap() + } + + #[test] + fn test_encode_restart_arguments() { + assert_eq!(encode_restart_arguments(&[]), OsStr::new("")); + assert_eq!( + encode_restart_arguments(&[ + OsString::from("--user-data-dir"), + OsString::from(r"C:\Zed Data"), + ]), + OsStr::new(r#""--user-data-dir" "C:\Zed Data""#) + ); + assert_eq!( + encode_restart_arguments(&[OsString::from(r"C:\")]), + OsStr::new(r#""C:\\""#) + ); + } + + #[test] + fn test_clipboard() { + let item = ClipboardItem::new_string("你好,我是张小白".to_string()); + write_to_clipboard(item.clone()); + assert_eq!(read_from_clipboard(), Some(item)); + + let item = ClipboardItem::new_string("12345".to_string()); + write_to_clipboard(item.clone()); + assert_eq!(read_from_clipboard(), Some(item)); + + let item = ClipboardItem::new_string_with_json_metadata("abcdef".to_string(), vec![3, 4]); + write_to_clipboard(item.clone()); + assert_eq!(read_from_clipboard(), Some(item)); + + let item = + ClipboardItem::new_string_with_json_metadata("before\0after".to_string(), vec![12]); + write_to_clipboard(item); + assert_eq!( + read_from_clipboard(), + Some(ClipboardItem::new_string_with_json_metadata( + "before after".to_string(), + vec![12], + )), + ); + } +} diff --git a/vendor/gpui-pre-windows/src/shaders.hlsl b/vendor/gpui-pre-windows/src/shaders.hlsl new file mode 100644 index 0000000..ba89c68 --- /dev/null +++ b/vendor/gpui-pre-windows/src/shaders.hlsl @@ -0,0 +1,1268 @@ +#include "alpha_correction.hlsl" + +cbuffer GlobalParams: register(b0) { + float4 gamma_ratios; + float2 global_viewport_size; + float grayscale_enhanced_contrast; + float subpixel_enhanced_contrast; + uint is_bgr; + uint3 global_pad; +}; + +cbuffer BatchParams: register(b1) { + uint batch_start_index; + uint3 batch_pad; +}; + +Texture2D t_sprite: register(t0); +SamplerState s_sprite: register(s0); + +struct SubpixelSpriteFragmentOutput { + float4 foreground : SV_Target0; + float4 alpha : SV_Target1; +}; + +struct Bounds { + float2 origin; + float2 size; +}; + +struct Corners { + float top_left; + float top_right; + float bottom_right; + float bottom_left; +}; + +struct Edges { + float top; + float right; + float bottom; + float left; +}; + +struct Hsla { + float h; + float s; + float l; + float a; +}; + +struct LinearColorStop { + Hsla color; + float percentage; +}; + +struct Background { + // 0u is Solid + // 1u is LinearGradient + // 2u is PatternSlash + uint tag; + // 0u is sRGB linear color + // 1u is Oklab color + uint color_space; + Hsla solid; + float gradient_angle_or_pattern_height; + LinearColorStop colors[2]; + uint pad; +}; + +struct GradientColor { + float4 solid; + float4 color0; + float4 color1; +}; + +struct AtlasTextureId { + uint index; + uint kind; +}; + +struct AtlasBounds { + int2 origin; + int2 size; +}; + +struct AtlasTile { + AtlasTextureId texture_id; + uint tile_id; + uint padding; + AtlasBounds bounds; +}; + +struct TransformationMatrix { + float2x2 rotation_scale; + float2 translation; +}; + +static const float M_PI_F = 3.141592653f; +static const float3 GRAYSCALE_FACTORS = float3(0.2126f, 0.7152f, 0.0722f); + +float4 to_device_position_impl(float2 position) { + float2 device_position = position / global_viewport_size * float2(2.0, -2.0) + float2(-1.0, 1.0); + return float4(device_position, 0., 1.); +} + +float4 to_device_position(float2 unit_vertex, Bounds bounds) { + float2 position = unit_vertex * bounds.size + bounds.origin; + return to_device_position_impl(position); +} + +float4 distance_from_clip_rect_impl(float2 position, Bounds clip_bounds) { + float2 tl = position - clip_bounds.origin; + float2 br = clip_bounds.origin + clip_bounds.size - position; + return float4(tl.x, br.x, tl.y, br.y); +} + +float4 distance_from_clip_rect(float2 unit_vertex, Bounds bounds, Bounds clip_bounds) { + float2 position = unit_vertex * bounds.size + bounds.origin; + return distance_from_clip_rect_impl(position, clip_bounds); +} + +float4 distance_from_clip_rect_transformed(float2 unit_vertex, Bounds bounds, Bounds clip_bounds, TransformationMatrix transformation) { + float2 position = unit_vertex * bounds.size + bounds.origin; + float2 transformed = mul(position, transformation.rotation_scale) + transformation.translation; + return distance_from_clip_rect_impl(transformed, clip_bounds); +} + +// Convert linear RGB to sRGB +float3 linear_to_srgb(float3 color) { + return pow(color, float3(1.0 / 2.2, 1.0 / 2.2, 1.0 / 2.2)); +} + +// Convert sRGB to linear RGB +float3 srgb_to_linear(float3 color) { + return pow(color, float3(2.2, 2.2, 2.2)); +} + +/// Hsla to linear RGBA conversion. +float4 hsla_to_rgba(Hsla hsla) { + float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range + float s = hsla.s; + float l = hsla.l; + float a = hsla.a; + + float c = (1.0 - abs(2.0 * l - 1.0)) * s; + float x = c * (1.0 - abs(fmod(h, 2.0) - 1.0)); + float m = l - c / 2.0; + + float r = 0.0; + float g = 0.0; + float b = 0.0; + + if (h >= 0.0 && h < 1.0) { + r = c; + g = x; + b = 0.0; + } else if (h >= 1.0 && h < 2.0) { + r = x; + g = c; + b = 0.0; + } else if (h >= 2.0 && h < 3.0) { + r = 0.0; + g = c; + b = x; + } else if (h >= 3.0 && h < 4.0) { + r = 0.0; + g = x; + b = c; + } else if (h >= 4.0 && h < 5.0) { + r = x; + g = 0.0; + b = c; + } else { + r = c; + g = 0.0; + b = x; + } + + float4 rgba; + rgba.x = (r + m); + rgba.y = (g + m); + rgba.z = (b + m); + rgba.w = a; + return rgba; +} + +// Converts a sRGB color to the Oklab color space. +// Reference: https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab +float4 srgb_to_oklab(float4 color) { + // Convert non-linear sRGB to linear sRGB + color = float4(srgb_to_linear(color.rgb), color.a); + + float l = 0.4122214708 * color.r + 0.5363325363 * color.g + 0.0514459929 * color.b; + float m = 0.2119034982 * color.r + 0.6806995451 * color.g + 0.1073969566 * color.b; + float s = 0.0883024619 * color.r + 0.2817188376 * color.g + 0.6299787005 * color.b; + + float l_ = pow(l, 1.0/3.0); + float m_ = pow(m, 1.0/3.0); + float s_ = pow(s, 1.0/3.0); + + return float4( + 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_, + 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_, + 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_, + color.a + ); +} + +// Converts an Oklab color to the sRGB color space. +float4 oklab_to_srgb(float4 color) { + float l_ = color.r + 0.3963377774 * color.g + 0.2158037573 * color.b; + float m_ = color.r - 0.1055613458 * color.g - 0.0638541728 * color.b; + float s_ = color.r - 0.0894841775 * color.g - 1.2914855480 * color.b; + + float l = l_ * l_ * l_; + float m = m_ * m_ * m_; + float s = s_ * s_ * s_; + + float3 linear_rgb = float3( + 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, + -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, + -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s + ); + + // Convert linear sRGB to non-linear sRGB + return float4(linear_to_srgb(linear_rgb), color.a); +} + +// This approximates the error function, needed for the gaussian integral +float2 erf(float2 x) { + float2 s = sign(x); + float2 a = abs(x); + x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a; + x *= x; + return s - s / (x * x); +} + +float blur_along_x(float x, float y, float sigma, float corner, float2 half_size) { + float delta = min(half_size.y - corner - abs(y), 0.); + float curved = half_size.x - corner + sqrt(max(0., corner * corner - delta * delta)); + float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma)); + return integral.y - integral.x; +} + +// A standard gaussian function, used for weighting samples +float gaussian(float x, float sigma) { + return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma); +} + +float4 over(float4 below, float4 above) { + float4 result; + float alpha = above.a + below.a * (1.0 - above.a); + result.rgb = (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha; + result.a = alpha; + return result; +} + +float2 to_tile_position(float2 unit_vertex, AtlasTile tile) { + float2 atlas_size; + t_sprite.GetDimensions(atlas_size.x, atlas_size.y); + return (float2(tile.bounds.origin) + unit_vertex * float2(tile.bounds.size)) / atlas_size; +} + +// Selects corner radius based on quadrant. +float pick_corner_radius(float2 center_to_point, Corners corner_radii) { + if (center_to_point.x < 0.) { + if (center_to_point.y < 0.) { + return corner_radii.top_left; + } else { + return corner_radii.bottom_left; + } + } else { + if (center_to_point.y < 0.) { + return corner_radii.top_right; + } else { + return corner_radii.bottom_right; + } + } +} + +float4 to_device_position_transformed(float2 unit_vertex, Bounds bounds, + TransformationMatrix transformation) { + float2 position = unit_vertex * bounds.size + bounds.origin; + float2 transformed = mul(position, transformation.rotation_scale) + transformation.translation; + float2 device_position = transformed / global_viewport_size * float2(2.0, -2.0) + float2(-1.0, 1.0); + return float4(device_position, 0.0, 1.0); +} + +// Implementation of quad signed distance field +float quad_sdf_impl(float2 corner_center_to_point, float corner_radius) { + if (corner_radius == 0.0) { + // Fast path for unrounded corners + return max(corner_center_to_point.x, corner_center_to_point.y); + } else { + // Signed distance of the point from a quad that is inset by corner_radius + // It is negative inside this quad, and positive outside + float signed_distance_to_inset_quad = + // 0 inside the inset quad, and positive outside + length(max(float2(0.0, 0.0), corner_center_to_point)) + + // 0 outside the inset quad, and negative inside + min(0.0, max(corner_center_to_point.x, corner_center_to_point.y)); + + return signed_distance_to_inset_quad - corner_radius; + } +} + +float quad_sdf(float2 pt, Bounds bounds, Corners corner_radii) { + float2 half_size = bounds.size / 2.; + float2 center = bounds.origin + half_size; + float2 center_to_point = pt - center; + float corner_radius = pick_corner_radius(center_to_point, corner_radii); + float2 corner_to_point = abs(center_to_point) - half_size; + float2 corner_center_to_point = corner_to_point + corner_radius; + return quad_sdf_impl(corner_center_to_point, corner_radius); +} + +GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid, LinearColorStop colors[2]) { + GradientColor output; + if (tag == 0 || tag == 2 || tag == 3) { + output.solid = hsla_to_rgba(solid); + } else if (tag == 1) { + output.color0 = hsla_to_rgba(colors[0].color); + output.color1 = hsla_to_rgba(colors[1].color); + + // Prepare color space in vertex for avoid conversion + // in fragment shader for performance reasons + if (color_space == 1) { + // Oklab + output.color0 = srgb_to_oklab(output.color0); + output.color1 = srgb_to_oklab(output.color1); + } + } + + return output; +} + +float2x2 rotate2d(float angle) { + float s = sin(angle); + float c = cos(angle); + return float2x2(c, -s, s, c); +} + +float4 gradient_color(Background background, + float2 position, + Bounds bounds, + float4 solid_color, float4 color0, float4 color1) { + float4 color; + + switch (background.tag) { + case 0: + color = solid_color; + break; + case 1: { + // -90 degrees to match the CSS gradient angle. + float gradient_angle = background.gradient_angle_or_pattern_height; + float radians = (fmod(gradient_angle, 360.0) - 90.0) * (M_PI_F / 180.0); + float2 direction = float2(cos(radians), sin(radians)); + + // Expand the short side to be the same as the long side + if (bounds.size.x > bounds.size.y) { + direction.y *= bounds.size.y / bounds.size.x; + } else { + direction.x *= bounds.size.x / bounds.size.y; + } + + // Get the t value for the linear gradient with the color stop percentages. + float2 half_size = bounds.size * 0.5; + float2 center = bounds.origin + half_size; + float2 center_to_point = position - center; + float t = dot(center_to_point, direction) / length(direction); + // Check the direct to determine the use x or y + if (abs(direction.x) > abs(direction.y)) { + t = (t + half_size.x) / bounds.size.x; + } else { + t = (t + half_size.y) / bounds.size.y; + } + + // Adjust t based on the stop percentages + t = (t - background.colors[0].percentage) + / (background.colors[1].percentage + - background.colors[0].percentage); + t = clamp(t, 0.0, 1.0); + + switch (background.color_space) { + case 0: + color = lerp(color0, color1, t); + break; + case 1: { + float4 oklab_color = lerp(color0, color1, t); + color = oklab_to_srgb(oklab_color); + break; + } + } + + // Dither to reduce banding in gradients (especially dark/alpha). + // Triangular-distributed noise breaks up 8-bit quantization steps. + // ±2/255 for RGB (enough for dark-on-dark compositing), + // ±3/255 for alpha (needs more because alpha × dark color = tiny steps). + { + float2 seed = position * 0.6180339887; // golden ratio spread + float r1 = frac(sin(dot(seed, float2(12.9898, 78.233))) * 43758.5453); + float r2 = frac(sin(dot(seed, float2(39.3460, 11.135))) * 24634.6345); + float tri = r1 + r2 - 1.0; // triangular PDF, range [-1, +1] + color.rgb += tri * 2.0 / 255.0; + color.a += tri * 3.0 / 255.0; + } + + break; + } + case 2: { + float gradient_angle_or_pattern_height = background.gradient_angle_or_pattern_height; + float pattern_width = (gradient_angle_or_pattern_height / 65535.0f) / 255.0f; + float pattern_interval = fmod(gradient_angle_or_pattern_height, 65535.0f) / 255.0f; + float pattern_height = pattern_width + pattern_interval; + float stripe_angle = M_PI_F / 4.0; + float pattern_period = pattern_height * sin(stripe_angle); + float2x2 rotation = rotate2d(stripe_angle); + float2 relative_position = position - bounds.origin; + float2 rotated_point = mul(relative_position, rotation); + float pattern = fmod(rotated_point.x, pattern_period); + float distance = min(pattern, pattern_period - pattern) - pattern_period * (pattern_width / pattern_height) / 2.0f; + color = solid_color; + color.a *= saturate(0.5 - distance); + break; + } + case 3: { + // checkerboard + float size = background.gradient_angle_or_pattern_height; + float2 relative_position = position - bounds.origin; + + float x_index = floor(relative_position.x / size); + float y_index = floor(relative_position.y / size); + float should_be_colored = (x_index + y_index) % 2.0; + + color = solid_color; + color.a *= saturate(should_be_colored); + break; + } + } + + return color; +} + +// Returns the dash velocity of a corner given the dash velocity of the two +// sides, by returning the slower velocity (larger dashes). +// +// Since 0 is used for dash velocity when the border width is 0 (instead of +// +inf), this returns the other dash velocity in that case. +// +// An alternative to this might be to appropriately interpolate the dash +// velocity around the corner, but that seems overcomplicated. +float corner_dash_velocity(float dv1, float dv2) { + if (dv1 == 0.0) { + return dv2; + } else if (dv2 == 0.0) { + return dv1; + } else { + return min(dv1, dv2); + } +} + +// Returns alpha used to render antialiased dashes. +// `t` is within the dash when `fmod(t, period) < length`. +float dash_alpha( + float t, float period, float length, float dash_velocity, + float antialias_threshold +) { + float half_period = period / 2.0; + float half_length = length / 2.0; + // Value in [-half_period, half_period] + // The dash is in [-half_length, half_length] + float centered = fmod(t + half_period - half_length, period) - half_period; + // Signed distance for the dash, negative values are inside the dash + float signed_distance = abs(centered) - half_length; + // Antialiased alpha based on the signed distance + return saturate(antialias_threshold - signed_distance / dash_velocity); +} + +// This approximates distance to the nearest point to a quarter ellipse in a way +// that is sufficient for anti-aliasing when the ellipse is not very eccentric. +// The components of `point` are expected to be positive. +// +// Negative on the outside and positive on the inside. +float quarter_ellipse_sdf(float2 pt, float2 radii) { + // Scale the space to treat the ellipse like a unit circle + float2 circle_vec = pt / radii; + float unit_circle_sdf = length(circle_vec) - 1.0; + // Approximate up-scaling of the length by using the average of the radii. + // + // TODO: A better solution would be to use the gradient of the implicit + // function for an ellipse to approximate a scaling factor. + return unit_circle_sdf * (radii.x + radii.y) * -0.5; +} + +/* +** +** Quads +** +*/ + +struct Quad { + uint order; + uint border_style; + Bounds bounds; + Bounds content_mask; + Background background; + Hsla border_color; + Corners corner_radii; + Edges border_widths; +}; + +struct QuadVertexOutput { + nointerpolation uint quad_id: TEXCOORD0; + float4 position: SV_Position; + nointerpolation float4 border_color: COLOR0; + nointerpolation float4 background_solid: COLOR1; + nointerpolation float4 background_color0: COLOR2; + nointerpolation float4 background_color1: COLOR3; + float4 clip_distance: SV_ClipDistance; +}; + +struct QuadFragmentInput { + nointerpolation uint quad_id: TEXCOORD0; + float4 position: SV_Position; + nointerpolation float4 border_color: COLOR0; + nointerpolation float4 background_solid: COLOR1; + nointerpolation float4 background_color0: COLOR2; + nointerpolation float4 background_color1: COLOR3; +}; + +StructuredBuffer quads: register(t1); + +QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) { + float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u)); + uint quad_id = batch_start_index + instance_id; + Quad quad = quads[quad_id]; + float4 device_position = to_device_position(unit_vertex, quad.bounds); + + GradientColor gradient = prepare_gradient_color( + quad.background.tag, + quad.background.color_space, + quad.background.solid, + quad.background.colors + ); + float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask); + float4 border_color = hsla_to_rgba(quad.border_color); + + QuadVertexOutput output; + output.position = device_position; + output.border_color = border_color; + output.quad_id = quad_id; + output.background_solid = gradient.solid; + output.background_color0 = gradient.color0; + output.background_color1 = gradient.color1; + output.clip_distance = clip_distance; + return output; +} + +float4 quad_fragment(QuadFragmentInput input): SV_Target { + Quad quad = quads[input.quad_id]; + float4 background_color = gradient_color(quad.background, input.position.xy, quad.bounds, + input.background_solid, input.background_color0, input.background_color1); + + bool unrounded = quad.corner_radii.top_left == 0.0 && + quad.corner_radii.top_right == 0.0 && + quad.corner_radii.bottom_left == 0.0 && + quad.corner_radii.bottom_right == 0.0; + + // Fast path when the quad is not rounded and doesn't have any border + if (quad.border_widths.top == 0.0 && + quad.border_widths.left == 0.0 && + quad.border_widths.right == 0.0 && + quad.border_widths.bottom == 0.0 && + unrounded) { + return background_color; + } + + float2 size = quad.bounds.size; + float2 half_size = size / 2.; + float2 the_point = input.position.xy - quad.bounds.origin; + float2 center_to_point = the_point - half_size; + + // Signed distance field threshold for inclusion of pixels. 0.5 is the + // minimum distance between the center of the pixel and the edge. + const float antialias_threshold = 0.5; + + // Radius of the nearest corner + float corner_radius = pick_corner_radius(center_to_point, quad.corner_radii); + + float2 border = float2( + center_to_point.x < 0.0 ? quad.border_widths.left : quad.border_widths.right, + center_to_point.y < 0.0 ? quad.border_widths.top : quad.border_widths.bottom + ); + + // 0-width borders are reduced so that `inner_sdf >= antialias_threshold`. + // The purpose of this is to not draw antialiasing pixels in this case. + float2 reduced_border = float2( + border.x == 0.0 ? -antialias_threshold : border.x, + border.y == 0.0 ? -antialias_threshold : border.y + ); + + // Vector from the corner of the quad bounds to the point, after mirroring + // the point into the bottom right quadrant. Both components are <= 0. + float2 corner_to_point = abs(center_to_point) - half_size; + + // Vector from the point to the center of the rounded corner's circle, also + // mirrored into bottom right quadrant. + float2 corner_center_to_point = corner_to_point + corner_radius; + + // Whether the nearest point on the border is rounded + bool is_near_rounded_corner = + corner_center_to_point.x >= 0.0 && + corner_center_to_point.y >= 0.0; + + // Vector from straight border inner corner to point. + // + // 0-width borders are turned into width -1 so that inner_sdf is > 1.0 near + // the border. Without this, antialiasing pixels would be drawn. + float2 straight_border_inner_corner_to_point = corner_to_point + reduced_border; + + // Whether the point is beyond the inner edge of the straight border + bool is_beyond_inner_straight_border = + straight_border_inner_corner_to_point.x > 0.0 || + straight_border_inner_corner_to_point.y > 0.0; + + // Whether the point is far enough inside the quad, such that the pixels are + // not affected by the straight border. + bool is_within_inner_straight_border = + straight_border_inner_corner_to_point.x < -antialias_threshold && + straight_border_inner_corner_to_point.y < -antialias_threshold; + + // Fast path for points that must be part of the background + if (is_within_inner_straight_border && !is_near_rounded_corner) { + return background_color; + } + + // Signed distance of the point to the outside edge of the quad's border + float outer_sdf = quad_sdf_impl(corner_center_to_point, corner_radius); + + // Approximate signed distance of the point to the inside edge of the quad's + // border. It is negative outside this edge (within the border), and + // positive inside. + // + // This is not always an accurate signed distance: + // * The rounded portions with varying border width use an approximation of + // nearest-point-on-ellipse. + // * When it is quickly known to be outside the edge, -1.0 is used. + float inner_sdf = 0.0; + if (corner_center_to_point.x <= 0.0 || corner_center_to_point.y <= 0.0) { + // Fast paths for straight borders + inner_sdf = -max(straight_border_inner_corner_to_point.x, + straight_border_inner_corner_to_point.y); + } else if (is_beyond_inner_straight_border) { + // Fast path for points that must be outside the inner edge + inner_sdf = -1.0; + } else if (reduced_border.x == reduced_border.y) { + // Fast path for circular inner edge. + inner_sdf = -(outer_sdf + reduced_border.x); + } else { + float2 ellipse_radii = max(float2(0.0, 0.0), float2(corner_radius, corner_radius) - reduced_border); + inner_sdf = quarter_ellipse_sdf(corner_center_to_point, ellipse_radii); + } + + // Negative when inside the border + float border_sdf = max(inner_sdf, outer_sdf); + + float4 color = background_color; + if (border_sdf < antialias_threshold) { + float4 border_color = input.border_color; + // Dashed border logic when border_style == 1 + if (quad.border_style == 1) { + // Position along the perimeter in "dash space", where each dash + // period has length 1 + float t = 0.0; + + // Total number of dash periods, so that the dash spacing can be + // adjusted to evenly divide it + float max_t = 0.0; + + // Border width is proportional to dash size. This is the behavior + // used by browsers, but also avoids dashes from different segments + // overlapping when dash size is smaller than the border width. + // + // Dash pattern: (2 * border width) dash, (1 * border width) gap + const float dash_length_per_width = 2.0; + const float dash_gap_per_width = 1.0; + const float dash_period_per_width = dash_length_per_width + dash_gap_per_width; + + // Since the dash size is determined by border width, the density of + // dashes varies. Multiplying a pixel distance by this returns a + // position in dash space - it has units (dash period / pixels). So + // a dash velocity of (1 / 10) is 1 dash every 10 pixels. + float dash_velocity = 0.0; + + // Dividing this by the border width gives the dash velocity + const float dv_numerator = 1.0 / dash_period_per_width; + + if (unrounded) { + // When corners aren't rounded, the dashes are separately laid + // out on each straight line, rather than around the whole + // perimeter. This way each line starts and ends with a dash. + bool is_horizontal = corner_center_to_point.x < corner_center_to_point.y; + // Choosing the right border width for dashed borders. + // TODO: A better solution exists taking a look at the whole file. + // this does not fix single dashed borders at the corners + float2 dashed_border = float2( + max(quad.border_widths.bottom, quad.border_widths.top), + max(quad.border_widths.right, quad.border_widths.left) + ); + float border_width = is_horizontal ? dashed_border.x : dashed_border.y; + dash_velocity = dv_numerator / border_width; + t = is_horizontal ? the_point.x : the_point.y; + t *= dash_velocity; + max_t = is_horizontal ? size.x : size.y; + max_t *= dash_velocity; + } else { + // When corners are rounded, the dashes are laid out clockwise + // around the whole perimeter. + + float r_tr = quad.corner_radii.top_right; + float r_br = quad.corner_radii.bottom_right; + float r_bl = quad.corner_radii.bottom_left; + float r_tl = quad.corner_radii.top_left; + + float w_t = quad.border_widths.top; + float w_r = quad.border_widths.right; + float w_b = quad.border_widths.bottom; + float w_l = quad.border_widths.left; + + // Straight side dash velocities + float dv_t = w_t <= 0.0 ? 0.0 : dv_numerator / w_t; + float dv_r = w_r <= 0.0 ? 0.0 : dv_numerator / w_r; + float dv_b = w_b <= 0.0 ? 0.0 : dv_numerator / w_b; + float dv_l = w_l <= 0.0 ? 0.0 : dv_numerator / w_l; + + // Straight side lengths in dash space + float s_t = (size.x - r_tl - r_tr) * dv_t; + float s_r = (size.y - r_tr - r_br) * dv_r; + float s_b = (size.x - r_br - r_bl) * dv_b; + float s_l = (size.y - r_bl - r_tl) * dv_l; + + float corner_dash_velocity_tr = corner_dash_velocity(dv_t, dv_r); + float corner_dash_velocity_br = corner_dash_velocity(dv_b, dv_r); + float corner_dash_velocity_bl = corner_dash_velocity(dv_b, dv_l); + float corner_dash_velocity_tl = corner_dash_velocity(dv_t, dv_l); + + // Corner lengths in dash space + float c_tr = r_tr * (M_PI_F / 2.0) * corner_dash_velocity_tr; + float c_br = r_br * (M_PI_F / 2.0) * corner_dash_velocity_br; + float c_bl = r_bl * (M_PI_F / 2.0) * corner_dash_velocity_bl; + float c_tl = r_tl * (M_PI_F / 2.0) * corner_dash_velocity_tl; + + // Cumulative dash space upto each segment + float upto_tr = s_t; + float upto_r = upto_tr + c_tr; + float upto_br = upto_r + s_r; + float upto_b = upto_br + c_br; + float upto_bl = upto_b + s_b; + float upto_l = upto_bl + c_bl; + float upto_tl = upto_l + s_l; + max_t = upto_tl + c_tl; + + if (is_near_rounded_corner) { + float radians = atan2(corner_center_to_point.y, corner_center_to_point.x); + float corner_t = radians * corner_radius; + + if (center_to_point.x >= 0.0) { + if (center_to_point.y < 0.0) { + dash_velocity = corner_dash_velocity_tr; + // Subtracted because radians is pi/2 to 0 when + // going clockwise around the top right corner, + // since the y axis has been flipped + t = upto_r - corner_t * dash_velocity; + } else { + dash_velocity = corner_dash_velocity_br; + // Added because radians is 0 to pi/2 when going + // clockwise around the bottom-right corner + t = upto_br + corner_t * dash_velocity; + } + } else { + if (center_to_point.y >= 0.0) { + dash_velocity = corner_dash_velocity_bl; + // Subtracted because radians is pi/1 to 0 when + // going clockwise around the bottom-left corner, + // since the x axis has been flipped + t = upto_l - corner_t * dash_velocity; + } else { + dash_velocity = corner_dash_velocity_tl; + // Added because radians is 0 to pi/2 when going + // clockwise around the top-left corner, since both + // axis were flipped + t = upto_tl + corner_t * dash_velocity; + } + } + } else { + // Straight borders + bool is_horizontal = corner_center_to_point.x < corner_center_to_point.y; + if (is_horizontal) { + if (center_to_point.y < 0.0) { + dash_velocity = dv_t; + t = (the_point.x - r_tl) * dash_velocity; + } else { + dash_velocity = dv_b; + t = upto_bl - (the_point.x - r_bl) * dash_velocity; + } + } else { + if (center_to_point.x < 0.0) { + dash_velocity = dv_l; + t = upto_tl - (the_point.y - r_tl) * dash_velocity; + } else { + dash_velocity = dv_r; + t = upto_r + (the_point.y - r_tr) * dash_velocity; + } + } + } + } + float dash_length = dash_length_per_width / dash_period_per_width; + float desired_dash_gap = dash_gap_per_width / dash_period_per_width; + + // Straight borders should start and end with a dash, so max_t is + // reduced to cause this. + max_t -= unrounded ? dash_length : 0.0; + if (max_t >= 1.0) { + // Adjust dash gap to evenly divide max_t + float dash_count = floor(max_t); + float dash_period = max_t / dash_count; + border_color.a *= dash_alpha(t, dash_period, dash_length, dash_velocity, antialias_threshold); + } else if (unrounded) { + // When there isn't enough space for the full gap between the + // two start / end dashes of a straight border, reduce gap to + // make them fit. + float dash_gap = max_t - dash_length; + if (dash_gap > 0.0) { + float dash_period = dash_length + dash_gap; + border_color.a *= dash_alpha(t, dash_period, dash_length, dash_velocity, antialias_threshold); + } + } + } + + // Blend the border on top of the background and then linearly interpolate + // between the two as we slide inside the background. + float4 blended_border = over(background_color, border_color); + color = lerp(background_color, blended_border, + saturate(antialias_threshold - inner_sdf)); + } + + return color * float4(1.0, 1.0, 1.0, saturate(antialias_threshold - outer_sdf)); +} + +/* +** +** Shadows +** +*/ + +struct Shadow { + uint order; + float blur_radius; + Bounds bounds; + Corners corner_radii; + Bounds content_mask; + Hsla color; + Bounds element_bounds; + Corners element_corner_radii; + uint inset; + uint pad; // align to 8 bytes +}; + +struct ShadowVertexOutput { + nointerpolation uint shadow_id: TEXCOORD0; + float4 position: SV_Position; + nointerpolation float4 color: COLOR; + float4 clip_distance: SV_ClipDistance; +}; + +struct ShadowFragmentInput { + nointerpolation uint shadow_id: TEXCOORD0; + float4 position: SV_Position; + nointerpolation float4 color: COLOR; +}; + +StructuredBuffer shadows: register(t1); + +ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) { + float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u)); + uint shadow_id = batch_start_index + instance_id; + Shadow shadow = shadows[shadow_id]; + + Bounds bounds; + if (shadow.inset != 0u) { + bounds = shadow.element_bounds; + } else { + // Leave room for the gaussian tail outside the shadow rect. + float margin = 3.0 * shadow.blur_radius; + bounds = shadow.bounds; + bounds.origin -= margin; + bounds.size += 2.0 * margin; + } + + float4 device_position = to_device_position(unit_vertex, bounds); + float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask); + float4 color = hsla_to_rgba(shadow.color); + + ShadowVertexOutput output; + output.position = device_position; + output.color = color; + output.shadow_id = shadow_id; + output.clip_distance = clip_distance; + + return output; +} + +float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET { + Shadow shadow = shadows[input.shadow_id]; + + float2 half_size = shadow.bounds.size / 2.; + float2 center = shadow.bounds.origin + half_size; + float2 point0 = input.position.xy - center; + float corner_radius = pick_corner_radius(point0, shadow.corner_radii); + + float alpha; + if (shadow.blur_radius == 0.) { + float distance = quad_sdf(input.position.xy, shadow.bounds, shadow.corner_radii); + alpha = saturate(0.5 - distance); + } else { + // The signal is only non-zero in a limited range, so don't waste samples + float low = point0.y - half_size.y; + float high = point0.y + half_size.y; + float start = clamp(-3. * shadow.blur_radius, low, high); + float end = clamp(3. * shadow.blur_radius, low, high); + + // Accumulate samples (we can get away with surprisingly few samples) + float step = (end - start) / 4.; + float y = start + step * 0.5; + alpha = 0.; + for (int i = 0; i < 4; i++) { + alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius, + corner_radius, half_size) * + gaussian(y, shadow.blur_radius) * step; + y += step; + } + } + + if (shadow.inset != 0u) { + // The inset shadow is the complement of the (blurred) hole rect, clipped to the element. + // `saturate(0.5 - d)` gives a 1-pixel antialiased edge: d <= -0.5 -> 1, d >= 0.5 -> 0. + alpha = 1.0 - alpha; + float element_distance = quad_sdf(input.position.xy, shadow.element_bounds, + shadow.element_corner_radii); + alpha *= saturate(0.5 - element_distance); + } + + return input.color * float4(1., 1., 1., alpha); +} + +/* +** +** Path Rasterization +** +*/ + +struct PathRasterizationSprite { + float2 xy_position; + float2 st_position; + Background color; + Bounds bounds; +}; + +StructuredBuffer path_rasterization_sprites: register(t1); + +struct PathVertexOutput { + float4 position: SV_Position; + float2 st_position: TEXCOORD0; + nointerpolation uint vertex_id: TEXCOORD1; + float4 clip_distance: SV_ClipDistance; +}; + +struct PathFragmentInput { + float4 position: SV_Position; + float2 st_position: TEXCOORD0; + nointerpolation uint vertex_id: TEXCOORD1; +}; + +PathVertexOutput path_rasterization_vertex(uint vertex_id: SV_VertexID) { + PathRasterizationSprite sprite = path_rasterization_sprites[vertex_id]; + + PathVertexOutput output; + output.position = to_device_position_impl(sprite.xy_position); + output.st_position = sprite.st_position; + output.vertex_id = vertex_id; + output.clip_distance = distance_from_clip_rect_impl(sprite.xy_position, sprite.bounds); + + return output; +} + +float4 path_rasterization_fragment(PathFragmentInput input): SV_Target { + float2 dx = ddx(input.st_position); + float2 dy = ddy(input.st_position); + PathRasterizationSprite sprite = path_rasterization_sprites[input.vertex_id]; + + Background background = sprite.color; + Bounds bounds = sprite.bounds; + + float alpha; + if (length(float2(dx.x, dy.x))) { + alpha = 1.0; + } else { + float2 gradient = 2.0 * input.st_position.xx * float2(dx.x, dy.x) - float2(dx.y, dy.y); + float f = input.st_position.x * input.st_position.x - input.st_position.y; + float distance = f / length(gradient); + alpha = saturate(0.5 - distance); + } + + GradientColor gradient = prepare_gradient_color( + background.tag, background.color_space, background.solid, background.colors); + + float4 color = gradient_color(background, input.position.xy, bounds, + gradient.solid, gradient.color0, gradient.color1); + return float4(color.rgb * color.a * alpha, alpha * color.a); +} + +/* +** +** Path Sprites +** +*/ + +struct PathSprite { + Bounds bounds; +}; + +struct PathSpriteVertexOutput { + float4 position: SV_Position; + float2 texture_coords: TEXCOORD0; +}; + +StructuredBuffer path_sprites: register(t1); + +PathSpriteVertexOutput path_sprite_vertex(uint vertex_id: SV_VertexID, uint sprite_id: SV_InstanceID) { + float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u)); + PathSprite sprite = path_sprites[sprite_id]; + + // Don't apply content mask because it was already accounted for when rasterizing the path + float4 device_position = to_device_position(unit_vertex, sprite.bounds); + + float2 screen_position = sprite.bounds.origin + unit_vertex * sprite.bounds.size; + float2 texture_coords = screen_position / global_viewport_size; + + PathSpriteVertexOutput output; + output.position = device_position; + output.texture_coords = texture_coords; + return output; +} + +float4 path_sprite_fragment(PathSpriteVertexOutput input): SV_Target { + return t_sprite.Sample(s_sprite, input.texture_coords); +} + +/* +** +** Underlines +** +*/ + +struct Underline { + uint order; + uint pad; + Bounds bounds; + Bounds content_mask; + Hsla color; + float thickness; + uint wavy; +}; + +struct UnderlineVertexOutput { + nointerpolation uint underline_id: TEXCOORD0; + float4 position: SV_Position; + nointerpolation float4 color: COLOR; + float4 clip_distance: SV_ClipDistance; +}; + +struct UnderlineFragmentInput { + nointerpolation uint underline_id: TEXCOORD0; + float4 position: SV_Position; + nointerpolation float4 color: COLOR; +}; + +StructuredBuffer underlines: register(t1); + +UnderlineVertexOutput underline_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) { + float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u)); + uint underline_id = batch_start_index + instance_id; + Underline underline = underlines[underline_id]; + float4 device_position = to_device_position(unit_vertex, underline.bounds); + float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds, + underline.content_mask); + float4 color = hsla_to_rgba(underline.color); + + UnderlineVertexOutput output; + output.position = device_position; + output.color = color; + output.underline_id = underline_id; + output.clip_distance = clip_distance; + return output; +} + +float4 underline_fragment(UnderlineFragmentInput input): SV_Target { + const float WAVE_FREQUENCY = 2.0; + const float WAVE_HEIGHT_RATIO = 0.8; + + Underline underline = underlines[input.underline_id]; + if (underline.wavy) { + float half_thickness = underline.thickness * 0.5; + float2 origin = underline.bounds.origin; + + float2 st = ((input.position.xy - origin) / underline.bounds.size.y) - float2(0., 0.5); + float frequency = (M_PI_F * WAVE_FREQUENCY * underline.thickness) / underline.bounds.size.y; + float amplitude = (underline.thickness * WAVE_HEIGHT_RATIO) / underline.bounds.size.y; + + float sine = sin(st.x * frequency) * amplitude; + float dSine = cos(st.x * frequency) * amplitude * frequency; + float distance = (st.y - sine) / sqrt(1. + dSine * dSine); + float distance_in_pixels = distance * underline.bounds.size.y; + float distance_from_top_border = distance_in_pixels - half_thickness; + float distance_from_bottom_border = distance_in_pixels + half_thickness; + float alpha = saturate( + 0.5 - max(-distance_from_bottom_border, distance_from_top_border)); + return input.color * float4(1., 1., 1., alpha); + } else { + return input.color; + } +} + +/* +** +** Monochrome sprites +** +*/ + +struct MonochromeSprite { + uint order; + uint pad; + Bounds bounds; + Bounds content_mask; + Hsla color; + AtlasTile tile; + TransformationMatrix transformation; +}; + +struct MonochromeSpriteVertexOutput { + float4 position: SV_Position; + float2 tile_position: POSITION; + nointerpolation float4 color: COLOR; + float4 clip_distance: SV_ClipDistance; +}; + +struct MonochromeSpriteFragmentInput { + float4 position: SV_Position; + float2 tile_position: POSITION; + nointerpolation float4 color: COLOR; + float4 clip_distance: SV_ClipDistance; +}; + +StructuredBuffer mono_sprites: register(t1); + +MonochromeSpriteVertexOutput monochrome_sprite_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) { + float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u)); + uint sprite_id = batch_start_index + instance_id; + MonochromeSprite sprite = mono_sprites[sprite_id]; + float4 device_position = + to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation); + float4 clip_distance = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation); + float2 tile_position = to_tile_position(unit_vertex, sprite.tile); + float4 color = hsla_to_rgba(sprite.color); + + MonochromeSpriteVertexOutput output; + output.position = device_position; + output.tile_position = tile_position; + output.color = color; + output.clip_distance = clip_distance; + return output; +} + +float4 monochrome_sprite_fragment(MonochromeSpriteFragmentInput input): SV_Target { + float sample = t_sprite.Sample(s_sprite, input.tile_position).r; + float alpha_corrected = apply_contrast_and_gamma_correction(sample, input.color.rgb, grayscale_enhanced_contrast, gamma_ratios); + return float4(input.color.rgb, input.color.a * alpha_corrected); +} + +MonochromeSpriteVertexOutput subpixel_sprite_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) { + return monochrome_sprite_vertex(vertex_id, instance_id); +} + +SubpixelSpriteFragmentOutput subpixel_sprite_fragment(MonochromeSpriteFragmentInput input) { + float3 sample = t_sprite.Sample(s_sprite, input.tile_position).rgb; + if (is_bgr) { + sample = sample.bgr; + } + float3 alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, subpixel_enhanced_contrast, gamma_ratios); + + SubpixelSpriteFragmentOutput output; + output.foreground = float4(input.color.rgb, 1.0f); + output.alpha = float4(input.color.a * alpha_corrected, 1.0f); + return output; +} + +/* +** +** Polychrome sprites +** +*/ + +struct PolychromeSprite { + uint order; + uint pad; + uint grayscale; + float opacity; + Bounds bounds; + Bounds content_mask; + Corners corner_radii; + AtlasTile tile; +}; + +struct PolychromeSpriteVertexOutput { + nointerpolation uint sprite_id: TEXCOORD0; + float4 position: SV_Position; + float2 tile_position: POSITION; + float4 clip_distance: SV_ClipDistance; +}; + +struct PolychromeSpriteFragmentInput { + nointerpolation uint sprite_id: TEXCOORD0; + float4 position: SV_Position; + float2 tile_position: POSITION; +}; + +StructuredBuffer poly_sprites: register(t1); + +PolychromeSpriteVertexOutput polychrome_sprite_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) { + float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u)); + uint sprite_id = batch_start_index + instance_id; + PolychromeSprite sprite = poly_sprites[sprite_id]; + float4 device_position = to_device_position(unit_vertex, sprite.bounds); + float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds, + sprite.content_mask); + float2 tile_position = to_tile_position(unit_vertex, sprite.tile); + + PolychromeSpriteVertexOutput output; + output.position = device_position; + output.tile_position = tile_position; + output.sprite_id = sprite_id; + output.clip_distance = clip_distance; + return output; +} + +float4 polychrome_sprite_fragment(PolychromeSpriteFragmentInput input): SV_Target { + PolychromeSprite sprite = poly_sprites[input.sprite_id]; + float4 sample = t_sprite.Sample(s_sprite, input.tile_position); + float distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); + + float4 color = sample; + if (sprite.grayscale != 0u) { + float3 grayscale = dot(color.rgb, GRAYSCALE_FACTORS); + color = float4(grayscale, sample.a); + } + color.a *= sprite.opacity * saturate(0.5 - distance); + return color; +} diff --git a/vendor/gpui-pre-windows/src/system_notifications.rs b/vendor/gpui-pre-windows/src/system_notifications.rs new file mode 100644 index 0000000..b0075f0 --- /dev/null +++ b/vendor/gpui-pre-windows/src/system_notifications.rs @@ -0,0 +1,198 @@ +//! System notifications as Windows toast notifications. + +use std::cell::RefCell; +use std::collections::{HashMap, hash_map::DefaultHasher}; +use std::hash::{Hash as _, Hasher as _}; +use std::rc::Rc; + +use futures::StreamExt as _; +use futures::channel::mpsc; +use gpui::{ + ForegroundExecutor, SharedString, SystemNotification, SystemNotificationResponse, Task, +}; +use windows::Data::Xml::Dom::XmlDocument; +use windows::Foundation::TypedEventHandler; +use windows::UI::Notifications::{ + ToastActivatedEventArgs, ToastNotification, ToastNotificationManager, ToastNotifier, +}; +use windows::core::{IInspectable, Interface as _, h}; + +type ResponseCallback = Rc>>>; + +pub(crate) struct SystemNotificationState { + notifier: Option, + active_toasts: HashMap, + response_sender: mpsc::UnboundedSender, + response_receiver: Option>, + callback: ResponseCallback, + _response_task: Option>, +} + +impl SystemNotificationState { + pub(crate) fn new() -> Self { + let (response_sender, response_receiver) = mpsc::unbounded(); + Self { + notifier: None, + active_toasts: HashMap::new(), + response_sender, + response_receiver: Some(response_receiver), + callback: Rc::new(RefCell::new(None)), + _response_task: None, + } + } + + pub(crate) fn show( + &mut self, + has_package_identity: bool, + app_identity: Option<(&str, &str)>, + notification: SystemNotification, + ) -> windows::core::Result<()> { + let Some(notifier) = self.notifier(has_package_identity, app_identity)? else { + return Ok(()); + }; + + let document = toast_document(¬ification)?; + let toast = ToastNotification::CreateToastNotification(&document)?; + // Windows caps toast tags at 64 characters (post-Creators Update), + // so hash the arbitrary GPUI tag down to a fixed-width value. + let tag = { + let mut hasher = DefaultHasher::new(); + notification.tag.hash(&mut hasher); + format!("{:016x}", hasher.finish()) + }; + toast.SetTag(&tag.into())?; + + let sender = self.response_sender.clone(); + let response_tag = notification.tag.clone(); + toast.Activated(&TypedEventHandler::::new( + move |_sender, arguments| { + let action_id = arguments + .as_ref() + .and_then(|arguments| arguments.cast::().ok()) + .and_then(|arguments| arguments.Arguments().ok()) + .filter(|arguments| !arguments.is_empty()) + .map(|arguments| SharedString::from(arguments.to_string())); + sender + .unbounded_send(SystemNotificationResponse { + tag: response_tag.clone(), + action_id, + }) + .ok(); + Ok(()) + }, + ))?; + + if let Some(previous) = self.active_toasts.remove(¬ification.tag) { + notifier.Hide(&previous)?; + } + notifier.Show(&toast)?; + self.active_toasts.insert(notification.tag, toast); + Ok(()) + } + + pub(crate) fn dismiss(&mut self, tag: &str) { + let Some(toast) = self.active_toasts.remove(tag) else { + return; + }; + let Some(notifier) = &self.notifier else { + return; + }; + if let Err(error) = notifier.Hide(&toast) { + log::warn!("failed to dismiss system notification: {error}"); + } + } + + pub(crate) fn on_response( + &mut self, + executor: &ForegroundExecutor, + callback: Box, + ) { + *self.callback.borrow_mut() = Some(callback); + + if let Some(mut receiver) = self.response_receiver.take() { + let callback = self.callback.clone(); + self._response_task = Some(executor.spawn(async move { + while let Some(response) = receiver.next().await { + // Take the callback out for the call: it may re-enter the + // platform (e.g. to dismiss the notification it was told + // about) or replace itself. + let taken = callback.borrow_mut().take(); + if let Some(mut taken) = taken { + taken(response); + callback.borrow_mut().get_or_insert(taken); + } + } + })); + } + } + + fn notifier( + &mut self, + has_package_identity: bool, + app_identity: Option<(&str, &str)>, + ) -> windows::core::Result> { + if let Some(notifier) = &self.notifier { + return Ok(Some(notifier.clone())); + } + + let notifier = if has_package_identity { + ToastNotificationManager::CreateToastNotifier()? + } else { + let Some((app_identifier, app_name)) = app_identity else { + log::warn!( + "cannot show a system notification without an app identity; \ + call `App::set_app_identity` during startup" + ); + return Ok(None); + }; + register_app_user_model_id(app_identifier, app_name); + ToastNotificationManager::CreateToastNotifierWithId(&app_identifier.into())? + }; + + self.notifier = Some(notifier.clone()); + Ok(Some(notifier)) + } +} + +fn toast_document(notification: &SystemNotification) -> windows::core::Result { + let document = XmlDocument::new()?; + let toast = document.CreateElement(h!("toast"))?; + document.AppendChild(&toast)?; + + let visual = document.CreateElement(h!("visual"))?; + toast.AppendChild(&visual)?; + let binding = document.CreateElement(h!("binding"))?; + binding.SetAttribute(h!("template"), h!("ToastGeneric"))?; + visual.AppendChild(&binding)?; + for text in [¬ification.title, ¬ification.body] { + let element = document.CreateElement(h!("text"))?; + element.SetInnerText(&text.as_ref().into())?; + binding.AppendChild(&element)?; + } + + if !notification.actions.is_empty() { + let actions = document.CreateElement(h!("actions"))?; + toast.AppendChild(&actions)?; + for action in ¬ification.actions { + let action_element = document.CreateElement(h!("action"))?; + action_element.SetAttribute(h!("content"), &action.label.as_ref().into())?; + action_element.SetAttribute(h!("arguments"), &action.id.as_ref().into())?; + actions.AppendChild(&action_element)?; + } + } + + let audio = document.CreateElement(h!("audio"))?; + audio.SetAttribute(h!("silent"), h!("true"))?; + toast.AppendChild(&audio)?; + Ok(document) +} + +/// Registers the app's AUMID so toasts display correctly for an unpackaged app without a Start Menu shortcut. +fn register_app_user_model_id(app_identifier: &str, app_name: &str) { + let result = windows_registry::CURRENT_USER + .create(format!(r"Software\Classes\AppUserModelId\{app_identifier}")) + .and_then(|key| key.set_string("DisplayName", app_name)); + if let Err(error) = result { + log::warn!("failed to register AppUserModelID; notifications may not display: {error}"); + } +} diff --git a/vendor/gpui-pre-windows/src/system_settings.rs b/vendor/gpui-pre-windows/src/system_settings.rs new file mode 100644 index 0000000..8cce544 --- /dev/null +++ b/vendor/gpui-pre-windows/src/system_settings.rs @@ -0,0 +1,87 @@ +use std::{ + cell::Cell, + ffi::{c_uint, c_void}, +}; + +use gpui_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, + /// SEE: SPI_GETWHEELSCROLLLINES + pub(crate) wheel_scroll_lines: Cell, +} + +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); + } + } +} diff --git a/vendor/gpui-pre-windows/src/util.rs b/vendor/gpui-pre-windows/src/util.rs new file mode 100644 index 0000000..b3badd8 --- /dev/null +++ b/vendor/gpui-pre-windows/src/util.rs @@ -0,0 +1,191 @@ +use std::sync::OnceLock; + +use anyhow::Context; +use gpui_util::ResultExt; +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 { + static ARROW: OnceLock = OnceLock::new(); + static IBEAM: OnceLock = OnceLock::new(); + static CROSS: OnceLock = OnceLock::new(); + static HAND: OnceLock = OnceLock::new(); + static SIZEWE: OnceLock = OnceLock::new(); + static SIZENS: OnceLock = OnceLock::new(); + static SIZENWSE: OnceLock = OnceLock::new(); + static SIZENESW: OnceLock = OnceLock::new(); + static NO: OnceLock = 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::() as u32, + ) + .log_err(); + } +} + +#[inline] +pub(crate) fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point { + 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 { + 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(dll_name: PCSTR, f: F) -> Result +where + F: FnOnce(HMODULE) -> Result, +{ + 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 +} diff --git a/vendor/gpui-pre-windows/src/vsync.rs b/vendor/gpui-pre-windows/src/vsync.rs new file mode 100644 index 0000000..5121c41 --- /dev/null +++ b/vendor/gpui-pre-windows/src/vsync.rs @@ -0,0 +1,119 @@ +use std::{ + sync::LazyLock, + time::{Duration, Instant}, +}; + +use anyhow::{Context, Result}; +use gpui::FrameRequestSource; +use gpui_util::ResultExt; +use windows::Win32::{ + Foundation::HWND, + Graphics::Dwm::{DWM_TIMING_INFO, DwmFlush, DwmGetCompositionTimingInfo}, + System::Performance::QueryPerformanceFrequency, +}; + +static QPC_TICKS_PER_SECOND: LazyLock = 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 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) -> FrameRequestSource { + 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); + FrameRequestSource::LocalSchedule + } else { + FrameRequestSource::NativeCallback + } + } +} + +fn get_dwm_interval() -> Result { + let mut timing_info = DWM_TIMING_INFO { + cbSize: std::mem::size_of::() 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) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn successful_compositor_wait_reports_native_callback() { + let provider = VSyncProvider { + interval: Duration::ZERO, + f: Box::new(|| { + std::thread::sleep(VSYNC_INTERVAL_THRESHOLD * 2); + true + }), + }; + + assert_eq!( + provider.wait_for_vsync(), + FrameRequestSource::NativeCallback + ); + } + + #[test] + fn failed_compositor_wait_reports_local_schedule_even_after_threshold() { + let provider = VSyncProvider { + interval: Duration::ZERO, + f: Box::new(|| { + std::thread::sleep(VSYNC_INTERVAL_THRESHOLD * 2); + false + }), + }; + + assert_eq!(provider.wait_for_vsync(), FrameRequestSource::LocalSchedule); + } +} diff --git a/vendor/gpui-pre-windows/src/window.rs b/vendor/gpui-pre-windows/src/window.rs new file mode 100644 index 0000000..8a5327d --- /dev/null +++ b/vendor/gpui-pre-windows/src/window.rs @@ -0,0 +1,1735 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use std::{ + cell::{Cell, RefCell}, + num::NonZeroIsize, + path::PathBuf, + rc::{Rc, Weak}, + str::FromStr, + sync::{Arc, Once, atomic::AtomicBool}, + time::{Duration, Instant}, +}; + +use anyhow::{Context as _, Result}; +use futures::channel::oneshot::{self, Receiver}; +use gpui_util::ResultExt; +use raw_window_handle as rwh; +use smallvec::SmallVec; +use windows::{ + Win32::{ + Foundation::*, + Graphics::Dwm::*, + Graphics::Gdi::*, + System::{ + Com::*, Diagnostics::Debug::MessageBeep, LibraryLoader::*, Ole::*, SystemServices::*, + }, + UI::{Controls::*, HiDpi::*, Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*}, + }, + core::*, +}; + +use crate::direct_manipulation::DirectManipulationHandler; +use crate::*; +use gpui::*; + +pub(crate) struct WindowsWindow(pub Rc); + +impl std::ops::Deref for WindowsWindow { + type Target = WindowsWindowInner; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub struct WindowsWindowState { + pub origin: Cell>, + pub logical_size: Cell>, + pub min_size: Option>, + pub fullscreen_restore_bounds: Cell>, + pub border_offset: WindowBorderOffset, + pub appearance: Cell, + pub background_appearance: Cell, + pub scale_factor: Cell, + pub restore_from_minimized: Cell>>, + + pub callbacks: Callbacks, + pub frame_signal: Arc, + pub input_handler: Cell>, + pub ime_enabled: Cell, + pub pending_surrogate: Cell>, + pub last_reported_modifiers: Cell>, + pub last_reported_capslock: Cell>, + pub hovered: Cell, + pub last_visibility: Cell>, + pub direct_manipulation: DirectManipulationHandler, + + pub renderer: RefCell, + /// Set when the next `draw_window` call must be treated as a forced + /// render. Used after a GPU device-lost recovery, where the next frame + /// must both re-enable drawing (via `mark_drawable`) and bypass the GPUI + /// view cache (which would otherwise replay stale atlas tile references + /// from the previous frame and panic in `DirectXAtlasTextures::texture`), + /// and when a forced render was requested while another draw was in + /// progress and had to be deferred. + pub force_render_pending: Cell, + + pub click_state: ClickState, + pub current_cursor: Cell>, + /// Shared with [`WindowsPlatformState::cursor_visible`]. + pub cursor_visible: Arc, + pub nc_button_pressed: Cell>, + + pub display: Cell, + /// Flag to instruct the `VSyncProvider` thread to invalidate the directx devices + /// as resizing them has failed, causing us to have lost at least the render target. + pub invalidate_devices: Arc, + /// Shared with [`WindowsPlatformState::draw_coordinator`] and every other window. + pub(crate) draw_coordinator: Rc, + fullscreen: Cell>, + initial_placement: Cell>, + hwnd: HWND, + pub(crate) a11y: RefCell>, +} + +pub(crate) struct WindowsWindowInner { + hwnd: HWND, + pub(crate) dialog_owner: Rc, + drop_target_helper: IDropTargetHelper, + pub(crate) state: WindowsWindowState, + system_settings: WindowsSystemSettings, + pub(crate) handle: AnyWindowHandle, + pub(crate) hide_title_bar: bool, + pub(crate) is_movable: bool, + pub(crate) is_resizable: bool, + pub(crate) is_minimizable: bool, + pub(crate) executor: ForegroundExecutor, + pub(crate) validation_number: usize, + pub(crate) main_receiver: PriorityQueueReceiver, + pub(crate) platform_window_handle: HWND, + pub(crate) parent_hwnd: Option, +} + +impl WindowsWindowState { + fn new( + hwnd: HWND, + directx_devices: &DirectXDevices, + window_params: &CREATESTRUCTW, + current_cursor: Option, + cursor_visible: Arc, + display: WindowsDisplay, + min_size: Option>, + appearance: WindowAppearance, + disable_direct_composition: bool, + invalidate_devices: Arc, + draw_coordinator: Rc, + ) -> Result { + let scale_factor = { + let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32; + monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32 + }; + let origin = logical_point(window_params.x as f32, window_params.y as f32, scale_factor); + let logical_size = { + let physical_size = size( + DevicePixels(window_params.cx), + DevicePixels(window_params.cy), + ); + physical_size.to_pixels(scale_factor) + }; + let fullscreen_restore_bounds = Bounds { + origin, + size: logical_size, + }; + let border_offset = WindowBorderOffset::default(); + let restore_from_minimized = None; + let renderer = DirectXRenderer::new(hwnd, directx_devices, disable_direct_composition) + .context("Creating DirectX renderer")?; + let callbacks = Callbacks::default(); + let input_handler = None; + let pending_surrogate = None; + let last_reported_modifiers = None; + let last_reported_capslock = None; + let hovered = false; + let click_state = ClickState::new(); + let nc_button_pressed = None; + let fullscreen = None; + let initial_placement = None; + + let direct_manipulation = DirectManipulationHandler::new(hwnd, scale_factor) + .context("initializing Direct Manipulation")?; + + Ok(Self { + origin: Cell::new(origin), + logical_size: Cell::new(logical_size), + fullscreen_restore_bounds: Cell::new(fullscreen_restore_bounds), + border_offset, + appearance: Cell::new(appearance), + background_appearance: Cell::new(WindowBackgroundAppearance::Opaque), + scale_factor: Cell::new(scale_factor), + restore_from_minimized: Cell::new(restore_from_minimized), + min_size, + callbacks, + frame_signal: Arc::new(PlatformFrameSignal::new()), + input_handler: Cell::new(input_handler), + ime_enabled: Cell::new(true), + pending_surrogate: Cell::new(pending_surrogate), + last_reported_modifiers: Cell::new(last_reported_modifiers), + last_reported_capslock: Cell::new(last_reported_capslock), + hovered: Cell::new(hovered), + last_visibility: Cell::new(None), + renderer: RefCell::new(renderer), + force_render_pending: Cell::new(false), + click_state, + current_cursor: Cell::new(current_cursor), + cursor_visible, + nc_button_pressed: Cell::new(nc_button_pressed), + display: Cell::new(display), + fullscreen: Cell::new(fullscreen), + initial_placement: Cell::new(initial_placement), + hwnd, + invalidate_devices, + draw_coordinator, + direct_manipulation, + a11y: RefCell::new(None), + }) + } + + #[inline] + pub(crate) fn is_fullscreen(&self) -> bool { + self.fullscreen.get().is_some() + } + + pub(crate) fn is_maximized(&self) -> bool { + !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool() + } + + fn bounds(&self) -> Bounds { + Bounds { + origin: self.origin.get(), + size: self.logical_size.get(), + } + } + + // Calculate the bounds used for saving and whether the window is maximized. + fn calculate_window_bounds(&self) -> (Bounds, bool) { + let placement = unsafe { + let mut placement = WINDOWPLACEMENT { + length: std::mem::size_of::() as u32, + ..Default::default() + }; + GetWindowPlacement(self.hwnd, &mut placement) + .context("failed to get window placement") + .log_err(); + placement + }; + ( + calculate_client_rect( + placement.rcNormalPosition, + &self.border_offset, + self.scale_factor.get(), + ), + placement.showCmd == SW_SHOWMAXIMIZED.0 as u32, + ) + } + + fn window_bounds(&self) -> WindowBounds { + let (bounds, maximized) = self.calculate_window_bounds(); + + if self.is_fullscreen() { + WindowBounds::Fullscreen(self.fullscreen_restore_bounds.get()) + } else if maximized { + WindowBounds::Maximized(bounds) + } else { + WindowBounds::Windowed(bounds) + } + } + + /// get the logical size of the app's drawable area. + /// + /// Currently, GPUI uses the logical size of the app to handle mouse interactions (such as + /// whether the mouse collides with other elements of GPUI). + fn content_size(&self) -> Size { + self.logical_size.get() + } +} + +impl WindowsWindowInner { + /// Whether the window is being presented: shown and not minimized. Windows + /// has no notification for a window fully covered by other windows, so + /// that case reports `Visible`. + pub(crate) fn visibility(&self) -> WindowVisibility { + let is_visible = + unsafe { IsWindowVisible(self.hwnd).as_bool() && !IsIconic(self.hwnd).as_bool() }; + if is_visible { + WindowVisibility::Visible + } else { + WindowVisibility::Hidden + } + } + + fn new(context: &mut WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Result> { + let state = WindowsWindowState::new( + hwnd, + &context.directx_devices, + cs, + context.current_cursor, + context.cursor_visible.clone(), + context.display, + context.min_size, + context.appearance, + context.disable_direct_composition, + context.invalidate_devices.clone(), + context.draw_coordinator.clone(), + )?; + + Ok(Rc::new(Self { + hwnd, + dialog_owner: crate::dialog::DialogOwner::new(hwnd), + drop_target_helper: context.drop_target_helper.clone(), + state, + handle: context.handle, + hide_title_bar: context.hide_title_bar, + is_movable: context.is_movable, + is_resizable: context.is_resizable, + is_minimizable: context.is_minimizable, + executor: context.executor.clone(), + validation_number: context.validation_number, + main_receiver: context.main_receiver.clone(), + platform_window_handle: context.platform_window_handle, + system_settings: WindowsSystemSettings::new(), + parent_hwnd: context.parent_hwnd, + })) + } + + fn toggle_fullscreen(self: &Rc) { + let this = self.clone(); + self.executor + .spawn(async move { + let StyleAndBounds { + style, + x, + y, + cx, + cy, + } = match this.state.fullscreen.take() { + Some(state) => state, + None => { + let (window_bounds, _) = this.state.calculate_window_bounds(); + this.state.fullscreen_restore_bounds.set(window_bounds); + + let style = + WINDOW_STYLE(unsafe { get_window_long(this.hwnd, GWL_STYLE) } as _); + let mut rc = RECT::default(); + unsafe { GetWindowRect(this.hwnd, &mut rc) } + .context("failed to get window rect") + .log_err(); + let _ = this.state.fullscreen.set(Some(StyleAndBounds { + style, + x: rc.left, + y: rc.top, + cx: rc.right - rc.left, + cy: rc.bottom - rc.top, + })); + let style = style + & !(WS_THICKFRAME + | WS_SYSMENU + | WS_MAXIMIZEBOX + | WS_MINIMIZEBOX + | WS_CAPTION); + let physical_bounds = this.state.display.get().physical_bounds(); + StyleAndBounds { + style, + x: physical_bounds.left().0, + y: physical_bounds.top().0, + cx: physical_bounds.size.width.0, + cy: physical_bounds.size.height.0, + } + } + }; + set_non_rude_hwnd(this.hwnd, !this.state.is_fullscreen()); + unsafe { set_window_long(this.hwnd, GWL_STYLE, style.0 as isize) }; + unsafe { + SetWindowPos( + this.hwnd, + None, + x, + y, + cx, + cy, + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER, + ) + } + .log_err(); + }) + .detach(); + } + + fn set_window_placement(self: &Rc) -> Result<()> { + let Some(open_status) = self.state.initial_placement.take() else { + return Ok(()); + }; + match open_status.state { + WindowOpenState::Maximized => unsafe { + SetWindowPlacement(self.hwnd, &open_status.placement) + .context("failed to set window placement")?; + ShowWindowAsync(self.hwnd, SW_MAXIMIZE).ok()?; + }, + WindowOpenState::Fullscreen => { + unsafe { + SetWindowPlacement(self.hwnd, &open_status.placement) + .context("failed to set window placement")? + }; + self.toggle_fullscreen(); + } + WindowOpenState::Windowed => unsafe { + SetWindowPlacement(self.hwnd, &open_status.placement) + .context("failed to set window placement")?; + }, + } + Ok(()) + } + + pub(crate) fn system_settings(&self) -> &WindowsSystemSettings { + &self.system_settings + } +} + +#[derive(Default)] +pub(crate) struct Callbacks { + pub(crate) request_frame: Cell>>, + pub(crate) input: Cell DispatchEventResult>>>, + pub(crate) active_status_change: Cell>>, + pub(crate) visibility_change: Cell>>, + pub(crate) hovered_status_change: Cell>>, + pub(crate) resize: Cell, f32)>>>, + pub(crate) moved: Cell>>, + pub(crate) should_close: Cell bool>>>, + pub(crate) close: Cell>>, + pub(crate) hit_test_window_control: Cell Option>>>, + pub(crate) appearance_changed: Cell>>, +} + +struct WindowCreateContext { + inner: Option>>, + handle: AnyWindowHandle, + hide_title_bar: bool, + display: WindowsDisplay, + is_movable: bool, + is_resizable: bool, + is_minimizable: bool, + min_size: Option>, + executor: ForegroundExecutor, + current_cursor: Option, + cursor_visible: Arc, + drop_target_helper: IDropTargetHelper, + validation_number: usize, + main_receiver: PriorityQueueReceiver, + platform_window_handle: HWND, + appearance: WindowAppearance, + disable_direct_composition: bool, + directx_devices: DirectXDevices, + invalidate_devices: Arc, + draw_coordinator: Rc, + parent_hwnd: Option, +} + +impl WindowsWindow { + pub(crate) fn new( + handle: AnyWindowHandle, + params: WindowParams, + creation_info: WindowCreationInfo, + ) -> Result { + // Native popups are not implemented on Windows yet. Rejecting lets callers fall back to + // gpui's in-window popovers. + if let WindowKind::AnchoredPopup(_) = params.kind { + return Err(popup::PopupNotSupportedError.into()); + } + + let WindowCreationInfo { + icon, + executor, + current_cursor, + cursor_visible, + drop_target_helper, + validation_number, + main_receiver, + platform_window_handle, + disable_direct_composition, + directx_devices, + invalidate_devices, + draw_coordinator, + } = creation_info; + register_window_class(icon); + let parent_hwnd = if params.kind == WindowKind::Dialog { + let parent_window = unsafe { GetActiveWindow() }; + if parent_window.is_invalid() { + None + } else { + // Disable the parent window to make this dialog modal + unsafe { + EnableWindow(parent_window, false).as_bool(); + }; + Some(parent_window) + } + } else { + None + }; + let hide_title_bar = params + .titlebar + .as_ref() + .map(|titlebar| titlebar.appears_transparent) + .unwrap_or(true); + let window_name = HSTRING::from( + params + .titlebar + .as_ref() + .and_then(|titlebar| titlebar.title.as_ref()) + .map(|title| title.as_ref()) + .unwrap_or(""), + ); + + let (mut dwexstyle, dwstyle) = if params.kind == WindowKind::PopUp { + (WS_EX_TOOLWINDOW | WS_EX_TOPMOST, WINDOW_STYLE(0x0)) + } else { + let mut dwstyle = WS_SYSMENU; + + if params.is_resizable { + dwstyle |= WS_THICKFRAME | WS_MAXIMIZEBOX; + } + + if params.is_minimizable { + dwstyle |= WS_MINIMIZEBOX; + } + let dwexstyle = if params.kind == WindowKind::Dialog { + dwstyle |= WS_POPUP | WS_CAPTION; + WS_EX_DLGMODALFRAME + } else { + WS_EX_APPWINDOW + }; + + (dwexstyle, dwstyle) + }; + if !disable_direct_composition { + dwexstyle |= WS_EX_NOREDIRECTIONBITMAP; + } + + let hinstance = get_module_handle(); + let display = if let Some(display_id) = params.display_id { + WindowsDisplay::new(display_id) + } else { + None + } + .or_else(WindowsDisplay::primary_monitor) + .context("failed to find any monitor")?; + let appearance = system_appearance().unwrap_or_default(); + let mut context = WindowCreateContext { + inner: None, + handle, + hide_title_bar, + display, + is_movable: params.is_movable, + is_resizable: params.is_resizable, + is_minimizable: params.is_minimizable, + min_size: params.window_min_size, + executor, + current_cursor, + cursor_visible, + drop_target_helper, + validation_number, + main_receiver, + platform_window_handle, + appearance, + disable_direct_composition, + directx_devices, + invalidate_devices, + draw_coordinator, + parent_hwnd, + }; + let creation_result = unsafe { + CreateWindowExW( + dwexstyle, + WINDOW_CLASS_NAME, + &window_name, + dwstyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + parent_hwnd, + None, + Some(hinstance.into()), + Some(&context as *const _ as *const _), + ) + }; + + // Failure to create a `WindowsWindowState` can cause window creation to fail, + // so check the inner result first. + let this = context.inner.take().transpose()?; + let hwnd = creation_result?; + let this = this.unwrap(); + + register_drag_drop(&this)?; + set_non_rude_hwnd(hwnd, true); + configure_dwm_dark_mode(hwnd, appearance); + this.state.border_offset.update(hwnd)?; + let placement = + retrieve_window_placement(hwnd, display, params.bounds, &this.state.border_offset)?; + if params.show { + let mut placement = placement; + if !params.focus { + placement.showCmd = SW_SHOWNOACTIVATE.0 as u32; + } + unsafe { SetWindowPlacement(hwnd, &placement)? }; + } else { + this.state.initial_placement.set(Some(WindowOpenStatus { + placement, + state: WindowOpenState::Windowed, + })); + } + + Ok(Self(this)) + } +} + +impl rwh::HasWindowHandle for WindowsWindow { + fn window_handle(&self) -> std::result::Result, rwh::HandleError> { + let raw = rwh::Win32WindowHandle::new(unsafe { + NonZeroIsize::new_unchecked(self.0.hwnd.0 as isize) + }) + .into(); + Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) }) + } +} + +impl rwh::HasDisplayHandle for WindowsWindow { + fn display_handle(&self) -> std::result::Result, rwh::HandleError> { + Ok(rwh::DisplayHandle::windows()) + } +} + +impl Drop for WindowsWindow { + fn drop(&mut self) { + self.0.dialog_owner.close(); + unsafe { ShowWindowAsync(self.0.hwnd, SW_HIDE).ok().log_err() }; + // `DestroyWindow` below sends `WM_SHOWWINDOW`; without a callback the + // resulting visibility report has nothing to notify. + self.0.state.callbacks.visibility_change.take(); + // clone this `Rc` to prevent early release of the pointer + let this = self.0.clone(); + self.0 + .executor + .spawn(async move { + this.dialog_owner.when_idle().await; + let handle = this.hwnd; + unsafe { + RevokeDragDrop(handle).log_err(); + DestroyWindow(handle).log_err(); + } + }) + .detach(); + } +} + +impl PlatformWindow for WindowsWindow { + fn bounds(&self) -> Bounds { + self.state.bounds() + } + + fn is_maximized(&self) -> bool { + self.state.is_maximized() + } + + fn window_bounds(&self) -> WindowBounds { + self.state.window_bounds() + } + + /// get the logical size of the app's drawable area. + /// + /// Currently, GPUI uses the logical size of the app to handle mouse interactions (such as + /// whether the mouse collides with other elements of GPUI). + fn content_size(&self) -> Size { + self.state.content_size() + } + + fn resize(&mut self, size: Size) { + let hwnd = self.0.hwnd; + let bounds = gpui::bounds(self.bounds().origin, size).to_device_pixels(self.scale_factor()); + let rect = calculate_window_rect(bounds, &self.state.border_offset); + + self.0 + .executor + .spawn(async move { + unsafe { + SetWindowPos( + hwnd, + None, + bounds.origin.x.0, + bounds.origin.y.0, + rect.right - rect.left, + rect.bottom - rect.top, + SWP_NOMOVE, + ) + .context("unable to set window content size") + .log_err(); + } + }) + .detach(); + } + + fn scale_factor(&self) -> f32 { + self.state.scale_factor.get() + } + + fn appearance(&self) -> WindowAppearance { + self.state.appearance.get() + } + + fn display(&self) -> Option> { + Some(Rc::new(self.state.display.get())) + } + + fn mouse_position(&self) -> Point { + let scale_factor = self.scale_factor(); + let point = unsafe { + let mut point: POINT = std::mem::zeroed(); + GetCursorPos(&mut point) + .context("unable to get cursor position") + .log_err(); + ScreenToClient(self.0.hwnd, &mut point).ok().log_err(); + point + }; + logical_point(point.x as f32, point.y as f32, scale_factor) + } + + fn modifiers(&self) -> Modifiers { + current_modifiers() + } + + fn capslock(&self) -> Capslock { + current_capslock() + } + + fn set_input_handler(&mut self, input_handler: PlatformInputHandler) { + self.state.input_handler.set(Some(input_handler)); + } + + fn take_input_handler(&mut self) -> Option { + self.state.input_handler.take() + } + + fn prompt( + &self, + level: PromptLevel, + msg: &str, + detail: Option<&str>, + answers: &[PromptButton], + ) -> Option> { + let (mut done_tx, done_rx) = oneshot::channel(); + let msg = msg.to_string(); + let detail_string = detail.map(|detail| detail.to_string()); + let answers = answers.to_vec(); + let dialog = crate::dialog::show_dialog( + Some(self.0.dialog_owner.clone()), + &self.0.executor, + move |handle| { + unsafe { + let mut config = TASKDIALOGCONFIG::default(); + config.cbSize = std::mem::size_of::() as _; + config.hwndParent = handle; + config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION; + let title; + let main_icon; + match level { + PromptLevel::Info => { + title = windows::core::w!("Info"); + main_icon = TD_INFORMATION_ICON; + } + PromptLevel::Warning => { + title = windows::core::w!("Warning"); + main_icon = TD_WARNING_ICON; + } + PromptLevel::Critical => { + title = windows::core::w!("Critical"); + main_icon = TD_ERROR_ICON; + } + }; + config.pszWindowTitle = title; + config.Anonymous1.pszMainIcon = main_icon; + let instruction = HSTRING::from(msg); + config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr()); + let hints_encoded; + if let Some(ref hints) = detail_string { + hints_encoded = HSTRING::from(hints); + config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr()); + }; + let mut button_id_map = Vec::with_capacity(answers.len()); + let mut buttons = Vec::new(); + let mut btn_encoded = Vec::new(); + for (index, btn) in answers.iter().enumerate() { + let encoded = HSTRING::from(btn.label().as_ref()); + let button_id = match btn { + PromptButton::Ok(_) => IDOK.0, + PromptButton::Cancel(_) => IDCANCEL.0, + // the first few low integer values are reserved for known buttons + // so for simplicity we just go backwards from -1 + PromptButton::Other(_) => -(index as i32) - 1, + }; + button_id_map.push(button_id); + buttons.push(TASKDIALOG_BUTTON { + nButtonID: button_id, + pszButtonText: PCWSTR::from_raw(encoded.as_ptr()), + }); + btn_encoded.push(encoded); + } + config.cButtons = buttons.len() as _; + config.pButtons = buttons.as_ptr(); + + config.pfCallback = Some(crate::dialog::task_dialog_callback); + config.lpCallbackData = button_id_map.contains(&IDCANCEL.0) as isize; + let mut res = std::mem::zeroed(); + TaskDialogIndirect(&config, Some(&mut res), None, None) + .context("unable to create task dialog")?; + Ok(button_id_map.iter().position(|&button_id| button_id == res)) + } + }, + ); + self.0 + .executor + .spawn(async move { + if let futures::future::Either::Left((result, _)) = + futures::future::select(dialog, done_tx.cancellation()).await + && let Some(Some(clicked)) = result + .context("native dialog thread stopped") + .and_then(|result| result) + .log_err() + { + done_tx.send(clicked).ok(); + } + }) + .detach(); + + Some(done_rx) + } + + fn activate(&self) { + let hwnd = self.0.hwnd; + let this = self.0.clone(); + self.0 + .executor + .spawn(async move { + this.set_window_placement().log_err(); + + unsafe { + // If the window is minimized, restore it. + if IsIconic(hwnd).as_bool() { + ShowWindowAsync(hwnd, SW_RESTORE).ok().log_err(); + } + + SetActiveWindow(hwnd).ok(); + SetFocus(Some(hwnd)).ok(); + } + + // premium ragebait by windows, this is needed because the window + // must have received an input event to be able to set itself to foreground + // so let's just simulate user input as that seems to be the most reliable way + // some more info: https://gist.github.com/Aetopia/1581b40f00cc0cadc93a0e8ccb65dc8c + // bonus: this bug also doesn't manifest if you have vs attached to the process + let inputs = [ + INPUT { + r#type: INPUT_KEYBOARD, + Anonymous: INPUT_0 { + ki: KEYBDINPUT { + wVk: VK_MENU, + dwFlags: KEYBD_EVENT_FLAGS(0), + ..Default::default() + }, + }, + }, + INPUT { + r#type: INPUT_KEYBOARD, + Anonymous: INPUT_0 { + ki: KEYBDINPUT { + wVk: VK_MENU, + dwFlags: KEYEVENTF_KEYUP, + ..Default::default() + }, + }, + }, + ]; + unsafe { SendInput(&inputs, std::mem::size_of::() as i32) }; + + // todo(windows) + // crate `windows 0.56` reports true as Err + unsafe { SetForegroundWindow(hwnd).as_bool() }; + }) + .detach(); + } + + fn request_attention(&self) { + if self.is_active() { + return; + } + + let hwnd = self.0.hwnd; + self.0 + .executor + .spawn(async move { + let info = FLASHWINFO { + cbSize: std::mem::size_of::() as u32, + hwnd, + dwFlags: FLASHW_ALL, + uCount: 1, + dwTimeout: 0, + }; + unsafe { FlashWindowEx(&info).ok().log_err() }; + }) + .detach(); + } + + fn is_active(&self) -> bool { + self.0.hwnd == unsafe { GetActiveWindow() } + } + + fn visibility(&self) -> WindowVisibility { + self.0.visibility() + } + + fn is_hovered(&self) -> bool { + self.state.hovered.get() + } + + fn background_appearance(&self) -> WindowBackgroundAppearance { + self.state.background_appearance.get() + } + + fn is_subpixel_rendering_supported(&self) -> bool { + true + } + + fn set_title(&mut self, title: &str) { + unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) } + .inspect_err(|e| log::error!("Set title failed: {e}")) + .ok(); + } + + fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) { + self.state.background_appearance.set(background_appearance); + let hwnd = self.0.hwnd; + + // using Dwm APIs for Mica and MicaAlt backdrops. + // others follow the set_window_composition_attribute approach + match background_appearance { + WindowBackgroundAppearance::Opaque => { + set_window_composition_attribute(hwnd, None, 0); + } + WindowBackgroundAppearance::Transparent => { + set_window_composition_attribute(hwnd, None, 2); + } + WindowBackgroundAppearance::Blurred => { + set_window_composition_attribute(hwnd, Some((0, 0, 0, 0)), 4); + } + WindowBackgroundAppearance::MicaBackdrop => { + // DWMSBT_MAINWINDOW => MicaBase + dwm_set_window_composition_attribute(hwnd, 2); + } + WindowBackgroundAppearance::MicaAltBackdrop => { + // DWMSBT_TABBEDWINDOW => MicaAlt + dwm_set_window_composition_attribute(hwnd, 4); + } + } + } + + fn minimize(&self) { + unsafe { ShowWindowAsync(self.0.hwnd, SW_MINIMIZE).ok().log_err() }; + } + + fn zoom(&self) { + unsafe { + if IsWindowVisible(self.0.hwnd).as_bool() { + ShowWindowAsync(self.0.hwnd, SW_MAXIMIZE).ok().log_err(); + } else if let Some(mut status) = self.state.initial_placement.take() { + status.state = WindowOpenState::Maximized; + self.state.initial_placement.set(Some(status)); + } + } + } + + fn toggle_fullscreen(&self) { + if unsafe { IsWindowVisible(self.0.hwnd).as_bool() } { + self.0.toggle_fullscreen(); + } else if let Some(mut status) = self.state.initial_placement.take() { + status.state = WindowOpenState::Fullscreen; + self.state.initial_placement.set(Some(status)); + } + } + + fn is_fullscreen(&self) -> bool { + self.state.is_fullscreen() + } + + fn on_request_frame(&self, callback: Box) { + self.state.callbacks.request_frame.set(Some(callback)); + } + + fn on_input(&self, callback: Box DispatchEventResult>) { + self.state.callbacks.input.set(Some(callback)); + } + + fn on_active_status_change(&self, callback: Box) { + self.0 + .state + .callbacks + .active_status_change + .set(Some(callback)); + } + + fn on_visibility_change(&self, callback: Box) { + self.0.state.last_visibility.set(Some(self.0.visibility())); + self.0.state.callbacks.visibility_change.set(Some(callback)); + } + + fn on_hover_status_change(&self, callback: Box) { + self.0 + .state + .callbacks + .hovered_status_change + .set(Some(callback)); + } + + fn on_resize(&self, callback: Box, f32)>) { + self.state.callbacks.resize.set(Some(callback)); + } + + fn on_moved(&self, callback: Box) { + self.state.callbacks.moved.set(Some(callback)); + } + + fn on_should_close(&self, callback: Box bool>) { + self.state.callbacks.should_close.set(Some(callback)); + } + + fn on_close(&self, callback: Box) { + self.state.callbacks.close.set(Some(callback)); + } + + fn on_hit_test_window_control(&self, callback: Box Option>) { + self.0 + .state + .callbacks + .hit_test_window_control + .set(Some(callback)); + } + + fn on_appearance_changed(&self, callback: Box) { + self.0 + .state + .callbacks + .appearance_changed + .set(Some(callback)); + } + + fn draw(&self, scene: &Scene) { + self.state + .renderer + .borrow_mut() + .draw(scene, self.state.background_appearance.get()) + .log_err(); + } + + #[cfg(any(test, feature = "test-support"))] + fn render_to_image(&self, scene: &Scene) -> anyhow::Result { + self.state + .renderer + .borrow_mut() + .render_to_image(scene, self.state.background_appearance.get()) + } + + fn sprite_atlas(&self) -> Arc { + self.state.renderer.borrow().sprite_atlas() + } + + fn get_raw_handle(&self) -> HWND { + self.0.hwnd + } + + fn gpu_specs(&self) -> Option { + self.state.renderer.borrow().gpu_specs().log_err() + } + + fn update_ime_position(&self, bounds: Bounds) { + let scale_factor = self.state.scale_factor.get(); + let caret_position = POINT { + x: (bounds.origin.x.as_f32() * scale_factor) as i32, + y: (bounds.origin.y.as_f32() * scale_factor) as i32 + + ((bounds.size.height.as_f32() * scale_factor) as i32 / 2), + }; + + self.0.update_ime_position(self.0.hwnd, caret_position); + } + + fn play_system_bell(&self) { + // MB_OK: The sound specified as the Windows Default Beep sound. + let _ = unsafe { MessageBeep(MB_OK) }; + } + + fn a11y_init(&self, callbacks: gpui::A11yCallbacks) { + let action_handler = A11yActionHandler(callbacks.action); + let is_focused = unsafe { GetForegroundWindow() } == self.0.hwnd; + + let adapter = accesskit_windows::Adapter::new( + accesskit_windows::HWND(self.0.hwnd.0), + is_focused, + action_handler, + ); + + let activation_handler = A11yActivationHandler { + callback: callbacks.activation, + }; + + *self.state.a11y.borrow_mut() = Some(A11yState { + adapter, + activation_handler, + }); + } + + fn a11y_tree_update(&self, tree_update: accesskit::TreeUpdate) { + let events = { + let mut a11y = self.state.a11y.borrow_mut(); + a11y.as_mut() + .and_then(|a11y| a11y.adapter.update_if_active(|| tree_update)) + }; + // The borrow must be dropped before raising events, because + // `events.raise()` calls `UiaRaiseAutomationPropertyChangedEvent` + // which may send a nested `WM_GETOBJECT` back into this window + // procedure, re-entering `handle_wm_getobject` which also borrows + // `self.state.a11y`. + if let Some(events) = events { + events.raise(); + } + } + + fn a11y_update_window_bounds(&self) { + // Windows UIA handles window bounds tracking automatically. + } +} + +pub(crate) struct A11yState { + pub(crate) adapter: accesskit_windows::Adapter, + pub(crate) activation_handler: A11yActivationHandler, +} + +pub(crate) struct A11yActivationHandler { + callback: Box Option + Send + 'static>, +} + +impl accesskit::ActivationHandler for A11yActivationHandler { + fn request_initial_tree(&mut self) -> Option { + (self.callback)() + } +} + +struct A11yActionHandler(Box); + +impl accesskit::ActionHandler for A11yActionHandler { + fn do_action(&mut self, request: accesskit::ActionRequest) { + (self.0)(request); + } +} + +#[implement(IDropTarget)] +struct WindowsDragDropHandler(pub Rc); + +impl WindowsDragDropHandler { + fn handle_drag_drop(&self, input: PlatformInput) { + if let Some(mut func) = self.0.state.callbacks.input.take() { + func(input); + self.0.state.callbacks.input.set(Some(func)); + } + } +} + +#[allow(non_snake_case)] +impl IDropTarget_Impl for WindowsDragDropHandler_Impl { + fn DragEnter( + &self, + pdataobj: windows::core::Ref, + _grfkeystate: MODIFIERKEYS_FLAGS, + pt: &POINTL, + pdweffect: *mut DROPEFFECT, + ) -> windows::core::Result<()> { + unsafe { + let idata_obj = pdataobj.ok()?; + let config = FORMATETC { + cfFormat: CF_HDROP.0, + ptd: std::ptr::null_mut() as _, + dwAspect: DVASPECT_CONTENT.0, + lindex: -1, + tymed: TYMED_HGLOBAL.0 as _, + }; + let cursor_position = POINT { x: pt.x, y: pt.y }; + if idata_obj.QueryGetData(&config as _) == S_OK { + *pdweffect = DROPEFFECT_COPY; + let Some(mut idata) = idata_obj.GetData(&config as _).log_err() else { + return Ok(()); + }; + if idata.u.hGlobal.is_invalid() { + return Ok(()); + } + let hdrop = HDROP(idata.u.hGlobal.0); + let mut paths = SmallVec::<[PathBuf; 2]>::new(); + with_file_names(hdrop, |file_name| { + if let Some(path) = PathBuf::from_str(&file_name).log_err() { + paths.push(path); + } + }); + ReleaseStgMedium(&mut idata); + let mut cursor_position = cursor_position; + ScreenToClient(self.0.hwnd, &mut cursor_position) + .ok() + .log_err(); + let scale_factor = self.0.state.scale_factor.get(); + let input = PlatformInput::FileDrop(FileDropEvent::Entered { + position: logical_point( + cursor_position.x as f32, + cursor_position.y as f32, + scale_factor, + ), + paths: ExternalPaths(paths), + }); + self.handle_drag_drop(input); + } else { + *pdweffect = DROPEFFECT_NONE; + } + self.0 + .drop_target_helper + .DragEnter(self.0.hwnd, idata_obj, &cursor_position, *pdweffect) + .log_err(); + } + Ok(()) + } + + fn DragOver( + &self, + _grfkeystate: MODIFIERKEYS_FLAGS, + pt: &POINTL, + pdweffect: *mut DROPEFFECT, + ) -> windows::core::Result<()> { + let mut cursor_position = POINT { x: pt.x, y: pt.y }; + unsafe { + *pdweffect = DROPEFFECT_COPY; + self.0 + .drop_target_helper + .DragOver(&cursor_position, *pdweffect) + .log_err(); + ScreenToClient(self.0.hwnd, &mut cursor_position) + .ok() + .log_err(); + } + let scale_factor = self.0.state.scale_factor.get(); + let input = PlatformInput::FileDrop(FileDropEvent::Pending { + position: logical_point( + cursor_position.x as f32, + cursor_position.y as f32, + scale_factor, + ), + }); + self.handle_drag_drop(input); + + Ok(()) + } + + fn DragLeave(&self) -> windows::core::Result<()> { + unsafe { + self.0.drop_target_helper.DragLeave().log_err(); + } + let input = PlatformInput::FileDrop(FileDropEvent::Exited); + self.handle_drag_drop(input); + + Ok(()) + } + + fn Drop( + &self, + pdataobj: windows::core::Ref, + _grfkeystate: MODIFIERKEYS_FLAGS, + pt: &POINTL, + pdweffect: *mut DROPEFFECT, + ) -> windows::core::Result<()> { + let idata_obj = pdataobj.ok()?; + let mut cursor_position = POINT { x: pt.x, y: pt.y }; + unsafe { + *pdweffect = DROPEFFECT_COPY; + self.0 + .drop_target_helper + .Drop(idata_obj, &cursor_position, *pdweffect) + .log_err(); + ScreenToClient(self.0.hwnd, &mut cursor_position) + .ok() + .log_err(); + } + let scale_factor = self.0.state.scale_factor.get(); + let input = PlatformInput::FileDrop(FileDropEvent::Submit { + position: logical_point( + cursor_position.x as f32, + cursor_position.y as f32, + scale_factor, + ), + }); + self.handle_drag_drop(input); + + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct ClickState { + button: Cell, + last_click: Cell, + last_position: Cell>, + double_click_spatial_tolerance_width: Cell, + double_click_spatial_tolerance_height: Cell, + double_click_interval: Cell, + pub(crate) current_count: Cell, +} + +impl ClickState { + pub fn new() -> Self { + let double_click_spatial_tolerance_width = unsafe { GetSystemMetrics(SM_CXDOUBLECLK) }; + let double_click_spatial_tolerance_height = unsafe { GetSystemMetrics(SM_CYDOUBLECLK) }; + let double_click_interval = Duration::from_millis(unsafe { GetDoubleClickTime() } as u64); + + ClickState { + button: Cell::new(MouseButton::Left), + last_click: Cell::new(Instant::now()), + last_position: Cell::new(Point::default()), + double_click_spatial_tolerance_width: Cell::new(double_click_spatial_tolerance_width), + double_click_spatial_tolerance_height: Cell::new(double_click_spatial_tolerance_height), + double_click_interval: Cell::new(double_click_interval), + current_count: Cell::new(0), + } + } + + /// update self and return the needed click count + pub fn update(&self, button: MouseButton, new_position: Point) -> usize { + if self.button.get() == button && self.is_double_click(new_position) { + self.current_count.update(|it| it + 1); + } else { + self.current_count.set(1); + } + self.last_click.set(Instant::now()); + self.last_position.set(new_position); + self.button.set(button); + + self.current_count.get() + } + + pub fn system_update(&self, wparam: usize) { + match wparam { + // SPI_SETDOUBLECLKWIDTH + 29 => self + .double_click_spatial_tolerance_width + .set(unsafe { GetSystemMetrics(SM_CXDOUBLECLK) }), + // SPI_SETDOUBLECLKHEIGHT + 30 => self + .double_click_spatial_tolerance_height + .set(unsafe { GetSystemMetrics(SM_CYDOUBLECLK) }), + // SPI_SETDOUBLECLICKTIME + 32 => self + .double_click_interval + .set(Duration::from_millis(unsafe { GetDoubleClickTime() } as u64)), + _ => {} + } + } + + #[inline] + fn is_double_click(&self, new_position: Point) -> bool { + let diff = self.last_position.get() - new_position; + + self.last_click.get().elapsed() < self.double_click_interval.get() + && diff.x.0.abs() <= self.double_click_spatial_tolerance_width.get() + && diff.y.0.abs() <= self.double_click_spatial_tolerance_height.get() + } +} + +#[derive(Copy, Clone)] +struct StyleAndBounds { + style: WINDOW_STYLE, + x: i32, + y: i32, + cx: i32, + cy: i32, +} + +#[repr(C)] +struct WINDOWCOMPOSITIONATTRIBDATA { + attrib: u32, + pv_data: *mut std::ffi::c_void, + cb_data: usize, +} + +#[repr(C)] +struct AccentPolicy { + accent_state: u32, + accent_flags: u32, + gradient_color: u32, + animation_id: u32, +} + +type Color = (u8, u8, u8, u8); + +#[derive(Debug, Default, Clone)] +pub(crate) struct WindowBorderOffset { + pub(crate) width_offset: Cell, + pub(crate) height_offset: Cell, +} + +impl WindowBorderOffset { + pub(crate) fn update(&self, hwnd: HWND) -> anyhow::Result<()> { + let window_rect = unsafe { + let mut rect = std::mem::zeroed(); + GetWindowRect(hwnd, &mut rect)?; + rect + }; + let client_rect = unsafe { + let mut rect = std::mem::zeroed(); + GetClientRect(hwnd, &mut rect)?; + rect + }; + self.width_offset + .set((window_rect.right - window_rect.left) - (client_rect.right - client_rect.left)); + self.height_offset + .set((window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top)); + Ok(()) + } +} + +#[derive(Clone)] +struct WindowOpenStatus { + placement: WINDOWPLACEMENT, + state: WindowOpenState, +} + +#[derive(Clone, Copy)] +enum WindowOpenState { + Maximized, + Fullscreen, + Windowed, +} + +const WINDOW_CLASS_NAME: PCWSTR = w!("Zed::Window"); + +fn register_window_class(icon_handle: HICON) { + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + let wc = WNDCLASSW { + lpfnWndProc: Some(window_procedure), + hIcon: icon_handle, + lpszClassName: PCWSTR(WINDOW_CLASS_NAME.as_ptr()), + style: CS_HREDRAW | CS_VREDRAW, + hInstance: get_module_handle().into(), + hbrBackground: unsafe { CreateSolidBrush(COLORREF(0x00000000)) }, + ..Default::default() + }; + unsafe { RegisterClassW(&wc) }; + }); +} + +unsafe extern "system" fn window_procedure( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + if msg == WM_NCCREATE { + let window_params = unsafe { &*(lparam.0 as *const CREATESTRUCTW) }; + let window_creation_context = window_params.lpCreateParams as *mut WindowCreateContext; + let window_creation_context = unsafe { &mut *window_creation_context }; + return match WindowsWindowInner::new(window_creation_context, hwnd, window_params) { + Ok(window_state) => { + let weak = Box::new(Rc::downgrade(&window_state)); + unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) }; + window_creation_context.inner = Some(Ok(window_state)); + unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } + } + Err(error) => { + window_creation_context.inner = Some(Err(error)); + LRESULT(0) + } + }; + } + + let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; + if ptr.is_null() { + return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }; + } + let inner = unsafe { &*ptr }; + let result = if let Some(inner) = inner.upgrade() { + if msg == WM_NCDESTROY { + inner.dialog_owner.close(); + } + inner.handle_msg(hwnd, msg, wparam, lparam) + } else { + unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } + }; + + if msg == WM_NCDESTROY { + unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) }; + unsafe { drop(Box::from_raw(ptr)) }; + } + + result +} + +pub(crate) fn window_from_hwnd(hwnd: HWND) -> Option> { + if hwnd.is_invalid() { + return None; + } + + let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; + if !ptr.is_null() { + let inner = unsafe { &*ptr }; + inner.upgrade() + } else { + None + } +} + +fn get_module_handle() -> HMODULE { + unsafe { + let mut h_module = std::mem::zeroed(); + GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + windows::core::w!("ZedModule"), + &mut h_module, + ) + .expect("Unable to get module handle"); // this should never fail + + h_module + } +} + +fn register_drag_drop(window: &Rc) -> Result<()> { + let window_handle = window.hwnd; + let handler = WindowsDragDropHandler(window.clone()); + // The lifetime of `IDropTarget` is handled by Windows, it won't release until + // we call `RevokeDragDrop`. + // So, it's safe to drop it here. + let drag_drop_handler: IDropTarget = handler.into(); + unsafe { + RegisterDragDrop(window_handle, &drag_drop_handler) + .context("unable to register drag-drop event")?; + } + Ok(()) +} + +fn calculate_window_rect(bounds: Bounds, border_offset: &WindowBorderOffset) -> RECT { + // NOTE: + // The reason we're not using `AdjustWindowRectEx()` here is + // that the size reported by this function is incorrect. + // You can test it, and there are similar discussions online. + // See: https://stackoverflow.com/questions/12423584/how-to-set-exact-client-size-for-overlapped-window-winapi + // + // So we manually calculate these values here. + let mut rect = RECT { + left: bounds.left().0, + top: bounds.top().0, + right: bounds.right().0, + bottom: bounds.bottom().0, + }; + let left_offset = border_offset.width_offset.get() / 2; + let top_offset = border_offset.height_offset.get() / 2; + let right_offset = border_offset.width_offset.get() - left_offset; + let bottom_offset = border_offset.height_offset.get() - top_offset; + rect.left -= left_offset; + rect.top -= top_offset; + rect.right += right_offset; + rect.bottom += bottom_offset; + rect +} + +fn calculate_client_rect( + rect: RECT, + border_offset: &WindowBorderOffset, + scale_factor: f32, +) -> Bounds { + let left_offset = border_offset.width_offset.get() / 2; + let top_offset = border_offset.height_offset.get() / 2; + let right_offset = border_offset.width_offset.get() - left_offset; + let bottom_offset = border_offset.height_offset.get() - top_offset; + let left = rect.left + left_offset; + let top = rect.top + top_offset; + let right = rect.right - right_offset; + let bottom = rect.bottom - bottom_offset; + let physical_size = size(DevicePixels(right - left), DevicePixels(bottom - top)); + Bounds { + origin: logical_point(left as f32, top as f32, scale_factor), + size: physical_size.to_pixels(scale_factor), + } +} + +fn retrieve_window_placement( + hwnd: HWND, + display: WindowsDisplay, + initial_bounds: Bounds, + border_offset: &WindowBorderOffset, +) -> Result { + let mut placement = WINDOWPLACEMENT { + length: std::mem::size_of::() as u32, + ..Default::default() + }; + unsafe { GetWindowPlacement(hwnd, &mut placement)? }; + // the bounds may be not inside the display + let bounds = if display.check_given_bounds(initial_bounds) { + initial_bounds + } else { + display.default_bounds() + }; + // `bounds` is expressed in logical pixels for `display`, so it must be converted + // to device pixels using that display's own scale factor. The window's current + // scale factor can't be used here: `CreateWindowExW` was called with + // `CW_USEDEFAULT`, so at this point the window may still be sitting on whichever + // monitor Windows picked by default, which can have a different DPI than `display` + // and would otherwise throw off the physical position (e.g. leaving the window + // partially off-screen when moved to a monitor with a different scale factor). + let bounds = bounds.to_device_pixels(display.scale_factor()); + placement.rcNormalPosition = calculate_window_rect(bounds, border_offset); + Ok(placement) +} + +fn dwm_set_window_composition_attribute(hwnd: HWND, backdrop_type: u32) { + let mut version = unsafe { std::mem::zeroed() }; + let status = unsafe { windows::Wdk::System::SystemServices::RtlGetVersion(&mut version) }; + + // DWMWA_SYSTEMBACKDROP_TYPE is available only on version 22621 or later + // using SetWindowCompositionAttributeType as a fallback + if !status.is_ok() || version.dwBuildNumber < 22621 { + return; + } + + unsafe { + let result = DwmSetWindowAttribute( + hwnd, + DWMWA_SYSTEMBACKDROP_TYPE, + &backdrop_type as *const _ as *const _, + std::mem::size_of_val(&backdrop_type) as u32, + ); + + if !result.is_ok() { + return; + } + } +} + +fn set_window_composition_attribute(hwnd: HWND, color: Option, state: u32) { + let mut version = unsafe { std::mem::zeroed() }; + let status = unsafe { windows::Wdk::System::SystemServices::RtlGetVersion(&mut version) }; + + if !status.is_ok() || version.dwBuildNumber < 17763 { + return; + } + + unsafe { + type SetWindowCompositionAttributeType = + unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL; + let module_name = PCSTR::from_raw(c"user32.dll".as_ptr() as *const u8); + if let Some(user32) = GetModuleHandleA(module_name) + .context("Unable to get user32.dll handle") + .log_err() + { + let func_name = PCSTR::from_raw(c"SetWindowCompositionAttribute".as_ptr() as *const u8); + let Some(raw_set_window_composition_attribute) = GetProcAddress(user32, func_name) + else { + return; + }; + let set_window_composition_attribute: SetWindowCompositionAttributeType = + std::mem::transmute(raw_set_window_composition_attribute); + let mut color = color.unwrap_or_default(); + let is_acrylic = state == 4; + if is_acrylic && color.3 == 0 { + color.3 = 1; + } + let accent = AccentPolicy { + accent_state: state, + accent_flags: if is_acrylic { 0 } else { 2 }, + gradient_color: (color.0 as u32) + | ((color.1 as u32) << 8) + | ((color.2 as u32) << 16) + | ((color.3 as u32) << 24), + animation_id: 0, + }; + let mut data = WINDOWCOMPOSITIONATTRIBDATA { + attrib: 0x13, + pv_data: &accent as *const _ as *mut _, + cb_data: std::mem::size_of::(), + }; + let _ = set_window_composition_attribute(hwnd, &mut data as *mut _ as _); + } + } +} + +// When the platform title bar is hidden, Windows may think that our application is meant to appear 'fullscreen' +// and will stop the taskbar from appearing on top of our window. Prevent this. +// https://devblogs.microsoft.com/oldnewthing/20250522-00/?p=111211 +fn set_non_rude_hwnd(hwnd: HWND, non_rude: bool) { + if non_rude { + unsafe { SetPropW(hwnd, w!("NonRudeHWND"), Some(HANDLE(1 as _))) }.log_err(); + } else { + unsafe { RemovePropW(hwnd, w!("NonRudeHWND")) }.log_err(); + } +} + +#[cfg(test)] +mod tests { + use super::ClickState; + use gpui::{DevicePixels, MouseButton, point}; + use std::time::Duration; + + #[test] + fn test_double_click_interval() { + let state = ClickState::new(); + assert_eq!( + state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))), + 1 + ); + assert_eq!( + state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))), + 1 + ); + assert_eq!( + state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))), + 1 + ); + assert_eq!( + state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))), + 2 + ); + state + .last_click + .update(|it| it - Duration::from_millis(700)); + assert_eq!( + state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))), + 1 + ); + } + + #[test] + fn test_double_click_spatial_tolerance() { + let state = ClickState::new(); + assert_eq!( + state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))), + 1 + ); + assert_eq!( + state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))), + 2 + ); + assert_eq!( + state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))), + 1 + ); + assert_eq!( + state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))), + 1 + ); + } +} diff --git a/vendor/gpui-pre-windows/src/wrapper.rs b/vendor/gpui-pre-windows/src/wrapper.rs new file mode 100644 index 0000000..60bbc43 --- /dev/null +++ b/vendor/gpui-pre-windows/src/wrapper.rs @@ -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 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 for SafeHwnd { + fn from(value: HWND) -> Self { + SafeHwnd { raw: value } + } +} + +impl Deref for SafeHwnd { + type Target = HWND; + + fn deref(&self) -> &Self::Target { + &self.raw + } +}