winit-0.30.9/.cargo_vcs_info.json0000644000000001360000000000100122570ustar { "git": { "sha1": "1ae4f5cdeab1231d14b24bc515dc14ef2ee69ece" }, "path_in_vcs": "" }winit-0.30.9/Cargo.lock0000644000001571720000000000100102470ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ab_glyph" version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", ] [[package]] name = "ab_glyph_rasterizer" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", "zerocopy", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "android-activity" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", "bitflags 2.6.0", "cc", "cesu8", "jni", "jni-sys", "libc", "log", "ndk", "ndk-context", "ndk-sys", "num_enum", "thiserror", ] [[package]] name = "android-properties" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[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-raw-xcb-connection" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block2" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ "objc2", ] [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[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.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "calloop" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags 2.6.0", "log", "polling", "rustix", "slab", "thiserror", ] [[package]] name = "calloop-wayland-source" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", "rustix", "wayland-backend", "wayland-client", ] [[package]] name = "cc" version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" dependencies = [ "jobserver", "libc", "shlex", ] [[package]] name = "cesu8" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[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 = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", ] [[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 = "console_error_panic_hook" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ "cfg-if", "wasm-bindgen", ] [[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.6.0", "core-foundation 0.10.0", "core-graphics-types 0.2.0", "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.6.0", "core-foundation 0.10.0", "libc", ] [[package]] name = "core-text" version = "20.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" dependencies = [ "core-foundation 0.9.4", "core-graphics 0.23.2", "foreign-types", "libc", ] [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crossfont" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c44e28d120f3c9254800ea53349b09cbb45ac1f15f09215011a16241ae0289bc" dependencies = [ "cocoa", "core-foundation 0.9.4", "core-foundation-sys", "core-graphics 0.23.2", "core-text", "dwrote", "foreign-types", "freetype-rs", "libc", "log", "objc", "once_cell", "pkg-config", "winapi", "yeslogic-fontconfig-sys", ] [[package]] name = "cstr" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "ctor-lite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" [[package]] name = "cty" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "cursor-icon" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" dependencies = [ "serde", ] [[package]] name = "dispatch" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dlib" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ "libloading", ] [[package]] name = "downcast-rs" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" dependencies = [ "mint", "serde", ] [[package]] name = "dwrote" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da3498378ed373237bdef1eddcc64e7be2d3ba4841f4c22a998e81cadeea83c" dependencies = [ "lazy_static", "libc", "serde", "serde_derive", "winapi", "wio", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fdeflate" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", ] [[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", ] [[package]] name = "foreign-types-shared" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "freetype-rs" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5442dee36ca09604133580dc0553780e867936bb3cbef3275859e889026d2b17" dependencies = [ "bitflags 2.6.0", "freetype-sys", "libc", ] [[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 = "gethostname" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ "libc", "windows-targets 0.48.5", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "image" version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" dependencies = [ "bytemuck", "byteorder-lite", "num-traits", "png", ] [[package]] name = "indexmap" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "jni" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", "cfg-if", "combine", "jni-sys", "log", "thiserror", "walkdir", "windows-sys 0.45.0", ] [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libloading" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets 0.52.6", ] [[package]] name = "libredox" version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ "bitflags 2.6.0", "libc", "redox_syscall 0.4.1", ] [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "malloc_buf" version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" dependencies = [ "libc", ] [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata 0.1.10", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] [[package]] name = "miniz_oxide" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "mint" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" [[package]] name = "ndk" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ "bitflags 2.6.0", "jni-sys", "log", "ndk-sys", "num_enum", "raw-window-handle 0.4.3", "raw-window-handle 0.5.2", "raw-window-handle 0.6.2", "thiserror", ] [[package]] name = "ndk-context" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] name = "ndk-sys" version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ "jni-sys", ] [[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", "winapi", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "num_enum" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "objc" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", ] [[package]] name = "objc-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] name = "objc2" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ "objc-sys", "objc2-encode", ] [[package]] name = "objc2-app-kit" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ "bitflags 2.6.0", "block2", "libc", "objc2", "objc2-core-data", "objc2-core-image", "objc2-foundation", "objc2-quartz-core", ] [[package]] name = "objc2-cloud-kit" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.6.0", "block2", "objc2", "objc2-core-location", "objc2-foundation", ] [[package]] name = "objc2-contacts" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ "block2", "objc2", "objc2-foundation", ] [[package]] name = "objc2-core-data" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.6.0", "block2", "objc2", "objc2-foundation", ] [[package]] name = "objc2-core-image" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ "block2", "objc2", "objc2-foundation", "objc2-metal", ] [[package]] name = "objc2-core-location" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ "block2", "objc2", "objc2-contacts", "objc2-foundation", ] [[package]] name = "objc2-encode" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.6.0", "block2", "dispatch", "libc", "objc2", ] [[package]] name = "objc2-link-presentation" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2", "objc2", "objc2-app-kit", "objc2-foundation", ] [[package]] name = "objc2-metal" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.6.0", "block2", "objc2", "objc2-foundation", ] [[package]] name = "objc2-quartz-core" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.6.0", "block2", "objc2", "objc2-foundation", "objc2-metal", ] [[package]] name = "objc2-symbols" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ "objc2", "objc2-foundation", ] [[package]] name = "objc2-ui-kit" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.6.0", "block2", "objc2", "objc2-cloud-kit", "objc2-core-data", "objc2-core-image", "objc2-core-location", "objc2-foundation", "objc2-link-presentation", "objc2-quartz-core", "objc2-symbols", "objc2-uniform-type-identifiers", "objc2-user-notifications", ] [[package]] name = "objc2-uniform-type-identifiers" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ "block2", "objc2", "objc2-foundation", ] [[package]] name = "objc2-user-notifications" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.6.0", "block2", "objc2", "objc2-core-location", "objc2-foundation", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "orbclient" version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ "libredox", ] [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owned_ttf_parser" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90" dependencies = [ "ttf-parser", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "polling" version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", "rustix", "tracing", "windows-sys 0.59.0", ] [[package]] name = "proc-macro-crate" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "raw-window-handle" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" dependencies = [ "cty", ] [[package]] name = "raw-window-handle" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c178f952cc7eac391f3124bd9851d1ac0bdbc4c9de2d892ccd5f0d8b160e96" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.7", "regex-syntax 0.8.4", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax 0.8.4", ] [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustix" version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[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 = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "sctk-adwaita" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "crossfont", "log", "memmap2", "smithay-client-toolkit", "tiny-skia", ] [[package]] name = "serde" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", "syn", ] [[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 = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay-client-toolkit" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags 2.6.0", "calloop", "calloop-wayland-source", "cursor-icon", "libc", "log", "memmap2", "rustix", "thiserror", "wayland-backend", "wayland-client", "wayland-csd-frame", "wayland-cursor", "wayland-protocols", "wayland-protocols-wlr", "wayland-scanner", "xkeysym", ] [[package]] name = "smol_str" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" dependencies = [ "serde", ] [[package]] name = "softbuffer" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" dependencies = [ "as-raw-xcb-connection", "bytemuck", "cfg_aliases", "core-graphics 0.24.0", "fastrand", "foreign-types", "js-sys", "log", "memmap2", "objc2", "objc2-foundation", "objc2-quartz-core", "raw-window-handle 0.6.2", "redox_syscall 0.5.0", "rustix", "tiny-xlib", "wasm-bindgen", "wayland-backend", "wayland-client", "wayland-sys", "web-sys", "windows-sys 0.59.0", "x11rb", ] [[package]] name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" [[package]] name = "syn" version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[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", "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 = "tiny-xlib" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d52f22673960ad13af14ff4025997312def1223bfa7c8e4949d099e6b3d5d1c" dependencies = [ "as-raw-xcb-connection", "ctor-lite", "libloading", "pkg-config", "tracing", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 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.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[package]] name = "tracing-web" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e6a141feebd51f8d91ebfd785af50fca223c570b86852166caa3b141defe7c" dependencies = [ "js-sys", "tracing-core", "tracing-subscriber", "wasm-bindgen", "web-sys", ] [[package]] name = "ttf-parser" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[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.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wayland-backend" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", "rustix", "scoped-tls", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" dependencies = [ "bitflags 2.6.0", "rustix", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-csd-frame" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ "bitflags 2.6.0", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a94697e66e76c85923b0d28a0c251e8f0666f58fc47d316c0f4da6da75d37cb" dependencies = [ "rustix", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" version = "0.32.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" dependencies = [ "bitflags 2.6.0", "wayland-backend", "wayland-client", "wayland-scanner", ] [[package]] name = "wayland-protocols-plasma" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0a41a6875e585172495f7a96dfa42ca7e0213868f4f15c313f7c33221a7eff" dependencies = [ "bitflags 2.6.0", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-protocols-wlr" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dad87b5fd1b1d3ca2f792df8f686a2a11e3fe1077b71096f7a175ab699f89109" dependencies = [ "bitflags 2.6.0", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", "quick-xml", "quote", ] [[package]] name = "wayland-sys" version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" dependencies = [ "dlib", "log", "once_cell", "pkg-config", ] [[package]] name = "web-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", ] [[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 = "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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[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-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[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-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[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_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[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 = "winit" version = "0.30.9" dependencies = [ "ahash", "android-activity", "atomic-waker", "bitflags 2.6.0", "block2", "bytemuck", "calloop", "cfg_aliases", "concurrent-queue", "console_error_panic_hook", "core-foundation 0.9.4", "core-graphics 0.23.2", "cursor-icon", "dpi", "image", "js-sys", "libc", "memmap2", "ndk", "objc2", "objc2-app-kit", "objc2-foundation", "objc2-ui-kit", "orbclient", "percent-encoding", "pin-project", "raw-window-handle 0.4.3", "raw-window-handle 0.5.2", "raw-window-handle 0.6.2", "redox_syscall 0.4.1", "rustix", "sctk-adwaita", "serde", "smithay-client-toolkit", "smol_str", "softbuffer", "tracing", "tracing-subscriber", "tracing-web", "unicode-segmentation", "wasm-bindgen", "wasm-bindgen-futures", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-protocols-plasma", "web-sys", "web-time", "windows-sys 0.52.0", "x11-dl", "x11rb", "xkbcommon-dl", ] [[package]] name = "winnow" version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] [[package]] name = "wio" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" dependencies = [ "winapi", ] [[package]] name = "x11-dl" version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ "libc", "once_cell", "pkg-config", ] [[package]] name = "x11rb" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", "libloading", "once_cell", "rustix", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" [[package]] name = "xkbcommon-dl" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ "bitflags 2.6.0", "dlib", "log", "once_cell", "xkeysym", ] [[package]] name = "xkeysym" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "yeslogic-fontconfig-sys" version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb6b23999a8b1a997bf47c7bb4d19ad4029c3327bb3386ebe0a5ff584b33c7a" dependencies = [ "cstr", "dlib", "once_cell", "pkg-config", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] winit-0.30.9/Cargo.toml0000644000000324750000000000100102700ustar # 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 = "2021" rust-version = "1.70.0" name = "winit" version = "0.30.9" authors = [ "The winit contributors", "Pierre Krieger ", ] build = "build.rs" include = [ "/build.rs", "/docs", "/examples", "/FEATURES.md", "/LICENSE", "/src", "!/src/platform_impl/web/script", "/src/platform_impl/web/script/**/*.min.js", "/tests", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Cross-platform window creation library." documentation = "https://docs.rs/winit" readme = "README.md" keywords = ["windowing"] categories = ["gui"] license = "Apache-2.0" repository = "https://github.com/rust-windowing/winit" [package.metadata.docs.rs] features = [ "rwh_04", "rwh_05", "rwh_06", "serde", "mint", "android-native-activity", ] rustdoc-args = [ "--cfg", "docsrs", ] targets = [ "i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "x86_64-apple-darwin", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-ios", "aarch64-linux-android", "wasm32-unknown-unknown", ] [lib] name = "winit" path = "src/lib.rs" [[example]] name = "child_window" path = "examples/child_window.rs" [[example]] name = "control_flow" path = "examples/control_flow.rs" [[example]] name = "pump_events" path = "examples/pump_events.rs" [[example]] name = "run_on_demand" path = "examples/run_on_demand.rs" [[example]] name = "window" path = "examples/window.rs" doc-scrape-examples = true [[example]] name = "x11_embed" path = "examples/x11_embed.rs" [[test]] name = "send_objects" path = "tests/send_objects.rs" [[test]] name = "serde_objects" path = "tests/serde_objects.rs" [[test]] name = "sync_object" path = "tests/sync_object.rs" [dependencies.bitflags] version = "2" [dependencies.cursor-icon] version = "1.1.0" [dependencies.dpi] version = "0.1.1" [dependencies.rwh_04] version = "0.4" optional = true package = "raw-window-handle" [dependencies.rwh_05] version = "0.5.2" features = ["std"] optional = true package = "raw-window-handle" [dependencies.rwh_06] version = "0.6" features = ["std"] optional = true package = "raw-window-handle" [dependencies.serde] version = "1" features = ["serde_derive"] optional = true [dependencies.smol_str] version = "0.2.0" [dependencies.tracing] version = "0.1.40" default-features = false [dev-dependencies.image] version = "0.25.0" features = ["png"] default-features = false [dev-dependencies.tracing] version = "0.1.40" features = ["log"] default-features = false [dev-dependencies.tracing-subscriber] version = "0.3.18" features = ["env-filter"] [build-dependencies.cfg_aliases] version = "0.2.1" [features] android-game-activity = ["android-activity/game-activity"] android-native-activity = ["android-activity/native-activity"] default = [ "rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita", ] mint = ["dpi/mint"] rwh_04 = [ "dep:rwh_04", "ndk/rwh_04", ] rwh_05 = [ "dep:rwh_05", "ndk/rwh_05", ] rwh_06 = [ "dep:rwh_06", "ndk/rwh_06", ] serde = [ "dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde", ] wayland = [ "wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2", ] wayland-csd-adwaita = [ "sctk-adwaita", "sctk-adwaita/ab_glyph", ] wayland-csd-adwaita-crossfont = [ "sctk-adwaita", "sctk-adwaita/crossfont", ] wayland-csd-adwaita-notitle = ["sctk-adwaita"] wayland-dlopen = ["wayland-backend/dlopen"] x11 = [ "x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb", ] [target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies.atomic-waker] version = "1" [target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies.concurrent-queue] version = "2" default-features = false [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.ahash] version = "0.8.7" features = ["no-rng"] optional = true [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.bytemuck] version = "1.13.1" optional = true default-features = false [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.calloop] version = "0.13.0" [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.libc] version = "0.2.64" [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.memmap2] version = "0.9.0" optional = true [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.percent-encoding] version = "2.0" optional = true [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.rustix] version = "0.38.4" features = [ "std", "system", "thread", "process", ] default-features = false [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.sctk] version = "0.19.2" features = ["calloop"] optional = true default-features = false package = "smithay-client-toolkit" [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.sctk-adwaita] version = "0.10.1" optional = true default-features = false [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.wayland-backend] version = "0.3.5" features = ["client_system"] optional = true default-features = false [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.wayland-client] version = "0.31.4" optional = true [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.wayland-protocols] version = "0.32.2" features = ["staging"] optional = true [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.wayland-protocols-plasma] version = "0.3.2" features = ["client"] optional = true [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.x11-dl] version = "2.19.1" optional = true [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.x11rb] version = "0.13.0" features = [ "allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb", ] optional = true default-features = false [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies.xkbcommon-dl] version = "0.4.2" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies.core-foundation] version = "0.9.3" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies.objc2] version = "0.5.2" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies.softbuffer] version = "0.4.0" features = [ "x11", "x11-dlopen", "wayland", "wayland-dlopen", ] default-features = false [target.'cfg(target_family = "wasm")'.dependencies.js-sys] version = "0.3.70" [target.'cfg(target_family = "wasm")'.dependencies.pin-project] version = "1" [target.'cfg(target_family = "wasm")'.dependencies.wasm-bindgen] version = "0.2.93" [target.'cfg(target_family = "wasm")'.dependencies.wasm-bindgen-futures] version = "0.4.43" [target.'cfg(target_family = "wasm")'.dependencies.web-time] version = "1" [target.'cfg(target_family = "wasm")'.dependencies.web_sys] version = "0.3.70" features = [ "AbortController", "AbortSignal", "Blob", "BlobPropertyBag", "console", "CssStyleDeclaration", "Document", "DomException", "DomRect", "DomRectReadOnly", "Element", "Event", "EventTarget", "FocusEvent", "HtmlCanvasElement", "HtmlElement", "HtmlImageElement", "ImageBitmap", "ImageBitmapOptions", "ImageBitmapRenderingContext", "ImageData", "IntersectionObserver", "IntersectionObserverEntry", "KeyboardEvent", "MediaQueryList", "MessageChannel", "MessagePort", "Navigator", "Node", "OrientationLockType", "OrientationType", "PageTransitionEvent", "Permissions", "PermissionState", "PermissionStatus", "PointerEvent", "PremultiplyAlpha", "ResizeObserver", "ResizeObserverBoxOptions", "ResizeObserverEntry", "ResizeObserverOptions", "ResizeObserverSize", "Screen", "ScreenOrientation", "Url", "VisibilityState", "WheelEvent", "Window", "Worker", ] package = "web-sys" [target.'cfg(target_family = "wasm")'.dev-dependencies.console_error_panic_hook] version = "0.1" [target.'cfg(target_family = "wasm")'.dev-dependencies.tracing-web] version = "0.1" [target.'cfg(target_os = "android")'.dependencies.android-activity] version = "0.6.0" [target.'cfg(target_os = "android")'.dependencies.ndk] version = "0.9.0" default-features = false [target.'cfg(target_os = "ios")'.dependencies.objc2-foundation] version = "0.2.2" features = [ "dispatch", "NSArray", "NSEnumerator", "NSGeometry", "NSObjCRuntime", "NSString", "NSProcessInfo", "NSThread", "NSSet", ] [target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit] version = "0.2.2" features = [ "UIApplication", "UIDevice", "UIEvent", "UIGeometry", "UIGestureRecognizer", "UITextInput", "UITextInputTraits", "UIOrientation", "UIPanGestureRecognizer", "UIPinchGestureRecognizer", "UIResponder", "UIRotationGestureRecognizer", "UIScreen", "UIScreenMode", "UITapGestureRecognizer", "UITouch", "UITraitCollection", "UIView", "UIViewController", "UIWindow", ] [target.'cfg(target_os = "macos")'.dependencies.block2] version = "0.5.1" [target.'cfg(target_os = "macos")'.dependencies.core-graphics] version = "0.23.1" [target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit] version = "0.2.2" features = [ "NSAppearance", "NSApplication", "NSBitmapImageRep", "NSButton", "NSColor", "NSControl", "NSCursor", "NSDragging", "NSEvent", "NSGraphics", "NSGraphicsContext", "NSImage", "NSImageRep", "NSMenu", "NSMenuItem", "NSOpenGLView", "NSPasteboard", "NSResponder", "NSRunningApplication", "NSScreen", "NSTextInputClient", "NSTextInputContext", "NSView", "NSWindow", "NSWindowScripting", "NSWindowTabGroup", ] [target.'cfg(target_os = "macos")'.dependencies.objc2-foundation] version = "0.2.2" features = [ "block2", "dispatch", "NSArray", "NSAttributedString", "NSData", "NSDictionary", "NSDistributedNotificationCenter", "NSEnumerator", "NSKeyValueObserving", "NSNotification", "NSObjCRuntime", "NSPathUtilities", "NSProcessInfo", "NSRunLoop", "NSString", "NSThread", "NSValue", ] [target.'cfg(target_os = "redox")'.dependencies.orbclient] version = "0.3.47" default-features = false [target.'cfg(target_os = "redox")'.dependencies.redox_syscall] version = "0.4.1" [target.'cfg(target_os = "windows")'.dependencies.unicode-segmentation] version = "1.7.1" [target.'cfg(target_os = "windows")'.dependencies.windows-sys] version = "0.52.0" features = [ "Win32_Devices_HumanInterfaceDevice", "Win32_Foundation", "Win32_Globalization", "Win32_Graphics_Dwm", "Win32_Graphics_Gdi", "Win32_Media", "Win32_System_Com_StructuredStorage", "Win32_System_Com", "Win32_System_LibraryLoader", "Win32_System_Ole", "Win32_Security", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming", "Win32_UI_Accessibility", "Win32_UI_Controls", "Win32_UI_HiDpi", "Win32_UI_Input_Ime", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Input_Pointer", "Win32_UI_Input_Touch", "Win32_UI_Shell", "Win32_UI_TextServices", "Win32_UI_WindowsAndMessaging", ] winit-0.30.9/Cargo.toml.orig000064400000000000000000000230361046102023000137420ustar 00000000000000[package] name = "winit" version = "0.30.9" authors = [ "The winit contributors", "Pierre Krieger ", ] description = "Cross-platform window creation library." keywords = ["windowing"] readme = "README.md" documentation = "https://docs.rs/winit" categories = ["gui"] rust-version.workspace = true repository.workspace = true license.workspace = true edition.workspace = true include = [ "/build.rs", "/docs", "/examples", "/FEATURES.md", "/LICENSE", "/src", "!/src/platform_impl/web/script", "/src/platform_impl/web/script/**/*.min.js", "/tests", ] [package.metadata.docs.rs] features = [ "rwh_04", "rwh_05", "rwh_06", "serde", "mint", # Enabled to get docs to compile "android-native-activity", ] # These are all tested in CI targets = [ # Windows "i686-pc-windows-msvc", "x86_64-pc-windows-msvc", # macOS "x86_64-apple-darwin", # Unix (X11 & Wayland) "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", # iOS "x86_64-apple-ios", # Android "aarch64-linux-android", # Web "wasm32-unknown-unknown", ] rustdoc-args = ["--cfg", "docsrs"] # Features are documented in either `lib.rs` or under `winit::platform`. [features] default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"] wayland = [ "wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2", ] wayland-dlopen = ["wayland-backend/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"] wayland-csd-adwaita-notitle = ["sctk-adwaita"] android-native-activity = ["android-activity/native-activity"] android-game-activity = ["android-activity/game-activity"] serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde"] mint = ["dpi/mint"] rwh_04 = ["dep:rwh_04", "ndk/rwh_04"] rwh_05 = ["dep:rwh_05", "ndk/rwh_05"] rwh_06 = ["dep:rwh_06", "ndk/rwh_06"] [build-dependencies] cfg_aliases = "0.2.1" [dependencies] bitflags = "2" cursor-icon = "1.1.0" dpi = { version = "0.1.1", path = "dpi" } rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true } rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = [ "std", ], optional = true } rwh_06 = { package = "raw-window-handle", version = "0.6", features = [ "std", ], optional = true } serde = { workspace = true, optional = true } smol_str = "0.2.0" tracing = { version = "0.1.40", default-features = false } [dev-dependencies] image = { version = "0.25.0", default-features = false, features = ["png"] } tracing = { version = "0.1.40", default-features = false, features = ["log"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } winit = { path = ".", features = ["rwh_05"] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies] softbuffer = { version = "0.4.0", default-features = false, features = [ "x11", "x11-dlopen", "wayland", "wayland-dlopen", ] } [target.'cfg(target_os = "android")'.dependencies] android-activity = "0.6.0" ndk = { version = "0.9.0", default-features = false } [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] core-foundation = "0.9.3" objc2 = "0.5.2" [target.'cfg(target_os = "macos")'.dependencies] core-graphics = "0.23.1" block2 = "0.5.1" [target.'cfg(target_os = "macos")'.dependencies.objc2-foundation] version = "0.2.2" features = [ "block2", "dispatch", "NSArray", "NSAttributedString", "NSData", "NSDictionary", "NSDistributedNotificationCenter", "NSEnumerator", "NSKeyValueObserving", "NSNotification", "NSObjCRuntime", "NSPathUtilities", "NSProcessInfo", "NSRunLoop", "NSString", "NSThread", "NSValue", ] [target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit] version = "0.2.2" features = [ "NSAppearance", "NSApplication", "NSBitmapImageRep", "NSButton", "NSColor", "NSControl", "NSCursor", "NSDragging", "NSEvent", "NSGraphics", "NSGraphicsContext", "NSImage", "NSImageRep", "NSMenu", "NSMenuItem", "NSOpenGLView", "NSPasteboard", "NSResponder", "NSRunningApplication", "NSScreen", "NSTextInputClient", "NSTextInputContext", "NSView", "NSWindow", "NSWindowScripting", "NSWindowTabGroup", ] [target.'cfg(target_os = "ios")'.dependencies.objc2-foundation] version = "0.2.2" features = [ "dispatch", "NSArray", "NSEnumerator", "NSGeometry", "NSObjCRuntime", "NSString", "NSProcessInfo", "NSThread", "NSSet", ] [target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit] version = "0.2.2" features = [ "UIApplication", "UIDevice", "UIEvent", "UIGeometry", "UIGestureRecognizer", "UITextInput", "UITextInputTraits", "UIOrientation", "UIPanGestureRecognizer", "UIPinchGestureRecognizer", "UIResponder", "UIRotationGestureRecognizer", "UIScreen", "UIScreenMode", "UITapGestureRecognizer", "UITouch", "UITraitCollection", "UIView", "UIViewController", "UIWindow", ] [target.'cfg(target_os = "windows")'.dependencies] unicode-segmentation = "1.7.1" [target.'cfg(target_os = "windows")'.dependencies.windows-sys] version = "0.52.0" features = [ "Win32_Devices_HumanInterfaceDevice", "Win32_Foundation", "Win32_Globalization", "Win32_Graphics_Dwm", "Win32_Graphics_Gdi", "Win32_Media", "Win32_System_Com_StructuredStorage", "Win32_System_Com", "Win32_System_LibraryLoader", "Win32_System_Ole", "Win32_Security", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming", "Win32_UI_Accessibility", "Win32_UI_Controls", "Win32_UI_HiDpi", "Win32_UI_Input_Ime", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Input_Pointer", "Win32_UI_Input_Touch", "Win32_UI_Shell", "Win32_UI_TextServices", "Win32_UI_WindowsAndMessaging", ] [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] ahash = { version = "0.8.7", features = ["no-rng"], optional = true } bytemuck = { version = "1.13.1", default-features = false, optional = true } calloop = "0.13.0" libc = "0.2.64" memmap2 = { version = "0.9.0", optional = true } percent-encoding = { version = "2.0", optional = true } rustix = { version = "0.38.4", default-features = false, features = [ "std", "system", "thread", "process", ] } sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [ "calloop", ], optional = true } sctk-adwaita = { version = "0.10.1", default-features = false, optional = true } wayland-backend = { version = "0.3.5", default-features = false, features = [ "client_system", ], optional = true } wayland-client = { version = "0.31.4", optional = true } wayland-protocols = { version = "0.32.2", features = [ "staging", ], optional = true } wayland-protocols-plasma = { version = "0.3.2", features = [ "client", ], optional = true } x11-dl = { version = "2.19.1", optional = true } x11rb = { version = "0.13.0", default-features = false, features = [ "allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb", ], optional = true } xkbcommon-dl = "0.4.2" [target.'cfg(target_os = "redox")'.dependencies] orbclient = { version = "0.3.47", default-features = false } redox_syscall = "0.4.1" [target.'cfg(target_family = "wasm")'.dependencies] js-sys = "0.3.70" pin-project = "1" wasm-bindgen = "0.2.93" wasm-bindgen-futures = "0.4.43" web-time = "1" web_sys = { package = "web-sys", version = "0.3.70", features = [ "AbortController", "AbortSignal", "Blob", "BlobPropertyBag", "console", "CssStyleDeclaration", "Document", "DomException", "DomRect", "DomRectReadOnly", "Element", "Event", "EventTarget", "FocusEvent", "HtmlCanvasElement", "HtmlElement", "HtmlImageElement", "ImageBitmap", "ImageBitmapOptions", "ImageBitmapRenderingContext", "ImageData", "IntersectionObserver", "IntersectionObserverEntry", "KeyboardEvent", "MediaQueryList", "MessageChannel", "MessagePort", "Navigator", "Node", "OrientationLockType", "OrientationType", "PageTransitionEvent", "Permissions", "PermissionState", "PermissionStatus", "PointerEvent", "PremultiplyAlpha", "ResizeObserver", "ResizeObserverBoxOptions", "ResizeObserverEntry", "ResizeObserverOptions", "ResizeObserverSize", "Screen", "ScreenOrientation", "Url", "VisibilityState", "WheelEvent", "Window", "Worker", ] } [target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies] atomic-waker = "1" concurrent-queue = { version = "2", default-features = false } [target.'cfg(target_family = "wasm")'.dev-dependencies] console_error_panic_hook = "0.1" tracing-web = "0.1" [[example]] doc-scrape-examples = true name = "window" [workspace] resolver = "2" members = ["dpi"] [workspace.package] rust-version = "1.70.0" repository = "https://github.com/rust-windowing/winit" license = "Apache-2.0" edition = "2021" [workspace.dependencies] serde = { version = "1", features = ["serde_derive"] } mint = "0.5.6" winit-0.30.9/FEATURES.md000064400000000000000000000357461046102023000126260ustar 00000000000000# Winit Scope Winit aims to expose an interface that abstracts over window creation and input handling and can be used to create both games and applications. It supports the following main graphical platforms: - Desktop - Windows - macOS - Unix - via X11 - via Wayland - Redox OS, via Orbital - Mobile - iOS - Android - Web Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not aim to support every single feature of every platform, but rather to abstract over the common features available everywhere. In this context, APIs exposed in winit can be split into different "support tiers": - **Core:** Features that are essential to providing a well-formed abstraction over each platform's windowing and input APIs. - **Platform:** Platform-specific features that can't be meaningfully exposed through a common API and cannot be implemented outside of Winit without exposing a significant amount of Winit's internals or interfering with Winit's abstractions. - **Usability:** Features that are not strictly essential to Winit's functionality, but provide meaningful usability improvements and cannot be reasonably implemented in an external crate. These are generally optional and exposed through Cargo features. Core features are taken care of by the core Winit maintainers. Platform features are not. When a platform feature is submitted, the submitter is considered the expert in the feature and may be asked to support the feature should it break in the future. Winit ***does not*** directly expose functionality for drawing inside windows or creating native menus, but ***does*** commit to providing APIs that higher-level crates can use to implement that functionality. ## `1.0` and stability When all core features are implemented to the satisfaction of the Winit maintainers, Winit 1.0 will be released and the library will enter maintenance mode. For the most part, new core features will not be added past this point. New platform features may be accepted and exposed through point releases. ### Tier upgrades Some platform features could, in theory, be exposed across multiple platforms, but have not gone through the implementation work necessary to function on all platforms. When one of these features gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature. If that gets accepted, the platform-specific functions get deprecated and become permanently exposed through the core, cross-platform API. # Features ## Extending this section If your PR makes notable changes to Winit's features, please update this section as follows: - If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core feature, add a row to the feature matrix and describe what platforms the feature has been implemented on. - If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the API rework on all relevant platforms, please move it to the `Completed API Reworks` table. - If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*, or mark it as *mostly completed* and link to an issue describing the problems with the implementation. ## Core ### Windowing - **Window initialization**: Winit allows the creation of a window - **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context - **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan - **Window decorations**: The windows created by winit are properly decorated, and the decorations can be deactivated - **Window decorations toggle**: Decorations can be turned on or off after window creation - **Window resizing**: The windows created by winit can be resized and generate the appropriate events when they are. The application can precisely control its window size if desired. - **Window resize increments**: When the window gets resized, the application can choose to snap the window's size to specific values. - **Window transparency**: Winit allows the creation of windows with a transparent background. - **Window maximization**: The windows created by winit can be maximized upon creation. - **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after creation. - **Window minimization**: The windows created by winit can be minimized after creation. - **Fullscreen**: The windows created by winit can be put into fullscreen mode. - **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after creation. - **Exclusive fullscreen**: Winit allows changing the video mode of the monitor for fullscreen windows and, if applicable, captures the monitor for exclusive use by this application. - **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content. - **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent windows can be disabled in favor of popup windows. This feature also guarantees that popup windows get drawn above their owner. ### System Information - **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary. - **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth). ### Input Handling - **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events. - **Mouse set location**: Forcibly changing the location of the pointer. - **Cursor locking**: Locking the cursor inside the window so it cannot move. - **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them. - **Cursor icon**: Changing the cursor icon or hiding the cursor. - **Cursor image**: Changing the cursor to your own image. - **Cursor hittest**: Handle or ignore mouse events for a window. - **Touch events**: Single-touch events. - **Touch pressure**: Touch events contain information about the amount of force being applied. - **Multitouch**: Multi-touch events, including cancellation of a gesture. - **Keyboard events**: Properly processing keyboard events using the user-specified keymap and translating keypresses into UTF-8 characters, handling dead keys and IMEs. - **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled. - **Raw Device Events**: Capturing input from input devices without any OS filtering. - **Gamepad/Joystick events**: Capturing input from gamepads and joysticks. - **Device movement events**: Capturing input from the device gyroscope and accelerometer. ## Platform ### Windows * Setting the name of the internal window class * Setting the taskbar icon * Setting the parent window * Setting a menu bar * `WS_EX_NOREDIRECTIONBITMAP` support * Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme * Changing a system-drawn backdrop * Setting the window border color * Setting the title bar background color * Setting the title color * Setting the corner rounding preference ### macOS * Window activation policy * Window movable by background * Transparent titlebar * Hidden titlebar * Hidden titlebar buttons * Full-size content view * Accepts first mouse * Set a preferred theme and get current theme. ### Unix * Window urgency * X11 Window Class * X11 Override Redirect Flag * GTK Theme Variant * Base window size * Setting the X11 parent window ### iOS * Get the `UIScreen` object pointer * Setting the `UIView` hidpi factor * Valid orientations * Home indicator visibility * Status bar visibility and style * Deferring system gestures * Getting the device idiom * Getting the preferred video mode ### Web * Get if the systems preferred color scheme is "dark" ## Compatibility Matrix Legend: - ✔ï¸: Works as intended - â–¢: Mostly works, but some bugs are known - âŒ: Missing feature or large bugs making it unusable - **N/A**: Not applicable for this platform - â“: Unknown status ### Windowing |Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS| |-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | |Window initialization |âœ”ï¸ |âœ”ï¸ |â–¢[#5] |âœ”ï¸ |â–¢[#33]|â–¢[#33] |âœ”ï¸ |âœ”ï¸ | |Providing pointer to init OpenGL |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|âœ”ï¸ | |Providing pointer to init Vulkan |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |â“ |**N/A**|**N/A** | |Window decorations |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A**|âœ”ï¸ | |Window decorations toggle |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A**|**N/A** | |Window resizing |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|âœ”ï¸ |âœ”ï¸ | |Window resize increments |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |⌠|**N/A**|**N/A**|**N/A**|**N/A** | |Window transparency |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|N/A |âœ”ï¸ | |Window blur |⌠|⌠|⌠|âœ”ï¸ |**N/A**|**N/A**|N/A |⌠| |Window maximization |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A**|**N/A** | |Window maximization toggle |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A**|**N/A** | |Window minimization |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A**|**N/A** | |Fullscreen |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|âœ”ï¸ |âœ”ï¸ |**N/A** | |Fullscreen toggle |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|âœ”ï¸ |âœ”ï¸ |**N/A** | |Exclusive fullscreen |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A** |⌠|âœ”ï¸ |**N/A**|**N/A** | |HiDPI support |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |⌠| |Popup windows |⌠|⌠|⌠|⌠|⌠|⌠|**N/A**|**N/A** | ### System information |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ | |Monitor list |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|⌠| |Video mode query |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|⌠| ### Input handling |Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | |Mouse events |âœ”ï¸ |â–¢[#63] |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|âœ”ï¸ |âœ”ï¸ | |Mouse set location |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |✔ï¸(when locked) |**N/A**|**N/A**|**N/A**|**N/A** | |Cursor locking |⌠|âœ”ï¸ |⌠|âœ”ï¸ |**N/A**|**N/A**|âœ”ï¸ |⌠| |Cursor confining |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|⌠|⌠| |Cursor icon |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|âœ”ï¸ |**N/A** | |Cursor image |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|âœ”ï¸ |**N/A** | |Cursor hittest |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|⌠|⌠| |Touch events |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A** | |Touch pressure |âœ”ï¸ |⌠|⌠|⌠|⌠|âœ”ï¸ |âœ”ï¸ |**N/A** | |Multitouch |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |⌠|**N/A** | |Keyboard events |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ | |Drag & Drop |â–¢[#720] |â–¢[#720] |â–¢[#720] |â–¢[#720] |**N/A**|**N/A**|â“ |**N/A** | |Raw Device Events |â–¢[#750] |â–¢[#750] |â–¢[#750] |⌠|⌠|⌠|â“ |**N/A** | |Gamepad/Joystick events |âŒ[#804] |⌠|⌠|⌠|⌠|⌠|â“ |**N/A** | |Device movement events |â“ |â“ |â“ |â“ |⌠|⌠|â“ |**N/A** | |Drag window with cursor |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A** |**N/A** | |Resize with cursor |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A** |**N/A** | ### Pending API Reworks Changes in the API that have been agreed upon but aren't implemented across all platforms. |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | |New API for HiDPI ([#315] [#319]) |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |â“ |â“ | |Event Loop 2.0 ([#459]) |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |â“ |âœ”ï¸ | |Keyboard Input 2.0 ([#753]) |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ | ### Completed API Reworks |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | [#165]: https://github.com/rust-windowing/winit/issues/165 [#219]: https://github.com/rust-windowing/winit/issues/219 [#242]: https://github.com/rust-windowing/winit/issues/242 [#306]: https://github.com/rust-windowing/winit/issues/306 [#315]: https://github.com/rust-windowing/winit/issues/315 [#319]: https://github.com/rust-windowing/winit/issues/319 [#33]: https://github.com/rust-windowing/winit/issues/33 [#459]: https://github.com/rust-windowing/winit/issues/459 [#5]: https://github.com/rust-windowing/winit/issues/5 [#63]: https://github.com/rust-windowing/winit/issues/63 [#720]: https://github.com/rust-windowing/winit/issues/720 [#721]: https://github.com/rust-windowing/winit/issues/721 [#750]: https://github.com/rust-windowing/winit/issues/750 [#753]: https://github.com/rust-windowing/winit/issues/753 [#804]: https://github.com/rust-windowing/winit/issues/804 winit-0.30.9/LICENSE000064400000000000000000000260731046102023000120640ustar 00000000000000Apache 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 APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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.winit-0.30.9/README.md000064400000000000000000000053561046102023000123370ustar 00000000000000# winit - Cross-platform window creation and management in Rust [![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit) [![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit) [![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs )](https://rust-windowing.github.io/winit/winit/index.html) [![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions) ```toml [dependencies] winit = "0.30.9" ``` ## [Documentation](https://docs.rs/winit) For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md). For features _outside_ the scope of winit, see [Are we GUI Yet?](https://areweguiyet.com/) and [Are we game yet?](https://arewegameyet.rs/), depending on what kind of project you're looking to do. ## Contact Us Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings). ## Usage Winit is a window creation and management library. It can create windows and lets you handle events (for example: the window being resized, a key being pressed, a mouse movement, etc.) produced by the window. Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to show something on the window you need to use the platform-specific getters provided by winit, or another library. ## MSRV Policy This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to the MSRV will be accompanied by a minor version bump. As a **tentative** policy, the upper bound of the MSRV is given by the following formula: ``` min(sid, stable - 3) ``` Where `sid` is the current version of `rustc` provided by [Debian Sid], and `stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability. [Debian Sid]: https://packages.debian.org/sid/rustc The exception is for the Android platform, where a higher Rust version must be used for certain Android features. In this case, the MSRV will be capped at the latest stable version of Rust minus three. This inconsistency is not reflected in Cargo metadata, as it is not powerful enough to expose this restriction. All crates in the [`rust-windowing`] organizations have the same MSRV policy. [`rust-windowing`]: https://github.com/rust-windowing ### Platform-specific usage Check out the [`winit::platform`](https://rust-windowing.github.io/winit/winit/platform/index.html) module for platform-specific usage. winit-0.30.9/build.rs000064400000000000000000000017541046102023000125230ustar 00000000000000use cfg_aliases::cfg_aliases; fn main() { // The script doesn't depend on our code. println!("cargo:rerun-if-changed=build.rs"); // Setup cfg aliases. cfg_aliases! { // Systems. android_platform: { target_os = "android" }, web_platform: { all(target_family = "wasm", target_os = "unknown") }, macos_platform: { target_os = "macos" }, ios_platform: { target_os = "ios" }, windows_platform: { target_os = "windows" }, apple: { any(target_os = "ios", target_os = "macos") }, free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) }, redox: { target_os = "redox" }, // Native displays. x11_platform: { all(feature = "x11", free_unix, not(redox)) }, wayland_platform: { all(feature = "wayland", free_unix, not(redox)) }, orbital_platform: { redox }, } // Winit defined cfgs. println!("cargo:rustc-check-cfg=cfg(unreleased_changelogs)"); } winit-0.30.9/docs/res/ATTRIBUTION.md000064400000000000000000000010711046102023000147150ustar 00000000000000# Image Attribution These images are used in the documentation of `winit`. ## keyboard_*.svg These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)" by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en) License. Minor modifications have been made by [John Nunley](https://github.com/notgull), which have been released under the same license as a derivative work. winit-0.30.9/docs/res/keyboard_left_shift_key.svg000064400000000000000000002216701046102023000202000ustar 00000000000000winit-0.30.9/docs/res/keyboard_numpad_1_key.svg000064400000000000000000002216721046102023000175570ustar 00000000000000winit-0.30.9/docs/res/keyboard_right_shift_key.svg000064400000000000000000002216671046102023000203710ustar 00000000000000winit-0.30.9/docs/res/keyboard_standard_1_key.svg000064400000000000000000002216711046102023000200720ustar 00000000000000winit-0.30.9/examples/child_window.rs000064400000000000000000000073551046102023000157170ustar 00000000000000#[cfg(all(feature = "rwh_06", any(x11_platform, macos_platform, windows_platform)))] #[allow(deprecated)] fn main() -> Result<(), impl std::error::Error> { use std::collections::HashMap; use winit::dpi::{LogicalPosition, LogicalSize, Position}; use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::raw_window_handle::HasRawWindowHandle; use winit::window::Window; #[path = "util/fill.rs"] mod fill; fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window { let parent = parent.raw_window_handle().unwrap(); let mut window_attributes = Window::default_attributes() .with_title("child window") .with_inner_size(LogicalSize::new(200.0f32, 200.0f32)) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_visible(true); // `with_parent_window` is unsafe. Parent window must be a valid window. window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) }; event_loop.create_window(window_attributes).unwrap() } let mut windows = HashMap::new(); let event_loop: EventLoop<()> = EventLoop::new().unwrap(); let mut parent_window_id = None; event_loop.run(move |event: Event<()>, event_loop| { match event { Event::Resumed => { let attributes = Window::default_attributes() .with_title("parent window") .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_inner_size(LogicalSize::new(640.0f32, 480.0f32)); let window = event_loop.create_window(attributes).unwrap(); parent_window_id = Some(window.id()); println!("Parent window id: {parent_window_id:?})"); windows.insert(window.id(), window); }, Event::WindowEvent { window_id, event } => match event { WindowEvent::CloseRequested => { windows.clear(); event_loop.exit(); }, WindowEvent::CursorEntered { device_id: _ } => { // On x11, println when the cursor entered in a window even if the child window // is created by some key inputs. // the child windows are always placed at (0, 0) with size (200, 200) in the // parent window, so we also can see this log when we move // the cursor around (200, 200) in parent window. println!("cursor entered in the window {window_id:?}"); }, WindowEvent::KeyboardInput { event: KeyEvent { state: ElementState::Pressed, .. }, .. } => { let parent_window = windows.get(&parent_window_id.unwrap()).unwrap(); let child_window = spawn_child_window(parent_window, event_loop); let child_id = child_window.id(); println!("Child window created with id: {child_id:?}"); windows.insert(child_id, child_window); }, WindowEvent::RedrawRequested => { if let Some(window) = windows.get(&window_id) { fill::fill_window(window); } }, _ => (), }, _ => (), } }) } #[cfg(all(feature = "rwh_06", not(any(x11_platform, macos_platform, windows_platform))))] fn main() { panic!( "This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature \ enabled." ); } winit-0.30.9/examples/control_flow.rs000064400000000000000000000110231046102023000157370ustar 00000000000000#![allow(clippy::single_match)] use std::thread; #[cfg(not(web_platform))] use std::time; use ::tracing::{info, warn}; #[cfg(web_platform)] use web_time as time; use winit::application::ApplicationHandler; use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::keyboard::{Key, NamedKey}; use winit::window::{Window, WindowId}; #[path = "util/fill.rs"] mod fill; #[path = "util/tracing.rs"] mod tracing; const WAIT_TIME: time::Duration = time::Duration::from_millis(100); const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] enum Mode { #[default] Wait, WaitUntil, Poll, } fn main() -> Result<(), impl std::error::Error> { #[cfg(web_platform)] console_error_panic_hook::set_once(); tracing::init(); info!("Press '1' to switch to Wait mode."); info!("Press '2' to switch to WaitUntil mode."); info!("Press '3' to switch to Poll mode."); info!("Press 'R' to toggle request_redraw() calls."); info!("Press 'Esc' to close the window."); let event_loop = EventLoop::new().unwrap(); let mut app = ControlFlowDemo::default(); event_loop.run_app(&mut app) } #[derive(Default)] struct ControlFlowDemo { mode: Mode, request_redraw: bool, wait_cancelled: bool, close_requested: bool, window: Option, } impl ApplicationHandler for ControlFlowDemo { fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) { info!("new_events: {cause:?}"); self.wait_cancelled = match cause { StartCause::WaitCancelled { .. } => self.mode == Mode::WaitUntil, _ => false, } } fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window_attributes = Window::default_attributes().with_title( "Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.", ); self.window = Some(event_loop.create_window(window_attributes).unwrap()); } fn window_event( &mut self, _event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent, ) { info!("{event:?}"); match event { WindowEvent::CloseRequested => { self.close_requested = true; }, WindowEvent::KeyboardInput { event: KeyEvent { logical_key: key, state: ElementState::Pressed, .. }, .. } => match key.as_ref() { // WARNING: Consider using `key_without_modifiers()` if available on your platform. // See the `key_binding` example Key::Character("1") => { self.mode = Mode::Wait; warn!("mode: {:?}", self.mode); }, Key::Character("2") => { self.mode = Mode::WaitUntil; warn!("mode: {:?}", self.mode); }, Key::Character("3") => { self.mode = Mode::Poll; warn!("mode: {:?}", self.mode); }, Key::Character("r") => { self.request_redraw = !self.request_redraw; warn!("request_redraw: {}", self.request_redraw); }, Key::Named(NamedKey::Escape) => { self.close_requested = true; }, _ => (), }, WindowEvent::RedrawRequested => { let window = self.window.as_ref().unwrap(); window.pre_present_notify(); fill::fill_window(window); }, _ => (), } } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { if self.request_redraw && !self.wait_cancelled && !self.close_requested { self.window.as_ref().unwrap().request_redraw(); } match self.mode { Mode::Wait => event_loop.set_control_flow(ControlFlow::Wait), Mode::WaitUntil => { if !self.wait_cancelled { event_loop .set_control_flow(ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME)); } }, Mode::Poll => { thread::sleep(POLL_SLEEP_TIME); event_loop.set_control_flow(ControlFlow::Poll); }, }; if self.close_requested { event_loop.exit(); } } } winit-0.30.9/examples/data/cross.png000064400000000000000000000002371046102023000154370ustar 00000000000000‰PNG  IHDRóÿafIDAT8Ëc`Àþ30üÿÏÀðŸ& Ű sÌÀ{Òö#²œŽú¸Ü•›6ŒƒÇ 8ùaƒÿ°X@v>:`$Mçõ#p/ džðEÌ{Œ Œ¸¢xGãÐÉL­ ,…*qqoIEND®B`‚winit-0.30.9/examples/data/cross2.png000064400000000000000000000002011046102023000155100ustar 00000000000000‰PNG  IHDR Vu\çHIDAT(ÏA Ã:þÿg½hbTfpÇQ`  @ Œ&»áà1ø0d`)+d^:mÕºU.ï-bPÔ¤ÒÑ¥·:øÖ/x¯uv" }u–>IEND®B`‚winit-0.30.9/examples/data/gradient.png000064400000000000000000000003451046102023000161030ustar 00000000000000‰PNG  IHDRóÿa¬IDAT8Ë’Ý Â0 FOêæœâ… ˆ ˆï±÷,ãÓ4¤?(4ió%9­è² À ˜=pÀ 8à Ü€;ðH|MÌ^ÉÍŸ­‹Dl6a?wJþ¢˜êÖÏ´2ÂŒðìaõGS]Þ¹ZƒØòµÅ ·5q¤¢Ì€– J%q²gé‡ö³ Hå|l×bPêÀ‹h)t¥ÿtà‹Ì©cn­1z‡'¸ÜZ IEND®B`‚winit-0.30.9/examples/data/icon.png000064400000000000000000000056061046102023000152430ustar 00000000000000‰PNG  IHDR szzôbKGDÿÿÿ ½§“ pHYs  šœtIMEâC\C" IDATX×ytUÕ½Ç?{ŸáŽIBE Ðs£ Š JS,EÀꞥ¥Ë:DÑZÂ-H@JE*Ê+åÙªˆ.­.áÕDÉE ‚ÈL@„„$äæŽçœýþÈe°u½õöZgí³öÙgý¾¿ù÷üW0$Rðì­sCôn¦öî@`¦þJ À`?°ØJU Ž¹!X@ÿ?…VVƒ!a~ T †Ê€éÀP 7J€Jí. ›êÞ¨Ž»üøo”€úw)dTV 4)˜_âPò#™Šbž€nR(lG€¥u6mÒMSØ4ž;9Ý. 5å¤]g Ð@|‡¬Ô¾¨ôŠÆ?=Pe lG›çÎ®Í ÌŒÒÓ§‹×"Ç'yaÅ:^[¼ýkþvé'|±³¸(¦:ªJ`!ˆ«‚_:‹8såËÒxv„M0Ô ¥Þ8% æM0ºk;3old`V” ÓÂ-\éi`ºÀQDÃQ5êGóÄ®œió÷›¸Ü´ÅD*ŽÖS¨¿"V\õu¸–Oì8 VÏPˆUhºpãŸã—E#_^&b Ž@7tâ ‹Õý˜_Ÿâ†ž]xòá |ð÷Óî}À±’Àf`%Uõüv§ÀBñ\I @0$RA†˜óñ+Jº~†áƒÆ‡oÈ.M{ÉÖÚ¸·|L&‹!¥ sï´%äæî{Êùô“íØ»•7þ0“Ò·;–7[’ˆ¸jè(5ƒ…¥Çxí8œ¬G0g'?i~7-—Úú Ä&`°`h,ð Ø@e¨—dn¨ÁSn›«_8²iCâôÉõïÊ›o½Ã°aCéÞ½;………45·i­#/7ÛQL›|Ó¦?Dyù {ô¤°G/6lÜÂÈá=8×Ð ð½ë*©FU`sª€$’×%‚å¸Òþ“½ï¼ÖbáÙVý5U¿yO·¼Ë²ÿÈì§Ã÷‹oe×;y|ædf¤Ñ‰3cê”rYºd>ùs©¯;Ôñ73åÞÛ¨«mÄ#e1Ju¨ Øü6$R)xÐŒC ØFUààN`óôûGóç¥3ù¦¶‘¥+ÖràðYr;¥1kz#†ô¥=ë(mŽC~n&/®Ú@ç¼tÜ\È [zò?›÷R1ýÊoq8a]Ë4)J ÁУÀr=U"ŸHÊÕIj|öÑ>Ÿ¬ãÆÂ<žváö(†®#¥ ¥-‚}I ·Ç8pä,§75°öõÙÄâ_©e€&U¬àq¸|ÑÛäΣ²èXJ†JYä%‚¡'%ŠõTö§>æHóÑûr”ÙKßãPMšZQBbºM4C';ËßçB)…”‚X<ÉëËIç¼L~öè |]Ó8q¦ž.¶%‹z ‚Œ¼)HíÖŽ`¯þÁЄë²áUÁ*€ŠGiå{¶ÿ|¬ÏOŸìNüׯ/ùáæ/1LènƒN9™¸½&cn/¢âwn¡”âRk”åU2|újNú+'¶|FUV:ÉHØIøÇ²âWïÙJjkPN‚`h UuÀvÖÆmå{¶Oü¾×;¬Èã!©Ov#fÙ´Y6I¥Û6‘ aÖ_¼ÈžN~òJ¾ùœO{—²±ÏX~?¶’ó¿z›ûz ‚h„òšO˜n(2¥À’¨é%7|‘‹þ\,—Ÿ2¾f‡¢OÉL‘Œµ¡™óyz·¦ëø³ï >”ßÒ@:;lÞûÞd .Ÿ%/|‘Óy7sÛég² iógñëƒ,) r¾¥‘“—/’Õ=æ³^|yÈD„„îb֮׬#_¬55Ô=„i$zðI:LÕýét;ùïC©Í±À—C«'MÙti>Å”/ß#+ÑήÖïúŽV³µx 1w:qÝdìñ­øãmL<¸Žœö&„ãàÑ QÔvìÈ ¶©®(µØ­““ŒOt›‡×÷f}Û4Ç+Ža'ÁMM5Dca.µ4ÐÇ4¹»ï`¾8އ62äÜ>Nd2æ›ÏÜp”´Dþx;Ý[jiÕݲ³iÑ‘|~ðÁŽh³t‘ŒU׌šUz´õ¤êÛp\DMB]s†¦lPB9èv’6—\'AÏd˜,_[F?±6ØýÙÑf† ˜á"=ÞJR3T·Ÿ¨îÁ’Rf&Lyþ’q@,¤*°Wb[#4yFÔ¥w!=ÖŠ¸. 4GhÜÐTCN¸Ÿ¤ÿ¥Óä6Ÿå®ƒcÁ¦ùdE/Õ=Øš†BÔ ¢†O2J‹4X[¾ú+¾…¥Á«C©¨Üñ°2Ý+ü‘f5wóbqKý!"¦[hTüh)S÷½ÃèŸ1¼HåàH‰BoÅÃ{¸GHÜÉš²ù*¿H-ù¨s)³@±ð&µ0Pv¥C ~÷Oxf"Z¢tói¬“ö¿Ïm 'ÒDÄðà²âÄu¶Ð(!‘ª#e!¯YK) ÃNbÚ Nf÷bmѽlï{' Éø‹, T¤q/dåŽ')_ÀðÕRÇŽ~ÄÈšdGšq[qL;#$¶©IW!•B:6Ž$t“ˆááD§Þlì7Ž/» &áÎD$ÚÏIå std::process::ExitCode { use std::process::ExitCode; use std::thread::sleep; use std::time::Duration; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}; use winit::window::{Window, WindowId}; #[path = "util/fill.rs"] mod fill; #[derive(Default)] struct PumpDemo { window: Option, } impl ApplicationHandler for PumpDemo { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window_attributes = Window::default_attributes().with_title("A fantastic window!"); self.window = Some(event_loop.create_window(window_attributes).unwrap()); } fn window_event( &mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent, ) { println!("{event:?}"); let window = match self.window.as_ref() { Some(window) => window, None => return, }; match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { fill::fill_window(window); window.request_redraw(); }, _ => (), } } } let mut event_loop = EventLoop::new().unwrap(); tracing_subscriber::fmt::init(); let mut app = PumpDemo::default(); loop { let timeout = Some(Duration::ZERO); let status = event_loop.pump_app_events(timeout, &mut app); if let PumpStatus::Exit(exit_code) = status { break ExitCode::from(exit_code as u8); } // Sleep for 1/60 second to simulate application work // // Since `pump_events` doesn't block it will be important to // throttle the loop in the app somehow. println!("Update()"); sleep(Duration::from_millis(16)); } } #[cfg(any(ios_platform, web_platform, orbital_platform))] fn main() { println!("This platform doesn't support pump_events."); } winit-0.30.9/examples/run_on_demand.rs000064400000000000000000000064611046102023000160520ustar 00000000000000#![allow(clippy::single_match)] // Limit this example to only compatible platforms. #[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))] fn main() -> Result<(), Box> { use std::time::Duration; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::platform::run_on_demand::EventLoopExtRunOnDemand; use winit::window::{Window, WindowId}; #[path = "util/fill.rs"] mod fill; #[derive(Default)] struct App { idx: usize, window_id: Option, window: Option, } impl ApplicationHandler for App { fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { if let Some(window) = self.window.as_ref() { window.request_redraw(); } } fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window_attributes = Window::default_attributes() .with_title("Fantastic window number one!") .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)); let window = event_loop.create_window(window_attributes).unwrap(); self.window_id = Some(window.id()); self.window = Some(window); } fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent, ) { if event == WindowEvent::Destroyed && self.window_id == Some(window_id) { println!( "--------------------------------------------------------- Window {} Destroyed", self.idx ); self.window_id = None; event_loop.exit(); return; } let window = match self.window.as_mut() { Some(window) => window, None => return, }; match event { WindowEvent::CloseRequested => { println!( "--------------------------------------------------------- Window {} \ CloseRequested", self.idx ); fill::cleanup_window(window); self.window = None; }, WindowEvent::RedrawRequested => { fill::fill_window(window); }, _ => (), } } } tracing_subscriber::fmt::init(); let mut event_loop = EventLoop::new().unwrap(); let mut app = App { idx: 1, ..Default::default() }; event_loop.run_app_on_demand(&mut app)?; println!("--------------------------------------------------------- Finished first loop"); println!("--------------------------------------------------------- Waiting 5 seconds"); std::thread::sleep(Duration::from_secs(5)); app.idx += 1; event_loop.run_app_on_demand(&mut app)?; println!("--------------------------------------------------------- Finished second loop"); Ok(()) } #[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))] fn main() { println!("This example is not supported on this platform"); } winit-0.30.9/examples/util/fill.rs000064400000000000000000000101211046102023000151310ustar 00000000000000//! Fill the window buffer with a solid color. //! //! Launching a window without drawing to it has unpredictable results varying from platform to //! platform. In order to have well-defined examples, this module provides an easy way to //! fill the window buffer with a solid color. //! //! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could //! also be used to fill the window buffer, but they are more complicated to use. #[allow(unused_imports)] pub use platform::cleanup_window; pub use platform::fill_window; #[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))] mod platform { use std::cell::RefCell; use std::collections::HashMap; use std::mem; use std::mem::ManuallyDrop; use std::num::NonZeroU32; use softbuffer::{Context, Surface}; use winit::window::{Window, WindowId}; thread_local! { // NOTE: You should never do things like that, create context and drop it before // you drop the event loop. We do this for brevity to not blow up examples. We use // ManuallyDrop to prevent destructors from running. // // A static, thread-local map of graphics contexts to open windows. static GC: ManuallyDrop>> = const { ManuallyDrop::new(RefCell::new(None)) }; } /// The graphics context used to draw to a window. struct GraphicsContext { /// The global softbuffer context. context: RefCell>, /// The hash map of window IDs to surfaces. surfaces: HashMap>, } impl GraphicsContext { fn new(w: &Window) -> Self { Self { context: RefCell::new( Context::new(unsafe { mem::transmute::<&'_ Window, &'static Window>(w) }) .expect("Failed to create a softbuffer context"), ), surfaces: HashMap::new(), } } fn create_surface( &mut self, window: &Window, ) -> &mut Surface<&'static Window, &'static Window> { self.surfaces.entry(window.id()).or_insert_with(|| { Surface::new(&self.context.borrow(), unsafe { mem::transmute::<&'_ Window, &'static Window>(window) }) .expect("Failed to create a softbuffer surface") }) } fn destroy_surface(&mut self, window: &Window) { self.surfaces.remove(&window.id()); } } pub fn fill_window(window: &Window) { GC.with(|gc| { let size = window.inner_size(); let (Some(width), Some(height)) = (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) else { return; }; // Either get the last context used or create a new one. let mut gc = gc.borrow_mut(); let surface = gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window); // Fill a buffer with a solid color. const DARK_GRAY: u32 = 0xff181818; surface.resize(width, height).expect("Failed to resize the softbuffer surface"); let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer"); buffer.fill(DARK_GRAY); buffer.present().expect("Failed to present the softbuffer buffer"); }) } #[allow(dead_code)] pub fn cleanup_window(window: &Window) { GC.with(|gc| { let mut gc = gc.borrow_mut(); if let Some(context) = gc.as_mut() { context.destroy_surface(window); } }); } } #[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))] mod platform { pub fn fill_window(_window: &winit::window::Window) { // No-op on mobile platforms. } #[allow(dead_code)] pub fn cleanup_window(_window: &winit::window::Window) { // No-op on mobile platforms. } } winit-0.30.9/examples/util/tracing.rs000064400000000000000000000012661046102023000156440ustar 00000000000000#[cfg(not(web_platform))] pub fn init() { use tracing_subscriber::filter::{EnvFilter, LevelFilter}; tracing_subscriber::fmt() .with_env_filter( EnvFilter::builder().with_default_directive(LevelFilter::INFO.into()).from_env_lossy(), ) .init(); } #[cfg(web_platform)] pub fn init() { use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; tracing_subscriber::registry() .with( tracing_subscriber::fmt::layer() .with_ansi(false) .without_time() .with_writer(tracing_web::MakeWebConsoleWriter::new()), ) .init(); } winit-0.30.9/examples/window.rs000064400000000000000000001155161046102023000145530ustar 00000000000000//! Simple winit application. use std::collections::HashMap; use std::error::Error; use std::fmt::Debug; #[cfg(not(any(android_platform, ios_platform)))] use std::num::NonZeroU32; use std::sync::Arc; use std::{fmt, mem}; use ::tracing::{error, info}; use cursor_icon::CursorIcon; #[cfg(not(any(android_platform, ios_platform)))] use rwh_06::{DisplayHandle, HasDisplayHandle}; #[cfg(not(any(android_platform, ios_platform)))] use softbuffer::{Context, Surface}; use winit::application::ApplicationHandler; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent}; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::keyboard::{Key, ModifiersState}; use winit::window::{ Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection, Theme, Window, WindowId, }; #[cfg(macos_platform)] use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS}; #[cfg(any(x11_platform, wayland_platform))] use winit::platform::startup_notify::{ self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify, }; #[cfg(x11_platform)] use winit::platform::x11::WindowAttributesExtX11; #[path = "util/tracing.rs"] mod tracing; /// The amount of points to around the window for drag resize direction calculations. const BORDER_SIZE: f64 = 20.; fn main() -> Result<(), Box> { #[cfg(web_platform)] console_error_panic_hook::set_once(); tracing::init(); let event_loop = EventLoop::::with_user_event().build()?; let _event_loop_proxy = event_loop.create_proxy(); // Wire the user event from another thread. #[cfg(not(web_platform))] std::thread::spawn(move || { // Wake up the `event_loop` once every second and dispatch a custom event // from a different thread. info!("Starting to send user event every second"); loop { let _ = _event_loop_proxy.send_event(UserEvent::WakeUp); std::thread::sleep(std::time::Duration::from_secs(1)); } }); let mut state = Application::new(&event_loop); event_loop.run_app(&mut state).map_err(Into::into) } #[allow(dead_code)] #[derive(Debug, Clone, Copy)] enum UserEvent { WakeUp, } /// Application state and event handling. struct Application { /// Custom cursors assets. custom_cursors: Vec, /// Application icon. icon: Icon, windows: HashMap, /// Drawing context. /// /// With OpenGL it could be EGLDisplay. #[cfg(not(any(android_platform, ios_platform)))] context: Option>>, } impl Application { fn new(event_loop: &EventLoop) -> Self { // SAFETY: we drop the context right before the event loop is stopped, thus making it safe. #[cfg(not(any(android_platform, ios_platform)))] let context = Some( Context::new(unsafe { std::mem::transmute::, DisplayHandle<'static>>( event_loop.display_handle().unwrap(), ) }) .unwrap(), ); // You'll have to choose an icon size at your own discretion. On X11, the desired size // varies by WM, and on Windows, you still have to account for screen scaling. Here // we use 32px, since it seems to work well enough in most cases. Be careful about // going too high, or you'll be bitten by the low-quality downscaling built into the // WM. let icon = load_icon(include_bytes!("data/icon.png")); info!("Loading cursor assets"); let custom_cursors = vec![ event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross.png"))), event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross2.png"))), event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/gradient.png"))), ]; Self { #[cfg(not(any(android_platform, ios_platform)))] context, custom_cursors, icon, windows: Default::default(), } } fn create_window( &mut self, event_loop: &ActiveEventLoop, _tab_id: Option, ) -> Result> { // TODO read-out activation token. #[allow(unused_mut)] let mut window_attributes = Window::default_attributes() .with_title("Winit window") .with_transparent(true) .with_window_icon(Some(self.icon.clone())); #[cfg(any(x11_platform, wayland_platform))] if let Some(token) = event_loop.read_token_from_env() { startup_notify::reset_activation_token_env(); info!("Using token {:?} to activate a window", token); window_attributes = window_attributes.with_activation_token(token); } #[cfg(x11_platform)] match std::env::var("X11_VISUAL_ID") { Ok(visual_id_str) => { info!("Using X11 visual id {visual_id_str}"); let visual_id = visual_id_str.parse()?; window_attributes = window_attributes.with_x11_visual(visual_id); }, Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"), } #[cfg(x11_platform)] match std::env::var("X11_SCREEN_ID") { Ok(screen_id_str) => { info!("Placing the window on X11 screen {screen_id_str}"); let screen_id = screen_id_str.parse()?; window_attributes = window_attributes.with_x11_screen(screen_id); }, Err(_) => info!( "Set the X11_SCREEN_ID env variable to place the window on non-default screen" ), } #[cfg(macos_platform)] if let Some(tab_id) = _tab_id { window_attributes = window_attributes.with_tabbing_identifier(&tab_id); } #[cfg(web_platform)] { use winit::platform::web::WindowAttributesExtWebSys; window_attributes = window_attributes.with_append(true); } let window = event_loop.create_window(window_attributes)?; #[cfg(ios_platform)] { use winit::platform::ios::WindowExtIOS; window.recognize_doubletap_gesture(true); window.recognize_pinch_gesture(true); window.recognize_rotation_gesture(true); window.recognize_pan_gesture(true, 2, 2); } let window_state = WindowState::new(self, window)?; let window_id = window_state.window.id(); info!("Created new window with id={window_id:?}"); self.windows.insert(window_id, window_state); Ok(window_id) } fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) { // let cursor_position = self.cursor_position; let window = self.windows.get_mut(&window_id).unwrap(); info!("Executing action: {action:?}"); match action { Action::CloseWindow => { let _ = self.windows.remove(&window_id); }, Action::CreateNewWindow => { #[cfg(any(x11_platform, wayland_platform))] if let Err(err) = window.window.request_activation_token() { info!("Failed to get activation token: {err}"); } else { return; } if let Err(err) = self.create_window(event_loop, None) { error!("Error creating new window: {err}"); } }, Action::ToggleResizeIncrements => window.toggle_resize_increments(), Action::ToggleCursorVisibility => window.toggle_cursor_visibility(), Action::ToggleResizable => window.toggle_resizable(), Action::ToggleDecorations => window.toggle_decorations(), Action::ToggleFullscreen => window.toggle_fullscreen(), Action::ToggleMaximize => window.toggle_maximize(), Action::ToggleImeInput => window.toggle_ime(), Action::Minimize => window.minimize(), Action::NextCursor => window.next_cursor(), Action::NextCustomCursor => window.next_custom_cursor(&self.custom_cursors), #[cfg(web_platform)] Action::UrlCustomCursor => window.url_custom_cursor(event_loop), #[cfg(web_platform)] Action::AnimationCustomCursor => { window.animation_custom_cursor(event_loop, &self.custom_cursors) }, Action::CycleCursorGrab => window.cycle_cursor_grab(), Action::DragWindow => window.drag_window(), Action::DragResizeWindow => window.drag_resize_window(), Action::ShowWindowMenu => window.show_menu(), Action::PrintHelp => self.print_help(), #[cfg(macos_platform)] Action::CycleOptionAsAlt => window.cycle_option_as_alt(), Action::SetTheme(theme) => { window.window.set_theme(theme); // Get the resulting current theme to draw with let actual_theme = theme.or_else(|| window.window.theme()).unwrap_or(Theme::Dark); window.set_draw_theme(actual_theme); }, #[cfg(macos_platform)] Action::CreateNewTab => { let tab_id = window.window.tabbing_identifier(); if let Err(err) = self.create_window(event_loop, Some(tab_id)) { error!("Error creating new window: {err}"); } }, Action::RequestResize => window.swap_dimensions(), } } fn dump_monitors(&self, event_loop: &ActiveEventLoop) { info!("Monitors information"); let primary_monitor = event_loop.primary_monitor(); for monitor in event_loop.available_monitors() { let intro = if primary_monitor.as_ref() == Some(&monitor) { "Primary monitor" } else { "Monitor" }; if let Some(name) = monitor.name() { info!("{intro}: {name}"); } else { info!("{intro}: [no name]"); } let PhysicalSize { width, height } = monitor.size(); info!( " Current mode: {width}x{height}{}", if let Some(m_hz) = monitor.refresh_rate_millihertz() { format!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000) } else { String::new() } ); let PhysicalPosition { x, y } = monitor.position(); info!(" Position: {x},{y}"); info!(" Scale factor: {}", monitor.scale_factor()); info!(" Available modes (width x height x bit-depth):"); for mode in monitor.video_modes() { let PhysicalSize { width, height } = mode.size(); let bits = mode.bit_depth(); let m_hz = mode.refresh_rate_millihertz(); info!(" {width}x{height}x{bits} @ {}.{} Hz", m_hz / 1000, m_hz % 1000); } } } /// Process the key binding. fn process_key_binding(key: &str, mods: &ModifiersState) -> Option { KEY_BINDINGS .iter() .find_map(|binding| binding.is_triggered_by(&key, mods).then_some(binding.action)) } /// Process mouse binding. fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option { MOUSE_BINDINGS .iter() .find_map(|binding| binding.is_triggered_by(&button, mods).then_some(binding.action)) } fn print_help(&self) { info!("Keyboard bindings:"); for binding in KEY_BINDINGS { info!( "{}{:<10} - {} ({})", modifiers_to_string(binding.mods), binding.trigger, binding.action, binding.action.help(), ); } info!("Mouse bindings:"); for binding in MOUSE_BINDINGS { info!( "{}{:<10} - {} ({})", modifiers_to_string(binding.mods), mouse_button_to_string(binding.trigger), binding.action, binding.action.help(), ); } } } impl ApplicationHandler for Application { fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) { info!("User event: {event:?}"); } fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent, ) { let window = match self.windows.get_mut(&window_id) { Some(window) => window, None => return, }; match event { WindowEvent::Resized(size) => { window.resize(size); }, WindowEvent::Focused(focused) => { if focused { info!("Window={window_id:?} focused"); } else { info!("Window={window_id:?} unfocused"); } }, WindowEvent::ScaleFactorChanged { scale_factor, .. } => { info!("Window={window_id:?} changed scale to {scale_factor}"); }, WindowEvent::ThemeChanged(theme) => { info!("Theme changed to {theme:?}"); window.set_draw_theme(theme); }, WindowEvent::RedrawRequested => { if let Err(err) = window.draw() { error!("Error drawing window: {err}"); } }, WindowEvent::Occluded(occluded) => { window.set_occluded(occluded); }, WindowEvent::CloseRequested => { info!("Closing Window={window_id:?}"); self.windows.remove(&window_id); }, WindowEvent::ModifiersChanged(modifiers) => { window.modifiers = modifiers.state(); info!("Modifiers changed to {:?}", window.modifiers); }, WindowEvent::MouseWheel { delta, .. } => match delta { MouseScrollDelta::LineDelta(x, y) => { info!("Mouse wheel Line Delta: ({x},{y})"); }, MouseScrollDelta::PixelDelta(px) => { info!("Mouse wheel Pixel Delta: ({},{})", px.x, px.y); }, }, WindowEvent::KeyboardInput { event, is_synthetic: false, .. } => { let mods = window.modifiers; // Dispatch actions only on press. if event.state.is_pressed() { let action = if let Key::Character(ch) = event.logical_key.as_ref() { Self::process_key_binding(&ch.to_uppercase(), &mods) } else { None }; if let Some(action) = action { self.handle_action(event_loop, window_id, action); } } }, WindowEvent::MouseInput { button, state, .. } => { let mods = window.modifiers; if let Some(action) = state.is_pressed().then(|| Self::process_mouse_binding(button, &mods)).flatten() { self.handle_action(event_loop, window_id, action); } }, WindowEvent::CursorLeft { .. } => { info!("Cursor left Window={window_id:?}"); window.cursor_left(); }, WindowEvent::CursorMoved { position, .. } => { info!("Moved cursor to {position:?}"); window.cursor_moved(position); }, WindowEvent::ActivationTokenDone { token: _token, .. } => { #[cfg(any(x11_platform, wayland_platform))] { startup_notify::set_activation_token_env(_token); if let Err(err) = self.create_window(event_loop, None) { error!("Error creating new window: {err}"); } } }, WindowEvent::Ime(event) => match event { Ime::Enabled => info!("IME enabled for Window={window_id:?}"), Ime::Preedit(text, caret_pos) => { info!("Preedit: {}, with caret at {:?}", text, caret_pos); }, Ime::Commit(text) => { info!("Committed: {}", text); }, Ime::Disabled => info!("IME disabled for Window={window_id:?}"), }, WindowEvent::PinchGesture { delta, .. } => { window.zoom += delta; let zoom = window.zoom; if delta > 0.0 { info!("Zoomed in {delta:.5} (now: {zoom:.5})"); } else { info!("Zoomed out {delta:.5} (now: {zoom:.5})"); } }, WindowEvent::RotationGesture { delta, .. } => { window.rotated += delta; let rotated = window.rotated; if delta > 0.0 { info!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})"); } else { info!("Rotated clockwise {delta:.5} (now: {rotated:.5})"); } }, WindowEvent::PanGesture { delta, phase, .. } => { window.panned.x += delta.x; window.panned.y += delta.y; info!("Panned ({delta:?})) (now: {:?}), {phase:?}", window.panned); }, WindowEvent::DoubleTapGesture { .. } => { info!("Smart zoom"); }, WindowEvent::TouchpadPressure { .. } | WindowEvent::HoveredFileCancelled | WindowEvent::KeyboardInput { .. } | WindowEvent::CursorEntered { .. } | WindowEvent::AxisMotion { .. } | WindowEvent::DroppedFile(_) | WindowEvent::HoveredFile(_) | WindowEvent::Destroyed | WindowEvent::Touch(_) | WindowEvent::Moved(_) => (), } } fn device_event( &mut self, _event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent, ) { info!("Device {device_id:?} event: {event:?}"); } fn resumed(&mut self, event_loop: &ActiveEventLoop) { info!("Resumed the event loop"); self.dump_monitors(event_loop); // Create initial window. self.create_window(event_loop, None).expect("failed to create initial window"); self.print_help(); } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { if self.windows.is_empty() { info!("No windows left, exiting..."); event_loop.exit(); } } #[cfg(not(any(android_platform, ios_platform)))] fn exiting(&mut self, _event_loop: &ActiveEventLoop) { // We must drop the context here. self.context = None; } } /// State of the window. struct WindowState { /// IME input. ime: bool, /// Render surface. /// /// NOTE: This surface must be dropped before the `Window`. #[cfg(not(any(android_platform, ios_platform)))] surface: Surface, Arc>, /// The actual winit Window. window: Arc, /// The window theme we're drawing with. theme: Theme, /// Cursor position over the window. cursor_position: Option>, /// Window modifiers state. modifiers: ModifiersState, /// Occlusion state of the window. occluded: bool, /// Current cursor grab mode. cursor_grab: CursorGrabMode, /// The amount of zoom into window. zoom: f64, /// The amount of rotation of the window. rotated: f32, /// The amount of pan of the window. panned: PhysicalPosition, #[cfg(macos_platform)] option_as_alt: OptionAsAlt, // Cursor states. named_idx: usize, custom_idx: usize, cursor_hidden: bool, } impl WindowState { fn new(app: &Application, window: Window) -> Result> { let window = Arc::new(window); // SAFETY: the surface is dropped before the `window` which provided it with handle, thus // it doesn't outlive it. #[cfg(not(any(android_platform, ios_platform)))] let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?; let theme = window.theme().unwrap_or(Theme::Dark); info!("Theme: {theme:?}"); let named_idx = 0; window.set_cursor(CURSORS[named_idx]); // Allow IME out of the box. let ime = true; window.set_ime_allowed(ime); let size = window.inner_size(); let mut state = Self { #[cfg(macos_platform)] option_as_alt: window.option_as_alt(), custom_idx: app.custom_cursors.len() - 1, cursor_grab: CursorGrabMode::None, named_idx, #[cfg(not(any(android_platform, ios_platform)))] surface, window, theme, ime, cursor_position: Default::default(), cursor_hidden: Default::default(), modifiers: Default::default(), occluded: Default::default(), rotated: Default::default(), panned: Default::default(), zoom: Default::default(), }; state.resize(size); Ok(state) } pub fn toggle_ime(&mut self) { self.ime = !self.ime; self.window.set_ime_allowed(self.ime); if let Some(position) = self.ime.then_some(self.cursor_position).flatten() { self.window.set_ime_cursor_area(position, PhysicalSize::new(20, 20)); } } pub fn minimize(&mut self) { self.window.set_minimized(true); } pub fn cursor_moved(&mut self, position: PhysicalPosition) { self.cursor_position = Some(position); if self.ime { self.window.set_ime_cursor_area(position, PhysicalSize::new(20, 20)); } } pub fn cursor_left(&mut self) { self.cursor_position = None; } /// Toggle maximized. fn toggle_maximize(&self) { let maximized = self.window.is_maximized(); self.window.set_maximized(!maximized); } /// Toggle window decorations. fn toggle_decorations(&self) { let decorated = self.window.is_decorated(); self.window.set_decorations(!decorated); } /// Toggle window resizable state. fn toggle_resizable(&self) { let resizable = self.window.is_resizable(); self.window.set_resizable(!resizable); } /// Toggle cursor visibility fn toggle_cursor_visibility(&mut self) { self.cursor_hidden = !self.cursor_hidden; self.window.set_cursor_visible(!self.cursor_hidden); } /// Toggle resize increments on a window. fn toggle_resize_increments(&mut self) { let new_increments = match self.window.resize_increments() { Some(_) => None, None => Some(LogicalSize::new(25.0, 25.0)), }; info!("Had increments: {}", new_increments.is_none()); self.window.set_resize_increments(new_increments); } /// Toggle fullscreen. fn toggle_fullscreen(&self) { let fullscreen = if self.window.fullscreen().is_some() { None } else { Some(Fullscreen::Borderless(None)) }; self.window.set_fullscreen(fullscreen); } /// Cycle through the grab modes ignoring errors. fn cycle_cursor_grab(&mut self) { self.cursor_grab = match self.cursor_grab { CursorGrabMode::None => CursorGrabMode::Confined, CursorGrabMode::Confined => CursorGrabMode::Locked, CursorGrabMode::Locked => CursorGrabMode::None, }; info!("Changing cursor grab mode to {:?}", self.cursor_grab); if let Err(err) = self.window.set_cursor_grab(self.cursor_grab) { error!("Error setting cursor grab: {err}"); } } #[cfg(macos_platform)] fn cycle_option_as_alt(&mut self) { self.option_as_alt = match self.option_as_alt { OptionAsAlt::None => OptionAsAlt::OnlyLeft, OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight, OptionAsAlt::OnlyRight => OptionAsAlt::Both, OptionAsAlt::Both => OptionAsAlt::None, }; info!("Setting option as alt {:?}", self.option_as_alt); self.window.set_option_as_alt(self.option_as_alt); } /// Swap the window dimensions with `request_inner_size`. fn swap_dimensions(&mut self) { let old_inner_size = self.window.inner_size(); let mut inner_size = old_inner_size; mem::swap(&mut inner_size.width, &mut inner_size.height); info!("Requesting resize from {old_inner_size:?} to {inner_size:?}"); if let Some(new_inner_size) = self.window.request_inner_size(inner_size) { if old_inner_size == new_inner_size { info!("Inner size change got ignored"); } else { self.resize(new_inner_size); } } else { info!("Request inner size is asynchronous"); } } /// Pick the next cursor. fn next_cursor(&mut self) { self.named_idx = (self.named_idx + 1) % CURSORS.len(); info!("Setting cursor to \"{:?}\"", CURSORS[self.named_idx]); self.window.set_cursor(Cursor::Icon(CURSORS[self.named_idx])); } /// Pick the next custom cursor. fn next_custom_cursor(&mut self, custom_cursors: &[CustomCursor]) { self.custom_idx = (self.custom_idx + 1) % custom_cursors.len(); let cursor = Cursor::Custom(custom_cursors[self.custom_idx].clone()); self.window.set_cursor(cursor); } /// Custom cursor from an URL. #[cfg(web_platform)] fn url_custom_cursor(&mut self, event_loop: &ActiveEventLoop) { let cursor = event_loop.create_custom_cursor(url_custom_cursor()); self.window.set_cursor(cursor); } /// Custom cursor from a URL. #[cfg(web_platform)] fn animation_custom_cursor( &mut self, event_loop: &ActiveEventLoop, custom_cursors: &[CustomCursor], ) { use std::time::Duration; use winit::platform::web::CustomCursorExtWebSys; let cursors = vec![ custom_cursors[0].clone(), custom_cursors[1].clone(), event_loop.create_custom_cursor(url_custom_cursor()), ]; let cursor = CustomCursor::from_animation(Duration::from_secs(3), cursors).unwrap(); let cursor = event_loop.create_custom_cursor(cursor); self.window.set_cursor(cursor); } /// Resize the window to the new size. fn resize(&mut self, size: PhysicalSize) { info!("Resized to {size:?}"); #[cfg(not(any(android_platform, ios_platform)))] { let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) { (Some(width), Some(height)) => (width, height), _ => return, }; self.surface.resize(width, height).expect("failed to resize inner buffer"); } self.window.request_redraw(); } /// Change the theme that things are drawn in. fn set_draw_theme(&mut self, theme: Theme) { self.theme = theme; self.window.request_redraw(); } /// Show window menu. fn show_menu(&self) { if let Some(position) = self.cursor_position { self.window.show_window_menu(position); } } /// Drag the window. fn drag_window(&self) { if let Err(err) = self.window.drag_window() { info!("Error starting window drag: {err}"); } else { info!("Dragging window Window={:?}", self.window.id()); } } /// Drag-resize the window. fn drag_resize_window(&self) { let position = match self.cursor_position { Some(position) => position, None => { info!("Drag-resize requires cursor to be inside the window"); return; }, }; let win_size = self.window.inner_size(); let border_size = BORDER_SIZE * self.window.scale_factor(); let x_direction = if position.x < border_size { ResizeDirection::West } else if position.x > (win_size.width as f64 - border_size) { ResizeDirection::East } else { // Use arbitrary direction instead of None for simplicity. ResizeDirection::SouthEast }; let y_direction = if position.y < border_size { ResizeDirection::North } else if position.y > (win_size.height as f64 - border_size) { ResizeDirection::South } else { // Use arbitrary direction instead of None for simplicity. ResizeDirection::SouthEast }; let direction = match (x_direction, y_direction) { (ResizeDirection::West, ResizeDirection::North) => ResizeDirection::NorthWest, (ResizeDirection::West, ResizeDirection::South) => ResizeDirection::SouthWest, (ResizeDirection::West, _) => ResizeDirection::West, (ResizeDirection::East, ResizeDirection::North) => ResizeDirection::NorthEast, (ResizeDirection::East, ResizeDirection::South) => ResizeDirection::SouthEast, (ResizeDirection::East, _) => ResizeDirection::East, (_, ResizeDirection::South) => ResizeDirection::South, (_, ResizeDirection::North) => ResizeDirection::North, _ => return, }; if let Err(err) = self.window.drag_resize_window(direction) { info!("Error starting window drag-resize: {err}"); } else { info!("Drag-resizing window Window={:?}", self.window.id()); } } /// Change window occlusion state. fn set_occluded(&mut self, occluded: bool) { self.occluded = occluded; if !occluded { self.window.request_redraw(); } } /// Draw the window contents. #[cfg(not(any(android_platform, ios_platform)))] fn draw(&mut self) -> Result<(), Box> { if self.occluded { info!("Skipping drawing occluded window={:?}", self.window.id()); return Ok(()); } const WHITE: u32 = 0xffffffff; const DARK_GRAY: u32 = 0xff181818; let color = match self.theme { Theme::Light => WHITE, Theme::Dark => DARK_GRAY, }; let mut buffer = self.surface.buffer_mut()?; buffer.fill(color); self.window.pre_present_notify(); buffer.present()?; Ok(()) } #[cfg(any(android_platform, ios_platform))] fn draw(&mut self) -> Result<(), Box> { info!("Drawing but without rendering..."); Ok(()) } } struct Binding { trigger: T, mods: ModifiersState, action: Action, } impl Binding { const fn new(trigger: T, mods: ModifiersState, action: Action) -> Self { Self { trigger, mods, action } } fn is_triggered_by(&self, trigger: &T, mods: &ModifiersState) -> bool { &self.trigger == trigger && &self.mods == mods } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Action { CloseWindow, ToggleCursorVisibility, CreateNewWindow, ToggleResizeIncrements, ToggleImeInput, ToggleDecorations, ToggleResizable, ToggleFullscreen, ToggleMaximize, Minimize, NextCursor, NextCustomCursor, #[cfg(web_platform)] UrlCustomCursor, #[cfg(web_platform)] AnimationCustomCursor, CycleCursorGrab, PrintHelp, DragWindow, DragResizeWindow, ShowWindowMenu, #[cfg(macos_platform)] CycleOptionAsAlt, SetTheme(Option), #[cfg(macos_platform)] CreateNewTab, RequestResize, } impl Action { fn help(&self) -> &'static str { match self { Action::CloseWindow => "Close window", Action::ToggleCursorVisibility => "Hide cursor", Action::CreateNewWindow => "Create new window", Action::ToggleImeInput => "Toggle IME input", Action::ToggleDecorations => "Toggle decorations", Action::ToggleResizable => "Toggle window resizable state", Action::ToggleFullscreen => "Toggle fullscreen", Action::ToggleMaximize => "Maximize", Action::Minimize => "Minimize", Action::ToggleResizeIncrements => "Use resize increments when resizing window", Action::NextCursor => "Advance the cursor to the next value", Action::NextCustomCursor => "Advance custom cursor to the next value", #[cfg(web_platform)] Action::UrlCustomCursor => "Custom cursor from an URL", #[cfg(web_platform)] Action::AnimationCustomCursor => "Custom cursor from an animation", Action::CycleCursorGrab => "Cycle through cursor grab mode", Action::PrintHelp => "Print help", Action::DragWindow => "Start window drag", Action::DragResizeWindow => "Start window drag-resize", Action::ShowWindowMenu => "Show window menu", #[cfg(macos_platform)] Action::CycleOptionAsAlt => "Cycle option as alt mode", Action::SetTheme(None) => "Change to the system theme", Action::SetTheme(Some(Theme::Light)) => "Change to a light theme", Action::SetTheme(Some(Theme::Dark)) => "Change to a dark theme", #[cfg(macos_platform)] Action::CreateNewTab => "Create new tab", Action::RequestResize => "Request a resize", } } } impl fmt::Display for Action { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Debug::fmt(&self, f) } } fn decode_cursor(bytes: &[u8]) -> CustomCursorSource { let img = image::load_from_memory(bytes).unwrap().to_rgba8(); let samples = img.into_flat_samples(); let (_, w, h) = samples.extents(); let (w, h) = (w as u16, h as u16); CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap() } #[cfg(web_platform)] fn url_custom_cursor() -> CustomCursorSource { use std::sync::atomic::{AtomicU64, Ordering}; use winit::platform::web::CustomCursorExtWebSys; static URL_COUNTER: AtomicU64 = AtomicU64::new(0); CustomCursor::from_url( format!("https://picsum.photos/128?random={}", URL_COUNTER.fetch_add(1, Ordering::Relaxed)), 64, 64, ) } fn load_icon(bytes: &[u8]) -> Icon { let (icon_rgba, icon_width, icon_height) = { let image = image::load_from_memory(bytes).unwrap().into_rgba8(); let (width, height) = image.dimensions(); let rgba = image.into_raw(); (rgba, width, height) }; Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") } fn modifiers_to_string(mods: ModifiersState) -> String { let mut mods_line = String::new(); // Always add + since it's printed as a part of the bindings. for (modifier, desc) in [ (ModifiersState::SUPER, "Super+"), (ModifiersState::ALT, "Alt+"), (ModifiersState::CONTROL, "Ctrl+"), (ModifiersState::SHIFT, "Shift+"), ] { if !mods.contains(modifier) { continue; } mods_line.push_str(desc); } mods_line } fn mouse_button_to_string(button: MouseButton) -> &'static str { match button { MouseButton::Left => "LMB", MouseButton::Right => "RMB", MouseButton::Middle => "MMB", MouseButton::Back => "Back", MouseButton::Forward => "Forward", MouseButton::Other(_) => "", } } /// Cursor list to cycle through. const CURSORS: &[CursorIcon] = &[ CursorIcon::Default, CursorIcon::Crosshair, CursorIcon::Pointer, CursorIcon::Move, CursorIcon::Text, CursorIcon::Wait, CursorIcon::Help, CursorIcon::Progress, CursorIcon::NotAllowed, CursorIcon::ContextMenu, CursorIcon::Cell, CursorIcon::VerticalText, CursorIcon::Alias, CursorIcon::Copy, CursorIcon::NoDrop, CursorIcon::Grab, CursorIcon::Grabbing, CursorIcon::AllScroll, CursorIcon::ZoomIn, CursorIcon::ZoomOut, CursorIcon::EResize, CursorIcon::NResize, CursorIcon::NeResize, CursorIcon::NwResize, CursorIcon::SResize, CursorIcon::SeResize, CursorIcon::SwResize, CursorIcon::WResize, CursorIcon::EwResize, CursorIcon::NsResize, CursorIcon::NeswResize, CursorIcon::NwseResize, CursorIcon::ColResize, CursorIcon::RowResize, ]; const KEY_BINDINGS: &[Binding<&'static str>] = &[ Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow), Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp), Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen), Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations), Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput), Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab), Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements), Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable), Binding::new("R", ModifiersState::ALT, Action::RequestResize), // M. Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize), Binding::new("M", ModifiersState::ALT, Action::Minimize), // N. Binding::new("N", ModifiersState::CONTROL, Action::CreateNewWindow), // C. Binding::new("C", ModifiersState::CONTROL, Action::NextCursor), Binding::new("C", ModifiersState::ALT, Action::NextCustomCursor), #[cfg(web_platform)] Binding::new( "C", ModifiersState::CONTROL.union(ModifiersState::SHIFT), Action::UrlCustomCursor, ), #[cfg(web_platform)] Binding::new( "C", ModifiersState::ALT.union(ModifiersState::SHIFT), Action::AnimationCustomCursor, ), Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility), // K. Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)), Binding::new("K", ModifiersState::SUPER, Action::SetTheme(Some(Theme::Light))), Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))), #[cfg(macos_platform)] Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab), #[cfg(macos_platform)] Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt), ]; const MOUSE_BINDINGS: &[Binding] = &[ Binding::new(MouseButton::Left, ModifiersState::ALT, Action::DragResizeWindow), Binding::new(MouseButton::Left, ModifiersState::CONTROL, Action::DragWindow), Binding::new(MouseButton::Right, ModifiersState::CONTROL, Action::ShowWindowMenu), ]; winit-0.30.9/examples/x11_embed.rs000064400000000000000000000043131046102023000150010ustar 00000000000000//! A demonstration of embedding a winit window in an existing X11 application. use std::error::Error; #[cfg(x11_platform)] fn main() -> Result<(), Box> { use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::platform::x11::WindowAttributesExtX11; use winit::window::{Window, WindowId}; #[path = "util/fill.rs"] mod fill; pub struct XEmbedDemo { parent_window_id: u32, window: Option, } impl ApplicationHandler for XEmbedDemo { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window_attributes = Window::default_attributes() .with_title("An embedded window!") .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .with_embed_parent_window(self.parent_window_id); self.window = Some(event_loop.create_window(window_attributes).unwrap()); } fn window_event( &mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent, ) { let window = self.window.as_ref().unwrap(); match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { window.pre_present_notify(); fill::fill_window(window); }, _ => (), } } fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { self.window.as_ref().unwrap().request_redraw(); } } // First argument should be a 32-bit X11 window ID. let parent_window_id = std::env::args() .nth(1) .ok_or("Expected a 32-bit X11 window ID as the first argument.")? .parse::()?; tracing_subscriber::fmt::init(); let event_loop = EventLoop::new()?; let mut app = XEmbedDemo { parent_window_id, window: None }; event_loop.run_app(&mut app).map_err(Into::into) } #[cfg(not(x11_platform))] fn main() -> Result<(), Box> { println!("This example is only supported on X11 platforms."); Ok(()) } winit-0.30.9/src/application.rs000064400000000000000000000334671046102023000145240ustar 00000000000000//! End user application handling. use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; use crate::event_loop::ActiveEventLoop; use crate::window::WindowId; /// The handler of the application events. pub trait ApplicationHandler { /// Emitted when new events arrive from the OS to be processed. /// /// This is a useful place to put code that should be done before you start processing /// events, such as updating frame timing information for benchmarking or checking the /// [`StartCause`] to see if a timer set by /// [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil] has elapsed. fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { let _ = (event_loop, cause); } /// Emitted when the application has been resumed. /// /// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a /// formal suspend/resume lifecycle. For systems without a formal suspend/resume lifecycle /// the `Resumed` event is always emitted after the /// [`NewEvents(StartCause::Init)`][StartCause::Init] event. /// /// # Portability /// /// It's recommended that applications should only initialize their graphics context and create /// a window after they have received their first `Resumed` event. Some systems /// (specifically Android) won't allow applications to create a render surface until they are /// resumed. /// /// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally /// driven by multiple platform-specific events, and that there may be subtle differences across /// platforms with how these internal events are delivered, it's recommended that applications /// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed` /// events. /// /// Also see [`Suspended`] notes. /// /// ## Android /// /// On Android, the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is /// expected to closely correlate with the [`onResume`] lifecycle event but there may /// technically be a discrepancy. /// /// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume() /// /// Applications that need to run on Android must wait until they have been `Resumed` /// before they will be able to create a render surface (such as an `EGLSurface`, /// [`VkSurfaceKHR`] or [`wgpu::Surface`]) which depend on having a /// [`SurfaceView`]. Applications must also assume that if they are [`Suspended`], then their /// render surfaces are invalid and should be dropped. /// /// Also see [`Suspended`] notes. /// /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html /// /// ## iOS /// /// On iOS, the `Resumed` event is emitted in response to an [`applicationDidBecomeActive`] /// callback which means the application is "active" (according to the /// [iOS application lifecycle]). /// /// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle /// /// ## Web /// /// On Web, the `Resumed` event is emitted in response to a [`pageshow`] event /// with the property [`persisted`] being true, which means that the page is being /// restored from the [`bfcache`] (back/forward cache) - an in-memory cache that /// stores a complete snapshot of a page (including the JavaScript heap) as the /// user is navigating away. /// /// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event /// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted /// [`bfcache`]: https://web.dev/bfcache/ /// [`Suspended`]: Self::suspended fn resumed(&mut self, event_loop: &ActiveEventLoop); /// Emitted when an event is sent from [`EventLoopProxy::send_event`]. /// /// [`EventLoopProxy::send_event`]: crate::event_loop::EventLoopProxy::send_event fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) { let _ = (event_loop, event); } /// Emitted when the OS sends an event to a winit window. fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent, ); /// Emitted when the OS sends an event to a device. fn device_event( &mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent, ) { let _ = (event_loop, device_id, event); } /// Emitted when the event loop is about to block and wait for new events. /// /// Most applications shouldn't need to hook into this event since there is no real relationship /// between how often the event loop needs to wake up and the dispatching of any specific /// events. /// /// High frequency event sources, such as input devices could potentially lead to lots of wake /// ups and also lots of corresponding `AboutToWait` events. /// /// This is not an ideal event to drive application rendering from and instead applications /// should render in response to [`WindowEvent::RedrawRequested`] events. fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { let _ = event_loop; } /// Emitted when the application has been suspended. /// /// # Portability /// /// Not all platforms support the notion of suspending applications, and there may be no /// technical way to guarantee being able to emit a `Suspended` event if the OS has /// no formal application lifecycle (currently only Android, iOS, and Web do). For this reason, /// Winit does not currently try to emit pseudo `Suspended` events before the application /// quits on platforms without an application lifecycle. /// /// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally /// driven by multiple platform-specific events, and that there may be subtle differences across /// platforms with how these internal events are delivered, it's recommended that applications /// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`] /// events. /// /// Also see [`Resumed`] notes. /// /// ## Android /// /// On Android, the `Suspended` event is only sent when the application's associated /// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`] /// lifecycle event but there may technically be a discrepancy. /// /// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause() /// /// Applications that need to run on Android should assume their [`SurfaceView`] has been /// destroyed, which indirectly invalidates any existing render surfaces that may have been /// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]). /// /// After being `Suspended` on Android applications must drop all render surfaces before /// the event callback completes, which may be re-created when the application is next /// [`Resumed`]. /// /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html /// /// ## iOS /// /// On iOS, the `Suspended` event is currently emitted in response to an /// [`applicationWillResignActive`] callback which means that the application is /// about to transition from the active to inactive state (according to the /// [iOS application lifecycle]). /// /// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle /// /// ## Web /// /// On Web, the `Suspended` event is emitted in response to a [`pagehide`] event /// with the property [`persisted`] being true, which means that the page is being /// put in the [`bfcache`] (back/forward cache) - an in-memory cache that stores a /// complete snapshot of a page (including the JavaScript heap) as the user is /// navigating away. /// /// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event /// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted /// [`bfcache`]: https://web.dev/bfcache/ /// [`Resumed`]: Self::resumed fn suspended(&mut self, event_loop: &ActiveEventLoop) { let _ = event_loop; } /// Emitted when the event loop is being shut down. /// /// This is irreversible - if this method is called, it is guaranteed that the event loop /// will exit right after. fn exiting(&mut self, event_loop: &ActiveEventLoop) { let _ = event_loop; } /// Emitted when the application has received a memory warning. /// /// ## Platform-specific /// /// ### Android /// /// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The /// application must [release memory] or risk being killed. /// /// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory() /// [release memory]: https://developer.android.com/topic/performance/memory#release /// /// ### iOS /// /// On iOS, the `MemoryWarning` event is emitted in response to an /// [`applicationDidReceiveMemoryWarning`] callback. The application must free as much /// memory as possible or risk being terminated, see [how to respond to memory warnings]. /// /// [`applicationDidReceiveMemoryWarning`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni /// [how to respond to memory warnings]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle/responding_to_memory_warnings /// /// ### Others /// /// - **macOS / Orbital / Wayland / Web / Windows:** Unsupported. fn memory_warning(&mut self, event_loop: &ActiveEventLoop) { let _ = event_loop; } } impl, T: 'static> ApplicationHandler for &mut A { #[inline] fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { (**self).new_events(event_loop, cause); } #[inline] fn resumed(&mut self, event_loop: &ActiveEventLoop) { (**self).resumed(event_loop); } #[inline] fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) { (**self).user_event(event_loop, event); } #[inline] fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent, ) { (**self).window_event(event_loop, window_id, event); } #[inline] fn device_event( &mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent, ) { (**self).device_event(event_loop, device_id, event); } #[inline] fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { (**self).about_to_wait(event_loop); } #[inline] fn suspended(&mut self, event_loop: &ActiveEventLoop) { (**self).suspended(event_loop); } #[inline] fn exiting(&mut self, event_loop: &ActiveEventLoop) { (**self).exiting(event_loop); } #[inline] fn memory_warning(&mut self, event_loop: &ActiveEventLoop) { (**self).memory_warning(event_loop); } } impl, T: 'static> ApplicationHandler for Box { #[inline] fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { (**self).new_events(event_loop, cause); } #[inline] fn resumed(&mut self, event_loop: &ActiveEventLoop) { (**self).resumed(event_loop); } #[inline] fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) { (**self).user_event(event_loop, event); } #[inline] fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent, ) { (**self).window_event(event_loop, window_id, event); } #[inline] fn device_event( &mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent, ) { (**self).device_event(event_loop, device_id, event); } #[inline] fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { (**self).about_to_wait(event_loop); } #[inline] fn suspended(&mut self, event_loop: &ActiveEventLoop) { (**self).suspended(event_loop); } #[inline] fn exiting(&mut self, event_loop: &ActiveEventLoop) { (**self).exiting(event_loop); } #[inline] fn memory_warning(&mut self, event_loop: &ActiveEventLoop) { (**self).memory_warning(event_loop); } } winit-0.30.9/src/changelog/mod.rs000064400000000000000000000031601046102023000147120ustar 00000000000000//! # Changelog and migrations //! //! All notable changes to this project will be documented in this module, //! along with migration instructions for larger changes. // Put the current entry at the top of this page, for discoverability. // See `.cargo/config.toml` for details about `unreleased_changelogs`. #![cfg_attr(unreleased_changelogs, doc = include_str!("unreleased.md"))] #![cfg_attr(not(unreleased_changelogs), doc = include_str!("v0.30.md"))] #[doc = include_str!("v0.30.md")] pub mod v0_30 {} #[doc = include_str!("v0.29.md")] pub mod v0_29 {} #[doc = include_str!("v0.28.md")] pub mod v0_28 {} #[doc = include_str!("v0.27.md")] pub mod v0_27 {} #[doc = include_str!("v0.26.md")] pub mod v0_26 {} #[doc = include_str!("v0.25.md")] pub mod v0_25 {} #[doc = include_str!("v0.24.md")] pub mod v0_24 {} #[doc = include_str!("v0.23.md")] pub mod v0_23 {} #[doc = include_str!("v0.22.md")] pub mod v0_22 {} #[doc = include_str!("v0.21.md")] pub mod v0_21 {} #[doc = include_str!("v0.20.md")] pub mod v0_20 {} #[doc = include_str!("v0.19.md")] pub mod v0_19 {} #[doc = include_str!("v0.18.md")] pub mod v0_18 {} #[doc = include_str!("v0.17.md")] pub mod v0_17 {} #[doc = include_str!("v0.16.md")] pub mod v0_16 {} #[doc = include_str!("v0.15.md")] pub mod v0_15 {} #[doc = include_str!("v0.14.md")] pub mod v0_14 {} #[doc = include_str!("v0.13.md")] pub mod v0_13 {} #[doc = include_str!("v0.12.md")] pub mod v0_12 {} #[doc = include_str!("v0.11.md")] pub mod v0_11 {} #[doc = include_str!("v0.10.md")] pub mod v0_10 {} #[doc = include_str!("v0.9.md")] pub mod v0_9 {} #[doc = include_str!("v0.8.md")] pub mod v0_8 {} winit-0.30.9/src/changelog/unreleased.md000064400000000000000000000022501046102023000162350ustar 00000000000000The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The sections should follow the order `Added`, `Changed`, `Deprecated`, `Removed`, and `Fixed`. Platform specific changed should be added to the end of the section and grouped by platform name. Common API additions should have `, implemented` at the end for platforms where the API was initially implemented. See the following example on how to add them: ```md ### Added - Add `Window::turbo()`, implemented on X11, Wayland, and Web. - On X11, add `Window::some_rare_api`. - On X11, add `Window::even_more_rare_api`. - On Wayland, add `Window::common_api`. - On Windows, add `Window::some_rare_api`. ``` When the change requires non-trivial amount of work for users to comply with it, the migration guide should be added below the entry, like: ```md - Deprecate `Window` creation outside of `EventLoop::run` This was done to simply migration in the future. Consider the following code: // Code snippet. To migrate it we should do X, Y, and then Z, for example: // Code snippet. ``` The migration guide could reference other migration examples in the current changelog entry. ## Unreleased winit-0.30.9/src/changelog/v0.10.md000064400000000000000000000021521046102023000146530ustar 00000000000000## 0.10.1 _Yanked_ ## 0.10.0 - Add support for `Touch` for emscripten backend. - Added support for `DroppedFile`, `HoveredFile`, and `HoveredFileCancelled` to X11 backend. - **Breaking:** `unix::WindowExt` no longer returns pointers for things that aren't actually pointers; `get_xlib_window` now returns `Option` and `get_xlib_screen_id` returns `Option`. Additionally, methods that previously returned `libc::c_void` have been changed to return `std::os::raw::c_void`, which are not interchangeable types, so users wanting the former will need to explicitly cast. - Added `set_decorations` method to `Window` to allow decorations to be toggled after the window is built. Presently only implemented on X11. - Raised the minimum supported version of Rust to 1.20 on MacOS due to usage of associated constants in new versions of cocoa and core-graphics. - Added `modifiers` field to `MouseInput`, `MouseWheel`, and `CursorMoved` events to track the modifiers state (`ModifiersState`). - Fixed the emscripten backend to return the size of the canvas instead of the size of the window. winit-0.30.9/src/changelog/v0.11.md000064400000000000000000000031371046102023000146600ustar 00000000000000## 0.11.3 - Added `set_min_dimensions` and `set_max_dimensions` methods to `Window`, and implemented on Windows, X11, Wayland, and OSX. - On X11, dropping a `Window` actually closes it now, and clicking the window's × button (or otherwise having the WM signal to close it) will result in the window closing. - Added `WindowBuilderExt` methods for macos: `with_titlebar_transparent`, `with_title_hidden`, `with_titlebar_buttons_hidden`, `with_fullsize_content_view`. - Mapped X11 numpad keycodes (arrows, Home, End, PageUp, PageDown, Insert and Delete) to corresponding virtual keycodes ## 0.11.2 - Impl `Hash`, `PartialEq`, and `Eq` for `events::ModifiersState`. - Implement `MonitorId::get_hidpi_factor` for MacOS. - Added method `os::macos::MonitorIdExt::get_nsscreen() -> *mut c_void` that gets a `NSScreen` object matching the monitor ID. - Send `Awakened` event on Android when event loop is woken up. ## 0.11.1 - Fixed windows not receiving mouse events when click-dragging the mouse outside the client area of a window, on Windows platforms. - Added method `os::android::EventsLoopExt:set_suspend_callback(Option ()>>)` that allows glutin to register a callback when a suspend event happens ## 0.11.0 - Implement `MonitorId::get_dimensions` for Android. - Added method `os::macos::WindowBuilderExt::with_movable_by_window_background(bool)` that allows to move a window without a titlebar - `with_decorations(false)` - Implement `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for Wayland. - Added `Caret` as VirtualKeyCode and support OSX ^-Key with german input. winit-0.30.9/src/changelog/v0.12.md000064400000000000000000000013071046102023000146560ustar 00000000000000## 0.12.0 - Added subclass to macos windows so they can be made resizable even with no decorations. - Dead keys now work properly on X11, no longer resulting in a panic. - On X11, input method creation first tries to use the value from the user's `XMODIFIERS` environment variable, so application developers should no longer need to manually call `XSetLocaleModifiers`. If that fails, fallbacks are tried, which should prevent input method initialization from ever outright failing. - Fixed thread safety issues with input methods on X11. - Add support for `Touch` for win32 backend. - Fixed `Window::get_inner_size` and friends to return the size in pixels instead of points when using HIDPI displays on OSX. winit-0.30.9/src/changelog/v0.13.md000064400000000000000000000035671046102023000146710ustar 00000000000000## 0.13.1 - Ensure necessary `x11-dl` version is used. ## 0.13.0 - Implement `WindowBuilder::with_maximized`, `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for MacOS. - Implement `WindowBuilder::with_maximized`, `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for Windows. - On Windows, `WindowBuilder::with_fullscreen` no longer changing monitor display resolution. - Overhauled X11 window geometry calculations. `get_position` and `set_position` are more universally accurate across different window managers, and `get_outer_size` actually works now. - Fixed SIGSEGV/SIGILL crashes on macOS caused by stabilization of the `!` (never) type. - Implement `WindowEvent::HiDPIFactorChanged` for macOS - On X11, input methods now work completely out of the box, no longer requiring application developers to manually call `setlocale`. Additionally, when input methods are started, stopped, or restarted on the server end, it's correctly handled. - Implemented `Refresh` event on Windows. - Properly calculate the minimum and maximum window size on Windows, including window decorations. - Map more `MouseCursor` variants to cursor icons on Windows. - Corrected `get_position` on macOS to return outer frame position, not content area position. - Corrected `set_position` on macOS to set outer frame position, not content area position. - Added `get_inner_position` method to `Window`, which gets the position of the window's client area. This is implemented on all applicable platforms (all desktop platforms other than Wayland, where this isn't possible). - **Breaking:** the `Closed` event has been replaced by `CloseRequested` and `Destroyed`. To migrate, you typically just need to replace all usages of `Closed` with `CloseRequested`; see example programs for more info. The exception is iOS, where `Closed` must be replaced by `Destroyed`. winit-0.30.9/src/changelog/v0.14.md000064400000000000000000000054051046102023000146630ustar 00000000000000## 0.14.0 - Created the `Copy`, `Paste` and `Cut` `VirtualKeyCode`s and added support for them on X11 and Wayland - Fix `.with_decorations(false)` in macOS - On Mac, `NSWindow` and supporting objects might be alive long after they were `closed` which resulted in apps consuming more heap then needed. Mainly it was affecting multi window applications. Not expecting any user visible change of behaviour after the fix. - Fix regression of Window platform extensions for macOS where `NSFullSizeContentViewWindowMask` was not being correctly applied to `.fullsize_content_view`. - Corrected `get_position` on Windows to be relative to the screen rather than to the taskbar. - Corrected `Moved` event on Windows to use position values equivalent to those returned by `get_position`. It previously supplied client area positions instead of window positions, and would additionally interpret negative values as being very large (around `u16::MAX`). - Implemented `Moved` event on macOS. - On X11, the `Moved` event correctly use window positions rather than client area positions. Additionally, a stray `Moved` that unconditionally accompanied `Resized` with the client area position relative to the parent has been eliminated; `Moved` is still received alongside `Resized`, but now only once and always correctly. - On Windows, implemented all variants of `DeviceEvent` other than `Text`. Mouse `DeviceEvent`s are now received even if the window isn't in the foreground. - `DeviceId` on Windows is no longer a unit struct, and now contains a `u32`. For `WindowEvent`s, this will always be 0, but on `DeviceEvent`s it will be the handle to that device. `DeviceIdExt::get_persistent_identifier` can be used to acquire a unique identifier for that device that persists across replugs/reboots/etc. - Corrected `run_forever` on X11 to stop discarding `Awakened` events. - Various safety and correctness improvements to the X11 backend internals. - Fixed memory leak on X11 every time the mouse entered the window. - On X11, drag and drop now works reliably in release mode. - Added `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` to X11, allowing for more optional hints to be set. - Rework of the wayland backend, migrating it to use [Smithay's Client Toolkit](https://github.com/Smithay/client-toolkit). - Added `WindowBuilder::with_window_icon` and `Window::set_window_icon`, finally making it possible to set the window icon on Windows and X11. The `icon_loading` feature can be enabled to allow for icons to be easily loaded; see example program `window_icon.rs` for usage. - Windows additionally has `WindowBuilderExt::with_taskbar_icon` and `WindowExt::set_taskbar_icon`. - On Windows, fix panic when trying to call `set_fullscreen(None)` on a window that has not been fullscreened prior. winit-0.30.9/src/changelog/v0.15.md000064400000000000000000000075371046102023000146740ustar 00000000000000## 0.15.1 - On X11, the `Moved` event is no longer sent when the window is resized without changing position. - `MouseCursor` and `CursorState` now implement `Default`. - `WindowBuilder::with_resizable` implemented for Windows, X11, Wayland, and macOS. - `Window::set_resizable` implemented for Windows, X11, Wayland, and macOS. - On X11, if the monitor's width or height in millimeters is reported as 0, the DPI is now 1.0 instead of +inf. - On X11, the environment variable `WINIT_HIDPI_FACTOR` has been added for overriding DPI factor. - On X11, enabling transparency no longer causes the window contents to flicker when resizing. - On X11, `with_override_redirect` now actually enables override redirect. - macOS now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead of `None` for both. - On macOS, `VirtualKeyCode::RWin` and `VirtualKeyCode::LWin` are no longer switched. - On macOS, windows without decorations can once again be resized. - Fixed race conditions when creating an `EventsLoop` on X11, most commonly manifesting as `"[xcb] Unknown sequence number while processing queue"`. - On macOS, `CursorMoved` and `MouseInput` events are only generated if they occurs within the window's client area. - On macOS, resizing the window no longer generates a spurious `MouseInput` event. ## 0.15.0 - `Icon::to_cardinals` is no longer public, since it was never supposed to be. - Wayland: improve diagnostics if initialization fails - Fix some system event key doesn't work when focused, do not block keyevent forward to system on macOS - On X11, the scroll wheel position is now correctly reset on i3 and other WMs that have the same quirk. - On X11, `Window::get_current_monitor` now reliably returns the correct monitor. - On X11, `Window::hidpi_factor` returns values from XRandR rather than the inaccurate values previously queried from the core protocol. - On X11, the primary monitor is detected correctly even when using versions of XRandR less than 1.5. - `MonitorId` now implements `Debug`. - Fixed bug on macOS where using `with_decorations(false)` would cause `set_decorations(true)` to produce a transparent titlebar with no title. - Implemented `MonitorId::get_position` on macOS. - On macOS, `Window::get_current_monitor` now returns accurate values. - Added `WindowBuilderExt::with_resize_increments` to macOS. - **Breaking:** On X11, `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` now take `u32` values rather than `i32`. - macOS keyboard handling has been overhauled, allowing for the use of dead keys, IME, etc. Right modifier keys are also no longer reported as being left. - Added the `Window::set_ime_spot(x: i32, y: i32)` method, which is implemented on X11 and macOS. - **Breaking**: `os::unix::WindowExt::send_xim_spot(x: i16, y: i16)` no longer exists. Switch to the new `Window::set_ime_spot(x: i32, y: i32)`, which has equivalent functionality. - Fixed detection of `Pause` and `Scroll` keys on Windows. - On Windows, alt-tabbing while the cursor is grabbed no longer makes it impossible to re-grab the cursor. - On Windows, using `CursorState::Hide` when the cursor is grabbed now ungrabs the cursor first. - Implemented `MouseCursor::NoneCursor` on Windows. - Added `WindowBuilder::with_always_on_top` and `Window::set_always_on_top`. Implemented on Windows, macOS, and X11. - On X11, `WindowBuilderExt` now has `with_class`, `with_override_redirect`, and `with_x11_window_type` to allow for more control over window creation. `WindowExt` additionally has `set_urgent`. - More hints are set by default on X11, including `_NET_WM_PID` and `WM_CLIENT_MACHINE`. Note that prior to this, the `WM_CLASS` hint was automatically set to whatever value was passed to `with_title`. It's now set to the executable name to better conform to expectations and the specification; if this is undesirable, you must explicitly use `WindowBuilderExt::with_class`. winit-0.30.9/src/changelog/v0.16.md000064400000000000000000000071311046102023000146630ustar 00000000000000## 0.16.2 - On Windows, non-resizable windows now have the maximization button disabled. This is consistent with behavior on macOS and popular X11 WMs. - Corrected incorrect `unreachable!` usage when guessing the DPI factor with no detected monitors. ## 0.16.1 - Added logging through `log`. Logging will become more extensive over time. - On X11 and Windows, the window's DPI factor is guessed before creating the window. This _greatly_ cuts back on unsightly auto-resizing that would occur immediately after window creation. - Fixed X11 backend compilation for environments where `c_char` is unsigned. ## 0.16.0 - Windows additionally has `WindowBuilderExt::with_no_redirection_bitmap`. - **Breaking:** Removed `VirtualKeyCode::LMenu` and `VirtualKeyCode::RMenu`; Windows now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead. - On X11, exiting fullscreen no longer leaves the window in the monitor's top left corner. - **Breaking:** `Window::hidpi_factor` has been renamed to `Window::get_hidpi_factor` for better consistency. `WindowEvent::HiDPIFactorChanged` has been renamed to `WindowEvent::HiDpiFactorChanged`. DPI factors are always represented as `f64` instead of `f32` now. - The Windows backend is now DPI aware. `WindowEvent::HiDpiFactorChanged` is implemented, and `MonitorId::get_hidpi_factor` and `Window::hidpi_factor` return accurate values. - Implemented `WindowEvent::HiDpiFactorChanged` on X11. - On macOS, `Window::set_cursor_position` is now relative to the client area. - On macOS, setting the maximum and minimum dimensions now applies to the client area dimensions rather than to the window dimensions. - On iOS, `MonitorId::get_dimensions` has been implemented and both `MonitorId::get_hidpi_factor` and `Window::get_hidpi_factor` return accurate values. - On Emscripten, `MonitorId::get_hidpi_factor` now returns the same value as `Window::get_hidpi_factor` (it previously would always return 1.0). - **Breaking:** The entire API for sizes, positions, etc. has changed. In the majority of cases, winit produces and consumes positions and sizes as `LogicalPosition` and `LogicalSize`, respectively. The notable exception is `MonitorId` methods, which deal in `PhysicalPosition` and `PhysicalSize`. See the documentation for specifics and explanations of the types. Additionally, winit automatically conserves logical size when the DPI factor changes. - **Breaking:** All deprecated methods have been removed. For `Window::platform_display` and `Window::platform_window`, switch to the appropriate platform-specific `WindowExt` methods. For `Window::get_inner_size_points` and `Window::get_inner_size_pixels`, use the `LogicalSize` returned by `Window::get_inner_size` and convert as needed. - HiDPI support for Wayland. - `EventsLoop::get_available_monitors` and `EventsLoop::get_primary_monitor` now have identical counterparts on `Window`, so this information can be acquired without an `EventsLoop` borrow. - `AvailableMonitorsIter` now implements `Debug`. - Fixed quirk on macOS where certain keys would generate characters at twice the normal rate when held down. - On X11, all event loops now share the same `XConnection`. - **Breaking:** `Window::set_cursor_state` and `CursorState` enum removed in favor of the more composable `Window::grab_cursor` and `Window::hide_cursor`. As a result, grabbing the cursor no longer automatically hides it; you must call both methods to retain the old behavior on Windows and macOS. `Cursor::NoneCursor` has been removed, as it's no longer useful. - **Breaking:** `Window::set_cursor_position` now returns `Result<(), String>`, thus allowing for `Box` conversion via `?`. winit-0.30.9/src/changelog/v0.17.md000064400000000000000000000024701046102023000146650ustar 00000000000000## 0.17.2 - On macOS, fix `` so applications receive the event. - On macOS, fix `` so applications receive the event. - On Wayland, key press events will now be repeated. ## 0.17.1 - On X11, prevent a compilation failure in release mode for versions of Rust greater than or equal to 1.30. - Fixed deadlock that broke fullscreen mode on Windows. ## 0.17.0 - Cocoa and core-graphics updates. - Fixed thread-safety issues in several `Window` functions on Windows. - On MacOS, the key state for modifiers key events is now properly set. - On iOS, the view is now set correctly. This makes it possible to render things (instead of being stuck on a black screen), and touch events work again. - Added NetBSD support. - **Breaking:** On iOS, `UIView` is now the default root view. `WindowBuilderExt::with_root_view_class` can be used to set the root view objective-c class to `GLKView` (OpenGLES) or `MTKView` (Metal/MoltenVK). - On iOS, the `UIApplication` is not started until `Window::new` is called. - Fixed thread unsafety with cursor hiding on macOS. - On iOS, fixed the size of the `JmpBuf` type used for `setjmp`/`longjmp` calls. Previously this was a buffer overflow on most architectures. - On Windows, use cached window DPI instead of repeatedly querying the system. This fixes sporadic crashes on Windows 7. winit-0.30.9/src/changelog/v0.18.md000064400000000000000000000104161046102023000146650ustar 00000000000000## 0.18.1 - On macOS, fix `Yen` (JIS) so applications receive the event. - On X11 with a tiling WM, fixed high CPU usage when moving windows across monitors. - On X11, fixed panic caused by dropping the window before running the event loop. - on macOS, added `WindowExt::set_simple_fullscreen` which does not require a separate space - Introduce `WindowBuilderExt::with_app_id` to allow setting the application ID on Wayland. - On Windows, catch panics in event loop child thread and forward them to the parent thread. This prevents an invocation of undefined behavior due to unwinding into foreign code. - On Windows, fix issue where resizing or moving window combined with grabbing the cursor would freeze program. - On Windows, fix issue where resizing or moving window would eat `Awakened` events. - On Windows, exiting fullscreen after entering fullscreen with disabled decorations no longer shrinks window. - On X11, fixed a segfault when using virtual monitors with XRandR. - Derive `Ord` and `PartialOrd` for `VirtualKeyCode` enum. - On Windows, fix issue where hovering or dropping a non file item would create a panic. - On Wayland, fix resizing and DPI calculation when a `wl_output` is removed without sending a `leave` event to the `wl_surface`, such as disconnecting a monitor from a laptop. - On Wayland, DPI calculation is handled by smithay-client-toolkit. - On X11, `WindowBuilder::with_min_dimensions` and `WindowBuilder::with_max_dimensions` now correctly account for DPI. - Added support for generating dummy `DeviceId`s and `WindowId`s to better support unit testing. - On macOS, fixed unsoundness in drag-and-drop that could result in drops being rejected. - On macOS, implemented `WindowEvent::Refresh`. - On macOS, all `MouseCursor` variants are now implemented and the cursor will no longer reset after unfocusing. - Removed minimum supported Rust version guarantee. ## 0.18.0 - **Breaking:** `image` crate upgraded to 0.20. This is exposed as part of the `icon_loading` API. - On Wayland, pointer events will now provide the current modifiers state. - On Wayland, titles will now be displayed in the window header decoration. - On Wayland, key repetition is now ended when keyboard loses focus. - On Wayland, windows will now use more stylish and modern client side decorations. - On Wayland, windows will use server-side decorations when available. - **Breaking:** Added support for F16-F24 keys (variants were added to the `VirtualKeyCode` enum). - Fixed graphical glitches when resizing on Wayland. - On Windows, fix freezes when performing certain actions after a window resize has been triggered. Reintroduces some visual artifacts when resizing. - Updated window manager hints under X11 to v1.5 of [Extended Window Manager Hints](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm140200472629520). - Added `WindowBuilderExt::with_gtk_theme_variant` to X11-specific `WindowBuilder` functions. - Fixed UTF8 handling bug in X11 `set_title` function. - On Windows, `Window::set_cursor` now applies immediately instead of requiring specific events to occur first. - On Windows, the `HoveredFile` and `HoveredFileCancelled` events are now implemented. - On Windows, fix `Window::set_maximized`. - On Windows 10, fix transparency (#260). - On macOS, fix modifiers during key repeat. - Implemented the `Debug` trait for `Window`, `EventsLoop`, `EventsLoopProxy` and `WindowBuilder`. - On X11, now a `Resized` event will always be generated after a DPI change to ensure the window's logical size is consistent with the new DPI. - Added further clarifications to the DPI docs. - On Linux, if neither X11 nor Wayland manage to initialize, the corresponding panic now consists of a single line only. - Add optional `serde` feature with implementations of `Serialize`/`Deserialize` for DPI types and various event types. - Add `PartialEq`, `Eq`, and `Hash` implementations on public types that could have them but were missing them. - On X11, drag-and-drop receiving an unsupported drop type can no longer cause the WM to freeze. - Fix issue whereby the OpenGL context would not appear at startup on macOS Mojave (#1069). - **Breaking:** Removed `From` impl from `ActivationPolicy` on macOS. - On macOS, the application can request the user's attention with `WindowExt::request_user_attention`. winit-0.30.9/src/changelog/v0.19.md000064400000000000000000000041221046102023000146630ustar 00000000000000## 0.19.1 - On Wayland, added a `get_wayland_display` function to `EventsLoopExt`. - On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus. - On macOS, fix command key event left and right reverse. - On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend. - On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes - On macOS, the numpad's subtract key has been added to the `Subtract` mapping - On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes - On Windows, fix icon not showing up in corner of window. - On X11, change DPI scaling factor behavior. First, winit tries to read it from "Xft.dpi" XResource, and uses DPI calculation from xrandr dimensions as fallback behavior. ## 0.19.0 - On X11, we will use the faster `XRRGetScreenResourcesCurrent` function instead of `XRRGetScreenResources` when available. - On macOS, fix keycodes being incorrect when using a non-US keyboard layout. - On Wayland, fix `with_title()` not setting the windows title - On Wayland, add `set_wayland_theme()` to control client decoration color theme - Added serde serialization to `os::unix::XWindowType`. - **Breaking:** Remove the `icon_loading` feature and the associated `image` dependency. - On X11, make event loop thread safe by replacing XNextEvent with select(2) and XCheckIfEvent - On Windows, fix malformed function pointer typecast that could invoke undefined behavior. - Refactored Windows state/flag-setting code. - On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on. - On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area. - On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled. - On Windows, ignore the AltGr key when populating the `ModifiersState` type. winit-0.30.9/src/changelog/v0.20.md000064400000000000000000000332331046102023000146600ustar 00000000000000## 0.20.0 - On X11, fix `ModifiersChanged` emitting incorrect modifier change events - **Breaking**: Overhaul how Winit handles DPI: - Window functions and events now return `PhysicalSize` instead of `LogicalSize`. - Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types. - `hidpi_factor` has been renamed to `scale_factor`. - `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS resizes the window in response to the change. - On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`. - `Size` and `Position` types are now generic over their exact pixel type. ## 0.20.0-alpha6 - On macOS, fix `set_cursor_visible` hides cursor outside of window. - On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size. - On macOS, fix error when `set_fullscreen` is called during fullscreen transition. - On all platforms except mobile and WASM, implement `Window::set_minimized`. - On X11, fix `CursorEntered` event being generated for non-winit windows. - On macOS, fix crash when starting maximized without decorations. - On macOS, fix application not terminating on `run_return`. - On Wayland, fix cursor icon updates on window borders when using CSD. - On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode. - On Windows, theme the title bar according to whether the system theme is "Light" or "Dark". - Added `WindowEvent::ThemeChanged` variant to handle changes to the system theme. Currently only implemented on Windows. - **Breaking**: Changes to the `RedrawRequested` event (#1041): - `RedrawRequested` has been moved from `WindowEvent` to `Event`. - `EventsCleared` has been renamed to `MainEventsCleared`. - `RedrawRequested` is now issued only after `MainEventsCleared`. - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. - Implement synthetic window focus key events on Windows. - **Breaking**: Change `ModifiersState` to a `bitflags` struct. - On Windows, implement `VirtualKeyCode` translation for `LWin` and `RWin`. - On Windows, fix closing the last opened window causing `DeviceEvent`s to stop getting emitted. - On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior. - Add `DeviceEvent::ModifiersChanged`. - Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`. - On X11, `WINIT_HIDPI_FACTOR` now dominates `Xft.dpi` when picking DPI factor for output. - On X11, add special value `randr` for `WINIT_HIDPI_FACTOR` to make winit use self computed DPI factor instead of the one from `Xft.dpi`. ## 0.20.0-alpha5 - On macOS, fix application termination on `ControlFlow::Exit` - On Windows, fix missing `ReceivedCharacter` events when Alt is held. - On macOS, stop emitting private corporate characters in `ReceivedCharacter` events. - On X11, fix misreporting DPI factor at startup. - On X11, fix events not being reported when using `run_return`. - On X11, fix key modifiers being incorrectly reported. - On X11, fix window creation hanging when another window is fullscreen. - On Windows, fix focusing unfocused windows when switching from fullscreen to windowed. - On X11, fix reporting incorrect DPI factor when waking from suspend. - Change `EventLoopClosed` to contain the original event. - **Breaking**: Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`, indicating that the event is generated by winit. - On X11, generate synthetic key events for keys held when a window gains or loses focus. - On X11, issue a `CursorMoved` event when a `Touch` event occurs, as X11 implicitly moves the cursor for such events. ## 0.20.0-alpha4 - Add web support via the 'stdweb' or 'web-sys' features - On Windows, implemented function to get HINSTANCE - On macOS, implement `run_return`. - On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`. - On X11, performance is improved when rapidly calling `Window::set_cursor_icon`. - On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized. - On Windows, unset `maximized` when manually changing the window's position or size. - On Windows, add touch pressure information for touch events. - On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`. - On Wayland, fix event processing sometimes stalling when using OpenGL with vsync. - Officially remove the Emscripten backend. - On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`. - On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. - On iOS, fix panic upon closing the app. - On X11, allow setting multiple `XWindowType`s. - On iOS, fix null window on initial `HiDpiFactorChanged` event. - On Windows, fix fullscreen window shrinking upon getting restored to a normal window. - On macOS, fix events not being emitted during modal loops, such as when windows are being resized by the user. - On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events. - Always dispatch a `RedrawRequested` event after creating a new window. - On X11, return dummy monitor data to avoid panicking when no monitors exist. - On X11, prevent stealing input focus when creating a new window. Only steal input focus when entering fullscreen mode. - On Wayland, fixed DeviceEvents for relative mouse movement is not always produced - On Wayland, add support for set_cursor_visible and set_cursor_grab. - On Wayland, fixed DeviceEvents for relative mouse movement is not always produced. - Removed `derivative` crate dependency. - On Wayland, add support for set_cursor_icon. - Use `impl Iterator` instead of `AvailableMonitorsIter` consistently. - On macOS, fix fullscreen state being updated after entering fullscreen instead of before, resulting in `Window::fullscreen` returning the old state in `Resized` events instead of reflecting the new fullscreen state - On X11, fix use-after-free during window creation - On Windows, disable monitor change keyboard shortcut while in exclusive fullscreen. - On Windows, ensure that changing a borderless fullscreen window's monitor via keyboard shortcuts keeps the window fullscreen on the new monitor. - Prevent `EventLoop::new` and `EventLoop::with_user_event` from getting called outside the main thread. - This is because some platforms cannot run the event loop outside the main thread. Preventing this reduces the potential for cross-platform compatibility gotchyas. - On Windows and Linux X11/Wayland, add platform-specific functions for creating an `EventLoop` outside the main thread. - On Wayland, drop resize events identical to the current window size. - On Windows, fix window rectangle not getting set correctly on high-DPI systems. ## 0.20.0-alpha3 - On macOS, drop the run closure on exit. - On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. - On X11, fix delayed events after window redraw. - On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface. - On Windows, screen saver won't start if the window is in fullscreen mode. - Change all occurrences of the `new_user_event` method to `with_user_event`. - On macOS, the dock and the menu bar are now hidden in fullscreen mode. - `Window::set_fullscreen` now takes `Option` where `Fullscreen` consists of `Fullscreen::Exclusive(VideoMode)` and `Fullscreen::Borderless(MonitorHandle)` variants. - Adds support for exclusive fullscreen mode. - On iOS, add support for hiding the home indicator. - On iOS, add support for deferring system gestures. - On iOS, fix a crash that occurred while acquiring a monitor's name. - On iOS, fix armv7-apple-ios compile target. - Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy`. - On iOS, disable overscan compensation for external displays (removes black bars surrounding the image). - On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait. - On iOS, add `set_prefers_status_bar_hidden` extension function instead of hijacking `set_decorations` for this purpose. - On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`. - On iOS, add touch pressure information for touch events. - Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms. - On macOS, fix the signature of `-[NSView drawRect:]`. - On iOS, fix the behavior of `ControlFlow::Poll`. It wasn't polling if that was the only mode ever used by the application. - On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number. - On iOS, RedrawRequested now works for gl/metal backed views. - On iOS, RedrawRequested is generally ordered after EventsCleared. ## 0.20.0-alpha2 - On X11, non-resizable windows now have maximize explicitly disabled. - On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile` and `WindowEvent::HoveredFile`. - On Mac, implement `DeviceEvent::Button`. - Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`. - On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor. - Revert the use of invisible surfaces in Wayland, which introduced graphical glitches with OpenGL (#835) - On X11, implement `_NET_WM_PING` to allow desktop environment to kill unresponsive programs. - On Windows, when a window is initially invisible, it won't take focus from the existing visible windows. - On Windows, fix multiple calls to `request_redraw` during `EventsCleared` sending multiple `RedrawRequested events.` - On Windows, fix edge case where `RedrawRequested` could be dispatched before input events in event loop iteration. - On Windows, fix timing issue that could cause events to be improperly dispatched after `RedrawRequested` but before `EventsCleared`. - On macOS, drop unused Metal dependency. - On Windows, fix the trail effect happening on transparent decorated windows. Borderless (or un-decorated) windows were not affected. - On Windows, fix `with_maximized` not properly setting window size to entire window. - On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`. ## 0.20.0-alpha1 - Changes below are considered **breaking**. - Change all occurrences of `EventsLoop` to `EventLoop`. - Previously flat API is now exposed through `event`, `event_loop`, `monitor`, and `window` modules. - `os` module changes: - Renamed to `platform`. - All traits now have platform-specific suffixes. - Exposes new `desktop` module on Windows, Mac, and Linux. - Changes to event loop types: - `EventLoopProxy::wakeup` has been removed in favor of `send_event`. - **Major:** New `run` method drives winit event loop. - Returns `!` to ensure API behaves identically across all supported platforms. - This allows `emscripten` implementation to work without lying about the API. - `ControlFlow`'s variants have been replaced with `Wait`, `WaitUntil(Instant)`, `Poll`, and `Exit`. - Is read after `EventsCleared` is processed. - `Wait` waits until new events are available. - `WaitUntil` waits until either new events are available or the provided time has been reached. - `Poll` instantly resumes the event loop. - `Exit` aborts the event loop. - Takes a closure that implements `'static + FnMut(Event, &EventLoop, &mut ControlFlow)`. - `&EventLoop` is provided to allow new `Window`s to be created. - **Major:** `platform::desktop` module exposes `EventLoopExtDesktop` trait with `run_return` method. - Behaves identically to `run`, but returns control flow to the calling context and can take non-`'static` closures. - `EventLoop`'s `poll_events` and `run_forever` methods have been removed in favor of `run` and `run_return`. - Changes to events: - Remove `Event::Awakened` in favor of `Event::UserEvent(T)`. - Can be sent with `EventLoopProxy::send_event`. - Rename `WindowEvent::Refresh` to `WindowEvent::RedrawRequested`. - `RedrawRequested` can be sent by the user with the `Window::request_redraw` method. - `EventLoop`, `EventLoopProxy`, and `Event` are now generic over `T`, for use in `UserEvent`. - **Major:** Add `NewEvents(StartCause)`, `EventsCleared`, and `LoopDestroyed` variants to `Event`. - `NewEvents` is emitted when new events are ready to be processed by event loop. - `StartCause` describes why new events are available, with `ResumeTimeReached`, `Poll`, `WaitCancelled`, and `Init` (sent once at start of loop). - `EventsCleared` is emitted when all available events have been processed. - Can be used to perform logic that depends on all events being processed (e.g. an iteration of a game loop). - `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit. - Rename `MonitorId` to `MonitorHandle`. - Removed `serde` implementations from `ControlFlow`. - Rename several functions to improve both internal consistency and compliance with Rust API guidelines. - Remove `WindowBuilder::multitouch` field, since it was only implemented on a few platforms. Multitouch is always enabled now. - **Breaking:** On macOS, change `ns` identifiers to use snake_case for consistency with iOS's `ui` identifiers. - Add `MonitorHandle::video_modes` method for retrieving supported video modes for the given monitor. - On Wayland, the window now exists even if nothing has been drawn. - On Windows, fix initial dimensions of a fullscreen window. - On Windows, Fix transparent borderless windows rendering wrong. winit-0.30.9/src/changelog/v0.21.md000064400000000000000000000025021046102023000146540ustar 00000000000000## 0.21.0 - On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows. - On macOS, fix set_minimized(true) works only with decorations. - On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`. - On macOS, fix not sending ReceivedCharacter event for specific keys combinations. - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. - **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends - On Wayland, fix coordinates in mouse events when scale factor isn't 1 - On Web, add the ability to provide a custom canvas - **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration winit-0.30.9/src/changelog/v0.22.md000064400000000000000000000037761046102023000146730ustar 00000000000000## 0.22.2 - Added Clone implementation for 'static events. - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. - On Android, minimal platform support. - On iOS, touch positions are now properly converted to physical pixels. - On macOS, updated core-* dependencies and cocoa ## 0.22.1 - On X11, fix `ResumeTimeReached` being fired too early. - On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling - On macOS, fix `EventLoopProxy` leaking memory for every instance. ## 0.22.0 - On Windows, fix minor timing issue in wait_until_time_or_msg - On Windows, rework handling of request_redraw() to address panics. - On macOS, fix `set_simple_screen` to remember frame excluding title bar. - On Wayland, fix coordinates in touch events when scale factor isn't 1. - On Wayland, fix color from `close_button_icon_color` not applying. - Ignore locale if unsupported by X11 backend - On Wayland, Add HiDPI cursor support - On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change. - Fix `Event::to_static` returning `None` for user events. - On Wayland, Hide CSD for fullscreen windows. - On Windows, ignore spurious mouse move messages. - **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`. - On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource - Add `BadIcon::OsError` variant for when OS icon functionality fails - On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode - Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations. - on macOS, fix incorrect ReceivedCharacter events for some key combinations. - **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`. - On macOS, a mouse motion event is now generated before every mouse click. winit-0.30.9/src/changelog/v0.23.md000064400000000000000000000120211046102023000146530ustar 00000000000000## 0.23.0 - On iOS, fixed support for the "Debug View Hierarchy" feature in Xcode. - On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop. - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas - On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling - On Windows, drag and drop is now optional (enabled by default) and can be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`. - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. - On android added support for `run_return`. - On MacOS, Fixed fullscreen and dialog support for `run_return`. - On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops. - On Web, use mouse events if pointer events aren't supported. This affects Safari. - On Windows, `set_ime_position` is now a no-op instead of a runtime crash. - On Android, `set_fullscreen` is now a no-op instead of a runtime crash. - On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. - On Android, fix `ControlFlow::Poll` not polling the Android event queue. - On macOS, add `NSWindow.hasShadow` support. - On Web, fix vertical mouse wheel scrolling being inverted. - On Web, implement mouse capturing for click-dragging out of the canvas. - On Web, fix `ControlFlow::Exit` not properly handled. - On Web (web-sys only), send `WindowEvent::ScaleFactorChanged` event when `window.devicePixelRatio` is changed. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. - **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract`. - On macOS, fix inverted horizontal scroll. - **Breaking:** `current_monitor` now returns `Option`. - **Breaking:** `primary_monitor` now returns `Option`. - On macOS, updated core-* dependencies and cocoa. - Bump `parking_lot` to 0.11 - On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute. - On iOS, fixed starting the app in landscape where the view still had portrait dimensions. - Deprecate the stdweb backend, to be removed in a future release - **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`. - Added `Asterisk` and `Plus` virtual key codes. - On Web (web-sys only), the `Event::LoopDestroyed` event is correctly emitted when leaving the page. - On Web, the `WindowEvent::Destroyed` event now gets emitted when a `Window` is dropped. - On Web (web-sys only), the event listeners are now removed when a `Window` is dropped or when the event loop is destroyed. - On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed. - **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped. - On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called. - **Breaking:** `Fullscreen` enum now uses `Borderless(Option)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor. - On MacOS, fix `WindowEvent::Moved` ignoring the scale factor. - On Wayland, add missing virtual keycodes. - On Wayland, implement proper `set_cursor_grab`. - On Wayland, the cursor will use similar icons if the requested one isn't available. - On Wayland, right clicking on client side decorations will request application menu. - On Wayland, fix tracking of window size after state changes. - On Wayland, fix client side decorations not being hidden properly in fullscreen. - On Wayland, fix incorrect size event when entering fullscreen with client side decorations. - On Wayland, fix `resizable` attribute not being applied properly on startup. - On Wayland, fix disabled repeat rate not being handled. - On Wayland, fix decoration buttons not working after tty switch. - On Wayland, fix scaling not being applied on output re-enable. - On Wayland, fix crash when `XCURSOR_SIZE` is `0`. - On Wayland, fix pointer getting created in some cases without pointer capability. - On Wayland, on kwin, fix space between window and decorations on startup. - **Breaking:** On Wayland, `Theme` trait was reworked. - On Wayland, disable maximize button for non-resizable window. - On Wayland, added support for `set_ime_position`. - On Wayland, fix crash on startup since GNOME 3.37.90. - On X11, fix incorrect modifiers state on startup. winit-0.30.9/src/changelog/v0.24.md000064400000000000000000000036621046102023000146670ustar 00000000000000## 0.24.0 - On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory. - On Windows, implement `Window::set_ime_position`. - **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`. - **Breaking:** On Windows, renamed `WindowBuilderExtWindows::is_dark_mode` to `theme`. - On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme. - On Windows, fix bug causing message boxes to appear delayed. - On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. - On Windows, fix Alt-Tab behaviour by removing borderless fullscreen "always on top" flag. - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. - **Breaking:** On Windows, include prefix byte in scancodes. - On Wayland, fix window not being resizeable when using `WindowBuilder::with_min_inner_size`. - On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland. - On Windows, fix use-after-free crash during window destruction. - On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input. - On macOS, fix compilation when targeting aarch64. - On X11, fix `Window::request_redraw` not waking the event loop. - On Wayland, the keypad arrow keys are now recognized. - **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`. - Added `request_user_attention` method to `Window`. - **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`. - **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`. - On Wayland, default font size in CSD increased from 11 to 17. - On Windows, fix bug causing message boxes to appear delayed. - On Android, support multi-touch. - On Wayland, extra mouse buttons are not dropped anymore. - **Breaking**: `MouseButton::Other` now uses `u16`. winit-0.30.9/src/changelog/v0.25.md000064400000000000000000000052111046102023000146600ustar 00000000000000## 0.25.0 - **Breaking:** On macOS, replace `WindowBuilderExtMacOS::with_activation_policy` with `EventLoopExtMacOS::set_activation_policy` - On macOS, wait with activating the application until the application has initialized. - On macOS, fix creating new windows when the application has a main menu. - On Windows, fix fractional deltas for mouse wheel device events. - On macOS, fix segmentation fault after dropping the main window. - On Android, `InputEvent::KeyEvent` is partially implemented providing the key scancode. - Added `is_maximized` method to `Window`. - On Windows, fix bug where clicking the decoration bar would make the cursor blink. - On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor. - On macOS, wake up the event loop immediately when a redraw is requested. - On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600). - On Windows, fix bug causing mouse capture to not be released. - On Windows, fix fullscreen not preserving minimized/maximized state. - On Android, unimplemented events are marked as unhandled on the native event loop. - On Windows, added `WindowBuilderExtWindows::with_menu` to set a custom menu at window creation time. - On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`. - On macOS, fix objects captured by the event loop closure not being dropped on panic. - On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme. - On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements. - Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11. - Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland. - On X11, bump `mio` to 0.7. - On Windows, added `WindowBuilderExtWindows::with_owner_window` to allow creating popup windows. - On Windows, added `WindowExtWindows::set_enable` to allow creating modal popup windows. - On macOS, emit `RedrawRequested` events immediately while the window is being resized. - Implement `Default`, `Hash`, and `Eq` for `LogicalPosition`, `PhysicalPosition`, `LogicalSize`, and `PhysicalSize`. - On macOS, initialize the Menu Bar with minimal defaults. (Can be prevented using `enable_default_menu_creation`) - On macOS, change the default behavior for first click when the window was unfocused. Now the window becomes focused and then emits a `MouseInput` event on a "first mouse click". - Implement mint (math interoperability standard types) conversions (under feature flag `mint`). winit-0.30.9/src/changelog/v0.26.md000064400000000000000000000052711046102023000146670ustar 00000000000000## 0.26.1 - Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions. - On Web, implement cursor grabbing through the pointer lock API. - On X11, add mappings for numpad comma, numpad enter, numlock and pause. - On macOS, fix Pinyin IME input by reverting a change that intended to improve IME. - On Windows, fix a crash with transparent windows on Windows 11. ## 0.26.0 - Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74). Note that you might have to run `cargo update -p raw-window-handle` after upgrading. - On X11, bump `mio` to 0.8. - On Android, fixed `WindowExtAndroid::config` initially returning an empty `Configuration`. - On Android, fixed `Window::scale_factor` and `MonitorHandle::scale_factor` initially always returning 1.0. - On X11, select an appropriate visual for transparency if is requested - On Wayland and X11, fix diagonal window resize cursor orientation. - On macOS, drop the event callback before exiting. - On Android, implement `Window::request_redraw` - **Breaking:** On Web, remove the `stdweb` backend. - Added `Window::focus_window`to bring the window to the front and set input focus. - On Wayland and X11, implement `is_maximized` method on `Window`. - On Windows, prevent ghost window from showing up in the taskbar after either several hours of use or restarting `explorer.exe`. - On macOS, fix issue where `ReceivedCharacter` was not being emitted during some key repeat events. - On Wayland, load cursor icons `hand2` and `hand1` for `CursorIcon::Hand`. - **Breaking:** On Wayland, Theme trait and its support types are dropped. - On Wayland, bump `smithay-client-toolkit` to 0.15.1. - On Wayland, implement `request_user_attention` with `xdg_activation_v1`. - On X11, emit missing `WindowEvent::ScaleFactorChanged` when the only monitor gets reconnected. - On X11, if RANDR based scale factor is higher than 20 reset it to 1 - On Wayland, add an enabled-by-default feature called `wayland-dlopen` so users can opt out of using `dlopen` to load system libraries. - **Breaking:** On Android, bump `ndk` and `ndk-glue` to 0.5. - On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`. - On macOS, fix native file dialogs hanging the event loop. - On Wayland, implement a workaround for wrong configure size when using `xdg_decoration` in `kwin_wayland` - On macOS, fix an issue that prevented the menu bar from showing in borderless fullscreen mode. - On X11, EINTR while polling for events no longer causes a panic. Instead it will be treated as a spurious wakeup. winit-0.30.9/src/changelog/v0.27.md000064400000000000000000000213051046102023000146640ustar 00000000000000## 0.27.5 - On Wayland, fix byte offset in `Ime::Preedit` pointing to invalid bytes. ## 0.27.4 - On Windows, emit `ReceivedCharacter` events on system keybindings. - On Windows, fixed focus event emission on minimize. - On X11, fixed IME crashing during reload. ## 0.27.3 - On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window. - On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled. - On Windows, fixed ALT+Space shortcut to open window menu. - On Wayland, fixed `Ime::Preedit` not being sent on IME reset. - Fixed unbound version specified for `raw-window-handle` leading to compilation failures. - Empty `Ime::Preedit` event will be sent before `Ime::Commit` to help clearing preedit. - On X11, fixed IME context picking by querying for supported styles beforehand. ## 0.27.2 - On macOS, fixed touch phase reporting when scrolling. - On X11, fix min, max and resize increment hints not persisting for resizable windows (e.g. on DPI change). - On Windows, respect min/max inner sizes when creating the window. - For backwards compatibility, `Window` now (additionally) implements the old version (`0.4`) of the `HasRawWindowHandle` trait - On Windows, added support for `EventLoopWindowTarget::set_device_event_filter`. - On Wayland, fix user requested `WindowEvent::RedrawRequested` being delayed by a frame. ## 0.27.1 - The minimum supported Rust version was lowered to `1.57.0` and now explicitly tested. - On X11, fix crash on start due to inability to create an IME context without any preedit. ## 0.27.0 - On Windows, fix hiding a maximized window. - On Android, `ndk-glue`'s `NativeWindow` lock is now held between `Event::Resumed` and `Event::Suspended`. - On Web, added `EventLoopExtWebSys` with a `spawn` method to start the event loop without throwing an exception. - Added `WindowEvent::Occluded(bool)`, currently implemented on macOS and X11. - On X11, fix events for caps lock key not being sent - Build docs on `docs.rs` for iOS and Android as well. - **Breaking:** Removed the `WindowAttributes` struct, since all its functionality is accessible from `WindowBuilder`. - Added `WindowBuilder::transparent` getter to check if the user set `transparent` attribute. - On macOS, Fix emitting `Event::LoopDestroyed` on CMD+Q. - On macOS, fixed an issue where having multiple windows would prevent run_return from ever returning. - On Wayland, fix bug where the cursor wouldn't hide in GNOME. - On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events. - On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`. - On Windows, added `EventLoopBuilderExtWindows::with_msg_hook`. - On Windows, remove internally unique DC per window. - On macOS, remove the need to call `set_ime_position` after moving the window. - Added `Window::is_visible`. - Added `Window::is_resizable`. - Added `Window::is_decorated`. - On X11, fix for repeated event loop iteration when `ControlFlow` was `Wait` - On X11, fix scale factor calculation when the only monitor is reconnected - On Wayland, report unaccelerated mouse deltas in `DeviceEvent::MouseMotion`. - On Web, a focused event is manually generated when a click occurs to emulate behaviour of other backends. - **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`. - Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable. - **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs. - Add `EventLoopBuilder`, which allows you to create and tweak the settings of an event loop before creating it. - Deprecated `EventLoop::with_user_event`; use `EventLoopBuilder::with_user_event` instead. - **Breaking:** Replaced `EventLoopExtMacOS` with `EventLoopBuilderExtMacOS` (which also has renamed methods). - **Breaking:** Replaced `EventLoopExtWindows` with `EventLoopBuilderExtWindows` (which also has renamed methods). - **Breaking:** Replaced `EventLoopExtUnix` with `EventLoopBuilderExtUnix` (which also has renamed methods). - **Breaking:** The platform specific extensions for Windows `winit::platform::windows` have changed. All `HANDLE`-like types e.g. `HWND` and `HMENU` were converted from winapi types or `*mut c_void` to `isize`. This was done to be consistent with the type definitions in windows-sys and to not expose internal dependencies. - The internal bindings to the [Windows API](https://docs.microsoft.com/en-us/windows/) were changed from the unofficial [winapi](https://github.com/retep998/winapi-rs) bindings to the official Microsoft [windows-sys](https://github.com/microsoft/windows-rs) bindings. - On Wayland, fix polling during consecutive `EventLoop::run_return` invocations. - On Windows, fix race issue creating fullscreen windows with `WindowBuilder::with_fullscreen` - On Android, `virtual_keycode` for `KeyboardInput` events is now filled in where a suitable match is found. - Added helper methods on `ControlFlow` to set its value. - On Wayland, fix `TouchPhase::Ended` always reporting the location of the first touch down, unless the compositor sent a cancel or frame event. - On iOS, send `RedrawEventsCleared` even if there are no redraw events, consistent with other platforms. - **Breaking:** Replaced `Window::with_app_id` and `Window::with_class` with `Window::with_name` on `WindowBuilderExtUnix`. - On Wayland, fallback CSD was replaced with proper one: - `WindowBuilderExtUnix::with_wayland_csd_theme` to set color theme in builder. - `WindowExtUnix::wayland_set_csd_theme` to set color theme when creating a window. - `WINIT_WAYLAND_CSD_THEME` env variable was added, it can be used to set "dark"/"light" theme in apps that don't expose theme setting. - `wayland-csd-adwaita` feature that enables proper CSD with title rendering using FreeType system library. - `wayland-csd-adwaita-notitle` feature that enables CSD but without title rendering. - On Wayland and X11, fix window not resizing with `Window::set_inner_size` after calling `Window:set_resizable(false)`. - On Windows, fix wrong fullscreen monitors being recognized when handling WM_WINDOWPOSCHANGING messages - **Breaking:** Added new `WindowEvent::Ime` supported on desktop platforms. - Added `Window::set_ime_allowed` supported on desktop platforms. - **Breaking:** IME input on desktop platforms won't be received unless it's explicitly allowed via `Window::set_ime_allowed` and new `WindowEvent::Ime` events are handled. - On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`. - **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level. - Implemented `Default` on `EventLoop<()>`. - Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`. - **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior. - On Wayland, add support for `Window::set_cursor_position`. - Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value. - `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once. - Added `From` for `WindowId` and `From` for `u64`. - Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate. - **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision. - On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated. - On Windows, fix focus events being sent to inactive windows. - **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`. - On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors coming from Xlib. - On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`. - All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability. - **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down. - On MacOS, fix deadlock when calling `set_maximized` from event loop. winit-0.30.9/src/changelog/v0.28.md000064400000000000000000000157341046102023000146760ustar 00000000000000## 0.28.7 - Fix window size sometimes being invalid when resizing on macOS 14 Sonoma. ## 0.28.6 - On macOS, fixed memory leak when getting monitor handle. - On macOS, fix `Backspace` being emitted when clearing preedit with it. ## 0.28.5 - On macOS, fix `key_up` being ignored when `Ime` is disabled. ## 0.28.4 - On macOS, fix empty marked text blocking regular input. - On macOS, fix potential panic when getting refresh rate. - On macOS, fix crash when calling `Window::set_ime_position` from another thread. ## 0.28.3 - Fix macOS memory leaks. ## 0.28.2 - Implement `HasRawDisplayHandle` for `EventLoop`. - On macOS, set resize increments only for live resizes. - On Wayland, fix rare crash on DPI change - Web: Added support for `Window::theme`. - On Wayland, fix rounding issues when doing resize. - On macOS, fix wrong focused state on startup. - On Windows, fix crash on setting taskbar when using Visual Studio debugger. - On macOS, resize simple fullscreen windows on windowDidChangeScreen events. ## 0.28.1 - On Wayland, fix crash when dropping a window in multi-window setup. ## 0.28.0 - On macOS, fixed `Ime::Commit` persisting for all input after interacting with `Ime`. - On macOS, added `WindowExtMacOS::option_as_alt` and `WindowExtMacOS::set_option_as_alt`. - On Windows, fix window size for maximized, undecorated windows. - On Windows and macOS, add `WindowBuilder::with_active`. - Add `Window::is_minimized`. - On X11, fix errors handled during `register_xlib_error_hook` invocation bleeding into winit. - Add `Window::has_focus`. - On Windows, fix `Window::set_minimized(false)` not working for windows minimized by `Win + D` hotkey. - **Breaking:** On Web, touch input no longer fires `WindowEvent::Cursor*`, `WindowEvent::MouseInput`, or `DeviceEvent::MouseMotion` like other platforms, but instead it fires `WindowEvent::Touch`. - **Breaking:** Removed platform specific `WindowBuilder::with_parent` API in favor of `WindowBuilder::with_parent_window`. - On Windows, retain `WS_MAXIMIZE` window style when un-minimizing a maximized window. - On Windows, fix left mouse button release event not being sent after `Window::drag_window`. - On macOS, run most actions on the main thread, which is strictly more correct, but might make multithreaded applications block slightly more. - On macOS, fix panic when getting current monitor without any monitor attached. - On Windows and MacOS, add API to enable/disable window buttons (close, minimize, ...etc). - On Windows, macOS, X11 and Wayland, add `Window::set_theme`. - **Breaking:** Remove `WindowExtWayland::wayland_set_csd_theme` and `WindowBuilderExtX11::with_gtk_theme_variant`. - On Windows, revert window background to an empty brush to avoid white flashes when changing scaling. - **Breaking:** Removed `Window::set_always_on_top` and related APIs in favor of `Window::set_window_level`. - On Windows, MacOS and X11, add always on bottom APIs. - On Windows, fix the value in `MouseButton::Other`. - On macOS, add `WindowExtMacOS::is_document_edited` and `WindowExtMacOS::set_document_edited` APIs. - **Breaking:** Removed `WindowBuilderExtIOS::with_root_view_class`; instead, you should use `[[view layer] addSublayer: ...]` to add an instance of the desired layer class (e.g. `CAEAGLLayer` or `CAMetalLayer`). See `vulkano-win` or `wgpu` for examples of this. - On MacOS and Windows, add `Window::set_content_protected`. - On MacOS, add `EventLoopBuilderExtMacOS::with_activate_ignoring_other_apps`. - On Windows, fix icons specified on `WindowBuilder` not taking effect for windows created after the first one. - On Windows and macOS, add `Window::title` to query the current window title. - On Windows, fix focusing menubar when pressing `Alt`. - On MacOS, made `accepts_first_mouse` configurable. - Migrated `WindowBuilderExtUnix::with_resize_increments` to `WindowBuilder`. - Added `Window::resize_increments`/`Window::set_resize_increments` to update resize increments at runtime for X11/macOS. - macOS/iOS: Use `objc2` instead of `objc` internally. - **Breaking:** Bump MSRV from `1.57` to `1.60`. - **Breaking:** Split the `platform::unix` module into `platform::x11` and `platform::wayland`. The extension types are similarly renamed. - **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`. - Removed `parking_lot` dependency. - **Breaking:** On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. Also add support for touchpad smart-magnification gesture with a new event `WindowEvent::SmartMagnify`. - **Breaking:** On web, the `WindowBuilderExtWebSys::with_prevent_default` setting (enabled by default), now additionally prevents scrolling of the webpage in mobile browsers, previously it only disabled scrolling on desktop. - On Wayland, `wayland-csd-adwaita` now uses `ab_glyph` instead of `crossfont` to render the title for decorations. - On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations. - On Wayland, if not otherwise specified use upstream automatic CSD theme selection. - On X11, added `WindowExtX11::with_parent` to create child windows. - Added support for `WindowBuilder::with_theme` and `Window::theme` to support per-window dark/light/system theme configuration on macos, windows and wayland. - On macOS, added support for `WindowEvent::ThemeChanged`. - **Breaking:** Removed `WindowBuilderExtWindows::with_theme` and `WindowBuilderExtWayland::with_wayland_csd_theme` in favour of `WindowBuilder::with_theme`. - **Breaking:** Removed `WindowExtWindows::theme` in favour of `Window::theme`. - Enabled `doc_auto_cfg` when generating docs on docs.rs for feature labels. - **Breaking:** On Android, switched to using [`android-activity`](https://github.com/rib/android-activity) crate as a glue layer instead of [`ndk-glue`](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue). See [README.md#Android](https://github.com/rust-windowing/winit#Android) for more details. ([#2444](https://github.com/rust-windowing/winit/pull/2444)) - **Breaking:** Removed support for `raw-window-handle` version `0.4` - On Wayland, `RedrawRequested` not emitted during resize. - Add a `set_wait_timeout` function to `ControlFlow` to allow waiting for a `Duration`. - **Breaking:** Remove the unstable `xlib_xconnection()` function from the private interface. - Added Orbital support for Redox OS - On X11, added `drag_resize_window` method. - Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS. - On macOS, fix the mouse buttons other than left/right/middle being reported as middle. - On Wayland, support fractional scaling via the wp-fractional-scale protocol. - On web, fix removal of mouse event listeners from the global object upon window destruction. - Add WindowAttributes getter to WindowBuilder to allow introspection of default values. - Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only. winit-0.30.9/src/changelog/v0.29.md000064400000000000000000000442771046102023000147030ustar 00000000000000## 0.29.15 - On X11, fix crash due to xsettings query on systems with incomplete xsettings. ## 0.29.14 - On X11/Wayland, fix `text` and `text_with_all_modifiers` not being `None` during compose. - On Wayland, don't reapply cursor grab when unchanged. - On X11, fix a bug where some mouse events would be unexpectedly filtered out. ## 0.29.13 - On Web, fix possible crash with `ControlFlow::Wait` and `ControlFlow::WaitUntil`. ## 0.29.12 - On X11, fix use after free during xinput2 handling. - On X11, filter close to zero values in mouse device events ## 0.29.11 - Fix compatibility with 32-bit platforms without 64-bit atomics. - On macOS, fix incorrect IME cursor rect origin. - On Windows, fixed a race condition when sending an event through the loop proxy. - On X11, fix swapped instance and general class names. - On X11, don't require XIM to run. - On X11, fix xkb state not being updated correctly sometimes leading to wrong input. - On X11, reload dpi on `_XSETTINGS_SETTINGS` update. - On X11, fix deadlock when adjusting DPI and resizing at the same time. - On Wayland, disable `Occluded` event handling. - On Wayland, fix DeviceEvent::Motion not being sent - On Wayland, fix `Focused(false)` being send when other seats still have window focused. - On Wayland, fix `Window::set_{min,max}_inner_size` not always applied. - On Wayland, fix title in CSD not updated from `AboutToWait`. - On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop. - On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform. - On Orbital, fix `logical_key` and `text` not reported in `KeyEvent`. - On Orbital, implement `KeyEventExtModifierSupplement`. - On Orbital, map keys to `NamedKey` when possible. - On Orbital, implement `set_cursor_grab`. - On Orbital, implement `set_cursor_visible`. - On Orbital, implement `drag_window`. - On Orbital, implement `drag_resize_window`. - On Orbital, implement `set_transparent`. - On Orbital, implement `set_visible`. - On Orbital, implement `is_visible`. - On Orbital, implement `set_resizable`. - On Orbital, implement `is_resizable`. - On Orbital, implement `set_maximized`. - On Orbital, implement `is_maximized`. - On Orbital, implement `set_decorations`. - On Orbital, implement `is_decorated`. - On Orbital, implement `set_window_level`. - On Orbital, emit `DeviceEvent::MouseMotion`. ## 0.29.10 - On Web, account for canvas being focused already before event loop starts. - On Web, increase cursor position accuracy. ## 0.29.9 - On X11, fix `NotSupported` error not propagated when creating event loop. - On Wayland, fix resize not issued when scale changes - On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`. - On macOS, report correct logical key when Ctrl or Cmd is pressed. ## 0.29.8 - On X11, fix IME input lagging behind. - On X11, fix `ModifiersChanged` not sent from xdotool-like input - On X11, fix keymap not updated from xmodmap. - On X11, reduce the amount of time spent fetching screen resources. - On Wayland, fix `Window::request_inner_size` being overwritten by resize. - On Wayland, fix `Window::inner_size` not using the correct rounding. ## 0.29.7 - On X11, fix `Xft.dpi` reload during runtime. - On X11, fix window minimize. ## 0.29.6 - On Web, fix context menu not being disabled by `with_prevent_default(true)`. - On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window. - Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation ## 0.29.5 - On macOS, remove spurious error logging when handling `Fn`. - On X11, fix an issue where floating point data from the server is misinterpreted during a drag and drop operation. - On X11, fix a bug where focusing the window would panic. - On macOS, fix `refresh_rate_millihertz`. - On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported. - On X11, fix `Xft.dpi` detection from Xresources. - On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`. - On Wayland, fix resize being sent on focus change. - On Windows, fix `set_ime_cursor_area`. ## 0.29.4 - Fix crash when running iOS app on macOS. - On X11, check common alternative cursor names when loading cursor. - On X11, reload the DPI after a property change event. - On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread. - On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account. - On macOS, send a `Resized` event after each `ScaleFactorChanged` event. - On Wayland, fix `wl_surface` being destroyed before associated objects. - On macOS, fix assertion when pressing `Fn` key. - On Windows, add `WindowBuilderExtWindows::with_clip_children` to control `WS_CLIPCHILDREN` style. ## 0.29.3 - On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible. - On Wayland, fix `RedrawRequested` being always sent without decorations and `sctk-adwaita` feature. - On Wayland, ignore resize requests when the window is fully tiled. - On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size. - On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`. - On Windows, add support for `Window::set_transparent`. - On macOS, fix deadlock when entering a nested event loop from an event handler. - On macOS, add support for `Window::set_blur`. ## 0.29.2 - **Breaking:** Bump MSRV from `1.60` to `1.65`. - **Breaking:** Add `Event::MemoryWarning`; implemented on iOS/Android. - **Breaking:** Bump `ndk` version to `0.8.0`, ndk-sys to `0.5.0`, `android-activity` to `0.5.0`. - **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`. - **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`. - **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely. - **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`. - **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result` - **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables. - **Breaking:** on Wayland, dispatching user created Wayland queue won't wake up the loop unless winit has event to send back. - **Breaking:** remove `DeviceEvent::Text`. - **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`. - **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately. - **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more. - **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size. - **Breaking** `run() -> !` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767)) - **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_on_demand` ([#2767](https://github.com/rust-windowing/winit/pull/2767)) - `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`. - On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()` - **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900)) - **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900)) - **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900)) - **Breaking:** Remove all deprecated `modifiers` fields. - **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants. - **Breaking** Add `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900)) - **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`. - **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone. - **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer. - **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events on Wayland, X11, Windows, macOS and Web. - **Breaking:** On Web, `instant` is now replaced by `web_time`. - **Breaking:** On Web, dropped support for Safari versions below 13.1. - **Breaking:** On Web, the canvas output bitmap size is no longer adjusted. - **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to the canvas size will be reported through `WindowEvent::Resized`. - **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types. - **Breaking:** `CursorIcon::Arrow` was removed. - **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`. - **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate. - **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`. - **Breaking:** Overhaul keyboard input handling. - Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`. - Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`. - Change `Event::Key` to contain a `RawKeyEvent`. - Remove `Event::ReceivedCharacter`. In its place, you should use `KeyEvent.text` in combination with `WindowEvent::Ime`. - Replace `VirtualKeyCode` with the `Key` enum. - Replace `ScanCode` with the `KeyCode` enum. - Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`. - Add `PhysicalKey` wrapping `KeyCode` and `NativeKeyCode`. - Add `KeyCode` to refer to keys (roughly) by their physical location. - Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't understand. - Add `Key` to represent the keys after they've been interpreted by the active (software) keyboard layout. - Add `NamedKey` to represent the categorized keys. - Add `NativeKey` to represent raw `Key`s which Winit doesn't understand. - Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing, but can appear simultaneously in different spots on the same keyboard layout. - Add `Window::reset_dead_keys` to enable application-controlled cancellation of dead key sequences. - Add `KeyEventExtModifierSupplement` to expose additional (and less portable) interpretations of a given key-press. - Add `PhysicalKeyExtScancode`, which lets you convert between scancodes and `PhysicalKey`. - `ModifiersChanged` now uses dedicated `Modifiers` struct. - Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead: - `platform::windows::HINSTANCE`. - `WindowExtWindows::hinstance`. - `WindowExtWindows::hwnd`. - `WindowExtIOS::ui_window`. - `WindowExtIOS::ui_view_controller`. - `WindowExtIOS::ui_view`. - `WindowExtMacOS::ns_window`. - `WindowExtMacOS::ns_view`. - `EventLoopWindowTargetExtWayland::wayland_display`. - `WindowExtWayland::wayland_surface`. - `WindowExtWayland::wayland_display`. - `WindowExtX11::xlib_window`. - `WindowExtX11::xlib_display`. - `WindowExtX11::xlib_screen_id`. - `WindowExtX11::xcb_connection`. - Reexport `raw-window-handle` in `window` module. - Add `ElementState::is_pressed`. - Add `Window::pre_present_notify` to notify winit before presenting to the windowing system. - Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now. - Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now. - Implement `AsFd`/`AsRawFd` for `EventLoop` on X11 and Wayland. - Implement `PartialOrd` and `Ord` for `MouseButton`. - Implement `PartialOrd` and `Ord` on types in the `dpi` module. - Make `WindowBuilder` `Send + Sync`. - Make iOS `MonitorHandle` and `VideoMode` usable from other threads. - Make iOS windows usable from other threads. - On Android, add force data to touch events. - On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually. - On Android, fix `DeviceId` to contain device id's. - On Orbital, fix `ModifiersChanged` not being sent. - On Wayland, `Window::outer_size` now accounts for **client side** decorations. - On Wayland, add `Window::drag_resize_window` method. - On Wayland, remove `WINIT_WAYLAND_CSD_THEME` variable. - On Wayland, fix `TouchPhase::Canceled` being sent for moved events. - On Wayland, fix forward compatibility issues. - On Wayland, fix initial window size not restored for maximized/fullscreened on startup window. - On Wayland, fix maximized startup not taking full size on GNOME. - On Wayland, fix maximized window creation and window geometry handling. - On Wayland, fix window not checking that it actually got initial configure event. - On Wayland, make double clicking and moving the CSD frame more reliable. - On Wayland, support `Occluded` event with xdg-shell v6 - On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor. - On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback. - On Web, `EventLoopProxy` now implements `Send`. - On Web, `Window` now implements `Send` and `Sync`. - On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position. - On Web, add Fullscreen API compatibility for Safari. - On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and `DeviceEvent::Key` support. - On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback. - On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation. - On Web, allow event loops to be recreated with `spawn`. - On Web, enable event propagation. - On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time. - On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window. - On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during transient activation. - On Web, fix pen treated as mouse input. - On Web, fix pointer button events not being processed when a buttons is already pressed. - On Web, fix scale factor resize suggestion always overwriting the canvas size. - On Web, fix some `WindowBuilder` methods doing nothing. - On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties. - On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events. - On Web, fix touch input not gaining or loosing focus. - On Web, fix touch location to be as accurate as mouse position. - On Web, handle coalesced pointer events, which increases the resolution of pointer inputs. - On Web, implement `Window::focus_window()`. - On Web, implement `Window::set_(min|max)_inner_size()`. - On Web, implement `WindowEvent::Occluded`. - On Web, never return a `MonitorHandle`. - On Web, prevent clicks on the canvas to select text. - On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected. - On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`. - On Web, respect `EventLoopWindowTarget::listen_device_events()` settings. - On Web, scale factor and dark mode detection are now more robust. - On Web, send mouse position on button release as well. - On Web, take all transient activations on the canvas and window into account to queue a fullscreen request. - On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events. - On Web, use the correct canvas size when calculating the new size during scale factor change, instead of using the output bitmap size. - On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop. - On Web: fix position of touch events to be relative to the canvas. - On Windows, add `drag_resize_window` method support. - On Windows, add horizontal MouseWheel `DeviceEvent`. - On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name. - On Windows, fix IME APIs not working when from non event loop thread. - On Windows, fix `CursorEnter/Left` not being sent when grabbing the mouse. - On Windows, fix `RedrawRequested` not being delivered when calling `Window::request_redraw` from `RedrawRequested`. - On Windows, port to `windows-sys` version 0.48.0. - On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window. - On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`. - On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses. - On X11, set `visual_id` in returned `raw-window-handle`. - On iOS, add ability to change the status bar style. - On iOS, add force data to touch events when using the Apple Pencil. - On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`. - On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground. - On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`. - On macOS, fix assertion when pressing `Globe` key. - On macOS, fix crash in `window.set_minimized(false)`. - On macOS, fix crash when dropping `Window`. winit-0.30.9/src/changelog/v0.30.md000064400000000000000000000343311046102023000146610ustar 00000000000000## 0.30.9 ### Changed - On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`. ### Fixed - On X11, fix crash with uim. - On X11, fix modifiers for keys that were sent by the same X11 request. - On iOS, fix high CPU usage even when using `ControlFlow::Wait`. ## 0.30.8 ### Added - `ActivationToken::from_raw` and `ActivationToken::into_raw`. - On X11, add a workaround for disabling IME on GNOME. ### Fixed - On Windows, fixed the event loop not waking on accessibility requests. - On X11, fixed cursor grab mode state tracking on error. ## 0.30.7 ### Fixed - On X11, fixed KeyboardInput delivered twice when IME enabled. ## 0.30.6 ### Added - On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game` to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games. - On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env variables to test the respective modifiers of window creation. - On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`. - Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`. ### Fixed - On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize. - On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the default activation policy, unless explicitly provided during initialization. - On macOS, fix crash when calling `drag_window()` without a left click present. - On X11, key events forward to IME anyway, even when it's disabled. - On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`. - On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again. - On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again. - On X11, fix XInput handling that prevented a new window from getting the focus in some cases. - On macOS, fix crash when pressing Caps Lock in certain configurations. - On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations. - On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor. ## 0.30.5 ### Added - Add `ActiveEventLoop::system_theme()`, returning the current system theme. - On Web, implement `Error` for `platform::web::CustomCursorError`. - On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop. ### Fixed - On MacOS, fix building with `feature = "rwh_04"`. - On Web, pen events are now routed through to `WindowEvent::Cursor*`. - On macOS, fix panic when releasing not available monitor. - On MacOS, return the system theme in `Window::theme()` if no theme override is set. ## 0.30.4 ### Changed - `DeviceId::dummy()` and `WindowId::dummy()` are no longer marked `unsafe`. ### Fixed - On Wayland, avoid crashing when compositor is misbehaving. - On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending `WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested` to not be de-duplicated. - Account for different browser engine implementations of pointer movement coordinate space. ## 0.30.3 ### Added - On Web, add `EventLoopExtWebSys::(set_)poll_strategy()` to allow setting control flow strategies before starting the event loop. - On Web, add `WaitUntilStrategy`, which allows to set different strategies for `ControlFlow::WaitUntil`. By default the Prioritized Task Scheduling API is used, with a fallback to `setTimeout()` with a trick to circumvent throttling to 4ms. But an option to use a Web worker to schedule the timer is available as well, which commonly prevents any throttling when the window is not focused. ### Changed - On macOS, set the window theme on the `NSWindow` instead of application-wide. ### Fixed - On X11, build on arm platforms. - On macOS, fixed `WindowBuilder::with_theme` not having any effect on the window. ## 0.30.2 ### Fixed - On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately when not called from inside the event loop. Now queues a microtask instead. - On Web, stop overwriting default cursor with `CursorIcon::Default`. - On Web, prevent crash when using `InnerSizeWriter::request_inner_size()`. - On macOS, fix not working opacity for entire window. ## 0.30.1 ### Added - Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`. - Implement `ApplicationHandler` for `&mut` references and heap allocations to something that implements `ApplicationHandler`. ### Fixed - On macOS, fix panic on exit when dropping windows outside the event loop. - On macOS, fix window dragging glitches when dragging across a monitor boundary with different scale factor. - On macOS, fix the range in `Ime::Preedit`. - On macOS, use the system's internal mechanisms for queuing events. - On macOS, handle events directly instead of queuing when possible. ## 0.30.0 ### Added - Add `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases. - Add `ApplicationHandler` trait which mimics `Event`. - Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`. - Add `Sync` implementation for `EventLoopProxy`. - Add `Window::default_attributes` to get default `WindowAttributes`. - Add `EventLoop::builder` to get `EventLoopBuilder` without export. - Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data. - Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs. - Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s. - Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources. - Add `ActiveEventLoop::create_window` and `EventLoop::create_window`. - Add `CustomCursor` which could be set via `Window::set_cursor`, implemented on Windows, macOS, X11, Wayland, and Web. - On Web, add to toggle calling `Event.preventDefault()` on `Window`. - On iOS, add `PinchGesture`, `DoubleTapGesture`, `PanGesture` and `RotationGesture`. - on iOS, use `UIGestureRecognizerDelegate` for fine grained control of gesture recognizers. - On macOS, add services menu. - On Windows, add `with_title_text_color`, and `with_corner_preference` on `WindowAttributesExtWindows`. - On Windows, implement resize increments. - On Windows, add `AnyThread` API to access window handle off the main thread. ### Changed - Bump MSRV from `1.65` to `1.70`. - On Wayland, bump `sctk-adwaita` to `0.9.0`, which changed system library crates. This change is a **cascading breaking change**, you must do breaking change as well, even if you don't expose winit. - Rename `TouchpadMagnify` to `PinchGesture`. - Rename `SmartMagnify` to `DoubleTapGesture`. - Rename `TouchpadRotate` to `RotationGesture`. - Rename `EventLoopWindowTarget` to `ActiveEventLoop`. - Rename `platform::x11::XWindowType` to `platform::x11::WindowType`. - Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data. - Make `Debug` formatting of `WindowId` more concise. - Move `dpi` types to its own crate, and re-export it from the root crate. - Replace `log` with `tracing`, use `log` feature on `tracing` to restore old behavior. - `EventLoop::with_user_event` now returns `EventLoopBuilder`. - On Web, return `HandleError::Unavailable` when a window handle is not available. - On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`. - On Web, remove queuing fullscreen request in absence of transient activation. - On iOS, return `HandleError::Unavailable` when a window handle is not available. - On macOS, return `HandleError::Unavailable` when a window handle is not available. - On Windows, remove `WS_CAPTION`, `WS_BORDER`, and `WS_EX_WINDOWEDGE` styles for child windows without decorations. - On Android, bump `ndk` to `0.9.0` and `android-activity` to `0.6.0`, and remove unused direct dependency on `ndk-sys`. ### Deprecated - Deprecate `EventLoop::run`, use `EventLoop::run_app`. - Deprecate `EventLoopExtRunOnDemand::run_on_demand`, use `EventLoop::run_app_on_demand`. - Deprecate `EventLoopExtPumpEvents::pump_events`, use `EventLoopExtPumpEvents::pump_app_events`. The new `app` APIs accept a newly added `ApplicationHandler` instead of `Fn`. The semantics are mostly the same, given that the capture list of the closure is your new `State`. Consider the following code: ```rust,no_run use winit::event::Event; use winit::event_loop::EventLoop; use winit::window::Window; struct MyUserEvent; let event_loop = EventLoop::::with_user_event().build().unwrap(); let window = event_loop.create_window(Window::default_attributes()).unwrap(); let mut counter = 0; let _ = event_loop.run(move |event, event_loop| { match event { Event::AboutToWait => { window.request_redraw(); counter += 1; } Event::WindowEvent { window_id, event } => { // Handle window event. } Event::UserEvent(event) => { // Handle user event. } Event::DeviceEvent { device_id, event } => { // Handle device event. } _ => (), } }); ``` To migrate this code, you should move all the captured values into some newtype `State` and implement `ApplicationHandler` for this type. Finally, we move particular `match event` arms into methods on `ApplicationHandler`, for example: ```rust,no_run use winit::application::ApplicationHandler; use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId}; use winit::event_loop::{EventLoop, ActiveEventLoop}; use winit::window::{Window, WindowId}; struct MyUserEvent; struct State { window: Window, counter: i32, } impl ApplicationHandler for State { fn user_event(&mut self, event_loop: &ActiveEventLoop, user_event: MyUserEvent) { // Handle user event. } fn resumed(&mut self, event_loop: &ActiveEventLoop) { // Your application got resumed. } fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) { // Handle window event. } fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent) { // Handle device event. } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { self.window.request_redraw(); self.counter += 1; } } let event_loop = EventLoop::::with_user_event().build().unwrap(); #[allow(deprecated)] let window = event_loop.create_window(Window::default_attributes()).unwrap(); let mut state = State { window, counter: 0 }; let _ = event_loop.run_app(&mut state); ``` Please submit your feedback after migrating in [this issue](https://github.com/rust-windowing/winit/issues/3626). - Deprecate `Window::set_cursor_icon`, use `Window::set_cursor`. ### Removed - Remove `Window::new`, use `ActiveEventLoop::create_window` instead. You now have to create your windows inside the actively running event loop (usually the `new_events(cause: StartCause::Init)` or `resumed()` events), and can no longer do it before the application has properly launched. This change is done to fix many long-standing issues on iOS and macOS, and will improve things on Wayland once fully implemented. To ease migration, we provide the deprecated `EventLoop::create_window` that will allow you to bypass this restriction in this release. Using the migration example from above, you can change your code as follows: ```rust,no_run use winit::application::ApplicationHandler; use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId}; use winit::event_loop::{EventLoop, ActiveEventLoop}; use winit::window::{Window, WindowId}; #[derive(Default)] struct State { // Use an `Option` to allow the window to not be available until the // application is properly running. window: Option, counter: i32, } impl ApplicationHandler for State { // This is a common indicator that you can create a window. fn resumed(&mut self, event_loop: &ActiveEventLoop) { self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap()); } fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) { // `unwrap` is fine, the window will always be available when // receiving a window event. let window = self.window.as_ref().unwrap(); // Handle window event. } fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent) { // Handle window event. } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { if let Some(window) = self.window.as_ref() { window.request_redraw(); self.counter += 1; } } } let event_loop = EventLoop::new().unwrap(); let mut state = State::default(); let _ = event_loop.run_app(&mut state); ``` - Remove `Deref` implementation for `EventLoop` that gave `EventLoopWindowTarget`. - Remove `WindowBuilder` in favor of `WindowAttributes`. - Remove Generic parameter `T` from `ActiveEventLoop`. - Remove `EventLoopBuilder::with_user_event`, use `EventLoop::with_user_event`. - Remove Redundant `EventLoopError::AlreadyRunning`. - Remove `WindowAttributes::fullscreen` and expose as field directly. - On X11, remove `platform::x11::XNotSupported` export. ### Fixed - On Web, fix setting cursor icon overriding cursor visibility. - On Windows, fix cursor not confined to center of window when grabbed and hidden. - On macOS, fix sequence of mouse events being out of order when dragging on the trackpad. - On Wayland, fix decoration glitch on close with some compositors. - On Android, fix a regression introduced in #2748 to allow volume key events to be received again. - On Windows, don't return a valid window handle outside of the GUI thread. - On macOS, don't set the background color when initializing a window with transparency. winit-0.30.9/src/changelog/v0.8.md000064400000000000000000000026701046102023000146070ustar 00000000000000## 0.8.3 - Fixed issue of calls to `set_inner_size` blocking on Windows. - Mapped `ISO_Left_Tab` to `VirtualKeyCode::Tab` to make the key work with modifiers - Fixed the X11 backed on 32bit targets ## 0.8.2 - Uniformize keyboard scancode values across Wayland and X11 (#297). - Internal rework of the wayland event loop - Added method `os::linux::WindowExt::is_ready` ## 0.8.1 - Added various methods to `os::linux::EventsLoopExt`, plus some hidden items necessary to make glutin work. ## 0.8.0 - Added `Window::set_maximized`, `WindowAttributes::maximized` and `WindowBuilder::with_maximized`. - Added `Window::set_fullscreen`. - Changed `with_fullscreen` to take a `Option` instead of a `MonitorId`. - Removed `MonitorId::get_native_identifier()` in favor of platform-specific traits in the `os` module. - Changed `get_available_monitors()` and `get_primary_monitor()` to be methods of `EventsLoop` instead of stand-alone methods. - Changed `EventsLoop` to be tied to a specific X11 or Wayland connection. - Added a `os::linux::EventsLoopExt` trait that makes it possible to configure the connection. - Fixed the emscripten code, which now compiles. - Changed the X11 fullscreen code to use `xrandr` instead of `xxf86vm`. - Fixed the Wayland backend to produce `Refresh` event after window creation. - Changed the `Suspended` event to be outside of `WindowEvent`. - Fixed the X11 backend sometimes reporting the wrong virtual key (#273). winit-0.30.9/src/changelog/v0.9.md000064400000000000000000000025311046102023000146040ustar 00000000000000## 0.9.0 - Added event `WindowEvent::HiDPIFactorChanged`. - Added method `MonitorId::get_hidpi_factor`. - Deprecated `get_inner_size_pixels` and `get_inner_size_points` methods of `Window` in favor of `get_inner_size`. - **Breaking:** `EventsLoop` is `!Send` and `!Sync` because of platform-dependant constraints, but `Window`, `WindowId`, `DeviceId` and `MonitorId` guaranteed to be `Send`. - `MonitorId::get_position` now returns `(i32, i32)` instead of `(u32, u32)`. - Rewrite of the wayland backend to use wayland-client-0.11 - Support for dead keys on wayland for keyboard utf8 input - Monitor enumeration on Windows is now implemented using `EnumDisplayMonitors` instead of `EnumDisplayDevices`. This changes the value returned by `MonitorId::get_name()`. - On Windows added `MonitorIdExt::hmonitor` method - Impl `Clone` for `EventsLoopProxy` - `EventsLoop::get_primary_monitor()` on X11 will fallback to any available monitor if no primary is found - Support for touch event on wayland - `WindowEvent`s `MouseMoved`, `MouseEntered`, and `MouseLeft` have been renamed to `CursorMoved`, `CursorEntered`, and `CursorLeft`. - New `DeviceEvent`s added, `MouseMotion` and `MouseWheel`. - Send `CursorMoved` event after `CursorEntered` and `Focused` events. - Add support for `ModifiersState`, `MouseMove`, `MouseInput`, `MouseMotion` for emscripten backend. winit-0.30.9/src/cursor.rs000064400000000000000000000176651046102023000135400ustar 00000000000000use core::fmt; use std::error::Error; use std::hash::{Hash, Hasher}; use std::sync::Arc; use cursor_icon::CursorIcon; use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource}; /// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`]. pub const MAX_CURSOR_SIZE: u16 = 2048; const PIXEL_SIZE: usize = 4; /// See [`Window::set_cursor()`][crate::window::Window::set_cursor] for more details. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum Cursor { Icon(CursorIcon), Custom(CustomCursor), } impl Default for Cursor { fn default() -> Self { Self::Icon(CursorIcon::default()) } } impl From for Cursor { fn from(icon: CursorIcon) -> Self { Self::Icon(icon) } } impl From for Cursor { fn from(custom: CustomCursor) -> Self { Self::Custom(custom) } } /// Use a custom image as a cursor (mouse pointer). /// /// Is guaranteed to be cheap to clone. /// /// ## Platform-specific /// /// **Web**: Some browsers have limits on cursor sizes usually at 128x128. /// /// # Example /// /// ```no_run /// # use winit::event_loop::ActiveEventLoop; /// # use winit::window::Window; /// # fn scope(event_loop: &ActiveEventLoop, window: &Window) { /// use winit::window::CustomCursor; /// /// let w = 10; /// let h = 10; /// let rgba = vec![255; (w * h * 4) as usize]; /// /// #[cfg(not(target_family = "wasm"))] /// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap(); /// /// #[cfg(target_family = "wasm")] /// let source = { /// use winit::platform::web::CustomCursorExtWebSys; /// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0) /// }; /// /// let custom_cursor = event_loop.create_custom_cursor(source); /// /// window.set_cursor(custom_cursor.clone()); /// # } /// ``` #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct CustomCursor { /// Platforms should make sure this is cheap to clone. pub(crate) inner: PlatformCustomCursor, } impl CustomCursor { /// Creates a new cursor from an rgba buffer. /// /// The alpha channel is assumed to be **not** premultiplied. pub fn from_rgba( rgba: impl Into>, width: u16, height: u16, hotspot_x: u16, hotspot_y: u16, ) -> Result { let _span = tracing::debug_span!("winit::Cursor::from_rgba", width, height, hotspot_x, hotspot_y) .entered(); Ok(CustomCursorSource { inner: PlatformCustomCursorSource::from_rgba( rgba.into(), width, height, hotspot_x, hotspot_y, )?, }) } } /// Source for [`CustomCursor`]. /// /// See [`CustomCursor`] for more details. #[derive(Debug)] pub struct CustomCursorSource { pub(crate) inner: PlatformCustomCursorSource, } /// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments. #[derive(Debug, Clone)] pub enum BadImage { /// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't /// guarantee that the cursor will work, but should avoid many platform and device specific /// limits. TooLarge { width: u16, height: u16 }, /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be /// safely interpreted as 32bpp RGBA pixels. ByteCountNotDivisibleBy4 { byte_count: usize }, /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`. /// At least one of your arguments is incorrect. DimensionsVsPixelCount { width: u16, height: u16, width_x_height: u64, pixel_count: u64 }, /// Produced when the hotspot is outside the image bounds HotspotOutOfBounds { width: u16, height: u16, hotspot_x: u16, hotspot_y: u16 }, } impl fmt::Display for BadImage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { BadImage::TooLarge { width, height } => write!( f, "The specified dimensions ({width:?}x{height:?}) are too large. The maximum is \ {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.", ), BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!( f, "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \ it impossible to interpret as 32bpp RGBA pixels.", ), BadImage::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => { write!( f, "The specified dimensions ({width:?}x{height:?}) don't match the number of \ pixels supplied by the `rgba` argument ({pixel_count:?}). For those \ dimensions, the expected pixel count is {width_x_height:?}.", ) }, BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y } => write!( f, "The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds \ ({width:?}x{height:?}).", ), } } } impl Error for BadImage {} /// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with /// images. #[allow(dead_code)] #[derive(Debug)] pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage); #[allow(dead_code)] impl OnlyCursorImageSource { pub(crate) fn from_rgba( rgba: Vec, width: u16, height: u16, hotspot_x: u16, hotspot_y: u16, ) -> Result { CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self) } } /// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching. #[allow(dead_code)] #[derive(Debug, Clone)] pub(crate) struct OnlyCursorImage(pub(crate) Arc); impl Hash for OnlyCursorImage { fn hash(&self, state: &mut H) { Arc::as_ptr(&self.0).hash(state); } } impl PartialEq for OnlyCursorImage { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.0, &other.0) } } impl Eq for OnlyCursorImage {} #[derive(Debug)] #[allow(dead_code)] pub(crate) struct CursorImage { pub(crate) rgba: Vec, pub(crate) width: u16, pub(crate) height: u16, pub(crate) hotspot_x: u16, pub(crate) hotspot_y: u16, } impl CursorImage { pub(crate) fn from_rgba( rgba: Vec, width: u16, height: u16, hotspot_x: u16, hotspot_y: u16, ) -> Result { if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE { return Err(BadImage::TooLarge { width, height }); } if rgba.len() % PIXEL_SIZE != 0 { return Err(BadImage::ByteCountNotDivisibleBy4 { byte_count: rgba.len() }); } let pixel_count = (rgba.len() / PIXEL_SIZE) as u64; let width_x_height = width as u64 * height as u64; if pixel_count != width_x_height { return Err(BadImage::DimensionsVsPixelCount { width, height, width_x_height, pixel_count, }); } if hotspot_x >= width || hotspot_y >= height { return Err(BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y }); } Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y }) } } // Platforms that don't support cursors will export this as `PlatformCustomCursor`. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub(crate) struct NoCustomCursor; #[allow(dead_code)] impl NoCustomCursor { pub(crate) fn from_rgba( rgba: Vec, width: u16, height: u16, hotspot_x: u16, hotspot_y: u16, ) -> Result { CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?; Ok(Self) } } winit-0.30.9/src/error.rs000064400000000000000000000071331046102023000133410ustar 00000000000000use std::{error, fmt}; use crate::platform_impl; // TODO: Rename /// An error that may be generated when requesting Winit state #[derive(Debug)] pub enum ExternalError { /// The operation is not supported by the backend. NotSupported(NotSupportedError), /// The operation was ignored. Ignored, /// The OS cannot perform the operation. Os(OsError), } /// The error type for when the requested operation is not supported by the backend. #[derive(Clone)] pub struct NotSupportedError { _marker: (), } /// The error type for when the OS cannot perform the requested operation. #[derive(Debug)] pub struct OsError { line: u32, file: &'static str, error: platform_impl::OsError, } /// A general error that may occur while running the Winit event loop #[derive(Debug)] pub enum EventLoopError { /// The operation is not supported by the backend. NotSupported(NotSupportedError), /// The OS cannot perform the operation. Os(OsError), /// The event loop can't be re-created. RecreationAttempt, /// Application has exit with an error status. ExitFailure(i32), } impl From for EventLoopError { fn from(value: OsError) -> Self { Self::Os(value) } } impl NotSupportedError { #[inline] #[allow(dead_code)] pub(crate) fn new() -> NotSupportedError { NotSupportedError { _marker: () } } } impl OsError { #[allow(dead_code)] pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError { OsError { line, file, error } } } #[allow(unused_macros)] macro_rules! os_error { ($error:expr) => {{ crate::error::OsError::new(line!(), file!(), $error) }}; } impl fmt::Display for OsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error)) } } impl fmt::Display for ExternalError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { ExternalError::NotSupported(e) => e.fmt(f), ExternalError::Ignored => write!(f, "Operation was ignored"), ExternalError::Os(e) => e.fmt(f), } } } impl fmt::Debug for NotSupportedError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.debug_struct("NotSupportedError").finish() } } impl fmt::Display for NotSupportedError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.pad("the requested operation is not supported by Winit") } } impl fmt::Display for EventLoopError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { EventLoopError::RecreationAttempt => write!(f, "EventLoop can't be recreated"), EventLoopError::NotSupported(e) => e.fmt(f), EventLoopError::Os(e) => e.fmt(f), EventLoopError::ExitFailure(status) => write!(f, "Exit Failure: {status}"), } } } impl error::Error for OsError {} impl error::Error for ExternalError {} impl error::Error for NotSupportedError {} impl error::Error for EventLoopError {} #[cfg(test)] #[allow(clippy::redundant_clone)] mod tests { use super::*; // Eat attributes for testing #[test] fn ensure_fmt_does_not_panic() { let _ = format!("{:?}, {}", NotSupportedError::new(), NotSupportedError::new().clone()); let _ = format!( "{:?}, {}", ExternalError::NotSupported(NotSupportedError::new()), ExternalError::NotSupported(NotSupportedError::new()) ); } } winit-0.30.9/src/event.rs000064400000000000000000001322531046102023000133330ustar 00000000000000//! The [`Event`] enum and assorted supporting types. //! //! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get //! processed and used to modify the program state. For more details, see the root-level //! documentation. //! //! Some of these events represent different "parts" of a traditional event-handling loop. You could //! approximate the basic ordering loop of [`EventLoop::run_app(...)`] like this: //! //! ```rust,ignore //! let mut start_cause = StartCause::Init; //! //! while !elwt.exiting() { //! app.new_events(event_loop, start_cause); //! //! for event in (window events, user events, device events) { //! // This will pick the right method on the application based on the event. //! app.handle_event(event_loop, event); //! } //! //! for window_id in (redraw windows) { //! app.window_event(event_loop, window_id, RedrawRequested); //! } //! //! app.about_to_wait(event_loop); //! start_cause = wait_if_necessary(); //! } //! //! app.exiting(event_loop); //! ``` //! //! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully //! describes what happens in what order. //! //! [`EventLoop::run_app(...)`]: crate::event_loop::EventLoop::run_app //! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil use std::path::PathBuf; use std::sync::{Mutex, Weak}; #[cfg(not(web_platform))] use std::time::Instant; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use smol_str::SmolStr; #[cfg(web_platform)] use web_time::Instant; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::error::ExternalError; use crate::event_loop::AsyncRequestSerial; use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState}; use crate::platform_impl; #[cfg(doc)] use crate::window::Window; use crate::window::{ActivationToken, Theme, WindowId}; /// Describes a generic event. /// /// See the module-level docs for more information on the event loop manages each event. #[derive(Debug, Clone, PartialEq)] pub enum Event { /// See [`ApplicationHandler::new_events`] for details. /// /// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events NewEvents(StartCause), /// See [`ApplicationHandler::window_event`] for details. /// /// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event WindowEvent { window_id: WindowId, event: WindowEvent }, /// See [`ApplicationHandler::device_event`] for details. /// /// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event DeviceEvent { device_id: DeviceId, event: DeviceEvent }, /// See [`ApplicationHandler::user_event`] for details. /// /// [`ApplicationHandler::user_event`]: crate::application::ApplicationHandler::user_event UserEvent(T), /// See [`ApplicationHandler::suspended`] for details. /// /// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended Suspended, /// See [`ApplicationHandler::resumed`] for details. /// /// [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed Resumed, /// See [`ApplicationHandler::about_to_wait`] for details. /// /// [`ApplicationHandler::about_to_wait`]: crate::application::ApplicationHandler::about_to_wait AboutToWait, /// See [`ApplicationHandler::exiting`] for details. /// /// [`ApplicationHandler::exiting`]: crate::application::ApplicationHandler::exiting LoopExiting, /// See [`ApplicationHandler::memory_warning`] for details. /// /// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning MemoryWarning, } impl Event { #[allow(clippy::result_large_err)] pub fn map_nonuser_event(self) -> Result, Event> { use self::Event::*; match self { UserEvent(_) => Err(self), WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }), DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }), NewEvents(cause) => Ok(NewEvents(cause)), AboutToWait => Ok(AboutToWait), LoopExiting => Ok(LoopExiting), Suspended => Ok(Suspended), Resumed => Ok(Resumed), MemoryWarning => Ok(MemoryWarning), } } } /// Describes the reason the event loop is resuming. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StartCause { /// Sent if the time specified by [`ControlFlow::WaitUntil`] has been reached. Contains the /// moment the timeout was requested and the requested resume time. The actual resume time is /// guaranteed to be equal to or after the requested resume time. /// /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil ResumeTimeReached { start: Instant, requested_resume: Instant }, /// Sent if the OS has new events to send to the window, after a wait was requested. Contains /// the moment the wait was requested and the resume time, if requested. WaitCancelled { start: Instant, requested_resume: Option }, /// Sent if the event loop is being resumed after the loop's control flow was set to /// [`ControlFlow::Poll`]. /// /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll Poll, /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized. Init, } /// Describes an event from a [`Window`]. #[derive(Debug, Clone, PartialEq)] pub enum WindowEvent { /// The activation token was delivered back and now could be used. #[cfg_attr(not(any(x11_platform, wayland_platform)), allow(rustdoc::broken_intra_doc_links))] /// Delivered in response to [`request_activation_token`]. /// /// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken }, /// The size of the window has changed. Contains the client area's new dimensions. Resized(PhysicalSize), /// The position of the window has changed. Contains the window's new position. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Wayland:** Unsupported. Moved(PhysicalPosition), /// The window has been requested to close. CloseRequested, /// The window has been destroyed. Destroyed, /// A file has been dropped into the window. /// /// When the user drops multiple files at once, this event will be emitted for each file /// separately. DroppedFile(PathBuf), /// A file is being hovered over the window. /// /// When the user hovers multiple files at once, this event will be emitted for each file /// separately. HoveredFile(PathBuf), /// A file was hovered, but has exited the window. /// /// There will be a single `HoveredFileCancelled` event triggered even if multiple files were /// hovered. HoveredFileCancelled, /// The window gained or lost focus. /// /// The parameter is true if the window has gained focus, and false if it has lost focus. Focused(bool), /// An event from the keyboard has been received. /// /// ## Platform-specific /// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down, /// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key /// events which are not marked as `is_synthetic`. KeyboardInput { device_id: DeviceId, event: KeyEvent, /// If `true`, the event was generated synthetically by winit /// in one of the following circumstances: /// /// * Synthetic key press events are generated for all keys pressed when a window gains /// focus. Likewise, synthetic key release events are generated for all keys pressed when /// a window goes out of focus. ***Currently, this is only functional on X11 and /// Windows*** /// /// Otherwise, this value is always `false`. is_synthetic: bool, }, /// The keyboard modifiers have changed. ModifiersChanged(Modifiers), /// An event from an input method. /// /// **Note:** You have to explicitly enable this event using [`Window::set_ime_allowed`]. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Orbital:** Unsupported. Ime(Ime), /// The cursor has moved on the window. /// /// ## Platform-specific /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform CursorMoved { device_id: DeviceId, /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range /// of this data is limited by the display area and it may have been transformed by /// the OS to implement effects such as cursor acceleration, it should not be used /// to implement non-cursor-like interactions such as 3D camera control. position: PhysicalPosition, }, /// The cursor has entered the window. /// /// ## Platform-specific /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform CursorEntered { device_id: DeviceId }, /// The cursor has left the window. /// /// ## Platform-specific /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform CursorLeft { device_id: DeviceId }, /// A mouse wheel movement or touchpad scroll occurred. MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase }, /// An mouse button press has been received. MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton }, /// Two-finger pinch gesture, often used for magnification. /// /// ## Platform-specific /// /// - Only available on **macOS** and **iOS**. /// - On iOS, not recognized by default. It must be enabled when needed. PinchGesture { device_id: DeviceId, /// Positive values indicate magnification (zooming in) and negative /// values indicate shrinking (zooming out). /// /// This value may be NaN. delta: f64, phase: TouchPhase, }, /// N-finger pan gesture /// /// ## Platform-specific /// /// - Only available on **iOS**. /// - On iOS, not recognized by default. It must be enabled when needed. PanGesture { device_id: DeviceId, /// Change in pixels of pan gesture from last update. delta: PhysicalPosition, phase: TouchPhase, }, /// Double tap gesture. /// /// On a Mac, smart magnification is triggered by a double tap with two fingers /// on the trackpad and is commonly used to zoom on a certain object /// (e.g. a paragraph of a PDF) or (sort of like a toggle) to reset any zoom. /// The gesture is also supported in Safari, Pages, etc. /// /// The event is general enough that its generating gesture is allowed to vary /// across platforms. It could also be generated by another device. /// /// Unfortunately, neither [Windows](https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741) /// nor [Wayland](https://wayland.freedesktop.org/libinput/doc/latest/gestures.html) /// support this gesture or any other gesture with the same effect. /// /// ## Platform-specific /// /// - Only available on **macOS 10.8** and later, and **iOS**. /// - On iOS, not recognized by default. It must be enabled when needed. DoubleTapGesture { device_id: DeviceId }, /// Two-finger rotation gesture. /// /// Positive delta values indicate rotation counterclockwise and /// negative delta values indicate rotation clockwise. /// /// ## Platform-specific /// /// - Only available on **macOS** and **iOS**. /// - On iOS, not recognized by default. It must be enabled when needed. RotationGesture { device_id: DeviceId, /// change in rotation in degrees delta: f32, phase: TouchPhase, }, /// Touchpad pressure event. /// /// At the moment, only supported on Apple forcetouch-capable macbooks. /// The parameters are: pressure level (value between 0 and 1 representing how hard the /// touchpad is being pressed) and stage (integer representing the click level). TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64 }, /// Motion on some analog axis. May report data redundant to other, more specific events. AxisMotion { device_id: DeviceId, axis: AxisId, value: f64 }, /// Touch event has been received /// /// ## Platform-specific /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// - **macOS:** Unsupported. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform Touch(Touch), /// The window's scale factor has changed. /// /// The following user actions can cause DPI changes: /// /// * Changing the display's resolution. /// * Changing the display's scale factor (e.g. in Control Panel on Windows). /// * Moving the window to a display with a different scale factor. /// /// To update the window size, use the provided [`InnerSizeWriter`] handle. By default, the /// window is resized to the value suggested by the OS, but it can be changed to any value. /// /// For more information about DPI in general, see the [`dpi`] crate. ScaleFactorChanged { scale_factor: f64, /// Handle to update inner size during scale changes. /// /// See [`InnerSizeWriter`] docs for more details. inner_size_writer: InnerSizeWriter, }, /// The system window theme has changed. /// /// Applications might wish to react to this to change the theme of the content of the window /// when the system changes the window theme. /// /// This only reports a change if the window theme was not overridden by [`Window::set_theme`]. /// /// ## Platform-specific /// /// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported. ThemeChanged(Theme), /// The window has been occluded (completely hidden from view). /// /// This is different to window visibility as it depends on whether the window is closed, /// minimised, set invisible, or fully occluded by another window. /// /// ## Platform-specific /// /// ### iOS /// /// On iOS, the `Occluded(false)` event is emitted in response to an /// [`applicationWillEnterForeground`] callback which means the application should start /// preparing its data. The `Occluded(true)` event is emitted in response to an /// [`applicationDidEnterBackground`] callback which means the application should free /// resources (according to the [iOS application lifecycle]). /// /// [`applicationWillEnterForeground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623076-applicationwillenterforeground /// [`applicationDidEnterBackground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle /// /// ### Others /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// - **Android / Wayland / Windows / Orbital:** Unsupported. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform Occluded(bool), /// Emitted when a window should be redrawn. /// /// This gets triggered in two scenarios: /// - The OS has performed an operation that's invalidated the window's contents (such as /// resizing the window). /// - The application has explicitly requested a redraw via [`Window::request_redraw`]. /// /// Winit will aggregate duplicate redraw requests into a single event, to /// help avoid duplicating rendering work. RedrawRequested, } /// Identifier of an input device. /// /// Whenever you receive an event arising from a particular input device, this event contains a /// `DeviceId` which identifies its origin. Note that devices may be virtual (representing an /// on-screen cursor and keyboard focus) or physical. Virtual devices typically aggregate inputs /// from multiple physical devices. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(pub(crate) platform_impl::DeviceId); impl DeviceId { /// Returns a dummy id, useful for unit testing. /// /// # Notes /// /// The only guarantee made about the return value of this function is that /// it will always be equal to itself and to future values returned by this function. /// No other guarantees are made. This may be equal to a real `DeviceId`. pub const fn dummy() -> Self { DeviceId(platform_impl::DeviceId::dummy()) } } /// Represents raw hardware events that are not associated with any particular window. /// /// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera /// or first-person game controls. Many physical actions, such as mouse movement, can produce both /// device and window events. Because window events typically arise from virtual devices /// (corresponding to GUI cursors and keyboard focus) the device IDs may not match. /// /// Note that these events are delivered regardless of input focus. #[derive(Clone, Debug, PartialEq)] pub enum DeviceEvent { Added, Removed, /// Change in physical position of a pointing device. /// /// This represents raw, unfiltered physical motion. Not to be confused with /// [`WindowEvent::CursorMoved`]. MouseMotion { /// (x, y) change in position in unspecified units. /// /// Different devices may use different units. delta: (f64, f64), }, /// Physical scroll event MouseWheel { delta: MouseScrollDelta, }, /// Motion on some analog axis. This event will be reported for all arbitrary input devices /// that winit supports on this platform, including mouse devices. If the device is a mouse /// device then this will be reported alongside the MouseMotion event. Motion { axis: AxisId, value: f64, }, Button { button: ButtonId, state: ElementState, }, Key(RawKeyEvent), } /// Describes a keyboard input as a raw device event. /// /// Note that holding down a key may produce repeated `RawKeyEvent`s. The /// operating system doesn't provide information whether such an event is a /// repeat or the initial keypress. An application may emulate this by, for /// example keeping a Map/Set of pressed keys and determining whether a keypress /// corresponds to an already pressed key. #[derive(Debug, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct RawKeyEvent { pub physical_key: keyboard::PhysicalKey, pub state: ElementState, } /// Describes a keyboard input targeting a window. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct KeyEvent { /// Represents the position of a key independent of the currently active layout. /// /// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode). /// The most prevalent use case for this is games. For example the default keys for the player /// to move around might be the W, A, S, and D keys on a US layout. The position of these keys /// is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY" /// layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.) /// /// ## Caveats /// /// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that /// implements DVORAK in hardware (or firmware) /// - Your application will likely have to handle keyboards which are missing keys that your /// own keyboard has. /// - Certain `KeyCode`s will move between a couple of different positions depending on what /// layout the keyboard was manufactured to support. /// /// **Because of these caveats, it is important that you provide users with a way to configure /// most (if not all) keybinds in your application.** /// /// ## `Fn` and `FnLock` /// /// `Fn` and `FnLock` key events are *exceedingly unlikely* to be emitted by Winit. These keys /// are usually handled at the hardware or OS level, and aren't surfaced to applications. If /// you somehow see this in the wild, we'd like to know :) pub physical_key: keyboard::PhysicalKey, // Allowing `broken_intra_doc_links` for `logical_key`, because // `key_without_modifiers` is not available on all platforms #[cfg_attr( not(any(windows_platform, macos_platform, x11_platform, wayland_platform)), allow(rustdoc::broken_intra_doc_links) )] /// This value is affected by all modifiers except Ctrl. /// /// This has two use cases: /// - Allows querying whether the current input is a Dead key. /// - Allows handling key-bindings on platforms which don't support [`key_without_modifiers`]. /// /// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard /// shortcuts, **it is important that you provide users with a way to configure your /// application's shortcuts so you don't render your application unusable for users with an /// incompatible keyboard layout.** /// /// ## Platform-specific /// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the /// browser/OS. /// /// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers pub logical_key: keyboard::Key, /// Contains the text produced by this keypress. /// /// In most cases this is identical to the content /// of the `Character` variant of `logical_key`. /// However, on Windows when a dead key was pressed earlier /// but cannot be combined with the character from this /// keypress, the produced text will consist of two characters: /// the dead-key-character followed by the character resulting /// from this keypress. /// /// An additional difference from `logical_key` is that /// this field stores the text representation of any key /// that has such a representation. For example when /// `logical_key` is `Key::Named(NamedKey::Enter)`, this field is `Some("\r")`. /// /// This is `None` if the current keypress cannot /// be interpreted as text. /// /// See also: `text_with_all_modifiers()` pub text: Option, /// Contains the location of this key on the keyboard. /// /// Certain keys on the keyboard may appear in more than once place. For example, the "Shift" /// key appears on the left side of the QWERTY keyboard as well as the right side. However, /// both keys have the same symbolic value. Another example of this phenomenon is the "1" /// key, which appears both above the "Q" key and as the "Keypad 1" key. /// /// This field allows the user to differentiate between keys like this that have the same /// symbolic value but different locations on the keyboard. /// /// See the [`KeyLocation`] type for more details. /// /// [`KeyLocation`]: crate::keyboard::KeyLocation pub location: keyboard::KeyLocation, /// Whether the key is being pressed or released. /// /// See the [`ElementState`] type for more details. pub state: ElementState, /// Whether or not this key is a key repeat event. /// /// On some systems, holding down a key for some period of time causes that key to be repeated /// as though it were being pressed and released repeatedly. This field is `true` if and only /// if this event is the result of one of those repeats. /// /// # Example /// /// In games, you often want to ignore repated key events - this can be /// done by ignoring events where this property is set. /// /// ``` /// use winit::event::{ElementState, KeyEvent, WindowEvent}; /// use winit::keyboard::{KeyCode, PhysicalKey}; /// # let window_event = WindowEvent::RedrawRequested; // To make the example compile /// match window_event { /// WindowEvent::KeyboardInput { /// event: /// KeyEvent { /// physical_key: PhysicalKey::Code(KeyCode::KeyW), /// state: ElementState::Pressed, /// repeat: false, /// .. /// }, /// .. /// } => { /// // The physical key `W` was pressed, and it was not a repeat /// }, /// _ => {}, // Handle other events /// } /// ``` pub repeat: bool, /// Platform-specific key event information. /// /// On Windows, Linux and macOS, this type contains the key without modifiers and the text with /// all modifiers applied. /// /// On Android, iOS, Redox and Web, this type is a no-op. pub(crate) platform_specific: platform_impl::KeyEventExtra, } /// Describes keyboard modifiers event. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Modifiers { pub(crate) state: ModifiersState, // NOTE: Currently pressed modifiers keys. // // The field providing a metadata, it shouldn't be used as a source of truth. pub(crate) pressed_mods: ModifiersKeys, } impl Modifiers { /// The state of the modifiers. pub fn state(&self) -> ModifiersState { self.state } /// The state of the left shift key. pub fn lshift_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::LSHIFT) } /// The state of the right shift key. pub fn rshift_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::RSHIFT) } /// The state of the left alt key. pub fn lalt_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::LALT) } /// The state of the right alt key. pub fn ralt_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::RALT) } /// The state of the left control key. pub fn lcontrol_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::LCONTROL) } /// The state of the right control key. pub fn rcontrol_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::RCONTROL) } /// The state of the left super key. pub fn lsuper_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::LSUPER) } /// The state of the right super key. pub fn rsuper_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::RSUPER) } fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState { if self.pressed_mods.contains(modifier) { ModifiersKeyState::Pressed } else { ModifiersKeyState::Unknown } } } impl From for Modifiers { fn from(value: ModifiersState) -> Self { Self { state: value, pressed_mods: Default::default() } } } /// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events. /// /// This is also called a "composition event". /// /// Most keypresses using a latin-like keyboard layout simply generate a /// [`WindowEvent::KeyboardInput`]. However, one couldn't possibly have a key for every single /// unicode character that the user might want to type /// - so the solution operating systems employ is to allow the user to type these using _a sequence /// of keypresses_ instead. /// /// A prominent example of this is accents - many keyboard layouts allow you to first click the /// "accent key", and then the character you want to apply the accent to. In this case, some /// platforms will generate the following event sequence: /// /// ```ignore /// // Press "`" key /// Ime::Preedit("`", Some((0, 0))) /// // Press "E" key /// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit. /// Ime::Commit("é") /// ``` /// /// Additionally, certain input devices are configured to display a candidate box that allow the /// user to select the desired character interactively. (To properly position this box, you must use /// [`Window::set_ime_cursor_area`].) /// /// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the /// following event sequence could be obtained: /// /// ```ignore /// // Press "A" key /// Ime::Preedit("a", Some((1, 1))) /// // Press "B" key /// Ime::Preedit("a b", Some((3, 3))) /// // Press left arrow key /// Ime::Preedit("a b", Some((1, 1))) /// // Press space key /// Ime::Preedit("啊b", Some((3, 3))) /// // Press space key /// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit. /// Ime::Commit("啊ä¸") /// ``` #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Ime { /// Notifies when the IME was enabled. /// /// After getting this event you could receive [`Preedit`][Self::Preedit] and /// [`Commit`][Self::Commit] events. You should also start performing IME related requests /// like [`Window::set_ime_cursor_area`]. Enabled, /// Notifies when a new composing text should be set at the cursor position. /// /// The value represents a pair of the preedit string and the cursor begin position and end /// position. When it's `None`, the cursor should be hidden. When `String` is an empty string /// this indicates that preedit was cleared. /// /// The cursor position is byte-wise indexed. Preedit(String, Option<(usize, usize)>), /// Notifies when text should be inserted into the editor widget. /// /// Right before this event winit will send empty [`Self::Preedit`] event. Commit(String), /// Notifies when the IME was disabled. /// /// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or /// [`Commit`][Self::Commit] events until the next [`Enabled`][Self::Enabled] event. You should /// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear /// pending preedit text. Disabled, } /// Describes touch-screen input state. #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TouchPhase { Started, Moved, Ended, Cancelled, } /// Represents a touch event /// /// Every time the user touches the screen, a new [`TouchPhase::Started`] event with an unique /// identifier for the finger is generated. When the finger is lifted, an [`TouchPhase::Ended`] /// event is generated with the same finger id. /// /// After a `Started` event has been emitted, there may be zero or more `Move` /// events when the finger is moved or the touch pressure changes. /// /// The finger id may be reused by the system after an `Ended` event. The user /// should assume that a new `Started` event received with the same id has nothing /// to do with the old finger and is a new finger. /// /// A [`TouchPhase::Cancelled`] event is emitted when the system has canceled tracking this /// touch, such as when the window loses focus, or on iOS if the user moves the /// device against their face. /// /// ## Platform-specific /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// - **macOS:** Unsupported. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[derive(Debug, Clone, Copy, PartialEq)] pub struct Touch { pub device_id: DeviceId, pub phase: TouchPhase, pub location: PhysicalPosition, /// Describes how hard the screen was pressed. May be `None` if the platform /// does not support pressure sensitivity. /// /// ## Platform-specific /// /// - Only available on **iOS** 9.0+, **Windows** 8+, **Web**, and **Android**. /// - **Android**: This will never be [None]. If the device doesn't support pressure /// sensitivity, force will either be 0.0 or 1.0. Also see the /// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE). pub force: Option, /// Unique identifier of a finger. pub id: u64, } /// Describes the force of a touch event #[derive(Debug, Clone, Copy, PartialEq)] pub enum Force { /// On iOS, the force is calibrated so that the same number corresponds to /// roughly the same amount of pressure on the screen regardless of the /// device. Calibrated { /// The force of the touch, where a value of 1.0 represents the force of /// an average touch (predetermined by the system, not user-specific). /// /// The force reported by Apple Pencil is measured along the axis of the /// pencil. If you want a force perpendicular to the device, you need to /// calculate this value using the `altitude_angle` value. force: f64, /// The maximum possible force for a touch. /// /// The value of this field is sufficiently high to provide a wide /// dynamic range for values of the `force` field. max_possible_force: f64, /// The altitude (in radians) of the stylus. /// /// A value of 0 radians indicates that the stylus is parallel to the /// surface. The value of this property is Pi/2 when the stylus is /// perpendicular to the surface. altitude_angle: Option, }, /// If the platform reports the force as normalized, we have no way of /// knowing how much pressure 1.0 corresponds to – we know it's the maximum /// amount of force, but as to how much force, you might either have to /// press really really hard, or not hard at all, depending on the device. Normalized(f64), } impl Force { /// Returns the force normalized to the range between 0.0 and 1.0 inclusive. /// /// Instead of normalizing the force, you should prefer to handle /// [`Force::Calibrated`] so that the amount of force the user has to apply is /// consistent across devices. pub fn normalized(&self) -> f64 { match self { Force::Calibrated { force, max_possible_force, altitude_angle } => { let force = match altitude_angle { Some(altitude_angle) => force / altitude_angle.sin(), None => *force, }; force / max_possible_force }, Force::Normalized(force) => *force, } } } /// Identifier for a specific analog axis on some device. pub type AxisId = u32; /// Identifier for a specific button on some device. pub type ButtonId = u32; /// Describes the input state of a key. #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ElementState { Pressed, Released, } impl ElementState { /// True if `self == Pressed`. pub fn is_pressed(self) -> bool { self == ElementState::Pressed } } /// Describes a button of a mouse controller. /// /// ## Platform-specific /// /// **macOS:** `Back` and `Forward` might not work with all hardware. /// **Orbital:** `Back` and `Forward` are unsupported due to orbital not supporting them. #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum MouseButton { Left, Right, Middle, Back, Forward, Other(u16), } /// Describes a difference in the mouse scroll wheel state. #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum MouseScrollDelta { /// Amount in lines or rows to scroll in the horizontal /// and vertical directions. /// /// Positive values indicate that the content that is being scrolled should move /// right and down (revealing more content left and up). LineDelta(f32, f32), /// Amount in pixels to scroll in the horizontal and /// vertical direction. /// /// Scroll events are expressed as a `PixelDelta` if /// supported by the device (eg. a touchpad) and /// platform. /// /// Positive values indicate that the content being scrolled should /// move right/down. /// /// For a 'natural scrolling' touch pad (that acts like a touch screen) /// this means moving your fingers right and down should give positive values, /// and move the content right and down (to reveal more things left and up). PixelDelta(PhysicalPosition), } /// Handle to synchronously change the size of the window from the /// [`WindowEvent`]. #[derive(Debug, Clone)] pub struct InnerSizeWriter { pub(crate) new_inner_size: Weak>>, } impl InnerSizeWriter { #[cfg(not(orbital_platform))] pub(crate) fn new(new_inner_size: Weak>>) -> Self { Self { new_inner_size } } /// Try to request inner size which will be set synchronously on the window. pub fn request_inner_size( &mut self, new_inner_size: PhysicalSize, ) -> Result<(), ExternalError> { if let Some(inner) = self.new_inner_size.upgrade() { *inner.lock().unwrap() = new_inner_size; Ok(()) } else { Err(ExternalError::Ignored) } } } impl PartialEq for InnerSizeWriter { fn eq(&self, other: &Self) -> bool { self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr() } } #[cfg(test)] mod tests { use crate::dpi::PhysicalPosition; use crate::event; use std::collections::{BTreeSet, HashSet}; macro_rules! foreach_event { ($closure:expr) => {{ #[allow(unused_mut)] let mut x = $closure; let did = event::DeviceId::dummy(); #[allow(deprecated)] { use crate::event::Event::*; use crate::event::Ime::Enabled; use crate::event::WindowEvent::*; use crate::window::WindowId; // Mainline events. let wid = WindowId::dummy(); x(UserEvent(())); x(NewEvents(event::StartCause::Init)); x(AboutToWait); x(LoopExiting); x(Suspended); x(Resumed); // Window events. let with_window_event = |wev| x(WindowEvent { window_id: wid, event: wev }); with_window_event(CloseRequested); with_window_event(Destroyed); with_window_event(Focused(true)); with_window_event(Moved((0, 0).into())); with_window_event(Resized((0, 0).into())); with_window_event(DroppedFile("x.txt".into())); with_window_event(HoveredFile("x.txt".into())); with_window_event(HoveredFileCancelled); with_window_event(Ime(Enabled)); with_window_event(CursorMoved { device_id: did, position: (0, 0).into() }); with_window_event(ModifiersChanged(event::Modifiers::default())); with_window_event(CursorEntered { device_id: did }); with_window_event(CursorLeft { device_id: did }); with_window_event(MouseWheel { device_id: did, delta: event::MouseScrollDelta::LineDelta(0.0, 0.0), phase: event::TouchPhase::Started, }); with_window_event(MouseInput { device_id: did, state: event::ElementState::Pressed, button: event::MouseButton::Other(0), }); with_window_event(PinchGesture { device_id: did, delta: 0.0, phase: event::TouchPhase::Started, }); with_window_event(DoubleTapGesture { device_id: did }); with_window_event(RotationGesture { device_id: did, delta: 0.0, phase: event::TouchPhase::Started, }); with_window_event(PanGesture { device_id: did, delta: PhysicalPosition::::new(0.0, 0.0), phase: event::TouchPhase::Started, }); with_window_event(TouchpadPressure { device_id: did, pressure: 0.0, stage: 0 }); with_window_event(AxisMotion { device_id: did, axis: 0, value: 0.0 }); with_window_event(Touch(event::Touch { device_id: did, phase: event::TouchPhase::Started, location: (0.0, 0.0).into(), id: 0, force: Some(event::Force::Normalized(0.0)), })); with_window_event(ThemeChanged(crate::window::Theme::Light)); with_window_event(Occluded(true)); } #[allow(deprecated)] { use event::DeviceEvent::*; let with_device_event = |dev_ev| x(event::Event::DeviceEvent { device_id: did, event: dev_ev }); with_device_event(Added); with_device_event(Removed); with_device_event(MouseMotion { delta: (0.0, 0.0).into() }); with_device_event(MouseWheel { delta: event::MouseScrollDelta::LineDelta(0.0, 0.0), }); with_device_event(Motion { axis: 0, value: 0.0 }); with_device_event(Button { button: 0, state: event::ElementState::Pressed }); } }}; } #[allow(clippy::redundant_clone)] #[test] fn test_event_clone() { foreach_event!(|event: event::Event<()>| { let event2 = event.clone(); assert_eq!(event, event2); }) } #[test] fn test_map_nonuser_event() { foreach_event!(|event: event::Event<()>| { let is_user = matches!(event, event::Event::UserEvent(())); let event2 = event.map_nonuser_event::<()>(); if is_user { assert_eq!(event2, Err(event::Event::UserEvent(()))); } else { assert!(event2.is_ok()); } }) } #[test] fn test_force_normalize() { let force = event::Force::Normalized(0.0); assert_eq!(force.normalized(), 0.0); let force2 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5, altitude_angle: None }; assert_eq!(force2.normalized(), 2.0); let force3 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5, altitude_angle: Some(std::f64::consts::PI / 2.0), }; assert_eq!(force3.normalized(), 2.0); } #[allow(clippy::clone_on_copy)] #[test] fn ensure_attrs_do_not_panic() { foreach_event!(|event: event::Event<()>| { let _ = format!("{:?}", event); }); let _ = event::StartCause::Init.clone(); let did = crate::event::DeviceId::dummy().clone(); HashSet::new().insert(did); let mut set = [did, did, did]; set.sort_unstable(); let mut set2 = BTreeSet::new(); set2.insert(did); set2.insert(did); HashSet::new().insert(event::TouchPhase::Started.clone()); HashSet::new().insert(event::MouseButton::Left.clone()); HashSet::new().insert(event::Ime::Enabled); let _ = event::Touch { device_id: did, phase: event::TouchPhase::Started, location: (0.0, 0.0).into(), id: 0, force: Some(event::Force::Normalized(0.0)), } .clone(); let _ = event::Force::Calibrated { force: 0.0, max_possible_force: 0.0, altitude_angle: None } .clone(); } } winit-0.30.9/src/event_loop.rs000064400000000000000000000573201046102023000143650ustar 00000000000000//! The [`EventLoop`] struct and assorted supporting types, including //! [`ControlFlow`]. //! //! If you want to send custom events to the event loop, use //! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its //! [`send_event`][EventLoopProxy::send_event] method. //! //! See the root-level documentation for information on how to create and use an event loop to //! handle events. use std::marker::PhantomData; #[cfg(any(x11_platform, wayland_platform))] use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{error, fmt}; #[cfg(not(web_platform))] use std::time::{Duration, Instant}; #[cfg(web_platform)] use web_time::{Duration, Instant}; use crate::application::ApplicationHandler; use crate::error::{EventLoopError, OsError}; use crate::event::Event; use crate::monitor::MonitorHandle; use crate::platform_impl; use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes}; /// Provides a way to retrieve events from the system and from the windows that were registered to /// the events loop. /// /// An `EventLoop` can be seen more or less as a "context". Calling [`EventLoop::new`] /// initializes everything that will be required to create windows. For example on Linux creating /// an event loop opens a connection to the X or Wayland server. /// /// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs. /// /// Note that this cannot be shared across threads (due to platform-dependant logic /// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, /// the [`Window`] created from this _can_ be sent to an other thread, and the /// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread. /// /// [`Window`]: crate::window::Window pub struct EventLoop { pub(crate) event_loop: platform_impl::EventLoop, pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync } /// Target that associates windows with an [`EventLoop`]. /// /// This type exists to allow you to create new windows while Winit executes /// your callback. pub struct ActiveEventLoop { pub(crate) p: platform_impl::ActiveEventLoop, pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync } /// Object that allows building the event loop. /// /// This is used to make specifying options that affect the whole application /// easier. But note that constructing multiple event loops is not supported. /// /// This can be created using [`EventLoop::new`] or [`EventLoop::with_user_event`]. #[derive(Default)] pub struct EventLoopBuilder { pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes, _p: PhantomData, } static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false); impl EventLoopBuilder<()> { /// Start building a new event loop. #[inline] #[deprecated = "use `EventLoop::builder` instead"] pub fn new() -> Self { EventLoop::builder() } } impl EventLoopBuilder { /// Builds a new event loop. /// /// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread, /// and only once per application.*** /// /// Calling this function will result in display backend initialisation. /// /// ## Panics /// /// Attempting to create the event loop off the main thread will panic. This /// restriction isn't strictly necessary on all platforms, but is imposed to /// eliminate any nasty surprises when porting to platforms that require it. /// `EventLoopBuilderExt::any_thread` functions are exposed in the relevant /// [`platform`] module if the target platform supports creating an event /// loop on any thread. /// /// ## Platform-specific /// /// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY` or /// `DISPLAY` respectively when building the event loop. /// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling /// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic. /// /// [`platform`]: crate::platform #[cfg_attr( android_platform, doc = "[`.with_android_app(app)`]: \ crate::platform::android::EventLoopBuilderExtAndroid::with_android_app" )] #[cfg_attr( not(android_platform), doc = "[`.with_android_app(app)`]: #only-available-on-android" )] #[inline] pub fn build(&mut self) -> Result, EventLoopError> { let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered(); if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) { return Err(EventLoopError::RecreationAttempt); } // Certain platforms accept a mutable reference in their API. #[allow(clippy::unnecessary_mut_passed)] Ok(EventLoop { event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?, _marker: PhantomData, }) } #[cfg(web_platform)] pub(crate) fn allow_event_loop_recreation() { EVENT_LOOP_CREATED.store(false, Ordering::Relaxed); } } impl fmt::Debug for EventLoop { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.pad("EventLoop { .. }") } } impl fmt::Debug for ActiveEventLoop { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.pad("ActiveEventLoop { .. }") } } /// Set through [`ActiveEventLoop::set_control_flow()`]. /// /// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted. /// /// Defaults to [`Wait`]. /// /// [`Wait`]: Self::Wait #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub enum ControlFlow { /// When the current loop iteration finishes, immediately begin a new iteration regardless of /// whether or not new events are available to process. Poll, /// When the current loop iteration finishes, suspend the thread until another event arrives. #[default] Wait, /// When the current loop iteration finishes, suspend the thread until either another event /// arrives or the given time is reached. /// /// Useful for implementing efficient timers. Applications which want to render at the /// display's native refresh rate should instead use [`Poll`] and the VSync functionality /// of a graphics API to reduce odds of missed frames. /// /// [`Poll`]: Self::Poll WaitUntil(Instant), } impl ControlFlow { /// Creates a [`ControlFlow`] that waits until a timeout has expired. /// /// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is /// instead set to [`Wait`]. /// /// [`WaitUntil`]: Self::WaitUntil /// [`Wait`]: Self::Wait pub fn wait_duration(timeout: Duration) -> Self { match Instant::now().checked_add(timeout) { Some(instant) => Self::WaitUntil(instant), None => Self::Wait, } } } impl EventLoop<()> { /// Create the event loop. /// /// This is an alias of `EventLoop::builder().build()`. #[inline] pub fn new() -> Result, EventLoopError> { Self::builder().build() } /// Start building a new event loop. /// /// This returns an [`EventLoopBuilder`], to allow configuring the event loop before creation. /// /// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that. #[inline] pub fn builder() -> EventLoopBuilder<()> { Self::with_user_event() } } impl EventLoop { /// Start building a new event loop, with the given type as the user event /// type. pub fn with_user_event() -> EventLoopBuilder { EventLoopBuilder { platform_specific: Default::default(), _p: PhantomData } } /// See [`run_app`]. /// /// [`run_app`]: Self::run_app #[inline] #[deprecated = "use `EventLoop::run_app` instead"] #[cfg(not(all(web_platform, target_feature = "exception-handling")))] pub fn run(self, event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &ActiveEventLoop), { let _span = tracing::debug_span!("winit::EventLoop::run").entered(); self.event_loop.run(event_handler) } /// Run the application with the event loop on the calling thread. /// /// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// /// ## Platform-specific /// /// - **iOS:** Will never return to the caller and so values not passed to this function will /// *not* be dropped before the process exits. /// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript /// exception (that Rust doesn't see) that will also mean that the rest of the function is /// never executed and any values not passed to this function will *not* be dropped. /// /// Web applications are recommended to use #[cfg_attr( web_platform, doc = "[`EventLoopExtWebSys::spawn_app()`][crate::platform::web::EventLoopExtWebSys::spawn_app()]" )] #[cfg_attr(not(web_platform), doc = "`EventLoopExtWebSys::spawn()`")] /// [^1] instead of [`run_app()`] to avoid the need /// for the Javascript exception trick, and to make it clearer that the event loop runs /// asynchronously (via the browser's own, internal, event loop) and doesn't block the /// current thread of execution like it does on other platforms. /// /// This function won't be available with `target_feature = "exception-handling"`. /// /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`run_app()`]: Self::run_app() /// [^1]: `EventLoopExtWebSys::spawn_app()` is only available on Web. #[inline] #[cfg(not(all(web_platform, target_feature = "exception-handling")))] pub fn run_app>(self, app: &mut A) -> Result<(), EventLoopError> { self.event_loop.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event)) } /// Creates an [`EventLoopProxy`] that can be used to dispatch user events /// to the main event loop, possibly from another thread. pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { event_loop_proxy: self.event_loop.create_proxy() } } /// Gets a persistent reference to the underlying platform display. /// /// See the [`OwnedDisplayHandle`] type for more information. pub fn owned_display_handle(&self) -> OwnedDisplayHandle { OwnedDisplayHandle { platform: self.event_loop.window_target().p.owned_display_handle() } } /// Change if or when [`DeviceEvent`]s are captured. /// /// See [`ActiveEventLoop::listen_device_events`] for details. /// /// [`DeviceEvent`]: crate::event::DeviceEvent pub fn listen_device_events(&self, allowed: DeviceEvents) { let _span = tracing::debug_span!( "winit::EventLoop::listen_device_events", allowed = ?allowed ) .entered(); self.event_loop.window_target().p.listen_device_events(allowed); } /// Sets the [`ControlFlow`]. pub fn set_control_flow(&self, control_flow: ControlFlow) { self.event_loop.window_target().p.set_control_flow(control_flow) } /// Create a window. /// /// Creating window without event loop running often leads to improper window creation; /// use [`ActiveEventLoop::create_window`] instead. #[deprecated = "use `ActiveEventLoop::create_window` instead"] #[inline] pub fn create_window(&self, window_attributes: WindowAttributes) -> Result { let _span = tracing::debug_span!( "winit::EventLoop::create_window", window_attributes = ?window_attributes ) .entered(); let window = platform_impl::Window::new(&self.event_loop.window_target().p, window_attributes)?; Ok(Window { window }) } /// Create custom cursor. pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor { self.event_loop.window_target().p.create_custom_cursor(custom_cursor) } } #[cfg(feature = "rwh_06")] impl rwh_06::HasDisplayHandle for EventLoop { fn display_handle(&self) -> Result, rwh_06::HandleError> { rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target()) } } #[cfg(feature = "rwh_05")] unsafe impl rwh_05::HasRawDisplayHandle for EventLoop { /// Returns a [`rwh_05::RawDisplayHandle`] for the event loop. fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { rwh_05::HasRawDisplayHandle::raw_display_handle(self.event_loop.window_target()) } } #[cfg(any(x11_platform, wayland_platform))] impl AsFd for EventLoop { /// Get the underlying [EventLoop]'s `fd` which you can register /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the /// loop must be polled with the [`pump_app_events`] API. /// /// [`calloop`]: https://crates.io/crates/calloop /// [`mio`]: https://crates.io/crates/mio /// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events fn as_fd(&self) -> BorrowedFd<'_> { self.event_loop.as_fd() } } #[cfg(any(x11_platform, wayland_platform))] impl AsRawFd for EventLoop { /// Get the underlying [EventLoop]'s raw `fd` which you can register /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the /// loop must be polled with the [`pump_app_events`] API. /// /// [`calloop`]: https://crates.io/crates/calloop /// [`mio`]: https://crates.io/crates/mio /// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events fn as_raw_fd(&self) -> RawFd { self.event_loop.as_raw_fd() } } impl ActiveEventLoop { /// Create the window. /// /// Possible causes of error include denied permission, incompatible system, and lack of memory. /// /// ## Platform-specific /// /// - **Web:** The window is created but not inserted into the web page automatically. Please /// see the web platform module for more information. #[inline] pub fn create_window(&self, window_attributes: WindowAttributes) -> Result { let _span = tracing::debug_span!( "winit::ActiveEventLoop::create_window", window_attributes = ?window_attributes ) .entered(); let window = platform_impl::Window::new(&self.p, window_attributes)?; Ok(Window { window }) } /// Create custom cursor. pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor { let _span = tracing::debug_span!("winit::ActiveEventLoop::create_custom_cursor",).entered(); self.p.create_custom_cursor(custom_cursor) } /// Returns the list of all the monitors available on the system. #[inline] pub fn available_monitors(&self) -> impl Iterator { let _span = tracing::debug_span!("winit::ActiveEventLoop::available_monitors",).entered(); #[allow(clippy::useless_conversion)] // false positive on some platforms self.p.available_monitors().into_iter().map(|inner| MonitorHandle { inner }) } /// Returns the primary monitor of the system. /// /// Returns `None` if it can't identify any monitor as a primary one. /// /// ## Platform-specific /// /// **Wayland / Web:** Always returns `None`. #[inline] pub fn primary_monitor(&self) -> Option { let _span = tracing::debug_span!("winit::ActiveEventLoop::primary_monitor",).entered(); self.p.primary_monitor().map(|inner| MonitorHandle { inner }) } /// Change if or when [`DeviceEvent`]s are captured. /// /// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit /// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing /// this at runtime to explicitly capture them again. /// /// ## Platform-specific /// /// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported. /// /// [`DeviceEvent`]: crate::event::DeviceEvent pub fn listen_device_events(&self, allowed: DeviceEvents) { let _span = tracing::debug_span!( "winit::ActiveEventLoop::listen_device_events", allowed = ?allowed ) .entered(); self.p.listen_device_events(allowed); } /// Returns the current system theme. /// /// Returns `None` if it cannot be determined on the current platform. /// /// ## Platform-specific /// /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported. pub fn system_theme(&self) -> Option { self.p.system_theme() } /// Sets the [`ControlFlow`]. pub fn set_control_flow(&self, control_flow: ControlFlow) { self.p.set_control_flow(control_flow) } /// Gets the current [`ControlFlow`]. pub fn control_flow(&self) -> ControlFlow { self.p.control_flow() } /// This exits the event loop. /// /// See [`LoopExiting`][Event::LoopExiting]. pub fn exit(&self) { let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered(); self.p.exit() } /// Returns if the [`EventLoop`] is about to stop. /// /// See [`exit()`][Self::exit]. pub fn exiting(&self) -> bool { self.p.exiting() } /// Gets a persistent reference to the underlying platform display. /// /// See the [`OwnedDisplayHandle`] type for more information. pub fn owned_display_handle(&self) -> OwnedDisplayHandle { OwnedDisplayHandle { platform: self.p.owned_display_handle() } } } #[cfg(feature = "rwh_06")] impl rwh_06::HasDisplayHandle for ActiveEventLoop { fn display_handle(&self) -> Result, rwh_06::HandleError> { let raw = self.p.raw_display_handle_rwh_06()?; // SAFETY: The display will never be deallocated while the event loop is alive. Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) }) } } #[cfg(feature = "rwh_05")] unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop { /// Returns a [`rwh_05::RawDisplayHandle`] for the event loop. fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { self.p.raw_display_handle_rwh_05() } } /// A proxy for the underlying display handle. /// /// The purpose of this type is to provide a cheaply cloneable handle to the underlying /// display handle. This is often used by graphics APIs to connect to the underlying APIs. /// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`] /// type. In contrast, this type involves no lifetimes and can be persisted for as long as /// needed. /// /// For all platforms, this is one of the following: /// /// - A zero-sized type that is likely optimized out. /// - A reference-counted pointer to the underlying type. #[derive(Clone)] pub struct OwnedDisplayHandle { #[cfg_attr(not(any(feature = "rwh_05", feature = "rwh_06")), allow(dead_code))] platform: platform_impl::OwnedDisplayHandle, } impl fmt::Debug for OwnedDisplayHandle { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive() } } #[cfg(feature = "rwh_06")] impl rwh_06::HasDisplayHandle for OwnedDisplayHandle { #[inline] fn display_handle(&self) -> Result, rwh_06::HandleError> { let raw = self.platform.raw_display_handle_rwh_06()?; // SAFETY: The underlying display handle should be safe. let handle = unsafe { rwh_06::DisplayHandle::borrow_raw(raw) }; Ok(handle) } } #[cfg(feature = "rwh_05")] unsafe impl rwh_05::HasRawDisplayHandle for OwnedDisplayHandle { #[inline] fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { self.platform.raw_display_handle_rwh_05() } } /// Used to send custom events to [`EventLoop`]. pub struct EventLoopProxy { event_loop_proxy: platform_impl::EventLoopProxy, } impl Clone for EventLoopProxy { fn clone(&self) -> Self { Self { event_loop_proxy: self.event_loop_proxy.clone() } } } impl EventLoopProxy { /// Send an event to the [`EventLoop`] from which this proxy was created. This emits a /// `UserEvent(event)` event in the event loop, where `event` is the value passed to this /// function. /// /// Returns an `Err` if the associated [`EventLoop`] no longer exists. /// /// [`UserEvent(event)`]: Event::UserEvent pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { let _span = tracing::debug_span!("winit::EventLoopProxy::send_event",).entered(); self.event_loop_proxy.send_event(event) } } impl fmt::Debug for EventLoopProxy { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.pad("EventLoopProxy { .. }") } } /// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that /// no longer exists. /// /// Contains the original event given to [`EventLoopProxy::send_event`]. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct EventLoopClosed(pub T); impl fmt::Display for EventLoopClosed { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Tried to wake up a closed `EventLoop`") } } impl error::Error for EventLoopClosed {} /// Control when device events are captured. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub enum DeviceEvents { /// Report device events regardless of window focus. Always, /// Only capture device events while the window is focused. #[default] WhenFocused, /// Never capture device events. Never, } /// A unique identifier of the winit's async request. /// /// This could be used to identify the async request once it's done /// and a specific action must be taken. /// /// One of the handling scenarios could be to maintain a working list /// containing [`AsyncRequestSerial`] and some closure associated with it. /// Then once event is arriving the working list is being traversed and a job /// executed and removed from the list. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct AsyncRequestSerial { serial: usize, } impl AsyncRequestSerial { // TODO(kchibisov): Remove `cfg` when the clipboard will be added. #[allow(dead_code)] pub(crate) fn get() -> Self { static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0); // NOTE: We rely on wrap around here, while the user may just request // in the loop usize::MAX times that's issue is considered on them. let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed); Self { serial } } } /// Shim for various run APIs. #[inline(always)] pub(crate) fn dispatch_event_for_app>( app: &mut A, event_loop: &ActiveEventLoop, event: Event, ) { match event { Event::NewEvents(cause) => app.new_events(event_loop, cause), Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event), Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event), Event::UserEvent(event) => app.user_event(event_loop, event), Event::Suspended => app.suspended(event_loop), Event::Resumed => app.resumed(event_loop), Event::AboutToWait => app.about_to_wait(event_loop), Event::LoopExiting => app.exiting(event_loop), Event::MemoryWarning => app.memory_warning(event_loop), } } winit-0.30.9/src/icon.rs000064400000000000000000000077621046102023000131500ustar 00000000000000use crate::platform_impl::PlatformIcon; use std::error::Error; use std::{fmt, io, mem}; #[repr(C)] #[derive(Debug)] pub(crate) struct Pixel { pub(crate) r: u8, pub(crate) g: u8, pub(crate) b: u8, pub(crate) a: u8, } pub(crate) const PIXEL_SIZE: usize = mem::size_of::(); #[derive(Debug)] /// An error produced when using [`Icon::from_rgba`] with invalid arguments. pub enum BadIcon { /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be /// safely interpreted as 32bpp RGBA pixels. ByteCountNotDivisibleBy4 { byte_count: usize }, /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`. /// At least one of your arguments is incorrect. DimensionsVsPixelCount { width: u32, height: u32, width_x_height: usize, pixel_count: usize }, /// Produced when underlying OS functionality failed to create the icon OsError(io::Error), } impl fmt::Display for BadIcon { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!( f, "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \ it impossible to interpret as 32bpp RGBA pixels.", ), BadIcon::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => { write!( f, "The specified dimensions ({width:?}x{height:?}) don't match the number of \ pixels supplied by the `rgba` argument ({pixel_count:?}). For those \ dimensions, the expected pixel count is {width_x_height:?}.", ) }, BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"), } } } impl Error for BadIcon {} #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct RgbaIcon { pub(crate) rgba: Vec, pub(crate) width: u32, pub(crate) height: u32, } /// For platforms which don't have window icons (e.g. web) #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct NoIcon; #[allow(dead_code)] // These are not used on every platform mod constructors { use super::*; impl RgbaIcon { pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { if rgba.len() % PIXEL_SIZE != 0 { return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() }); } let pixel_count = rgba.len() / PIXEL_SIZE; if pixel_count != (width * height) as usize { Err(BadIcon::DimensionsVsPixelCount { width, height, width_x_height: (width * height) as usize, pixel_count, }) } else { Ok(RgbaIcon { rgba, width, height }) } } } impl NoIcon { pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { // Create the rgba icon anyway to validate the input let _ = RgbaIcon::from_rgba(rgba, width, height)?; Ok(NoIcon) } } } /// An icon used for the window titlebar, taskbar, etc. #[derive(Clone)] pub struct Icon { pub(crate) inner: PlatformIcon, } impl fmt::Debug for Icon { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fmt::Debug::fmt(&self.inner, formatter) } } impl Icon { /// Creates an icon from 32bpp RGBA data. /// /// The length of `rgba` must be divisible by 4, and `width * height` must equal /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { let _span = tracing::debug_span!("winit::Icon::from_rgba", width, height).entered(); Ok(Icon { inner: PlatformIcon::from_rgba(rgba, width, height)? }) } } winit-0.30.9/src/keyboard.rs000064400000000000000000002040171046102023000140100ustar 00000000000000//! Types related to the keyboard. // This file contains a substantial portion of the UI Events Specification by the W3C. In // particular, the variant names within `Key` and `KeyCode` and their documentation are modified // versions of contents of the aforementioned specification. // // The original documents are: // // ### For `Key` // UI Events KeyboardEvent key Values // https://www.w3.org/TR/2017/CR-uievents-key-20170601/ // Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). // // ### For `KeyCode` // UI Events KeyboardEvent code Values // https://www.w3.org/TR/2017/CR-uievents-code-20170601/ // Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). // // These documents were used under the terms of the following license. This W3C license as well as // the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the // documentation attached to their variants. // --------- BEGINNING OF W3C LICENSE -------------------------------------------------------------- // // License // // By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, // and will comply with the following terms and conditions. // // Permission to copy, modify, and distribute this work, with or without modification, for any // purpose and without fee or royalty is hereby granted, provided that you include the following on // ALL copies of the work or portions thereof, including modifications: // // - The full text of this NOTICE in a location viewable to users of the redistributed or derivative // work. // - Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none // exist, the W3C Software and Document Short Notice should be included. // - Notice of any changes or modifications, through a copyright statement on the new code or // document such as "This software or document includes material copied from or derived from // [title and URI of the W3C document]. Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." // // Disclaimers // // THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR // ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD // PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. // // COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES // ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. // // The name and trademarks of copyright holders may NOT be used in advertising or publicity // pertaining to the work without specific, written prior permission. Title to copyright in this // work will at all times remain with copyright holders. // // --------- END OF W3C LICENSE -------------------------------------------------------------------- // --------- BEGINNING OF W3C SHORT NOTICE --------------------------------------------------------- // // winit: https://github.com/rust-windowing/winit // // Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology, European // Research Consortium for Informatics and Mathematics, Keio University, Beihang). All Rights // Reserved. This work is distributed under the W3C® Software License [1] in the hope that it will // be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // [1] http://www.w3.org/Consortium/Legal/copyright-software // // --------- END OF W3C SHORT NOTICE --------------------------------------------------------------- use bitflags::bitflags; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; pub use smol_str::SmolStr; /// Contains the platform-native physical key identifier /// /// The exact values vary from platform to platform (which is part of why this is a per-platform /// enum), but the values are primarily tied to the key's physical location on the keyboard. /// /// This enum is primarily used to store raw keycodes when Winit doesn't map a given native /// physical key identifier to a meaningful [`KeyCode`] variant. In the presence of identifiers we /// haven't mapped for you yet, this lets you use use [`KeyCode`] to: /// /// - Correctly match key press and release events. /// - On non-web platforms, support assigning keybinds to virtually any key through a UI. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NativeKeyCode { Unidentified, /// An Android "scancode". Android(u32), /// A macOS "scancode". MacOS(u16), /// A Windows "scancode". Windows(u16), /// An XKB "keycode". Xkb(u32), } impl std::fmt::Debug for NativeKeyCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use NativeKeyCode::{Android, MacOS, Unidentified, Windows, Xkb}; let mut debug_tuple; match self { Unidentified => { debug_tuple = f.debug_tuple("Unidentified"); }, Android(code) => { debug_tuple = f.debug_tuple("Android"); debug_tuple.field(&format_args!("0x{code:04X}")); }, MacOS(code) => { debug_tuple = f.debug_tuple("MacOS"); debug_tuple.field(&format_args!("0x{code:04X}")); }, Windows(code) => { debug_tuple = f.debug_tuple("Windows"); debug_tuple.field(&format_args!("0x{code:04X}")); }, Xkb(code) => { debug_tuple = f.debug_tuple("Xkb"); debug_tuple.field(&format_args!("0x{code:04X}")); }, } debug_tuple.finish() } } /// Contains the platform-native logical key identifier /// /// Exactly what that means differs from platform to platform, but the values are to some degree /// tied to the currently active keyboard layout. The same key on the same keyboard may also report /// different values on different platforms, which is one of the reasons this is a per-platform /// enum. /// /// This enum is primarily used to store raw keysym when Winit doesn't map a given native logical /// key identifier to a meaningful [`Key`] variant. This lets you use [`Key`], and let the user /// define keybinds which work in the presence of identifiers we haven't mapped for you yet. #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NativeKey { Unidentified, /// An Android "keycode", which is similar to a "virtual-key code" on Windows. Android(u32), /// A macOS "scancode". There does not appear to be any direct analogue to either keysyms or /// "virtual-key" codes in macOS, so we report the scancode instead. MacOS(u16), /// A Windows "virtual-key code". Windows(u16), /// An XKB "keysym". Xkb(u32), /// A "key value string". Web(SmolStr), } impl std::fmt::Debug for NativeKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use NativeKey::{Android, MacOS, Unidentified, Web, Windows, Xkb}; let mut debug_tuple; match self { Unidentified => { debug_tuple = f.debug_tuple("Unidentified"); }, Android(code) => { debug_tuple = f.debug_tuple("Android"); debug_tuple.field(&format_args!("0x{code:04X}")); }, MacOS(code) => { debug_tuple = f.debug_tuple("MacOS"); debug_tuple.field(&format_args!("0x{code:04X}")); }, Windows(code) => { debug_tuple = f.debug_tuple("Windows"); debug_tuple.field(&format_args!("0x{code:04X}")); }, Xkb(code) => { debug_tuple = f.debug_tuple("Xkb"); debug_tuple.field(&format_args!("0x{code:04X}")); }, Web(code) => { debug_tuple = f.debug_tuple("Web"); debug_tuple.field(code); }, } debug_tuple.finish() } } impl From for NativeKey { #[inline] fn from(code: NativeKeyCode) -> Self { match code { NativeKeyCode::Unidentified => NativeKey::Unidentified, NativeKeyCode::Android(x) => NativeKey::Android(x), NativeKeyCode::MacOS(x) => NativeKey::MacOS(x), NativeKeyCode::Windows(x) => NativeKey::Windows(x), NativeKeyCode::Xkb(x) => NativeKey::Xkb(x), } } } impl PartialEq for NativeKeyCode { #[allow(clippy::cmp_owned)] // uses less code than direct match; target is stack allocated #[inline] fn eq(&self, rhs: &NativeKey) -> bool { NativeKey::from(*self) == *rhs } } impl PartialEq for NativeKey { #[inline] fn eq(&self, rhs: &NativeKeyCode) -> bool { rhs == self } } /// Represents the location of a physical key. /// /// This type is a superset of [`KeyCode`], including an [`Unidentified`][Self::Unidentified] /// variant. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum PhysicalKey { /// A known key code Code(KeyCode), /// This variant is used when the key cannot be translated to a [`KeyCode`] /// /// The native keycode is provided (if available) so you're able to more reliably match /// key-press and key-release events by hashing the [`PhysicalKey`]. It is also possible to use /// this for keybinds for non-standard keys, but such keybinds are tied to a given platform. Unidentified(NativeKeyCode), } impl From for PhysicalKey { #[inline] fn from(code: KeyCode) -> Self { PhysicalKey::Code(code) } } impl From for PhysicalKey { #[inline] fn from(code: NativeKeyCode) -> Self { PhysicalKey::Unidentified(code) } } impl PartialEq for PhysicalKey { #[inline] fn eq(&self, rhs: &KeyCode) -> bool { match self { PhysicalKey::Code(ref code) => code == rhs, _ => false, } } } impl PartialEq for KeyCode { #[inline] fn eq(&self, rhs: &PhysicalKey) -> bool { rhs == self } } impl PartialEq for PhysicalKey { #[inline] fn eq(&self, rhs: &NativeKeyCode) -> bool { match self { PhysicalKey::Unidentified(ref code) => code == rhs, _ => false, } } } impl PartialEq for NativeKeyCode { #[inline] fn eq(&self, rhs: &PhysicalKey) -> bool { rhs == self } } /// Code representing the location of a physical key /// /// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few /// exceptions: /// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and /// "SuperRight" here. /// - The key that the specification calls "Super" is reported as `Unidentified` here. /// /// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum KeyCode { /// ` on a US keyboard. This is also called a backtick or grave. /// This is the åŠè§’/全角/漢字 /// (hankaku/zenkaku/kanji) key on Japanese keyboards Backquote, /// Used for both the US \\ (on the 101-key layout) and also for the key /// located between the " and Enter keys on row C of the 102-, /// 104- and 106-key layouts. /// Labeled # on a UK (102) keyboard. Backslash, /// [ on a US keyboard. BracketLeft, /// ] on a US keyboard. BracketRight, /// , on a US keyboard. Comma, /// 0 on a US keyboard. Digit0, /// 1 on a US keyboard. Digit1, /// 2 on a US keyboard. Digit2, /// 3 on a US keyboard. Digit3, /// 4 on a US keyboard. Digit4, /// 5 on a US keyboard. Digit5, /// 6 on a US keyboard. Digit6, /// 7 on a US keyboard. Digit7, /// 8 on a US keyboard. Digit8, /// 9 on a US keyboard. Digit9, /// = on a US keyboard. Equal, /// Located between the left Shift and Z keys. /// Labeled \\ on a UK keyboard. IntlBackslash, /// Located between the / and right Shift keys. /// Labeled \\ (ro) on a Japanese keyboard. IntlRo, /// Located between the = and Backspace keys. /// Labeled Â¥ (yen) on a Japanese keyboard. \\ on a /// Russian keyboard. IntlYen, /// a on a US keyboard. /// Labeled q on an AZERTY (e.g., French) keyboard. KeyA, /// b on a US keyboard. KeyB, /// c on a US keyboard. KeyC, /// d on a US keyboard. KeyD, /// e on a US keyboard. KeyE, /// f on a US keyboard. KeyF, /// g on a US keyboard. KeyG, /// h on a US keyboard. KeyH, /// i on a US keyboard. KeyI, /// j on a US keyboard. KeyJ, /// k on a US keyboard. KeyK, /// l on a US keyboard. KeyL, /// m on a US keyboard. KeyM, /// n on a US keyboard. KeyN, /// o on a US keyboard. KeyO, /// p on a US keyboard. KeyP, /// q on a US keyboard. /// Labeled a on an AZERTY (e.g., French) keyboard. KeyQ, /// r on a US keyboard. KeyR, /// s on a US keyboard. KeyS, /// t on a US keyboard. KeyT, /// u on a US keyboard. KeyU, /// v on a US keyboard. KeyV, /// w on a US keyboard. /// Labeled z on an AZERTY (e.g., French) keyboard. KeyW, /// x on a US keyboard. KeyX, /// y on a US keyboard. /// Labeled z on a QWERTZ (e.g., German) keyboard. KeyY, /// z on a US keyboard. /// Labeled w on an AZERTY (e.g., French) keyboard, and y on a /// QWERTZ (e.g., German) keyboard. KeyZ, /// - on a US keyboard. Minus, /// . on a US keyboard. Period, /// ' on a US keyboard. Quote, /// ; on a US keyboard. Semicolon, /// / on a US keyboard. Slash, /// Alt, Option, or ⌥. AltLeft, /// Alt, Option, or ⌥. /// This is labeled AltGr on many keyboard layouts. AltRight, /// Backspace or ⌫. /// Labeled Delete on Apple keyboards. Backspace, /// CapsLock or ⇪ CapsLock, /// The application context menu key, which is typically found between the right /// Super key and the right Control key. ContextMenu, /// Control or ⌃ ControlLeft, /// Control or ⌃ ControlRight, /// Enter or ↵. Labeled Return on Apple keyboards. Enter, /// The Windows, ⌘, Command, or other OS symbol key. SuperLeft, /// The Windows, ⌘, Command, or other OS symbol key. SuperRight, /// Shift or ⇧ ShiftLeft, /// Shift or ⇧ ShiftRight, ///   (space) Space, /// Tab or ⇥ Tab, /// Japanese: 変 (henkan) Convert, /// Japanese: カタカナ/ã²ã‚‰ãŒãª/ローマ字 /// (katakana/hiragana/romaji) KanaMode, /// Korean: HangulMode 한/ì˜ (han/yeong) /// /// Japanese (Mac keyboard): ã‹ (kana) Lang1, /// Korean: Hanja 한 (hanja) /// /// Japanese (Mac keyboard): 英 (eisu) Lang2, /// Japanese (word-processing keyboard): Katakana Lang3, /// Japanese (word-processing keyboard): Hiragana Lang4, /// Japanese (word-processing keyboard): Zenkaku/Hankaku Lang5, /// Japanese: ç„¡å¤‰æ› (muhenkan) NonConvert, /// ⌦. The forward delete key. /// Note that on Apple keyboards, the key labelled Delete on the main part of /// the keyboard is encoded as [`Backspace`]. /// /// [`Backspace`]: Self::Backspace Delete, /// Page Down, End, or ↘ End, /// Help. Not present on standard PC keyboards. Help, /// Home or ↖ Home, /// Insert or Ins. Not present on Apple keyboards. Insert, /// Page Down, PgDn, or ⇟ PageDown, /// Page Up, PgUp, or ⇞ PageUp, /// ↓ ArrowDown, /// ↠ArrowLeft, /// → ArrowRight, /// ↑ ArrowUp, /// On the Mac, this is used for the numpad Clear key. NumLock, /// 0 Ins on a keyboard. 0 on a phone or remote control Numpad0, /// 1 End on a keyboard. 1 or 1 QZ on a phone or remote /// control Numpad1, /// 2 ↓ on a keyboard. 2 ABC on a phone or remote control Numpad2, /// 3 PgDn on a keyboard. 3 DEF on a phone or remote control Numpad3, /// 4 ↠on a keyboard. 4 GHI on a phone or remote control Numpad4, /// 5 on a keyboard. 5 JKL on a phone or remote control Numpad5, /// 6 → on a keyboard. 6 MNO on a phone or remote control Numpad6, /// 7 Home on a keyboard. 7 PQRS or 7 PRS on a phone /// or remote control Numpad7, /// 8 ↑ on a keyboard. 8 TUV on a phone or remote control Numpad8, /// 9 PgUp on a keyboard. 9 WXYZ or 9 WXY on a phone /// or remote control Numpad9, /// + NumpadAdd, /// Found on the Microsoft Natural Keyboard. NumpadBackspace, /// C or A (All Clear). Also for use with numpads that have a /// Clear key that is separate from the NumLock key. On the Mac, the /// numpad Clear key is encoded as [`NumLock`]. /// /// [`NumLock`]: Self::NumLock NumpadClear, /// C (Clear Entry) NumpadClearEntry, /// , (thousands separator). For locales where the thousands separator /// is a "." (e.g., Brazil), this key may generate a .. NumpadComma, /// . Del. For locales where the decimal separator is "," (e.g., /// Brazil), this key may generate a ,. NumpadDecimal, /// / NumpadDivide, NumpadEnter, /// = NumpadEqual, /// # on a phone or remote control device. This key is typically found /// below the 9 key and to the right of the 0 key. NumpadHash, /// M Add current entry to the value stored in memory. NumpadMemoryAdd, /// M Clear the value stored in memory. NumpadMemoryClear, /// M Replace the current entry with the value stored in memory. NumpadMemoryRecall, /// M Replace the value stored in memory with the current entry. NumpadMemoryStore, /// M Subtract current entry from the value stored in memory. NumpadMemorySubtract, /// * on a keyboard. For use with numpads that provide mathematical /// operations (+, - * and /). /// /// Use `NumpadStar` for the * key on phones and remote controls. NumpadMultiply, /// ( Found on the Microsoft Natural Keyboard. NumpadParenLeft, /// ) Found on the Microsoft Natural Keyboard. NumpadParenRight, /// * on a phone or remote control device. /// /// This key is typically found below the 7 key and to the left of /// the 0 key. /// /// Use "NumpadMultiply" for the * key on /// numeric keypads. NumpadStar, /// - NumpadSubtract, /// Esc or ⎋ Escape, /// Fn This is typically a hardware key that does not generate a separate code. Fn, /// FLock or FnLock. Function Lock key. Found on the Microsoft /// Natural Keyboard. FnLock, /// PrtScr SysRq or Print Screen PrintScreen, /// Scroll Lock ScrollLock, /// Pause Break Pause, /// Some laptops place this key to the left of the ↑ key. /// /// This also the "back" button (triangle) on Android. BrowserBack, BrowserFavorites, /// Some laptops place this key to the right of the ↑ key. BrowserForward, /// The "home" button on Android. BrowserHome, BrowserRefresh, BrowserSearch, BrowserStop, /// Eject or â. This key is placed in the function section on some Apple /// keyboards. Eject, /// Sometimes labelled My Computer on the keyboard LaunchApp1, /// Sometimes labelled Calculator on the keyboard LaunchApp2, LaunchMail, MediaPlayPause, MediaSelect, MediaStop, MediaTrackNext, MediaTrackPrevious, /// This key is placed in the function section on some Apple keyboards, replacing the /// Eject key. Power, Sleep, AudioVolumeDown, AudioVolumeMute, AudioVolumeUp, WakeUp, // Legacy modifier key. Also called "Super" in certain places. Meta, // Legacy modifier key. Hyper, Turbo, Abort, Resume, Suspend, /// Found on Sun’s USB keyboard. Again, /// Found on Sun’s USB keyboard. Copy, /// Found on Sun’s USB keyboard. Cut, /// Found on Sun’s USB keyboard. Find, /// Found on Sun’s USB keyboard. Open, /// Found on Sun’s USB keyboard. Paste, /// Found on Sun’s USB keyboard. Props, /// Found on Sun’s USB keyboard. Select, /// Found on Sun’s USB keyboard. Undo, /// Use for dedicated ã²ã‚‰ãŒãª key found on some Japanese word processing keyboards. Hiragana, /// Use for dedicated カタカナ key found on some Japanese word processing keyboards. Katakana, /// General-purpose function key. /// Usually found at the top of the keyboard. F1, /// General-purpose function key. /// Usually found at the top of the keyboard. F2, /// General-purpose function key. /// Usually found at the top of the keyboard. F3, /// General-purpose function key. /// Usually found at the top of the keyboard. F4, /// General-purpose function key. /// Usually found at the top of the keyboard. F5, /// General-purpose function key. /// Usually found at the top of the keyboard. F6, /// General-purpose function key. /// Usually found at the top of the keyboard. F7, /// General-purpose function key. /// Usually found at the top of the keyboard. F8, /// General-purpose function key. /// Usually found at the top of the keyboard. F9, /// General-purpose function key. /// Usually found at the top of the keyboard. F10, /// General-purpose function key. /// Usually found at the top of the keyboard. F11, /// General-purpose function key. /// Usually found at the top of the keyboard. F12, /// General-purpose function key. /// Usually found at the top of the keyboard. F13, /// General-purpose function key. /// Usually found at the top of the keyboard. F14, /// General-purpose function key. /// Usually found at the top of the keyboard. F15, /// General-purpose function key. /// Usually found at the top of the keyboard. F16, /// General-purpose function key. /// Usually found at the top of the keyboard. F17, /// General-purpose function key. /// Usually found at the top of the keyboard. F18, /// General-purpose function key. /// Usually found at the top of the keyboard. F19, /// General-purpose function key. /// Usually found at the top of the keyboard. F20, /// General-purpose function key. /// Usually found at the top of the keyboard. F21, /// General-purpose function key. /// Usually found at the top of the keyboard. F22, /// General-purpose function key. /// Usually found at the top of the keyboard. F23, /// General-purpose function key. /// Usually found at the top of the keyboard. F24, /// General-purpose function key. F25, /// General-purpose function key. F26, /// General-purpose function key. F27, /// General-purpose function key. F28, /// General-purpose function key. F29, /// General-purpose function key. F30, /// General-purpose function key. F31, /// General-purpose function key. F32, /// General-purpose function key. F33, /// General-purpose function key. F34, /// General-purpose function key. F35, } /// A [`Key::Named`] value /// /// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few /// exceptions: /// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's /// another key which the specification calls `Super`. That does not exist here.) /// - The `Space` variant here, can be identified by the character it generates in the /// specification. /// /// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/ #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NamedKey { /// The `Alt` (Alternative) key. /// /// This key enables the alternate modifier function for interpreting concurrent or subsequent /// keyboard input. This key value is also used for the Apple Option key. Alt, /// The Alternate Graphics (AltGr or AltGraph) key. /// /// This key is used enable the ISO Level 3 shift modifier (the standard `Shift` key is the /// level 2 modifier). AltGraph, /// The `Caps Lock` (Capital) key. /// /// Toggle capital character lock function for interpreting subsequent keyboard input event. CapsLock, /// The `Control` or `Ctrl` key. /// /// Used to enable control modifier function for interpreting concurrent or subsequent keyboard /// input. Control, /// The Function switch `Fn` key. Activating this key simultaneously with another key changes /// that key’s value to an alternate character or function. This key is often handled directly /// in the keyboard hardware and does not usually generate key events. Fn, /// The Function-Lock (`FnLock` or `F-Lock`) key. Activating this key switches the mode of the /// keyboard to changes some keys' values to an alternate character or function. This key is /// often handled directly in the keyboard hardware and does not usually generate key events. FnLock, /// The `NumLock` or Number Lock key. Used to toggle numpad mode function for interpreting /// subsequent keyboard input. NumLock, /// Toggle between scrolling and cursor movement modes. ScrollLock, /// Used to enable shift modifier function for interpreting concurrent or subsequent keyboard /// input. Shift, /// The Symbol modifier key (used on some virtual keyboards). Symbol, SymbolLock, // Legacy modifier key. Also called "Super" in certain places. Meta, // Legacy modifier key. Hyper, /// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard /// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` /// key. /// /// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key. Super, /// The `Enter` or `↵` key. Used to activate current selection or accept current input. This /// key value is also used for the `Return` (Macintosh numpad) key. This key value is also /// used for the Android `KEYCODE_DPAD_CENTER`. Enter, /// The Horizontal Tabulation `Tab` key. Tab, /// Used in text to insert a space between words. Usually located below the character keys. Space, /// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`) ArrowDown, /// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`) ArrowLeft, /// Navigate or traverse rightward. (`KEYCODE_DPAD_RIGHT`) ArrowRight, /// Navigate or traverse upward. (`KEYCODE_DPAD_UP`) ArrowUp, /// The End key, used with keyboard entry to go to the end of content (`KEYCODE_MOVE_END`). End, /// The Home key, used with keyboard entry, to go to start of content (`KEYCODE_MOVE_HOME`). /// For the mobile phone `Home` key (which goes to the phone’s main screen), use [`GoHome`]. /// /// [`GoHome`]: Self::GoHome Home, /// Scroll down or display next page of content. PageDown, /// Scroll up or display previous page of content. PageUp, /// Used to remove the character to the left of the cursor. This key value is also used for /// the key labeled `Delete` on MacOS keyboards. Backspace, /// Remove the currently selected input. Clear, /// Copy the current selection. (`APPCOMMAND_COPY`) Copy, /// The Cursor Select key. CrSel, /// Cut the current selection. (`APPCOMMAND_CUT`) Cut, /// Used to delete the character to the right of the cursor. This key value is also used for /// the key labeled `Delete` on MacOS keyboards when `Fn` is active. Delete, /// The Erase to End of Field key. This key deletes all characters from the current cursor /// position to the end of the current field. EraseEof, /// The Extend Selection (Exsel) key. ExSel, /// Toggle between text modes for insertion or overtyping. /// (`KEYCODE_INSERT`) Insert, /// The Paste key. (`APPCOMMAND_PASTE`) Paste, /// Redo the last action. (`APPCOMMAND_REDO`) Redo, /// Undo the last action. (`APPCOMMAND_UNDO`) Undo, /// The Accept (Commit, OK) key. Accept current option or input method sequence conversion. Accept, /// Redo or repeat an action. Again, /// The Attention (Attn) key. Attn, Cancel, /// Show the application’s context menu. /// This key is commonly found between the right `Super` key and the right `Control` key. ContextMenu, /// The `Esc` key. This key was originally used to initiate an escape sequence, but is /// now more generally used to exit or "escape" the current context, such as closing a dialog /// or exiting full screen mode. Escape, Execute, /// Open the Find dialog. (`APPCOMMAND_FIND`) Find, /// Open a help dialog or toggle display of help information. (`APPCOMMAND_HELP`, /// `KEYCODE_HELP`) Help, /// Pause the current state or application (as appropriate). /// /// Note: Do not use this value for the `Pause` button on media controllers. Use `"MediaPause"` /// instead. Pause, /// Play or resume the current state or application (as appropriate). /// /// Note: Do not use this value for the `Play` button on media controllers. Use `"MediaPlay"` /// instead. Play, /// The properties (Props) key. Props, Select, /// The ZoomIn key. (`KEYCODE_ZOOM_IN`) ZoomIn, /// The ZoomOut key. (`KEYCODE_ZOOM_OUT`) ZoomOut, /// The Brightness Down key. Typically controls the display brightness. /// (`KEYCODE_BRIGHTNESS_DOWN`) BrightnessDown, /// The Brightness Up key. Typically controls the display brightness. (`KEYCODE_BRIGHTNESS_UP`) BrightnessUp, /// Toggle removable media to eject (open) and insert (close) state. (`KEYCODE_MEDIA_EJECT`) Eject, LogOff, /// Toggle power state. (`KEYCODE_POWER`) /// Note: Note: Some devices might not expose this key to the operating environment. Power, /// The `PowerOff` key. Sometime called `PowerDown`. PowerOff, /// Initiate print-screen function. PrintScreen, /// The Hibernate key. This key saves the current state of the computer to disk so that it can /// be restored. The computer will then shutdown. Hibernate, /// The Standby key. This key turns off the display and places the computer into a low-power /// mode without completely shutting down. It is sometimes labelled `Suspend` or `Sleep` key. /// (`KEYCODE_SLEEP`) Standby, /// The WakeUp key. (`KEYCODE_WAKEUP`) WakeUp, /// Initiate the multi-candidate mode. AllCandidates, Alphanumeric, /// Initiate the Code Input mode to allow characters to be entered by /// their code points. CodeInput, /// The Compose key, also known as "Multi_key" on the X Window System. This key acts in a /// manner similar to a dead key, triggering a mode where subsequent key presses are combined /// to produce a different character. Compose, /// Convert the current input method sequence. Convert, /// The Final Mode `Final` key used on some Asian keyboards, to enable the final mode for IMEs. FinalMode, /// Switch to the first character group. (ISO/IEC 9995) GroupFirst, /// Switch to the last character group. (ISO/IEC 9995) GroupLast, /// Switch to the next character group. (ISO/IEC 9995) GroupNext, /// Switch to the previous character group. (ISO/IEC 9995) GroupPrevious, /// Toggle between or cycle through input modes of IMEs. ModeChange, NextCandidate, /// Accept current input method sequence without /// conversion in IMEs. NonConvert, PreviousCandidate, Process, SingleCandidate, /// Toggle between Hangul and English modes. HangulMode, HanjaMode, JunjaMode, /// The Eisu key. This key may close the IME, but its purpose is defined by the current IME. /// (`KEYCODE_EISU`) Eisu, /// The (Half-Width) Characters key. Hankaku, /// The Hiragana (Japanese Kana characters) key. Hiragana, /// The Hiragana/Katakana toggle key. (`KEYCODE_KATAKANA_HIRAGANA`) HiraganaKatakana, /// The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from /// romaji mode). KanaMode, /// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key /// is typically used to switch to a hiragana keyboard for the purpose of converting input /// into kanji. (`KEYCODE_KANA`) KanjiMode, /// The Katakana (Japanese Kana characters) key. Katakana, /// The Roman characters function key. Romaji, /// The Zenkaku (Full-Width) Characters key. Zenkaku, /// The Zenkaku/Hankaku (full-width/half-width) toggle key. (`KEYCODE_ZENKAKU_HANKAKU`) ZenkakuHankaku, /// General purpose virtual function key, as index 1. Soft1, /// General purpose virtual function key, as index 2. Soft2, /// General purpose virtual function key, as index 3. Soft3, /// General purpose virtual function key, as index 4. Soft4, /// Select next (numerically or logically) lower channel. (`APPCOMMAND_MEDIA_CHANNEL_DOWN`, /// `KEYCODE_CHANNEL_DOWN`) ChannelDown, /// Select next (numerically or logically) higher channel. (`APPCOMMAND_MEDIA_CHANNEL_UP`, /// `KEYCODE_CHANNEL_UP`) ChannelUp, /// Close the current document or message (Note: This doesn’t close the application). /// (`APPCOMMAND_CLOSE`) Close, /// Open an editor to forward the current message. (`APPCOMMAND_FORWARD_MAIL`) MailForward, /// Open an editor to reply to the current message. (`APPCOMMAND_REPLY_TO_MAIL`) MailReply, /// Send the current message. (`APPCOMMAND_SEND_MAIL`) MailSend, /// Close the current media, for example to close a CD or DVD tray. (`KEYCODE_MEDIA_CLOSE`) MediaClose, /// Initiate or continue forward playback at faster than normal speed, or increase speed if /// already fast forwarding. (`APPCOMMAND_MEDIA_FAST_FORWARD`, `KEYCODE_MEDIA_FAST_FORWARD`) MediaFastForward, /// Pause the currently playing media. (`APPCOMMAND_MEDIA_PAUSE`, `KEYCODE_MEDIA_PAUSE`) /// /// Note: Media controller devices should use this value rather than `"Pause"` for their pause /// keys. MediaPause, /// Initiate or continue media playback at normal speed, if not currently playing at normal /// speed. (`APPCOMMAND_MEDIA_PLAY`, `KEYCODE_MEDIA_PLAY`) MediaPlay, /// Toggle media between play and pause states. (`APPCOMMAND_MEDIA_PLAY_PAUSE`, /// `KEYCODE_MEDIA_PLAY_PAUSE`) MediaPlayPause, /// Initiate or resume recording of currently selected media. (`APPCOMMAND_MEDIA_RECORD`, /// `KEYCODE_MEDIA_RECORD`) MediaRecord, /// Initiate or continue reverse playback at faster than normal speed, or increase speed if /// already rewinding. (`APPCOMMAND_MEDIA_REWIND`, `KEYCODE_MEDIA_REWIND`) MediaRewind, /// Stop media playing, pausing, forwarding, rewinding, or recording, if not already stopped. /// (`APPCOMMAND_MEDIA_STOP`, `KEYCODE_MEDIA_STOP`) MediaStop, /// Seek to next media or program track. (`APPCOMMAND_MEDIA_NEXTTRACK`, `KEYCODE_MEDIA_NEXT`) MediaTrackNext, /// Seek to previous media or program track. (`APPCOMMAND_MEDIA_PREVIOUSTRACK`, /// `KEYCODE_MEDIA_PREVIOUS`) MediaTrackPrevious, /// Open a new document or message. (`APPCOMMAND_NEW`) New, /// Open an existing document or message. (`APPCOMMAND_OPEN`) Open, /// Print the current document or message. (`APPCOMMAND_PRINT`) Print, /// Save the current document or message. (`APPCOMMAND_SAVE`) Save, /// Spellcheck the current document or selection. (`APPCOMMAND_SPELL_CHECK`) SpellCheck, /// The `11` key found on media numpads that /// have buttons from `1` ... `12`. Key11, /// The `12` key found on media numpads that /// have buttons from `1` ... `12`. Key12, /// Adjust audio balance leftward. (`VK_AUDIO_BALANCE_LEFT`) AudioBalanceLeft, /// Adjust audio balance rightward. (`VK_AUDIO_BALANCE_RIGHT`) AudioBalanceRight, /// Decrease audio bass boost or cycle down through bass boost states. (`APPCOMMAND_BASS_DOWN`, /// `VK_BASS_BOOST_DOWN`) AudioBassBoostDown, /// Toggle bass boost on/off. (`APPCOMMAND_BASS_BOOST`) AudioBassBoostToggle, /// Increase audio bass boost or cycle up through bass boost states. (`APPCOMMAND_BASS_UP`, /// `VK_BASS_BOOST_UP`) AudioBassBoostUp, /// Adjust audio fader towards front. (`VK_FADER_FRONT`) AudioFaderFront, /// Adjust audio fader towards rear. (`VK_FADER_REAR`) AudioFaderRear, /// Advance surround audio mode to next available mode. (`VK_SURROUND_MODE_NEXT`) AudioSurroundModeNext, /// Decrease treble. (`APPCOMMAND_TREBLE_DOWN`) AudioTrebleDown, /// Increase treble. (`APPCOMMAND_TREBLE_UP`) AudioTrebleUp, /// Decrease audio volume. (`APPCOMMAND_VOLUME_DOWN`, `KEYCODE_VOLUME_DOWN`) AudioVolumeDown, /// Increase audio volume. (`APPCOMMAND_VOLUME_UP`, `KEYCODE_VOLUME_UP`) AudioVolumeUp, /// Toggle between muted state and prior volume level. (`APPCOMMAND_VOLUME_MUTE`, /// `KEYCODE_VOLUME_MUTE`) AudioVolumeMute, /// Toggle the microphone on/off. (`APPCOMMAND_MIC_ON_OFF_TOGGLE`) MicrophoneToggle, /// Decrease microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_DOWN`) MicrophoneVolumeDown, /// Increase microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_UP`) MicrophoneVolumeUp, /// Mute the microphone. (`APPCOMMAND_MICROPHONE_VOLUME_MUTE`, `KEYCODE_MUTE`) MicrophoneVolumeMute, /// Show correction list when a word is incorrectly identified. (`APPCOMMAND_CORRECTION_LIST`) SpeechCorrectionList, /// Toggle between dictation mode and command/control mode. /// (`APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE`) SpeechInputToggle, /// The first generic "LaunchApplication" key. This is commonly associated with launching "My /// Computer", and may have a computer symbol on the key. (`APPCOMMAND_LAUNCH_APP1`) LaunchApplication1, /// The second generic "LaunchApplication" key. This is commonly associated with launching /// "Calculator", and may have a calculator symbol on the key. (`APPCOMMAND_LAUNCH_APP2`, /// `KEYCODE_CALCULATOR`) LaunchApplication2, /// The "Calendar" key. (`KEYCODE_CALENDAR`) LaunchCalendar, /// The "Contacts" key. (`KEYCODE_CONTACTS`) LaunchContacts, /// The "Mail" key. (`APPCOMMAND_LAUNCH_MAIL`) LaunchMail, /// The "Media Player" key. (`APPCOMMAND_LAUNCH_MEDIA_SELECT`) LaunchMediaPlayer, LaunchMusicPlayer, LaunchPhone, LaunchScreenSaver, LaunchSpreadsheet, LaunchWebBrowser, LaunchWebCam, LaunchWordProcessor, /// Navigate to previous content or page in current history. (`APPCOMMAND_BROWSER_BACKWARD`) BrowserBack, /// Open the list of browser favorites. (`APPCOMMAND_BROWSER_FAVORITES`) BrowserFavorites, /// Navigate to next content or page in current history. (`APPCOMMAND_BROWSER_FORWARD`) BrowserForward, /// Go to the user’s preferred home page. (`APPCOMMAND_BROWSER_HOME`) BrowserHome, /// Refresh the current page or content. (`APPCOMMAND_BROWSER_REFRESH`) BrowserRefresh, /// Call up the user’s preferred search page. (`APPCOMMAND_BROWSER_SEARCH`) BrowserSearch, /// Stop loading the current page or content. (`APPCOMMAND_BROWSER_STOP`) BrowserStop, /// The Application switch key, which provides a list of recent apps to switch between. /// (`KEYCODE_APP_SWITCH`) AppSwitch, /// The Call key. (`KEYCODE_CALL`) Call, /// The Camera key. (`KEYCODE_CAMERA`) Camera, /// The Camera focus key. (`KEYCODE_FOCUS`) CameraFocus, /// The End Call key. (`KEYCODE_ENDCALL`) EndCall, /// The Back key. (`KEYCODE_BACK`) GoBack, /// The Home key, which goes to the phone’s main screen. (`KEYCODE_HOME`) GoHome, /// The Headset Hook key. (`KEYCODE_HEADSETHOOK`) HeadsetHook, LastNumberRedial, /// The Notification key. (`KEYCODE_NOTIFICATION`) Notification, /// Toggle between manner mode state: silent, vibrate, ring, ... (`KEYCODE_MANNER_MODE`) MannerMode, VoiceDial, /// Switch to viewing TV. (`KEYCODE_TV`) TV, /// TV 3D Mode. (`KEYCODE_3D_MODE`) TV3DMode, /// Toggle between antenna and cable input. (`KEYCODE_TV_ANTENNA_CABLE`) TVAntennaCable, /// Audio description. (`KEYCODE_TV_AUDIO_DESCRIPTION`) TVAudioDescription, /// Audio description mixing volume down. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN`) TVAudioDescriptionMixDown, /// Audio description mixing volume up. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP`) TVAudioDescriptionMixUp, /// Contents menu. (`KEYCODE_TV_CONTENTS_MENU`) TVContentsMenu, /// Contents menu. (`KEYCODE_TV_DATA_SERVICE`) TVDataService, /// Switch the input mode on an external TV. (`KEYCODE_TV_INPUT`) TVInput, /// Switch to component input #1. (`KEYCODE_TV_INPUT_COMPONENT_1`) TVInputComponent1, /// Switch to component input #2. (`KEYCODE_TV_INPUT_COMPONENT_2`) TVInputComponent2, /// Switch to composite input #1. (`KEYCODE_TV_INPUT_COMPOSITE_1`) TVInputComposite1, /// Switch to composite input #2. (`KEYCODE_TV_INPUT_COMPOSITE_2`) TVInputComposite2, /// Switch to HDMI input #1. (`KEYCODE_TV_INPUT_HDMI_1`) TVInputHDMI1, /// Switch to HDMI input #2. (`KEYCODE_TV_INPUT_HDMI_2`) TVInputHDMI2, /// Switch to HDMI input #3. (`KEYCODE_TV_INPUT_HDMI_3`) TVInputHDMI3, /// Switch to HDMI input #4. (`KEYCODE_TV_INPUT_HDMI_4`) TVInputHDMI4, /// Switch to VGA input #1. (`KEYCODE_TV_INPUT_VGA_1`) TVInputVGA1, /// Media context menu. (`KEYCODE_TV_MEDIA_CONTEXT_MENU`) TVMediaContext, /// Toggle network. (`KEYCODE_TV_NETWORK`) TVNetwork, /// Number entry. (`KEYCODE_TV_NUMBER_ENTRY`) TVNumberEntry, /// Toggle the power on an external TV. (`KEYCODE_TV_POWER`) TVPower, /// Radio. (`KEYCODE_TV_RADIO_SERVICE`) TVRadioService, /// Satellite. (`KEYCODE_TV_SATELLITE`) TVSatellite, /// Broadcast Satellite. (`KEYCODE_TV_SATELLITE_BS`) TVSatelliteBS, /// Communication Satellite. (`KEYCODE_TV_SATELLITE_CS`) TVSatelliteCS, /// Toggle between available satellites. (`KEYCODE_TV_SATELLITE_SERVICE`) TVSatelliteToggle, /// Analog Terrestrial. (`KEYCODE_TV_TERRESTRIAL_ANALOG`) TVTerrestrialAnalog, /// Digital Terrestrial. (`KEYCODE_TV_TERRESTRIAL_DIGITAL`) TVTerrestrialDigital, /// Timer programming. (`KEYCODE_TV_TIMER_PROGRAMMING`) TVTimer, /// Switch the input mode on an external AVR (audio/video receiver). (`KEYCODE_AVR_INPUT`) AVRInput, /// Toggle the power on an external AVR (audio/video receiver). (`KEYCODE_AVR_POWER`) AVRPower, /// General purpose color-coded media function key, as index 0 (red). (`VK_COLORED_KEY_0`, /// `KEYCODE_PROG_RED`) ColorF0Red, /// General purpose color-coded media function key, as index 1 (green). (`VK_COLORED_KEY_1`, /// `KEYCODE_PROG_GREEN`) ColorF1Green, /// General purpose color-coded media function key, as index 2 (yellow). (`VK_COLORED_KEY_2`, /// `KEYCODE_PROG_YELLOW`) ColorF2Yellow, /// General purpose color-coded media function key, as index 3 (blue). (`VK_COLORED_KEY_3`, /// `KEYCODE_PROG_BLUE`) ColorF3Blue, /// General purpose color-coded media function key, as index 4 (grey). (`VK_COLORED_KEY_4`) ColorF4Grey, /// General purpose color-coded media function key, as index 5 (brown). (`VK_COLORED_KEY_5`) ColorF5Brown, /// Toggle the display of Closed Captions. (`VK_CC`, `KEYCODE_CAPTIONS`) ClosedCaptionToggle, /// Adjust brightness of device, by toggling between or cycling through states. (`VK_DIMMER`) Dimmer, /// Swap video sources. (`VK_DISPLAY_SWAP`) DisplaySwap, /// Select Digital Video Rrecorder. (`KEYCODE_DVR`) DVR, /// Exit the current application. (`VK_EXIT`) Exit, /// Clear program or content stored as favorite 0. (`VK_CLEAR_FAVORITE_0`) FavoriteClear0, /// Clear program or content stored as favorite 1. (`VK_CLEAR_FAVORITE_1`) FavoriteClear1, /// Clear program or content stored as favorite 2. (`VK_CLEAR_FAVORITE_2`) FavoriteClear2, /// Clear program or content stored as favorite 3. (`VK_CLEAR_FAVORITE_3`) FavoriteClear3, /// Select (recall) program or content stored as favorite 0. (`VK_RECALL_FAVORITE_0`) FavoriteRecall0, /// Select (recall) program or content stored as favorite 1. (`VK_RECALL_FAVORITE_1`) FavoriteRecall1, /// Select (recall) program or content stored as favorite 2. (`VK_RECALL_FAVORITE_2`) FavoriteRecall2, /// Select (recall) program or content stored as favorite 3. (`VK_RECALL_FAVORITE_3`) FavoriteRecall3, /// Store current program or content as favorite 0. (`VK_STORE_FAVORITE_0`) FavoriteStore0, /// Store current program or content as favorite 1. (`VK_STORE_FAVORITE_1`) FavoriteStore1, /// Store current program or content as favorite 2. (`VK_STORE_FAVORITE_2`) FavoriteStore2, /// Store current program or content as favorite 3. (`VK_STORE_FAVORITE_3`) FavoriteStore3, /// Toggle display of program or content guide. (`VK_GUIDE`, `KEYCODE_GUIDE`) Guide, /// If guide is active and displayed, then display next day’s content. (`VK_NEXT_DAY`) GuideNextDay, /// If guide is active and displayed, then display previous day’s content. (`VK_PREV_DAY`) GuidePreviousDay, /// Toggle display of information about currently selected context or media. (`VK_INFO`, /// `KEYCODE_INFO`) Info, /// Toggle instant replay. (`VK_INSTANT_REPLAY`) InstantReplay, /// Launch linked content, if available and appropriate. (`VK_LINK`) Link, /// List the current program. (`VK_LIST`) ListProgram, /// Toggle display listing of currently available live content or programs. (`VK_LIVE`) LiveContent, /// Lock or unlock current content or program. (`VK_LOCK`) Lock, /// Show a list of media applications: audio/video players and image viewers. (`VK_APPS`) /// /// Note: Do not confuse this key value with the Windows' `VK_APPS` / `VK_CONTEXT_MENU` key, /// which is encoded as `"ContextMenu"`. MediaApps, /// Audio track key. (`KEYCODE_MEDIA_AUDIO_TRACK`) MediaAudioTrack, /// Select previously selected channel or media. (`VK_LAST`, `KEYCODE_LAST_CHANNEL`) MediaLast, /// Skip backward to next content or program. (`KEYCODE_MEDIA_SKIP_BACKWARD`) MediaSkipBackward, /// Skip forward to next content or program. (`VK_SKIP`, `KEYCODE_MEDIA_SKIP_FORWARD`) MediaSkipForward, /// Step backward to next content or program. (`KEYCODE_MEDIA_STEP_BACKWARD`) MediaStepBackward, /// Step forward to next content or program. (`KEYCODE_MEDIA_STEP_FORWARD`) MediaStepForward, /// Media top menu. (`KEYCODE_MEDIA_TOP_MENU`) MediaTopMenu, /// Navigate in. (`KEYCODE_NAVIGATE_IN`) NavigateIn, /// Navigate to next key. (`KEYCODE_NAVIGATE_NEXT`) NavigateNext, /// Navigate out. (`KEYCODE_NAVIGATE_OUT`) NavigateOut, /// Navigate to previous key. (`KEYCODE_NAVIGATE_PREVIOUS`) NavigatePrevious, /// Cycle to next favorite channel (in favorites list). (`VK_NEXT_FAVORITE_CHANNEL`) NextFavoriteChannel, /// Cycle to next user profile (if there are multiple user profiles). (`VK_USER`) NextUserProfile, /// Access on-demand content or programs. (`VK_ON_DEMAND`) OnDemand, /// Pairing key to pair devices. (`KEYCODE_PAIRING`) Pairing, /// Move picture-in-picture window down. (`VK_PINP_DOWN`) PinPDown, /// Move picture-in-picture window. (`VK_PINP_MOVE`) PinPMove, /// Toggle display of picture-in-picture window. (`VK_PINP_TOGGLE`) PinPToggle, /// Move picture-in-picture window up. (`VK_PINP_UP`) PinPUp, /// Decrease media playback speed. (`VK_PLAY_SPEED_DOWN`) PlaySpeedDown, /// Reset playback to normal speed. (`VK_PLAY_SPEED_RESET`) PlaySpeedReset, /// Increase media playback speed. (`VK_PLAY_SPEED_UP`) PlaySpeedUp, /// Toggle random media or content shuffle mode. (`VK_RANDOM_TOGGLE`) RandomToggle, /// Not a physical key, but this key code is sent when the remote control battery is low. /// (`VK_RC_LOW_BATTERY`) RcLowBattery, /// Toggle or cycle between media recording speeds. (`VK_RECORD_SPEED_NEXT`) RecordSpeedNext, /// Toggle RF (radio frequency) input bypass mode (pass RF input directly to the RF output). /// (`VK_RF_BYPASS`) RfBypass, /// Toggle scan channels mode. (`VK_SCAN_CHANNELS_TOGGLE`) ScanChannelsToggle, /// Advance display screen mode to next available mode. (`VK_SCREEN_MODE_NEXT`) ScreenModeNext, /// Toggle display of device settings screen. (`VK_SETTINGS`, `KEYCODE_SETTINGS`) Settings, /// Toggle split screen mode. (`VK_SPLIT_SCREEN_TOGGLE`) SplitScreenToggle, /// Switch the input mode on an external STB (set top box). (`KEYCODE_STB_INPUT`) STBInput, /// Toggle the power on an external STB (set top box). (`KEYCODE_STB_POWER`) STBPower, /// Toggle display of subtitles, if available. (`VK_SUBTITLE`) Subtitle, /// Toggle display of teletext, if available (`VK_TELETEXT`, `KEYCODE_TV_TELETEXT`). Teletext, /// Advance video mode to next available mode. (`VK_VIDEO_MODE_NEXT`) VideoModeNext, /// Cause device to identify itself in some manner, e.g., audibly or visibly. (`VK_WINK`) Wink, /// Toggle between full-screen and scaled content, or alter magnification level. (`VK_ZOOM`, /// `KEYCODE_TV_ZOOM_MODE`) ZoomToggle, /// General-purpose function key. /// Usually found at the top of the keyboard. F1, /// General-purpose function key. /// Usually found at the top of the keyboard. F2, /// General-purpose function key. /// Usually found at the top of the keyboard. F3, /// General-purpose function key. /// Usually found at the top of the keyboard. F4, /// General-purpose function key. /// Usually found at the top of the keyboard. F5, /// General-purpose function key. /// Usually found at the top of the keyboard. F6, /// General-purpose function key. /// Usually found at the top of the keyboard. F7, /// General-purpose function key. /// Usually found at the top of the keyboard. F8, /// General-purpose function key. /// Usually found at the top of the keyboard. F9, /// General-purpose function key. /// Usually found at the top of the keyboard. F10, /// General-purpose function key. /// Usually found at the top of the keyboard. F11, /// General-purpose function key. /// Usually found at the top of the keyboard. F12, /// General-purpose function key. /// Usually found at the top of the keyboard. F13, /// General-purpose function key. /// Usually found at the top of the keyboard. F14, /// General-purpose function key. /// Usually found at the top of the keyboard. F15, /// General-purpose function key. /// Usually found at the top of the keyboard. F16, /// General-purpose function key. /// Usually found at the top of the keyboard. F17, /// General-purpose function key. /// Usually found at the top of the keyboard. F18, /// General-purpose function key. /// Usually found at the top of the keyboard. F19, /// General-purpose function key. /// Usually found at the top of the keyboard. F20, /// General-purpose function key. /// Usually found at the top of the keyboard. F21, /// General-purpose function key. /// Usually found at the top of the keyboard. F22, /// General-purpose function key. /// Usually found at the top of the keyboard. F23, /// General-purpose function key. /// Usually found at the top of the keyboard. F24, /// General-purpose function key. F25, /// General-purpose function key. F26, /// General-purpose function key. F27, /// General-purpose function key. F28, /// General-purpose function key. F29, /// General-purpose function key. F30, /// General-purpose function key. F31, /// General-purpose function key. F32, /// General-purpose function key. F33, /// General-purpose function key. F34, /// General-purpose function key. F35, } /// Key represents the meaning of a keypress. /// /// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with /// additions: /// - All simple variants are wrapped under the `Named` variant /// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`. /// - The `Dead` variant here, can specify the character which is inserted when pressing the /// dead-key twice. /// /// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/ #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Key { /// A simple (unparameterised) action Named(NamedKey), /// A key string that corresponds to the character typed by the user, taking into account the /// user’s current locale setting, and any system-level keyboard mapping overrides that are in /// effect. Character(Str), /// This variant is used when the key cannot be translated to any other variant. /// /// The native key is provided (if available) in order to allow the user to specify keybindings /// for keys which are not defined by this API, mainly through some sort of UI. Unidentified(NativeKey), /// Contains the text representation of the dead-key when available. /// /// ## Platform-specific /// - **Web:** Always contains `None` Dead(Option), } impl From for Key { #[inline] fn from(action: NamedKey) -> Self { Key::Named(action) } } impl From for Key { #[inline] fn from(code: NativeKey) -> Self { Key::Unidentified(code) } } impl PartialEq for Key { #[inline] fn eq(&self, rhs: &NamedKey) -> bool { match self { Key::Named(ref a) => a == rhs, _ => false, } } } impl> PartialEq for Key { #[inline] fn eq(&self, rhs: &str) -> bool { match self { Key::Character(ref s) => s == rhs, _ => false, } } } impl> PartialEq<&str> for Key { #[inline] fn eq(&self, rhs: &&str) -> bool { self == *rhs } } impl PartialEq for Key { #[inline] fn eq(&self, rhs: &NativeKey) -> bool { match self { Key::Unidentified(ref code) => code == rhs, _ => false, } } } impl PartialEq> for NativeKey { #[inline] fn eq(&self, rhs: &Key) -> bool { rhs == self } } impl Key { /// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on /// `Key`. All other variants remain unchanged. pub fn as_ref(&self) -> Key<&str> { match self { Key::Named(a) => Key::Named(*a), Key::Character(ch) => Key::Character(ch.as_str()), Key::Dead(d) => Key::Dead(*d), Key::Unidentified(u) => Key::Unidentified(u.clone()), } } } impl NamedKey { /// Convert an action to its approximate textual equivalent. /// /// # Examples /// /// ``` /// use winit::keyboard::NamedKey; /// /// assert_eq!(NamedKey::Enter.to_text(), Some("\r")); /// assert_eq!(NamedKey::F20.to_text(), None); /// ``` pub fn to_text(&self) -> Option<&str> { match self { NamedKey::Enter => Some("\r"), NamedKey::Backspace => Some("\x08"), NamedKey::Tab => Some("\t"), NamedKey::Space => Some(" "), NamedKey::Escape => Some("\x1b"), _ => None, } } } impl Key { /// Convert a key to its approximate textual equivalent. /// /// # Examples /// /// ``` /// use winit::keyboard::{Key, NamedKey}; /// /// assert_eq!(Key::Character("a".into()).to_text(), Some("a")); /// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r")); /// assert_eq!(Key::Named(NamedKey::F20).to_text(), None); /// ``` pub fn to_text(&self) -> Option<&str> { match self { Key::Named(action) => action.to_text(), Key::Character(ch) => Some(ch.as_str()), _ => None, } } } /// The location of the key on the keyboard. /// /// Certain physical keys on the keyboard can have the same value, but are in different locations. /// For instance, the Shift key can be on the left or right side of the keyboard, or the number /// keys can be above the letters or on the numpad. This enum allows the user to differentiate /// them. /// /// See the documentation for the [`location`] field on the [`KeyEvent`] struct for more /// information. /// /// [`location`]: ../event/struct.KeyEvent.html#structfield.location /// [`KeyEvent`]: crate::event::KeyEvent #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum KeyLocation { /// The key is in its "normal" location on the keyboard. /// /// For instance, the "1" key above the "Q" key on a QWERTY keyboard will use this location. /// This invariant is also returned when the location of the key cannot be identified. /// /// ![Standard 1 key](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/keyboard_standard_1_key.svg) /// /// /// For image attribution, see the /// /// ATTRIBUTION.md /// /// file. /// Standard, /// The key is on the left side of the keyboard. /// /// For instance, the left Shift key below the Caps Lock key on a QWERTY keyboard will use this /// location. /// /// ![Left Shift key](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/keyboard_left_shift_key.svg) /// /// /// For image attribution, see the /// /// ATTRIBUTION.md /// /// file. /// Left, /// The key is on the right side of the keyboard. /// /// For instance, the right Shift key below the Enter key on a QWERTY keyboard will use this /// location. /// /// ![Right Shift key](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/keyboard_right_shift_key.svg) /// /// /// For image attribution, see the /// /// ATTRIBUTION.md /// /// file. /// Right, /// The key is on the numpad. /// /// For instance, the "1" key on the numpad will use this location. /// /// ![Numpad 1 key](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/keyboard_numpad_1_key.svg) /// /// /// For image attribution, see the /// /// ATTRIBUTION.md /// /// file. /// Numpad, } bitflags! { /// Represents the current state of the keyboard modifiers /// /// Each flag represents a modifier and is set if this modifier is active. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ModifiersState: u32 { /// The "shift" key. const SHIFT = 0b100; /// The "control" key. const CONTROL = 0b100 << 3; /// The "alt" key. const ALT = 0b100 << 6; /// This is the "windows" key on PC and "command" key on Mac. const SUPER = 0b100 << 9; } } impl ModifiersState { /// Returns `true` if the shift key is pressed. pub fn shift_key(&self) -> bool { self.intersects(Self::SHIFT) } /// Returns `true` if the control key is pressed. pub fn control_key(&self) -> bool { self.intersects(Self::CONTROL) } /// Returns `true` if the alt key is pressed. pub fn alt_key(&self) -> bool { self.intersects(Self::ALT) } /// Returns `true` if the super key is pressed. pub fn super_key(&self) -> bool { self.intersects(Self::SUPER) } } /// The state of the particular modifiers key. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum ModifiersKeyState { /// The particular key is pressed. Pressed, /// The state of the key is unknown. #[default] Unknown, } // NOTE: the exact modifier key is not used to represent modifiers state in the // first place due to a fact that modifiers state could be changed without any // key being pressed and on some platforms like Wayland/X11 which key resulted // in modifiers change is hidden, also, not that it really matters. // // The reason this API is even exposed is mostly to provide a way for users // to treat modifiers differently based on their position, which is required // on macOS due to their AltGr/Option situation. bitflags! { #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct ModifiersKeys: u8 { const LSHIFT = 0b0000_0001; const RSHIFT = 0b0000_0010; const LCONTROL = 0b0000_0100; const RCONTROL = 0b0000_1000; const LALT = 0b0001_0000; const RALT = 0b0010_0000; const LSUPER = 0b0100_0000; const RSUPER = 0b1000_0000; } } #[cfg(feature = "serde")] mod modifiers_serde { use super::ModifiersState; use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[derive(Default, Serialize, Deserialize)] #[serde(default)] #[serde(rename = "ModifiersState")] pub struct ModifiersStateSerialize { pub shift_key: bool, pub control_key: bool, pub alt_key: bool, pub super_key: bool, } impl Serialize for ModifiersState { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let s = ModifiersStateSerialize { shift_key: self.shift_key(), control_key: self.control_key(), alt_key: self.alt_key(), super_key: self.super_key(), }; s.serialize(serializer) } } impl<'de> Deserialize<'de> for ModifiersState { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let ModifiersStateSerialize { shift_key, control_key, alt_key, super_key } = ModifiersStateSerialize::deserialize(deserializer)?; let mut m = ModifiersState::empty(); m.set(ModifiersState::SHIFT, shift_key); m.set(ModifiersState::CONTROL, control_key); m.set(ModifiersState::ALT, alt_key); m.set(ModifiersState::SUPER, super_key); Ok(m) } } } winit-0.30.9/src/lib.rs000064400000000000000000000222431046102023000127550ustar 00000000000000//! Winit is a cross-platform window creation and event loop management library. //! //! # Building windows //! //! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with //! the [`EventLoop::new()`] function. //! //! ```no_run //! use winit::event_loop::EventLoop; //! //! # // Intentionally use `fn main` for clarity //! fn main() { //! let event_loop = EventLoop::new().unwrap(); //! // ... //! } //! ``` //! //! Then you create a [`Window`] with [`create_window`]. //! //! # Event handling //! //! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can //! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the //! window or a key getting pressed while the window is focused. Devices can generate //! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window. //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a //! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if //! desired. //! //! You can retrieve events by calling [`EventLoop::run_app()`]. This function will //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and //! will run until [`exit()`] is used, at which point [`Event::LoopExiting`]. //! //! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator`-based event loop //! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works //! poorly on most other platforms. However, this model can be re-implemented to an extent with #![cfg_attr( any(windows_platform, macos_platform, android_platform, x11_platform, wayland_platform), doc = "[`EventLoopExtPumpEvents::pump_app_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_app_events()]" )] #![cfg_attr( not(any(windows_platform, macos_platform, android_platform, x11_platform, wayland_platform)), doc = "`EventLoopExtPumpEvents::pump_app_events()`" )] //! [^1]. See that method's documentation for more reasons about why //! it's discouraged beyond compatibility reasons. //! //! //! ```no_run //! use winit::application::ApplicationHandler; //! use winit::event::WindowEvent; //! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; //! use winit::window::{Window, WindowId}; //! //! #[derive(Default)] //! struct App { //! window: Option, //! } //! //! impl ApplicationHandler for App { //! fn resumed(&mut self, event_loop: &ActiveEventLoop) { //! self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap()); //! } //! //! fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { //! match event { //! WindowEvent::CloseRequested => { //! println!("The close button was pressed; stopping"); //! event_loop.exit(); //! }, //! WindowEvent::RedrawRequested => { //! // Redraw the application. //! // //! // It's preferable for applications that do not render continuously to render in //! // this event rather than in AboutToWait, since rendering in here allows //! // the program to gracefully handle redraws requested by the OS. //! //! // Draw. //! //! // Queue a RedrawRequested event. //! // //! // You only need to call this if you've determined that you need to redraw in //! // applications which do not always need to. Applications that redraw continuously //! // can render here instead. //! self.window.as_ref().unwrap().request_redraw(); //! } //! _ => (), //! } //! } //! } //! //! # // Intentionally use `fn main` for clarity //! fn main() { //! let event_loop = EventLoop::new().unwrap(); //! //! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't //! // dispatched any events. This is ideal for games and similar applications. //! event_loop.set_control_flow(ControlFlow::Poll); //! //! // ControlFlow::Wait pauses the event loop if no events are available to process. //! // This is ideal for non-game applications that only update in response to user //! // input, and uses significantly less power/CPU time than ControlFlow::Poll. //! event_loop.set_control_flow(ControlFlow::Wait); //! //! let mut app = App::default(); //! event_loop.run_app(&mut app); //! } //! ``` //! //! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be //! compared to the value returned by [`Window::id()`] to determine which [`Window`] //! dispatched the event. //! //! # Drawing on the window //! //! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you //! to retrieve the raw handle of the window and display (see the [`platform`] module and/or the //! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows //! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. //! //! Note that many platforms will display garbage data in the window's client area if the //! application doesn't render anything to the window by the time the desktop compositor is ready to //! display the window to the user. If you notice this happening, you should create the window with //! [`visible` set to `false`][crate::window::WindowAttributes::with_visible] and explicitly make //! the window visible only once you're ready to render into it. //! //! # UI scaling //! //! UI scaling is important, go read the docs for the [`dpi`] crate for an //! introduction. //! //! All of Winit's functions return physical types, but can take either logical or physical //! coordinates as input, allowing you to use the most convenient coordinate system for your //! particular application. //! //! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed. //! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI //! monitor or if the user changes their DPI settings. This allows you to rescale your application's //! UI elements and adjust how the platform changes the window's size to reflect the new scale //! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor //! can be found by calling [`window.scale_factor()`]. //! //! [`ScaleFactorChanged`]: event::WindowEvent::ScaleFactorChanged //! [`window.scale_factor()`]: window::Window::scale_factor //! //! # Cargo Features //! //! Winit provides the following Cargo features: //! //! * `x11` (enabled by default): On Unix platforms, enables the X11 backend. //! * `wayland` (enabled by default): On Unix platforms, enables the Wayland backend. //! * `rwh_04`: Implement `raw-window-handle v0.4` traits. //! * `rwh_05`: Implement `raw-window-handle v0.5` traits. //! * `rwh_06`: Implement `raw-window-handle v0.6` traits. //! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde). //! * `mint`: Enables mint (math interoperability standard types) conversions. //! //! See the [`platform`] module for documentation on platform-specific cargo //! features. //! //! [`EventLoop`]: event_loop::EventLoop //! [`EventLoop::new()`]: event_loop::EventLoop::new //! [`EventLoop::run_app()`]: event_loop::EventLoop::run_app //! [`exit()`]: event_loop::ActiveEventLoop::exit //! [`Window`]: window::Window //! [`WindowId`]: window::WindowId //! [`WindowAttributes`]: window::WindowAttributes //! [window_new]: window::Window::new //! [`create_window`]: event_loop::ActiveEventLoop::create_window //! [`Window::id()`]: window::Window::id //! [`WindowEvent`]: event::WindowEvent //! [`DeviceEvent`]: event::DeviceEvent //! [`Event::UserEvent`]: event::Event::UserEvent //! [`Event::LoopExiting`]: event::Event::LoopExiting //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle //! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland. #![deny(rust_2018_idioms)] #![deny(rustdoc::broken_intra_doc_links)] #![deny(clippy::all)] #![deny(unsafe_op_in_unsafe_fn)] #![cfg_attr(clippy, deny(warnings))] // Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly // doc #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))] #![allow(clippy::missing_safety_doc)] #[cfg(feature = "rwh_04")] pub use rwh_04 as raw_window_handle_04; #[cfg(feature = "rwh_05")] pub use rwh_05 as raw_window_handle_05; #[cfg(feature = "rwh_06")] pub use rwh_06 as raw_window_handle; // Re-export DPI types so that users don't have to put it in Cargo.toml. #[doc(inline)] pub use dpi; pub mod application; #[cfg(any(doc, doctest, test))] pub mod changelog; #[macro_use] pub mod error; mod cursor; pub mod event; pub mod event_loop; mod icon; pub mod keyboard; pub mod monitor; mod platform_impl; mod utils; pub mod window; pub mod platform; winit-0.30.9/src/monitor.rs000064400000000000000000000125241046102023000136770ustar 00000000000000//! Types useful for interacting with a user's monitors. //! //! If you want to get basic information about a monitor, you can use the //! [`MonitorHandle`] type. This is retrieved from one of the following //! methods, which return an iterator of [`MonitorHandle`]: //! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors]. //! - [`Window::available_monitors`][crate::window::Window::available_monitors]. use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::platform_impl; /// Deprecated! Use `VideoModeHandle` instead. #[deprecated = "Renamed to `VideoModeHandle`"] pub type VideoMode = VideoModeHandle; /// Describes a fullscreen video mode of a monitor. /// /// Can be acquired with [`MonitorHandle::video_modes`]. #[derive(Clone, PartialEq, Eq, Hash)] pub struct VideoModeHandle { pub(crate) video_mode: platform_impl::VideoModeHandle, } impl std::fmt::Debug for VideoModeHandle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.video_mode.fmt(f) } } impl PartialOrd for VideoModeHandle { fn partial_cmp(&self, other: &VideoModeHandle) -> Option { Some(self.cmp(other)) } } impl Ord for VideoModeHandle { fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering { self.monitor().cmp(&other.monitor()).then( self.size() .cmp(&other.size()) .then( self.refresh_rate_millihertz() .cmp(&other.refresh_rate_millihertz()) .then(self.bit_depth().cmp(&other.bit_depth())), ) .reverse(), ) } } impl VideoModeHandle { /// Returns the resolution of this video mode. #[inline] pub fn size(&self) -> PhysicalSize { self.video_mode.size() } /// Returns the bit depth of this video mode, as in how many bits you have /// available per color. This is generally 24 bits or 32 bits on modern /// systems, depending on whether the alpha channel is counted or not. /// /// ## Platform-specific /// /// - **Wayland / Orbital:** Always returns 32. /// - **iOS:** Always returns 32. #[inline] pub fn bit_depth(&self) -> u16 { self.video_mode.bit_depth() } /// Returns the refresh rate of this video mode in mHz. #[inline] pub fn refresh_rate_millihertz(&self) -> u32 { self.video_mode.refresh_rate_millihertz() } /// Returns the monitor that this video mode is valid for. Each monitor has /// a separate set of valid video modes. #[inline] pub fn monitor(&self) -> MonitorHandle { MonitorHandle { inner: self.video_mode.monitor() } } } impl std::fmt::Display for VideoModeHandle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}x{} @ {} mHz ({} bpp)", self.size().width, self.size().height, self.refresh_rate_millihertz(), self.bit_depth() ) } } /// Handle to a monitor. /// /// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. /// /// [`Window`]: crate::window::Window #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct MonitorHandle { pub(crate) inner: platform_impl::MonitorHandle, } impl MonitorHandle { /// Returns a human-readable name of the monitor. /// /// Returns `None` if the monitor doesn't exist anymore. #[inline] pub fn name(&self) -> Option { self.inner.name() } /// Returns the monitor's resolution. #[inline] pub fn size(&self) -> PhysicalSize { self.inner.size() } /// Returns the top-left corner position of the monitor relative to the larger full /// screen area. #[inline] pub fn position(&self) -> PhysicalPosition { self.inner.position() } /// The monitor refresh rate used by the system. /// /// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor /// the window is on is removed. /// /// When using exclusive fullscreen, the refresh rate of the [`VideoModeHandle`] that was /// used to enter fullscreen should be used instead. #[inline] pub fn refresh_rate_millihertz(&self) -> Option { self.inner.refresh_rate_millihertz() } /// Returns the scale factor of the underlying monitor. To map logical pixels to physical /// pixels and vice versa, use [`Window::scale_factor`]. /// /// See the [`dpi`] module for more information. /// /// ## Platform-specific /// /// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable. /// - **Wayland:** May differ from [`Window::scale_factor`]. /// - **Android:** Always returns 1.0. /// /// [`Window::scale_factor`]: crate::window::Window::scale_factor #[inline] pub fn scale_factor(&self) -> f64 { self.inner.scale_factor() } /// Returns all fullscreen video modes supported by this monitor. /// /// ## Platform-specific /// /// - **Web:** Always returns an empty iterator #[inline] pub fn video_modes(&self) -> impl Iterator { self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode }) } } winit-0.30.9/src/platform/android.rs000064400000000000000000000177251046102023000154640ustar 00000000000000//! # Android //! //! The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/) crate. //! //! Native Android applications need some form of "glue" crate that is responsible //! for defining the main entry point for your Rust application as well as tracking //! various life-cycle events and synchronizing with the main JVM thread. //! //! Winit uses the [android-activity](https://docs.rs/android-activity/) as a //! glue crate (prior to `0.28` it used //! [ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)). //! //! The version of the glue crate that your application depends on _must_ match the //! version that Winit depends on because the glue crate is responsible for your //! application's main entry point. If Cargo resolves multiple versions, they will //! clash. //! //! `winit` glue compatibility table: //! //! | winit | ndk-glue | //! | :---: | :--------------------------: | //! | 0.30 | `android-activity = "0.6"` | //! | 0.29 | `android-activity = "0.5"` | //! | 0.28 | `android-activity = "0.4"` | //! | 0.27 | `ndk-glue = "0.7"` | //! | 0.26 | `ndk-glue = "0.5"` | //! | 0.25 | `ndk-glue = "0.3"` | //! | 0.24 | `ndk-glue = "0.2"` | //! //! The recommended way to avoid a conflict with the glue version is to avoid explicitly //! depending on the `android-activity` crate, and instead consume the API that //! is re-exported by Winit under `winit::platform::android::activity::*` //! //! Running on an Android device needs a dynamic system library. Add this to Cargo.toml: //! //! ```toml //! [lib] //! name = "main" //! crate-type = ["cdylib"] //! ``` //! //! All Android applications are based on an `Activity` subclass, and the //! `android-activity` crate is designed to support different choices for this base //! class. Your application _must_ specify the base class it needs via a feature flag: //! //! | Base Class | Feature Flag | Notes | //! | :--------------: | :---------------: | :-----: | //! | `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.| //! | [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) | //! //! [`GameActivity`]: https://developer.android.com/games/agdk/game-activity //! [`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input //! [`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity //! [agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games //! [agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries //! [Gradle]: https://developer.android.com/studio/build //! //! For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples). //! //! ## Converting from `ndk-glue` to `android-activity` //! //! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building //! with `cargo apk`, then the minimal changes would be: //! 1. Remove `ndk-glue` from your `Cargo.toml` //! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.9", //! features = [ "android-native-activity" ] }` //! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc //! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize //! logging as above). //! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your //! event loop (as shown above). use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}; use crate::window::{Window, WindowAttributes}; use self::activity::{AndroidApp, ConfigurationRef, Rect}; /// Additional methods on [`EventLoop`] that are specific to Android. pub trait EventLoopExtAndroid { /// Get the [`AndroidApp`] which was used to create this event loop. fn android_app(&self) -> &AndroidApp; } impl EventLoopExtAndroid for EventLoop { fn android_app(&self) -> &AndroidApp { &self.event_loop.android_app } } /// Additional methods on [`ActiveEventLoop`] that are specific to Android. pub trait ActiveEventLoopExtAndroid { /// Get the [`AndroidApp`] which was used to create this event loop. fn android_app(&self) -> &AndroidApp; } /// Additional methods on [`Window`] that are specific to Android. pub trait WindowExtAndroid { fn content_rect(&self) -> Rect; fn config(&self) -> ConfigurationRef; } impl WindowExtAndroid for Window { fn content_rect(&self) -> Rect { self.window.content_rect() } fn config(&self) -> ConfigurationRef { self.window.config() } } impl ActiveEventLoopExtAndroid for ActiveEventLoop { fn android_app(&self) -> &AndroidApp { &self.p.app } } /// Additional methods on [`WindowAttributes`] that are specific to Android. pub trait WindowAttributesExtAndroid {} impl WindowAttributesExtAndroid for WindowAttributes {} pub trait EventLoopBuilderExtAndroid { /// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop /// /// This must be called on Android since the [`AndroidApp`] is not global state. fn with_android_app(&mut self, app: AndroidApp) -> &mut Self; /// Calling this will mark the volume keys to be manually handled by the application /// /// Default is to let the operating system handle the volume keys fn handle_volume_keys(&mut self) -> &mut Self; } impl EventLoopBuilderExtAndroid for EventLoopBuilder { fn with_android_app(&mut self, app: AndroidApp) -> &mut Self { self.platform_specific.android_app = Some(app); self } fn handle_volume_keys(&mut self) -> &mut Self { self.platform_specific.ignore_volume_keys = false; self } } /// Re-export of the `android_activity` API /// /// Winit re-exports the `android_activity` API for convenience so that most /// applications can rely on the Winit crate to resolve the required version of /// `android_activity` and avoid any chance of a conflict between Winit and the /// application crate. /// /// Unlike most libraries there can only be a single implementation /// of the `android_activity` glue crate linked with an application because /// it is responsible for the application's `android_main()` entry point. /// /// Since Winit depends on a specific version of `android_activity` the simplest /// way to avoid creating a conflict is for applications to avoid explicitly /// depending on the `android_activity` crate, and instead consume the API that /// is re-exported by Winit. /// /// For compatibility applications should then import the [`AndroidApp`] type for /// their `android_main(app: AndroidApp)` function like: /// ```rust /// #[cfg(target_os = "android")] /// use winit::platform::android::activity::AndroidApp; /// ``` pub mod activity { // We enable the `"native-activity"` feature just so that we can build the // docs, but it'll be very confusing for users to see the docs with that // feature enabled, so we avoid inlining it so that they're forced to view // it on the crate's own docs.rs page. #[doc(no_inline)] #[cfg(android_platform)] pub use android_activity::*; #[cfg(not(android_platform))] #[doc(hidden)] pub struct Rect; #[cfg(not(android_platform))] #[doc(hidden)] pub struct ConfigurationRef; #[cfg(not(android_platform))] #[doc(hidden)] pub struct AndroidApp; } winit-0.30.9/src/platform/ios.rs000064400000000000000000000402171046102023000146260ustar 00000000000000//! # iOS / UIKit //! //! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on //! iOS 9.3. //! //! iOS's main `UIApplicationMain` does some init work that's required by all //! UI-related code (see issue [#1705]). It is best to create your windows //! inside `Event::Resumed`. //! //! [#1705]: https://github.com/rust-windowing/winit/issues/1705 //! //! ## Building app //! //! To build ios app you will need rustc built for this targets: //! //! - armv7-apple-ios //! - armv7s-apple-ios //! - i386-apple-ios //! - aarch64-apple-ios //! - x86_64-apple-ios //! //! Then //! //! ``` //! cargo build --target=... //! ``` //! The simplest way to integrate your app into xcode environment is to build it //! as a static library. Wrap your main function and export it. //! //! ```rust, ignore //! #[no_mangle] //! pub extern fn start_winit_app() { //! start_inner() //! } //! //! fn start_inner() { //! ... //! } //! ``` //! //! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode. //! //! ```ignore //! void start_winit_app(); //! ``` //! //! Use start_winit_app inside your xcode's main function. //! //! //! ## App lifecycle and events //! //! iOS environment is very different from other platforms and you must be very //! careful with it's events. Familiarize yourself with //! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/). //! //! This is how those event are represented in winit: //! //! - applicationDidBecomeActive is Resumed //! - applicationWillResignActive is Suspended //! - applicationWillTerminate is LoopExiting //! //! Keep in mind that after LoopExiting event is received every attempt to draw with //! opengl will result in segfault. //! //! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed. use std::os::raw::c_void; use crate::event_loop::EventLoop; use crate::monitor::{MonitorHandle, VideoModeHandle}; use crate::window::{Window, WindowAttributes}; /// Additional methods on [`EventLoop`] that are specific to iOS. pub trait EventLoopExtIOS { /// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device. fn idiom(&self) -> Idiom; } impl EventLoopExtIOS for EventLoop { fn idiom(&self) -> Idiom { self.event_loop.idiom() } } /// Additional methods on [`Window`] that are specific to iOS. pub trait WindowExtIOS { /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. /// /// The default value is device dependent, and it's recommended GLES or Metal applications set /// this to [`MonitorHandle::scale_factor()`]. /// /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc fn set_scale_factor(&self, scale_factor: f64); /// Sets the valid orientations for the [`Window`]. /// /// The default value is [`ValidOrientations::LandscapeAndPortrait`]. /// /// This changes the value returned by /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc), /// and then calls /// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc). fn set_valid_orientations(&self, valid_orientations: ValidOrientations); /// Sets whether the [`Window`] prefers the home indicator hidden. /// /// The default is to prefer showing the home indicator. /// /// This changes the value returned by /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc), /// and then calls /// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc). /// /// This only has an effect on iOS 11.0+. fn set_prefers_home_indicator_hidden(&self, hidden: bool); /// Sets the screen edges for which the system gestures will take a lower priority than the /// application's touch handling. /// /// This changes the value returned by /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc), /// and then calls /// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc). /// /// This only has an effect on iOS 11.0+. fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge); /// Sets whether the [`Window`] prefers the status bar hidden. /// /// The default is to prefer showing the status bar. /// /// This sets the value of the /// [`prefersStatusBarHidden`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc) /// property. /// /// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc) /// is also called for you. fn set_prefers_status_bar_hidden(&self, hidden: bool); /// Sets the preferred status bar style for the [`Window`]. /// /// The default is system-defined. /// /// This sets the value of the /// [`preferredStatusBarStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc) /// property. /// /// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc) /// is also called for you. fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle); /// Sets whether the [`Window`] should recognize pinch gestures. /// /// The default is to not recognize gestures. fn recognize_pinch_gesture(&self, should_recognize: bool); /// Sets whether the [`Window`] should recognize pan gestures. /// /// The default is to not recognize gestures. /// Installs [`UIPanGestureRecognizer`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer) onto view /// /// Set the minimum number of touches required: [`minimumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-minimumnumberoftouches) /// /// Set the maximum number of touches recognized: [`maximumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-maximumnumberoftouches) fn recognize_pan_gesture( &self, should_recognize: bool, minimum_number_of_touches: u8, maximum_number_of_touches: u8, ); /// Sets whether the [`Window`] should recognize double tap gestures. /// /// The default is to not recognize gestures. fn recognize_doubletap_gesture(&self, should_recognize: bool); /// Sets whether the [`Window`] should recognize rotation gestures. /// /// The default is to not recognize gestures. fn recognize_rotation_gesture(&self, should_recognize: bool); } impl WindowExtIOS for Window { #[inline] fn set_scale_factor(&self, scale_factor: f64) { self.window.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor)) } #[inline] fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { self.window.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations)) } #[inline] fn set_prefers_home_indicator_hidden(&self, hidden: bool) { self.window.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden)) } #[inline] fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { self.window.maybe_queue_on_main(move |w| { w.set_preferred_screen_edges_deferring_system_gestures(edges) }) } #[inline] fn set_prefers_status_bar_hidden(&self, hidden: bool) { self.window.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden)) } #[inline] fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) { self.window.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style)) } #[inline] fn recognize_pinch_gesture(&self, should_recognize: bool) { self.window.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize)); } #[inline] fn recognize_pan_gesture( &self, should_recognize: bool, minimum_number_of_touches: u8, maximum_number_of_touches: u8, ) { self.window.maybe_queue_on_main(move |w| { w.recognize_pan_gesture( should_recognize, minimum_number_of_touches, maximum_number_of_touches, ) }); } #[inline] fn recognize_doubletap_gesture(&self, should_recognize: bool) { self.window.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize)); } #[inline] fn recognize_rotation_gesture(&self, should_recognize: bool) { self.window.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize)); } } /// Additional methods on [`WindowAttributes`] that are specific to iOS. pub trait WindowAttributesExtIOS { /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. /// /// The default value is device dependent, and it's recommended GLES or Metal applications set /// this to [`MonitorHandle::scale_factor()`]. /// /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc fn with_scale_factor(self, scale_factor: f64) -> Self; /// Sets the valid orientations for the [`Window`]. /// /// The default value is [`ValidOrientations::LandscapeAndPortrait`]. /// /// This sets the initial value returned by /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc). fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> Self; /// Sets whether the [`Window`] prefers the home indicator hidden. /// /// The default is to prefer showing the home indicator. /// /// This sets the initial value returned by /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc). /// /// This only has an effect on iOS 11.0+. fn with_prefers_home_indicator_hidden(self, hidden: bool) -> Self; /// Sets the screen edges for which the system gestures will take a lower priority than the /// application's touch handling. /// /// This sets the initial value returned by /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc). /// /// This only has an effect on iOS 11.0+. fn with_preferred_screen_edges_deferring_system_gestures(self, edges: ScreenEdge) -> Self; /// Sets whether the [`Window`] prefers the status bar hidden. /// /// The default is to prefer showing the status bar. /// /// This sets the initial value returned by /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc). fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self; /// Sets the style of the [`Window`]'s status bar. /// /// The default is system-defined. /// /// This sets the initial value returned by /// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc), fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self; } impl WindowAttributesExtIOS for WindowAttributes { #[inline] fn with_scale_factor(mut self, scale_factor: f64) -> Self { self.platform_specific.scale_factor = Some(scale_factor); self } #[inline] fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self { self.platform_specific.valid_orientations = valid_orientations; self } #[inline] fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self { self.platform_specific.prefers_home_indicator_hidden = hidden; self } #[inline] fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self { self.platform_specific.preferred_screen_edges_deferring_system_gestures = edges; self } #[inline] fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self { self.platform_specific.prefers_status_bar_hidden = hidden; self } #[inline] fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self { self.platform_specific.preferred_status_bar_style = status_bar_style; self } } /// Additional methods on [`MonitorHandle`] that are specific to iOS. pub trait MonitorHandleExtIOS { /// Returns a pointer to the [`UIScreen`] that is used by this monitor. /// /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc fn ui_screen(&self) -> *mut c_void; /// Returns the preferred [`VideoModeHandle`] for this monitor. /// /// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc). fn preferred_video_mode(&self) -> VideoModeHandle; } impl MonitorHandleExtIOS for MonitorHandle { #[inline] fn ui_screen(&self) -> *mut c_void { // SAFETY: The marker is only used to get the pointer of the screen let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void } #[inline] fn preferred_video_mode(&self) -> VideoModeHandle { VideoModeHandle { video_mode: self.inner.preferred_video_mode() } } } /// Valid orientations for a particular [`Window`]. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ValidOrientations { /// Excludes `PortraitUpsideDown` on iphone #[default] LandscapeAndPortrait, Landscape, /// Excludes `PortraitUpsideDown` on iphone Portrait, } /// The device [idiom]. /// /// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Idiom { Unspecified, /// iPhone and iPod touch. Phone, /// iPad. Pad, /// tvOS and Apple TV. TV, CarPlay, } bitflags::bitflags! { /// The [edges] of a screen. /// /// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ScreenEdge: u8 { const NONE = 0; const TOP = 1 << 0; const LEFT = 1 << 1; const BOTTOM = 1 << 2; const RIGHT = 1 << 3; const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits() | ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits(); } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum StatusBarStyle { #[default] Default, LightContent, DarkContent, } winit-0.30.9/src/platform/macos.rs000064400000000000000000000367671046102023000151550ustar 00000000000000//! # macOS / AppKit //! //! Winit has an OS requirement of macOS 10.11 or higher (same as Rust //! itself), and is regularly tested on macOS 10.14. //! //! A lot of functionality expects the application to be ready before you //! start doing anything; this includes creating windows, fetching monitors, //! drawing, and so on, see issues [#2238], [#2051] and [#2087]. //! //! If you encounter problems, you should try doing your initialization inside //! `Event::Resumed`. //! //! [#2238]: https://github.com/rust-windowing/winit/issues/2238 //! [#2051]: https://github.com/rust-windowing/winit/issues/2051 //! [#2087]: https://github.com/rust-windowing/winit/issues/2087 use std::os::raw::c_void; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::event_loop::{ActiveEventLoop, EventLoopBuilder}; use crate::monitor::MonitorHandle; use crate::window::{Window, WindowAttributes}; /// Additional methods on [`Window`] that are specific to MacOS. pub trait WindowExtMacOS { /// Returns whether or not the window is in simple fullscreen mode. fn simple_fullscreen(&self) -> bool; /// Toggles a fullscreen mode that doesn't require a new macOS space. /// Returns a boolean indicating whether the transition was successful (this /// won't work if the window was already in the native fullscreen). /// /// This is how fullscreen used to work on macOS in versions before Lion. /// And allows the user to have a fullscreen window without using another /// space or taking control over the entire monitor. fn set_simple_fullscreen(&self, fullscreen: bool) -> bool; /// Returns whether or not the window has shadow. fn has_shadow(&self) -> bool; /// Sets whether or not the window has shadow. fn set_has_shadow(&self, has_shadow: bool); /// Group windows together by using the same tabbing identifier. /// /// fn set_tabbing_identifier(&self, identifier: &str); /// Returns the window's tabbing identifier. fn tabbing_identifier(&self) -> String; /// Select next tab. fn select_next_tab(&self); /// Select previous tab. fn select_previous_tab(&self); /// Select the tab with the given index. /// /// Will no-op when the index is out of bounds. fn select_tab_at_index(&self, index: usize); /// Get the number of tabs in the window tab group. fn num_tabs(&self) -> usize; /// Get the window's edit state. /// /// # Examples /// /// ```ignore /// WindowEvent::CloseRequested => { /// if window.is_document_edited() { /// // Show the user a save pop-up or similar /// } else { /// // Close the window /// drop(window); /// } /// } /// ``` fn is_document_edited(&self) -> bool; /// Put the window in a state which indicates a file save is required. fn set_document_edited(&self, edited: bool); /// Set option as alt behavior as described in [`OptionAsAlt`]. /// /// This will ignore diacritical marks and accent characters from /// being processed as received characters. Instead, the input /// device's raw character will be placed in event queues with the /// Alt modifier set. fn set_option_as_alt(&self, option_as_alt: OptionAsAlt); /// Getter for the [`WindowExtMacOS::set_option_as_alt`]. fn option_as_alt(&self) -> OptionAsAlt; /// Disable the Menu Bar and Dock in Borderless Fullscreen mode. Useful for games. fn set_borderless_game(&self, borderless_game: bool); /// Getter for the [`WindowExtMacOS::set_borderless_game`]. fn is_borderless_game(&self) -> bool; } impl WindowExtMacOS for Window { #[inline] fn simple_fullscreen(&self) -> bool { self.window.maybe_wait_on_main(|w| w.simple_fullscreen()) } #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { self.window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen)) } #[inline] fn has_shadow(&self) -> bool { self.window.maybe_wait_on_main(|w| w.has_shadow()) } #[inline] fn set_has_shadow(&self, has_shadow: bool) { self.window.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow)) } #[inline] fn set_tabbing_identifier(&self, identifier: &str) { self.window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier)) } #[inline] fn tabbing_identifier(&self) -> String { self.window.maybe_wait_on_main(|w| w.tabbing_identifier()) } #[inline] fn select_next_tab(&self) { self.window.maybe_queue_on_main(|w| w.select_next_tab()) } #[inline] fn select_previous_tab(&self) { self.window.maybe_queue_on_main(|w| w.select_previous_tab()) } #[inline] fn select_tab_at_index(&self, index: usize) { self.window.maybe_queue_on_main(move |w| w.select_tab_at_index(index)) } #[inline] fn num_tabs(&self) -> usize { self.window.maybe_wait_on_main(|w| w.num_tabs()) } #[inline] fn is_document_edited(&self) -> bool { self.window.maybe_wait_on_main(|w| w.is_document_edited()) } #[inline] fn set_document_edited(&self, edited: bool) { self.window.maybe_queue_on_main(move |w| w.set_document_edited(edited)) } #[inline] fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { self.window.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt)) } #[inline] fn option_as_alt(&self) -> OptionAsAlt { self.window.maybe_wait_on_main(|w| w.option_as_alt()) } #[inline] fn set_borderless_game(&self, borderless_game: bool) { self.window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game)) } #[inline] fn is_borderless_game(&self) -> bool { self.window.maybe_wait_on_main(|w| w.is_borderless_game()) } } /// Corresponds to `NSApplicationActivationPolicy`. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] pub enum ActivationPolicy { /// Corresponds to `NSApplicationActivationPolicyRegular`. #[default] Regular, /// Corresponds to `NSApplicationActivationPolicyAccessory`. Accessory, /// Corresponds to `NSApplicationActivationPolicyProhibited`. Prohibited, } /// Additional methods on [`WindowAttributes`] that are specific to MacOS. /// /// **Note:** Properties dealing with the titlebar will be overwritten by the /// [`WindowAttributes::with_decorations`] method: /// - `with_titlebar_transparent` /// - `with_title_hidden` /// - `with_titlebar_hidden` /// - `with_titlebar_buttons_hidden` /// - `with_fullsize_content_view` pub trait WindowAttributesExtMacOS { /// Enables click-and-drag behavior for the entire window, not just the titlebar. fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self; /// Makes the titlebar transparent and allows the content to appear behind it. fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self; /// Hides the window title. fn with_title_hidden(self, title_hidden: bool) -> Self; /// Hides the window titlebar. fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self; /// Hides the window titlebar buttons. fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self; /// Makes the window content appear behind the titlebar. fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self; fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self; fn with_has_shadow(self, has_shadow: bool) -> Self; /// Window accepts click-through mouse events. fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self; /// Defines the window tabbing identifier. /// /// fn with_tabbing_identifier(self, identifier: &str) -> Self; /// Set how the Option keys are interpreted. /// /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set. fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self; /// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set. fn with_borderless_game(self, borderless_game: bool) -> Self; } impl WindowAttributesExtMacOS for WindowAttributes { #[inline] fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self { self.platform_specific.movable_by_window_background = movable_by_window_background; self } #[inline] fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self { self.platform_specific.titlebar_transparent = titlebar_transparent; self } #[inline] fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self { self.platform_specific.titlebar_hidden = titlebar_hidden; self } #[inline] fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self { self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden; self } #[inline] fn with_title_hidden(mut self, title_hidden: bool) -> Self { self.platform_specific.title_hidden = title_hidden; self } #[inline] fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self { self.platform_specific.fullsize_content_view = fullsize_content_view; self } #[inline] fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self { self.platform_specific.disallow_hidpi = disallow_hidpi; self } #[inline] fn with_has_shadow(mut self, has_shadow: bool) -> Self { self.platform_specific.has_shadow = has_shadow; self } #[inline] fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self { self.platform_specific.accepts_first_mouse = accepts_first_mouse; self } #[inline] fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self { self.platform_specific.tabbing_identifier.replace(tabbing_identifier.to_string()); self } #[inline] fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self { self.platform_specific.option_as_alt = option_as_alt; self } #[inline] fn with_borderless_game(mut self, borderless_game: bool) -> Self { self.platform_specific.borderless_game = borderless_game; self } } pub trait EventLoopBuilderExtMacOS { /// Sets the activation policy for the application. If used, this will override /// any relevant settings provided in the package manifest. /// For instance, `with_activation_policy(ActivationPolicy::Regular)` will prevent /// the application from running as an "agent", even if LSUIElement is set to true. /// /// If unused, the Winit will honor the package manifest. /// /// # Example /// /// Set the activation policy to "accessory". /// /// ``` /// use winit::event_loop::EventLoopBuilder; /// #[cfg(target_os = "macos")] /// use winit::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS}; /// /// let mut builder = EventLoopBuilder::new(); /// #[cfg(target_os = "macos")] /// builder.with_activation_policy(ActivationPolicy::Accessory); /// # if false { // We can't test this part /// let event_loop = builder.build(); /// # } /// ``` fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self; /// Used to control whether a default menubar menu is created. /// /// Menu creation is enabled by default. /// /// # Example /// /// Disable creating a default menubar. /// /// ``` /// use winit::event_loop::EventLoopBuilder; /// #[cfg(target_os = "macos")] /// use winit::platform::macos::EventLoopBuilderExtMacOS; /// /// let mut builder = EventLoopBuilder::new(); /// #[cfg(target_os = "macos")] /// builder.with_default_menu(false); /// # if false { // We can't test this part /// let event_loop = builder.build(); /// # } /// ``` fn with_default_menu(&mut self, enable: bool) -> &mut Self; /// Used to prevent the application from automatically activating when launched if /// another application is already active. /// /// The default behavior is to ignore other applications and activate when launched. fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self; } impl EventLoopBuilderExtMacOS for EventLoopBuilder { #[inline] fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self { self.platform_specific.activation_policy = Some(activation_policy); self } #[inline] fn with_default_menu(&mut self, enable: bool) -> &mut Self { self.platform_specific.default_menu = enable; self } #[inline] fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self { self.platform_specific.activate_ignoring_other_apps = ignore; self } } /// Additional methods on [`MonitorHandle`] that are specific to MacOS. pub trait MonitorHandleExtMacOS { /// Returns the identifier of the monitor for Cocoa. fn native_id(&self) -> u32; /// Returns a pointer to the NSScreen representing this monitor. fn ns_screen(&self) -> Option<*mut c_void>; } impl MonitorHandleExtMacOS for MonitorHandle { #[inline] fn native_id(&self) -> u32 { self.inner.native_identifier() } fn ns_screen(&self) -> Option<*mut c_void> { // SAFETY: We only use the marker to get a pointer let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _) } } /// Additional methods on [`ActiveEventLoop`] that are specific to macOS. pub trait ActiveEventLoopExtMacOS { /// Hide the entire application. In most applications this is typically triggered with /// Command-H. fn hide_application(&self); /// Hide the other applications. In most applications this is typically triggered with /// Command+Option-H. fn hide_other_applications(&self); /// Set whether the system can automatically organize windows into tabs. /// /// fn set_allows_automatic_window_tabbing(&self, enabled: bool); /// Returns whether the system can automatically organize windows into tabs. fn allows_automatic_window_tabbing(&self) -> bool; } impl ActiveEventLoopExtMacOS for ActiveEventLoop { fn hide_application(&self) { self.p.hide_application() } fn hide_other_applications(&self) { self.p.hide_other_applications() } fn set_allows_automatic_window_tabbing(&self, enabled: bool) { self.p.set_allows_automatic_window_tabbing(enabled); } fn allows_automatic_window_tabbing(&self) -> bool { self.p.allows_automatic_window_tabbing() } } /// Option as alt behavior. /// /// The default is `None`. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum OptionAsAlt { /// The left `Option` key is treated as `Alt`. OnlyLeft, /// The right `Option` key is treated as `Alt`. OnlyRight, /// Both `Option` keys are treated as `Alt`. Both, /// No special handling is applied for `Option` key. #[default] None, } winit-0.30.9/src/platform/mod.rs000064400000000000000000000023001046102023000146020ustar 00000000000000//! Contains traits with platform-specific methods in them. //! //! Only the modules corresponding to the platform you're compiling to will be available. #[cfg(any(android_platform, docsrs))] pub mod android; #[cfg(any(ios_platform, docsrs))] pub mod ios; #[cfg(any(macos_platform, docsrs))] pub mod macos; #[cfg(any(orbital_platform, docsrs))] pub mod orbital; #[cfg(any(x11_platform, wayland_platform, docsrs))] pub mod startup_notify; #[cfg(any(wayland_platform, docsrs))] pub mod wayland; #[cfg(any(web_platform, docsrs))] pub mod web; #[cfg(any(windows_platform, docsrs))] pub mod windows; #[cfg(any(x11_platform, docsrs))] pub mod x11; #[cfg(any( windows_platform, macos_platform, android_platform, x11_platform, wayland_platform, docsrs, ))] pub mod run_on_demand; #[cfg(any( windows_platform, macos_platform, android_platform, x11_platform, wayland_platform, docsrs, ))] pub mod pump_events; #[cfg(any( windows_platform, macos_platform, x11_platform, wayland_platform, orbital_platform, docsrs ))] pub mod modifier_supplement; #[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))] pub mod scancode; winit-0.30.9/src/platform/modifier_supplement.rs000064400000000000000000000024401046102023000201020ustar 00000000000000use crate::event::KeyEvent; use crate::keyboard::Key; /// Additional methods for the `KeyEvent` which cannot be implemented on all /// platforms. pub trait KeyEventExtModifierSupplement { /// Identical to `KeyEvent::text` but this is affected by Ctrl. /// /// For example, pressing Ctrl+a produces `Some("\x01")`. fn text_with_all_modifiers(&self) -> Option<&str>; /// This value ignores all modifiers including, /// but not limited to Shift, Caps Lock, /// and Ctrl. In most cases this means that the /// unicode character in the resulting string is lowercase. /// /// This is useful for key-bindings / shortcut key combinations. /// /// In case `logical_key` reports `Dead`, this will still report the /// key as `Character` according to the current keyboard layout. This value /// cannot be `Dead`. fn key_without_modifiers(&self) -> Key; } impl KeyEventExtModifierSupplement for KeyEvent { #[inline] fn text_with_all_modifiers(&self) -> Option<&str> { self.platform_specific.text_with_all_modifiers.as_ref().map(|s| s.as_str()) } #[inline] fn key_without_modifiers(&self) -> Key { self.platform_specific.key_without_modifiers.clone() } } winit-0.30.9/src/platform/orbital.rs000064400000000000000000000003111046102023000154570ustar 00000000000000//! # Orbital / Redox OS //! //! Redox OS has some functionality not yet present that will be implemented //! when its orbital display server provides it. // There are no Orbital specific traits yet. winit-0.30.9/src/platform/pump_events.rs000064400000000000000000000142241046102023000164000ustar 00000000000000use std::time::Duration; use crate::application::ApplicationHandler; use crate::event::Event; use crate::event_loop::{self, ActiveEventLoop, EventLoop}; /// Additional methods on [`EventLoop`] for pumping events within an external event loop pub trait EventLoopExtPumpEvents { /// A type provided by the user that can be passed through [`Event::UserEvent`]. type UserEvent: 'static; /// Pump the `EventLoop` to check for and dispatch pending events. /// /// This API is designed to enable applications to integrate Winit into an /// external event loop, for platforms that can support this. /// /// The given `timeout` limits how long it may block waiting for new events. /// /// Passing a `timeout` of `Some(Duration::ZERO)` would ensure your external /// event loop is never blocked but you would likely need to consider how /// to throttle your own external loop. /// /// Passing a `timeout` of `None` means that it may wait indefinitely for new /// events before returning control back to the external loop. /// /// **Note:** This is not a portable API, and its usage involves a number of /// caveats and trade offs that should be considered before using this API! /// /// You almost certainly shouldn't use this API, unless you absolutely know it's /// the only practical option you have. /// /// ## Synchronous events /// /// Some events _must_ only be handled synchronously via the closure that /// is passed to Winit so that the handler will also be synchronized with /// the window system and operating system. /// /// This is because some events are driven by a window system callback /// where the window systems expects the application to have handled the /// event before returning. /// /// **These events can not be buffered and handled outside of the closure /// passed to Winit.** /// /// As a general rule it is not recommended to ever buffer events to handle /// them outside of the closure passed to Winit since it's difficult to /// provide guarantees about which events are safe to buffer across all /// operating systems. /// /// Notable events that will certainly create portability problems if /// buffered and handled outside of Winit include: /// - `RedrawRequested` events, used to schedule rendering. /// /// macOS for example uses a `drawRect` callback to drive rendering /// within applications and expects rendering to be finished before /// the `drawRect` callback returns. /// /// For portability it's strongly recommended that applications should /// keep their rendering inside the closure provided to Winit. /// - Any lifecycle events, such as `Suspended` / `Resumed`. /// /// The handling of these events needs to be synchronized with the /// operating system and it would never be appropriate to buffer a /// notification that your application has been suspended or resumed and /// then handled that later since there would always be a chance that /// other lifecycle events occur while the event is buffered. /// /// ## Supported Platforms /// /// - Windows /// - Linux /// - MacOS /// - Android /// /// ## Unsupported Platforms /// /// - **Web:** This API is fundamentally incompatible with the event-based way in which Web /// browsers work because it's not possible to have a long-running external loop that would /// block the browser and there is nothing that can be polled to ask for new new events. /// Events are delivered via callbacks based on an event loop that is internal to the browser /// itself. /// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so /// there's no way to support the same approach to polling as on MacOS. /// /// ## Platform-specific /// /// - **Windows**: The implementation will use `PeekMessage` when checking for window messages /// to avoid blocking your external event loop. /// /// - **MacOS**: The implementation works in terms of stopping the global application whenever /// the application `RunLoop` indicates that it is preparing to block and wait for new events. /// /// This is very different to the polling APIs that are available on other /// platforms (the lower level polling primitives on MacOS are private /// implementation details for `NSApplication` which aren't accessible to /// application developers) /// /// It's likely this will be less efficient than polling on other OSs and /// it also means the `NSApplication` is stopped while outside of the Winit /// event loop - and that's observable (for example to crates like `rfd`) /// because the `NSApplication` is global state. /// /// If you render outside of Winit you are likely to see window resizing artifacts /// since MacOS expects applications to render synchronously during any `drawRect` /// callback. fn pump_app_events>( &mut self, timeout: Option, app: &mut A, ) -> PumpStatus { #[allow(deprecated)] self.pump_events(timeout, |event, event_loop| { event_loop::dispatch_event_for_app(app, event_loop, event) }) } /// See [`pump_app_events`]. /// /// [`pump_app_events`]: Self::pump_app_events #[deprecated = "use EventLoopExtPumpEvents::pump_app_events"] fn pump_events(&mut self, timeout: Option, event_handler: F) -> PumpStatus where F: FnMut(Event, &ActiveEventLoop); } impl EventLoopExtPumpEvents for EventLoop { type UserEvent = T; fn pump_events(&mut self, timeout: Option, event_handler: F) -> PumpStatus where F: FnMut(Event, &ActiveEventLoop), { self.event_loop.pump_events(timeout, event_handler) } } /// The return status for `pump_events` pub enum PumpStatus { /// Continue running external loop. Continue, /// Exit external loop. Exit(i32), } winit-0.30.9/src/platform/run_on_demand.rs000064400000000000000000000111041046102023000166350ustar 00000000000000use crate::application::ApplicationHandler; use crate::error::EventLoopError; use crate::event::Event; use crate::event_loop::{self, ActiveEventLoop, EventLoop}; #[cfg(doc)] use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window}; /// Additional methods on [`EventLoop`] to return control flow to the caller. pub trait EventLoopExtRunOnDemand { /// A type provided by the user that can be passed through [`Event::UserEvent`]. type UserEvent: 'static; /// See [`run_app_on_demand`]. /// /// [`run_app_on_demand`]: Self::run_app_on_demand #[deprecated = "use EventLoopExtRunOnDemand::run_app_on_demand"] fn run_on_demand(&mut self, event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &ActiveEventLoop); /// Run the application with the event loop on the calling thread. /// /// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`) /// closures and it is possible to return control back to the caller without /// consuming the `EventLoop` (by using [`exit()`]) and /// so the event loop can be re-run after it has exit. /// /// It's expected that each run of the loop will be for orthogonal instantiations of your /// Winit application, but internally each instantiation may re-use some common window /// system resources, such as a display server connection. /// /// This API is not designed to run an event loop in bursts that you can exit from and return /// to while maintaining the full state of your application. (If you need something like this /// you can look at the [`EventLoopExtPumpEvents::pump_app_events()`] API) /// /// Each time `run_app_on_demand` is called the startup sequence of `init`, followed by /// `resume` is being preserved. /// /// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// /// # Caveats /// - This extension isn't available on all platforms, since it's not always possible to return /// to the caller (specifically this is impossible on iOS and Web - though with the Web /// backend it is possible to use `EventLoopExtWebSys::spawn()` #[cfg_attr(not(web_platform), doc = "[^1]")] /// more than once instead). /// - No [`Window`] state can be carried between separate runs of the event loop. /// /// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you /// specifically need the ability to re-run a single event loop more than once /// /// # Supported Platforms /// - Windows /// - Linux /// - macOS /// - Android /// /// # Unsupported Platforms /// - **Web:** This API is fundamentally incompatible with the event-based way in which Web /// browsers work because it's not possible to have a long-running external loop that would /// block the browser and there is nothing that can be polled to ask for new events. Events /// are delivered via callbacks based on an event loop that is internal to the browser itself. /// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS. #[cfg_attr(not(web_platform), doc = "[^1]: `spawn()` is only available on `wasm` platforms.")] #[rustfmt::skip] /// /// [`exit()`]: ActiveEventLoop::exit() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() fn run_app_on_demand>( &mut self, app: &mut A, ) -> Result<(), EventLoopError> { #[allow(deprecated)] self.run_on_demand(|event, event_loop| { event_loop::dispatch_event_for_app(app, event_loop, event) }) } } impl EventLoopExtRunOnDemand for EventLoop { type UserEvent = T; fn run_on_demand(&mut self, event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &ActiveEventLoop), { self.event_loop.window_target().clear_exit(); self.event_loop.run_on_demand(event_handler) } } impl ActiveEventLoop { /// Clear exit status. pub(crate) fn clear_exit(&self) { self.p.clear_exit() } } /// ```compile_fail /// use winit::event_loop::EventLoop; /// use winit::platform::run_on_demand::EventLoopExtRunOnDemand; /// /// let mut event_loop = EventLoop::new().unwrap(); /// event_loop.run_on_demand(|_, _| { /// // Attempt to run the event loop re-entrantly; this must fail. /// event_loop.run_on_demand(|_, _| {}); /// }); /// ``` #[allow(dead_code)] fn test_run_on_demand_cannot_access_event_loop() {} winit-0.30.9/src/platform/scancode.rs000064400000000000000000000034341046102023000156130ustar 00000000000000use crate::keyboard::{KeyCode, PhysicalKey}; // TODO: Describe what this value contains for each platform /// Additional methods for the [`PhysicalKey`] type that allow the user to access the /// platform-specific scancode. /// /// [`PhysicalKey`]: crate::keyboard::PhysicalKey pub trait PhysicalKeyExtScancode { /// The raw value of the platform-specific physical key identifier. /// /// Returns `Some(key_id)` if the conversion was successful; returns `None` otherwise. /// /// ## Platform-specific /// - **Windows:** A 16bit extended scancode /// - **Wayland/X11**: A 32-bit linux scancode, which is X11/Wayland keycode subtracted by 8. fn to_scancode(self) -> Option; /// Constructs a `PhysicalKey` from a platform-specific physical key identifier. /// /// Note that this conversion may be lossy, i.e. converting the returned `PhysicalKey` back /// using `to_scancode` might not yield the original value. /// /// ## Platform-specific /// - **Wayland/X11**: A 32-bit linux scancode. When building from X11/Wayland keycode subtract /// `8` to get the value you wanted. fn from_scancode(scancode: u32) -> PhysicalKey; } impl PhysicalKeyExtScancode for PhysicalKey { fn to_scancode(self) -> Option { crate::platform_impl::physicalkey_to_scancode(self) } fn from_scancode(scancode: u32) -> PhysicalKey { crate::platform_impl::scancode_to_physicalkey(scancode) } } impl PhysicalKeyExtScancode for KeyCode { #[inline] fn to_scancode(self) -> Option { ::to_scancode(PhysicalKey::Code(self)) } #[inline] fn from_scancode(scancode: u32) -> PhysicalKey { ::from_scancode(scancode) } } winit-0.30.9/src/platform/startup_notify.rs000064400000000000000000000071311046102023000171240ustar 00000000000000//! Window startup notification to handle window raising. //! //! The [`ActivationToken`] is essential to ensure that your newly //! created window will obtain the focus, otherwise the user could //! be requered to click on the window. //! //! Such token is usually delivered via the environment variable and //! could be read from it with the [`EventLoopExtStartupNotify::read_token_from_env`]. //! //! Such token must also be reset after reading it from your environment with //! [`reset_activation_token_env`] otherwise child processes could inherit it. //! //! When starting a new child process with a newly obtained [`ActivationToken`] from //! [`WindowExtStartupNotify::request_activation_token`] the [`set_activation_token_env`] //! must be used to propagate it to the child //! //! To ensure the delivery of such token by other processes to you, the user should //! set `StartupNotify=true` inside the `.desktop` file of their application. //! //! The specification could be found [`here`]. //! //! [`here`]: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt use std::env; use crate::error::NotSupportedError; use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial}; use crate::window::{ActivationToken, Window, WindowAttributes}; /// The variable which is used mostly on X11. const X11_VAR: &str = "DESKTOP_STARTUP_ID"; /// The variable which is used mostly on Wayland. const WAYLAND_VAR: &str = "XDG_ACTIVATION_TOKEN"; pub trait EventLoopExtStartupNotify { /// Read the token from the environment. /// /// It's recommended **to unset** this environment variable for child processes. fn read_token_from_env(&self) -> Option; } pub trait WindowExtStartupNotify { /// Request a new activation token. /// /// The token will be delivered inside fn request_activation_token(&self) -> Result; } pub trait WindowAttributesExtStartupNotify { /// Use this [`ActivationToken`] during window creation. /// /// Not using such a token upon a window could make your window not gaining /// focus until the user clicks on the window. fn with_activation_token(self, token: ActivationToken) -> Self; } impl EventLoopExtStartupNotify for ActiveEventLoop { fn read_token_from_env(&self) -> Option { match self.p { #[cfg(wayland_platform)] crate::platform_impl::ActiveEventLoop::Wayland(_) => env::var(WAYLAND_VAR), #[cfg(x11_platform)] crate::platform_impl::ActiveEventLoop::X(_) => env::var(X11_VAR), } .ok() .map(ActivationToken::from_raw) } } impl WindowExtStartupNotify for Window { fn request_activation_token(&self) -> Result { self.window.request_activation_token() } } impl WindowAttributesExtStartupNotify for WindowAttributes { fn with_activation_token(mut self, token: ActivationToken) -> Self { self.platform_specific.activation_token = Some(token); self } } /// Remove the activation environment variables from the current process. /// /// This is wise to do before running child processes, /// which may not to support the activation token. pub fn reset_activation_token_env() { env::remove_var(X11_VAR); env::remove_var(WAYLAND_VAR); } /// Set environment variables responsible for activation token. /// /// This could be used before running daemon processes. pub fn set_activation_token_env(token: ActivationToken) { env::set_var(X11_VAR, &token.token); env::set_var(WAYLAND_VAR, token.token); } winit-0.30.9/src/platform/wayland.rs000064400000000000000000000071301046102023000154700ustar 00000000000000//! # Wayland //! //! **Note:** Windows don't appear on Wayland until you draw/present to them. //! //! By default, Winit loads system libraries using `dlopen`. This can be //! disabled by disabling the `"wayland-dlopen"` cargo feature. //! //! ## Client-side decorations //! //! Winit provides client-side decorations by default, but the behaviour can //! be controlled with the following feature flags: //! //! * `wayland-csd-adwaita` (default). //! * `wayland-csd-adwaita-crossfont`. //! * `wayland-csd-adwaita-notitle`. use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}; use crate::monitor::MonitorHandle; use crate::window::{Window, WindowAttributes}; pub use crate::window::Theme; /// Additional methods on [`ActiveEventLoop`] that are specific to Wayland. pub trait ActiveEventLoopExtWayland { /// True if the [`ActiveEventLoop`] uses Wayland. fn is_wayland(&self) -> bool; } impl ActiveEventLoopExtWayland for ActiveEventLoop { #[inline] fn is_wayland(&self) -> bool { self.p.is_wayland() } } /// Additional methods on [`EventLoop`] that are specific to Wayland. pub trait EventLoopExtWayland { /// True if the [`EventLoop`] uses Wayland. fn is_wayland(&self) -> bool; } impl EventLoopExtWayland for EventLoop { #[inline] fn is_wayland(&self) -> bool { self.event_loop.is_wayland() } } /// Additional methods on [`EventLoopBuilder`] that are specific to Wayland. pub trait EventLoopBuilderExtWayland { /// Force using Wayland. fn with_wayland(&mut self) -> &mut Self; /// Whether to allow the event loop to be created off of the main thread. /// /// By default, the window is only allowed to be created on the main /// thread, to make platform compatibility easier. fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; } impl EventLoopBuilderExtWayland for EventLoopBuilder { #[inline] fn with_wayland(&mut self) -> &mut Self { self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland); self } #[inline] fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { self.platform_specific.any_thread = any_thread; self } } /// Additional methods on [`Window`] that are specific to Wayland. pub trait WindowExtWayland {} impl WindowExtWayland for Window {} /// Additional methods on [`WindowAttributes`] that are specific to Wayland. pub trait WindowAttributesExtWayland { /// Build window with the given name. /// /// The `general` name sets an application ID, which should match the `.desktop` /// file distributed with your program. The `instance` is a `no-op`. /// /// For details about application ID conventions, see the /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) fn with_name(self, general: impl Into, instance: impl Into) -> Self; } impl WindowAttributesExtWayland for WindowAttributes { #[inline] fn with_name(mut self, general: impl Into, instance: impl Into) -> Self { self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(general.into(), instance.into())); self } } /// Additional methods on `MonitorHandle` that are specific to Wayland. pub trait MonitorHandleExtWayland { /// Returns the inner identifier of the monitor. fn native_id(&self) -> u32; } impl MonitorHandleExtWayland for MonitorHandle { #[inline] fn native_id(&self) -> u32 { self.inner.native_identifier() } } winit-0.30.9/src/platform/web.rs000064400000000000000000000404461046102023000146150ustar 00000000000000//! # Web //! //! The officially supported browsers are Chrome, Firefox and Safari 13.1+, //! though forks of these should work fine. //! //! Winit supports compiling to the `wasm32-unknown-unknown` target with //! `web-sys`. //! //! On the web platform, a Winit window is backed by a `` element. You //! can either [provide Winit with a `` element][with_canvas], or //! [let Winit create a `` element which you can then retrieve][get] //! and insert it into the DOM yourself. //! //! Currently, there is no example code using Winit on Web, see [#3473]. For //! information on using Rust on WebAssembly, check out the [Rust and //! WebAssembly book]. //! //! [with_canvas]: WindowAttributesExtWebSys::with_canvas //! [get]: WindowExtWebSys::canvas //! [#3473]: https://github.com/rust-windowing/winit/issues/3473 //! [Rust and WebAssembly book]: https://rustwasm.github.io/book/ //! //! ## CSS properties //! //! It is recommended **not** to apply certain CSS properties to the canvas: //! - [`transform`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform) //! - [`border`](https://developer.mozilla.org/en-US/docs/Web/CSS/border) //! - [`padding`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding) //! //! The following APIs can't take them into account and will therefore provide inaccurate results: //! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`] //! - [`WindowEvent::Occluded`] //! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`], and //! [`WindowEvent::Touch`]. //! - [`Window::set_outer_position()`] //! //! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized //! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size //! [`WindowEvent::Occluded`]: crate::event::WindowEvent::Occluded //! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved //! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered //! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft //! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch //! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position use std::error::Error; use std::fmt::{self, Display, Formatter}; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Duration; #[cfg(web_platform)] use web_sys::HtmlCanvasElement; use crate::application::ApplicationHandler; use crate::cursor::CustomCursorSource; use crate::event::Event; use crate::event_loop::{self, ActiveEventLoop, EventLoop}; #[cfg(web_platform)] use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture; use crate::platform_impl::PlatformCustomCursorSource; use crate::window::{CustomCursor, Window, WindowAttributes}; #[cfg(not(web_platform))] #[doc(hidden)] pub struct HtmlCanvasElement; pub trait WindowExtWebSys { /// Only returns the canvas if called from inside the window context (the /// main thread). fn canvas(&self) -> Option; /// Returns [`true`] if calling `event.preventDefault()` is enabled. /// /// See [`Window::set_prevent_default()`] for more details. fn prevent_default(&self) -> bool; /// Sets whether `event.preventDefault()` should be called on events on the /// canvas that have side effects. /// /// For example, by default using the mouse wheel would cause the page to scroll, enabling this /// would prevent that. /// /// Some events are impossible to prevent. E.g. Firefox allows to access the native browser /// context menu with Shift+Rightclick. fn set_prevent_default(&self, prevent_default: bool); } impl WindowExtWebSys for Window { #[inline] fn canvas(&self) -> Option { self.window.canvas() } fn prevent_default(&self) -> bool { self.window.prevent_default() } fn set_prevent_default(&self, prevent_default: bool) { self.window.set_prevent_default(prevent_default) } } pub trait WindowAttributesExtWebSys { /// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`], /// [`WindowAttributes::default()`] will create one. /// /// In any case, the canvas won't be automatically inserted into the web page. /// /// [`None`] by default. #[cfg_attr(not(web_platform), doc = "", doc = "[`HtmlCanvasElement`]: #only-available-on-wasm")] fn with_canvas(self, canvas: Option) -> Self; /// Sets whether `event.preventDefault()` should be called on events on the /// canvas that have side effects. /// /// See [`Window::set_prevent_default()`] for more details. /// /// Enabled by default. fn with_prevent_default(self, prevent_default: bool) -> Self; /// Whether the canvas should be focusable using the tab key. This is necessary to capture /// canvas keyboard events. /// /// Enabled by default. fn with_focusable(self, focusable: bool) -> Self; /// On window creation, append the canvas element to the web page if it isn't already. /// /// Disabled by default. fn with_append(self, append: bool) -> Self; } impl WindowAttributesExtWebSys for WindowAttributes { fn with_canvas(mut self, canvas: Option) -> Self { self.platform_specific.set_canvas(canvas); self } fn with_prevent_default(mut self, prevent_default: bool) -> Self { self.platform_specific.prevent_default = prevent_default; self } fn with_focusable(mut self, focusable: bool) -> Self { self.platform_specific.focusable = focusable; self } fn with_append(mut self, append: bool) -> Self { self.platform_specific.append = append; self } } /// Additional methods on `EventLoop` that are specific to the web. pub trait EventLoopExtWebSys { /// A type provided by the user that can be passed through `Event::UserEvent`. type UserEvent: 'static; /// Initializes the winit event loop. /// /// Unlike #[cfg_attr(all(web_platform, target_feature = "exception-handling"), doc = "`run_app()`")] #[cfg_attr( not(all(web_platform, target_feature = "exception-handling")), doc = "[`run_app()`]" )] /// [^1], this returns immediately, and doesn't throw an exception in order to /// satisfy its [`!`] return type. /// /// Once the event loop has been destroyed, it's possible to reinitialize another event loop /// by calling this function again. This can be useful if you want to recreate the event loop /// while the WebAssembly module is still loaded. For example, this can be used to recreate the /// event loop when switching between tabs on a single page application. #[rustfmt::skip] /// #[cfg_attr( not(all(web_platform, target_feature = "exception-handling")), doc = "[`run_app()`]: EventLoop::run_app()" )] /// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`. fn spawn_app + 'static>(self, app: A); /// See [`spawn_app`]. /// /// [`spawn_app`]: Self::spawn_app #[deprecated = "use EventLoopExtWebSys::spawn_app"] fn spawn(self, event_handler: F) where F: 'static + FnMut(Event, &ActiveEventLoop); /// Sets the strategy for [`ControlFlow::Poll`]. /// /// See [`PollStrategy`]. /// /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll fn set_poll_strategy(&self, strategy: PollStrategy); /// Gets the strategy for [`ControlFlow::Poll`]. /// /// See [`PollStrategy`]. /// /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll fn poll_strategy(&self) -> PollStrategy; /// Sets the strategy for [`ControlFlow::WaitUntil`]. /// /// See [`WaitUntilStrategy`]. /// /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy); /// Gets the strategy for [`ControlFlow::WaitUntil`]. /// /// See [`WaitUntilStrategy`]. /// /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil fn wait_until_strategy(&self) -> WaitUntilStrategy; } impl EventLoopExtWebSys for EventLoop { type UserEvent = T; fn spawn_app + 'static>(self, mut app: A) { self.event_loop.spawn(move |event, event_loop| { event_loop::dispatch_event_for_app(&mut app, event_loop, event) }); } fn spawn(self, event_handler: F) where F: 'static + FnMut(Event, &ActiveEventLoop), { self.event_loop.spawn(event_handler) } fn set_poll_strategy(&self, strategy: PollStrategy) { self.event_loop.set_poll_strategy(strategy); } fn poll_strategy(&self) -> PollStrategy { self.event_loop.poll_strategy() } fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { self.event_loop.set_wait_until_strategy(strategy); } fn wait_until_strategy(&self) -> WaitUntilStrategy { self.event_loop.wait_until_strategy() } } pub trait ActiveEventLoopExtWebSys { /// Sets the strategy for [`ControlFlow::Poll`]. /// /// See [`PollStrategy`]. /// /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll fn set_poll_strategy(&self, strategy: PollStrategy); /// Gets the strategy for [`ControlFlow::Poll`]. /// /// See [`PollStrategy`]. /// /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll fn poll_strategy(&self) -> PollStrategy; /// Sets the strategy for [`ControlFlow::WaitUntil`]. /// /// See [`WaitUntilStrategy`]. /// /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy); /// Gets the strategy for [`ControlFlow::WaitUntil`]. /// /// See [`WaitUntilStrategy`]. /// /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil fn wait_until_strategy(&self) -> WaitUntilStrategy; /// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the /// cursor has completely finished loading. fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture; } impl ActiveEventLoopExtWebSys for ActiveEventLoop { #[inline] fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture { self.p.create_custom_cursor_async(source) } #[inline] fn set_poll_strategy(&self, strategy: PollStrategy) { self.p.set_poll_strategy(strategy); } #[inline] fn poll_strategy(&self) -> PollStrategy { self.p.poll_strategy() } #[inline] fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { self.p.set_wait_until_strategy(strategy); } #[inline] fn wait_until_strategy(&self) -> WaitUntilStrategy { self.p.wait_until_strategy() } } /// Strategy used for [`ControlFlow::Poll`][crate::event_loop::ControlFlow::Poll]. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum PollStrategy { /// Uses [`Window.requestIdleCallback()`] to queue the next event loop. If not available /// this will fallback to [`setTimeout()`]. /// /// This strategy will wait for the browser to enter an idle period before running and might /// be affected by browser throttling. /// /// [`Window.requestIdleCallback()`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback /// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout IdleCallback, /// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available /// this will fallback to [`setTimeout()`]. /// /// This strategy will run as fast as possible without disturbing users from interacting with /// the page and is not affected by browser throttling. /// /// This is the default strategy. /// /// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API /// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout #[default] Scheduler, } /// Strategy used for [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil]. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum WaitUntilStrategy { /// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available /// this will fallback to [`setTimeout()`]. /// /// This strategy is commonly not affected by browser throttling unless the window is not /// focused. /// /// This is the default strategy. /// /// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API /// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout #[default] Scheduler, /// Equal to [`Scheduler`][Self::Scheduler] but wakes up the event loop from a [worker]. /// /// This strategy is commonly not affected by browser throttling regardless of window focus. /// /// [worker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API Worker, } pub trait CustomCursorExtWebSys { /// Returns if this cursor is an animation. fn is_animation(&self) -> bool; /// Creates a new cursor from a URL pointing to an image. /// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url), /// but browser support for image formats is inconsistent. Using [PNG] is recommended. /// /// [PNG]: https://en.wikipedia.org/wiki/PNG fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource; /// Crates a new animated cursor from multiple [`CustomCursor`]s. /// Supplied `cursors` can't be empty or other animations. fn from_animation( duration: Duration, cursors: Vec, ) -> Result; } impl CustomCursorExtWebSys for CustomCursor { fn is_animation(&self) -> bool { self.inner.animation } fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource { CustomCursorSource { inner: PlatformCustomCursorSource::Url { url, hotspot_x, hotspot_y } } } fn from_animation( duration: Duration, cursors: Vec, ) -> Result { if cursors.is_empty() { return Err(BadAnimation::Empty); } if cursors.iter().any(CustomCursor::is_animation) { return Err(BadAnimation::Animation); } Ok(CustomCursorSource { inner: PlatformCustomCursorSource::Animation { duration, cursors }, }) } } /// An error produced when using [`CustomCursor::from_animation`] with invalid arguments. #[derive(Debug, Clone)] pub enum BadAnimation { /// Produced when no cursors were supplied. Empty, /// Produced when a supplied cursor is an animation. Animation, } impl fmt::Display for BadAnimation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Empty => write!(f, "No cursors supplied"), Self::Animation => write!(f, "A supplied cursor is an animation"), } } } impl Error for BadAnimation {} #[cfg(not(web_platform))] struct PlatformCustomCursorFuture; #[derive(Debug)] pub struct CustomCursorFuture(pub(crate) PlatformCustomCursorFuture); impl Future for CustomCursorFuture { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor { inner: cursor }) } } #[derive(Clone, Debug)] pub enum CustomCursorError { Blob, Decode(String), Animation, } impl Display for CustomCursorError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::Blob => write!(f, "failed to create `Blob`"), Self::Decode(error) => write!(f, "failed to decode image: {error}"), Self::Animation => { write!(f, "found `CustomCursor` that is an animation when building an animation") }, } } } impl Error for CustomCursorError {} winit-0.30.9/src/platform/windows.rs000064400000000000000000000601551046102023000155310ustar 00000000000000//! # Windows //! //! The supported OS version is Windows 7 or higher, though Windows 10 is //! tested regularly. use std::borrow::Borrow; use std::ffi::c_void; use std::path::Path; use crate::dpi::PhysicalSize; use crate::event::DeviceId; use crate::event_loop::EventLoopBuilder; use crate::monitor::MonitorHandle; use crate::window::{BadIcon, Icon, Window, WindowAttributes}; /// Window Handle type used by Win32 API pub type HWND = isize; /// Menu Handle type used by Win32 API pub type HMENU = isize; /// Monitor Handle type used by Win32 API pub type HMONITOR = isize; /// Describes a system-drawn backdrop material of a window. /// /// For a detailed explanation, see [`DWM_SYSTEMBACKDROP_TYPE docs`]. /// /// [`DWM_SYSTEMBACKDROP_TYPE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] pub enum BackdropType { /// Corresponds to `DWMSBT_AUTO`. /// /// Usually draws a default backdrop effect on the title bar. #[default] Auto = 0, /// Corresponds to `DWMSBT_NONE`. None = 1, /// Corresponds to `DWMSBT_MAINWINDOW`. /// /// Draws the Mica backdrop material. MainWindow = 2, /// Corresponds to `DWMSBT_TRANSIENTWINDOW`. /// /// Draws the Background Acrylic backdrop material. TransientWindow = 3, /// Corresponds to `DWMSBT_TABBEDWINDOW`. /// /// Draws the Alt Mica backdrop material. TabbedWindow = 4, } /// Describes a color used by Windows #[repr(transparent)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Color(u32); impl Color { // Special constant only valid for the window border and therefore modeled using Option // for user facing code const NONE: Color = Color(0xfffffffe); /// Use the system's default color pub const SYSTEM_DEFAULT: Color = Color(0xffffffff); /// Create a new color from the given RGB values pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { Self((r as u32) | ((g as u32) << 8) | ((b as u32) << 16)) } } impl Default for Color { fn default() -> Self { Self::SYSTEM_DEFAULT } } /// Describes how the corners of a window should look like. /// /// For a detailed explanation, see [`DWM_WINDOW_CORNER_PREFERENCE docs`]. /// /// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference #[repr(i32)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] pub enum CornerPreference { /// Corresponds to `DWMWCP_DEFAULT`. /// /// Let the system decide when to round window corners. #[default] Default = 0, /// Corresponds to `DWMWCP_DONOTROUND`. /// /// Never round window corners. DoNotRound = 1, /// Corresponds to `DWMWCP_ROUND`. /// /// Round the corners, if appropriate. Round = 2, /// Corresponds to `DWMWCP_ROUNDSMALL`. /// /// Round the corners if appropriate, with a small radius. RoundSmall = 3, } /// A wrapper around a [`Window`] that ignores thread-specific window handle limitations. /// /// See [`WindowBorrowExtWindows::any_thread`] for more information. #[derive(Debug)] pub struct AnyThread(W); impl> AnyThread { /// Get a reference to the inner window. #[inline] pub fn get_ref(&self) -> &Window { self.0.borrow() } /// Get a reference to the inner object. #[inline] pub fn inner(&self) -> &W { &self.0 } /// Unwrap and get the inner window. #[inline] pub fn into_inner(self) -> W { self.0 } } impl> AsRef for AnyThread { fn as_ref(&self) -> &Window { self.get_ref() } } impl> Borrow for AnyThread { fn borrow(&self) -> &Window { self.get_ref() } } impl> std::ops::Deref for AnyThread { type Target = Window; fn deref(&self) -> &Self::Target { self.get_ref() } } #[cfg(feature = "rwh_06")] impl> rwh_06::HasWindowHandle for AnyThread { fn window_handle(&self) -> Result, rwh_06::HandleError> { // SAFETY: The top level user has asserted this is only used safely. unsafe { self.get_ref().window_handle_any_thread() } } } /// Additional methods on `EventLoop` that are specific to Windows. pub trait EventLoopBuilderExtWindows { /// Whether to allow the event loop to be created off of the main thread. /// /// By default, the window is only allowed to be created on the main /// thread, to make platform compatibility easier. /// /// # `Window` caveats /// /// Note that any `Window` created on the new thread will be destroyed when the thread /// terminates. Attempting to use a `Window` after its parent thread terminates has /// unspecified, although explicitly not undefined, behavior. fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; /// Whether to enable process-wide DPI awareness. /// /// By default, `winit` will attempt to enable process-wide DPI awareness. If /// that's undesirable, you can disable it with this function. /// /// # Example /// /// Disable process-wide DPI awareness. /// /// ``` /// use winit::event_loop::EventLoopBuilder; /// #[cfg(target_os = "windows")] /// use winit::platform::windows::EventLoopBuilderExtWindows; /// /// let mut builder = EventLoopBuilder::new(); /// #[cfg(target_os = "windows")] /// builder.with_dpi_aware(false); /// # if false { // We can't test this part /// let event_loop = builder.build(); /// # } /// ``` fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self; /// A callback to be executed before dispatching a win32 message to the window procedure. /// Return true to disable winit's internal message dispatching. /// /// # Example /// /// ``` /// # use windows_sys::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG}; /// use winit::event_loop::EventLoopBuilder; /// #[cfg(target_os = "windows")] /// use winit::platform::windows::EventLoopBuilderExtWindows; /// /// let mut builder = EventLoopBuilder::new(); /// #[cfg(target_os = "windows")] /// builder.with_msg_hook(|msg|{ /// let msg = msg as *const MSG; /// # let accels: Vec = Vec::new(); /// let translated = unsafe { /// TranslateAcceleratorW( /// (*msg).hwnd, /// CreateAcceleratorTableW(accels.as_ptr() as _, 1), /// msg, /// ) == 1 /// }; /// translated /// }); /// ``` fn with_msg_hook(&mut self, callback: F) -> &mut Self where F: FnMut(*const c_void) -> bool + 'static; } impl EventLoopBuilderExtWindows for EventLoopBuilder { #[inline] fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { self.platform_specific.any_thread = any_thread; self } #[inline] fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self { self.platform_specific.dpi_aware = dpi_aware; self } #[inline] fn with_msg_hook(&mut self, callback: F) -> &mut Self where F: FnMut(*const c_void) -> bool + 'static, { self.platform_specific.msg_hook = Some(Box::new(callback)); self } } /// Additional methods on `Window` that are specific to Windows. pub trait WindowExtWindows { /// Enables or disables mouse and keyboard input to the specified window. /// /// A window must be enabled before it can be activated. /// If an application has create a modal dialog box by disabling its owner window /// (as described in [`WindowAttributesExtWindows::with_owner_window`]), the application must /// enable the owner window before destroying the dialog box. /// Otherwise, another window will receive the keyboard focus and be activated. /// /// If a child window is disabled, it is ignored when the system tries to determine which /// window should receive mouse messages. /// /// For more information, see /// and fn set_enable(&self, enabled: bool); /// This sets `ICON_BIG`. A good ceiling here is 256x256. fn set_taskbar_icon(&self, taskbar_icon: Option); /// Whether to show or hide the window icon in the taskbar. fn set_skip_taskbar(&self, skip: bool); /// Shows or hides the background drop shadow for undecorated windows. /// /// Enabling the shadow causes a thin 1px line to appear on the top of the window. fn set_undecorated_shadow(&self, shadow: bool); /// Sets system-drawn backdrop type. /// /// Requires Windows 11 build 22523+. fn set_system_backdrop(&self, backdrop_type: BackdropType); /// Sets the color of the window border. /// /// Supported starting with Windows 11 Build 22000. fn set_border_color(&self, color: Option); /// Sets the background color of the title bar. /// /// Supported starting with Windows 11 Build 22000. fn set_title_background_color(&self, color: Option); /// Sets the color of the window title. /// /// Supported starting with Windows 11 Build 22000. fn set_title_text_color(&self, color: Color); /// Sets the preferred style of the window corners. /// /// Supported starting with Windows 11 Build 22000. fn set_corner_preference(&self, preference: CornerPreference); /// Get the raw window handle for this [`Window`] without checking for thread affinity. /// /// Window handles in Win32 have a property called "thread affinity" that ties them to their /// origin thread. Some operations can only happen on the window's origin thread, while others /// can be called from any thread. For example, [`SetWindowSubclass`] is not thread safe while /// [`GetDC`] is thread safe. /// /// In Rust terms, the window handle is `Send` sometimes but `!Send` other times. /// /// Therefore, in order to avoid confusing threading errors, [`Window`] only returns the /// window handle when the [`window_handle`] function is called from the thread that created /// the window. In other cases, it returns an [`Unavailable`] error. /// /// However in some cases you may already know that you are using the window handle for /// operations that are guaranteed to be thread-safe. In which case this function aims /// to provide an escape hatch so these functions are still accessible from other threads. /// /// # Safety /// /// It is the responsibility of the user to only pass the window handle into thread-safe /// Win32 APIs. /// /// [`SetWindowSubclass`]: https://learn.microsoft.com/en-us/windows/win32/api/commctrl/nf-commctrl-setwindowsubclass /// [`GetDC`]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdc /// [`Window`]: crate::window::Window /// [`window_handle`]: https://docs.rs/raw-window-handle/latest/raw_window_handle/trait.HasWindowHandle.html#tymethod.window_handle /// [`Unavailable`]: https://docs.rs/raw-window-handle/latest/raw_window_handle/enum.HandleError.html#variant.Unavailable /// /// ## Example /// /// ```no_run /// # use winit::window::Window; /// # fn scope(window: Window) { /// use std::thread; /// use winit::platform::windows::WindowExtWindows; /// use winit::raw_window_handle::HasWindowHandle; /// /// // We can get the window handle on the current thread. /// let handle = window.window_handle().unwrap(); /// /// // However, on another thread, we can't! /// thread::spawn(move || { /// assert!(window.window_handle().is_err()); /// /// // We can use this function as an escape hatch. /// let handle = unsafe { window.window_handle_any_thread().unwrap() }; /// }); /// # } /// ``` #[cfg(feature = "rwh_06")] unsafe fn window_handle_any_thread( &self, ) -> Result, rwh_06::HandleError>; } impl WindowExtWindows for Window { #[inline] fn set_enable(&self, enabled: bool) { self.window.set_enable(enabled) } #[inline] fn set_taskbar_icon(&self, taskbar_icon: Option) { self.window.set_taskbar_icon(taskbar_icon) } #[inline] fn set_skip_taskbar(&self, skip: bool) { self.window.set_skip_taskbar(skip) } #[inline] fn set_undecorated_shadow(&self, shadow: bool) { self.window.set_undecorated_shadow(shadow) } #[inline] fn set_system_backdrop(&self, backdrop_type: BackdropType) { self.window.set_system_backdrop(backdrop_type) } #[inline] fn set_border_color(&self, color: Option) { self.window.set_border_color(color.unwrap_or(Color::NONE)) } #[inline] fn set_title_background_color(&self, color: Option) { // The windows docs don't mention NONE as a valid options but it works in practice and is // useful to circumvent the Windows option "Show accent color on title bars and // window borders" self.window.set_title_background_color(color.unwrap_or(Color::NONE)) } #[inline] fn set_title_text_color(&self, color: Color) { self.window.set_title_text_color(color) } #[inline] fn set_corner_preference(&self, preference: CornerPreference) { self.window.set_corner_preference(preference) } #[cfg(feature = "rwh_06")] unsafe fn window_handle_any_thread( &self, ) -> Result, rwh_06::HandleError> { unsafe { let handle = self.window.rwh_06_no_thread_check()?; // SAFETY: The handle is valid in this context. Ok(rwh_06::WindowHandle::borrow_raw(handle)) } } } /// Additional methods for anything that dereference to [`Window`]. /// /// [`Window`]: crate::window::Window pub trait WindowBorrowExtWindows: Borrow + Sized { /// Create an object that allows accessing the inner window handle in a thread-unsafe way. /// /// It is possible to call [`window_handle_any_thread`] to get around Windows's thread /// affinity limitations. However, it may be desired to pass the [`Window`] into something /// that requires the [`HasWindowHandle`] trait, while ignoring thread affinity limitations. /// /// This function wraps anything that implements `Borrow` into a structure that /// uses the inner window handle as a mean of implementing [`HasWindowHandle`]. It wraps /// `Window`, `&Window`, `Arc`, and other reference types. /// /// # Safety /// /// It is the responsibility of the user to only pass the window handle into thread-safe /// Win32 APIs. /// /// [`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread /// [`Window`]: crate::window::Window /// [`HasWindowHandle`]: rwh_06::HasWindowHandle unsafe fn any_thread(self) -> AnyThread { AnyThread(self) } } impl + Sized> WindowBorrowExtWindows for W {} /// Additional methods on `WindowAttributes` that are specific to Windows. #[allow(rustdoc::broken_intra_doc_links)] pub trait WindowAttributesExtWindows { /// Set an owner to the window to be created. Can be used to create a dialog box, for example. /// This only works when [`WindowAttributes::with_parent_window`] isn't called or set to `None`. /// Can be used in combination with /// [`WindowExtWindows::set_enable(false)`][WindowExtWindows::set_enable] on the owner /// window to create a modal dialog box. /// /// From MSDN: /// - An owned window is always above its owner in the z-order. /// - The system automatically destroys an owned window when its owner is destroyed. /// - An owned window is hidden when its owner is minimized. /// /// For more information, see fn with_owner_window(self, parent: HWND) -> Self; /// Sets a menu on the window to be created. /// /// Parent and menu are mutually exclusive; a child window cannot have a menu! /// /// The menu must have been manually created beforehand with [`CreateMenu`] or similar. /// /// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how /// the menus look. If you use this, it is recommended that you combine it with /// `with_theme(Some(Theme::Light))` to avoid a jarring effect. #[cfg_attr( windows_platform, doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu" )] #[cfg_attr(not(windows_platform), doc = "[`CreateMenu`]: #only-available-on-windows")] fn with_menu(self, menu: HMENU) -> Self; /// This sets `ICON_BIG`. A good ceiling here is 256x256. fn with_taskbar_icon(self, taskbar_icon: Option) -> Self; /// This sets `WS_EX_NOREDIRECTIONBITMAP`. fn with_no_redirection_bitmap(self, flag: bool) -> Self; /// Enables or disables drag and drop support (enabled by default). Will interfere with other /// crates that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` /// instead of `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still /// attempt to initialize COM API regardless of this option. Currently only fullscreen mode /// does that, but there may be more in the future. If you need COM API with /// `COINIT_MULTITHREADED` you must initialize it before calling any winit functions. See for more information. fn with_drag_and_drop(self, flag: bool) -> Self; /// Whether show or hide the window icon in the taskbar. fn with_skip_taskbar(self, skip: bool) -> Self; /// Customize the window class name. fn with_class_name>(self, class_name: S) -> Self; /// Shows or hides the background drop shadow for undecorated windows. /// /// The shadow is hidden by default. /// Enabling the shadow causes a thin 1px line to appear on the top of the window. fn with_undecorated_shadow(self, shadow: bool) -> Self; /// Sets system-drawn backdrop type. /// /// Requires Windows 11 build 22523+. fn with_system_backdrop(self, backdrop_type: BackdropType) -> Self; /// This sets or removes `WS_CLIPCHILDREN` style. fn with_clip_children(self, flag: bool) -> Self; /// Sets the color of the window border. /// /// Supported starting with Windows 11 Build 22000. fn with_border_color(self, color: Option) -> Self; /// Sets the background color of the title bar. /// /// Supported starting with Windows 11 Build 22000. fn with_title_background_color(self, color: Option) -> Self; /// Sets the color of the window title. /// /// Supported starting with Windows 11 Build 22000. fn with_title_text_color(self, color: Color) -> Self; /// Sets the preferred style of the window corners. /// /// Supported starting with Windows 11 Build 22000. fn with_corner_preference(self, corners: CornerPreference) -> Self; } impl WindowAttributesExtWindows for WindowAttributes { #[inline] fn with_owner_window(mut self, parent: HWND) -> Self { self.platform_specific.owner = Some(parent); self } #[inline] fn with_menu(mut self, menu: HMENU) -> Self { self.platform_specific.menu = Some(menu); self } #[inline] fn with_taskbar_icon(mut self, taskbar_icon: Option) -> Self { self.platform_specific.taskbar_icon = taskbar_icon; self } #[inline] fn with_no_redirection_bitmap(mut self, flag: bool) -> Self { self.platform_specific.no_redirection_bitmap = flag; self } #[inline] fn with_drag_and_drop(mut self, flag: bool) -> Self { self.platform_specific.drag_and_drop = flag; self } #[inline] fn with_skip_taskbar(mut self, skip: bool) -> Self { self.platform_specific.skip_taskbar = skip; self } #[inline] fn with_class_name>(mut self, class_name: S) -> Self { self.platform_specific.class_name = class_name.into(); self } #[inline] fn with_undecorated_shadow(mut self, shadow: bool) -> Self { self.platform_specific.decoration_shadow = shadow; self } #[inline] fn with_system_backdrop(mut self, backdrop_type: BackdropType) -> Self { self.platform_specific.backdrop_type = backdrop_type; self } #[inline] fn with_clip_children(mut self, flag: bool) -> Self { self.platform_specific.clip_children = flag; self } #[inline] fn with_border_color(mut self, color: Option) -> Self { self.platform_specific.border_color = Some(color.unwrap_or(Color::NONE)); self } #[inline] fn with_title_background_color(mut self, color: Option) -> Self { self.platform_specific.title_background_color = Some(color.unwrap_or(Color::NONE)); self } #[inline] fn with_title_text_color(mut self, color: Color) -> Self { self.platform_specific.title_text_color = Some(color); self } #[inline] fn with_corner_preference(mut self, corners: CornerPreference) -> Self { self.platform_specific.corner_preference = Some(corners); self } } /// Additional methods on `MonitorHandle` that are specific to Windows. pub trait MonitorHandleExtWindows { /// Returns the name of the monitor adapter specific to the Win32 API. fn native_id(&self) -> String; /// Returns the handle of the monitor - `HMONITOR`. fn hmonitor(&self) -> HMONITOR; } impl MonitorHandleExtWindows for MonitorHandle { #[inline] fn native_id(&self) -> String { self.inner.native_identifier() } #[inline] fn hmonitor(&self) -> HMONITOR { self.inner.hmonitor() } } /// Additional methods on `DeviceId` that are specific to Windows. pub trait DeviceIdExtWindows { /// Returns an identifier that persistently refers to this specific device. /// /// Will return `None` if the device is no longer available. fn persistent_identifier(&self) -> Option; } impl DeviceIdExtWindows for DeviceId { #[inline] fn persistent_identifier(&self) -> Option { self.0.persistent_identifier() } } /// Additional methods on `Icon` that are specific to Windows. pub trait IconExtWindows: Sized { /// Create an icon from a file path. /// /// Specify `size` to load a specific icon size from the file, or `None` to load the default /// icon size from the file. /// /// In cases where the specified size does not exist in the file, Windows may perform scaling /// to get an icon of the desired size. fn from_path>(path: P, size: Option>) -> Result; /// Create an icon from a resource embedded in this executable or library. /// /// Specify `size` to load a specific icon size from the file, or `None` to load the default /// icon size from the file. /// /// In cases where the specified size does not exist in the file, Windows may perform scaling /// to get an icon of the desired size. fn from_resource(ordinal: u16, size: Option>) -> Result; } impl IconExtWindows for Icon { fn from_path>( path: P, size: Option>, ) -> Result { let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?; Ok(Icon { inner: win_icon }) } fn from_resource(ordinal: u16, size: Option>) -> Result { let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?; Ok(Icon { inner: win_icon }) } } winit-0.30.9/src/platform/x11.rs000064400000000000000000000222161046102023000144440ustar 00000000000000//! # X11 #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}; use crate::monitor::MonitorHandle; use crate::window::{Window, WindowAttributes}; use crate::dpi::Size; /// X window type. Maps directly to /// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html). #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum WindowType { /// A desktop feature. This can include a single window containing desktop icons with the same /// dimensions as the screen, allowing the desktop environment to have full control of the /// desktop, without the need for proxying root window clicks. Desktop, /// A dock or panel feature. Typically a Window Manager would keep such windows on top of all /// other windows. Dock, /// Toolbar windows. "Torn off" from the main application. Toolbar, /// Pinnable menu windows. "Torn off" from the main application. Menu, /// A small persistent utility window, such as a palette or toolbox. Utility, /// The window is a splash screen displayed as an application is starting up. Splash, /// This is a dialog window. Dialog, /// A dropdown menu that usually appears when the user clicks on an item in a menu bar. /// This property is typically used on override-redirect windows. DropdownMenu, /// A popup menu that usually appears when the user right clicks on an object. /// This property is typically used on override-redirect windows. PopupMenu, /// A tooltip window. Usually used to show additional information when hovering over an object /// with the cursor. This property is typically used on override-redirect windows. Tooltip, /// The window is a notification. /// This property is typically used on override-redirect windows. Notification, /// This should be used on the windows that are popped up by combo boxes. /// This property is typically used on override-redirect windows. Combo, /// This indicates the window is being dragged. /// This property is typically used on override-redirect windows. Dnd, /// This is a normal, top-level window. #[default] Normal, } /// The first argument in the provided hook will be the pointer to `XDisplay` /// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an /// indicator whether the error was handled by the callback. /// /// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent pub type XlibErrorHook = Box bool + Send + Sync>; /// A unique identifier for an X11 visual. pub type XVisualID = u32; /// A unique identifier for an X11 window. pub type XWindow = u32; /// Hook to winit's xlib error handling callback. /// /// This method is provided as a safe way to handle the errors coming from X11 /// when using xlib in external crates, like glutin for GLX access. Trying to /// handle errors by speculating with `XSetErrorHandler` is [`unsafe`]. /// /// **Be aware that your hook is always invoked and returning `true` from it will /// prevent `winit` from getting the error itself. It's wise to always return /// `false` if you're not initiated the `Sync`.** /// /// [`unsafe`]: https://www.remlab.net/op/xlib.shtml #[inline] pub fn register_xlib_error_hook(hook: XlibErrorHook) { // Append new hook. crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook); } /// Additional methods on [`ActiveEventLoop`] that are specific to X11. pub trait ActiveEventLoopExtX11 { /// True if the [`ActiveEventLoop`] uses X11. fn is_x11(&self) -> bool; } impl ActiveEventLoopExtX11 for ActiveEventLoop { #[inline] fn is_x11(&self) -> bool { !self.p.is_wayland() } } /// Additional methods on [`EventLoop`] that are specific to X11. pub trait EventLoopExtX11 { /// True if the [`EventLoop`] uses X11. fn is_x11(&self) -> bool; } impl EventLoopExtX11 for EventLoop { #[inline] fn is_x11(&self) -> bool { !self.event_loop.is_wayland() } } /// Additional methods on [`EventLoopBuilder`] that are specific to X11. pub trait EventLoopBuilderExtX11 { /// Force using X11. fn with_x11(&mut self) -> &mut Self; /// Whether to allow the event loop to be created off of the main thread. /// /// By default, the window is only allowed to be created on the main /// thread, to make platform compatibility easier. fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; } impl EventLoopBuilderExtX11 for EventLoopBuilder { #[inline] fn with_x11(&mut self) -> &mut Self { self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X); self } #[inline] fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { self.platform_specific.any_thread = any_thread; self } } /// Additional methods on [`Window`] that are specific to X11. pub trait WindowExtX11 {} impl WindowExtX11 for Window {} /// Additional methods on [`WindowAttributes`] that are specific to X11. pub trait WindowAttributesExtX11 { /// Create this window with a specific X11 visual. fn with_x11_visual(self, visual_id: XVisualID) -> Self; fn with_x11_screen(self, screen_id: i32) -> Self; /// Build window with the given `general` and `instance` names. /// /// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the /// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance", /// "general"`. /// /// For details about application ID conventions, see the /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) fn with_name(self, general: impl Into, instance: impl Into) -> Self; /// Build window with override-redirect flag; defaults to false. fn with_override_redirect(self, override_redirect: bool) -> Self; /// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. fn with_x11_window_type(self, x11_window_type: Vec) -> Self; /// Build window with base size hint. /// /// ``` /// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::window::Window; /// # use winit::platform::x11::WindowAttributesExtX11; /// // Specify the size in logical dimensions like this: /// Window::default_attributes().with_base_size(LogicalSize::new(400.0, 200.0)); /// /// // Or specify the size in physical dimensions like this: /// Window::default_attributes().with_base_size(PhysicalSize::new(400, 200)); /// ``` fn with_base_size>(self, base_size: S) -> Self; /// Embed this window into another parent window. /// /// # Example /// /// ```no_run /// use winit::window::Window; /// use winit::event_loop::ActiveEventLoop; /// use winit::platform::x11::{XWindow, WindowAttributesExtX11}; /// # fn create_window(event_loop: &ActiveEventLoop) -> Result<(), Box> { /// let parent_window_id = std::env::args().nth(1).unwrap().parse::()?; /// let window_attributes = Window::default_attributes().with_embed_parent_window(parent_window_id); /// let window = event_loop.create_window(window_attributes)?; /// # Ok(()) } /// ``` fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self; } impl WindowAttributesExtX11 for WindowAttributes { #[inline] fn with_x11_visual(mut self, visual_id: XVisualID) -> Self { self.platform_specific.x11.visual_id = Some(visual_id); self } #[inline] fn with_x11_screen(mut self, screen_id: i32) -> Self { self.platform_specific.x11.screen_id = Some(screen_id); self } #[inline] fn with_name(mut self, general: impl Into, instance: impl Into) -> Self { self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(general.into(), instance.into())); self } #[inline] fn with_override_redirect(mut self, override_redirect: bool) -> Self { self.platform_specific.x11.override_redirect = override_redirect; self } #[inline] fn with_x11_window_type(mut self, x11_window_types: Vec) -> Self { self.platform_specific.x11.x11_window_types = x11_window_types; self } #[inline] fn with_base_size>(mut self, base_size: S) -> Self { self.platform_specific.x11.base_size = Some(base_size.into()); self } #[inline] fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self { self.platform_specific.x11.embed_window = Some(parent_window_id); self } } /// Additional methods on `MonitorHandle` that are specific to X11. pub trait MonitorHandleExtX11 { /// Returns the inner identifier of the monitor. fn native_id(&self) -> u32; } impl MonitorHandleExtX11 for MonitorHandle { #[inline] fn native_id(&self) -> u32 { self.inner.native_identifier() } } winit-0.30.9/src/platform_impl/android/keycodes.rs000064400000000000000000000664761046102023000203220ustar 00000000000000use android_activity::input::{KeyAction, KeyEvent, KeyMapChar, Keycode}; use android_activity::AndroidApp; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; pub fn to_physical_key(keycode: Keycode) -> PhysicalKey { PhysicalKey::Code(match keycode { Keycode::A => KeyCode::KeyA, Keycode::B => KeyCode::KeyB, Keycode::C => KeyCode::KeyC, Keycode::D => KeyCode::KeyD, Keycode::E => KeyCode::KeyE, Keycode::F => KeyCode::KeyF, Keycode::G => KeyCode::KeyG, Keycode::H => KeyCode::KeyH, Keycode::I => KeyCode::KeyI, Keycode::J => KeyCode::KeyJ, Keycode::K => KeyCode::KeyK, Keycode::L => KeyCode::KeyL, Keycode::M => KeyCode::KeyM, Keycode::N => KeyCode::KeyN, Keycode::O => KeyCode::KeyO, Keycode::P => KeyCode::KeyP, Keycode::Q => KeyCode::KeyQ, Keycode::R => KeyCode::KeyR, Keycode::S => KeyCode::KeyS, Keycode::T => KeyCode::KeyT, Keycode::U => KeyCode::KeyU, Keycode::V => KeyCode::KeyV, Keycode::W => KeyCode::KeyW, Keycode::X => KeyCode::KeyX, Keycode::Y => KeyCode::KeyY, Keycode::Z => KeyCode::KeyZ, Keycode::Keycode0 => KeyCode::Digit0, Keycode::Keycode1 => KeyCode::Digit1, Keycode::Keycode2 => KeyCode::Digit2, Keycode::Keycode3 => KeyCode::Digit3, Keycode::Keycode4 => KeyCode::Digit4, Keycode::Keycode5 => KeyCode::Digit5, Keycode::Keycode6 => KeyCode::Digit6, Keycode::Keycode7 => KeyCode::Digit7, Keycode::Keycode8 => KeyCode::Digit8, Keycode::Keycode9 => KeyCode::Digit9, Keycode::Numpad0 => KeyCode::Numpad0, Keycode::Numpad1 => KeyCode::Numpad1, Keycode::Numpad2 => KeyCode::Numpad2, Keycode::Numpad3 => KeyCode::Numpad3, Keycode::Numpad4 => KeyCode::Numpad4, Keycode::Numpad5 => KeyCode::Numpad5, Keycode::Numpad6 => KeyCode::Numpad6, Keycode::Numpad7 => KeyCode::Numpad7, Keycode::Numpad8 => KeyCode::Numpad8, Keycode::Numpad9 => KeyCode::Numpad9, Keycode::NumpadAdd => KeyCode::NumpadAdd, Keycode::NumpadSubtract => KeyCode::NumpadSubtract, Keycode::NumpadMultiply => KeyCode::NumpadMultiply, Keycode::NumpadDivide => KeyCode::NumpadDivide, Keycode::NumpadEnter => KeyCode::NumpadEnter, Keycode::NumpadEquals => KeyCode::NumpadEqual, Keycode::NumpadComma => KeyCode::NumpadComma, Keycode::NumpadDot => KeyCode::NumpadDecimal, Keycode::NumLock => KeyCode::NumLock, Keycode::DpadLeft => KeyCode::ArrowLeft, Keycode::DpadRight => KeyCode::ArrowRight, Keycode::DpadUp => KeyCode::ArrowUp, Keycode::DpadDown => KeyCode::ArrowDown, Keycode::F1 => KeyCode::F1, Keycode::F2 => KeyCode::F2, Keycode::F3 => KeyCode::F3, Keycode::F4 => KeyCode::F4, Keycode::F5 => KeyCode::F5, Keycode::F6 => KeyCode::F6, Keycode::F7 => KeyCode::F7, Keycode::F8 => KeyCode::F8, Keycode::F9 => KeyCode::F9, Keycode::F10 => KeyCode::F10, Keycode::F11 => KeyCode::F11, Keycode::F12 => KeyCode::F12, Keycode::Space => KeyCode::Space, Keycode::Escape => KeyCode::Escape, Keycode::Enter => KeyCode::Enter, // not on the Numpad Keycode::Tab => KeyCode::Tab, Keycode::PageUp => KeyCode::PageUp, Keycode::PageDown => KeyCode::PageDown, Keycode::MoveHome => KeyCode::Home, Keycode::MoveEnd => KeyCode::End, Keycode::Insert => KeyCode::Insert, Keycode::Del => KeyCode::Backspace, // Backspace (above Enter) Keycode::ForwardDel => KeyCode::Delete, // Delete (below Insert) Keycode::Copy => KeyCode::Copy, Keycode::Paste => KeyCode::Paste, Keycode::Cut => KeyCode::Cut, Keycode::VolumeUp => KeyCode::AudioVolumeUp, Keycode::VolumeDown => KeyCode::AudioVolumeDown, Keycode::VolumeMute => KeyCode::AudioVolumeMute, // Keycode::Mute => None, // Microphone mute Keycode::MediaPlayPause => KeyCode::MediaPlayPause, Keycode::MediaStop => KeyCode::MediaStop, Keycode::MediaNext => KeyCode::MediaTrackNext, Keycode::MediaPrevious => KeyCode::MediaTrackPrevious, Keycode::Plus => KeyCode::Equal, Keycode::Minus => KeyCode::Minus, // Winit doesn't differentiate both '+' and '=', considering they are usually // on the same physical key Keycode::Equals => KeyCode::Equal, Keycode::Semicolon => KeyCode::Semicolon, Keycode::Slash => KeyCode::Slash, Keycode::Backslash => KeyCode::Backslash, Keycode::Comma => KeyCode::Comma, Keycode::Period => KeyCode::Period, Keycode::Apostrophe => KeyCode::Quote, Keycode::Grave => KeyCode::Backquote, // Winit doesn't expose a SysRq code, so map to PrintScreen since it's // usually the same physical key Keycode::Sysrq => KeyCode::PrintScreen, // These are usually the same (Pause/Break) Keycode::Break => KeyCode::Pause, // These are exactly the same Keycode::ScrollLock => KeyCode::ScrollLock, Keycode::Yen => KeyCode::IntlYen, Keycode::Kana => KeyCode::Lang1, Keycode::KatakanaHiragana => KeyCode::KanaMode, Keycode::CtrlLeft => KeyCode::ControlLeft, Keycode::CtrlRight => KeyCode::ControlRight, Keycode::ShiftLeft => KeyCode::ShiftLeft, Keycode::ShiftRight => KeyCode::ShiftRight, Keycode::AltLeft => KeyCode::AltLeft, Keycode::AltRight => KeyCode::AltRight, Keycode::MetaLeft => KeyCode::SuperLeft, Keycode::MetaRight => KeyCode::SuperRight, Keycode::LeftBracket => KeyCode::BracketLeft, Keycode::RightBracket => KeyCode::BracketRight, Keycode::Power => KeyCode::Power, Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep? Keycode::Wakeup => KeyCode::WakeUp, keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())), }) } /// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent /// /// This takes a `KeyEvent` and looks up its corresponding `KeyCharacterMap` and /// uses that to try and map the `key_code` + `meta_state` to a unicode /// character or a dead key that can be combined with the next key press. pub fn character_map_and_combine_key( app: &AndroidApp, key_event: &KeyEvent<'_>, combining_accent: &mut Option, ) -> Option { let device_id = key_event.device_id(); let key_map = match app.device_key_character_map(device_id) { Ok(key_map) => key_map, Err(err) => { tracing::warn!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}"); return None; }, }; match key_map.get(key_event.key_code(), key_event.meta_state()) { Ok(KeyMapChar::Unicode(unicode)) => { // Only do dead key combining on key down if key_event.action() == KeyAction::Down { let combined_unicode = if let Some(accent) = combining_accent { match key_map.get_dead_char(*accent, unicode) { Ok(Some(key)) => Some(key), Ok(None) => None, Err(err) => { tracing::warn!( "KeyEvent: Failed to combine 'dead key' accent '{accent}' with \ '{unicode}': {err:?}" ); None }, } } else { Some(unicode) }; *combining_accent = None; combined_unicode.map(KeyMapChar::Unicode) } else { Some(KeyMapChar::Unicode(unicode)) } }, Ok(KeyMapChar::CombiningAccent(accent)) => { if key_event.action() == KeyAction::Down { *combining_accent = Some(accent); } Some(KeyMapChar::CombiningAccent(accent)) }, Ok(KeyMapChar::None) => { // Leave any combining_accent state in tact (seems to match how other // Android apps work) None }, Err(err) => { tracing::warn!("KeyEvent: Failed to get key map character: {err:?}"); *combining_accent = None; None }, } } pub fn to_logical(key_char: Option, keycode: Keycode) -> Key { use android_activity::input::Keycode::*; let native = NativeKey::Android(keycode.into()); match key_char { Some(KeyMapChar::Unicode(c)) => Key::Character(smol_str::SmolStr::from_iter([c])), Some(KeyMapChar::CombiningAccent(c)) => Key::Dead(Some(c)), None | Some(KeyMapChar::None) => match keycode { // Using `BrowserHome` instead of `GoHome` according to // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values Home => Key::Named(NamedKey::BrowserHome), Back => Key::Named(NamedKey::BrowserBack), Call => Key::Named(NamedKey::Call), Endcall => Key::Named(NamedKey::EndCall), //------------------------------------------------------------------------------- // These should be redundant because they should have already been matched // as `KeyMapChar::Unicode`, but also matched here as a fallback Keycode0 => Key::Character("0".into()), Keycode1 => Key::Character("1".into()), Keycode2 => Key::Character("2".into()), Keycode3 => Key::Character("3".into()), Keycode4 => Key::Character("4".into()), Keycode5 => Key::Character("5".into()), Keycode6 => Key::Character("6".into()), Keycode7 => Key::Character("7".into()), Keycode8 => Key::Character("8".into()), Keycode9 => Key::Character("9".into()), Star => Key::Character("*".into()), Pound => Key::Character("#".into()), A => Key::Character("a".into()), B => Key::Character("b".into()), C => Key::Character("c".into()), D => Key::Character("d".into()), E => Key::Character("e".into()), F => Key::Character("f".into()), G => Key::Character("g".into()), H => Key::Character("h".into()), I => Key::Character("i".into()), J => Key::Character("j".into()), K => Key::Character("k".into()), L => Key::Character("l".into()), M => Key::Character("m".into()), N => Key::Character("n".into()), O => Key::Character("o".into()), P => Key::Character("p".into()), Q => Key::Character("q".into()), R => Key::Character("r".into()), S => Key::Character("s".into()), T => Key::Character("t".into()), U => Key::Character("u".into()), V => Key::Character("v".into()), W => Key::Character("w".into()), X => Key::Character("x".into()), Y => Key::Character("y".into()), Z => Key::Character("z".into()), Comma => Key::Character(",".into()), Period => Key::Character(".".into()), Grave => Key::Character("`".into()), Minus => Key::Character("-".into()), Equals => Key::Character("=".into()), LeftBracket => Key::Character("[".into()), RightBracket => Key::Character("]".into()), Backslash => Key::Character("\\".into()), Semicolon => Key::Character(";".into()), Apostrophe => Key::Character("'".into()), Slash => Key::Character("/".into()), At => Key::Character("@".into()), Plus => Key::Character("+".into()), //------------------------------------------------------------------------------- DpadUp => Key::Named(NamedKey::ArrowUp), DpadDown => Key::Named(NamedKey::ArrowDown), DpadLeft => Key::Named(NamedKey::ArrowLeft), DpadRight => Key::Named(NamedKey::ArrowRight), DpadCenter => Key::Named(NamedKey::Enter), VolumeUp => Key::Named(NamedKey::AudioVolumeUp), VolumeDown => Key::Named(NamedKey::AudioVolumeDown), Power => Key::Named(NamedKey::Power), Camera => Key::Named(NamedKey::Camera), Clear => Key::Named(NamedKey::Clear), AltLeft => Key::Named(NamedKey::Alt), AltRight => Key::Named(NamedKey::Alt), ShiftLeft => Key::Named(NamedKey::Shift), ShiftRight => Key::Named(NamedKey::Shift), Tab => Key::Named(NamedKey::Tab), Space => Key::Named(NamedKey::Space), Sym => Key::Named(NamedKey::Symbol), Explorer => Key::Named(NamedKey::LaunchWebBrowser), Envelope => Key::Named(NamedKey::LaunchMail), Enter => Key::Named(NamedKey::Enter), Del => Key::Named(NamedKey::Backspace), // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM Num => Key::Named(NamedKey::Alt), Headsethook => Key::Named(NamedKey::HeadsetHook), Focus => Key::Named(NamedKey::CameraFocus), Notification => Key::Named(NamedKey::Notification), Search => Key::Named(NamedKey::BrowserSearch), MediaPlayPause => Key::Named(NamedKey::MediaPlayPause), MediaStop => Key::Named(NamedKey::MediaStop), MediaNext => Key::Named(NamedKey::MediaTrackNext), MediaPrevious => Key::Named(NamedKey::MediaTrackPrevious), MediaRewind => Key::Named(NamedKey::MediaRewind), MediaFastForward => Key::Named(NamedKey::MediaFastForward), Mute => Key::Named(NamedKey::MicrophoneVolumeMute), PageUp => Key::Named(NamedKey::PageUp), PageDown => Key::Named(NamedKey::PageDown), Escape => Key::Named(NamedKey::Escape), ForwardDel => Key::Named(NamedKey::Delete), CtrlLeft => Key::Named(NamedKey::Control), CtrlRight => Key::Named(NamedKey::Control), CapsLock => Key::Named(NamedKey::CapsLock), ScrollLock => Key::Named(NamedKey::ScrollLock), MetaLeft => Key::Named(NamedKey::Super), MetaRight => Key::Named(NamedKey::Super), Function => Key::Named(NamedKey::Fn), Sysrq => Key::Named(NamedKey::PrintScreen), Break => Key::Named(NamedKey::Pause), MoveHome => Key::Named(NamedKey::Home), MoveEnd => Key::Named(NamedKey::End), Insert => Key::Named(NamedKey::Insert), Forward => Key::Named(NamedKey::BrowserForward), MediaPlay => Key::Named(NamedKey::MediaPlay), MediaPause => Key::Named(NamedKey::MediaPause), MediaClose => Key::Named(NamedKey::MediaClose), MediaEject => Key::Named(NamedKey::Eject), MediaRecord => Key::Named(NamedKey::MediaRecord), F1 => Key::Named(NamedKey::F1), F2 => Key::Named(NamedKey::F2), F3 => Key::Named(NamedKey::F3), F4 => Key::Named(NamedKey::F4), F5 => Key::Named(NamedKey::F5), F6 => Key::Named(NamedKey::F6), F7 => Key::Named(NamedKey::F7), F8 => Key::Named(NamedKey::F8), F9 => Key::Named(NamedKey::F9), F10 => Key::Named(NamedKey::F10), F11 => Key::Named(NamedKey::F11), F12 => Key::Named(NamedKey::F12), NumLock => Key::Named(NamedKey::NumLock), Numpad0 => Key::Character("0".into()), Numpad1 => Key::Character("1".into()), Numpad2 => Key::Character("2".into()), Numpad3 => Key::Character("3".into()), Numpad4 => Key::Character("4".into()), Numpad5 => Key::Character("5".into()), Numpad6 => Key::Character("6".into()), Numpad7 => Key::Character("7".into()), Numpad8 => Key::Character("8".into()), Numpad9 => Key::Character("9".into()), NumpadDivide => Key::Character("/".into()), NumpadMultiply => Key::Character("*".into()), NumpadSubtract => Key::Character("-".into()), NumpadAdd => Key::Character("+".into()), NumpadDot => Key::Character(".".into()), NumpadComma => Key::Character(",".into()), NumpadEnter => Key::Named(NamedKey::Enter), NumpadEquals => Key::Character("=".into()), NumpadLeftParen => Key::Character("(".into()), NumpadRightParen => Key::Character(")".into()), VolumeMute => Key::Named(NamedKey::AudioVolumeMute), Info => Key::Named(NamedKey::Info), ChannelUp => Key::Named(NamedKey::ChannelUp), ChannelDown => Key::Named(NamedKey::ChannelDown), ZoomIn => Key::Named(NamedKey::ZoomIn), ZoomOut => Key::Named(NamedKey::ZoomOut), Tv => Key::Named(NamedKey::TV), Guide => Key::Named(NamedKey::Guide), Dvr => Key::Named(NamedKey::DVR), Bookmark => Key::Named(NamedKey::BrowserFavorites), Captions => Key::Named(NamedKey::ClosedCaptionToggle), Settings => Key::Named(NamedKey::Settings), TvPower => Key::Named(NamedKey::TVPower), TvInput => Key::Named(NamedKey::TVInput), StbPower => Key::Named(NamedKey::STBPower), StbInput => Key::Named(NamedKey::STBInput), AvrPower => Key::Named(NamedKey::AVRPower), AvrInput => Key::Named(NamedKey::AVRInput), ProgRed => Key::Named(NamedKey::ColorF0Red), ProgGreen => Key::Named(NamedKey::ColorF1Green), ProgYellow => Key::Named(NamedKey::ColorF2Yellow), ProgBlue => Key::Named(NamedKey::ColorF3Blue), AppSwitch => Key::Named(NamedKey::AppSwitch), LanguageSwitch => Key::Named(NamedKey::GroupNext), MannerMode => Key::Named(NamedKey::MannerMode), Keycode3dMode => Key::Named(NamedKey::TV3DMode), Contacts => Key::Named(NamedKey::LaunchContacts), Calendar => Key::Named(NamedKey::LaunchCalendar), Music => Key::Named(NamedKey::LaunchMusicPlayer), Calculator => Key::Named(NamedKey::LaunchApplication2), ZenkakuHankaku => Key::Named(NamedKey::ZenkakuHankaku), Eisu => Key::Named(NamedKey::Eisu), Muhenkan => Key::Named(NamedKey::NonConvert), Henkan => Key::Named(NamedKey::Convert), KatakanaHiragana => Key::Named(NamedKey::HiraganaKatakana), Kana => Key::Named(NamedKey::KanjiMode), BrightnessDown => Key::Named(NamedKey::BrightnessDown), BrightnessUp => Key::Named(NamedKey::BrightnessUp), MediaAudioTrack => Key::Named(NamedKey::MediaAudioTrack), Sleep => Key::Named(NamedKey::Standby), Wakeup => Key::Named(NamedKey::WakeUp), Pairing => Key::Named(NamedKey::Pairing), MediaTopMenu => Key::Named(NamedKey::MediaTopMenu), LastChannel => Key::Named(NamedKey::MediaLast), TvDataService => Key::Named(NamedKey::TVDataService), VoiceAssist => Key::Named(NamedKey::VoiceDial), TvRadioService => Key::Named(NamedKey::TVRadioService), TvTeletext => Key::Named(NamedKey::Teletext), TvNumberEntry => Key::Named(NamedKey::TVNumberEntry), TvTerrestrialAnalog => Key::Named(NamedKey::TVTerrestrialAnalog), TvTerrestrialDigital => Key::Named(NamedKey::TVTerrestrialDigital), TvSatellite => Key::Named(NamedKey::TVSatellite), TvSatelliteBs => Key::Named(NamedKey::TVSatelliteBS), TvSatelliteCs => Key::Named(NamedKey::TVSatelliteCS), TvSatelliteService => Key::Named(NamedKey::TVSatelliteToggle), TvNetwork => Key::Named(NamedKey::TVNetwork), TvAntennaCable => Key::Named(NamedKey::TVAntennaCable), TvInputHdmi1 => Key::Named(NamedKey::TVInputHDMI1), TvInputHdmi2 => Key::Named(NamedKey::TVInputHDMI2), TvInputHdmi3 => Key::Named(NamedKey::TVInputHDMI3), TvInputHdmi4 => Key::Named(NamedKey::TVInputHDMI4), TvInputComposite1 => Key::Named(NamedKey::TVInputComposite1), TvInputComposite2 => Key::Named(NamedKey::TVInputComposite2), TvInputComponent1 => Key::Named(NamedKey::TVInputComponent1), TvInputComponent2 => Key::Named(NamedKey::TVInputComponent2), TvInputVga1 => Key::Named(NamedKey::TVInputVGA1), TvAudioDescription => Key::Named(NamedKey::TVAudioDescription), TvAudioDescriptionMixUp => Key::Named(NamedKey::TVAudioDescriptionMixUp), TvAudioDescriptionMixDown => Key::Named(NamedKey::TVAudioDescriptionMixDown), TvZoomMode => Key::Named(NamedKey::ZoomToggle), TvContentsMenu => Key::Named(NamedKey::TVContentsMenu), TvMediaContextMenu => Key::Named(NamedKey::TVMediaContext), TvTimerProgramming => Key::Named(NamedKey::TVTimer), Help => Key::Named(NamedKey::Help), NavigatePrevious => Key::Named(NamedKey::NavigatePrevious), NavigateNext => Key::Named(NamedKey::NavigateNext), NavigateIn => Key::Named(NamedKey::NavigateIn), NavigateOut => Key::Named(NamedKey::NavigateOut), MediaSkipForward => Key::Named(NamedKey::MediaSkipForward), MediaSkipBackward => Key::Named(NamedKey::MediaSkipBackward), MediaStepForward => Key::Named(NamedKey::MediaStepForward), MediaStepBackward => Key::Named(NamedKey::MediaStepBackward), Cut => Key::Named(NamedKey::Cut), Copy => Key::Named(NamedKey::Copy), Paste => Key::Named(NamedKey::Paste), Refresh => Key::Named(NamedKey::BrowserRefresh), // ----------------------------------------------------------------- // Keycodes that don't have a logical Key mapping // ----------------------------------------------------------------- Unknown => Key::Unidentified(native), // Can be added on demand SoftLeft => Key::Unidentified(native), SoftRight => Key::Unidentified(native), Menu => Key::Unidentified(native), Pictsymbols => Key::Unidentified(native), SwitchCharset => Key::Unidentified(native), // ----------------------------------------------------------------- // Gamepad events should be exposed through a separate API, not // keyboard events ButtonA => Key::Unidentified(native), ButtonB => Key::Unidentified(native), ButtonC => Key::Unidentified(native), ButtonX => Key::Unidentified(native), ButtonY => Key::Unidentified(native), ButtonZ => Key::Unidentified(native), ButtonL1 => Key::Unidentified(native), ButtonR1 => Key::Unidentified(native), ButtonL2 => Key::Unidentified(native), ButtonR2 => Key::Unidentified(native), ButtonThumbl => Key::Unidentified(native), ButtonThumbr => Key::Unidentified(native), ButtonStart => Key::Unidentified(native), ButtonSelect => Key::Unidentified(native), ButtonMode => Key::Unidentified(native), // ----------------------------------------------------------------- Window => Key::Unidentified(native), Button1 => Key::Unidentified(native), Button2 => Key::Unidentified(native), Button3 => Key::Unidentified(native), Button4 => Key::Unidentified(native), Button5 => Key::Unidentified(native), Button6 => Key::Unidentified(native), Button7 => Key::Unidentified(native), Button8 => Key::Unidentified(native), Button9 => Key::Unidentified(native), Button10 => Key::Unidentified(native), Button11 => Key::Unidentified(native), Button12 => Key::Unidentified(native), Button13 => Key::Unidentified(native), Button14 => Key::Unidentified(native), Button15 => Key::Unidentified(native), Button16 => Key::Unidentified(native), Yen => Key::Unidentified(native), Ro => Key::Unidentified(native), Assist => Key::Unidentified(native), Keycode11 => Key::Unidentified(native), Keycode12 => Key::Unidentified(native), StemPrimary => Key::Unidentified(native), Stem1 => Key::Unidentified(native), Stem2 => Key::Unidentified(native), Stem3 => Key::Unidentified(native), DpadUpLeft => Key::Unidentified(native), DpadDownLeft => Key::Unidentified(native), DpadUpRight => Key::Unidentified(native), DpadDownRight => Key::Unidentified(native), SoftSleep => Key::Unidentified(native), SystemNavigationUp => Key::Unidentified(native), SystemNavigationDown => Key::Unidentified(native), SystemNavigationLeft => Key::Unidentified(native), SystemNavigationRight => Key::Unidentified(native), AllApps => Key::Unidentified(native), ThumbsUp => Key::Unidentified(native), ThumbsDown => Key::Unidentified(native), ProfileSwitch => Key::Unidentified(native), // It's always possible that new versions of Android could introduce // key codes we can't know about at compile time. _ => Key::Unidentified(native), }, } } pub fn to_location(keycode: Keycode) -> KeyLocation { use android_activity::input::Keycode::*; match keycode { AltLeft => KeyLocation::Left, AltRight => KeyLocation::Right, ShiftLeft => KeyLocation::Left, ShiftRight => KeyLocation::Right, // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM Num => KeyLocation::Left, CtrlLeft => KeyLocation::Left, CtrlRight => KeyLocation::Right, MetaLeft => KeyLocation::Left, MetaRight => KeyLocation::Right, NumLock => KeyLocation::Numpad, Numpad0 => KeyLocation::Numpad, Numpad1 => KeyLocation::Numpad, Numpad2 => KeyLocation::Numpad, Numpad3 => KeyLocation::Numpad, Numpad4 => KeyLocation::Numpad, Numpad5 => KeyLocation::Numpad, Numpad6 => KeyLocation::Numpad, Numpad7 => KeyLocation::Numpad, Numpad8 => KeyLocation::Numpad, Numpad9 => KeyLocation::Numpad, NumpadDivide => KeyLocation::Numpad, NumpadMultiply => KeyLocation::Numpad, NumpadSubtract => KeyLocation::Numpad, NumpadAdd => KeyLocation::Numpad, NumpadDot => KeyLocation::Numpad, NumpadComma => KeyLocation::Numpad, NumpadEnter => KeyLocation::Numpad, NumpadEquals => KeyLocation::Numpad, NumpadLeftParen => KeyLocation::Numpad, NumpadRightParen => KeyLocation::Numpad, _ => KeyLocation::Standard, } } winit-0.30.9/src/platform_impl/android/mod.rs000064400000000000000000001116721046102023000172600ustar 00000000000000use std::cell::Cell; use std::collections::VecDeque; use std::hash::Hash; use std::marker::PhantomData; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{mpsc, Arc, Mutex}; use std::time::{Duration, Instant}; use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction}; use android_activity::{ AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect, }; use tracing::{debug, trace, warn}; use crate::cursor::Cursor; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error; use crate::error::EventLoopError; use crate::event::{self, Force, InnerSizeWriter, StartCause}; use crate::event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents}; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::Fullscreen; use crate::window::{ self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel, }; mod keycodes; pub(crate) use crate::cursor::{ NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource, }; pub(crate) use crate::icon::NoIcon as PlatformIcon; static HAS_FOCUS: AtomicBool = AtomicBool::new(true); /// Returns the minimum `Option`, taking into account that `None` /// equates to an infinite timeout, not a zero timeout (so can't just use /// `Option::min`) fn min_timeout(a: Option, b: Option) -> Option { a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))) } struct PeekableReceiver { recv: mpsc::Receiver, first: Option, } impl PeekableReceiver { pub fn from_recv(recv: mpsc::Receiver) -> Self { Self { recv, first: None } } pub fn has_incoming(&mut self) -> bool { if self.first.is_some() { return true; } match self.recv.try_recv() { Ok(v) => { self.first = Some(v); true }, Err(mpsc::TryRecvError::Empty) => false, Err(mpsc::TryRecvError::Disconnected) => { warn!("Channel was disconnected when checking incoming"); false }, } } pub fn try_recv(&mut self) -> Result { if let Some(first) = self.first.take() { return Ok(first); } self.recv.try_recv() } } #[derive(Clone)] struct SharedFlagSetter { flag: Arc, } impl SharedFlagSetter { pub fn set(&self) -> bool { self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok() } } struct SharedFlag { flag: Arc, } // Used for queuing redraws from arbitrary threads. We don't care how many // times a redraw is requested (so don't actually need to queue any data, // we just need to know at the start of a main loop iteration if a redraw // was queued and be able to read and clear the state atomically) impl SharedFlag { pub fn new() -> Self { Self { flag: Arc::new(AtomicBool::new(false)) } } pub fn setter(&self) -> SharedFlagSetter { SharedFlagSetter { flag: self.flag.clone() } } pub fn get_and_reset(&self) -> bool { self.flag.swap(false, std::sync::atomic::Ordering::AcqRel) } } #[derive(Clone)] pub struct RedrawRequester { flag: SharedFlagSetter, waker: AndroidAppWaker, } impl RedrawRequester { fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self { RedrawRequester { flag: flag.setter(), waker } } pub fn request_redraw(&self) { if self.flag.set() { // Only explicitly try to wake up the main loop when the flag // value changes self.waker.wake(); } } } #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct KeyEventExtra {} pub struct EventLoop { pub(crate) android_app: AndroidApp, window_target: event_loop::ActiveEventLoop, redraw_flag: SharedFlag, user_events_sender: mpsc::Sender, user_events_receiver: PeekableReceiver, // must wake looper whenever something gets sent loop_running: bool, // Dispatched `NewEvents` running: bool, pending_redraw: bool, cause: StartCause, ignore_volume_keys: bool, combining_accent: Option, } #[derive(Debug, Clone, PartialEq)] pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) android_app: Option, pub(crate) ignore_volume_keys: bool, } impl Default for PlatformSpecificEventLoopAttributes { fn default() -> Self { Self { android_app: Default::default(), ignore_volume_keys: true } } } impl EventLoop { pub(crate) fn new( attributes: &PlatformSpecificEventLoopAttributes, ) -> Result { let (user_events_sender, user_events_receiver) = mpsc::channel(); let android_app = attributes.android_app.as_ref().expect( "An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \ Android", ); let redraw_flag = SharedFlag::new(); Ok(Self { android_app: android_app.clone(), window_target: event_loop::ActiveEventLoop { p: ActiveEventLoop { app: android_app.clone(), control_flow: Cell::new(ControlFlow::default()), exit: Cell::new(false), redraw_requester: RedrawRequester::new( &redraw_flag, android_app.create_waker(), ), }, _marker: PhantomData, }, redraw_flag, user_events_sender, user_events_receiver: PeekableReceiver::from_recv(user_events_receiver), loop_running: false, running: false, pending_redraw: false, cause: StartCause::Init, ignore_volume_keys: attributes.ignore_volume_keys, combining_accent: None, }) } fn single_iteration(&mut self, main_event: Option>, callback: &mut F) where F: FnMut(event::Event, &RootAEL), { trace!("Mainloop iteration"); let cause = self.cause; let mut pending_redraw = self.pending_redraw; let mut resized = false; callback(event::Event::NewEvents(cause), self.window_target()); if let Some(event) = main_event { trace!("Handling main event {:?}", event); match event { MainEvent::InitWindow { .. } => { callback(event::Event::Resumed, self.window_target()); }, MainEvent::TerminateWindow { .. } => { callback(event::Event::Suspended, self.window_target()); }, MainEvent::WindowResized { .. } => resized = true, MainEvent::RedrawNeeded { .. } => pending_redraw = true, MainEvent::ContentRectChanged { .. } => { warn!("TODO: find a way to notify application of content rect change"); }, MainEvent::GainedFocus => { HAS_FOCUS.store(true, Ordering::Relaxed); callback( event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::Focused(true), }, self.window_target(), ); }, MainEvent::LostFocus => { HAS_FOCUS.store(false, Ordering::Relaxed); callback( event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::Focused(false), }, self.window_target(), ); }, MainEvent::ConfigChanged { .. } => { let monitor = MonitorHandle::new(self.android_app.clone()); let old_scale_factor = monitor.scale_factor(); let scale_factor = monitor.scale_factor(); if (scale_factor - old_scale_factor).abs() < f64::EPSILON { let new_inner_size = Arc::new(Mutex::new( MonitorHandle::new(self.android_app.clone()).size(), )); let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::ScaleFactorChanged { inner_size_writer: InnerSizeWriter::new(Arc::downgrade( &new_inner_size, )), scale_factor, }, }; callback(event, self.window_target()); } }, MainEvent::LowMemory => { callback(event::Event::MemoryWarning, self.window_target()); }, MainEvent::Start => { // XXX: how to forward this state to applications? warn!("TODO: forward onStart notification to application"); }, MainEvent::Resume { .. } => { debug!("App Resumed - is running"); self.running = true; }, MainEvent::SaveState { .. } => { // XXX: how to forward this state to applications? // XXX: also how do we expose state restoration to apps? warn!("TODO: forward saveState notification to application"); }, MainEvent::Pause => { debug!("App Paused - stopped running"); self.running = false; }, MainEvent::Stop => { // XXX: how to forward this state to applications? warn!("TODO: forward onStop notification to application"); }, MainEvent::Destroy => { // XXX: maybe exit mainloop to drop things before being // killed by the OS? warn!("TODO: forward onDestroy notification to application"); }, MainEvent::InsetsChanged { .. } => { // XXX: how to forward this state to applications? warn!("TODO: handle Android InsetsChanged notification"); }, unknown => { trace!("Unknown MainEvent {unknown:?} (ignored)"); }, } } else { trace!("No main event to handle"); } // temporarily decouple `android_app` from `self` so we aren't holding // a borrow of `self` while iterating let android_app = self.android_app.clone(); // Process input events match android_app.input_events_iter() { Ok(mut input_iter) => loop { let read_event = input_iter.next(|event| self.handle_input_event(&android_app, event, callback)); if !read_event { break; } }, Err(err) => { tracing::warn!("Failed to get input events iterator: {err:?}"); }, } // Empty the user event buffer { while let Ok(event) = self.user_events_receiver.try_recv() { callback(crate::event::Event::UserEvent(event), self.window_target()); } } if self.running { if resized { let size = if let Some(native_window) = self.android_app.native_window().as_ref() { let width = native_window.width() as _; let height = native_window.height() as _; PhysicalSize::new(width, height) } else { PhysicalSize::new(0, 0) }; let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::Resized(size), }; callback(event, self.window_target()); } pending_redraw |= self.redraw_flag.get_and_reset(); if pending_redraw { pending_redraw = false; let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::RedrawRequested, }; callback(event, self.window_target()); } } // This is always the last event we dispatch before poll again callback(event::Event::AboutToWait, self.window_target()); self.pending_redraw = pending_redraw; } fn handle_input_event( &mut self, android_app: &AndroidApp, event: &InputEvent<'_>, callback: &mut F, ) -> InputStatus where F: FnMut(event::Event, &RootAEL), { let mut input_status = InputStatus::Handled; match event { InputEvent::MotionEvent(motion_event) => { let window_id = window::WindowId(WindowId); let device_id = event::DeviceId(DeviceId(motion_event.device_id())); let phase = match motion_event.action() { MotionAction::Down | MotionAction::PointerDown => { Some(event::TouchPhase::Started) }, MotionAction::Up | MotionAction::PointerUp => Some(event::TouchPhase::Ended), MotionAction::Move => Some(event::TouchPhase::Moved), MotionAction::Cancel => Some(event::TouchPhase::Cancelled), _ => { None // TODO mouse events }, }; if let Some(phase) = phase { let pointers: Box>> = match phase { event::TouchPhase::Started | event::TouchPhase::Ended => { Box::new(std::iter::once( motion_event.pointer_at_index(motion_event.pointer_index()), )) }, event::TouchPhase::Moved | event::TouchPhase::Cancelled => { Box::new(motion_event.pointers()) }, }; for pointer in pointers { let location = PhysicalPosition { x: pointer.x() as _, y: pointer.y() as _ }; trace!( "Input event {device_id:?}, {phase:?}, loc={location:?}, \ pointer={pointer:?}" ); let event = event::Event::WindowEvent { window_id, event: event::WindowEvent::Touch(event::Touch { device_id, phase, location, id: pointer.pointer_id() as u64, force: Some(Force::Normalized(pointer.pressure() as f64)), }), }; callback(event, self.window_target()); } } }, InputEvent::KeyEvent(key) => { match key.key_code() { // Flag keys related to volume as unhandled. While winit does not have a way for // applications to configure what keys to flag as handled, // this appears to be a good default until winit // can be configured. Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute if self.ignore_volume_keys => { input_status = InputStatus::Unhandled }, keycode => { let state = match key.action() { KeyAction::Down => event::ElementState::Pressed, KeyAction::Up => event::ElementState::Released, _ => event::ElementState::Released, }; let key_char = keycodes::character_map_and_combine_key( android_app, key, &mut self.combining_accent, ); let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::KeyboardInput { device_id: event::DeviceId(DeviceId(key.device_id())), event: event::KeyEvent { state, physical_key: keycodes::to_physical_key(keycode), logical_key: keycodes::to_logical(key_char, keycode), location: keycodes::to_location(keycode), repeat: key.repeat_count() > 0, text: None, platform_specific: KeyEventExtra {}, }, is_synthetic: false, }, }; callback(event, self.window_target()); }, } }, _ => { warn!("Unknown android_activity input event {event:?}") }, } input_status } pub fn run(mut self, event_handler: F) -> Result<(), EventLoopError> where F: FnMut(event::Event, &event_loop::ActiveEventLoop), { self.run_on_demand(event_handler) } pub fn run_on_demand(&mut self, mut event_handler: F) -> Result<(), EventLoopError> where F: FnMut(event::Event, &event_loop::ActiveEventLoop), { loop { match self.pump_events(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); }, PumpStatus::Exit(code) => { break Err(EventLoopError::ExitFailure(code)); }, _ => { continue; }, } } } pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus where F: FnMut(event::Event, &RootAEL), { if !self.loop_running { self.loop_running = true; // Reset the internal state for the loop as we start running to // ensure consistent behaviour in case the loop runs and exits more // than once self.pending_redraw = false; self.cause = StartCause::Init; // run the initial loop iteration self.single_iteration(None, &mut callback); } // Consider the possibility that the `StartCause::Init` iteration could // request to Exit if !self.exiting() { self.poll_events_with_timeout(timeout, &mut callback); } if self.exiting() { self.loop_running = false; callback(event::Event::LoopExiting, self.window_target()); PumpStatus::Exit(0) } else { PumpStatus::Continue } } fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) where F: FnMut(event::Event, &RootAEL), { let start = Instant::now(); self.pending_redraw |= self.redraw_flag.get_and_reset(); timeout = if self.running && (self.pending_redraw || self.user_events_receiver.has_incoming()) { // If we already have work to do then we don't want to block on the next poll Some(Duration::ZERO) } else { let control_flow_timeout = match self.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::WaitUntil(wait_deadline) => { Some(wait_deadline.saturating_duration_since(start)) }, }; min_timeout(control_flow_timeout, timeout) }; let app = self.android_app.clone(); // Don't borrow self as part of poll expression app.poll_events(timeout, |poll_event| { let mut main_event = None; match poll_event { android_activity::PollEvent::Wake => { // In the X11 backend it's noted that too many false-positive wake ups // would cause the event loop to run continuously. They handle this by // re-checking for pending events (assuming they cover all // valid reasons for a wake up). // // For now, user_events and redraw_requests are the only reasons to expect // a wake up here so we can ignore the wake up if there are no events/requests. // We also ignore wake ups while suspended. self.pending_redraw |= self.redraw_flag.get_and_reset(); if !self.running || (!self.pending_redraw && !self.user_events_receiver.has_incoming()) { return; } }, android_activity::PollEvent::Timeout => {}, android_activity::PollEvent::Main(event) => { main_event = Some(event); }, unknown_event => { warn!("Unknown poll event {unknown_event:?} (ignored)"); }, } self.cause = match self.control_flow() { ControlFlow::Poll => StartCause::Poll, ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None }, ControlFlow::WaitUntil(deadline) => { if Instant::now() < deadline { StartCause::WaitCancelled { start, requested_resume: Some(deadline) } } else { StartCause::ResumeTimeReached { start, requested_resume: deadline } } }, }; self.single_iteration(main_event, &mut callback); }); } pub fn window_target(&self) -> &event_loop::ActiveEventLoop { &self.window_target } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { user_events_sender: self.user_events_sender.clone(), waker: self.android_app.create_waker(), } } fn control_flow(&self) -> ControlFlow { self.window_target.p.control_flow() } fn exiting(&self) -> bool { self.window_target.p.exiting() } } pub struct EventLoopProxy { user_events_sender: mpsc::Sender, waker: AndroidAppWaker, } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy { user_events_sender: self.user_events_sender.clone(), waker: self.waker.clone(), } } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { self.user_events_sender.send(event).map_err(|err| event_loop::EventLoopClosed(err.0))?; self.waker.wake(); Ok(()) } } pub struct ActiveEventLoop { pub(crate) app: AndroidApp, control_flow: Cell, exit: Cell, redraw_requester: RedrawRequester, } impl ActiveEventLoop { pub fn primary_monitor(&self) -> Option { Some(MonitorHandle::new(self.app.clone())) } pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor { let _ = source.inner; CustomCursor { inner: PlatformCustomCursor } } pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); v.push_back(MonitorHandle::new(self.app.clone())); v } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty()) } #[inline] pub fn system_theme(&self) -> Option { None } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Android(rwh_06::AndroidDisplayHandle::new())) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.control_flow.set(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.control_flow.get() } pub(crate) fn exit(&self) { self.exit.set(true) } pub(crate) fn clear_exit(&self) { self.exit.set(false) } pub(crate) fn exiting(&self) -> bool { self.exit.get() } pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle { OwnedDisplayHandle } } #[derive(Clone)] pub(crate) struct OwnedDisplayHandle; impl OwnedDisplayHandle { #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::AndroidDisplayHandle::empty().into() } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::AndroidDisplayHandle::new().into()) } } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub(crate) struct WindowId; impl WindowId { pub const fn dummy() -> Self { WindowId } } impl From for u64 { fn from(_: WindowId) -> Self { 0 } } impl From for WindowId { fn from(_: u64) -> Self { Self } } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct DeviceId(i32); impl DeviceId { pub const fn dummy() -> Self { DeviceId(0) } } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PlatformSpecificWindowAttributes; pub(crate) struct Window { app: AndroidApp, redraw_requester: RedrawRequester, } impl Window { pub(crate) fn new( el: &ActiveEventLoop, _window_attrs: window::WindowAttributes, ) -> Result { // FIXME this ignores requested window attributes Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { f(self) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { f(self) } pub fn id(&self) -> WindowId { WindowId } pub fn primary_monitor(&self) -> Option { Some(MonitorHandle::new(self.app.clone())) } pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); v.push_back(MonitorHandle::new(self.app.clone())); v } pub fn current_monitor(&self) -> Option { Some(MonitorHandle::new(self.app.clone())) } pub fn scale_factor(&self) -> f64 { MonitorHandle::new(self.app.clone()).scale_factor() } pub fn request_redraw(&self) { self.redraw_requester.request_redraw() } pub fn pre_present_notify(&self) {} pub fn inner_position(&self) -> Result, error::NotSupportedError> { Err(error::NotSupportedError::new()) } pub fn outer_position(&self) -> Result, error::NotSupportedError> { Err(error::NotSupportedError::new()) } pub fn set_outer_position(&self, _position: Position) { // no effect } pub fn inner_size(&self) -> PhysicalSize { self.outer_size() } pub fn request_inner_size(&self, _size: Size) -> Option> { Some(self.inner_size()) } pub fn outer_size(&self) -> PhysicalSize { MonitorHandle::new(self.app.clone()).size() } pub fn set_min_inner_size(&self, _: Option) {} pub fn set_max_inner_size(&self, _: Option) {} pub fn resize_increments(&self) -> Option> { None } pub fn set_resize_increments(&self, _increments: Option) {} pub fn set_title(&self, _title: &str) {} pub fn set_transparent(&self, _transparent: bool) {} pub fn set_blur(&self, _blur: bool) {} pub fn set_visible(&self, _visibility: bool) {} pub fn is_visible(&self) -> Option { None } pub fn set_resizable(&self, _resizeable: bool) {} pub fn is_resizable(&self) -> bool { false } pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} pub fn enabled_buttons(&self) -> WindowButtons { WindowButtons::all() } pub fn set_minimized(&self, _minimized: bool) {} pub fn is_minimized(&self) -> Option { None } pub fn set_maximized(&self, _maximized: bool) {} pub fn is_maximized(&self) -> bool { false } pub fn set_fullscreen(&self, _monitor: Option) { warn!("Cannot set fullscreen on Android"); } pub fn fullscreen(&self) -> Option { None } pub fn set_decorations(&self, _decorations: bool) {} pub fn is_decorated(&self) -> bool { true } pub fn set_window_level(&self, _level: WindowLevel) {} pub fn set_window_icon(&self, _window_icon: Option) {} pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {} pub fn set_ime_allowed(&self, allowed: bool) { if allowed { self.app.show_soft_input(true); } else { self.app.hide_soft_input(true); } } pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} pub fn focus_window(&self) {} pub fn request_user_attention(&self, _request_type: Option) {} pub fn set_cursor(&self, _: Cursor) {} pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported(error::NotSupportedError::new())) } pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported(error::NotSupportedError::new())) } pub fn set_cursor_visible(&self, _: bool) {} pub fn drag_window(&self) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported(error::NotSupportedError::new())) } pub fn drag_resize_window( &self, _direction: ResizeDirection, ) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported(error::NotSupportedError::new())) } #[inline] pub fn show_window_menu(&self, _position: Position) {} pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported(error::NotSupportedError::new())) } #[cfg(feature = "rwh_04")] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { use rwh_04::HasRawWindowHandle; if let Some(native_window) = self.app.native_window().as_ref() { native_window.raw_window_handle() } else { panic!( "Cannot get the native window, it's null and will always be null before \ Event::Resumed and after Event::Suspended. Make sure you only call this function \ between those events." ); } } #[cfg(feature = "rwh_05")] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { use rwh_05::HasRawWindowHandle; if let Some(native_window) = self.app.native_window().as_ref() { native_window.raw_window_handle() } else { panic!( "Cannot get the native window, it's null and will always be null before \ Event::Resumed and after Event::Suspended. Make sure you only call this function \ between those events." ); } } #[cfg(feature = "rwh_05")] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] // Allow the usage of HasRawWindowHandle inside this function #[allow(deprecated)] pub fn raw_window_handle_rwh_06(&self) -> Result { use rwh_06::HasRawWindowHandle; if let Some(native_window) = self.app.native_window().as_ref() { native_window.raw_window_handle() } else { tracing::error!( "Cannot get the native window, it's null and will always be null before \ Event::Resumed and after Event::Suspended. Make sure you only call this function \ between those events." ); Err(rwh_06::HandleError::Unavailable) } } #[cfg(feature = "rwh_06")] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Android(rwh_06::AndroidDisplayHandle::new())) } pub fn config(&self) -> ConfigurationRef { self.app.config() } pub fn content_rect(&self) -> Rect { self.app.content_rect() } pub fn set_theme(&self, _theme: Option) {} pub fn theme(&self) -> Option { None } pub fn set_content_protected(&self, _protected: bool) {} pub fn has_focus(&self) -> bool { HAS_FOCUS.load(Ordering::Relaxed) } pub fn title(&self) -> String { String::new() } pub fn reset_dead_keys(&self) {} } #[derive(Default, Clone, Debug)] pub struct OsError; use std::fmt::{self, Display, Formatter}; impl Display for OsError { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { write!(fmt, "Android OS Error") } } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct MonitorHandle { app: AndroidApp, } impl PartialOrd for MonitorHandle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for MonitorHandle { fn cmp(&self, _other: &Self) -> std::cmp::Ordering { std::cmp::Ordering::Equal } } impl MonitorHandle { pub(crate) fn new(app: AndroidApp) -> Self { Self { app } } pub fn name(&self) -> Option { Some("Android Device".to_owned()) } pub fn size(&self) -> PhysicalSize { if let Some(native_window) = self.app.native_window() { PhysicalSize::new(native_window.width() as _, native_window.height() as _) } else { PhysicalSize::new(0, 0) } } pub fn position(&self) -> PhysicalPosition { (0, 0).into() } pub fn scale_factor(&self) -> f64 { self.app.config().density().map(|dpi| dpi as f64 / 160.0).unwrap_or(1.0) } pub fn refresh_rate_millihertz(&self) -> Option { // FIXME no way to get real refresh rate for now. None } pub fn video_modes(&self) -> impl Iterator { let size = self.size().into(); // FIXME this is not the real refresh rate // (it is guaranteed to support 32 bit color though) std::iter::once(VideoModeHandle { size, bit_depth: 32, refresh_rate_millihertz: 60000, monitor: self.clone(), }) } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct VideoModeHandle { size: (u32, u32), bit_depth: u16, refresh_rate_millihertz: u32, monitor: MonitorHandle, } impl VideoModeHandle { pub fn size(&self) -> PhysicalSize { self.size.into() } pub fn bit_depth(&self) -> u16 { self.bit_depth } pub fn refresh_rate_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } } winit-0.30.9/src/platform_impl/apple/appkit/window.rs000064400000000000000000000273321046102023000207600ustar 00000000000000#![allow(clippy::unnecessary_cast)] use dpi::{Position, Size}; use objc2::rc::{autoreleasepool, Retained}; use objc2::{declare_class, mutability, ClassType, DeclaredClass}; use objc2_app_kit::{NSResponder, NSWindow}; use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject}; use super::event_loop::ActiveEventLoop; use super::window_delegate::WindowDelegate; use crate::error::RequestError; use crate::monitor::MonitorHandle as CoreMonitorHandle; use crate::window::{ Cursor, Fullscreen, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, }; pub(crate) struct Window { window: MainThreadBound>, /// The window only keeps a weak reference to this, so we must keep it around here. delegate: MainThreadBound>, } impl Window { pub(crate) fn new( window_target: &ActiveEventLoop, attributes: WindowAttributes, ) -> Result { let mtm = window_target.mtm; let delegate = autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?; Ok(Window { window: MainThreadBound::new(delegate.window().retain(), mtm), delegate: MainThreadBound::new(delegate, mtm), }) } pub(crate) fn maybe_wait_on_main( &self, f: impl FnOnce(&WindowDelegate) -> R + Send, ) -> R { self.delegate.get_on_main(|delegate| f(delegate)) } #[cfg(feature = "rwh_06")] #[inline] pub(crate) fn raw_window_handle_rwh_06( &self, ) -> Result { if let Some(mtm) = MainThreadMarker::new() { Ok(self.delegate.get(mtm).raw_window_handle_rwh_06()) } else { Err(rwh_06::HandleError::Unavailable) } } #[cfg(feature = "rwh_06")] #[inline] pub(crate) fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new())) } } impl Drop for Window { fn drop(&mut self) { // Restore the video mode. if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_))) { self.set_fullscreen(None); } self.window.get_on_main(|window| autoreleasepool(|_| window.close())) } } #[cfg(feature = "rwh_06")] impl rwh_06::HasDisplayHandle for Window { fn display_handle(&self) -> Result, rwh_06::HandleError> { let raw = self.raw_display_handle_rwh_06()?; unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) } } } #[cfg(feature = "rwh_06")] impl rwh_06::HasWindowHandle for Window { fn window_handle(&self) -> Result, rwh_06::HandleError> { let raw = self.raw_window_handle_rwh_06()?; unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) } } } impl CoreWindow for Window { fn id(&self) -> crate::window::WindowId { self.maybe_wait_on_main(|delegate| delegate.id()) } fn scale_factor(&self) -> f64 { self.maybe_wait_on_main(|delegate| delegate.scale_factor()) } fn request_redraw(&self) { self.maybe_wait_on_main(|delegate| delegate.request_redraw()); } fn pre_present_notify(&self) { self.maybe_wait_on_main(|delegate| delegate.pre_present_notify()); } fn reset_dead_keys(&self) { self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys()); } fn inner_position(&self) -> Result, RequestError> { Ok(self.maybe_wait_on_main(|delegate| delegate.inner_position())) } fn outer_position(&self) -> Result, RequestError> { Ok(self.maybe_wait_on_main(|delegate| delegate.outer_position())) } fn set_outer_position(&self, position: Position) { self.maybe_wait_on_main(|delegate| delegate.set_outer_position(position)); } fn surface_size(&self) -> dpi::PhysicalSize { self.maybe_wait_on_main(|delegate| delegate.surface_size()) } fn request_surface_size(&self, size: Size) -> Option> { self.maybe_wait_on_main(|delegate| delegate.request_surface_size(size)) } fn outer_size(&self) -> dpi::PhysicalSize { self.maybe_wait_on_main(|delegate| delegate.outer_size()) } fn set_min_surface_size(&self, min_size: Option) { self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size)) } fn set_max_surface_size(&self, max_size: Option) { self.maybe_wait_on_main(|delegate| delegate.set_max_surface_size(max_size)); } fn surface_resize_increments(&self) -> Option> { self.maybe_wait_on_main(|delegate| delegate.surface_resize_increments()) } fn set_surface_resize_increments(&self, increments: Option) { self.maybe_wait_on_main(|delegate| delegate.set_surface_resize_increments(increments)); } fn set_title(&self, title: &str) { self.maybe_wait_on_main(|delegate| delegate.set_title(title)); } fn set_transparent(&self, transparent: bool) { self.maybe_wait_on_main(|delegate| delegate.set_transparent(transparent)); } fn set_blur(&self, blur: bool) { self.maybe_wait_on_main(|delegate| delegate.set_blur(blur)); } fn set_visible(&self, visible: bool) { self.maybe_wait_on_main(|delegate| delegate.set_visible(visible)); } fn is_visible(&self) -> Option { self.maybe_wait_on_main(|delegate| delegate.is_visible()) } fn set_resizable(&self, resizable: bool) { self.maybe_wait_on_main(|delegate| delegate.set_resizable(resizable)) } fn is_resizable(&self) -> bool { self.maybe_wait_on_main(|delegate| delegate.is_resizable()) } fn set_enabled_buttons(&self, buttons: WindowButtons) { self.maybe_wait_on_main(|delegate| delegate.set_enabled_buttons(buttons)) } fn enabled_buttons(&self) -> WindowButtons { self.maybe_wait_on_main(|delegate| delegate.enabled_buttons()) } fn set_minimized(&self, minimized: bool) { self.maybe_wait_on_main(|delegate| delegate.set_minimized(minimized)); } fn is_minimized(&self) -> Option { self.maybe_wait_on_main(|delegate| delegate.is_minimized()) } fn set_maximized(&self, maximized: bool) { self.maybe_wait_on_main(|delegate| delegate.set_maximized(maximized)); } fn is_maximized(&self) -> bool { self.maybe_wait_on_main(|delegate| delegate.is_maximized()) } fn set_fullscreen(&self, fullscreen: Option) { self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen.map(Into::into))) } fn fullscreen(&self) -> Option { self.maybe_wait_on_main(|delegate| delegate.fullscreen().map(Into::into)) } fn set_decorations(&self, decorations: bool) { self.maybe_wait_on_main(|delegate| delegate.set_decorations(decorations)); } fn is_decorated(&self) -> bool { self.maybe_wait_on_main(|delegate| delegate.is_decorated()) } fn set_window_level(&self, level: WindowLevel) { self.maybe_wait_on_main(|delegate| delegate.set_window_level(level)); } fn set_window_icon(&self, window_icon: Option) { self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon)); } fn set_ime_cursor_area(&self, position: Position, size: Size) { self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size)); } fn set_ime_allowed(&self, allowed: bool) { self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed)); } fn set_ime_purpose(&self, purpose: ImePurpose) { self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose)); } fn focus_window(&self) { self.maybe_wait_on_main(|delegate| delegate.focus_window()); } fn has_focus(&self) -> bool { self.maybe_wait_on_main(|delegate| delegate.has_focus()) } fn request_user_attention(&self, request_type: Option) { self.maybe_wait_on_main(|delegate| delegate.request_user_attention(request_type)); } fn set_theme(&self, theme: Option) { self.maybe_wait_on_main(|delegate| delegate.set_theme(theme)); } fn theme(&self) -> Option { self.maybe_wait_on_main(|delegate| delegate.theme()) } fn set_content_protected(&self, protected: bool) { self.maybe_wait_on_main(|delegate| delegate.set_content_protected(protected)); } fn title(&self) -> String { self.maybe_wait_on_main(|delegate| delegate.title()) } fn set_cursor(&self, cursor: Cursor) { self.maybe_wait_on_main(|delegate| delegate.set_cursor(cursor)); } fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> { self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position)) } fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> { self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode)) } fn set_cursor_visible(&self, visible: bool) { self.maybe_wait_on_main(|delegate| delegate.set_cursor_visible(visible)) } fn drag_window(&self) -> Result<(), RequestError> { self.maybe_wait_on_main(|delegate| delegate.drag_window()) } fn drag_resize_window( &self, direction: crate::window::ResizeDirection, ) -> Result<(), RequestError> { Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?) } fn show_window_menu(&self, position: Position) { self.maybe_wait_on_main(|delegate| delegate.show_window_menu(position)) } fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> { self.maybe_wait_on_main(|delegate| delegate.set_cursor_hittest(hittest)); Ok(()) } fn current_monitor(&self) -> Option { self.maybe_wait_on_main(|delegate| { delegate.current_monitor().map(|inner| CoreMonitorHandle { inner }) }) } fn available_monitors(&self) -> Box> { self.maybe_wait_on_main(|delegate| { Box::new( delegate.available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner }), ) }) } fn primary_monitor(&self) -> Option { self.maybe_wait_on_main(|delegate| { delegate.primary_monitor().map(|inner| CoreMonitorHandle { inner }) }) } #[cfg(feature = "rwh_06")] fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle { self } #[cfg(feature = "rwh_06")] fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle { self } } declare_class!( #[derive(Debug)] pub struct WinitWindow; unsafe impl ClassType for WinitWindow { #[inherits(NSResponder, NSObject)] type Super = NSWindow; type Mutability = mutability::MainThreadOnly; const NAME: &'static str = "WinitWindow"; } impl DeclaredClass for WinitWindow {} unsafe impl WinitWindow { #[method(canBecomeMainWindow)] fn can_become_main_window(&self) -> bool { trace_scope!("canBecomeMainWindow"); true } #[method(canBecomeKeyWindow)] fn can_become_key_window(&self) -> bool { trace_scope!("canBecomeKeyWindow"); true } } ); impl WinitWindow { pub(super) fn id(&self) -> WindowId { WindowId::from_raw(self as *const Self as usize) } } winit-0.30.9/src/platform_impl/ios/app_delegate.rs000064400000000000000000000044031046102023000202560ustar 00000000000000use objc2::{declare_class, mutability, ClassType, DeclaredClass}; use objc2_foundation::{MainThreadMarker, NSObject}; use objc2_ui_kit::UIApplication; use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper}; use crate::event::Event; declare_class!( pub struct AppDelegate; unsafe impl ClassType for AppDelegate { type Super = NSObject; type Mutability = mutability::InteriorMutable; const NAME: &'static str = "WinitApplicationDelegate"; } impl DeclaredClass for AppDelegate {} // UIApplicationDelegate protocol unsafe impl AppDelegate { #[method(application:didFinishLaunchingWithOptions:)] fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool { app_state::did_finish_launching(MainThreadMarker::new().unwrap()); true } #[method(applicationDidBecomeActive:)] fn did_become_active(&self, _application: &UIApplication) { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed)) } #[method(applicationWillResignActive:)] fn will_resign_active(&self, _application: &UIApplication) { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended)) } #[method(applicationWillEnterForeground:)] fn will_enter_foreground(&self, application: &UIApplication) { send_occluded_event_for_all_windows(application, false); } #[method(applicationDidEnterBackground:)] fn did_enter_background(&self, application: &UIApplication) { send_occluded_event_for_all_windows(application, true); } #[method(applicationWillTerminate:)] fn will_terminate(&self, application: &UIApplication) { app_state::terminated(application); } #[method(applicationDidReceiveMemoryWarning:)] fn did_receive_memory_warning(&self, _application: &UIApplication) { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning)) } } ); winit-0.30.9/src/platform_impl/ios/app_state.rs000064400000000000000000001034741046102023000176340ustar 00000000000000#![deny(unused_results)] use std::cell::{RefCell, RefMut}; use std::collections::HashSet; use std::os::raw::c_void; use std::sync::{Arc, Mutex, OnceLock}; use std::time::Instant; use std::{fmt, mem, ptr}; use core_foundation::base::CFRelease; use core_foundation::date::CFAbsoluteTimeGetCurrent; use core_foundation::runloop::{ kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, }; use objc2::rc::Retained; use objc2::runtime::AnyObject; use objc2::{msg_send, sel}; use objc2_foundation::{ CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion, NSProcessInfo, }; use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow}; use super::window::WinitUIWindow; use crate::dpi::PhysicalSize; use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent}; use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow}; use crate::window::WindowId as RootWindowId; macro_rules! bug { ($($msg:tt)*) => { panic!("winit iOS bug, file an issue: {}", format!($($msg)*)) }; } macro_rules! bug_assert { ($test:expr, $($msg:tt)*) => { assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*)) }; } #[derive(Debug)] pub(crate) struct HandlePendingUserEvents; pub(crate) struct EventLoopHandler { #[allow(clippy::type_complexity)] pub(crate) handler: Box, &RootActiveEventLoop)>, pub(crate) event_loop: RootActiveEventLoop, } impl fmt::Debug for EventLoopHandler { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("EventLoopHandler") .field("handler", &"...") .field("event_loop", &self.event_loop) .finish() } } impl EventLoopHandler { fn handle_event(&mut self, event: Event) { (self.handler)(event, &self.event_loop) } } #[derive(Debug)] pub(crate) enum EventWrapper { StaticEvent(Event), ScaleFactorChanged(ScaleFactorChanged), } #[derive(Debug)] pub struct ScaleFactorChanged { pub(super) window: Retained, pub(super) suggested_size: PhysicalSize, pub(super) scale_factor: f64, } enum UserCallbackTransitionResult<'a> { Success { handler: EventLoopHandler, active_control_flow: ControlFlow, processing_redraws: bool, }, ReentrancyPrevented { queued_events: &'a mut Vec, }, } impl Event { fn is_redraw(&self) -> bool { matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. }) } } // this is the state machine for the app lifecycle #[derive(Debug)] #[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] enum AppStateImpl { NotLaunched { queued_windows: Vec>, queued_events: Vec, queued_gpu_redraws: HashSet>, }, Launching { queued_windows: Vec>, queued_events: Vec, queued_handler: EventLoopHandler, queued_gpu_redraws: HashSet>, }, ProcessingEvents { handler: EventLoopHandler, queued_gpu_redraws: HashSet>, active_control_flow: ControlFlow, }, // special state to deal with reentrancy and prevent mutable aliasing. InUserCallback { queued_events: Vec, queued_gpu_redraws: HashSet>, }, ProcessingRedraws { handler: EventLoopHandler, active_control_flow: ControlFlow, }, Waiting { waiting_handler: EventLoopHandler, start: Instant, }, PollFinished { waiting_handler: EventLoopHandler, }, Terminated, } pub(crate) struct AppState { // This should never be `None`, except for briefly during a state transition. app_state: Option, control_flow: ControlFlow, waker: EventLoopWaker, } impl AppState { pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> { // basically everything in UIKit requires the main thread, so it's pointless to use the // std::sync APIs. // must be mut because plain `static` requires `Sync` static mut APP_STATE: RefCell> = RefCell::new(None); #[allow(unknown_lints)] // New lint below #[allow(static_mut_refs)] // TODO: Use `MainThreadBound` instead. let mut guard = unsafe { APP_STATE.borrow_mut() }; if guard.is_none() { #[inline(never)] #[cold] fn init_guard(guard: &mut RefMut<'static, Option>) { let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() }); **guard = Some(AppState { app_state: Some(AppStateImpl::NotLaunched { queued_windows: Vec::new(), queued_events: Vec::new(), queued_gpu_redraws: HashSet::new(), }), control_flow: ControlFlow::default(), waker, }); } init_guard(&mut guard); } RefMut::map(guard, |state| state.as_mut().unwrap()) } fn state(&self) -> &AppStateImpl { match &self.app_state { Some(ref state) => state, None => bug!("`AppState` previously failed a state transition"), } } fn state_mut(&mut self) -> &mut AppStateImpl { match &mut self.app_state { Some(ref mut state) => state, None => bug!("`AppState` previously failed a state transition"), } } fn take_state(&mut self) -> AppStateImpl { match self.app_state.take() { Some(state) => state, None => bug!("`AppState` previously failed a state transition"), } } fn set_state(&mut self, new_state: AppStateImpl) { bug_assert!( self.app_state.is_none(), "attempted to set an `AppState` without calling `take_state` first {:?}", self.app_state ); self.app_state = Some(new_state) } fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl { match &mut self.app_state { Some(ref mut state) => mem::replace(state, new_state), None => bug!("`AppState` previously failed a state transition"), } } fn has_launched(&self) -> bool { !matches!(self.state(), AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. }) } fn has_terminated(&self) -> bool { matches!(self.state(), AppStateImpl::Terminated) } fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) { let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { AppStateImpl::NotLaunched { queued_windows, queued_events, queued_gpu_redraws } => { (queued_windows, queued_events, queued_gpu_redraws) }, s => bug!("unexpected state {:?}", s), }; self.set_state(AppStateImpl::Launching { queued_windows, queued_events, queued_handler, queued_gpu_redraws, }); } fn did_finish_launching_transition( &mut self, ) -> (Vec>, Vec) { let (windows, events, handler, queued_gpu_redraws) = match self.take_state() { AppStateImpl::Launching { queued_windows, queued_events, queued_handler, queued_gpu_redraws, } => (queued_windows, queued_events, queued_handler, queued_gpu_redraws), s => bug!("unexpected state {:?}", s), }; self.set_state(AppStateImpl::ProcessingEvents { handler, active_control_flow: self.control_flow, queued_gpu_redraws, }); (windows, events) } fn wakeup_transition(&mut self) -> Option { // before `AppState::did_finish_launching` is called, pretend there is no running // event loop. if !self.has_launched() || self.has_terminated() { return None; } let (handler, event) = match (self.control_flow, self.take_state()) { (ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => { (waiting_handler, EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll))) }, (ControlFlow::Wait, AppStateImpl::Waiting { waiting_handler, start }) => ( waiting_handler, EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { start, requested_resume: None, })), ), ( ControlFlow::WaitUntil(requested_resume), AppStateImpl::Waiting { waiting_handler, start }, ) => { let event = if Instant::now() >= requested_resume { EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached { start, requested_resume, })) } else { EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { start, requested_resume: Some(requested_resume), })) }; (waiting_handler, event) }, s => bug!("`EventHandler` unexpectedly woke up {:?}", s), }; self.set_state(AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws: Default::default(), active_control_flow: self.control_flow, }); Some(event) } fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> { // If we're not able to process an event due to recursion or `Init` not having been sent out // yet, then queue the events up. match self.state_mut() { &mut AppStateImpl::Launching { ref mut queued_events, .. } | &mut AppStateImpl::NotLaunched { ref mut queued_events, .. } | &mut AppStateImpl::InUserCallback { ref mut queued_events, .. } => { // A lifetime cast: early returns are not currently handled well with NLL, but // polonius handles them well. This transmute is a safe workaround. return unsafe { mem::transmute::< UserCallbackTransitionResult<'_>, UserCallbackTransitionResult<'_>, >(UserCallbackTransitionResult::ReentrancyPrevented { queued_events, }) }; }, &mut AppStateImpl::ProcessingEvents { .. } | &mut AppStateImpl::ProcessingRedraws { .. } => {}, s @ &mut AppStateImpl::PollFinished { .. } | s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::Terminated => { bug!("unexpected attempted to process an event {:?}", s) }, } let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) = match self .take_state() { AppStateImpl::Launching { .. } | AppStateImpl::NotLaunched { .. } | AppStateImpl::InUserCallback { .. } => unreachable!(), AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => { (handler, queued_gpu_redraws, active_control_flow, false) }, AppStateImpl::ProcessingRedraws { handler, active_control_flow } => { (handler, Default::default(), active_control_flow, true) }, AppStateImpl::PollFinished { .. } | AppStateImpl::Waiting { .. } | AppStateImpl::Terminated => unreachable!(), }; self.set_state(AppStateImpl::InUserCallback { queued_events: Vec::new(), queued_gpu_redraws, }); UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws } } fn main_events_cleared_transition(&mut self) -> HashSet> { let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() { AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => { (handler, queued_gpu_redraws, active_control_flow) }, s => bug!("unexpected state {:?}", s), }; self.set_state(AppStateImpl::ProcessingRedraws { handler, active_control_flow }); queued_gpu_redraws } fn events_cleared_transition(&mut self) { if !self.has_launched() || self.has_terminated() { return; } let (waiting_handler, old) = match self.take_state() { AppStateImpl::ProcessingRedraws { handler, active_control_flow } => { (handler, active_control_flow) }, s => bug!("unexpected state {:?}", s), }; let new = self.control_flow; match (old, new) { (ControlFlow::Wait, ControlFlow::Wait) => { let start = Instant::now(); self.set_state(AppStateImpl::Waiting { waiting_handler, start }); self.waker.stop() }, (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) if old_instant == new_instant => { let start = Instant::now(); self.set_state(AppStateImpl::Waiting { waiting_handler, start }); }, (_, ControlFlow::Wait) => { let start = Instant::now(); self.set_state(AppStateImpl::Waiting { waiting_handler, start }); self.waker.stop() }, (_, ControlFlow::WaitUntil(new_instant)) => { let start = Instant::now(); self.set_state(AppStateImpl::Waiting { waiting_handler, start }); self.waker.start_at(new_instant) }, // Unlike on macOS, handle Poll to Poll transition here to call the waker (_, ControlFlow::Poll) => { self.set_state(AppStateImpl::PollFinished { waiting_handler }); self.waker.start() }, } } fn terminated_transition(&mut self) -> EventLoopHandler { match self.replace_state(AppStateImpl::Terminated) { AppStateImpl::ProcessingEvents { handler, .. } => handler, s => bug!("`LoopExiting` happened while not processing events {:?}", s), } } pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) { self.control_flow = control_flow; } pub(crate) fn control_flow(&self) -> ControlFlow { self.control_flow } } pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Retained) { let mut this = AppState::get_mut(mtm); match this.state_mut() { &mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } => { return queued_windows.push(window.clone()) }, &mut AppStateImpl::ProcessingEvents { .. } | &mut AppStateImpl::InUserCallback { .. } | &mut AppStateImpl::ProcessingRedraws { .. } => {}, s @ &mut AppStateImpl::Launching { .. } | s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), &mut AppStateImpl::Terminated => { panic!("Attempt to create a `Window` after the app has terminated") }, } drop(this); window.makeKeyAndVisible(); } pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained) { let mut this = AppState::get_mut(mtm); match this.state_mut() { &mut AppStateImpl::NotLaunched { ref mut queued_gpu_redraws, .. } | &mut AppStateImpl::Launching { ref mut queued_gpu_redraws, .. } | &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } | &mut AppStateImpl::InUserCallback { ref mut queued_gpu_redraws, .. } => { let _ = queued_gpu_redraws.insert(window); }, s @ &mut AppStateImpl::ProcessingRedraws { .. } | s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), &mut AppStateImpl::Terminated => { panic!("Attempt to create a `Window` after the app has terminated") }, } } pub(crate) fn will_launch(mtm: MainThreadMarker, queued_handler: EventLoopHandler) { AppState::get_mut(mtm).will_launch_transition(queued_handler) } pub fn did_finish_launching(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); let windows = match this.state_mut() { AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), s => bug!("unexpected state {:?}", s), }; this.waker.start(); // have to drop RefMut because the window setup code below can trigger new events drop(this); for window in windows { // Do a little screen dance here to account for windows being created before // `UIApplicationMain` is called. This fixes visual issues such as being // offcenter and sized incorrectly. Additionally, to fix orientation issues, we // gotta reset the `rootViewController`. // // relevant iOS log: // ``` // [ApplicationLifecycle] Windows were created before application initialization // completed. This may result in incorrect visual appearance. // ``` let screen = window.screen(); let _: () = unsafe { msg_send![&window, setScreen: ptr::null::()] }; window.setScreen(&screen); let controller = window.rootViewController(); window.setRootViewController(None); window.setRootViewController(controller.as_deref()); window.makeKeyAndVisible(); } let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition(); let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(StartCause::Init))) .chain(events); handle_nonuser_events(mtm, events); // the above window dance hack, could possibly trigger new windows to be created. // we can just set those windows up normally, as they were created after didFinishLaunching for window in windows { window.makeKeyAndVisible(); } } // AppState::did_finish_launching handles the special transition `Init` pub fn handle_wakeup_transition(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); let wakeup_event = match this.wakeup_transition() { None => return, Some(wakeup_event) => wakeup_event, }; drop(this); handle_nonuser_event(mtm, wakeup_event) } pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) { handle_nonuser_events(mtm, std::iter::once(event)) } pub(crate) fn handle_nonuser_events>( mtm: MainThreadMarker, events: I, ) { let mut this = AppState::get_mut(mtm); if this.has_terminated() { return; } let (mut handler, active_control_flow, processing_redraws) = match this.try_user_callback_transition() { UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { queued_events.extend(events); return; }, UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws, } => (handler, active_control_flow, processing_redraws), }; drop(this); for wrapper in events { match wrapper { EventWrapper::StaticEvent(event) => { if !processing_redraws && event.is_redraw() { tracing::info!("processing `RedrawRequested` during the main event loop"); } else if processing_redraws && !event.is_redraw() { tracing::warn!( "processing non `RedrawRequested` event after the main event loop: {:#?}", event ); } handler.handle_event(event) }, EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event), } } loop { let mut this = AppState::get_mut(mtm); let queued_events = match this.state_mut() { &mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => { mem::take(queued_events) }, s => bug!("unexpected state {:?}", s), }; if queued_events.is_empty() { let queued_gpu_redraws = match this.take_state() { AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => { queued_gpu_redraws }, _ => unreachable!(), }; this.app_state = Some(if processing_redraws { bug_assert!( queued_gpu_redraws.is_empty(), "redraw queued while processing redraws" ); AppStateImpl::ProcessingRedraws { handler, active_control_flow } } else { AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } }); break; } drop(this); for wrapper in queued_events { match wrapper { EventWrapper::StaticEvent(event) => { if !processing_redraws && event.is_redraw() { tracing::info!("processing `RedrawRequested` during the main event loop"); } else if processing_redraws && !event.is_redraw() { tracing::warn!( "processing non-`RedrawRequested` event after the main event loop: \ {:#?}", event ); } handler.handle_event(event) }, EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event), } } } } fn handle_user_events(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); let (mut handler, active_control_flow, processing_redraws) = match this.try_user_callback_transition() { UserCallbackTransitionResult::ReentrancyPrevented { .. } => { bug!("unexpected attempted to process an event") }, UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws, } => (handler, active_control_flow, processing_redraws), }; if processing_redraws { bug!("user events attempted to be sent out while `ProcessingRedraws`"); } drop(this); handler.handle_event(Event::UserEvent(HandlePendingUserEvents)); loop { let mut this = AppState::get_mut(mtm); let queued_events = match this.state_mut() { &mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => { mem::take(queued_events) }, s => bug!("unexpected state {:?}", s), }; if queued_events.is_empty() { let queued_gpu_redraws = match this.take_state() { AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => { queued_gpu_redraws }, _ => unreachable!(), }; this.app_state = Some(AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow, }); break; } drop(this); for wrapper in queued_events { match wrapper { EventWrapper::StaticEvent(event) => handler.handle_event(event), EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event), } } handler.handle_event(Event::UserEvent(HandlePendingUserEvents)); } } pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, occluded: bool) { let mtm = MainThreadMarker::from(application); let mut events = Vec::new(); #[allow(deprecated)] for window in application.windows().iter() { if window.is_kind_of::() { // SAFETY: We just checked that the window is a `winit` window let window = unsafe { let ptr: *const UIWindow = window; let ptr: *const WinitUIWindow = ptr.cast(); &*ptr }; events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Occluded(occluded), })); } } handle_nonuser_events(mtm, events); } pub fn handle_main_events_cleared(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); if !this.has_launched() || this.has_terminated() { return; } match this.state_mut() { AppStateImpl::ProcessingEvents { .. } => {}, _ => bug!("`ProcessingRedraws` happened unexpectedly"), }; drop(this); handle_user_events(mtm); let mut this = AppState::get_mut(mtm); let redraw_events: Vec = this .main_events_cleared_transition() .into_iter() .map(|window| { EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::RedrawRequested, }) }) .collect(); drop(this); handle_nonuser_events(mtm, redraw_events); handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait)); } pub fn handle_events_cleared(mtm: MainThreadMarker) { AppState::get_mut(mtm).events_cleared_transition(); } pub(crate) fn terminated(application: &UIApplication) { let mtm = MainThreadMarker::from(application); let mut events = Vec::new(); #[allow(deprecated)] for window in application.windows().iter() { if window.is_kind_of::() { // SAFETY: We just checked that the window is a `winit` window let window = unsafe { let ptr: *const UIWindow = window; let ptr: *const WinitUIWindow = ptr.cast(); &*ptr }; events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Destroyed, })); } } handle_nonuser_events(mtm, events); let mut this = AppState::get_mut(mtm); let mut handler = this.terminated_transition(); drop(this); handler.handle_event(Event::LoopExiting) } fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) { let ScaleFactorChanged { suggested_size, scale_factor, window } = event; let new_inner_size = Arc::new(Mutex::new(suggested_size)); let event = Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), }, }; handler.handle_event(event); let (view, screen_frame) = get_view_and_screen_frame(&window); let physical_size = *new_inner_size.lock().unwrap(); drop(new_inner_size); let logical_size = physical_size.to_logical(scale_factor); let size = CGSize::new(logical_size.width, logical_size.height); let new_frame: CGRect = CGRect::new(screen_frame.origin, size); view.setFrame(new_frame); } fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained, CGRect) { let view_controller = window.rootViewController().unwrap(); let view = view_controller.view().unwrap(); let bounds = window.bounds(); let screen = window.screen(); let screen_space = screen.coordinateSpace(); let screen_frame = window.convertRect_toCoordinateSpace(bounds, &screen_space); (view, screen_frame) } struct EventLoopWaker { timer: CFRunLoopTimerRef, } impl Drop for EventLoopWaker { fn drop(&mut self) { unsafe { CFRunLoopTimerInvalidate(self.timer); CFRelease(self.timer as _); } } } impl EventLoopWaker { fn new(rl: CFRunLoopRef) -> EventLoopWaker { extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} unsafe { // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. // It is initially setup with a first fire time really far into the // future, but that gets changed to fire immediately in did_finish_launching let timer = CFRunLoopTimerCreate( ptr::null_mut(), f64::MAX, 0.000_000_1, 0, 0, wakeup_main_loop, ptr::null_mut(), ); CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes); EventLoopWaker { timer } } } fn stop(&mut self) { unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } } fn start(&mut self) { unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } } fn start_at(&mut self, instant: Instant) { let now = Instant::now(); if now >= instant { self.start(); } else { unsafe { let current = CFAbsoluteTimeGetCurrent(); let duration = instant - now; let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) } } } } macro_rules! os_capabilities { ( $( $(#[$attr:meta])* $error_name:ident: $objc_call:literal, $name:ident: $major:literal-$minor:literal ),* $(,)* ) => { #[derive(Clone, Debug)] pub struct OSCapabilities { $( pub $name: bool, )* os_version: NSOperatingSystemVersion, } impl OSCapabilities { fn from_os_version(os_version: NSOperatingSystemVersion) -> Self { $(let $name = meets_requirements(os_version, $major, $minor);)* Self { $($name,)* os_version, } } } impl OSCapabilities {$( $(#[$attr])* pub fn $error_name(&self, extra_msg: &str) { tracing::warn!( concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"), $major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion, extra_msg ) } )*} }; } os_capabilities! { /// #[allow(unused)] // error message unused safe_area_err_msg: "-[UIView safeAreaInsets]", safe_area: 11-0, /// home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]", home_indicator_hidden: 11-0, /// defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]", defer_system_gestures: 11-0, /// maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]", maximum_frames_per_second: 10-3, /// #[allow(unused)] // error message unused force_touch_err_msg: "-[UITouch force]", force_touch: 9-0, } fn meets_requirements( version: NSOperatingSystemVersion, required_major: NSInteger, required_minor: NSInteger, ) -> bool { (version.majorVersion, version.minorVersion) >= (required_major, required_minor) } fn get_version() -> NSOperatingSystemVersion { let process_info = NSProcessInfo::processInfo(); let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion)); // Winit requires atleast iOS 8 because no one has put the time into supporting earlier os // versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does // not support debugging on devices with an iOS version of less than 8. Another example, in // order to use an iOS simulator older than iOS 8, you must download an older version of Xcode // (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might? // // The minimum required iOS version is likely to grow in the future. assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater"); process_info.operatingSystemVersion() } pub fn os_capabilities() -> OSCapabilities { // Cache the version lookup for efficiency static OS_CAPABILITIES: OnceLock = OnceLock::new(); OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone() } winit-0.30.9/src/platform_impl/ios/event_loop.rs000064400000000000000000000320441046102023000200200ustar 00000000000000use std::collections::VecDeque; use std::ffi::{c_char, c_int, c_void}; use std::marker::PhantomData; use std::ptr::{self, NonNull}; use std::sync::mpsc::{self, Receiver, Sender}; use core_foundation::base::{CFIndex, CFRelease}; use core_foundation::runloop::{ kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode, kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, }; use objc2::rc::Retained; use objc2::{msg_send_id, ClassType}; use objc2_foundation::{MainThreadMarker, NSString}; use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom}; use crate::error::EventLoopError; use crate::event::Event; use crate::event_loop::{ ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed, }; use crate::platform::ios::Idiom; use crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents}; use crate::window::{CustomCursor, CustomCursorSource, Theme}; use super::app_delegate::AppDelegate; use super::app_state::AppState; use super::{app_state, monitor, MonitorHandle}; #[derive(Debug)] pub struct ActiveEventLoop { pub(super) mtm: MainThreadMarker, } impl ActiveEventLoop { pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor { let _ = source.inner; CustomCursor { inner: super::PlatformCustomCursor } } pub fn available_monitors(&self) -> VecDeque { monitor::uiscreens(self.mtm) } pub fn primary_monitor(&self) -> Option { #[allow(deprecated)] Some(MonitorHandle::new(UIScreen::mainScreen(self.mtm))) } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty()) } #[inline] pub fn system_theme(&self) -> Option { None } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new())) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { AppState::get_mut(self.mtm).set_control_flow(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { AppState::get_mut(self.mtm).control_flow() } pub(crate) fn exit(&self) { // https://developer.apple.com/library/archive/qa/qa1561/_index.html // it is not possible to quit an iOS app gracefully and programmatically tracing::warn!("`ControlFlow::Exit` ignored on iOS"); } pub(crate) fn exiting(&self) -> bool { false } pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle { OwnedDisplayHandle } } #[derive(Clone)] pub(crate) struct OwnedDisplayHandle; impl OwnedDisplayHandle { #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::UiKitDisplayHandle::empty().into() } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::UiKitDisplayHandle::new().into()) } } fn map_user_event( mut handler: impl FnMut(Event, &RootActiveEventLoop), receiver: mpsc::Receiver, ) -> impl FnMut(Event, &RootActiveEventLoop) { move |event, window_target| match event.map_nonuser_event() { Ok(event) => (handler)(event, window_target), Err(_) => { for event in receiver.try_iter() { (handler)(Event::UserEvent(event), window_target); } }, } } pub struct EventLoop { mtm: MainThreadMarker, sender: Sender, receiver: Receiver, window_target: RootActiveEventLoop, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} impl EventLoop { pub(crate) fn new( _: &PlatformSpecificEventLoopAttributes, ) -> Result, EventLoopError> { let mtm = MainThreadMarker::new() .expect("On iOS, `EventLoop` must be created on the main thread"); static mut SINGLETON_INIT: bool = false; unsafe { assert!( !SINGLETON_INIT, "Only one `EventLoop` is supported on iOS. `EventLoopProxy` might be helpful" ); SINGLETON_INIT = true; } let (sender, receiver) = mpsc::channel(); // this line sets up the main run loop before `UIApplicationMain` setup_control_flow_observers(); Ok(EventLoop { mtm, sender, receiver, window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData }, }) } pub fn run(self, handler: F) -> ! where F: FnMut(Event, &RootActiveEventLoop), { let application: Option> = unsafe { msg_send_id![UIApplication::class(), sharedApplication] }; assert!( application.is_none(), "\ `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\nNote: \ `EventLoop::run_app` calls `UIApplicationMain` on iOS", ); let handler = map_user_event(handler, self.receiver); let handler = unsafe { std::mem::transmute::< Box, &RootActiveEventLoop)>, Box, &RootActiveEventLoop)>, >(Box::new(handler)) }; let handler = EventLoopHandler { handler, event_loop: self.window_target }; app_state::will_launch(self.mtm, handler); // Ensure application delegate is initialized let _ = AppDelegate::class(); extern "C" { // These functions are in crt_externs.h. fn _NSGetArgc() -> *mut c_int; fn _NSGetArgv() -> *mut *mut *mut c_char; } unsafe { UIApplicationMain( *_NSGetArgc(), NonNull::new(*_NSGetArgv()).unwrap(), None, Some(&NSString::from_str(AppDelegate::NAME)), ) }; unreachable!() } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy::new(self.sender.clone()) } pub fn window_target(&self) -> &RootActiveEventLoop { &self.window_target } } // EventLoopExtIOS impl EventLoop { pub fn idiom(&self) -> Idiom { match UIDevice::currentDevice(self.mtm).userInterfaceIdiom() { UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified, UIUserInterfaceIdiom::Phone => Idiom::Phone, UIUserInterfaceIdiom::Pad => Idiom::Pad, UIUserInterfaceIdiom::TV => Idiom::TV, UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay, _ => Idiom::Unspecified, } } } pub struct EventLoopProxy { sender: Sender, source: CFRunLoopSourceRef, } unsafe impl Send for EventLoopProxy {} unsafe impl Sync for EventLoopProxy {} impl Clone for EventLoopProxy { fn clone(&self) -> EventLoopProxy { EventLoopProxy::new(self.sender.clone()) } } impl Drop for EventLoopProxy { fn drop(&mut self) { unsafe { CFRunLoopSourceInvalidate(self.source); CFRelease(self.source as _); } } } impl EventLoopProxy { fn new(sender: Sender) -> EventLoopProxy { unsafe { // just wake up the eventloop extern "C" fn event_loop_proxy_handler(_: *const c_void) {} // adding a Source to the main CFRunLoop lets us wake it up and // process user events through the normal OS EventLoop mechanisms. let rl = CFRunLoopGetMain(); let mut context = CFRunLoopSourceContext { version: 0, info: ptr::null_mut(), retain: None, release: None, copyDescription: None, equal: None, hash: None, schedule: None, cancel: None, perform: event_loop_proxy_handler, }; let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); CFRunLoopWakeUp(rl); EventLoopProxy { sender, source } } } pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.sender.send(event).map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?; unsafe { // let the main thread know there's a new event CFRunLoopSourceSignal(self.source); let rl = CFRunLoopGetMain(); CFRunLoopWakeUp(rl); } Ok(()) } } fn setup_control_flow_observers() { unsafe { // begin is queued with the highest priority to ensure it is processed before other // observers extern "C" fn control_flow_begin_handler( _: CFRunLoopObserverRef, activity: CFRunLoopActivity, _: *mut c_void, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm), _ => unreachable!(), } } // Core Animation registers its `CFRunLoopObserver` that performs drawing operations in // `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end // priority to be 0, in order to send AboutToWait before RedrawRequested. This value was // chosen conservatively to guard against apple using different priorities for their redraw // observers in different OS's or on different devices. If it so happens that it's too // conservative, the main symptom would be non-redraw events coming in after `AboutToWait`. // // The value of `0x1e8480` was determined by inspecting stack traces and the associated // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. // // Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4. extern "C" fn control_flow_main_end_handler( _: CFRunLoopObserverRef, activity: CFRunLoopActivity, _: *mut c_void, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm), kCFRunLoopExit => {}, // may happen when running on macOS _ => unreachable!(), } } // end is queued with the lowest priority to ensure it is processed after other observers extern "C" fn control_flow_end_handler( _: CFRunLoopObserverRef, activity: CFRunLoopActivity, _: *mut c_void, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm), kCFRunLoopExit => {}, // may happen when running on macOS _ => unreachable!(), } } let main_loop = CFRunLoopGetMain(); let begin_observer = CFRunLoopObserverCreate( ptr::null_mut(), kCFRunLoopAfterWaiting, 1, // repeat = true CFIndex::MIN, control_flow_begin_handler, ptr::null_mut(), ); CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode); let main_end_observer = CFRunLoopObserverCreate( ptr::null_mut(), kCFRunLoopExit | kCFRunLoopBeforeWaiting, 1, // repeat = true 0, // see comment on `control_flow_main_end_handler` control_flow_main_end_handler, ptr::null_mut(), ); CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode); let end_observer = CFRunLoopObserverCreate( ptr::null_mut(), kCFRunLoopExit | kCFRunLoopBeforeWaiting, 1, // repeat = true CFIndex::MAX, control_flow_end_handler, ptr::null_mut(), ); CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode); } } winit-0.30.9/src/platform_impl/ios/mod.rs000064400000000000000000000027161046102023000164300ustar 00000000000000#![allow(clippy::let_unit_value)] mod app_delegate; mod app_state; mod event_loop; mod monitor; mod view; mod view_controller; mod window; use std::fmt; use crate::event::DeviceId as RootDeviceId; pub(crate) use self::event_loop::{ ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle, PlatformSpecificEventLoopAttributes, }; pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle}; pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window, WindowId}; pub(crate) use crate::cursor::{ NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource, }; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; /// There is no way to detect which device that performed a certain event in /// UIKit (i.e. you can't differentiate between different external keyboards, /// or whether it was the main touchscreen, assistive technologies, or some /// other pointer device that caused a touch event). #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; impl DeviceId { pub const fn dummy() -> Self { DeviceId } } pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct KeyEventExtra {} #[derive(Debug)] pub enum OsError {} impl fmt::Display for OsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "os error") } } winit-0.30.9/src/platform_impl/ios/monitor.rs000064400000000000000000000222171046102023000173360ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::collections::{BTreeSet, VecDeque}; use std::{fmt, hash, ptr}; use objc2::mutability::IsRetainable; use objc2::rc::Retained; use objc2::Message; use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger}; use objc2_ui_kit::{UIScreen, UIScreenMode}; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::monitor::VideoModeHandle as RootVideoModeHandle; use crate::platform_impl::platform::app_state; // Workaround for `MainThreadBound` implementing almost no traits #[derive(Debug)] struct MainThreadBoundDelegateImpls(MainThreadBound>); impl Clone for MainThreadBoundDelegateImpls { fn clone(&self) -> Self { Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm))) } } impl hash::Hash for MainThreadBoundDelegateImpls { fn hash(&self, state: &mut H) { // SAFETY: Marker only used to get the pointer let mtm = unsafe { MainThreadMarker::new_unchecked() }; Retained::as_ptr(self.0.get(mtm)).hash(state); } } impl PartialEq for MainThreadBoundDelegateImpls { fn eq(&self, other: &Self) -> bool { // SAFETY: Marker only used to get the pointer let mtm = unsafe { MainThreadMarker::new_unchecked() }; Retained::as_ptr(self.0.get(mtm)) == Retained::as_ptr(other.0.get(mtm)) } } impl Eq for MainThreadBoundDelegateImpls {} #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct VideoModeHandle { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, screen_mode: MainThreadBoundDelegateImpls, pub(crate) monitor: MonitorHandle, } impl VideoModeHandle { fn new( uiscreen: Retained, screen_mode: Retained, mtm: MainThreadMarker, ) -> VideoModeHandle { let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen); let size = screen_mode.size(); VideoModeHandle { size: (size.width as u32, size.height as u32), bit_depth: 32, refresh_rate_millihertz, screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)), monitor: MonitorHandle::new(uiscreen), } } pub fn size(&self) -> PhysicalSize { self.size.into() } pub fn bit_depth(&self) -> u16 { self.bit_depth } pub fn refresh_rate_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Retained { self.screen_mode.0.get(mtm) } } pub struct MonitorHandle { ui_screen: MainThreadBound>, } impl Clone for MonitorHandle { fn clone(&self) -> Self { run_on_main(|mtm| Self { ui_screen: MainThreadBound::new(self.ui_screen.get(mtm).clone(), mtm), }) } } impl hash::Hash for MonitorHandle { fn hash(&self, state: &mut H) { // SAFETY: Only getting the pointer. let mtm = unsafe { MainThreadMarker::new_unchecked() }; Retained::as_ptr(self.ui_screen.get(mtm)).hash(state); } } impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { // SAFETY: Only getting the pointer. let mtm = unsafe { MainThreadMarker::new_unchecked() }; ptr::eq( Retained::as_ptr(self.ui_screen.get(mtm)), Retained::as_ptr(other.ui_screen.get(mtm)), ) } } impl Eq for MonitorHandle {} impl PartialOrd for MonitorHandle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for MonitorHandle { fn cmp(&self, other: &Self) -> std::cmp::Ordering { // SAFETY: Only getting the pointer. // TODO: Make a better ordering let mtm = unsafe { MainThreadMarker::new_unchecked() }; Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm))) } } impl fmt::Debug for MonitorHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MonitorHandle") .field("name", &self.name()) .field("size", &self.size()) .field("position", &self.position()) .field("scale_factor", &self.scale_factor()) .field("refresh_rate_millihertz", &self.refresh_rate_millihertz()) .finish_non_exhaustive() } } impl MonitorHandle { pub(crate) fn new(ui_screen: Retained) -> Self { // Holding `Retained` implies we're on the main thread. let mtm = MainThreadMarker::new().unwrap(); Self { ui_screen: MainThreadBound::new(ui_screen, mtm) } } pub fn name(&self) -> Option { run_on_main(|mtm| { #[allow(deprecated)] let main = UIScreen::mainScreen(mtm); if *self.ui_screen(mtm) == main { Some("Primary".to_string()) } else if Some(self.ui_screen(mtm)) == main.mirroredScreen().as_ref() { Some("Mirrored".to_string()) } else { #[allow(deprecated)] UIScreen::screens(mtm) .iter() .position(|rhs| rhs == &**self.ui_screen(mtm)) .map(|idx| idx.to_string()) } }) } pub fn size(&self) -> PhysicalSize { let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds()); PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) } pub fn position(&self) -> PhysicalPosition { let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds()); (bounds.origin.x as f64, bounds.origin.y as f64).into() } pub fn scale_factor(&self) -> f64 { self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64 } pub fn refresh_rate_millihertz(&self) -> Option { Some(self.ui_screen.get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen))) } pub fn video_modes(&self) -> impl Iterator { run_on_main(|mtm| { let ui_screen = self.ui_screen(mtm); // Use Ord impl of RootVideoModeHandle let modes: BTreeSet<_> = ui_screen .availableModes() .into_iter() .map(|mode| RootVideoModeHandle { video_mode: VideoModeHandle::new(ui_screen.clone(), mode, mtm), }) .collect(); modes.into_iter().map(|mode| mode.video_mode) }) } pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Retained { self.ui_screen.get(mtm) } pub fn preferred_video_mode(&self) -> VideoModeHandle { run_on_main(|mtm| { VideoModeHandle::new( self.ui_screen(mtm).clone(), self.ui_screen(mtm).preferredMode().unwrap(), mtm, ) }) } } fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 { let refresh_rate_millihertz: NSInteger = { let os_capabilities = app_state::os_capabilities(); if os_capabilities.maximum_frames_per_second { uiscreen.maximumFramesPerSecond() } else { // https://developer.apple.com/library/archive/technotes/tn2460/_index.html // https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison // // All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not // supported, they are all guaranteed to have 60hz refresh rates. This does not // correctly handle external displays. ProMotion displays support 120fps, but they were // introduced at the same time as the `maximumFramesPerSecond` API. // // FIXME: earlier OSs could calculate the refresh rate using // `-[CADisplayLink duration]`. os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps"); 60 } }; refresh_rate_millihertz as u32 * 1000 } pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque { #[allow(deprecated)] UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect() } #[cfg(test)] mod tests { use objc2_foundation::NSSet; use super::*; // Test that UIScreen pointer comparisons are correct. #[test] #[allow(deprecated)] fn screen_comparisons() { // Test code, doesn't matter that it's not thread safe let mtm = unsafe { MainThreadMarker::new_unchecked() }; assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm))); let main = UIScreen::mainScreen(mtm); assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main))); assert!(unsafe { NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm)) }); } } winit-0.30.9/src/platform_impl/ios/view.rs000064400000000000000000000614621046102023000166260ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::cell::{Cell, RefCell}; use objc2::rc::Retained; use objc2::runtime::{NSObjectProtocol, ProtocolObject}; use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass}; use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString}; use objc2_ui_kit::{ UICoordinateSpace, UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITextInputTraits, UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView, }; use super::app_state::{self, EventWrapper}; use super::window::WinitUIWindow; use crate::dpi::PhysicalPosition; use crate::event::{ElementState, Event, Force, KeyEvent, Touch, TouchPhase, WindowEvent}; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey}; use crate::platform_impl::platform::DEVICE_ID; use crate::platform_impl::KeyEventExtra; use crate::window::{WindowAttributes, WindowId as RootWindowId}; pub struct WinitViewState { pinch_gesture_recognizer: RefCell>>, doubletap_gesture_recognizer: RefCell>>, rotation_gesture_recognizer: RefCell>>, pan_gesture_recognizer: RefCell>>, // for iOS delta references the start of the Gesture rotation_last_delta: Cell, pinch_last_delta: Cell, pan_last_delta: Cell, } declare_class!( pub(crate) struct WinitView; unsafe impl ClassType for WinitView { #[inherits(UIResponder, NSObject)] type Super = UIView; type Mutability = mutability::MainThreadOnly; const NAME: &'static str = "WinitUIView"; } impl DeclaredClass for WinitView { type Ivars = WinitViewState; } unsafe impl WinitView { #[method(drawRect:)] fn draw_rect(&self, rect: CGRect) { let mtm = MainThreadMarker::new().unwrap(); let window = self.window().unwrap(); app_state::handle_nonuser_event( mtm, EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::RedrawRequested, }), ); let _: () = unsafe { msg_send![super(self), drawRect: rect] }; } #[method(layoutSubviews)] fn layout_subviews(&self) { let mtm = MainThreadMarker::new().unwrap(); let _: () = unsafe { msg_send![super(self), layoutSubviews] }; let window = self.window().unwrap(); let window_bounds = window.bounds(); let screen = window.screen(); let screen_space = screen.coordinateSpace(); let screen_frame = self.convertRect_toCoordinateSpace(window_bounds, &screen_space); let scale_factor = screen.scale(); let size = crate::dpi::LogicalSize { width: screen_frame.size.width as f64, height: screen_frame.size.height as f64, } .to_physical(scale_factor as f64); // If the app is started in landscape, the view frame and window bounds can be mismatched. // The view frame will be in portrait and the window bounds in landscape. So apply the // window bounds to the view frame to make it consistent. let view_frame = self.frame(); if view_frame != window_bounds { self.setFrame(window_bounds); } app_state::handle_nonuser_event( mtm, EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Resized(size), }), ); } #[method(setContentScaleFactor:)] fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) { let mtm = MainThreadMarker::new().unwrap(); let _: () = unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] }; // `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow // makeKeyAndVisible]` at window creation time (either manually or internally by // UIKit when the `UIView` is first created), in which case we send no events here let window = match self.window() { Some(window) => window, None => return, }; // `setContentScaleFactor` may be called with a value of 0, which means "reset the // content scale factor to a device-specific default value", so we can't use the // parameter here. We can query the actual factor using the getter let scale_factor = self.contentScaleFactor(); assert!( !scale_factor.is_nan() && scale_factor.is_finite() && scale_factor.is_sign_positive() && scale_factor > 0.0, "invalid scale_factor set on UIView", ); let scale_factor = scale_factor as f64; let bounds = self.bounds(); let screen = window.screen(); let screen_space = screen.coordinateSpace(); let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space); let size = crate::dpi::LogicalSize { width: screen_frame.size.width as f64, height: screen_frame.size.height as f64, }; let window_id = RootWindowId(window.id()); app_state::handle_nonuser_events( mtm, std::iter::once(EventWrapper::ScaleFactorChanged( app_state::ScaleFactorChanged { window, scale_factor, suggested_size: size.to_physical(scale_factor), }, )) .chain(std::iter::once(EventWrapper::StaticEvent( Event::WindowEvent { window_id, event: WindowEvent::Resized(size.to_physical(scale_factor)), }, ))), ); } #[method(touchesBegan:withEvent:)] fn touches_began(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } #[method(touchesMoved:withEvent:)] fn touches_moved(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } #[method(touchesEnded:withEvent:)] fn touches_ended(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } #[method(touchesCancelled:withEvent:)] fn touches_cancelled(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } #[method(pinchGesture:)] fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) { let window = self.window().unwrap(); let (phase, delta) = match recognizer.state() { UIGestureRecognizerState::Began => { self.ivars().pinch_last_delta.set(recognizer.scale()); (TouchPhase::Started, 0.0) } UIGestureRecognizerState::Changed => { let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale()); (TouchPhase::Moved, recognizer.scale() - last_scale) } UIGestureRecognizerState::Ended => { let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0); (TouchPhase::Moved, recognizer.scale() - last_scale) } UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { self.ivars().rotation_last_delta.set(0.0); // Pass -delta so that action is reversed (TouchPhase::Cancelled, -recognizer.scale()) } state => panic!("unexpected recognizer state: {:?}", state), }; let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::PinchGesture { device_id: DEVICE_ID, delta: delta as f64, phase, }, }); let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, gesture_event); } #[method(doubleTapGesture:)] fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) { let window = self.window().unwrap(); if recognizer.state() == UIGestureRecognizerState::Ended { let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::DoubleTapGesture { device_id: DEVICE_ID, }, }); let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, gesture_event); } } #[method(rotationGesture:)] fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) { let window = self.window().unwrap(); let (phase, delta) = match recognizer.state() { UIGestureRecognizerState::Began => { self.ivars().rotation_last_delta.set(0.0); (TouchPhase::Started, 0.0) } UIGestureRecognizerState::Changed => { let last_rotation = self.ivars().rotation_last_delta.replace(recognizer.rotation()); (TouchPhase::Moved, recognizer.rotation() - last_rotation) } UIGestureRecognizerState::Ended => { let last_rotation = self.ivars().rotation_last_delta.replace(0.0); (TouchPhase::Ended, recognizer.rotation() - last_rotation) } UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { self.ivars().rotation_last_delta.set(0.0); // Pass -delta so that action is reversed (TouchPhase::Cancelled, -recognizer.rotation()) } state => panic!("unexpected recognizer state: {:?}", state), }; // Make delta negative to match macos, convert to degrees let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::RotationGesture { device_id: DEVICE_ID, delta: -delta.to_degrees() as _, phase, }, }); let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, gesture_event); } #[method(panGesture:)] fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) { let window = self.window().unwrap(); let translation = recognizer.translationInView(Some(self)); let (phase, dx, dy) = match recognizer.state() { UIGestureRecognizerState::Began => { self.ivars().pan_last_delta.set(translation); (TouchPhase::Started, 0.0, 0.0) } UIGestureRecognizerState::Changed => { let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation); let dx = translation.x - last_pan.x; let dy = translation.y - last_pan.y; (TouchPhase::Moved, dx, dy) } UIGestureRecognizerState::Ended => { let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0}); let dx = translation.x - last_pan.x; let dy = translation.y - last_pan.y; (TouchPhase::Ended, dx, dy) } UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0}); // Pass -delta so that action is reversed (TouchPhase::Cancelled, -last_pan.x, -last_pan.y) } state => panic!("unexpected recognizer state: {:?}", state), }; let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::PanGesture { device_id: DEVICE_ID, delta: PhysicalPosition::new(dx as _, dy as _), phase, }, }); let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, gesture_event); } #[method(canBecomeFirstResponder)] fn can_become_first_responder(&self) -> bool { true } } unsafe impl NSObjectProtocol for WinitView {} unsafe impl UIGestureRecognizerDelegate for WinitView { #[method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)] fn should_recognize_simultaneously(&self, _gesture_recognizer: &UIGestureRecognizer, _other_gesture_recognizer: &UIGestureRecognizer) -> bool { true } } unsafe impl UITextInputTraits for WinitView { } unsafe impl UIKeyInput for WinitView { #[method(hasText)] fn has_text(&self) -> bool { true } #[method(insertText:)] fn insert_text(&self, text: &NSString) { self.handle_insert_text(text) } #[method(deleteBackward)] fn delete_backward(&self) { self.handle_delete_backward() } } ); impl WinitView { pub(crate) fn new( mtm: MainThreadMarker, window_attributes: &WindowAttributes, frame: CGRect, ) -> Retained { let this = mtm.alloc().set_ivars(WinitViewState { pinch_gesture_recognizer: RefCell::new(None), doubletap_gesture_recognizer: RefCell::new(None), rotation_gesture_recognizer: RefCell::new(None), pan_gesture_recognizer: RefCell::new(None), rotation_last_delta: Cell::new(0.0), pinch_last_delta: Cell::new(0.0), pan_last_delta: Cell::new(CGPoint { x: 0.0, y: 0.0 }), }); let this: Retained = unsafe { msg_send_id![super(this), initWithFrame: frame] }; this.setMultipleTouchEnabled(true); if let Some(scale_factor) = window_attributes.platform_specific.scale_factor { this.setContentScaleFactor(scale_factor as _); } this } fn window(&self) -> Option> { // SAFETY: `WinitView`s are always installed in a `WinitUIWindow` (**self).window().map(|window| unsafe { Retained::cast(window) }) } pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) { let mtm = MainThreadMarker::from(self); if should_recognize { if self.ivars().pinch_gesture_recognizer.borrow().is_none() { let pinch = unsafe { UIPinchGestureRecognizer::initWithTarget_action( mtm.alloc(), Some(self), Some(sel!(pinchGesture:)), ) }; pinch.setDelegate(Some(ProtocolObject::from_ref(self))); self.addGestureRecognizer(&pinch); self.ivars().pinch_gesture_recognizer.replace(Some(pinch)); } } else if let Some(recognizer) = self.ivars().pinch_gesture_recognizer.take() { self.removeGestureRecognizer(&recognizer); } } pub(crate) fn recognize_pan_gesture( &self, should_recognize: bool, minimum_number_of_touches: u8, maximum_number_of_touches: u8, ) { let mtm = MainThreadMarker::from(self); if should_recognize { if self.ivars().pan_gesture_recognizer.borrow().is_none() { let pan = unsafe { UIPanGestureRecognizer::initWithTarget_action( mtm.alloc(), Some(self), Some(sel!(panGesture:)), ) }; pan.setDelegate(Some(ProtocolObject::from_ref(self))); pan.setMinimumNumberOfTouches(minimum_number_of_touches as _); pan.setMaximumNumberOfTouches(maximum_number_of_touches as _); self.addGestureRecognizer(&pan); self.ivars().pan_gesture_recognizer.replace(Some(pan)); } } else if let Some(recognizer) = self.ivars().pan_gesture_recognizer.take() { self.removeGestureRecognizer(&recognizer); } } pub(crate) fn recognize_doubletap_gesture(&self, should_recognize: bool) { let mtm = MainThreadMarker::from(self); if should_recognize { if self.ivars().doubletap_gesture_recognizer.borrow().is_none() { let tap = unsafe { UITapGestureRecognizer::initWithTarget_action( mtm.alloc(), Some(self), Some(sel!(doubleTapGesture:)), ) }; tap.setDelegate(Some(ProtocolObject::from_ref(self))); tap.setNumberOfTapsRequired(2); tap.setNumberOfTouchesRequired(1); self.addGestureRecognizer(&tap); self.ivars().doubletap_gesture_recognizer.replace(Some(tap)); } } else if let Some(recognizer) = self.ivars().doubletap_gesture_recognizer.take() { self.removeGestureRecognizer(&recognizer); } } pub(crate) fn recognize_rotation_gesture(&self, should_recognize: bool) { let mtm = MainThreadMarker::from(self); if should_recognize { if self.ivars().rotation_gesture_recognizer.borrow().is_none() { let rotation = unsafe { UIRotationGestureRecognizer::initWithTarget_action( mtm.alloc(), Some(self), Some(sel!(rotationGesture:)), ) }; rotation.setDelegate(Some(ProtocolObject::from_ref(self))); self.addGestureRecognizer(&rotation); self.ivars().rotation_gesture_recognizer.replace(Some(rotation)); } } else if let Some(recognizer) = self.ivars().rotation_gesture_recognizer.take() { self.removeGestureRecognizer(&recognizer); } } fn handle_touches(&self, touches: &NSSet) { let window = self.window().unwrap(); let mut touch_events = Vec::new(); let os_supports_force = app_state::os_capabilities().force_touch; for touch in touches { let logical_location = touch.locationInView(None); let touch_type = touch.r#type(); let force = if os_supports_force { let trait_collection = self.traitCollection(); let touch_capability = trait_collection.forceTouchCapability(); // Both the OS _and_ the device need to be checked for force touch support. if touch_capability == UIForceTouchCapability::Available || touch_type == UITouchType::Pencil { let force = touch.force(); let max_possible_force = touch.maximumPossibleForce(); let altitude_angle: Option = if touch_type == UITouchType::Pencil { let angle = touch.altitudeAngle(); Some(angle as _) } else { None }; Some(Force::Calibrated { force: force as _, max_possible_force: max_possible_force as _, altitude_angle, }) } else { None } } else { None }; let touch_id = touch as *const UITouch as u64; let phase = touch.phase(); let phase = match phase { UITouchPhase::Began => TouchPhase::Started, UITouchPhase::Moved => TouchPhase::Moved, // 2 is UITouchPhase::Stationary and is not expected here UITouchPhase::Ended => TouchPhase::Ended, UITouchPhase::Cancelled => TouchPhase::Cancelled, _ => panic!("unexpected touch phase: {phase:?}"), }; let physical_location = { let scale_factor = self.contentScaleFactor(); PhysicalPosition::from_logical::<(f64, f64), f64>( (logical_location.x as _, logical_location.y as _), scale_factor as f64, ) }; touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Touch(Touch { device_id: DEVICE_ID, id: touch_id, location: physical_location, force, phase, }), })); } let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_events(mtm, touch_events); } fn handle_insert_text(&self, text: &NSString) { let window = self.window().unwrap(); let window_id = RootWindowId(window.id()); let mtm = MainThreadMarker::new().unwrap(); // send individual events for each character app_state::handle_nonuser_events( mtm, text.to_string().chars().flat_map(|c| { let text = smol_str::SmolStr::from_iter([c]); // Emit both press and release events [ElementState::Pressed, ElementState::Released].map(|state| { EventWrapper::StaticEvent(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { event: KeyEvent { text: if state == ElementState::Pressed { Some(text.clone()) } else { None }, state, location: KeyLocation::Standard, repeat: false, logical_key: Key::Character(text.clone()), physical_key: PhysicalKey::Unidentified( NativeKeyCode::Unidentified, ), platform_specific: KeyEventExtra {}, }, is_synthetic: false, device_id: DEVICE_ID, }, }) }) }), ); } fn handle_delete_backward(&self) { let window = self.window().unwrap(); let window_id = RootWindowId(window.id()); let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_events( mtm, [ElementState::Pressed, ElementState::Released].map(|state| { EventWrapper::StaticEvent(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { device_id: DEVICE_ID, event: KeyEvent { state, logical_key: Key::Named(NamedKey::Backspace), physical_key: PhysicalKey::Code(KeyCode::Backspace), platform_specific: KeyEventExtra {}, repeat: false, location: KeyLocation::Standard, text: None, }, is_synthetic: false, }, }) }), ); } } winit-0.30.9/src/platform_impl/ios/view_controller.rs000064400000000000000000000151101046102023000210560ustar 00000000000000use std::cell::Cell; use objc2::rc::Retained; use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; use objc2_foundation::{MainThreadMarker, NSObject}; use objc2_ui_kit::{ UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle, UIUserInterfaceIdiom, UIView, UIViewController, }; use super::app_state::{self}; use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}; use crate::window::WindowAttributes; pub struct ViewControllerState { prefers_status_bar_hidden: Cell, preferred_status_bar_style: Cell, prefers_home_indicator_auto_hidden: Cell, supported_orientations: Cell, preferred_screen_edges_deferring_system_gestures: Cell, } declare_class!( pub(crate) struct WinitViewController; unsafe impl ClassType for WinitViewController { #[inherits(UIResponder, NSObject)] type Super = UIViewController; type Mutability = mutability::MainThreadOnly; const NAME: &'static str = "WinitUIViewController"; } impl DeclaredClass for WinitViewController { type Ivars = ViewControllerState; } unsafe impl WinitViewController { #[method(shouldAutorotate)] fn should_autorotate(&self) -> bool { true } #[method(prefersStatusBarHidden)] fn prefers_status_bar_hidden(&self) -> bool { self.ivars().prefers_status_bar_hidden.get() } #[method(preferredStatusBarStyle)] fn preferred_status_bar_style(&self) -> UIStatusBarStyle { self.ivars().preferred_status_bar_style.get() } #[method(prefersHomeIndicatorAutoHidden)] fn prefers_home_indicator_auto_hidden(&self) -> bool { self.ivars().prefers_home_indicator_auto_hidden.get() } #[method(supportedInterfaceOrientations)] fn supported_orientations(&self) -> UIInterfaceOrientationMask { self.ivars().supported_orientations.get() } #[method(preferredScreenEdgesDeferringSystemGestures)] fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge { self.ivars() .preferred_screen_edges_deferring_system_gestures .get() } } ); impl WinitViewController { pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) { self.ivars().prefers_status_bar_hidden.set(val); self.setNeedsStatusBarAppearanceUpdate(); } pub(crate) fn set_preferred_status_bar_style(&self, val: StatusBarStyle) { let val = match val { StatusBarStyle::Default => UIStatusBarStyle::Default, StatusBarStyle::LightContent => UIStatusBarStyle::LightContent, StatusBarStyle::DarkContent => UIStatusBarStyle::DarkContent, }; self.ivars().preferred_status_bar_style.set(val); self.setNeedsStatusBarAppearanceUpdate(); } pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) { self.ivars().prefers_home_indicator_auto_hidden.set(val); let os_capabilities = app_state::os_capabilities(); if os_capabilities.home_indicator_hidden { self.setNeedsUpdateOfHomeIndicatorAutoHidden(); } else { os_capabilities.home_indicator_hidden_err_msg("ignoring") } } pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: ScreenEdge) { let val = { assert_eq!(val.bits() & !ScreenEdge::ALL.bits(), 0, "invalid `ScreenEdge`"); UIRectEdge(val.bits().into()) }; self.ivars().preferred_screen_edges_deferring_system_gestures.set(val); let os_capabilities = app_state::os_capabilities(); if os_capabilities.defer_system_gestures { self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures(); } else { os_capabilities.defer_system_gestures_err_msg("ignoring") } } pub(crate) fn set_supported_interface_orientations( &self, mtm: MainThreadMarker, valid_orientations: ValidOrientations, ) { let mask = match (valid_orientations, UIDevice::currentDevice(mtm).userInterfaceIdiom()) { (ValidOrientations::LandscapeAndPortrait, UIUserInterfaceIdiom::Phone) => { UIInterfaceOrientationMask::AllButUpsideDown }, (ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All, (ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape, (ValidOrientations::Portrait, UIUserInterfaceIdiom::Phone) => { UIInterfaceOrientationMask::Portrait }, (ValidOrientations::Portrait, _) => { UIInterfaceOrientationMask::Portrait | UIInterfaceOrientationMask::PortraitUpsideDown }, }; self.ivars().supported_orientations.set(mask); #[allow(deprecated)] UIViewController::attemptRotationToDeviceOrientation(mtm); } pub(crate) fn new( mtm: MainThreadMarker, window_attributes: &WindowAttributes, view: &UIView, ) -> Retained { // These are set properly below, we just to set them to something in the meantime. let this = mtm.alloc().set_ivars(ViewControllerState { prefers_status_bar_hidden: Cell::new(false), preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default), prefers_home_indicator_auto_hidden: Cell::new(false), supported_orientations: Cell::new(UIInterfaceOrientationMask::All), preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()), }); let this: Retained = unsafe { msg_send_id![super(this), init] }; this.set_prefers_status_bar_hidden( window_attributes.platform_specific.prefers_status_bar_hidden, ); this.set_preferred_status_bar_style( window_attributes.platform_specific.preferred_status_bar_style, ); this.set_supported_interface_orientations( mtm, window_attributes.platform_specific.valid_orientations, ); this.set_prefers_home_indicator_auto_hidden( window_attributes.platform_specific.prefers_home_indicator_hidden, ); this.set_preferred_screen_edges_deferring_system_gestures( window_attributes.platform_specific.preferred_screen_edges_deferring_system_gestures, ); this.setView(Some(view)); this } } winit-0.30.9/src/platform_impl/ios/window.rs000064400000000000000000000632621046102023000171630ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::collections::VecDeque; use objc2::rc::Retained; use objc2::runtime::{AnyObject, NSObject}; use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass}; use objc2_foundation::{ CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObjectProtocol, }; use objc2_ui_kit::{ UIApplication, UICoordinateSpace, UIResponder, UIScreen, UIScreenOverscanCompensation, UIViewController, UIWindow, }; use tracing::{debug, warn}; use super::app_state::EventWrapper; use super::view::WinitView; use super::view_controller::WinitViewController; use crate::cursor::Cursor; use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::event::{Event, WindowEvent}; use crate::icon::Icon; use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}; use crate::platform_impl::platform::{ app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle, }; use crate::window::{ CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel, }; declare_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct WinitUIWindow; unsafe impl ClassType for WinitUIWindow { #[inherits(UIResponder, NSObject)] type Super = UIWindow; type Mutability = mutability::MainThreadOnly; const NAME: &'static str = "WinitUIWindow"; } impl DeclaredClass for WinitUIWindow {} unsafe impl WinitUIWindow { #[method(becomeKeyWindow)] fn become_key_window(&self) { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event( mtm, EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(self.id()), event: WindowEvent::Focused(true), }), ); let _: () = unsafe { msg_send![super(self), becomeKeyWindow] }; } #[method(resignKeyWindow)] fn resign_key_window(&self) { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event( mtm, EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(self.id()), event: WindowEvent::Focused(false), }), ); let _: () = unsafe { msg_send![super(self), resignKeyWindow] }; } } ); impl WinitUIWindow { pub(crate) fn new( mtm: MainThreadMarker, window_attributes: &WindowAttributes, frame: CGRect, view_controller: &UIViewController, ) -> Retained { let this: Retained = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] }; this.setRootViewController(Some(view_controller)); match window_attributes.fullscreen.clone().map(Into::into) { Some(Fullscreen::Exclusive(ref video_mode)) => { let monitor = video_mode.monitor(); let screen = monitor.ui_screen(mtm); screen.setCurrentMode(Some(video_mode.screen_mode(mtm))); this.setScreen(screen); }, Some(Fullscreen::Borderless(Some(ref monitor))) => { let screen = monitor.ui_screen(mtm); this.setScreen(screen); }, _ => (), } this } pub(crate) fn id(&self) -> WindowId { (self as *const Self as usize as u64).into() } } pub struct Inner { window: Retained, view_controller: Retained, view: Retained, gl_or_metal_backed: bool, } impl Inner { pub fn set_title(&self, _title: &str) { debug!("`Window::set_title` is ignored on iOS") } pub fn set_transparent(&self, _transparent: bool) { debug!("`Window::set_transparent` is ignored on iOS") } pub fn set_blur(&self, _blur: bool) { debug!("`Window::set_blur` is ignored on iOS") } pub fn set_visible(&self, visible: bool) { self.window.setHidden(!visible) } pub fn is_visible(&self) -> Option { warn!("`Window::is_visible` is ignored on iOS"); None } pub fn request_redraw(&self) { if self.gl_or_metal_backed { let mtm = MainThreadMarker::new().unwrap(); // `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or // CAMetalLayer. Ordinarily the OS sets up a bunch of UIKit state before // calling drawRect: on a UIView, but when using raw or gl/metal for drawing // this work is completely avoided. // // The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been // confirmed via testing. // // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc app_state::queue_gl_or_metal_redraw(mtm, self.window.clone()); } else { self.view.setNeedsDisplay(); } } pub fn pre_present_notify(&self) {} pub fn inner_position(&self) -> Result, NotSupportedError> { let safe_area = self.safe_area_screen_space(); let position = LogicalPosition { x: safe_area.origin.x as f64, y: safe_area.origin.y as f64 }; let scale_factor = self.scale_factor(); Ok(position.to_physical(scale_factor)) } pub fn outer_position(&self) -> Result, NotSupportedError> { let screen_frame = self.screen_frame(); let position = LogicalPosition { x: screen_frame.origin.x as f64, y: screen_frame.origin.y as f64 }; let scale_factor = self.scale_factor(); Ok(position.to_physical(scale_factor)) } pub fn set_outer_position(&self, physical_position: Position) { let scale_factor = self.scale_factor(); let position = physical_position.to_logical::(scale_factor); let screen_frame = self.screen_frame(); let new_screen_frame = CGRect { origin: CGPoint { x: position.x as _, y: position.y as _ }, size: screen_frame.size, }; let bounds = self.rect_from_screen_space(new_screen_frame); self.window.setBounds(bounds); } pub fn inner_size(&self) -> PhysicalSize { let scale_factor = self.scale_factor(); let safe_area = self.safe_area_screen_space(); let size = LogicalSize { width: safe_area.size.width as f64, height: safe_area.size.height as f64, }; size.to_physical(scale_factor) } pub fn outer_size(&self) -> PhysicalSize { let scale_factor = self.scale_factor(); let screen_frame = self.screen_frame(); let size = LogicalSize { width: screen_frame.size.width as f64, height: screen_frame.size.height as f64, }; size.to_physical(scale_factor) } pub fn request_inner_size(&self, _size: Size) -> Option> { Some(self.inner_size()) } pub fn set_min_inner_size(&self, _dimensions: Option) { warn!("`Window::set_min_inner_size` is ignored on iOS") } pub fn set_max_inner_size(&self, _dimensions: Option) { warn!("`Window::set_max_inner_size` is ignored on iOS") } pub fn resize_increments(&self) -> Option> { None } #[inline] pub fn set_resize_increments(&self, _increments: Option) { warn!("`Window::set_resize_increments` is ignored on iOS") } pub fn set_resizable(&self, _resizable: bool) { warn!("`Window::set_resizable` is ignored on iOS") } pub fn is_resizable(&self) -> bool { warn!("`Window::is_resizable` is ignored on iOS"); false } #[inline] pub fn set_enabled_buttons(&self, _buttons: WindowButtons) { warn!("`Window::set_enabled_buttons` is ignored on iOS"); } #[inline] pub fn enabled_buttons(&self) -> WindowButtons { warn!("`Window::enabled_buttons` is ignored on iOS"); WindowButtons::all() } pub fn scale_factor(&self) -> f64 { self.view.contentScaleFactor() as _ } pub fn set_cursor(&self, _cursor: Cursor) { debug!("`Window::set_cursor` ignored on iOS") } pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } pub fn set_cursor_visible(&self, _visible: bool) { debug!("`Window::set_cursor_visible` is ignored on iOS") } pub fn drag_window(&self) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn show_window_menu(&self, _position: Position) {} pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } pub fn set_minimized(&self, _minimized: bool) { warn!("`Window::set_minimized` is ignored on iOS") } pub fn is_minimized(&self) -> Option { warn!("`Window::is_minimized` is ignored on iOS"); None } pub fn set_maximized(&self, _maximized: bool) { warn!("`Window::set_maximized` is ignored on iOS") } pub fn is_maximized(&self) -> bool { warn!("`Window::is_maximized` is ignored on iOS"); false } pub(crate) fn set_fullscreen(&self, monitor: Option) { let mtm = MainThreadMarker::new().unwrap(); let uiscreen = match &monitor { Some(Fullscreen::Exclusive(video_mode)) => { let uiscreen = video_mode.monitor.ui_screen(mtm); uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm))); uiscreen.clone() }, Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(), Some(Fullscreen::Borderless(None)) => { self.current_monitor_inner().ui_screen(mtm).clone() }, None => { warn!("`Window::set_fullscreen(None)` ignored on iOS"); return; }, }; // this is pretty slow on iOS, so avoid doing it if we can let current = self.window.screen(); if uiscreen != current { self.window.setScreen(&uiscreen); } let bounds = uiscreen.bounds(); self.window.setFrame(bounds); // For external displays, we must disable overscan compensation or // the displayed image will have giant black bars surrounding it on // each side uiscreen.setOverscanCompensation(UIScreenOverscanCompensation::None); } pub(crate) fn fullscreen(&self) -> Option { let mtm = MainThreadMarker::new().unwrap(); let monitor = self.current_monitor_inner(); let uiscreen = monitor.ui_screen(mtm); let screen_space_bounds = self.screen_frame(); let screen_bounds = uiscreen.bounds(); // TODO: track fullscreen instead of relying on brittle float comparisons if screen_space_bounds.origin.x == screen_bounds.origin.x && screen_space_bounds.origin.y == screen_bounds.origin.y && screen_space_bounds.size.width == screen_bounds.size.width && screen_space_bounds.size.height == screen_bounds.size.height { Some(Fullscreen::Borderless(Some(monitor))) } else { None } } pub fn set_decorations(&self, _decorations: bool) {} pub fn is_decorated(&self) -> bool { true } pub fn set_window_level(&self, _level: WindowLevel) { warn!("`Window::set_window_level` is ignored on iOS") } pub fn set_window_icon(&self, _icon: Option) { warn!("`Window::set_window_icon` is ignored on iOS") } pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) { warn!("`Window::set_ime_cursor_area` is ignored on iOS") } /// Show / hide the keyboard. To show the keyboard, we call `becomeFirstResponder`, /// requesting focus for the [WinitView]. Since [WinitView] implements /// [objc2_ui_kit::UIKeyInput], the keyboard will be shown. /// pub fn set_ime_allowed(&self, allowed: bool) { if allowed { unsafe { self.view.becomeFirstResponder(); } } else { unsafe { self.view.resignFirstResponder(); } } } pub fn set_ime_purpose(&self, _purpose: ImePurpose) { warn!("`Window::set_ime_purpose` is ignored on iOS") } pub fn focus_window(&self) { warn!("`Window::set_focus` is ignored on iOS") } pub fn request_user_attention(&self, _request_type: Option) { warn!("`Window::request_user_attention` is ignored on iOS") } // Allow directly accessing the current monitor internally without unwrapping. fn current_monitor_inner(&self) -> MonitorHandle { MonitorHandle::new(self.window.screen()) } pub fn current_monitor(&self) -> Option { Some(self.current_monitor_inner()) } pub fn available_monitors(&self) -> VecDeque { monitor::uiscreens(MainThreadMarker::new().unwrap()) } pub fn primary_monitor(&self) -> Option { #[allow(deprecated)] Some(MonitorHandle::new(UIScreen::mainScreen(MainThreadMarker::new().unwrap()))) } pub fn id(&self) -> WindowId { self.window.id() } #[cfg(feature = "rwh_04")] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::UiKitHandle::empty(); window_handle.ui_window = Retained::as_ptr(&self.window) as _; window_handle.ui_view = Retained::as_ptr(&self.view) as _; window_handle.ui_view_controller = Retained::as_ptr(&self.view_controller) as _; rwh_04::RawWindowHandle::UiKit(window_handle) } #[cfg(feature = "rwh_05")] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::UiKitWindowHandle::empty(); window_handle.ui_window = Retained::as_ptr(&self.window) as _; window_handle.ui_view = Retained::as_ptr(&self.view) as _; window_handle.ui_view_controller = Retained::as_ptr(&self.view_controller) as _; rwh_05::RawWindowHandle::UiKit(window_handle) } #[cfg(feature = "rwh_05")] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle { let mut window_handle = rwh_06::UiKitWindowHandle::new({ let ui_view = Retained::as_ptr(&self.view) as _; std::ptr::NonNull::new(ui_view).expect("Retained should never be null") }); window_handle.ui_view_controller = std::ptr::NonNull::new(Retained::as_ptr(&self.view_controller) as _); rwh_06::RawWindowHandle::UiKit(window_handle) } pub fn theme(&self) -> Option { warn!("`Window::theme` is ignored on iOS"); None } pub fn set_content_protected(&self, _protected: bool) {} pub fn has_focus(&self) -> bool { self.window.isKeyWindow() } #[inline] pub fn set_theme(&self, _theme: Option) { warn!("`Window::set_theme` is ignored on iOS"); } pub fn title(&self) -> String { warn!("`Window::title` is ignored on iOS"); String::new() } pub fn reset_dead_keys(&self) { // Noop } } pub struct Window { inner: MainThreadBound, } impl Window { pub(crate) fn new( event_loop: &ActiveEventLoop, window_attributes: WindowAttributes, ) -> Result { let mtm = event_loop.mtm; if window_attributes.min_inner_size.is_some() { warn!("`WindowAttributes::min_inner_size` is ignored on iOS"); } if window_attributes.max_inner_size.is_some() { warn!("`WindowAttributes::max_inner_size` is ignored on iOS"); } // TODO: transparency, visible #[allow(deprecated)] let main_screen = UIScreen::mainScreen(mtm); let fullscreen = window_attributes.fullscreen.clone().map(Into::into); let screen = match fullscreen { Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm), Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm), Some(Fullscreen::Borderless(None)) | None => &main_screen, }; let screen_bounds = screen.bounds(); let frame = match window_attributes.inner_size { Some(dim) => { let scale_factor = screen.scale(); let size = dim.to_logical::(scale_factor as f64); CGRect { origin: screen_bounds.origin, size: CGSize { width: size.width as _, height: size.height as _ }, } }, None => screen_bounds, }; let view = WinitView::new(mtm, &window_attributes, frame); let gl_or_metal_backed = view.isKindOfClass(class!(CAMetalLayer)) || view.isKindOfClass(class!(CAEAGLLayer)); let view_controller = WinitViewController::new(mtm, &window_attributes, &view); let window = WinitUIWindow::new(mtm, &window_attributes, frame, &view_controller); app_state::set_key_window(mtm, &window); // Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized` // event on window creation if the DPI factor != 1.0 let scale_factor = view.contentScaleFactor(); let scale_factor = scale_factor as f64; if scale_factor != 1.0 { let bounds = view.bounds(); let screen = window.screen(); let screen_space = screen.coordinateSpace(); let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space); let size = LogicalSize { width: screen_frame.size.width as f64, height: screen_frame.size.height as f64, }; let window_id = RootWindowId(window.id()); app_state::handle_nonuser_events( mtm, std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged { window: window.clone(), scale_factor, suggested_size: size.to_physical(scale_factor), })) .chain(std::iter::once(EventWrapper::StaticEvent( Event::WindowEvent { window_id, event: WindowEvent::Resized(size.to_physical(scale_factor)), }, ))), ); } let inner = Inner { window, view_controller, view, gl_or_metal_backed }; Ok(Window { inner: MainThreadBound::new(inner, mtm) }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) { // For now, don't actually do queuing, since it may be less predictable self.maybe_wait_on_main(f) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Inner) -> R + Send) -> R { self.inner.get_on_main(|inner| f(inner)) } #[cfg(feature = "rwh_06")] #[inline] pub(crate) fn raw_window_handle_rwh_06( &self, ) -> Result { if let Some(mtm) = MainThreadMarker::new() { Ok(self.inner.get(mtm).raw_window_handle_rwh_06()) } else { Err(rwh_06::HandleError::Unavailable) } } #[cfg(feature = "rwh_06")] #[inline] pub(crate) fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new())) } } // WindowExtIOS impl Inner { pub fn set_scale_factor(&self, scale_factor: f64) { assert!( dpi::validate_scale_factor(scale_factor), "`WindowExtIOS::set_scale_factor` received an invalid hidpi factor" ); let scale_factor = scale_factor as CGFloat; self.view.setContentScaleFactor(scale_factor); } pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { self.view_controller.set_supported_interface_orientations( MainThreadMarker::new().unwrap(), valid_orientations, ); } pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { self.view_controller.set_prefers_home_indicator_auto_hidden(hidden); } pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { self.view_controller.set_preferred_screen_edges_deferring_system_gestures(edges); } pub fn set_prefers_status_bar_hidden(&self, hidden: bool) { self.view_controller.set_prefers_status_bar_hidden(hidden); } pub fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) { self.view_controller.set_preferred_status_bar_style(status_bar_style); } pub fn recognize_pinch_gesture(&self, should_recognize: bool) { self.view.recognize_pinch_gesture(should_recognize); } pub fn recognize_pan_gesture( &self, should_recognize: bool, minimum_number_of_touches: u8, maximum_number_of_touches: u8, ) { self.view.recognize_pan_gesture( should_recognize, minimum_number_of_touches, maximum_number_of_touches, ); } pub fn recognize_doubletap_gesture(&self, should_recognize: bool) { self.view.recognize_doubletap_gesture(should_recognize); } pub fn recognize_rotation_gesture(&self, should_recognize: bool) { self.view.recognize_rotation_gesture(should_recognize); } } impl Inner { fn screen_frame(&self) -> CGRect { self.rect_to_screen_space(self.window.bounds()) } fn rect_to_screen_space(&self, rect: CGRect) -> CGRect { let screen_space = self.window.screen().coordinateSpace(); self.window.convertRect_toCoordinateSpace(rect, &screen_space) } fn rect_from_screen_space(&self, rect: CGRect) -> CGRect { let screen_space = self.window.screen().coordinateSpace(); self.window.convertRect_fromCoordinateSpace(rect, &screen_space) } fn safe_area_screen_space(&self) -> CGRect { let bounds = self.window.bounds(); if app_state::os_capabilities().safe_area { let safe_area = self.window.safeAreaInsets(); let safe_bounds = CGRect { origin: CGPoint { x: bounds.origin.x + safe_area.left, y: bounds.origin.y + safe_area.top, }, size: CGSize { width: bounds.size.width - safe_area.left - safe_area.right, height: bounds.size.height - safe_area.top - safe_area.bottom, }, }; self.rect_to_screen_space(safe_bounds) } else { let screen_frame = self.rect_to_screen_space(bounds); let status_bar_frame = { let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap()); #[allow(deprecated)] app.statusBarFrame() }; let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height { (screen_frame.origin.y, screen_frame.size.height) } else { let y = status_bar_frame.size.height; let height = screen_frame.size.height - (status_bar_frame.size.height - screen_frame.origin.y); (y, height) }; CGRect { origin: CGPoint { x: screen_frame.origin.x, y }, size: CGSize { width: screen_frame.size.width, height }, } } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId { window: *mut WinitUIWindow, } impl WindowId { pub const fn dummy() -> Self { WindowId { window: std::ptr::null_mut() } } } impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.window as u64 } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self { window: raw_id as _ } } } unsafe impl Send for WindowId {} unsafe impl Sync for WindowId {} impl From<&AnyObject> for WindowId { fn from(window: &AnyObject) -> WindowId { WindowId { window: window as *const _ as _ } } } #[derive(Clone, Debug, Default)] pub struct PlatformSpecificWindowAttributes { pub scale_factor: Option, pub valid_orientations: ValidOrientations, pub prefers_home_indicator_hidden: bool, pub prefers_status_bar_hidden: bool, pub preferred_status_bar_style: StatusBarStyle, pub preferred_screen_edges_deferring_system_gestures: ScreenEdge, } winit-0.30.9/src/platform_impl/linux/common/mod.rs000064400000000000000000000000151046102023000202530ustar 00000000000000pub mod xkb; winit-0.30.9/src/platform_impl/linux/common/xkb/compose.rs000064400000000000000000000065401046102023000217360ustar 00000000000000//! XKB compose handling. use std::env; use std::ffi::CString; use std::ops::Deref; use std::os::unix::ffi::OsStringExt; use std::ptr::NonNull; use super::{XkbContext, XKBCH}; use smol_str::SmolStr; use xkbcommon_dl::{ xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags, xkb_compose_status, xkb_compose_table, xkb_keysym_t, }; #[derive(Debug)] pub struct XkbComposeTable { table: NonNull, } impl XkbComposeTable { pub fn new(context: &XkbContext) -> Option { let locale = env::var_os("LC_ALL") .and_then(|v| if v.is_empty() { None } else { Some(v) }) .or_else(|| env::var_os("LC_CTYPE")) .and_then(|v| if v.is_empty() { None } else { Some(v) }) .or_else(|| env::var_os("LANG")) .and_then(|v| if v.is_empty() { None } else { Some(v) }) .unwrap_or_else(|| "C".into()); let locale = CString::new(locale.into_vec()).unwrap(); let table = unsafe { (XKBCH.xkb_compose_table_new_from_locale)( context.as_ptr(), locale.as_ptr(), xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, ) }; let table = NonNull::new(table)?; Some(Self { table }) } /// Create new state with the given compose table. pub fn new_state(&self) -> Option { let state = unsafe { (XKBCH.xkb_compose_state_new)( self.table.as_ptr(), xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, ) }; let state = NonNull::new(state)?; Some(XkbComposeState { state }) } } impl Deref for XkbComposeTable { type Target = NonNull; fn deref(&self) -> &Self::Target { &self.table } } impl Drop for XkbComposeTable { fn drop(&mut self) { unsafe { (XKBCH.xkb_compose_table_unref)(self.table.as_ptr()); } } } #[derive(Debug)] pub struct XkbComposeState { state: NonNull, } impl XkbComposeState { pub fn get_string(&mut self, scratch_buffer: &mut Vec) -> Option { super::make_string_with(scratch_buffer, |ptr, len| unsafe { (XKBCH.xkb_compose_state_get_utf8)(self.state.as_ptr(), ptr, len) }) } #[inline] pub fn feed(&mut self, keysym: xkb_keysym_t) -> ComposeStatus { let feed_result = unsafe { (XKBCH.xkb_compose_state_feed)(self.state.as_ptr(), keysym) }; match feed_result { xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored, xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => { ComposeStatus::Accepted(self.status()) }, } } #[inline] pub fn reset(&mut self) { unsafe { (XKBCH.xkb_compose_state_reset)(self.state.as_ptr()); } } #[inline] pub fn status(&mut self) -> xkb_compose_status { unsafe { (XKBCH.xkb_compose_state_get_status)(self.state.as_ptr()) } } } impl Drop for XkbComposeState { fn drop(&mut self) { unsafe { (XKBCH.xkb_compose_state_unref)(self.state.as_ptr()); }; } } #[derive(Copy, Clone, Debug)] pub enum ComposeStatus { Accepted(xkb_compose_status), Ignored, None, } winit-0.30.9/src/platform_impl/linux/common/xkb/keymap.rs000064400000000000000000001164371046102023000215660ustar 00000000000000//! XKB keymap. use std::ffi::c_char; use std::ops::Deref; use std::ptr::{self, NonNull}; #[cfg(x11_platform)] use x11_dl::xlib_xcb::xcb_connection_t; #[cfg(wayland_platform)] use {memmap2::MmapOptions, std::os::unix::io::OwnedFd}; use xkb::XKB_MOD_INVALID; use xkbcommon_dl::{ self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t, xkb_layout_index_t, xkb_mod_index_t, }; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; #[cfg(x11_platform)] use crate::platform_impl::common::xkb::XKBXH; use crate::platform_impl::common::xkb::{XkbContext, XKBH}; /// Map the raw X11-style keycode to the `KeyCode` enum. /// /// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses. pub fn raw_keycode_to_physicalkey(keycode: u32) -> PhysicalKey { scancode_to_physicalkey(keycode.saturating_sub(8)) } /// Map the linux scancode to Keycode. /// /// Both X11 and Wayland use keys with `+ 8` offset to linux scancode. pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { // The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as // libxkbcommon's documentation seems to suggest that the keycode values we're interested in // are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes, // I can only hope they agree on what the keycodes mean. // // Some of the keycodes are likely superfluous for our purposes, and some are ones which are // difficult to test the correctness of, or discover the purpose of. Because of this, they've // either been commented out here, or not included at all. PhysicalKey::Code(match scancode { 0 => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(0)), 1 => KeyCode::Escape, 2 => KeyCode::Digit1, 3 => KeyCode::Digit2, 4 => KeyCode::Digit3, 5 => KeyCode::Digit4, 6 => KeyCode::Digit5, 7 => KeyCode::Digit6, 8 => KeyCode::Digit7, 9 => KeyCode::Digit8, 10 => KeyCode::Digit9, 11 => KeyCode::Digit0, 12 => KeyCode::Minus, 13 => KeyCode::Equal, 14 => KeyCode::Backspace, 15 => KeyCode::Tab, 16 => KeyCode::KeyQ, 17 => KeyCode::KeyW, 18 => KeyCode::KeyE, 19 => KeyCode::KeyR, 20 => KeyCode::KeyT, 21 => KeyCode::KeyY, 22 => KeyCode::KeyU, 23 => KeyCode::KeyI, 24 => KeyCode::KeyO, 25 => KeyCode::KeyP, 26 => KeyCode::BracketLeft, 27 => KeyCode::BracketRight, 28 => KeyCode::Enter, 29 => KeyCode::ControlLeft, 30 => KeyCode::KeyA, 31 => KeyCode::KeyS, 32 => KeyCode::KeyD, 33 => KeyCode::KeyF, 34 => KeyCode::KeyG, 35 => KeyCode::KeyH, 36 => KeyCode::KeyJ, 37 => KeyCode::KeyK, 38 => KeyCode::KeyL, 39 => KeyCode::Semicolon, 40 => KeyCode::Quote, 41 => KeyCode::Backquote, 42 => KeyCode::ShiftLeft, 43 => KeyCode::Backslash, 44 => KeyCode::KeyZ, 45 => KeyCode::KeyX, 46 => KeyCode::KeyC, 47 => KeyCode::KeyV, 48 => KeyCode::KeyB, 49 => KeyCode::KeyN, 50 => KeyCode::KeyM, 51 => KeyCode::Comma, 52 => KeyCode::Period, 53 => KeyCode::Slash, 54 => KeyCode::ShiftRight, 55 => KeyCode::NumpadMultiply, 56 => KeyCode::AltLeft, 57 => KeyCode::Space, 58 => KeyCode::CapsLock, 59 => KeyCode::F1, 60 => KeyCode::F2, 61 => KeyCode::F3, 62 => KeyCode::F4, 63 => KeyCode::F5, 64 => KeyCode::F6, 65 => KeyCode::F7, 66 => KeyCode::F8, 67 => KeyCode::F9, 68 => KeyCode::F10, 69 => KeyCode::NumLock, 70 => KeyCode::ScrollLock, 71 => KeyCode::Numpad7, 72 => KeyCode::Numpad8, 73 => KeyCode::Numpad9, 74 => KeyCode::NumpadSubtract, 75 => KeyCode::Numpad4, 76 => KeyCode::Numpad5, 77 => KeyCode::Numpad6, 78 => KeyCode::NumpadAdd, 79 => KeyCode::Numpad1, 80 => KeyCode::Numpad2, 81 => KeyCode::Numpad3, 82 => KeyCode::Numpad0, 83 => KeyCode::NumpadDecimal, 85 => KeyCode::Lang5, 86 => KeyCode::IntlBackslash, 87 => KeyCode::F11, 88 => KeyCode::F12, 89 => KeyCode::IntlRo, 90 => KeyCode::Lang3, 91 => KeyCode::Lang4, 92 => KeyCode::Convert, 93 => KeyCode::KanaMode, 94 => KeyCode::NonConvert, // 95 => KeyCode::KPJPCOMMA, 96 => KeyCode::NumpadEnter, 97 => KeyCode::ControlRight, 98 => KeyCode::NumpadDivide, 99 => KeyCode::PrintScreen, 100 => KeyCode::AltRight, // 101 => KeyCode::LINEFEED, 102 => KeyCode::Home, 103 => KeyCode::ArrowUp, 104 => KeyCode::PageUp, 105 => KeyCode::ArrowLeft, 106 => KeyCode::ArrowRight, 107 => KeyCode::End, 108 => KeyCode::ArrowDown, 109 => KeyCode::PageDown, 110 => KeyCode::Insert, 111 => KeyCode::Delete, // 112 => KeyCode::MACRO, 113 => KeyCode::AudioVolumeMute, 114 => KeyCode::AudioVolumeDown, 115 => KeyCode::AudioVolumeUp, // 116 => KeyCode::POWER, 117 => KeyCode::NumpadEqual, // 118 => KeyCode::KPPLUSMINUS, 119 => KeyCode::Pause, // 120 => KeyCode::SCALE, 121 => KeyCode::NumpadComma, 122 => KeyCode::Lang1, 123 => KeyCode::Lang2, 124 => KeyCode::IntlYen, 125 => KeyCode::SuperLeft, 126 => KeyCode::SuperRight, 127 => KeyCode::ContextMenu, // 128 => KeyCode::STOP, // 129 => KeyCode::AGAIN, // 130 => KeyCode::PROPS, // 131 => KeyCode::UNDO, // 132 => KeyCode::FRONT, // 133 => KeyCode::COPY, // 134 => KeyCode::OPEN, // 135 => KeyCode::PASTE, // 136 => KeyCode::FIND, // 137 => KeyCode::CUT, // 138 => KeyCode::HELP, // 139 => KeyCode::MENU, // 140 => KeyCode::CALC, // 141 => KeyCode::SETUP, // 142 => KeyCode::SLEEP, // 143 => KeyCode::WAKEUP, // 144 => KeyCode::FILE, // 145 => KeyCode::SENDFILE, // 146 => KeyCode::DELETEFILE, // 147 => KeyCode::XFER, // 148 => KeyCode::PROG1, // 149 => KeyCode::PROG2, // 150 => KeyCode::WWW, // 151 => KeyCode::MSDOS, // 152 => KeyCode::COFFEE, // 153 => KeyCode::ROTATE_DISPLAY, // 154 => KeyCode::CYCLEWINDOWS, // 155 => KeyCode::MAIL, // 156 => KeyCode::BOOKMARKS, // 157 => KeyCode::COMPUTER, // 158 => KeyCode::BACK, // 159 => KeyCode::FORWARD, // 160 => KeyCode::CLOSECD, // 161 => KeyCode::EJECTCD, // 162 => KeyCode::EJECTCLOSECD, 163 => KeyCode::MediaTrackNext, 164 => KeyCode::MediaPlayPause, 165 => KeyCode::MediaTrackPrevious, 166 => KeyCode::MediaStop, // 167 => KeyCode::RECORD, // 168 => KeyCode::REWIND, // 169 => KeyCode::PHONE, // 170 => KeyCode::ISO, // 171 => KeyCode::CONFIG, // 172 => KeyCode::HOMEPAGE, // 173 => KeyCode::REFRESH, // 174 => KeyCode::EXIT, // 175 => KeyCode::MOVE, // 176 => KeyCode::EDIT, // 177 => KeyCode::SCROLLUP, // 178 => KeyCode::SCROLLDOWN, // 179 => KeyCode::KPLEFTPAREN, // 180 => KeyCode::KPRIGHTPAREN, // 181 => KeyCode::NEW, // 182 => KeyCode::REDO, 183 => KeyCode::F13, 184 => KeyCode::F14, 185 => KeyCode::F15, 186 => KeyCode::F16, 187 => KeyCode::F17, 188 => KeyCode::F18, 189 => KeyCode::F19, 190 => KeyCode::F20, 191 => KeyCode::F21, 192 => KeyCode::F22, 193 => KeyCode::F23, 194 => KeyCode::F24, // 200 => KeyCode::PLAYCD, // 201 => KeyCode::PAUSECD, // 202 => KeyCode::PROG3, // 203 => KeyCode::PROG4, // 204 => KeyCode::DASHBOARD, // 205 => KeyCode::SUSPEND, // 206 => KeyCode::CLOSE, // 207 => KeyCode::PLAY, // 208 => KeyCode::FASTFORWARD, // 209 => KeyCode::BASSBOOST, // 210 => KeyCode::PRINT, // 211 => KeyCode::HP, // 212 => KeyCode::CAMERA, // 213 => KeyCode::SOUND, // 214 => KeyCode::QUESTION, // 215 => KeyCode::EMAIL, // 216 => KeyCode::CHAT, // 217 => KeyCode::SEARCH, // 218 => KeyCode::CONNECT, // 219 => KeyCode::FINANCE, // 220 => KeyCode::SPORT, // 221 => KeyCode::SHOP, // 222 => KeyCode::ALTERASE, // 223 => KeyCode::CANCEL, // 224 => KeyCode::BRIGHTNESSDOW, // 225 => KeyCode::BRIGHTNESSU, // 226 => KeyCode::MEDIA, // 227 => KeyCode::SWITCHVIDEOMODE, // 228 => KeyCode::KBDILLUMTOGGLE, // 229 => KeyCode::KBDILLUMDOWN, // 230 => KeyCode::KBDILLUMUP, // 231 => KeyCode::SEND, // 232 => KeyCode::REPLY, // 233 => KeyCode::FORWARDMAIL, // 234 => KeyCode::SAVE, // 235 => KeyCode::DOCUMENTS, // 236 => KeyCode::BATTERY, // 237 => KeyCode::BLUETOOTH, // 238 => KeyCode::WLAN, // 239 => KeyCode::UWB, 240 => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified), // 241 => KeyCode::VIDEO_NEXT, // 242 => KeyCode::VIDEO_PREV, // 243 => KeyCode::BRIGHTNESS_CYCLE, // 244 => KeyCode::BRIGHTNESS_AUTO, // 245 => KeyCode::DISPLAY_OFF, // 246 => KeyCode::WWAN, // 247 => KeyCode::RFKILL, // 248 => KeyCode::KEY_MICMUTE, _ => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(scancode)), }) } pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option { let code = match key { PhysicalKey::Code(code) => code, PhysicalKey::Unidentified(code) => { return match code { NativeKeyCode::Unidentified => Some(240), NativeKeyCode::Xkb(raw) => Some(raw), _ => None, }; }, }; match code { KeyCode::Escape => Some(1), KeyCode::Digit1 => Some(2), KeyCode::Digit2 => Some(3), KeyCode::Digit3 => Some(4), KeyCode::Digit4 => Some(5), KeyCode::Digit5 => Some(6), KeyCode::Digit6 => Some(7), KeyCode::Digit7 => Some(8), KeyCode::Digit8 => Some(9), KeyCode::Digit9 => Some(10), KeyCode::Digit0 => Some(11), KeyCode::Minus => Some(12), KeyCode::Equal => Some(13), KeyCode::Backspace => Some(14), KeyCode::Tab => Some(15), KeyCode::KeyQ => Some(16), KeyCode::KeyW => Some(17), KeyCode::KeyE => Some(18), KeyCode::KeyR => Some(19), KeyCode::KeyT => Some(20), KeyCode::KeyY => Some(21), KeyCode::KeyU => Some(22), KeyCode::KeyI => Some(23), KeyCode::KeyO => Some(24), KeyCode::KeyP => Some(25), KeyCode::BracketLeft => Some(26), KeyCode::BracketRight => Some(27), KeyCode::Enter => Some(28), KeyCode::ControlLeft => Some(29), KeyCode::KeyA => Some(30), KeyCode::KeyS => Some(31), KeyCode::KeyD => Some(32), KeyCode::KeyF => Some(33), KeyCode::KeyG => Some(34), KeyCode::KeyH => Some(35), KeyCode::KeyJ => Some(36), KeyCode::KeyK => Some(37), KeyCode::KeyL => Some(38), KeyCode::Semicolon => Some(39), KeyCode::Quote => Some(40), KeyCode::Backquote => Some(41), KeyCode::ShiftLeft => Some(42), KeyCode::Backslash => Some(43), KeyCode::KeyZ => Some(44), KeyCode::KeyX => Some(45), KeyCode::KeyC => Some(46), KeyCode::KeyV => Some(47), KeyCode::KeyB => Some(48), KeyCode::KeyN => Some(49), KeyCode::KeyM => Some(50), KeyCode::Comma => Some(51), KeyCode::Period => Some(52), KeyCode::Slash => Some(53), KeyCode::ShiftRight => Some(54), KeyCode::NumpadMultiply => Some(55), KeyCode::AltLeft => Some(56), KeyCode::Space => Some(57), KeyCode::CapsLock => Some(58), KeyCode::F1 => Some(59), KeyCode::F2 => Some(60), KeyCode::F3 => Some(61), KeyCode::F4 => Some(62), KeyCode::F5 => Some(63), KeyCode::F6 => Some(64), KeyCode::F7 => Some(65), KeyCode::F8 => Some(66), KeyCode::F9 => Some(67), KeyCode::F10 => Some(68), KeyCode::NumLock => Some(69), KeyCode::ScrollLock => Some(70), KeyCode::Numpad7 => Some(71), KeyCode::Numpad8 => Some(72), KeyCode::Numpad9 => Some(73), KeyCode::NumpadSubtract => Some(74), KeyCode::Numpad4 => Some(75), KeyCode::Numpad5 => Some(76), KeyCode::Numpad6 => Some(77), KeyCode::NumpadAdd => Some(78), KeyCode::Numpad1 => Some(79), KeyCode::Numpad2 => Some(80), KeyCode::Numpad3 => Some(81), KeyCode::Numpad0 => Some(82), KeyCode::NumpadDecimal => Some(83), KeyCode::Lang5 => Some(85), KeyCode::IntlBackslash => Some(86), KeyCode::F11 => Some(87), KeyCode::F12 => Some(88), KeyCode::IntlRo => Some(89), KeyCode::Lang3 => Some(90), KeyCode::Lang4 => Some(91), KeyCode::Convert => Some(92), KeyCode::KanaMode => Some(93), KeyCode::NonConvert => Some(94), KeyCode::NumpadEnter => Some(96), KeyCode::ControlRight => Some(97), KeyCode::NumpadDivide => Some(98), KeyCode::PrintScreen => Some(99), KeyCode::AltRight => Some(100), KeyCode::Home => Some(102), KeyCode::ArrowUp => Some(103), KeyCode::PageUp => Some(104), KeyCode::ArrowLeft => Some(105), KeyCode::ArrowRight => Some(106), KeyCode::End => Some(107), KeyCode::ArrowDown => Some(108), KeyCode::PageDown => Some(109), KeyCode::Insert => Some(110), KeyCode::Delete => Some(111), KeyCode::AudioVolumeMute => Some(113), KeyCode::AudioVolumeDown => Some(114), KeyCode::AudioVolumeUp => Some(115), KeyCode::NumpadEqual => Some(117), KeyCode::Pause => Some(119), KeyCode::NumpadComma => Some(121), KeyCode::Lang1 => Some(122), KeyCode::Lang2 => Some(123), KeyCode::IntlYen => Some(124), KeyCode::SuperLeft => Some(125), KeyCode::SuperRight => Some(126), KeyCode::ContextMenu => Some(127), KeyCode::MediaTrackNext => Some(163), KeyCode::MediaPlayPause => Some(164), KeyCode::MediaTrackPrevious => Some(165), KeyCode::MediaStop => Some(166), KeyCode::F13 => Some(183), KeyCode::F14 => Some(184), KeyCode::F15 => Some(185), KeyCode::F16 => Some(186), KeyCode::F17 => Some(187), KeyCode::F18 => Some(188), KeyCode::F19 => Some(189), KeyCode::F20 => Some(190), KeyCode::F21 => Some(191), KeyCode::F22 => Some(192), KeyCode::F23 => Some(193), KeyCode::F24 => Some(194), _ => None, } } pub fn keysym_to_key(keysym: u32) -> Key { use xkbcommon_dl::keysyms; Key::Named(match keysym { // TTY function keys keysyms::BackSpace => NamedKey::Backspace, keysyms::Tab => NamedKey::Tab, // keysyms::Linefeed => NamedKey::Linefeed, keysyms::Clear => NamedKey::Clear, keysyms::Return => NamedKey::Enter, keysyms::Pause => NamedKey::Pause, keysyms::Scroll_Lock => NamedKey::ScrollLock, keysyms::Sys_Req => NamedKey::PrintScreen, keysyms::Escape => NamedKey::Escape, keysyms::Delete => NamedKey::Delete, // IME keys keysyms::Multi_key => NamedKey::Compose, keysyms::Codeinput => NamedKey::CodeInput, keysyms::SingleCandidate => NamedKey::SingleCandidate, keysyms::MultipleCandidate => NamedKey::AllCandidates, keysyms::PreviousCandidate => NamedKey::PreviousCandidate, // Japanese keys keysyms::Kanji => NamedKey::KanjiMode, keysyms::Muhenkan => NamedKey::NonConvert, keysyms::Henkan_Mode => NamedKey::Convert, keysyms::Romaji => NamedKey::Romaji, keysyms::Hiragana => NamedKey::Hiragana, keysyms::Hiragana_Katakana => NamedKey::HiraganaKatakana, keysyms::Zenkaku => NamedKey::Zenkaku, keysyms::Hankaku => NamedKey::Hankaku, keysyms::Zenkaku_Hankaku => NamedKey::ZenkakuHankaku, // keysyms::Touroku => NamedKey::Touroku, // keysyms::Massyo => NamedKey::Massyo, keysyms::Kana_Lock => NamedKey::KanaMode, keysyms::Kana_Shift => NamedKey::KanaMode, keysyms::Eisu_Shift => NamedKey::Alphanumeric, keysyms::Eisu_toggle => NamedKey::Alphanumeric, // NOTE: The next three items are aliases for values we've already mapped. // keysyms::Kanji_Bangou => NamedKey::CodeInput, // keysyms::Zen_Koho => NamedKey::AllCandidates, // keysyms::Mae_Koho => NamedKey::PreviousCandidate, // Cursor control & motion keysyms::Home => NamedKey::Home, keysyms::Left => NamedKey::ArrowLeft, keysyms::Up => NamedKey::ArrowUp, keysyms::Right => NamedKey::ArrowRight, keysyms::Down => NamedKey::ArrowDown, // keysyms::Prior => NamedKey::PageUp, keysyms::Page_Up => NamedKey::PageUp, // keysyms::Next => NamedKey::PageDown, keysyms::Page_Down => NamedKey::PageDown, keysyms::End => NamedKey::End, // keysyms::Begin => NamedKey::Begin, // Misc. functions keysyms::Select => NamedKey::Select, keysyms::Print => NamedKey::PrintScreen, keysyms::Execute => NamedKey::Execute, keysyms::Insert => NamedKey::Insert, keysyms::Undo => NamedKey::Undo, keysyms::Redo => NamedKey::Redo, keysyms::Menu => NamedKey::ContextMenu, keysyms::Find => NamedKey::Find, keysyms::Cancel => NamedKey::Cancel, keysyms::Help => NamedKey::Help, keysyms::Break => NamedKey::Pause, keysyms::Mode_switch => NamedKey::ModeChange, // keysyms::script_switch => NamedKey::ModeChange, keysyms::Num_Lock => NamedKey::NumLock, // Keypad keys // keysyms::KP_Space => return Key::Character(" "), keysyms::KP_Tab => NamedKey::Tab, keysyms::KP_Enter => NamedKey::Enter, keysyms::KP_F1 => NamedKey::F1, keysyms::KP_F2 => NamedKey::F2, keysyms::KP_F3 => NamedKey::F3, keysyms::KP_F4 => NamedKey::F4, keysyms::KP_Home => NamedKey::Home, keysyms::KP_Left => NamedKey::ArrowLeft, keysyms::KP_Up => NamedKey::ArrowUp, keysyms::KP_Right => NamedKey::ArrowRight, keysyms::KP_Down => NamedKey::ArrowDown, // keysyms::KP_Prior => NamedKey::PageUp, keysyms::KP_Page_Up => NamedKey::PageUp, // keysyms::KP_Next => NamedKey::PageDown, keysyms::KP_Page_Down => NamedKey::PageDown, keysyms::KP_End => NamedKey::End, // This is the key labeled "5" on the numpad when NumLock is off. // keysyms::KP_Begin => NamedKey::Begin, keysyms::KP_Insert => NamedKey::Insert, keysyms::KP_Delete => NamedKey::Delete, // keysyms::KP_Equal => NamedKey::Equal, // keysyms::KP_Multiply => NamedKey::Multiply, // keysyms::KP_Add => NamedKey::Add, // keysyms::KP_Separator => NamedKey::Separator, // keysyms::KP_Subtract => NamedKey::Subtract, // keysyms::KP_Decimal => NamedKey::Decimal, // keysyms::KP_Divide => NamedKey::Divide, // keysyms::KP_0 => return Key::Character("0"), // keysyms::KP_1 => return Key::Character("1"), // keysyms::KP_2 => return Key::Character("2"), // keysyms::KP_3 => return Key::Character("3"), // keysyms::KP_4 => return Key::Character("4"), // keysyms::KP_5 => return Key::Character("5"), // keysyms::KP_6 => return Key::Character("6"), // keysyms::KP_7 => return Key::Character("7"), // keysyms::KP_8 => return Key::Character("8"), // keysyms::KP_9 => return Key::Character("9"), // Function keys keysyms::F1 => NamedKey::F1, keysyms::F2 => NamedKey::F2, keysyms::F3 => NamedKey::F3, keysyms::F4 => NamedKey::F4, keysyms::F5 => NamedKey::F5, keysyms::F6 => NamedKey::F6, keysyms::F7 => NamedKey::F7, keysyms::F8 => NamedKey::F8, keysyms::F9 => NamedKey::F9, keysyms::F10 => NamedKey::F10, keysyms::F11 => NamedKey::F11, keysyms::F12 => NamedKey::F12, keysyms::F13 => NamedKey::F13, keysyms::F14 => NamedKey::F14, keysyms::F15 => NamedKey::F15, keysyms::F16 => NamedKey::F16, keysyms::F17 => NamedKey::F17, keysyms::F18 => NamedKey::F18, keysyms::F19 => NamedKey::F19, keysyms::F20 => NamedKey::F20, keysyms::F21 => NamedKey::F21, keysyms::F22 => NamedKey::F22, keysyms::F23 => NamedKey::F23, keysyms::F24 => NamedKey::F24, keysyms::F25 => NamedKey::F25, keysyms::F26 => NamedKey::F26, keysyms::F27 => NamedKey::F27, keysyms::F28 => NamedKey::F28, keysyms::F29 => NamedKey::F29, keysyms::F30 => NamedKey::F30, keysyms::F31 => NamedKey::F31, keysyms::F32 => NamedKey::F32, keysyms::F33 => NamedKey::F33, keysyms::F34 => NamedKey::F34, keysyms::F35 => NamedKey::F35, // Modifiers keysyms::Shift_L => NamedKey::Shift, keysyms::Shift_R => NamedKey::Shift, keysyms::Control_L => NamedKey::Control, keysyms::Control_R => NamedKey::Control, keysyms::Caps_Lock => NamedKey::CapsLock, // keysyms::Shift_Lock => NamedKey::ShiftLock, // keysyms::Meta_L => NamedKey::Meta, // keysyms::Meta_R => NamedKey::Meta, keysyms::Alt_L => NamedKey::Alt, keysyms::Alt_R => NamedKey::Alt, keysyms::Super_L => NamedKey::Super, keysyms::Super_R => NamedKey::Super, keysyms::Hyper_L => NamedKey::Hyper, keysyms::Hyper_R => NamedKey::Hyper, // XKB function and modifier keys // keysyms::ISO_Lock => NamedKey::IsoLock, // keysyms::ISO_Level2_Latch => NamedKey::IsoLevel2Latch, keysyms::ISO_Level3_Shift => NamedKey::AltGraph, keysyms::ISO_Level3_Latch => NamedKey::AltGraph, keysyms::ISO_Level3_Lock => NamedKey::AltGraph, // keysyms::ISO_Level5_Shift => NamedKey::IsoLevel5Shift, // keysyms::ISO_Level5_Latch => NamedKey::IsoLevel5Latch, // keysyms::ISO_Level5_Lock => NamedKey::IsoLevel5Lock, // keysyms::ISO_Group_Shift => NamedKey::IsoGroupShift, // keysyms::ISO_Group_Latch => NamedKey::IsoGroupLatch, // keysyms::ISO_Group_Lock => NamedKey::IsoGroupLock, keysyms::ISO_Next_Group => NamedKey::GroupNext, // keysyms::ISO_Next_Group_Lock => NamedKey::GroupNextLock, keysyms::ISO_Prev_Group => NamedKey::GroupPrevious, // keysyms::ISO_Prev_Group_Lock => NamedKey::GroupPreviousLock, keysyms::ISO_First_Group => NamedKey::GroupFirst, // keysyms::ISO_First_Group_Lock => NamedKey::GroupFirstLock, keysyms::ISO_Last_Group => NamedKey::GroupLast, // keysyms::ISO_Last_Group_Lock => NamedKey::GroupLastLock, keysyms::ISO_Left_Tab => NamedKey::Tab, // keysyms::ISO_Move_Line_Up => NamedKey::IsoMoveLineUp, // keysyms::ISO_Move_Line_Down => NamedKey::IsoMoveLineDown, // keysyms::ISO_Partial_Line_Up => NamedKey::IsoPartialLineUp, // keysyms::ISO_Partial_Line_Down => NamedKey::IsoPartialLineDown, // keysyms::ISO_Partial_Space_Left => NamedKey::IsoPartialSpaceLeft, // keysyms::ISO_Partial_Space_Right => NamedKey::IsoPartialSpaceRight, // keysyms::ISO_Set_Margin_Left => NamedKey::IsoSetMarginLeft, // keysyms::ISO_Set_Margin_Right => NamedKey::IsoSetMarginRight, // keysyms::ISO_Release_Margin_Left => NamedKey::IsoReleaseMarginLeft, // keysyms::ISO_Release_Margin_Right => NamedKey::IsoReleaseMarginRight, // keysyms::ISO_Release_Both_Margins => NamedKey::IsoReleaseBothMargins, // keysyms::ISO_Fast_Cursor_Left => NamedKey::IsoFastCursorLeft, // keysyms::ISO_Fast_Cursor_Right => NamedKey::IsoFastCursorRight, // keysyms::ISO_Fast_Cursor_Up => NamedKey::IsoFastCursorUp, // keysyms::ISO_Fast_Cursor_Down => NamedKey::IsoFastCursorDown, // keysyms::ISO_Continuous_Underline => NamedKey::IsoContinuousUnderline, // keysyms::ISO_Discontinuous_Underline => NamedKey::IsoDiscontinuousUnderline, // keysyms::ISO_Emphasize => NamedKey::IsoEmphasize, // keysyms::ISO_Center_Object => NamedKey::IsoCenterObject, keysyms::ISO_Enter => NamedKey::Enter, // dead_grave..dead_currency // dead_lowline..dead_longsolidusoverlay // dead_a..dead_capital_schwa // dead_greek // First_Virtual_Screen..Terminate_Server // AccessX_Enable..AudibleBell_Enable // Pointer_Left..Pointer_Drag5 // Pointer_EnableKeys..Pointer_DfltBtnPrev // ch..C_H // 3270 terminal keys // keysyms::3270_Duplicate => NamedKey::Duplicate, // keysyms::3270_FieldMark => NamedKey::FieldMark, // keysyms::3270_Right2 => NamedKey::Right2, // keysyms::3270_Left2 => NamedKey::Left2, // keysyms::3270_BackTab => NamedKey::BackTab, keysyms::_3270_EraseEOF => NamedKey::EraseEof, // keysyms::3270_EraseInput => NamedKey::EraseInput, // keysyms::3270_Reset => NamedKey::Reset, // keysyms::3270_Quit => NamedKey::Quit, // keysyms::3270_PA1 => NamedKey::Pa1, // keysyms::3270_PA2 => NamedKey::Pa2, // keysyms::3270_PA3 => NamedKey::Pa3, // keysyms::3270_Test => NamedKey::Test, keysyms::_3270_Attn => NamedKey::Attn, // keysyms::3270_CursorBlink => NamedKey::CursorBlink, // keysyms::3270_AltCursor => NamedKey::AltCursor, // keysyms::3270_KeyClick => NamedKey::KeyClick, // keysyms::3270_Jump => NamedKey::Jump, // keysyms::3270_Ident => NamedKey::Ident, // keysyms::3270_Rule => NamedKey::Rule, // keysyms::3270_Copy => NamedKey::Copy, keysyms::_3270_Play => NamedKey::Play, // keysyms::3270_Setup => NamedKey::Setup, // keysyms::3270_Record => NamedKey::Record, // keysyms::3270_ChangeScreen => NamedKey::ChangeScreen, // keysyms::3270_DeleteWord => NamedKey::DeleteWord, keysyms::_3270_ExSelect => NamedKey::ExSel, keysyms::_3270_CursorSelect => NamedKey::CrSel, keysyms::_3270_PrintScreen => NamedKey::PrintScreen, keysyms::_3270_Enter => NamedKey::Enter, keysyms::space => NamedKey::Space, // exclam..Sinh_kunddaliya // XFree86 // keysyms::XF86_ModeLock => NamedKey::ModeLock, // XFree86 - Backlight controls keysyms::XF86_MonBrightnessUp => NamedKey::BrightnessUp, keysyms::XF86_MonBrightnessDown => NamedKey::BrightnessDown, // keysyms::XF86_KbdLightOnOff => NamedKey::LightOnOff, // keysyms::XF86_KbdBrightnessUp => NamedKey::KeyboardBrightnessUp, // keysyms::XF86_KbdBrightnessDown => NamedKey::KeyboardBrightnessDown, // XFree86 - "Internet" keysyms::XF86_Standby => NamedKey::Standby, keysyms::XF86_AudioLowerVolume => NamedKey::AudioVolumeDown, keysyms::XF86_AudioRaiseVolume => NamedKey::AudioVolumeUp, keysyms::XF86_AudioPlay => NamedKey::MediaPlay, keysyms::XF86_AudioStop => NamedKey::MediaStop, keysyms::XF86_AudioPrev => NamedKey::MediaTrackPrevious, keysyms::XF86_AudioNext => NamedKey::MediaTrackNext, keysyms::XF86_HomePage => NamedKey::BrowserHome, keysyms::XF86_Mail => NamedKey::LaunchMail, // keysyms::XF86_Start => NamedKey::Start, keysyms::XF86_Search => NamedKey::BrowserSearch, keysyms::XF86_AudioRecord => NamedKey::MediaRecord, // XFree86 - PDA keysyms::XF86_Calculator => NamedKey::LaunchApplication2, // keysyms::XF86_Memo => NamedKey::Memo, // keysyms::XF86_ToDoList => NamedKey::ToDoList, keysyms::XF86_Calendar => NamedKey::LaunchCalendar, keysyms::XF86_PowerDown => NamedKey::Power, // keysyms::XF86_ContrastAdjust => NamedKey::AdjustContrast, // keysyms::XF86_RockerUp => NamedKey::RockerUp, // keysyms::XF86_RockerDown => NamedKey::RockerDown, // keysyms::XF86_RockerEnter => NamedKey::RockerEnter, // XFree86 - More "Internet" keysyms::XF86_Back => NamedKey::BrowserBack, keysyms::XF86_Forward => NamedKey::BrowserForward, // keysyms::XF86_Stop => NamedKey::Stop, keysyms::XF86_Refresh => NamedKey::BrowserRefresh, keysyms::XF86_PowerOff => NamedKey::Power, keysyms::XF86_WakeUp => NamedKey::WakeUp, keysyms::XF86_Eject => NamedKey::Eject, keysyms::XF86_ScreenSaver => NamedKey::LaunchScreenSaver, keysyms::XF86_WWW => NamedKey::LaunchWebBrowser, keysyms::XF86_Sleep => NamedKey::Standby, keysyms::XF86_Favorites => NamedKey::BrowserFavorites, keysyms::XF86_AudioPause => NamedKey::MediaPause, // keysyms::XF86_AudioMedia => NamedKey::AudioMedia, keysyms::XF86_MyComputer => NamedKey::LaunchApplication1, // keysyms::XF86_VendorHome => NamedKey::VendorHome, // keysyms::XF86_LightBulb => NamedKey::LightBulb, // keysyms::XF86_Shop => NamedKey::BrowserShop, // keysyms::XF86_History => NamedKey::BrowserHistory, // keysyms::XF86_OpenURL => NamedKey::OpenUrl, // keysyms::XF86_AddFavorite => NamedKey::AddFavorite, // keysyms::XF86_HotLinks => NamedKey::HotLinks, // keysyms::XF86_BrightnessAdjust => NamedKey::BrightnessAdjust, // keysyms::XF86_Finance => NamedKey::BrowserFinance, // keysyms::XF86_Community => NamedKey::BrowserCommunity, keysyms::XF86_AudioRewind => NamedKey::MediaRewind, // keysyms::XF86_BackForward => Key::???, // XF86_Launch0..XF86_LaunchF // XF86_ApplicationLeft..XF86_CD keysyms::XF86_Calculater => NamedKey::LaunchApplication2, // Nice typo, libxkbcommon :) // XF86_Clear keysyms::XF86_Close => NamedKey::Close, keysyms::XF86_Copy => NamedKey::Copy, keysyms::XF86_Cut => NamedKey::Cut, // XF86_Display..XF86_Documents keysyms::XF86_Excel => NamedKey::LaunchSpreadsheet, // XF86_Explorer..XF86iTouch keysyms::XF86_LogOff => NamedKey::LogOff, // XF86_Market..XF86_MenuPB keysyms::XF86_MySites => NamedKey::BrowserFavorites, keysyms::XF86_New => NamedKey::New, // XF86_News..XF86_OfficeHome keysyms::XF86_Open => NamedKey::Open, // XF86_Option keysyms::XF86_Paste => NamedKey::Paste, keysyms::XF86_Phone => NamedKey::LaunchPhone, // XF86_Q keysyms::XF86_Reply => NamedKey::MailReply, keysyms::XF86_Reload => NamedKey::BrowserRefresh, // XF86_RotateWindows..XF86_RotationKB keysyms::XF86_Save => NamedKey::Save, // XF86_ScrollUp..XF86_ScrollClick keysyms::XF86_Send => NamedKey::MailSend, keysyms::XF86_Spell => NamedKey::SpellCheck, keysyms::XF86_SplitScreen => NamedKey::SplitScreenToggle, // XF86_Support..XF86_User2KB keysyms::XF86_Video => NamedKey::LaunchMediaPlayer, // XF86_WheelButton keysyms::XF86_Word => NamedKey::LaunchWordProcessor, // XF86_Xfer keysyms::XF86_ZoomIn => NamedKey::ZoomIn, keysyms::XF86_ZoomOut => NamedKey::ZoomOut, // XF86_Away..XF86_Messenger keysyms::XF86_WebCam => NamedKey::LaunchWebCam, keysyms::XF86_MailForward => NamedKey::MailForward, // XF86_Pictures keysyms::XF86_Music => NamedKey::LaunchMusicPlayer, // XF86_Battery..XF86_UWB keysyms::XF86_AudioForward => NamedKey::MediaFastForward, // XF86_AudioRepeat keysyms::XF86_AudioRandomPlay => NamedKey::RandomToggle, keysyms::XF86_Subtitle => NamedKey::Subtitle, keysyms::XF86_AudioCycleTrack => NamedKey::MediaAudioTrack, // XF86_CycleAngle..XF86_Blue keysyms::XF86_Suspend => NamedKey::Standby, keysyms::XF86_Hibernate => NamedKey::Hibernate, // XF86_TouchpadToggle..XF86_TouchpadOff keysyms::XF86_AudioMute => NamedKey::AudioVolumeMute, // XF86_Switch_VT_1..XF86_Switch_VT_12 // XF86_Ungrab..XF86_ClearGrab keysyms::XF86_Next_VMode => NamedKey::VideoModeNext, // keysyms::XF86_Prev_VMode => NamedKey::VideoModePrevious, // XF86_LogWindowTree..XF86_LogGrabInfo // SunFA_Grave..SunFA_Cedilla // keysyms::SunF36 => NamedKey::F36 | NamedKey::F11, // keysyms::SunF37 => NamedKey::F37 | NamedKey::F12, // keysyms::SunSys_Req => NamedKey::PrintScreen, // The next couple of xkb (until SunStop) are already handled. // SunPrint_Screen..SunPageDown // SunUndo..SunFront keysyms::SUN_Copy => NamedKey::Copy, keysyms::SUN_Open => NamedKey::Open, keysyms::SUN_Paste => NamedKey::Paste, keysyms::SUN_Cut => NamedKey::Cut, // SunPowerSwitch keysyms::SUN_AudioLowerVolume => NamedKey::AudioVolumeDown, keysyms::SUN_AudioMute => NamedKey::AudioVolumeMute, keysyms::SUN_AudioRaiseVolume => NamedKey::AudioVolumeUp, // SUN_VideoDegauss keysyms::SUN_VideoLowerBrightness => NamedKey::BrightnessDown, keysyms::SUN_VideoRaiseBrightness => NamedKey::BrightnessUp, // SunPowerSwitchShift 0 => return Key::Unidentified(NativeKey::Unidentified), _ => return Key::Unidentified(NativeKey::Xkb(keysym)), }) } pub fn keysym_location(keysym: u32) -> KeyLocation { use xkbcommon_dl::keysyms; match keysym { keysyms::Shift_L | keysyms::Control_L | keysyms::Meta_L | keysyms::Alt_L | keysyms::Super_L | keysyms::Hyper_L => KeyLocation::Left, keysyms::Shift_R | keysyms::Control_R | keysyms::Meta_R | keysyms::Alt_R | keysyms::Super_R | keysyms::Hyper_R => KeyLocation::Right, keysyms::KP_0 | keysyms::KP_1 | keysyms::KP_2 | keysyms::KP_3 | keysyms::KP_4 | keysyms::KP_5 | keysyms::KP_6 | keysyms::KP_7 | keysyms::KP_8 | keysyms::KP_9 | keysyms::KP_Space | keysyms::KP_Tab | keysyms::KP_Enter | keysyms::KP_F1 | keysyms::KP_F2 | keysyms::KP_F3 | keysyms::KP_F4 | keysyms::KP_Home | keysyms::KP_Left | keysyms::KP_Up | keysyms::KP_Right | keysyms::KP_Down | keysyms::KP_Page_Up | keysyms::KP_Page_Down | keysyms::KP_End | keysyms::KP_Begin | keysyms::KP_Insert | keysyms::KP_Delete | keysyms::KP_Equal | keysyms::KP_Multiply | keysyms::KP_Add | keysyms::KP_Separator | keysyms::KP_Subtract | keysyms::KP_Decimal | keysyms::KP_Divide => KeyLocation::Numpad, _ => KeyLocation::Standard, } } #[derive(Debug)] pub struct XkbKeymap { keymap: NonNull, _mods_indices: ModsIndices, pub _core_keyboard_id: i32, } impl XkbKeymap { #[cfg(wayland_platform)] pub fn from_fd(context: &XkbContext, fd: OwnedFd, size: usize) -> Option { let map = unsafe { MmapOptions::new().len(size).map_copy_read_only(&fd).ok()? }; let keymap = unsafe { let keymap = (XKBH.xkb_keymap_new_from_string)( (*context).as_ptr(), map.as_ptr() as *const _, xkb::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1, xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, ); NonNull::new(keymap)? }; Some(Self::new_inner(keymap, 0)) } #[cfg(x11_platform)] pub fn from_x11_keymap( context: &XkbContext, xcb: *mut xcb_connection_t, core_keyboard_id: i32, ) -> Option { let keymap = unsafe { (XKBXH.xkb_x11_keymap_new_from_device)( context.as_ptr(), xcb, core_keyboard_id, xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, ) }; let keymap = NonNull::new(keymap)?; Some(Self::new_inner(keymap, core_keyboard_id)) } fn new_inner(keymap: NonNull, _core_keyboard_id: i32) -> Self { let mods_indices = ModsIndices { shift: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_SHIFT), caps: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CAPS), ctrl: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CTRL), alt: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_ALT), num: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_NUM), mod3: mod_index_for_name(keymap, b"Mod3\0"), logo: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_LOGO), mod5: mod_index_for_name(keymap, b"Mod5\0"), }; Self { keymap, _mods_indices: mods_indices, _core_keyboard_id } } #[cfg(x11_platform)] pub fn mods_indices(&self) -> ModsIndices { self._mods_indices } pub fn first_keysym_by_level( &mut self, layout: xkb_layout_index_t, keycode: xkb_keycode_t, ) -> xkb_keysym_t { unsafe { let mut keysyms = ptr::null(); let count = (XKBH.xkb_keymap_key_get_syms_by_level)( self.keymap.as_ptr(), keycode, layout, // NOTE: The level should be zero to ignore modifiers. 0, &mut keysyms, ); if count == 1 { *keysyms } else { 0 } } } /// Check whether the given key repeats. pub fn key_repeats(&mut self, keycode: xkb_keycode_t) -> bool { unsafe { (XKBH.xkb_keymap_key_repeats)(self.keymap.as_ptr(), keycode) == 1 } } } impl Drop for XkbKeymap { fn drop(&mut self) { unsafe { (XKBH.xkb_keymap_unref)(self.keymap.as_ptr()); }; } } impl Deref for XkbKeymap { type Target = NonNull; fn deref(&self) -> &Self::Target { &self.keymap } } /// Modifier index in the keymap. #[cfg_attr(not(x11_platform), allow(dead_code))] #[derive(Default, Debug, Clone, Copy)] pub struct ModsIndices { pub shift: Option, pub caps: Option, pub ctrl: Option, pub alt: Option, pub num: Option, pub mod3: Option, pub logo: Option, pub mod5: Option, } fn mod_index_for_name(keymap: NonNull, name: &[u8]) -> Option { unsafe { let mod_index = (XKBH.xkb_keymap_mod_get_index)(keymap.as_ptr(), name.as_ptr() as *const c_char); if mod_index == XKB_MOD_INVALID { None } else { Some(mod_index) } } } winit-0.30.9/src/platform_impl/linux/common/xkb/mod.rs000064400000000000000000000337021046102023000210500ustar 00000000000000use std::ops::Deref; use std::os::raw::c_char; use std::ptr::{self, NonNull}; use std::sync::atomic::{AtomicBool, Ordering}; use crate::utils::Lazy; use smol_str::SmolStr; #[cfg(wayland_platform)] use std::os::unix::io::OwnedFd; use tracing::warn; use xkbcommon_dl::{ self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle, xkbcommon_handle, XkbCommon, XkbCommonCompose, }; #[cfg(x11_platform)] use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle}; use crate::event::{ElementState, KeyEvent}; use crate::keyboard::{Key, KeyLocation}; use crate::platform_impl::KeyEventExtra; mod compose; mod keymap; mod state; use compose::{ComposeStatus, XkbComposeState, XkbComposeTable}; use keymap::XkbKeymap; #[cfg(x11_platform)] pub use keymap::raw_keycode_to_physicalkey; pub use keymap::{physicalkey_to_scancode, scancode_to_physicalkey}; pub use state::XkbState; // TODO: Wire this up without using a static `AtomicBool`. static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false); static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle); static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle); #[cfg(feature = "x11")] static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle); #[inline(always)] pub fn reset_dead_keys() { RESET_DEAD_KEYS.store(true, Ordering::SeqCst); } #[derive(Debug)] pub enum Error { /// libxkbcommon is not available XKBNotFound, } #[derive(Debug)] pub struct Context { // NOTE: field order matters. #[cfg(x11_platform)] pub core_keyboard_id: i32, state: Option, keymap: Option, compose_state1: Option, compose_state2: Option, _compose_table: Option, context: XkbContext, scratch_buffer: Vec, } impl Context { pub fn new() -> Result { if xkb::xkbcommon_option().is_none() { return Err(Error::XKBNotFound); } let context = XkbContext::new()?; let mut compose_table = XkbComposeTable::new(&context); let mut compose_state1 = compose_table.as_ref().and_then(|table| table.new_state()); let mut compose_state2 = compose_table.as_ref().and_then(|table| table.new_state()); // Disable compose if anything compose related failed to initialize. if compose_table.is_none() || compose_state1.is_none() || compose_state2.is_none() { compose_state2 = None; compose_state1 = None; compose_table = None; } Ok(Self { state: None, keymap: None, compose_state1, compose_state2, #[cfg(x11_platform)] core_keyboard_id: 0, _compose_table: compose_table, context, scratch_buffer: Vec::with_capacity(8), }) } #[cfg(feature = "x11")] pub fn from_x11_xkb(xcb: *mut xcb_connection_t) -> Result { let result = unsafe { (XKBXH.xkb_x11_setup_xkb_extension)( xcb, 1, 2, xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ) }; if result != 1 { return Err(Error::XKBNotFound); } let mut this = Self::new()?; this.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(xcb) }; this.set_keymap_from_x11(xcb); Ok(this) } pub fn state_mut(&mut self) -> Option<&mut XkbState> { self.state.as_mut() } pub fn keymap_mut(&mut self) -> Option<&mut XkbKeymap> { self.keymap.as_mut() } #[cfg(wayland_platform)] pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) { let keymap = XkbKeymap::from_fd(&self.context, fd, size); let state = keymap.as_ref().and_then(XkbState::new_wayland); if keymap.is_none() || state.is_none() { warn!("failed to update xkb keymap"); } self.state = state; self.keymap = keymap; } #[cfg(x11_platform)] pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) { let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id); let state = keymap.as_ref().and_then(|keymap| XkbState::new_x11(xcb, keymap)); if keymap.is_none() || state.is_none() { warn!("failed to update xkb keymap"); } self.state = state; self.keymap = keymap; } /// Key builder context with the user provided xkb state. pub fn key_context(&mut self) -> Option> { let state = self.state.as_mut()?; let keymap = self.keymap.as_mut()?; let compose_state1 = self.compose_state1.as_mut(); let compose_state2 = self.compose_state2.as_mut(); let scratch_buffer = &mut self.scratch_buffer; Some(KeyContext { state, keymap, compose_state1, compose_state2, scratch_buffer }) } /// Key builder context with the user provided xkb state. /// /// Should be used when the original context must not be altered. #[cfg(x11_platform)] pub fn key_context_with_state<'a>( &'a mut self, state: &'a mut XkbState, ) -> Option> { let keymap = self.keymap.as_mut()?; let compose_state1 = self.compose_state1.as_mut(); let compose_state2 = self.compose_state2.as_mut(); let scratch_buffer = &mut self.scratch_buffer; Some(KeyContext { state, keymap, compose_state1, compose_state2, scratch_buffer }) } } pub struct KeyContext<'a> { pub state: &'a mut XkbState, pub keymap: &'a mut XkbKeymap, compose_state1: Option<&'a mut XkbComposeState>, compose_state2: Option<&'a mut XkbComposeState>, scratch_buffer: &'a mut Vec, } impl KeyContext<'_> { pub fn process_key_event( &mut self, keycode: u32, state: ElementState, repeat: bool, ) -> KeyEvent { let mut event = KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed); let physical_key = keymap::raw_keycode_to_physicalkey(keycode); let (logical_key, location) = event.key(); let text = event.text(); let (key_without_modifiers, _) = event.key_without_modifiers(); let text_with_all_modifiers = event.text_with_all_modifiers(); let platform_specific = KeyEventExtra { text_with_all_modifiers, key_without_modifiers }; KeyEvent { physical_key, logical_key, text, location, state, repeat, platform_specific } } fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option { self.scratch_buffer.clear(); self.scratch_buffer.reserve(8); loop { let bytes_written = unsafe { (XKBH.xkb_keysym_to_utf8)( keysym, self.scratch_buffer.as_mut_ptr().cast(), self.scratch_buffer.capacity(), ) }; if bytes_written == 0 { return None; } else if bytes_written == -1 { self.scratch_buffer.reserve(8); } else { unsafe { self.scratch_buffer.set_len(bytes_written.try_into().unwrap()) }; break; } } // Remove the null-terminator self.scratch_buffer.pop(); byte_slice_to_smol_str(self.scratch_buffer) } } struct KeyEventResults<'a, 'b> { context: &'a mut KeyContext<'b>, keycode: u32, keysym: u32, compose: ComposeStatus, } impl<'a, 'b> KeyEventResults<'a, 'b> { fn new(context: &'a mut KeyContext<'b>, keycode: u32, compose: bool) -> Self { let keysym = context.state.get_one_sym_raw(keycode); let compose = if let Some(state) = context.compose_state1.as_mut().filter(|_| compose) { if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) { state.reset(); context.compose_state2.as_mut().unwrap().reset(); } state.feed(keysym) } else { ComposeStatus::None }; KeyEventResults { context, keycode, keysym, compose } } pub fn key(&mut self) -> (Key, KeyLocation) { let (key, location) = match self.keysym_to_key(self.keysym) { Ok(known) => return known, Err(undefined) => undefined, }; if let ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSING) = self.compose { let compose_state = self.context.compose_state2.as_mut().unwrap(); // When pressing a dead key twice, the non-combining variant of that character will // be produced. Since this function only concerns itself with a single keypress, we // simulate this double press here by feeding the keysym to the compose state // twice. compose_state.feed(self.keysym); if matches!(compose_state.feed(self.keysym), ComposeStatus::Accepted(_)) { // Extracting only a single `char` here *should* be fine, assuming that no // dead key's non-combining variant ever occupies more than one `char`. let text = compose_state.get_string(self.context.scratch_buffer); let key = Key::Dead(text.and_then(|s| s.chars().next())); (key, location) } else { (key, location) } } else { let key = self .composed_text() .unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym)) .map(Key::Character) .unwrap_or(key); (key, location) } } pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) { // This will become a pointer to an array which libxkbcommon owns, so we don't need to // deallocate it. let layout = self.context.state.layout(self.keycode); let keysym = self.context.keymap.first_keysym_by_level(layout, self.keycode); match self.keysym_to_key(keysym) { Ok((key, location)) => (key, location), Err((key, location)) => { let key = self.context.keysym_to_utf8_raw(keysym).map(Key::Character).unwrap_or(key); (key, location) }, } } fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> { let location = keymap::keysym_location(keysym); let key = keymap::keysym_to_key(keysym); if matches!(key, Key::Unidentified(_)) { Err((key, location)) } else { Ok((key, location)) } } pub fn text(&mut self) -> Option { self.composed_text().unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym)) } // The current behaviour makes it so composing a character overrides attempts to input a // control character with the `Ctrl` key. We can potentially add a configuration option // if someone specifically wants the oppsite behaviour. pub fn text_with_all_modifiers(&mut self) -> Option { match self.composed_text() { Ok(text) => text, Err(_) => self.context.state.get_utf8_raw(self.keycode, self.context.scratch_buffer), } } fn composed_text(&mut self) -> Result, ()> { match self.compose { ComposeStatus::Accepted(status) => match status { xkb_compose_status::XKB_COMPOSE_COMPOSED => { let state = self.context.compose_state1.as_mut().unwrap(); Ok(state.get_string(self.context.scratch_buffer)) }, xkb_compose_status::XKB_COMPOSE_COMPOSING | xkb_compose_status::XKB_COMPOSE_CANCELLED => Ok(None), xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()), }, _ => Err(()), } } } #[derive(Debug)] pub struct XkbContext { context: NonNull, } impl XkbContext { pub fn new() -> Result { let context = unsafe { (XKBH.xkb_context_new)(xkb_context_flags::XKB_CONTEXT_NO_FLAGS) }; let context = match NonNull::new(context) { Some(context) => context, None => return Err(Error::XKBNotFound), }; Ok(Self { context }) } } impl Drop for XkbContext { fn drop(&mut self) { unsafe { (XKBH.xkb_context_unref)(self.context.as_ptr()); } } } impl Deref for XkbContext { type Target = NonNull; fn deref(&self) -> &Self::Target { &self.context } } /// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and /// `xkb_state_key_get_utf8`. fn make_string_with(scratch_buffer: &mut Vec, mut f: F) -> Option where F: FnMut(*mut c_char, usize) -> i32, { let size = f(ptr::null_mut(), 0); if size == 0 { return None; } let size = usize::try_from(size).unwrap(); scratch_buffer.clear(); // The allocated buffer must include space for the null-terminator. scratch_buffer.reserve(size + 1); unsafe { let written = f(scratch_buffer.as_mut_ptr().cast(), scratch_buffer.capacity()); if usize::try_from(written).unwrap() != size { // This will likely never happen. return None; } scratch_buffer.set_len(size); }; byte_slice_to_smol_str(scratch_buffer) } // NOTE: This is track_caller so we can have more informative line numbers when logging #[track_caller] fn byte_slice_to_smol_str(bytes: &[u8]) -> Option { std::str::from_utf8(bytes) .map(SmolStr::new) .map_err(|e| { tracing::warn!("UTF-8 received from libxkbcommon ({:?}) was invalid: {e}", bytes) }) .ok() } winit-0.30.9/src/platform_impl/linux/common/xkb/state.rs000064400000000000000000000134451046102023000214130ustar 00000000000000//! XKB state. use std::os::raw::c_char; use std::ptr::NonNull; use smol_str::SmolStr; #[cfg(x11_platform)] use x11_dl::xlib_xcb::xcb_connection_t; use xkbcommon_dl::{ self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component, }; use crate::platform_impl::common::xkb::keymap::XkbKeymap; #[cfg(x11_platform)] use crate::platform_impl::common::xkb::XKBXH; use crate::platform_impl::common::xkb::{make_string_with, XKBH}; #[derive(Debug)] pub struct XkbState { state: NonNull, modifiers: ModifiersState, } impl XkbState { #[cfg(wayland_platform)] pub fn new_wayland(keymap: &XkbKeymap) -> Option { let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?; Some(Self::new_inner(state)) } #[cfg(x11_platform)] pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option { let state = unsafe { (XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id) }; let state = NonNull::new(state)?; Some(Self::new_inner(state)) } fn new_inner(state: NonNull) -> Self { let modifiers = ModifiersState::default(); let mut this = Self { state, modifiers }; this.reload_modifiers(); this } pub fn get_one_sym_raw(&mut self, keycode: xkb_keycode_t) -> xkb_keysym_t { unsafe { (XKBH.xkb_state_key_get_one_sym)(self.state.as_ptr(), keycode) } } pub fn layout(&mut self, key: xkb_keycode_t) -> xkb_layout_index_t { unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) } } #[cfg(x11_platform)] pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t { unsafe { (XKBH.xkb_state_serialize_mods)( self.state.as_ptr(), xkb_state_component::XKB_STATE_MODS_DEPRESSED, ) } } #[cfg(x11_platform)] pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t { unsafe { (XKBH.xkb_state_serialize_mods)( self.state.as_ptr(), xkb_state_component::XKB_STATE_MODS_LATCHED, ) } } #[cfg(x11_platform)] pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t { unsafe { (XKBH.xkb_state_serialize_mods)( self.state.as_ptr(), xkb_state_component::XKB_STATE_MODS_LOCKED, ) } } pub fn get_utf8_raw( &mut self, keycode: xkb_keycode_t, scratch_buffer: &mut Vec, ) -> Option { make_string_with(scratch_buffer, |ptr, len| unsafe { (XKBH.xkb_state_key_get_utf8)(self.state.as_ptr(), keycode, ptr, len) }) } pub fn modifiers(&self) -> ModifiersState { self.modifiers } pub fn update_modifiers( &mut self, mods_depressed: u32, mods_latched: u32, mods_locked: u32, depressed_group: u32, latched_group: u32, locked_group: u32, ) { let mask = unsafe { (XKBH.xkb_state_update_mask)( self.state.as_ptr(), mods_depressed, mods_latched, mods_locked, depressed_group, latched_group, locked_group, ) }; if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) { // Effective value of mods have changed, we need to update our state. self.reload_modifiers(); } } /// Reload the modifiers. fn reload_modifiers(&mut self) { self.modifiers.ctrl = self.mod_name_is_active(xkb::XKB_MOD_NAME_CTRL); self.modifiers.alt = self.mod_name_is_active(xkb::XKB_MOD_NAME_ALT); self.modifiers.shift = self.mod_name_is_active(xkb::XKB_MOD_NAME_SHIFT); self.modifiers.caps_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_CAPS); self.modifiers.logo = self.mod_name_is_active(xkb::XKB_MOD_NAME_LOGO); self.modifiers.num_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_NUM); } /// Check if the modifier is active within xkb. fn mod_name_is_active(&mut self, name: &[u8]) -> bool { unsafe { (XKBH.xkb_state_mod_name_is_active)( self.state.as_ptr(), name.as_ptr() as *const c_char, xkb_state_component::XKB_STATE_MODS_EFFECTIVE, ) > 0 } } } impl Drop for XkbState { fn drop(&mut self) { unsafe { (XKBH.xkb_state_unref)(self.state.as_ptr()); } } } /// Represents the current state of the keyboard modifiers /// /// Each field of this struct represents a modifier and is `true` if this modifier is active. /// /// For some modifiers, this means that the key is currently pressed, others are toggled /// (like caps lock). #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub struct ModifiersState { /// The "control" key pub ctrl: bool, /// The "alt" key pub alt: bool, /// The "shift" key pub shift: bool, /// The "Caps lock" key pub caps_lock: bool, /// The "logo" key /// /// Also known as the "windows" key on most keyboards pub logo: bool, /// The "Num lock" key pub num_lock: bool, } impl From for crate::keyboard::ModifiersState { fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState { let mut to_mods = crate::keyboard::ModifiersState::empty(); to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift); to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl); to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt); to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo); to_mods } } winit-0.30.9/src/platform_impl/linux/mod.rs000064400000000000000000000776341046102023000170100ustar 00000000000000#![cfg(free_unix)] #[cfg(all(not(x11_platform), not(wayland_platform)))] compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); use std::collections::VecDeque; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::sync::Arc; use std::time::Duration; use std::{env, fmt}; #[cfg(x11_platform)] use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex}; #[cfg(x11_platform)] use crate::utils::Lazy; use smol_str::SmolStr; #[cfg(x11_platform)] use self::x11::{X11Error, XConnection, XError, XNotSupported}; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError}; use crate::event_loop::{ ActiveEventLoop as RootELW, AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed, }; use crate::icon::Icon; use crate::keyboard::Key; use crate::platform::pump_events::PumpStatus; #[cfg(x11_platform)] use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook}; use crate::window::{ ActivationToken, Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }; pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey}; pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; pub(crate) mod common; #[cfg(wayland_platform)] pub(crate) mod wayland; #[cfg(x11_platform)] pub(crate) mod x11; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum Backend { #[cfg(x11_platform)] X, #[cfg(wayland_platform)] Wayland, } #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) forced_backend: Option, pub(crate) any_thread: bool, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ApplicationName { pub general: String, pub instance: String, } impl ApplicationName { pub fn new(general: String, instance: String) -> Self { Self { general, instance } } } #[derive(Clone, Debug)] pub struct PlatformSpecificWindowAttributes { pub name: Option, pub activation_token: Option, #[cfg(x11_platform)] pub x11: X11WindowAttributes, } #[derive(Clone, Debug)] #[cfg(x11_platform)] pub struct X11WindowAttributes { pub visual_id: Option, pub screen_id: Option, pub base_size: Option, pub override_redirect: bool, pub x11_window_types: Vec, /// The parent window to embed this window into. pub embed_window: Option, } #[cfg_attr(not(x11_platform), allow(clippy::derivable_impls))] impl Default for PlatformSpecificWindowAttributes { fn default() -> Self { Self { name: None, activation_token: None, #[cfg(x11_platform)] x11: X11WindowAttributes { visual_id: None, screen_id: None, base_size: None, override_redirect: false, x11_window_types: vec![XWindowType::Normal], embed_window: None, }, } } } #[cfg(x11_platform)] pub(crate) static X11_BACKEND: Lazy, XNotSupported>>> = Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))); #[derive(Debug, Clone)] pub enum OsError { Misc(&'static str), #[cfg(x11_platform)] XNotSupported(XNotSupported), #[cfg(x11_platform)] XError(Arc), #[cfg(wayland_platform)] WaylandError(Arc), } impl fmt::Display for OsError { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match *self { OsError::Misc(e) => _f.pad(e), #[cfg(x11_platform)] OsError::XNotSupported(ref e) => fmt::Display::fmt(e, _f), #[cfg(x11_platform)] OsError::XError(ref e) => fmt::Display::fmt(e, _f), #[cfg(wayland_platform)] OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f), } } } pub(crate) enum Window { #[cfg(x11_platform)] X(x11::Window), #[cfg(wayland_platform)] Wayland(wayland::Window), } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(u64); impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.0 } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id) } } impl WindowId { pub const fn dummy() -> Self { Self(0) } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DeviceId { #[cfg(x11_platform)] X(x11::DeviceId), #[cfg(wayland_platform)] Wayland(wayland::DeviceId), } impl DeviceId { pub const fn dummy() -> Self { #[cfg(wayland_platform)] return DeviceId::Wayland(wayland::DeviceId::dummy()); #[cfg(all(not(wayland_platform), x11_platform))] return DeviceId::X(x11::DeviceId::dummy()); } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum MonitorHandle { #[cfg(x11_platform)] X(x11::MonitorHandle), #[cfg(wayland_platform)] Wayland(wayland::MonitorHandle), } /// `x11_or_wayland!(match expr; Enum(foo) => foo.something())` /// expands to the equivalent of /// ```ignore /// match self { /// Enum::X(foo) => foo.something(), /// Enum::Wayland(foo) => foo.something(), /// } /// ``` /// The result can be converted to another enum by adding `; as AnotherEnum` macro_rules! x11_or_wayland { (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => { match $what { #[cfg(x11_platform)] $enum::X($($c1)*) => $enum2::X($x), #[cfg(wayland_platform)] $enum::Wayland($($c1)*) => $enum2::Wayland($x), } }; (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => { match $what { #[cfg(x11_platform)] $enum::X($($c1)*) => $x, #[cfg(wayland_platform)] $enum::Wayland($($c1)*) => $x, } }; } impl MonitorHandle { #[inline] pub fn name(&self) -> Option { x11_or_wayland!(match self; MonitorHandle(m) => m.name()) } #[inline] pub fn native_identifier(&self) -> u32 { x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier()) } #[inline] pub fn size(&self) -> PhysicalSize { x11_or_wayland!(match self; MonitorHandle(m) => m.size()) } #[inline] pub fn position(&self) -> PhysicalPosition { x11_or_wayland!(match self; MonitorHandle(m) => m.position()) } #[inline] pub fn refresh_rate_millihertz(&self) -> Option { x11_or_wayland!(match self; MonitorHandle(m) => m.refresh_rate_millihertz()) } #[inline] pub fn scale_factor(&self) -> f64 { x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _) } #[inline] pub fn video_modes(&self) -> Box> { x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes())) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum VideoModeHandle { #[cfg(x11_platform)] X(x11::VideoModeHandle), #[cfg(wayland_platform)] Wayland(wayland::VideoModeHandle), } impl VideoModeHandle { #[inline] pub fn size(&self) -> PhysicalSize { x11_or_wayland!(match self; VideoModeHandle(m) => m.size()) } #[inline] pub fn bit_depth(&self) -> u16 { x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth()) } #[inline] pub fn refresh_rate_millihertz(&self) -> u32 { x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz()) } #[inline] pub fn monitor(&self) -> MonitorHandle { x11_or_wayland!(match self; VideoModeHandle(m) => m.monitor(); as MonitorHandle) } } impl Window { #[inline] pub(crate) fn new( window_target: &ActiveEventLoop, attribs: WindowAttributes, ) -> Result { match *window_target { #[cfg(wayland_platform)] ActiveEventLoop::Wayland(ref window_target) => { wayland::Window::new(window_target, attribs).map(Window::Wayland) }, #[cfg(x11_platform)] ActiveEventLoop::X(ref window_target) => { x11::Window::new(window_target, attribs).map(Window::X) }, } } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { f(self) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { f(self) } #[inline] pub fn id(&self) -> WindowId { x11_or_wayland!(match self; Window(w) => w.id()) } #[inline] pub fn set_title(&self, title: &str) { x11_or_wayland!(match self; Window(w) => w.set_title(title)); } #[inline] pub fn set_transparent(&self, transparent: bool) { x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent)); } #[inline] pub fn set_blur(&self, blur: bool) { x11_or_wayland!(match self; Window(w) => w.set_blur(blur)); } #[inline] pub fn set_visible(&self, visible: bool) { x11_or_wayland!(match self; Window(w) => w.set_visible(visible)) } #[inline] pub fn is_visible(&self) -> Option { x11_or_wayland!(match self; Window(w) => w.is_visible()) } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { x11_or_wayland!(match self; Window(w) => w.outer_position()) } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { x11_or_wayland!(match self; Window(w) => w.inner_position()) } #[inline] pub fn set_outer_position(&self, position: Position) { x11_or_wayland!(match self; Window(w) => w.set_outer_position(position)) } #[inline] pub fn inner_size(&self) -> PhysicalSize { x11_or_wayland!(match self; Window(w) => w.inner_size()) } #[inline] pub fn outer_size(&self) -> PhysicalSize { x11_or_wayland!(match self; Window(w) => w.outer_size()) } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { x11_or_wayland!(match self; Window(w) => w.request_inner_size(size)) } #[inline] pub(crate) fn request_activation_token(&self) -> Result { x11_or_wayland!(match self; Window(w) => w.request_activation_token()) } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { x11_or_wayland!(match self; Window(w) => w.set_min_inner_size(dimensions)) } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions)) } #[inline] pub fn resize_increments(&self) -> Option> { x11_or_wayland!(match self; Window(w) => w.resize_increments()) } #[inline] pub fn set_resize_increments(&self, increments: Option) { x11_or_wayland!(match self; Window(w) => w.set_resize_increments(increments)) } #[inline] pub fn set_resizable(&self, resizable: bool) { x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable)) } #[inline] pub fn is_resizable(&self) -> bool { x11_or_wayland!(match self; Window(w) => w.is_resizable()) } #[inline] pub fn set_enabled_buttons(&self, buttons: WindowButtons) { x11_or_wayland!(match self; Window(w) => w.set_enabled_buttons(buttons)) } #[inline] pub fn enabled_buttons(&self) -> WindowButtons { x11_or_wayland!(match self; Window(w) => w.enabled_buttons()) } #[inline] pub fn set_cursor(&self, cursor: Cursor) { x11_or_wayland!(match self; Window(w) => w.set_cursor(cursor)) } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode)) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible)) } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(window) => window.drag_window()) } #[inline] pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction)) } #[inline] pub fn show_window_menu(&self, position: Position) { x11_or_wayland!(match self; Window(w) => w.show_window_menu(position)) } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest)) } #[inline] pub fn scale_factor(&self) -> f64 { x11_or_wayland!(match self; Window(w) => w.scale_factor()) } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(w) => w.set_cursor_position(position)) } #[inline] pub fn set_maximized(&self, maximized: bool) { x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized)) } #[inline] pub fn is_maximized(&self) -> bool { x11_or_wayland!(match self; Window(w) => w.is_maximized()) } #[inline] pub fn set_minimized(&self, minimized: bool) { x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized)) } #[inline] pub fn is_minimized(&self) -> Option { x11_or_wayland!(match self; Window(w) => w.is_minimized()) } #[inline] pub(crate) fn fullscreen(&self) -> Option { x11_or_wayland!(match self; Window(w) => w.fullscreen()) } #[inline] pub(crate) fn set_fullscreen(&self, monitor: Option) { x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor)) } #[inline] pub fn set_decorations(&self, decorations: bool) { x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations)) } #[inline] pub fn is_decorated(&self) -> bool { x11_or_wayland!(match self; Window(w) => w.is_decorated()) } #[inline] pub fn set_window_level(&self, level: WindowLevel) { x11_or_wayland!(match self; Window(w) => w.set_window_level(level)) } #[inline] pub fn set_window_icon(&self, window_icon: Option) { x11_or_wayland!(match self; Window(w) => w.set_window_icon(window_icon.map(|icon| icon.inner))) } #[inline] pub fn set_ime_cursor_area(&self, position: Position, size: Size) { x11_or_wayland!(match self; Window(w) => w.set_ime_cursor_area(position, size)) } #[inline] pub fn reset_dead_keys(&self) { common::xkb::reset_dead_keys() } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed)) } #[inline] pub fn set_ime_purpose(&self, purpose: ImePurpose) { x11_or_wayland!(match self; Window(w) => w.set_ime_purpose(purpose)) } #[inline] pub fn focus_window(&self) { x11_or_wayland!(match self; Window(w) => w.focus_window()) } pub fn request_user_attention(&self, request_type: Option) { x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type)) } #[inline] pub fn request_redraw(&self) { x11_or_wayland!(match self; Window(w) => w.request_redraw()) } #[inline] pub fn pre_present_notify(&self) { x11_or_wayland!(match self; Window(w) => w.pre_present_notify()) } #[inline] pub fn current_monitor(&self) -> Option { Some(x11_or_wayland!(match self; Window(w) => w.current_monitor()?; as MonitorHandle)) } #[inline] pub fn available_monitors(&self) -> VecDeque { match self { #[cfg(x11_platform)] Window::X(ref window) => { window.available_monitors().into_iter().map(MonitorHandle::X).collect() }, #[cfg(wayland_platform)] Window::Wayland(ref window) => { window.available_monitors().into_iter().map(MonitorHandle::Wayland).collect() }, } } #[inline] pub fn primary_monitor(&self) -> Option { Some(x11_or_wayland!(match self; Window(w) => w.primary_monitor()?; as MonitorHandle)) } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_04()) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_05()) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_05()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_06()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_06()) } #[inline] pub fn set_theme(&self, theme: Option) { x11_or_wayland!(match self; Window(window) => window.set_theme(theme)) } #[inline] pub fn theme(&self) -> Option { x11_or_wayland!(match self; Window(window) => window.theme()) } pub fn set_content_protected(&self, protected: bool) { x11_or_wayland!(match self; Window(window) => window.set_content_protected(protected)) } #[inline] pub fn has_focus(&self) -> bool { x11_or_wayland!(match self; Window(window) => window.has_focus()) } pub fn title(&self) -> String { x11_or_wayland!(match self; Window(window) => window.title()) } } #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct KeyEventExtra { pub text_with_all_modifiers: Option, pub key_without_modifiers: Key, } #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub(crate) enum PlatformCustomCursor { #[cfg(wayland_platform)] Wayland(wayland::CustomCursor), #[cfg(x11_platform)] X(x11::CustomCursor), } /// Hooks for X11 errors. #[cfg(x11_platform)] pub(crate) static XLIB_ERROR_HOOKS: Mutex> = Mutex::new(Vec::new()); #[cfg(x11_platform)] unsafe extern "C" fn x_error_callback( display: *mut x11::ffi::Display, event: *mut x11::ffi::XErrorEvent, ) -> c_int { let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()); if let Ok(ref xconn) = *xconn_lock { // Call all the hooks. let mut error_handled = false; for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() { error_handled |= hook(display as *mut _, event as *mut _); } // `assume_init` is safe here because the array consists of `MaybeUninit` values, // which do not require initialization. let mut buf: [MaybeUninit; 1024] = unsafe { MaybeUninit::uninit().assume_init() }; unsafe { (xconn.xlib.XGetErrorText)( display, (*event).error_code as c_int, buf.as_mut_ptr() as *mut c_char, buf.len() as c_int, ) }; let description = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy(); let error = unsafe { XError { description: description.into_owned(), error_code: (*event).error_code, request_code: (*event).request_code, minor_code: (*event).minor_code, } }; // Don't log error. if !error_handled { tracing::error!("X11 error: {:#?}", error); // XXX only update the error, if it wasn't handled by any of the hooks. *xconn.latest_error.lock().unwrap() = Some(error); } } // Fun fact: this return value is completely ignored. 0 } pub enum EventLoop { #[cfg(wayland_platform)] Wayland(Box>), #[cfg(x11_platform)] X(x11::EventLoop), } pub enum EventLoopProxy { #[cfg(x11_platform)] X(x11::EventLoopProxy), #[cfg(wayland_platform)] Wayland(wayland::EventLoopProxy), } impl Clone for EventLoopProxy { fn clone(&self) -> Self { x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy) } } impl EventLoop { pub(crate) fn new( attributes: &PlatformSpecificEventLoopAttributes, ) -> Result { if !attributes.any_thread && !is_main_thread() { panic!( "Initializing the event loop outside of the main thread is a significant \ cross-platform compatibility hazard. If you absolutely need to create an \ EventLoop on a different thread, you can use the \ `EventLoopBuilderExtX11::any_thread` or `EventLoopBuilderExtWayland::any_thread` \ functions." ); } // NOTE: Wayland first because of X11 could be present under Wayland as well. Empty // variables are also treated as not set. let backend = match ( attributes.forced_backend, env::var("WAYLAND_DISPLAY") .ok() .filter(|var| !var.is_empty()) .or_else(|| env::var("WAYLAND_SOCKET").ok()) .filter(|var| !var.is_empty()) .is_some(), env::var("DISPLAY").map(|var| !var.is_empty()).unwrap_or(false), ) { // User is forcing a backend. (Some(backend), ..) => backend, // Wayland is present. #[cfg(wayland_platform)] (None, true, _) => Backend::Wayland, // X11 is present. #[cfg(x11_platform)] (None, _, true) => Backend::X, // No backend is present. (_, wayland_display, x11_display) => { let msg = if wayland_display && !cfg!(wayland_platform) { "DISPLAY is not set; note: enable the `winit/wayland` feature to support \ Wayland" } else if x11_display && !cfg!(x11_platform) { "neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the \ `winit/x11` feature to support X11" } else { "neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set." }; return Err(EventLoopError::Os(os_error!(OsError::Misc(msg)))); }, }; // Create the display based on the backend. match backend { #[cfg(wayland_platform)] Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into), #[cfg(x11_platform)] Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into), } } #[cfg(wayland_platform)] fn new_wayland_any_thread() -> Result, EventLoopError> { wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp))) } #[cfg(x11_platform)] fn new_x11_any_thread() -> Result, EventLoopError> { let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() { Ok(xconn) => xconn.clone(), Err(err) => { return Err(EventLoopError::Os(os_error!(OsError::XNotSupported(err.clone())))) }, }; Ok(EventLoop::X(x11::EventLoop::new(xconn))) } #[inline] pub fn is_wayland(&self) -> bool { match *self { #[cfg(wayland_platform)] EventLoop::Wayland(_) => true, #[cfg(x11_platform)] _ => false, } } pub fn create_proxy(&self) -> EventLoopProxy { x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) } pub fn run(mut self, callback: F) -> Result<(), EventLoopError> where F: FnMut(crate::event::Event, &RootELW), { self.run_on_demand(callback) } pub fn run_on_demand(&mut self, callback: F) -> Result<(), EventLoopError> where F: FnMut(crate::event::Event, &RootELW), { x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback)) } pub fn pump_events(&mut self, timeout: Option, callback: F) -> PumpStatus where F: FnMut(crate::event::Event, &RootELW), { x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback)) } pub fn window_target(&self) -> &crate::event_loop::ActiveEventLoop { x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target()) } } impl AsFd for EventLoop { fn as_fd(&self) -> BorrowedFd<'_> { x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd()) } } impl AsRawFd for EventLoop { fn as_raw_fd(&self) -> RawFd { x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd()) } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event)) } } pub enum ActiveEventLoop { #[cfg(wayland_platform)] Wayland(wayland::ActiveEventLoop), #[cfg(x11_platform)] X(x11::ActiveEventLoop), } impl ActiveEventLoop { #[inline] pub fn is_wayland(&self) -> bool { match *self { #[cfg(wayland_platform)] ActiveEventLoop::Wayland(_) => true, #[cfg(x11_platform)] _ => false, } } pub fn create_custom_cursor(&self, cursor: CustomCursorSource) -> CustomCursor { x11_or_wayland!(match self; ActiveEventLoop(evlp) => evlp.create_custom_cursor(cursor)) } #[inline] pub fn available_monitors(&self) -> VecDeque { match *self { #[cfg(wayland_platform)] ActiveEventLoop::Wayland(ref evlp) => { evlp.available_monitors().map(MonitorHandle::Wayland).collect() }, #[cfg(x11_platform)] ActiveEventLoop::X(ref evlp) => { evlp.available_monitors().map(MonitorHandle::X).collect() }, } } #[inline] pub fn primary_monitor(&self) -> Option { Some( x11_or_wayland!(match self; ActiveEventLoop(evlp) => evlp.primary_monitor()?; as MonitorHandle), ) } #[inline] pub fn listen_device_events(&self, allowed: DeviceEvents) { x11_or_wayland!(match self; Self(evlp) => evlp.listen_device_events(allowed)) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05()) } #[inline] pub fn system_theme(&self) -> Option { None } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_06()) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { x11_or_wayland!(match self; Self(evlp) => evlp.set_control_flow(control_flow)) } pub(crate) fn control_flow(&self) -> ControlFlow { x11_or_wayland!(match self; Self(evlp) => evlp.control_flow()) } pub(crate) fn clear_exit(&self) { x11_or_wayland!(match self; Self(evlp) => evlp.clear_exit()) } pub(crate) fn exit(&self) { x11_or_wayland!(match self; Self(evlp) => evlp.exit()) } pub(crate) fn exiting(&self) -> bool { x11_or_wayland!(match self; Self(evlp) => evlp.exiting()) } pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle { match self { #[cfg(x11_platform)] Self::X(conn) => OwnedDisplayHandle::X(conn.x_connection().clone()), #[cfg(wayland_platform)] Self::Wayland(conn) => OwnedDisplayHandle::Wayland(conn.connection.clone()), } } #[allow(dead_code)] fn set_exit_code(&self, code: i32) { x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code)) } #[allow(dead_code)] fn exit_code(&self) -> Option { x11_or_wayland!(match self; Self(evlp) => evlp.exit_code()) } } #[derive(Clone)] #[allow(dead_code)] pub(crate) enum OwnedDisplayHandle { #[cfg(x11_platform)] X(Arc), #[cfg(wayland_platform)] Wayland(wayland_client::Connection), } impl OwnedDisplayHandle { #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { match self { #[cfg(x11_platform)] Self::X(xconn) => { let mut xlib_handle = rwh_05::XlibDisplayHandle::empty(); xlib_handle.display = xconn.display.cast(); xlib_handle.screen = xconn.default_screen_index() as _; xlib_handle.into() }, #[cfg(wayland_platform)] Self::Wayland(conn) => { use sctk::reexports::client::Proxy; let mut wayland_handle = rwh_05::WaylandDisplayHandle::empty(); wayland_handle.display = conn.display().id().as_ptr() as *mut _; wayland_handle.into() }, } } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { use std::ptr::NonNull; match self { #[cfg(x11_platform)] Self::X(xconn) => Ok(rwh_06::XlibDisplayHandle::new( NonNull::new(xconn.display.cast()), xconn.default_screen_index() as _, ) .into()), #[cfg(wayland_platform)] Self::Wayland(conn) => { use sctk::reexports::client::Proxy; Ok(rwh_06::WaylandDisplayHandle::new( NonNull::new(conn.display().id().as_ptr().cast()).unwrap(), ) .into()) }, } } } /// Returns the minimum `Option`, taking into account that `None` /// equates to an infinite timeout, not a zero timeout (so can't just use /// `Option::min`) fn min_timeout(a: Option, b: Option) -> Option { a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))) } #[cfg(target_os = "linux")] fn is_main_thread() -> bool { rustix::thread::gettid() == rustix::process::getpid() } #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] fn is_main_thread() -> bool { use libc::pthread_main_np; unsafe { pthread_main_np() == 1 } } #[cfg(target_os = "netbsd")] fn is_main_thread() -> bool { std::thread::current().name() == Some("main") } winit-0.30.9/src/platform_impl/linux/wayland/event_loop/mod.rs000064400000000000000000000614311046102023000226050ustar 00000000000000//! The event-loop routines. use std::cell::{Cell, RefCell}; use std::io::Result as IOResult; use std::marker::PhantomData; use std::mem; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::rc::Rc; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use sctk::reexports::calloop::Error as CalloopError; use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::reexports::client::{globals, Connection, QueueHandle}; use crate::cursor::OnlyCursorImage; use crate::dpi::LogicalSize; use crate::error::{EventLoopError, OsError as RootOsError}; use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent}; use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents}; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::platform::min_timeout; use crate::platform_impl::{ ActiveEventLoop as PlatformActiveEventLoop, OsError, PlatformCustomCursor, }; use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource}; mod proxy; pub mod sink; pub use proxy::EventLoopProxy; use sink::EventSink; use super::state::{WindowCompositorUpdate, WinitState}; use super::window::state::FrameCallbackState; use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId}; type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>; /// The Wayland event loop. pub struct EventLoop { /// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop loop_running: bool, buffer_sink: EventSink, compositor_updates: Vec, window_ids: Vec, /// Sender of user events. user_events_sender: calloop::channel::Sender, // XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which // we don't really want, since it'll break public API by a lot. /// Pending events from the user. pending_user_events: Rc>>, /// The Wayland dispatcher to has raw access to the queue when needed, such as /// when creating a new window. wayland_dispatcher: WaylandDispatcher, /// Connection to the wayland server. connection: Connection, /// Event loop window target. window_target: RootActiveEventLoop, // XXX drop after everything else, just to be safe. /// Calloop's event loop. event_loop: calloop::EventLoop<'static, WinitState>, } impl EventLoop { pub fn new() -> Result, EventLoopError> { macro_rules! map_err { ($e:expr, $err:expr) => { $e.map_err(|error| os_error!($err(error).into())) }; } let connection = map_err!(Connection::connect_to_env(), WaylandError::Connection)?; let (globals, mut event_queue) = map_err!(globals::registry_queue_init(&connection), WaylandError::Global)?; let queue_handle = event_queue.handle(); let event_loop = map_err!(calloop::EventLoop::::try_new(), WaylandError::Calloop)?; let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle()) .map_err(|error| os_error!(error))?; // NOTE: do a roundtrip after binding the globals to prevent potential // races with the server. map_err!(event_queue.roundtrip(&mut winit_state), WaylandError::Dispatch)?; // Register Wayland source. let wayland_source = WaylandSource::new(connection.clone(), event_queue); let wayland_dispatcher = calloop::Dispatcher::new(wayland_source, |_, queue, winit_state: &mut WinitState| { let result = queue.dispatch_pending(winit_state); if result.is_ok() && (!winit_state.events_sink.is_empty() || !winit_state.window_compositor_updates.is_empty()) { winit_state.dispatched_events = true; } result }); map_err!( event_loop.handle().register_dispatcher(wayland_dispatcher.clone()), WaylandError::Calloop )?; // Setup the user proxy. let pending_user_events = Rc::new(RefCell::new(Vec::new())); let pending_user_events_clone = pending_user_events.clone(); let (user_events_sender, user_events_channel) = calloop::channel::channel(); let result = event_loop .handle() .insert_source(user_events_channel, move |event, _, winit_state: &mut WinitState| { if let calloop::channel::Event::Msg(msg) = event { winit_state.dispatched_events = true; pending_user_events_clone.borrow_mut().push(msg); } }) .map_err(|error| error.error); map_err!(result, WaylandError::Calloop)?; // An event's loop awakener to wake up for window events from winit's windows. let (event_loop_awakener, event_loop_awakener_source) = map_err!( calloop::ping::make_ping() .map_err(|error| CalloopError::OtherError(Box::new(error).into())), WaylandError::Calloop )?; let result = event_loop .handle() .insert_source(event_loop_awakener_source, move |_, _, winit_state: &mut WinitState| { // Mark that we have something to dispatch. winit_state.dispatched_events = true; }) .map_err(|error| error.error); map_err!(result, WaylandError::Calloop)?; let window_target = ActiveEventLoop { connection: connection.clone(), wayland_dispatcher: wayland_dispatcher.clone(), event_loop_awakener, queue_handle, control_flow: Cell::new(ControlFlow::default()), exit: Cell::new(None), state: RefCell::new(winit_state), }; let event_loop = Self { loop_running: false, compositor_updates: Vec::new(), buffer_sink: EventSink::default(), window_ids: Vec::new(), connection, wayland_dispatcher, user_events_sender, pending_user_events, event_loop, window_target: RootActiveEventLoop { p: PlatformActiveEventLoop::Wayland(window_target), _marker: PhantomData, }, }; Ok(event_loop) } pub fn run_on_demand(&mut self, mut event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootActiveEventLoop), { let exit = loop { match self.pump_events(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); }, PumpStatus::Exit(code) => { break Err(EventLoopError::ExitFailure(code)); }, _ => { continue; }, } }; // Applications aren't allowed to carry windows between separate // `run_on_demand` calls but if they have only just dropped their // windows we need to make sure those last requests are sent to the // compositor. let _ = self.roundtrip().map_err(EventLoopError::Os); exit } pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus where F: FnMut(Event, &RootActiveEventLoop), { if !self.loop_running { self.loop_running = true; // Run the initial loop iteration. self.single_iteration(&mut callback, StartCause::Init); } // Consider the possibility that the `StartCause::Init` iteration could // request to Exit. if !self.exiting() { self.poll_events_with_timeout(timeout, &mut callback); } if let Some(code) = self.exit_code() { self.loop_running = false; callback(Event::LoopExiting, self.window_target()); PumpStatus::Exit(code) } else { PumpStatus::Continue } } pub fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) where F: FnMut(Event, &RootActiveEventLoop), { let cause = loop { let start = Instant::now(); timeout = { let control_flow_timeout = match self.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::WaitUntil(wait_deadline) => { Some(wait_deadline.saturating_duration_since(start)) }, }; min_timeout(control_flow_timeout, timeout) }; // NOTE Ideally we should flush as the last thing we do before polling // to wait for events, and this should be done by the calloop // WaylandSource but we currently need to flush writes manually. // // Checking for flush error is essential to perform an exit with error, since // once we have a protocol error, we could get stuck retrying... if self.connection.flush().is_err() { self.set_exit_code(1); return; } if let Err(error) = self.loop_dispatch(timeout) { // NOTE We exit on errors from dispatches, since if we've got protocol error // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is // not really an option. Instead we inform that the event loop got // destroyed. We may communicate an error that something was // terminated, but winit doesn't provide us with an API to do that // via some event. Still, we set the exit code to the error's OS // error code, or to 1 if not possible. let exit_code = error.raw_os_error().unwrap_or(1); self.set_exit_code(exit_code); return; } // NB: `StartCause::Init` is handled as a special case and doesn't need // to be considered here let cause = match self.control_flow() { ControlFlow::Poll => StartCause::Poll, ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None }, ControlFlow::WaitUntil(deadline) => { if Instant::now() < deadline { StartCause::WaitCancelled { start, requested_resume: Some(deadline) } } else { StartCause::ResumeTimeReached { start, requested_resume: deadline } } }, }; // Reduce spurious wake-ups. let dispatched_events = self.with_state(|state| state.dispatched_events); if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events { continue; } break cause; }; self.single_iteration(&mut callback, cause); } fn single_iteration(&mut self, callback: &mut F, cause: StartCause) where F: FnMut(Event, &RootActiveEventLoop), { // NOTE currently just indented to simplify the diff // We retain these grow-only scratch buffers as part of the EventLoop // for the sake of avoiding lots of reallocs. We take them here to avoid // trying to mutably borrow `self` more than once and we swap them back // when finished. let mut compositor_updates = std::mem::take(&mut self.compositor_updates); let mut buffer_sink = std::mem::take(&mut self.buffer_sink); let mut window_ids = std::mem::take(&mut self.window_ids); callback(Event::NewEvents(cause), &self.window_target); // NB: For consistency all platforms must emit a 'resumed' event even though Wayland // applications don't themselves have a formal suspend/resume lifecycle. if cause == StartCause::Init { callback(Event::Resumed, &self.window_target); } // Handle pending user events. We don't need back buffer, since we can't dispatch // user events indirectly via callback to the user. for user_event in self.pending_user_events.borrow_mut().drain(..) { callback(Event::UserEvent(user_event), &self.window_target); } // Drain the pending compositor updates. self.with_state(|state| compositor_updates.append(&mut state.window_compositor_updates)); for mut compositor_update in compositor_updates.drain(..) { let window_id = compositor_update.window_id; if compositor_update.scale_changed { let (physical_size, scale_factor) = self.with_state(|state| { let windows = state.windows.get_mut(); let window = windows.get(&window_id).unwrap().lock().unwrap(); let scale_factor = window.scale_factor(); let size = logical_to_physical_rounded(window.inner_size(), scale_factor); (size, scale_factor) }); // Stash the old window size. let old_physical_size = physical_size; let new_inner_size = Arc::new(Mutex::new(physical_size)); callback( Event::WindowEvent { window_id: crate::window::WindowId(window_id), event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade( &new_inner_size, )), }, }, &self.window_target, ); let physical_size = *new_inner_size.lock().unwrap(); drop(new_inner_size); // Resize the window when user altered the size. if old_physical_size != physical_size { self.with_state(|state| { let windows = state.windows.get_mut(); let mut window = windows.get(&window_id).unwrap().lock().unwrap(); let new_logical_size: LogicalSize = physical_size.to_logical(scale_factor); window.request_inner_size(new_logical_size.into()); }); // Make it queue resize. compositor_update.resized = true; } } // NOTE: Rescale changed the physical size which winit operates in, thus we should // resize. if compositor_update.resized || compositor_update.scale_changed { let physical_size = self.with_state(|state| { let windows = state.windows.get_mut(); let window = windows.get(&window_id).unwrap().lock().unwrap(); let scale_factor = window.scale_factor(); let size = logical_to_physical_rounded(window.inner_size(), scale_factor); // Mark the window as needed a redraw. state .window_requests .get_mut() .get_mut(&window_id) .unwrap() .redraw_requested .store(true, Ordering::Relaxed); size }); callback( Event::WindowEvent { window_id: crate::window::WindowId(window_id), event: WindowEvent::Resized(physical_size), }, &self.window_target, ); } if compositor_update.close_window { callback( Event::WindowEvent { window_id: crate::window::WindowId(window_id), event: WindowEvent::CloseRequested, }, &self.window_target, ); } } // Push the events directly from the window. self.with_state(|state| { buffer_sink.append(&mut state.window_events_sink.lock().unwrap()); }); for event in buffer_sink.drain() { let event = event.map_nonuser_event().unwrap(); callback(event, &self.window_target); } // Handle non-synthetic events. self.with_state(|state| { buffer_sink.append(&mut state.events_sink); }); for event in buffer_sink.drain() { let event = event.map_nonuser_event().unwrap(); callback(event, &self.window_target); } // Collect the window ids self.with_state(|state| { window_ids.extend(state.window_requests.get_mut().keys()); }); for window_id in window_ids.iter() { let event = self.with_state(|state| { let window_requests = state.window_requests.get_mut(); if window_requests.get(window_id).unwrap().take_closed() { mem::drop(window_requests.remove(window_id)); mem::drop(state.windows.get_mut().remove(window_id)); return Some(WindowEvent::Destroyed); } let mut window = state.windows.get_mut().get_mut(window_id).unwrap().lock().unwrap(); if window.frame_callback_state() == FrameCallbackState::Requested { return None; } // Reset the frame callbacks state. window.frame_callback_reset(); let mut redraw_requested = window_requests.get(window_id).unwrap().take_redraw_requested(); // Redraw the frame while at it. redraw_requested |= window.refresh_frame(); redraw_requested.then_some(WindowEvent::RedrawRequested) }); if let Some(event) = event { callback( Event::WindowEvent { window_id: crate::window::WindowId(*window_id), event }, &self.window_target, ); } } // Reset the hint that we've dispatched events. self.with_state(|state| { state.dispatched_events = false; }); // This is always the last event we dispatch before poll again callback(Event::AboutToWait, &self.window_target); // Update the window frames and schedule redraws. let mut wake_up = false; for window_id in window_ids.drain(..) { wake_up |= self.with_state(|state| match state.windows.get_mut().get_mut(&window_id) { Some(window) => { let refresh = window.lock().unwrap().refresh_frame(); if refresh { state .window_requests .get_mut() .get_mut(&window_id) .unwrap() .redraw_requested .store(true, Ordering::Relaxed); } refresh }, None => false, }); } // Wakeup event loop if needed. // // If the user draws from the `AboutToWait` this is likely not required, however // we can't do much about it. if wake_up { match &self.window_target.p { PlatformActiveEventLoop::Wayland(window_target) => { window_target.event_loop_awakener.ping(); }, #[cfg(x11_platform)] PlatformActiveEventLoop::X(_) => unreachable!(), } } std::mem::swap(&mut self.compositor_updates, &mut compositor_updates); std::mem::swap(&mut self.buffer_sink, &mut buffer_sink); std::mem::swap(&mut self.window_ids, &mut window_ids); } #[inline] pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy::new(self.user_events_sender.clone()) } #[inline] pub fn window_target(&self) -> &RootActiveEventLoop { &self.window_target } fn with_state<'a, U: 'a, F: FnOnce(&'a mut WinitState) -> U>(&'a mut self, callback: F) -> U { let state = match &mut self.window_target.p { PlatformActiveEventLoop::Wayland(window_target) => window_target.state.get_mut(), #[cfg(x11_platform)] _ => unreachable!(), }; callback(state) } fn loop_dispatch>>(&mut self, timeout: D) -> IOResult<()> { let state = match &mut self.window_target.p { PlatformActiveEventLoop::Wayland(window_target) => window_target.state.get_mut(), #[cfg(feature = "x11")] _ => unreachable!(), }; self.event_loop.dispatch(timeout, state).map_err(|error| { tracing::error!("Error dispatching event loop: {}", error); error.into() }) } fn roundtrip(&mut self) -> Result { let state = match &mut self.window_target.p { PlatformActiveEventLoop::Wayland(window_target) => window_target.state.get_mut(), #[cfg(feature = "x11")] _ => unreachable!(), }; let mut wayland_source = self.wayland_dispatcher.as_source_mut(); let event_queue = wayland_source.queue(); event_queue.roundtrip(state).map_err(|error| { os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error)))) }) } fn control_flow(&self) -> ControlFlow { self.window_target.p.control_flow() } fn exiting(&self) -> bool { self.window_target.p.exiting() } fn set_exit_code(&self, code: i32) { self.window_target.p.set_exit_code(code) } fn exit_code(&self) -> Option { self.window_target.p.exit_code() } } impl AsFd for EventLoop { fn as_fd(&self) -> BorrowedFd<'_> { self.event_loop.as_fd() } } impl AsRawFd for EventLoop { fn as_raw_fd(&self) -> RawFd { self.event_loop.as_raw_fd() } } pub struct ActiveEventLoop { /// The event loop wakeup source. pub event_loop_awakener: calloop::ping::Ping, /// The main queue used by the event loop. pub queue_handle: QueueHandle, /// The application's latest control_flow state pub(crate) control_flow: Cell, /// The application's exit state. pub(crate) exit: Cell>, // TODO remove that RefCell once we can pass `&mut` in `Window::new`. /// Winit state. pub state: RefCell, /// Dispatcher of Wayland events. pub wayland_dispatcher: WaylandDispatcher, /// Connection to the wayland server. pub connection: Connection, } impl ActiveEventLoop { pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.control_flow.set(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.control_flow.get() } pub(crate) fn exit(&self) { self.exit.set(Some(0)) } pub(crate) fn clear_exit(&self) { self.exit.set(None) } pub(crate) fn exiting(&self) -> bool { self.exit.get().is_some() } pub(crate) fn set_exit_code(&self, code: i32) { self.exit.set(Some(code)) } pub(crate) fn exit_code(&self) -> Option { self.exit.get() } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} pub(crate) fn create_custom_cursor(&self, cursor: CustomCursorSource) -> RootCustomCursor { RootCustomCursor { inner: PlatformCustomCursor::Wayland(OnlyCursorImage(Arc::from(cursor.inner.0))), } } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { use sctk::reexports::client::Proxy; let mut display_handle = rwh_05::WaylandDisplayHandle::empty(); display_handle.display = self.connection.display().id().as_ptr() as *mut _; rwh_05::RawDisplayHandle::Wayland(display_handle) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { use sctk::reexports::client::Proxy; Ok(rwh_06::WaylandDisplayHandle::new({ let ptr = self.connection.display().id().as_ptr(); std::ptr::NonNull::new(ptr as *mut _).expect("wl_display should never be null") }) .into()) } } winit-0.30.9/src/platform_impl/linux/wayland/event_loop/proxy.rs000064400000000000000000000014271046102023000232060ustar 00000000000000//! An event loop proxy. use std::sync::mpsc::SendError; use sctk::reexports::calloop::channel::Sender; use crate::event_loop::EventLoopClosed; /// A handle that can be sent across the threads and used to wake up the `EventLoop`. pub struct EventLoopProxy { user_events_sender: Sender, } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy { user_events_sender: self.user_events_sender.clone() } } } impl EventLoopProxy { pub fn new(user_events_sender: Sender) -> Self { Self { user_events_sender } } pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.user_events_sender.send(event).map_err(|SendError(error)| EventLoopClosed(error)) } } winit-0.30.9/src/platform_impl/linux/wayland/event_loop/sink.rs000064400000000000000000000027661046102023000230000ustar 00000000000000//! An event loop's sink to deliver events from the Wayland event callbacks. use std::vec::Drain; use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent}; use crate::platform_impl::platform::DeviceId as PlatformDeviceId; use crate::window::WindowId as RootWindowId; use super::{DeviceId, WindowId}; /// An event loop's sink to deliver events from the Wayland event callbacks /// to the winit's user. #[derive(Default)] pub struct EventSink { pub window_events: Vec>, } impl EventSink { pub fn new() -> Self { Default::default() } /// Return `true` if there're pending events. #[inline] pub fn is_empty(&self) -> bool { self.window_events.is_empty() } /// Add new device event to a queue. #[inline] pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) { self.window_events.push(Event::DeviceEvent { event, device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)), }); } /// Add new window event to a queue. #[inline] pub fn push_window_event(&mut self, event: WindowEvent, window_id: WindowId) { self.window_events.push(Event::WindowEvent { event, window_id: RootWindowId(window_id) }); } #[inline] pub fn append(&mut self, other: &mut Self) { self.window_events.append(&mut other.window_events); } #[inline] pub fn drain(&mut self) -> Drain<'_, Event<()>> { self.window_events.drain(..) } } winit-0.30.9/src/platform_impl/linux/wayland/mod.rs000064400000000000000000000045071046102023000204340ustar 00000000000000//! Winit's Wayland backend. use std::fmt::Display; use std::sync::Arc; use sctk::reexports::client::globals::{BindError, GlobalError}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy}; pub(super) use crate::cursor::OnlyCursorImage as CustomCursor; use crate::dpi::{LogicalSize, PhysicalSize}; pub use crate::platform_impl::platform::{OsError, WindowId}; pub use event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}; pub use output::{MonitorHandle, VideoModeHandle}; pub use window::Window; mod event_loop; mod output; mod seat; mod state; mod types; mod window; #[derive(Debug)] pub enum WaylandError { /// Error connecting to the socket. Connection(ConnectError), /// Error binding the global. Global(GlobalError), // Bind error. Bind(BindError), /// Error during the dispatching the event queue. Dispatch(DispatchError), /// Calloop error. Calloop(calloop::Error), /// Wayland Wire(client::backend::WaylandError), } impl Display for WaylandError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { WaylandError::Connection(error) => error.fmt(f), WaylandError::Global(error) => error.fmt(f), WaylandError::Bind(error) => error.fmt(f), WaylandError::Dispatch(error) => error.fmt(f), WaylandError::Calloop(error) => error.fmt(f), WaylandError::Wire(error) => error.fmt(f), } } } impl From for OsError { fn from(value: WaylandError) -> Self { Self::WaylandError(Arc::new(value)) } } /// Dummy device id, since Wayland doesn't have device events. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; impl DeviceId { pub const fn dummy() -> Self { DeviceId } } /// Get the WindowId out of the surface. #[inline] fn make_wid(surface: &WlSurface) -> WindowId { WindowId(surface.id().as_ptr() as u64) } /// The default routine does floor, but we need round on Wayland. fn logical_to_physical_rounded(size: LogicalSize, scale_factor: f64) -> PhysicalSize { let width = size.width as f64 * scale_factor; let height = size.height as f64 * scale_factor; (width.round(), height.round()).into() } winit-0.30.9/src/platform_impl/linux/wayland/output.rs000064400000000000000000000110621046102023000212070ustar 00000000000000use sctk::reexports::client::protocol::wl_output::WlOutput; use sctk::reexports::client::Proxy; use sctk::output::OutputData; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::platform_impl::platform::VideoModeHandle as PlatformVideoModeHandle; use super::event_loop::ActiveEventLoop; impl ActiveEventLoop { #[inline] pub fn available_monitors(&self) -> impl Iterator { self.state.borrow().output_state.outputs().map(MonitorHandle::new) } #[inline] pub fn primary_monitor(&self) -> Option { // There's no primary monitor on Wayland. None } } #[derive(Clone, Debug)] pub struct MonitorHandle { pub(crate) proxy: WlOutput, } impl MonitorHandle { #[inline] pub(crate) fn new(proxy: WlOutput) -> Self { Self { proxy } } #[inline] pub fn name(&self) -> Option { let output_data = self.proxy.data::().unwrap(); output_data.with_output_info(|info| info.name.clone()) } #[inline] pub fn native_identifier(&self) -> u32 { let output_data = self.proxy.data::().unwrap(); output_data.with_output_info(|info| info.id) } #[inline] pub fn size(&self) -> PhysicalSize { let output_data = self.proxy.data::().unwrap(); let dimensions = output_data.with_output_info(|info| { info.modes.iter().find_map(|mode| mode.current.then_some(mode.dimensions)) }); match dimensions { Some((width, height)) => (width as u32, height as u32), _ => (0, 0), } .into() } #[inline] pub fn position(&self) -> PhysicalPosition { let output_data = self.proxy.data::().unwrap(); output_data.with_output_info(|info| { info.logical_position.map_or_else( || { LogicalPosition::::from(info.location) .to_physical(info.scale_factor as f64) }, |logical_position| { LogicalPosition::::from(logical_position) .to_physical(info.scale_factor as f64) }, ) }) } #[inline] pub fn refresh_rate_millihertz(&self) -> Option { let output_data = self.proxy.data::().unwrap(); output_data.with_output_info(|info| { info.modes.iter().find_map(|mode| mode.current.then_some(mode.refresh_rate as u32)) }) } #[inline] pub fn scale_factor(&self) -> i32 { let output_data = self.proxy.data::().unwrap(); output_data.scale_factor() } #[inline] pub fn video_modes(&self) -> impl Iterator { let output_data = self.proxy.data::().unwrap(); let modes = output_data.with_output_info(|info| info.modes.clone()); let monitor = self.clone(); modes.into_iter().map(move |mode| { PlatformVideoModeHandle::Wayland(VideoModeHandle { size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(), refresh_rate_millihertz: mode.refresh_rate as u32, bit_depth: 32, monitor: monitor.clone(), }) }) } } impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { self.native_identifier() == other.native_identifier() } } impl Eq for MonitorHandle {} impl PartialOrd for MonitorHandle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for MonitorHandle { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.native_identifier().cmp(&other.native_identifier()) } } impl std::hash::Hash for MonitorHandle { fn hash(&self, state: &mut H) { self.native_identifier().hash(state); } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VideoModeHandle { pub(crate) size: PhysicalSize, pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, pub(crate) monitor: MonitorHandle, } impl VideoModeHandle { #[inline] pub fn size(&self) -> PhysicalSize { self.size } #[inline] pub fn bit_depth(&self) -> u16 { self.bit_depth } #[inline] pub fn refresh_rate_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } } winit-0.30.9/src/platform_impl/linux/wayland/seat/keyboard/mod.rs000064400000000000000000000341441046102023000231700ustar 00000000000000//! The keyboard input handling. use std::sync::Mutex; use std::time::Duration; use calloop::timer::{TimeoutAction, Timer}; use calloop::{LoopHandle, RegistrationToken}; use tracing::warn; use sctk::reexports::client::protocol::wl_keyboard::{ Event as WlKeyboardEvent, KeyState as WlKeyState, KeymapFormat as WlKeymapFormat, WlKeyboard, }; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum}; use crate::event::{ElementState, WindowEvent}; use crate::keyboard::ModifiersState; use crate::platform_impl::common::xkb::Context; use crate::platform_impl::wayland::event_loop::sink::EventSink; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::{self, DeviceId, WindowId}; impl Dispatch for WinitState { fn event( state: &mut WinitState, wl_keyboard: &WlKeyboard, event: ::Event, data: &KeyboardData, _: &Connection, _: &QueueHandle, ) { let seat_state = match state.seats.get_mut(&data.seat.id()) { Some(seat_state) => seat_state, None => { warn!("Received keyboard event {event:?} without seat"); return; }, }; let keyboard_state = match seat_state.keyboard_state.as_mut() { Some(keyboard_state) => keyboard_state, None => { warn!("Received keyboard event {event:?} without keyboard"); return; }, }; match event { WlKeyboardEvent::Keymap { format, fd, size } => match format { WEnum::Value(format) => match format { WlKeymapFormat::NoKeymap => { warn!("non-xkb compatible keymap") }, WlKeymapFormat::XkbV1 => { let context = &mut keyboard_state.xkb_context; context.set_keymap_from_fd(fd, size as usize); }, _ => unreachable!(), }, WEnum::Unknown(value) => { warn!("unknown keymap format 0x{:x}", value) }, }, WlKeyboardEvent::Enter { surface, .. } => { let window_id = wayland::make_wid(&surface); // Mark the window as focused. let was_unfocused = match state.windows.get_mut().get(&window_id) { Some(window) => { let mut window = window.lock().unwrap(); let was_unfocused = !window.has_focus(); window.add_seat_focus(data.seat.id()); was_unfocused }, None => return, }; // Drop the repeat, if there were any. keyboard_state.current_repeat = None; if let Some(token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(token); } *data.window_id.lock().unwrap() = Some(window_id); // The keyboard focus is considered as general focus. if was_unfocused { state.events_sink.push_window_event(WindowEvent::Focused(true), window_id); } // HACK: this is just for GNOME not fixing their ordering issue of modifiers. if std::mem::take(&mut seat_state.modifiers_pending) { state.events_sink.push_window_event( WindowEvent::ModifiersChanged(seat_state.modifiers.into()), window_id, ); } }, WlKeyboardEvent::Leave { surface, .. } => { let window_id = wayland::make_wid(&surface); // NOTE: we should drop the repeat regardless whethere it was for the present // window of for the window which just went gone. keyboard_state.current_repeat = None; if let Some(token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(token); } // NOTE: The check whether the window exists is essential as we might get a // nil surface, regardless of what protocol says. let focused = match state.windows.get_mut().get(&window_id) { Some(window) => { let mut window = window.lock().unwrap(); window.remove_seat_focus(&data.seat.id()); window.has_focus() }, None => return, }; // We don't need to update it above, because the next `Enter` will overwrite // anyway. *data.window_id.lock().unwrap() = None; if !focused { // Notify that no modifiers are being pressed. state.events_sink.push_window_event( WindowEvent::ModifiersChanged(ModifiersState::empty().into()), window_id, ); state.events_sink.push_window_event(WindowEvent::Focused(false), window_id); } }, WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Pressed), .. } => { let key = key + 8; key_input( keyboard_state, &mut state.events_sink, data, key, ElementState::Pressed, false, ); let delay = match keyboard_state.repeat_info { RepeatInfo::Repeat { delay, .. } => delay, RepeatInfo::Disable => return, }; if !keyboard_state.xkb_context.keymap_mut().unwrap().key_repeats(key) { return; } keyboard_state.current_repeat = Some(key); // NOTE terminate ongoing timer and start a new timer. if let Some(token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(token); } let timer = Timer::from_duration(delay); let wl_keyboard = wl_keyboard.clone(); keyboard_state.repeat_token = keyboard_state .loop_handle .insert_source(timer, move |_, _, state| { // Required to handle the wakeups from the repeat sources. state.dispatched_events = true; let data = wl_keyboard.data::().unwrap(); let seat_state = match state.seats.get_mut(&data.seat.id()) { Some(seat_state) => seat_state, None => return TimeoutAction::Drop, }; let keyboard_state = match seat_state.keyboard_state.as_mut() { Some(keyboard_state) => keyboard_state, None => return TimeoutAction::Drop, }; // NOTE: The removed on event source is batched, but key change to `None` // is instant. let repeat_keycode = match keyboard_state.current_repeat { Some(repeat_keycode) => repeat_keycode, None => return TimeoutAction::Drop, }; key_input( keyboard_state, &mut state.events_sink, data, repeat_keycode, ElementState::Pressed, true, ); // NOTE: the gap could change dynamically while repeat is going. match keyboard_state.repeat_info { RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap), RepeatInfo::Disable => TimeoutAction::Drop, } }) .ok(); }, WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Released), .. } => { let key = key + 8; key_input( keyboard_state, &mut state.events_sink, data, key, ElementState::Released, false, ); if keyboard_state.repeat_info != RepeatInfo::Disable && keyboard_state.xkb_context.keymap_mut().unwrap().key_repeats(key) && Some(key) == keyboard_state.current_repeat { keyboard_state.current_repeat = None; if let Some(token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(token); } } }, WlKeyboardEvent::Modifiers { mods_depressed, mods_latched, mods_locked, group, .. } => { let xkb_context = &mut keyboard_state.xkb_context; let xkb_state = match xkb_context.state_mut() { Some(state) => state, None => return, }; xkb_state.update_modifiers(mods_depressed, mods_latched, mods_locked, 0, 0, group); seat_state.modifiers = xkb_state.modifiers().into(); // HACK: part of the workaround from `WlKeyboardEvent::Enter`. let window_id = match *data.window_id.lock().unwrap() { Some(window_id) => window_id, None => { seat_state.modifiers_pending = true; return; }, }; state.events_sink.push_window_event( WindowEvent::ModifiersChanged(seat_state.modifiers.into()), window_id, ); }, WlKeyboardEvent::RepeatInfo { rate, delay } => { keyboard_state.repeat_info = if rate == 0 { // Stop the repeat once we get a disable event. keyboard_state.current_repeat = None; if let Some(repeat_token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(repeat_token); } RepeatInfo::Disable } else { let gap = Duration::from_micros(1_000_000 / rate as u64); let delay = Duration::from_millis(delay as u64); RepeatInfo::Repeat { gap, delay } }; }, _ => unreachable!(), } } } /// The state of the keyboard on the current seat. #[derive(Debug)] pub struct KeyboardState { /// The underlying WlKeyboard. pub keyboard: WlKeyboard, /// Loop handle to handle key repeat. pub loop_handle: LoopHandle<'static, WinitState>, /// The state of the keyboard. pub xkb_context: Context, /// The information about the repeat rate obtained from the compositor. pub repeat_info: RepeatInfo, /// The token of the current handle inside the calloop's event loop. pub repeat_token: Option, /// The current repeat raw key. pub current_repeat: Option, } impl KeyboardState { pub fn new(keyboard: WlKeyboard, loop_handle: LoopHandle<'static, WinitState>) -> Self { Self { keyboard, loop_handle, xkb_context: Context::new().unwrap(), repeat_info: RepeatInfo::default(), repeat_token: None, current_repeat: None, } } } impl Drop for KeyboardState { fn drop(&mut self) { if self.keyboard.version() >= 3 { self.keyboard.release(); } if let Some(token) = self.repeat_token.take() { self.loop_handle.remove(token); } } } /// The rate at which a pressed key is repeated. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RepeatInfo { /// Keys will be repeated at the specified rate and delay. Repeat { /// The time between the key repeats. gap: Duration, /// Delay (in milliseconds) between a key press and the start of repetition. delay: Duration, }, /// Keys should not be repeated. Disable, } impl Default for RepeatInfo { /// The default repeat rate is 25 keys per second with the delay of 200ms. /// /// The values are picked based on the default in various compositors and Xorg. fn default() -> Self { Self::Repeat { gap: Duration::from_millis(40), delay: Duration::from_millis(200) } } } /// Keyboard user data. #[derive(Debug)] pub struct KeyboardData { /// The currently focused window surface. Could be `None` on bugged compositors, like mutter. window_id: Mutex>, /// The seat used to create this keyboard. seat: WlSeat, } impl KeyboardData { pub fn new(seat: WlSeat) -> Self { Self { window_id: Default::default(), seat } } } fn key_input( keyboard_state: &mut KeyboardState, event_sink: &mut EventSink, data: &KeyboardData, keycode: u32, state: ElementState, repeat: bool, ) { let window_id = match *data.window_id.lock().unwrap() { Some(window_id) => window_id, None => return, }; let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); if let Some(mut key_context) = keyboard_state.xkb_context.key_context() { let event = key_context.process_key_event(keycode, state, repeat); let event = WindowEvent::KeyboardInput { device_id, event, is_synthetic: false }; event_sink.push_window_event(event, window_id); } } winit-0.30.9/src/platform_impl/linux/wayland/seat/mod.rs000064400000000000000000000166541046102023000213760ustar 00000000000000//! Seat handling. use std::sync::Arc; use ahash::AHashMap; use tracing::warn; use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_touch::WlTouch; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use crate::event::WindowEvent; use crate::keyboard::ModifiersState; use crate::platform_impl::wayland::state::WinitState; mod keyboard; mod pointer; mod text_input; mod touch; pub use pointer::relative_pointer::RelativePointerState; pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt}; pub use text_input::{TextInputState, ZwpTextInputV3Ext}; use keyboard::{KeyboardData, KeyboardState}; use text_input::TextInputData; use touch::TouchPoint; #[derive(Debug, Default)] pub struct WinitSeatState { /// The pointer bound on the seat. pointer: Option>>, /// The touch bound on the seat. touch: Option, /// The mapping from touched points to the surfaces they're present. touch_map: AHashMap, /// The text input bound on the seat. text_input: Option>, /// The relative pointer bound on the seat. relative_pointer: Option, /// The keyboard bound on the seat. keyboard_state: Option, /// The current modifiers state on the seat. modifiers: ModifiersState, /// Whether we have pending modifiers. modifiers_pending: bool, } impl WinitSeatState { pub fn new() -> Self { Default::default() } } impl SeatHandler for WinitState { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_capability( &mut self, _: &Connection, queue_handle: &QueueHandle, seat: WlSeat, capability: SeatCapability, ) { let seat_state = match self.seats.get_mut(&seat.id()) { Some(seat_state) => seat_state, None => { warn!("Received wl_seat::new_capability for unknown seat"); return; }, }; match capability { SeatCapability::Touch if seat_state.touch.is_none() => { seat_state.touch = self.seat_state.get_touch(queue_handle, &seat).ok(); }, SeatCapability::Keyboard if seat_state.keyboard_state.is_none() => { let keyboard = seat.get_keyboard(queue_handle, KeyboardData::new(seat.clone())); seat_state.keyboard_state = Some(KeyboardState::new(keyboard, self.loop_handle.clone())); }, SeatCapability::Pointer if seat_state.pointer.is_none() => { let surface = self.compositor_state.create_surface(queue_handle); let surface_id = surface.id(); let pointer_data = WinitPointerData::new(seat.clone()); let themed_pointer = self .seat_state .get_pointer_with_theme_and_data( queue_handle, &seat, self.shm.wl_shm(), surface, ThemeSpec::System, pointer_data, ) .expect("failed to create pointer with present capability."); seat_state.relative_pointer = self.relative_pointer.as_ref().map(|manager| { manager.get_relative_pointer( themed_pointer.pointer(), queue_handle, sctk::globals::GlobalData, ) }); let themed_pointer = Arc::new(themed_pointer); // Register cursor surface. self.pointer_surfaces.insert(surface_id, themed_pointer.clone()); seat_state.pointer = Some(themed_pointer); }, _ => (), } if let Some(text_input_state) = seat_state.text_input.is_none().then_some(self.text_input_state.as_ref()).flatten() { seat_state.text_input = Some(Arc::new(text_input_state.get_text_input( &seat, queue_handle, TextInputData::default(), ))); } } fn remove_capability( &mut self, _: &Connection, _queue_handle: &QueueHandle, seat: WlSeat, capability: SeatCapability, ) { let seat_state = match self.seats.get_mut(&seat.id()) { Some(seat_state) => seat_state, None => { warn!("Received wl_seat::remove_capability for unknown seat"); return; }, }; if let Some(text_input) = seat_state.text_input.take() { text_input.destroy(); } match capability { SeatCapability::Touch => { if let Some(touch) = seat_state.touch.take() { if touch.version() >= 3 { touch.release(); } } }, SeatCapability::Pointer => { if let Some(relative_pointer) = seat_state.relative_pointer.take() { relative_pointer.destroy(); } if let Some(pointer) = seat_state.pointer.take() { let pointer_data = pointer.pointer().winit_data(); // Remove the cursor from the mapping. let surface_id = pointer.surface().id(); let _ = self.pointer_surfaces.remove(&surface_id); // Remove the inner locks/confines before dropping the pointer. pointer_data.unlock_pointer(); pointer_data.unconfine_pointer(); if pointer.pointer().version() >= 3 { pointer.pointer().release(); } } }, SeatCapability::Keyboard => { seat_state.keyboard_state = None; self.on_keyboard_destroy(&seat.id()); }, _ => (), } } fn new_seat( &mut self, _connection: &Connection, _queue_handle: &QueueHandle, seat: WlSeat, ) { self.seats.insert(seat.id(), WinitSeatState::new()); } fn remove_seat( &mut self, _connection: &Connection, _queue_handle: &QueueHandle, seat: WlSeat, ) { let _ = self.seats.remove(&seat.id()); self.on_keyboard_destroy(&seat.id()); } } impl WinitState { fn on_keyboard_destroy(&mut self, seat: &ObjectId) { for (window_id, window) in self.windows.get_mut() { let mut window = window.lock().unwrap(); let had_focus = window.has_focus(); window.remove_seat_focus(seat); if had_focus != window.has_focus() { self.events_sink.push_window_event(WindowEvent::Focused(false), *window_id); } } } } sctk::delegate_seat!(WinitState); winit-0.30.9/src/platform_impl/linux/wayland/seat/pointer/mod.rs000064400000000000000000000415121046102023000230450ustar 00000000000000//! The pointer events. use std::ops::Deref; use std::sync::{Arc, Mutex}; use std::time::Duration; use tracing::warn; use sctk::reexports::client::delegate_dispatch; use sctk::reexports::client::protocol::wl_pointer::WlPointer; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{Connection, Proxy, QueueHandle, Dispatch}; use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1; use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1; use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1}; use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::csd_frame::FrameClick; use sctk::compositor::SurfaceData; use sctk::globals::GlobalData; use sctk::seat::pointer::{ PointerData, PointerDataExt, PointerEvent, PointerEventKind, PointerHandler, }; use sctk::seat::SeatState; use crate::dpi::{LogicalPosition, PhysicalPosition}; use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::{self, DeviceId, WindowId}; pub mod relative_pointer; impl PointerHandler for WinitState { fn pointer_frame( &mut self, connection: &Connection, _: &QueueHandle, pointer: &WlPointer, events: &[PointerEvent], ) { let seat = pointer.winit_data().seat(); let seat_state = match self.seats.get(&seat.id()) { Some(seat_state) => seat_state, None => { warn!("Received pointer event without seat"); return; }, }; let themed_pointer = match seat_state.pointer.as_ref() { Some(pointer) => pointer, None => { warn!("Received pointer event without pointer"); return; }, }; let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); for event in events { let surface = &event.surface; // The parent surface. let parent_surface = match event.surface.data::() { Some(data) => data.parent_surface().unwrap_or(surface), None => continue, }; let window_id = wayland::make_wid(parent_surface); // Ensure that window exists. let mut window = match self.windows.get_mut().get_mut(&window_id) { Some(window) => window.lock().unwrap(), None => continue, }; let scale_factor = window.scale_factor(); let position: PhysicalPosition = LogicalPosition::new(event.position.0, event.position.1).to_physical(scale_factor); match event.kind { // Pointer movements on decorations. PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. } if parent_surface != surface => { if let Some(icon) = window.frame_point_moved( seat, surface, Duration::ZERO, event.position.0, event.position.1, ) { let _ = themed_pointer.set_cursor(connection, icon); } }, PointerEventKind::Leave { .. } if parent_surface != surface => { window.frame_point_left(); }, ref kind @ PointerEventKind::Press { button, serial, time } | ref kind @ PointerEventKind::Release { button, serial, time } if parent_surface != surface => { let click = match wayland_button_to_winit(button) { MouseButton::Left => FrameClick::Normal, MouseButton::Right => FrameClick::Alternate, _ => continue, }; let pressed = matches!(kind, PointerEventKind::Press { .. }); // Emulate click on the frame. window.frame_click( click, pressed, seat, serial, Duration::from_millis(time as u64), window_id, &mut self.window_compositor_updates, ); }, // Regular events on the main surface. PointerEventKind::Enter { .. } => { self.events_sink .push_window_event(WindowEvent::CursorEntered { device_id }, window_id); window.pointer_entered(Arc::downgrade(themed_pointer)); // Set the currently focused surface. pointer.winit_data().inner.lock().unwrap().surface = Some(window_id); self.events_sink.push_window_event( WindowEvent::CursorMoved { device_id, position }, window_id, ); }, PointerEventKind::Leave { .. } => { window.pointer_left(Arc::downgrade(themed_pointer)); // Remove the active surface. pointer.winit_data().inner.lock().unwrap().surface = None; self.events_sink .push_window_event(WindowEvent::CursorLeft { device_id }, window_id); }, PointerEventKind::Motion { .. } => { self.events_sink.push_window_event( WindowEvent::CursorMoved { device_id, position }, window_id, ); }, ref kind @ PointerEventKind::Press { button, serial, .. } | ref kind @ PointerEventKind::Release { button, serial, .. } => { // Update the last button serial. pointer.winit_data().inner.lock().unwrap().latest_button_serial = serial; let button = wayland_button_to_winit(button); let state = if matches!(kind, PointerEventKind::Press { .. }) { ElementState::Pressed } else { ElementState::Released }; self.events_sink.push_window_event( WindowEvent::MouseInput { device_id, state, button }, window_id, ); }, PointerEventKind::Axis { horizontal, vertical, .. } => { // Get the current phase. let mut pointer_data = pointer.winit_data().inner.lock().unwrap(); let has_discrete_scroll = horizontal.discrete != 0 || vertical.discrete != 0; // Figure out what to do about start/ended phases here. // // Figure out how to deal with `Started`. Also the `Ended` is not guaranteed // to be sent for mouse wheels. let phase = if horizontal.stop || vertical.stop { TouchPhase::Ended } else { match pointer_data.phase { // Discrete scroll only results in moved events. _ if has_discrete_scroll => TouchPhase::Moved, TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, _ => TouchPhase::Started, } }; // Update the phase. pointer_data.phase = phase; // Mice events have both pixel and discrete delta's at the same time. So prefer // the descrite values if they are present. let delta = if has_discrete_scroll { // NOTE: Wayland sign convention is the inverse of winit. MouseScrollDelta::LineDelta( (-horizontal.discrete) as f32, (-vertical.discrete) as f32, ) } else { // NOTE: Wayland sign convention is the inverse of winit. MouseScrollDelta::PixelDelta( LogicalPosition::new(-horizontal.absolute, -vertical.absolute) .to_physical(scale_factor), ) }; self.events_sink.push_window_event( WindowEvent::MouseWheel { device_id, delta, phase }, window_id, ) }, } } } } #[derive(Debug)] pub struct WinitPointerData { /// The inner winit data associated with the pointer. inner: Mutex, /// The data required by the sctk. sctk_data: PointerData, } impl WinitPointerData { pub fn new(seat: WlSeat) -> Self { Self { inner: Mutex::new(WinitPointerDataInner::default()), sctk_data: PointerData::new(seat), } } pub fn lock_pointer( &self, pointer_constraints: &PointerConstraintsState, surface: &WlSurface, pointer: &WlPointer, queue_handle: &QueueHandle, ) { let mut inner = self.inner.lock().unwrap(); if inner.locked_pointer.is_none() { inner.locked_pointer = Some(pointer_constraints.lock_pointer( surface, pointer, None, Lifetime::Persistent, queue_handle, GlobalData, )); } } pub fn unlock_pointer(&self) { let mut inner = self.inner.lock().unwrap(); if let Some(locked_pointer) = inner.locked_pointer.take() { locked_pointer.destroy(); } } pub fn confine_pointer( &self, pointer_constraints: &PointerConstraintsState, surface: &WlSurface, pointer: &WlPointer, queue_handle: &QueueHandle, ) { self.inner.lock().unwrap().confined_pointer = Some(pointer_constraints.confine_pointer( surface, pointer, None, Lifetime::Persistent, queue_handle, GlobalData, )); } pub fn unconfine_pointer(&self) { let inner = self.inner.lock().unwrap(); if let Some(confined_pointer) = inner.confined_pointer.as_ref() { confined_pointer.destroy(); } } /// Seat associated with this pointer. pub fn seat(&self) -> &WlSeat { self.sctk_data.seat() } /// Active window. pub fn focused_window(&self) -> Option { self.inner.lock().unwrap().surface } /// Last button serial. pub fn latest_button_serial(&self) -> u32 { self.sctk_data.latest_button_serial().unwrap_or_default() } /// Last enter serial. pub fn latest_enter_serial(&self) -> u32 { self.sctk_data.latest_enter_serial().unwrap_or_default() } pub fn set_locked_cursor_position(&self, surface_x: f64, surface_y: f64) { let inner = self.inner.lock().unwrap(); if let Some(locked_pointer) = inner.locked_pointer.as_ref() { locked_pointer.set_cursor_position_hint(surface_x, surface_y); } } } impl PointerDataExt for WinitPointerData { fn pointer_data(&self) -> &PointerData { &self.sctk_data } } #[derive(Debug)] pub struct WinitPointerDataInner { /// The associated locked pointer. locked_pointer: Option, /// The associated confined pointer. confined_pointer: Option, /// Serial of the last button event. latest_button_serial: u32, /// Currently focused window. surface: Option, /// Current axis phase. phase: TouchPhase, } impl Drop for WinitPointerDataInner { fn drop(&mut self) { if let Some(locked_pointer) = self.locked_pointer.take() { locked_pointer.destroy(); } if let Some(confined_pointer) = self.confined_pointer.take() { confined_pointer.destroy(); } } } impl Default for WinitPointerDataInner { fn default() -> Self { Self { surface: None, locked_pointer: None, confined_pointer: None, latest_button_serial: 0, phase: TouchPhase::Ended, } } } /// Convert the Wayland button into winit. fn wayland_button_to_winit(button: u32) -> MouseButton { // These values are coming from . const BTN_LEFT: u32 = 0x110; const BTN_RIGHT: u32 = 0x111; const BTN_MIDDLE: u32 = 0x112; const BTN_SIDE: u32 = 0x113; const BTN_EXTRA: u32 = 0x114; const BTN_FORWARD: u32 = 0x115; const BTN_BACK: u32 = 0x116; match button { BTN_LEFT => MouseButton::Left, BTN_RIGHT => MouseButton::Right, BTN_MIDDLE => MouseButton::Middle, BTN_BACK | BTN_SIDE => MouseButton::Back, BTN_FORWARD | BTN_EXTRA => MouseButton::Forward, button => MouseButton::Other(button as u16), } } pub trait WinitPointerDataExt { fn winit_data(&self) -> &WinitPointerData; } impl WinitPointerDataExt for WlPointer { fn winit_data(&self) -> &WinitPointerData { self.data::().expect("failed to get pointer data.") } } pub struct PointerConstraintsState { pointer_constraints: ZwpPointerConstraintsV1, } impl PointerConstraintsState { pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let pointer_constraints = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { pointer_constraints }) } } impl Deref for PointerConstraintsState { type Target = ZwpPointerConstraintsV1; fn deref(&self) -> &Self::Target { &self.pointer_constraints } } impl Dispatch for PointerConstraintsState { fn event( _state: &mut WinitState, _proxy: &ZwpPointerConstraintsV1, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for PointerConstraintsState { fn event( _state: &mut WinitState, _proxy: &ZwpLockedPointerV1, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for PointerConstraintsState { fn event( _state: &mut WinitState, _proxy: &ZwpConfinedPointerV1, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for SeatState { fn event( _: &mut WinitState, _: &WpCursorShapeDeviceV1, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("wp_cursor_shape_manager has no events") } } impl Dispatch for SeatState { fn event( _: &mut WinitState, _: &WpCursorShapeManagerV1, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("wp_cursor_device_manager has no events") } } delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState); delegate_dispatch!(WinitState: [ WpCursorShapeManagerV1: GlobalData] => SeatState); delegate_dispatch!(WinitState: [ WpCursorShapeDeviceV1: GlobalData] => SeatState); delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState); delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState); delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState); winit-0.30.9/src/platform_impl/linux/wayland/seat/pointer/relative_pointer.rs000064400000000000000000000052731046102023000256450ustar 00000000000000//! Relative pointer. use std::ops::Deref; use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::{delegate_dispatch, Dispatch}; use sctk::reexports::client::{Connection, QueueHandle}; use sctk::reexports::protocols::wp::relative_pointer::zv1::{ client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, client::zwp_relative_pointer_v1::{self, ZwpRelativePointerV1}, }; use sctk::globals::GlobalData; use crate::event::DeviceEvent; use crate::platform_impl::wayland::state::WinitState; /// Wrapper around the relative pointer. pub struct RelativePointerState { manager: ZwpRelativePointerManagerV1, } impl RelativePointerState { /// Create new relative pointer manager. pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { manager }) } } impl Deref for RelativePointerState { type Target = ZwpRelativePointerManagerV1; fn deref(&self) -> &Self::Target { &self.manager } } impl Dispatch for RelativePointerState { fn event( _state: &mut WinitState, _proxy: &ZwpRelativePointerManagerV1, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for RelativePointerState { fn event( state: &mut WinitState, _proxy: &ZwpRelativePointerV1, event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { let (dx_unaccel, dy_unaccel) = match event { zwp_relative_pointer_v1::Event::RelativeMotion { dx_unaccel, dy_unaccel, .. } => { (dx_unaccel, dy_unaccel) }, _ => return, }; state .events_sink .push_device_event(DeviceEvent::Motion { axis: 0, value: dx_unaccel }, super::DeviceId); state .events_sink .push_device_event(DeviceEvent::Motion { axis: 1, value: dy_unaccel }, super::DeviceId); state.events_sink.push_device_event( DeviceEvent::MouseMotion { delta: (dx_unaccel, dy_unaccel) }, super::DeviceId, ); } } delegate_dispatch!(WinitState: [ZwpRelativePointerV1: GlobalData] => RelativePointerState); delegate_dispatch!(WinitState: [ZwpRelativePointerManagerV1: GlobalData] => RelativePointerState); winit-0.30.9/src/platform_impl/linux/wayland/seat/text_input/mod.rs000064400000000000000000000160531046102023000235720ustar 00000000000000use std::ops::Deref; use sctk::globals::GlobalData; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{delegate_dispatch, Dispatch}; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{ ContentHint, ContentPurpose, Event as TextInputEvent, ZwpTextInputV3, }; use crate::event::{Ime, WindowEvent}; use crate::platform_impl::wayland; use crate::platform_impl::wayland::state::WinitState; use crate::window::ImePurpose; pub struct TextInputState { text_input_manager: ZwpTextInputManagerV3, } impl TextInputState { pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let text_input_manager = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { text_input_manager }) } } impl Deref for TextInputState { type Target = ZwpTextInputManagerV3; fn deref(&self) -> &Self::Target { &self.text_input_manager } } impl Dispatch for TextInputState { fn event( _state: &mut WinitState, _proxy: &ZwpTextInputManagerV3, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for TextInputState { fn event( state: &mut WinitState, text_input: &ZwpTextInputV3, event: ::Event, data: &TextInputData, _conn: &Connection, _qhandle: &QueueHandle, ) { let windows = state.windows.get_mut(); let mut text_input_data = data.inner.lock().unwrap(); match event { TextInputEvent::Enter { surface } => { let window_id = wayland::make_wid(&surface); text_input_data.surface = Some(surface); let mut window = match windows.get(&window_id) { Some(window) => window.lock().unwrap(), None => return, }; if window.ime_allowed() { text_input.enable(); text_input.set_content_type_by_purpose(window.ime_purpose()); text_input.commit(); state.events_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id); } window.text_input_entered(text_input); }, TextInputEvent::Leave { surface } => { text_input_data.surface = None; // Always issue a disable. text_input.disable(); text_input.commit(); let window_id = wayland::make_wid(&surface); // XXX this check is essential, because `leave` could have a // reference to nil surface... let mut window = match windows.get(&window_id) { Some(window) => window.lock().unwrap(), None => return, }; window.text_input_left(text_input); state.events_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id); }, TextInputEvent::PreeditString { text, cursor_begin, cursor_end } => { let text = text.unwrap_or_default(); let cursor_begin = usize::try_from(cursor_begin) .ok() .and_then(|idx| text.is_char_boundary(idx).then_some(idx)); let cursor_end = usize::try_from(cursor_end) .ok() .and_then(|idx| text.is_char_boundary(idx).then_some(idx)); text_input_data.pending_preedit = Some(Preedit { text, cursor_begin, cursor_end }) }, TextInputEvent::CommitString { text } => { text_input_data.pending_preedit = None; text_input_data.pending_commit = text; }, TextInputEvent::Done { .. } => { let window_id = match text_input_data.surface.as_ref() { Some(surface) => wayland::make_wid(surface), None => return, }; // Clear preedit, unless all we'll be doing next is sending a new preedit. if text_input_data.pending_commit.is_some() || text_input_data.pending_preedit.is_none() { state.events_sink.push_window_event( WindowEvent::Ime(Ime::Preedit(String::new(), None)), window_id, ); } // Send `Commit`. if let Some(text) = text_input_data.pending_commit.take() { state .events_sink .push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id); } // Send preedit. if let Some(preedit) = text_input_data.pending_preedit.take() { let cursor_range = preedit.cursor_begin.map(|b| (b, preedit.cursor_end.unwrap_or(b))); state.events_sink.push_window_event( WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)), window_id, ); } }, TextInputEvent::DeleteSurroundingText { .. } => { // Not handled. }, _ => {}, } } } pub trait ZwpTextInputV3Ext { fn set_content_type_by_purpose(&self, purpose: ImePurpose); } impl ZwpTextInputV3Ext for ZwpTextInputV3 { fn set_content_type_by_purpose(&self, purpose: ImePurpose) { let (hint, purpose) = match purpose { ImePurpose::Normal => (ContentHint::None, ContentPurpose::Normal), ImePurpose::Password => (ContentHint::SensitiveData, ContentPurpose::Password), ImePurpose::Terminal => (ContentHint::None, ContentPurpose::Terminal), }; self.set_content_type(hint, purpose); } } /// The Data associated with the text input. #[derive(Default)] pub struct TextInputData { inner: std::sync::Mutex, } #[derive(Default)] pub struct TextInputDataInner { /// The `WlSurface` we're performing input to. surface: Option, /// The commit to submit on `done`. pending_commit: Option, /// The preedit to submit on `done`. pending_preedit: Option, } /// The state of the preedit. struct Preedit { text: String, cursor_begin: Option, cursor_end: Option, } delegate_dispatch!(WinitState: [ZwpTextInputManagerV3: GlobalData] => TextInputState); delegate_dispatch!(WinitState: [ZwpTextInputV3: TextInputData] => TextInputState); winit-0.30.9/src/platform_impl/linux/wayland/seat/touch/mod.rs000064400000000000000000000147531046102023000225160ustar 00000000000000//! Touch handling. use tracing::warn; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_touch::WlTouch; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::seat::touch::{TouchData, TouchHandler}; use crate::dpi::LogicalPosition; use crate::event::{Touch, TouchPhase, WindowEvent}; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::{self, DeviceId}; impl TouchHandler for WinitState { fn down( &mut self, _: &Connection, _: &QueueHandle, touch: &WlTouch, _: u32, _: u32, surface: WlSurface, id: i32, position: (f64, f64), ) { let window_id = wayland::make_wid(&surface); let scale_factor = match self.windows.get_mut().get(&window_id) { Some(window) => window.lock().unwrap().scale_factor(), None => return, }; let seat_state = match self.seats.get_mut(&touch.seat().id()) { Some(seat_state) => seat_state, None => { warn!("Received wl_touch::down without seat"); return; }, }; // Update the state of the point. let location = LogicalPosition::::from(position); seat_state.touch_map.insert(id, TouchPoint { surface, location }); self.events_sink.push_window_event( WindowEvent::Touch(Touch { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, )), phase: TouchPhase::Started, location: location.to_physical(scale_factor), force: None, id: id as u64, }), window_id, ); } fn up( &mut self, _: &Connection, _: &QueueHandle, touch: &WlTouch, _: u32, _: u32, id: i32, ) { let seat_state = match self.seats.get_mut(&touch.seat().id()) { Some(seat_state) => seat_state, None => { warn!("Received wl_touch::up without seat"); return; }, }; // Remove the touch point. let touch_point = match seat_state.touch_map.remove(&id) { Some(touch_point) => touch_point, None => return, }; let window_id = wayland::make_wid(&touch_point.surface); let scale_factor = match self.windows.get_mut().get(&window_id) { Some(window) => window.lock().unwrap().scale_factor(), None => return, }; self.events_sink.push_window_event( WindowEvent::Touch(Touch { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, )), phase: TouchPhase::Ended, location: touch_point.location.to_physical(scale_factor), force: None, id: id as u64, }), window_id, ); } fn motion( &mut self, _: &Connection, _: &QueueHandle, touch: &WlTouch, _: u32, id: i32, position: (f64, f64), ) { let seat_state = match self.seats.get_mut(&touch.seat().id()) { Some(seat_state) => seat_state, None => { warn!("Received wl_touch::motion without seat"); return; }, }; // Remove the touch point. let touch_point = match seat_state.touch_map.get_mut(&id) { Some(touch_point) => touch_point, None => return, }; let window_id = wayland::make_wid(&touch_point.surface); let scale_factor = match self.windows.get_mut().get(&window_id) { Some(window) => window.lock().unwrap().scale_factor(), None => return, }; touch_point.location = LogicalPosition::::from(position); self.events_sink.push_window_event( WindowEvent::Touch(Touch { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, )), phase: TouchPhase::Moved, location: touch_point.location.to_physical(scale_factor), force: None, id: id as u64, }), window_id, ); } fn cancel(&mut self, _: &Connection, _: &QueueHandle, touch: &WlTouch) { let seat_state = match self.seats.get_mut(&touch.seat().id()) { Some(seat_state) => seat_state, None => { warn!("Received wl_touch::cancel without seat"); return; }, }; for (id, touch_point) in seat_state.touch_map.drain() { let window_id = wayland::make_wid(&touch_point.surface); let scale_factor = match self.windows.get_mut().get(&window_id) { Some(window) => window.lock().unwrap().scale_factor(), None => return, }; let location = touch_point.location.to_physical(scale_factor); self.events_sink.push_window_event( WindowEvent::Touch(Touch { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, )), phase: TouchPhase::Cancelled, location, force: None, id: id as u64, }), window_id, ); } } fn shape( &mut self, _: &Connection, _: &QueueHandle, _: &WlTouch, _: i32, _: f64, _: f64, ) { // Blank. } fn orientation(&mut self, _: &Connection, _: &QueueHandle, _: &WlTouch, _: i32, _: f64) { // Blank. } } /// The state of the touch point. #[derive(Debug)] pub struct TouchPoint { /// The surface on which the point is present. pub surface: WlSurface, /// The location of the point on the surface. pub location: LogicalPosition, } pub trait TouchDataExt { fn seat(&self) -> &WlSeat; } impl TouchDataExt for WlTouch { fn seat(&self) -> &WlSeat { self.data::().expect("failed to get touch data.").seat() } } sctk::delegate_touch!(WinitState); winit-0.30.9/src/platform_impl/linux/wayland/state.rs000064400000000000000000000347201046102023000207750ustar 00000000000000use std::cell::RefCell; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; use ahash::AHashMap; use sctk::reexports::calloop::LoopHandle; use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::globals::GlobalList; use sctk::reexports::client::protocol::wl_output::WlOutput; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::compositor::{CompositorHandler, CompositorState}; use sctk::output::{OutputHandler, OutputState}; use sctk::registry::{ProvidesRegistryState, RegistryState}; use sctk::seat::pointer::ThemedPointer; use sctk::seat::SeatState; use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler}; use sctk::shell::xdg::XdgShell; use sctk::shell::WaylandSurface; use sctk::shm::slot::SlotPool; use sctk::shm::{Shm, ShmHandler}; use sctk::subcompositor::SubcompositorState; use crate::platform_impl::wayland::event_loop::sink::EventSink; use crate::platform_impl::wayland::output::MonitorHandle; use crate::platform_impl::wayland::seat::{ PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData, WinitPointerDataExt, WinitSeatState, }; use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager; use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager; use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState; use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState; use crate::platform_impl::wayland::window::{WindowRequests, WindowState}; use crate::platform_impl::wayland::{WaylandError, WindowId}; use crate::platform_impl::OsError; /// Winit's Wayland state. pub struct WinitState { /// The WlRegistry. pub registry_state: RegistryState, /// The state of the WlOutput handling. pub output_state: OutputState, /// The compositor state which is used to create new windows and regions. pub compositor_state: Arc, /// The state of the subcompositor. pub subcompositor_state: Option>, /// The seat state responsible for all sorts of input. pub seat_state: SeatState, /// The shm for software buffers, such as cursors. pub shm: Shm, /// The pool where custom cursors are allocated. pub custom_cursor_pool: Arc>, /// The XDG shell that is used for windows. pub xdg_shell: XdgShell, /// The currently present windows. pub windows: RefCell>>>, /// The requests from the `Window` to EventLoop, such as close operations and redraw requests. pub window_requests: RefCell>>, /// The events that were generated directly from the window. pub window_events_sink: Arc>, /// The update for the `windows` coming from the compositor. pub window_compositor_updates: Vec, /// Currently handled seats. pub seats: AHashMap, /// Currently present cursor surfaces. pub pointer_surfaces: AHashMap>>, /// The state of the text input on the client. pub text_input_state: Option, /// Observed monitors. pub monitors: Arc>>, /// Sink to accumulate window events from the compositor, which is latter dispatched in /// event loop run. pub events_sink: EventSink, /// Xdg activation. pub xdg_activation: Option, /// Relative pointer. pub relative_pointer: Option, /// Pointer constraints to handle pointer locking and confining. pub pointer_constraints: Option>, /// Viewporter state on the given window. pub viewporter_state: Option, /// Fractional scaling manager. pub fractional_scaling_manager: Option, /// KWin blur manager. pub kwin_blur_manager: Option, /// Loop handle to re-register event sources, such as keyboard repeat. pub loop_handle: LoopHandle<'static, Self>, /// Whether we have dispatched events to the user thus we want to /// send `AboutToWait` and normally wakeup the user. pub dispatched_events: bool, } impl WinitState { pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, loop_handle: LoopHandle<'static, WinitState>, ) -> Result { let registry_state = RegistryState::new(globals); let compositor_state = CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?; let subcompositor_state = match SubcompositorState::bind( compositor_state.wl_compositor().clone(), globals, queue_handle, ) { Ok(c) => Some(c), Err(e) => { tracing::warn!("Subcompositor protocol not available, ignoring CSD: {e:?}"); None }, }; let output_state = OutputState::new(globals, queue_handle); let monitors = output_state.outputs().map(MonitorHandle::new).collect(); let seat_state = SeatState::new(globals, queue_handle); let mut seats = AHashMap::default(); for seat in seat_state.seats() { seats.insert(seat.id(), WinitSeatState::new()); } let (viewporter_state, fractional_scaling_manager) = if let Ok(fsm) = FractionalScalingManager::new(globals, queue_handle) { (ViewporterState::new(globals, queue_handle).ok(), Some(fsm)) } else { (None, None) }; let shm = Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?; let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap())); Ok(Self { registry_state, compositor_state: Arc::new(compositor_state), subcompositor_state: subcompositor_state.map(Arc::new), output_state, seat_state, shm, custom_cursor_pool, xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?, xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(), windows: Default::default(), window_requests: Default::default(), window_compositor_updates: Vec::new(), window_events_sink: Default::default(), viewporter_state, fractional_scaling_manager, kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(), seats, text_input_state: TextInputState::new(globals, queue_handle).ok(), relative_pointer: RelativePointerState::new(globals, queue_handle).ok(), pointer_constraints: PointerConstraintsState::new(globals, queue_handle) .map(Arc::new) .ok(), pointer_surfaces: Default::default(), monitors: Arc::new(Mutex::new(monitors)), events_sink: EventSink::new(), loop_handle, // Make it true by default. dispatched_events: true, }) } pub fn scale_factor_changed( &mut self, surface: &WlSurface, scale_factor: f64, is_legacy: bool, ) { // Check if the cursor surface. let window_id = super::make_wid(surface); if let Some(window) = self.windows.get_mut().get(&window_id) { // Don't update the scaling factor, when legacy method is used. if is_legacy && self.fractional_scaling_manager.is_some() { return; } // The scale factor change is for the window. let pos = if let Some(pos) = self .window_compositor_updates .iter() .position(|update| update.window_id == window_id) { pos } else { self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id)); self.window_compositor_updates.len() - 1 }; // Update the scale factor right away. window.lock().unwrap().set_scale_factor(scale_factor); self.window_compositor_updates[pos].scale_changed = true; } else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) { // Get the window, where the pointer resides right now. let focused_window = match pointer.pointer().winit_data().focused_window() { Some(focused_window) => focused_window, None => return, }; if let Some(window_state) = self.windows.get_mut().get(&focused_window) { window_state.lock().unwrap().reload_cursor_style() } } } pub fn queue_close(updates: &mut Vec, window_id: WindowId) { let pos = if let Some(pos) = updates.iter().position(|update| update.window_id == window_id) { pos } else { updates.push(WindowCompositorUpdate::new(window_id)); updates.len() - 1 }; updates[pos].close_window = true; } } impl ShmHandler for WinitState { fn shm_state(&mut self) -> &mut Shm { &mut self.shm } } impl WindowHandler for WinitState { fn request_close(&mut self, _: &Connection, _: &QueueHandle, window: &Window) { let window_id = super::make_wid(window.wl_surface()); Self::queue_close(&mut self.window_compositor_updates, window_id); } fn configure( &mut self, _: &Connection, _: &QueueHandle, window: &Window, configure: WindowConfigure, _serial: u32, ) { let window_id = super::make_wid(window.wl_surface()); let pos = if let Some(pos) = self.window_compositor_updates.iter().position(|update| update.window_id == window_id) { pos } else { self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id)); self.window_compositor_updates.len() - 1 }; // Populate the configure to the window. self.window_compositor_updates[pos].resized |= self .windows .get_mut() .get_mut(&window_id) .expect("got configure for dead window.") .lock() .unwrap() .configure(configure, &self.shm, &self.subcompositor_state); // NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the // users, since it can break a lot of things, thus it'll ask users to redraw instead. self.window_requests .get_mut() .get(&window_id) .unwrap() .redraw_requested .store(true, Ordering::Relaxed); // Manually mark that we've got an event, since configure may not generate a resize. self.dispatched_events = true; } } impl OutputHandler for WinitState { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output(&mut self, _: &Connection, _: &QueueHandle, output: WlOutput) { self.monitors.lock().unwrap().push(MonitorHandle::new(output)); } fn update_output(&mut self, _: &Connection, _: &QueueHandle, updated: WlOutput) { let mut monitors = self.monitors.lock().unwrap(); let updated = MonitorHandle::new(updated); if let Some(pos) = monitors.iter().position(|output| output == &updated) { monitors[pos] = updated } else { monitors.push(updated) } } fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle, removed: WlOutput) { let mut monitors = self.monitors.lock().unwrap(); let removed = MonitorHandle::new(removed); if let Some(pos) = monitors.iter().position(|output| output == &removed) { monitors.remove(pos); } } } impl CompositorHandler for WinitState { fn transform_changed( &mut self, _: &Connection, _: &QueueHandle, _: &WlSurface, _: wayland_client::protocol::wl_output::Transform, ) { // TODO(kchibisov) we need to expose it somehow in winit. } fn surface_enter( &mut self, _: &Connection, _: &QueueHandle, _: &WlSurface, _: &WlOutput, ) { } fn surface_leave( &mut self, _: &Connection, _: &QueueHandle, _: &WlSurface, _: &WlOutput, ) { } fn scale_factor_changed( &mut self, _: &Connection, _: &QueueHandle, surface: &WlSurface, scale_factor: i32, ) { self.scale_factor_changed(surface, scale_factor as f64, true) } fn frame(&mut self, _: &Connection, _: &QueueHandle, surface: &WlSurface, _: u32) { let window_id = super::make_wid(surface); let window = match self.windows.get_mut().get(&window_id) { Some(window) => window, None => return, }; // In case we have a redraw requested we must indicate the wake up. if self .window_requests .get_mut() .get(&window_id) .unwrap() .redraw_requested .load(Ordering::Relaxed) { self.dispatched_events = true; } window.lock().unwrap().frame_callback_received(); } } impl ProvidesRegistryState for WinitState { sctk::registry_handlers![OutputState, SeatState]; fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } } // The window update coming from the compositor. #[derive(Debug, Clone, Copy)] pub struct WindowCompositorUpdate { /// The id of the window this updates belongs to. pub window_id: WindowId, /// New window size. pub resized: bool, /// New scale factor. pub scale_changed: bool, /// Close the window. pub close_window: bool, } impl WindowCompositorUpdate { fn new(window_id: WindowId) -> Self { Self { window_id, resized: false, scale_changed: false, close_window: false } } } sctk::delegate_subcompositor!(WinitState); sctk::delegate_compositor!(WinitState); sctk::delegate_output!(WinitState); sctk::delegate_registry!(WinitState); sctk::delegate_shm!(WinitState); sctk::delegate_xdg_shell!(WinitState); sctk::delegate_xdg_window!(WinitState); winit-0.30.9/src/platform_impl/linux/wayland/types/cursor.rs000064400000000000000000000031501046102023000223270ustar 00000000000000use cursor_icon::CursorIcon; use sctk::reexports::client::protocol::wl_shm::Format; use sctk::shm::slot::{Buffer, SlotPool}; use crate::cursor::CursorImage; #[derive(Debug)] pub enum SelectedCursor { Named(CursorIcon), Custom(CustomCursor), } impl Default for SelectedCursor { fn default() -> Self { Self::Named(Default::default()) } } #[derive(Debug)] pub struct CustomCursor { pub buffer: Buffer, pub w: i32, pub h: i32, pub hotspot_x: i32, pub hotspot_y: i32, } impl CustomCursor { pub(crate) fn new(pool: &mut SlotPool, image: &CursorImage) -> Self { let (buffer, canvas) = pool .create_buffer( image.width as i32, image.height as i32, 4 * (image.width as i32), Format::Argb8888, ) .unwrap(); for (canvas_chunk, rgba) in canvas.chunks_exact_mut(4).zip(image.rgba.chunks_exact(4)) { // Alpha in buffer is premultiplied. let alpha = rgba[3] as f32 / 255.; let r = (rgba[0] as f32 * alpha) as u32; let g = (rgba[1] as f32 * alpha) as u32; let b = (rgba[2] as f32 * alpha) as u32; let color = ((rgba[3] as u32) << 24) + (r << 16) + (g << 8) + b; let array: &mut [u8; 4] = canvas_chunk.try_into().unwrap(); *array = color.to_le_bytes(); } CustomCursor { buffer, w: image.width as i32, h: image.height as i32, hotspot_x: image.hotspot_x as i32, hotspot_y: image.hotspot_y as i32, } } } winit-0.30.9/src/platform_impl/linux/wayland/types/kwin_blur.rs000064400000000000000000000040241046102023000230070ustar 00000000000000//! Handling of KDE-compatible blur. use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur_manager::OrgKdeKwinBlurManager; use sctk::globals::GlobalData; use crate::platform_impl::wayland::state::WinitState; /// KWin blur manager. #[derive(Debug, Clone)] pub struct KWinBlurManager { manager: OrgKdeKwinBlurManager, } impl KWinBlurManager { pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { manager }) } pub fn blur( &self, surface: &WlSurface, queue_handle: &QueueHandle, ) -> OrgKdeKwinBlur { self.manager.create(surface, queue_handle, ()) } pub fn unset(&self, surface: &WlSurface) { self.manager.unset(surface) } } impl Dispatch for KWinBlurManager { fn event( _: &mut WinitState, _: &OrgKdeKwinBlurManager, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("no events defined for org_kde_kwin_blur_manager"); } } impl Dispatch for KWinBlurManager { fn event( _: &mut WinitState, _: &OrgKdeKwinBlur, _: ::Event, _: &(), _: &Connection, _: &QueueHandle, ) { unreachable!("no events defined for org_kde_kwin_blur"); } } delegate_dispatch!(WinitState: [OrgKdeKwinBlurManager: GlobalData] => KWinBlurManager); delegate_dispatch!(WinitState: [OrgKdeKwinBlur: ()] => KWinBlurManager); winit-0.30.9/src/platform_impl/linux/wayland/types/mod.rs000064400000000000000000000002431046102023000215710ustar 00000000000000//! Wayland protocol implementation boilerplate. pub mod cursor; pub mod kwin_blur; pub mod wp_fractional_scaling; pub mod wp_viewporter; pub mod xdg_activation; winit-0.30.9/src/platform_impl/linux/wayland/types/wp_fractional_scaling.rs000064400000000000000000000050631046102023000253470ustar 00000000000000//! Handling of the fractional scaling. use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::{ Event as FractionalScalingEvent, WpFractionalScaleV1, }; use sctk::globals::GlobalData; use crate::platform_impl::wayland::state::WinitState; /// The scaling factor denominator. const SCALE_DENOMINATOR: f64 = 120.; /// Fractional scaling manager. #[derive(Debug)] pub struct FractionalScalingManager { manager: WpFractionalScaleManagerV1, } pub struct FractionalScaling { /// The surface used for scaling. surface: WlSurface, } impl FractionalScalingManager { /// Create new viewporter. pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { manager }) } pub fn fractional_scaling( &self, surface: &WlSurface, queue_handle: &QueueHandle, ) -> WpFractionalScaleV1 { let data = FractionalScaling { surface: surface.clone() }; self.manager.get_fractional_scale(surface, queue_handle, data) } } impl Dispatch for FractionalScalingManager { fn event( _: &mut WinitState, _: &WpFractionalScaleManagerV1, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { // No events. } } impl Dispatch for FractionalScalingManager { fn event( state: &mut WinitState, _: &WpFractionalScaleV1, event: ::Event, data: &FractionalScaling, _: &Connection, _: &QueueHandle, ) { if let FractionalScalingEvent::PreferredScale { scale } = event { state.scale_factor_changed(&data.surface, scale as f64 / SCALE_DENOMINATOR, false); } } } delegate_dispatch!(WinitState: [WpFractionalScaleManagerV1: GlobalData] => FractionalScalingManager); delegate_dispatch!(WinitState: [WpFractionalScaleV1: FractionalScaling] => FractionalScalingManager); winit-0.30.9/src/platform_impl/linux/wayland/types/wp_viewporter.rs000064400000000000000000000036131046102023000237320ustar 00000000000000//! Handling of the wp-viewporter. use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; use sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter; use sctk::globals::GlobalData; use crate::platform_impl::wayland::state::WinitState; /// Viewporter. #[derive(Debug)] pub struct ViewporterState { viewporter: WpViewporter, } impl ViewporterState { /// Create new viewporter. pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let viewporter = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { viewporter }) } /// Get the viewport for the given object. pub fn get_viewport( &self, surface: &WlSurface, queue_handle: &QueueHandle, ) -> WpViewport { self.viewporter.get_viewport(surface, queue_handle, GlobalData) } } impl Dispatch for ViewporterState { fn event( _: &mut WinitState, _: &WpViewporter, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { // No events. } } impl Dispatch for ViewporterState { fn event( _: &mut WinitState, _: &WpViewport, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { // No events. } } delegate_dispatch!(WinitState: [WpViewporter: GlobalData] => ViewporterState); delegate_dispatch!(WinitState: [WpViewport: GlobalData] => ViewporterState); winit-0.30.9/src/platform_impl/linux/wayland/types/xdg_activation.rs000064400000000000000000000066621046102023000240300ustar 00000000000000//! Handling of xdg activation, which is used for user attention requests. use std::sync::atomic::AtomicBool; use std::sync::Weak; use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle}; use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_token_v1::{ Event as ActivationTokenEvent, XdgActivationTokenV1, }; use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; use sctk::globals::GlobalData; use crate::event_loop::AsyncRequestSerial; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::WindowId; use crate::window::ActivationToken; pub struct XdgActivationState { xdg_activation: XdgActivationV1, } impl XdgActivationState { pub fn bind( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let xdg_activation = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { xdg_activation }) } pub fn global(&self) -> &XdgActivationV1 { &self.xdg_activation } } impl Dispatch for XdgActivationState { fn event( _state: &mut WinitState, _proxy: &XdgActivationV1, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for XdgActivationState { fn event( state: &mut WinitState, proxy: &XdgActivationTokenV1, event: ::Event, data: &XdgActivationTokenData, _: &Connection, _: &QueueHandle, ) { let token = match event { ActivationTokenEvent::Done { token } => token, _ => return, }; let global = state .xdg_activation .as_ref() .expect("got xdg_activation event without global.") .global(); match data { XdgActivationTokenData::Attention((surface, fence)) => { global.activate(token, surface); // Mark that no request attention is in process. if let Some(attention_requested) = fence.upgrade() { attention_requested.store(false, std::sync::atomic::Ordering::Relaxed); } }, XdgActivationTokenData::Obtain((window_id, serial)) => { state.events_sink.push_window_event( crate::event::WindowEvent::ActivationTokenDone { serial: *serial, token: ActivationToken::from_raw(token), }, *window_id, ); }, } proxy.destroy(); } } /// The data associated with the activation request. pub enum XdgActivationTokenData { /// Request user attention for the given surface. Attention((WlSurface, Weak)), /// Get a token to be passed outside of the winit. Obtain((WindowId, AsyncRequestSerial)), } delegate_dispatch!(WinitState: [ XdgActivationV1: GlobalData] => XdgActivationState); delegate_dispatch!(WinitState: [ XdgActivationTokenV1: XdgActivationTokenData] => XdgActivationState); winit-0.30.9/src/platform_impl/linux/wayland/window/mod.rs000064400000000000000000000600061046102023000217370ustar 00000000000000//! The Wayland window. use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use sctk::reexports::client::protocol::wl_display::WlDisplay; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{Proxy, QueueHandle}; use sctk::compositor::{CompositorState, Region, SurfaceData}; use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; use sctk::shell::xdg::window::{Window as SctkWindow, WindowDecorations}; use sctk::shell::WaylandSurface; use tracing::warn; use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::event::{Ime, WindowEvent}; use crate::event_loop::AsyncRequestSerial; use crate::platform_impl::{ Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon, }; use crate::window::{ Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }; use super::event_loop::sink::EventSink; use super::output::MonitorHandle; use super::state::WinitState; use super::types::xdg_activation::XdgActivationTokenData; use super::{ActiveEventLoop, WaylandError, WindowId}; pub(crate) mod state; pub use state::WindowState; /// The Wayland window. pub struct Window { /// Reference to the underlying SCTK window. window: SctkWindow, /// Window id. window_id: WindowId, /// The state of the window. window_state: Arc>, /// Compositor to handle WlRegion stuff. compositor: Arc, /// The wayland display used solely for raw window handle. #[allow(dead_code)] display: WlDisplay, /// Xdg activation to request user attention. xdg_activation: Option, /// The state of the requested attention from the `xdg_activation`. attention_requested: Arc, /// Handle to the main queue to perform requests. queue_handle: QueueHandle, /// Window requests to the event loop. window_requests: Arc, /// Observed monitors. monitors: Arc>>, /// Source to wake-up the event-loop for window requests. event_loop_awakener: calloop::ping::Ping, /// The event sink to deliver synthetic events. window_events_sink: Arc>, } impl Window { pub(crate) fn new( event_loop_window_target: &ActiveEventLoop, attributes: WindowAttributes, ) -> Result { let queue_handle = event_loop_window_target.queue_handle.clone(); let mut state = event_loop_window_target.state.borrow_mut(); let monitors = state.monitors.clone(); let surface = state.compositor_state.create_surface(&queue_handle); let compositor = state.compositor_state.clone(); let xdg_activation = state.xdg_activation.as_ref().map(|activation_state| activation_state.global().clone()); let display = event_loop_window_target.connection.display(); let size: Size = attributes.inner_size.unwrap_or(LogicalSize::new(800., 600.).into()); // We prefer server side decorations, however to not have decorations we ask for client // side decorations instead. let default_decorations = if attributes.decorations { WindowDecorations::RequestServer } else { WindowDecorations::RequestClient }; let window = state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle); let mut window_state = WindowState::new( event_loop_window_target.connection.clone(), &event_loop_window_target.queue_handle, &state, size, window.clone(), attributes.preferred_theme, ); // Set transparency hint. window_state.set_transparent(attributes.transparent); window_state.set_blur(attributes.blur); // Set the decorations hint. window_state.set_decorate(attributes.decorations); // Set the app_id. if let Some(name) = attributes.platform_specific.name.map(|name| name.general) { window.set_app_id(name); } // Set the window title. window_state.set_title(attributes.title); // Set the min and max sizes. We must set the hints upon creating a window, so // we use the default `1.` scaling... let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.)); let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.)); window_state.set_min_inner_size(min_size); window_state.set_max_inner_size(max_size); // Non-resizable implies that the min and max sizes are set to the same value. window_state.set_resizable(attributes.resizable); // Set startup mode. match attributes.fullscreen.map(Into::into) { Some(Fullscreen::Exclusive(_)) => { warn!("`Fullscreen::Exclusive` is ignored on Wayland"); }, #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))] Some(Fullscreen::Borderless(monitor)) => { let output = monitor.and_then(|monitor| match monitor { PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), #[cfg(x11_platform)] PlatformMonitorHandle::X(_) => None, }); window.set_fullscreen(output.as_ref()) }, _ if attributes.maximized => window.set_maximized(), _ => (), }; match attributes.cursor { Cursor::Icon(icon) => window_state.set_cursor(icon), Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor), } // Activate the window when the token is passed. if let (Some(xdg_activation), Some(token)) = (xdg_activation.as_ref(), attributes.platform_specific.activation_token) { xdg_activation.activate(token.token, &surface); } // XXX Do initial commit. window.commit(); // Add the window and window requests into the state. let window_state = Arc::new(Mutex::new(window_state)); let window_id = super::make_wid(&surface); state.windows.get_mut().insert(window_id, window_state.clone()); let window_requests = WindowRequests { redraw_requested: AtomicBool::new(true), closed: AtomicBool::new(false), }; let window_requests = Arc::new(window_requests); state.window_requests.get_mut().insert(window_id, window_requests.clone()); // Setup the event sync to insert `WindowEvents` right from the window. let window_events_sink = state.window_events_sink.clone(); let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut(); let event_queue = wayland_source.queue(); // Do a roundtrip. event_queue.roundtrip(&mut state).map_err(|error| { os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error)))) })?; // XXX Wait for the initial configure to arrive. while !window_state.lock().unwrap().is_configured() { event_queue.blocking_dispatch(&mut state).map_err(|error| { os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error)))) })?; } // Wake-up event loop, so it'll send initial redraw requested. let event_loop_awakener = event_loop_window_target.event_loop_awakener.clone(); event_loop_awakener.ping(); Ok(Self { window, display, monitors, window_id, compositor, window_state, queue_handle, xdg_activation, attention_requested: Arc::new(AtomicBool::new(false)), event_loop_awakener, window_requests, window_events_sink, }) } } impl Window { #[inline] pub fn id(&self) -> WindowId { self.window_id } #[inline] pub fn set_title(&self, title: impl ToString) { let new_title = title.to_string(); self.window_state.lock().unwrap().set_title(new_title); } #[inline] pub fn set_visible(&self, _visible: bool) { // Not possible on Wayland. } #[inline] pub fn is_visible(&self) -> Option { None } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { Err(NotSupportedError::new()) } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { Err(NotSupportedError::new()) } #[inline] pub fn set_outer_position(&self, _: Position) { // Not possible on Wayland. } #[inline] pub fn inner_size(&self) -> PhysicalSize { let window_state = self.window_state.lock().unwrap(); let scale_factor = window_state.scale_factor(); super::logical_to_physical_rounded(window_state.inner_size(), scale_factor) } #[inline] pub fn request_redraw(&self) { // NOTE: try to not wake up the loop when the event was already scheduled and not yet // processed by the loop, because if at this point the value was `true` it could only // mean that the loop still haven't dispatched the value to the client and will do // eventually, resetting it to `false`. if self .window_requests .redraw_requested .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) .is_ok() { self.event_loop_awakener.ping(); } } #[inline] pub fn pre_present_notify(&self) { self.window_state.lock().unwrap().request_frame_callback(); } #[inline] pub fn outer_size(&self) -> PhysicalSize { let window_state = self.window_state.lock().unwrap(); let scale_factor = window_state.scale_factor(); super::logical_to_physical_rounded(window_state.outer_size(), scale_factor) } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let mut window_state = self.window_state.lock().unwrap(); let new_size = window_state.request_inner_size(size); self.request_redraw(); Some(new_size) } /// Set the minimum inner size for the window. #[inline] pub fn set_min_inner_size(&self, min_size: Option) { let scale_factor = self.scale_factor(); let min_size = min_size.map(|size| size.to_logical(scale_factor)); self.window_state.lock().unwrap().set_min_inner_size(min_size); // NOTE: Requires commit to be applied. self.request_redraw(); } /// Set the maximum inner size for the window. #[inline] pub fn set_max_inner_size(&self, max_size: Option) { let scale_factor = self.scale_factor(); let max_size = max_size.map(|size| size.to_logical(scale_factor)); self.window_state.lock().unwrap().set_max_inner_size(max_size); // NOTE: Requires commit to be applied. self.request_redraw(); } #[inline] pub fn resize_increments(&self) -> Option> { None } #[inline] pub fn set_resize_increments(&self, _increments: Option) { warn!("`set_resize_increments` is not implemented for Wayland"); } #[inline] pub fn set_transparent(&self, transparent: bool) { self.window_state.lock().unwrap().set_transparent(transparent); } #[inline] pub fn has_focus(&self) -> bool { self.window_state.lock().unwrap().has_focus() } #[inline] pub fn is_minimized(&self) -> Option { // XXX clients don't know whether they are minimized or not. None } #[inline] pub fn show_window_menu(&self, position: Position) { let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); self.window_state.lock().unwrap().show_window_menu(position); } #[inline] pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { self.window_state.lock().unwrap().drag_resize_window(direction) } #[inline] pub fn set_resizable(&self, resizable: bool) { if self.window_state.lock().unwrap().set_resizable(resizable) { // NOTE: Requires commit to be applied. self.request_redraw(); } } #[inline] pub fn is_resizable(&self) -> bool { self.window_state.lock().unwrap().resizable() } #[inline] pub fn set_enabled_buttons(&self, _buttons: WindowButtons) { // TODO(kchibisov) v5 of the xdg_shell allows that. } #[inline] pub fn enabled_buttons(&self) -> WindowButtons { // TODO(kchibisov) v5 of the xdg_shell allows that. WindowButtons::all() } #[inline] pub fn scale_factor(&self) -> f64 { self.window_state.lock().unwrap().scale_factor() } #[inline] pub fn set_blur(&self, blur: bool) { self.window_state.lock().unwrap().set_blur(blur); } #[inline] pub fn set_decorations(&self, decorate: bool) { self.window_state.lock().unwrap().set_decorate(decorate) } #[inline] pub fn is_decorated(&self) -> bool { self.window_state.lock().unwrap().is_decorated() } #[inline] pub fn set_window_level(&self, _level: WindowLevel) {} #[inline] pub(crate) fn set_window_icon(&self, _window_icon: Option) {} #[inline] pub fn set_minimized(&self, minimized: bool) { // You can't unminimize the window on Wayland. if !minimized { warn!("Unminimizing is ignored on Wayland."); return; } self.window.set_minimized(); } #[inline] pub fn is_maximized(&self) -> bool { self.window_state .lock() .unwrap() .last_configure .as_ref() .map(|last_configure| last_configure.is_maximized()) .unwrap_or_default() } #[inline] pub fn set_maximized(&self, maximized: bool) { if maximized { self.window.set_maximized() } else { self.window.unset_maximized() } } #[inline] pub(crate) fn fullscreen(&self) -> Option { let is_fullscreen = self .window_state .lock() .unwrap() .last_configure .as_ref() .map(|last_configure| last_configure.is_fullscreen()) .unwrap_or_default(); if is_fullscreen { let current_monitor = self.current_monitor().map(PlatformMonitorHandle::Wayland); Some(Fullscreen::Borderless(current_monitor)) } else { None } } #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { match fullscreen { Some(Fullscreen::Exclusive(_)) => { warn!("`Fullscreen::Exclusive` is ignored on Wayland"); }, #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))] Some(Fullscreen::Borderless(monitor)) => { let output = monitor.and_then(|monitor| match monitor { PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), #[cfg(x11_platform)] PlatformMonitorHandle::X(_) => None, }); self.window.set_fullscreen(output.as_ref()) }, None => self.window.unset_fullscreen(), } } #[inline] pub fn set_cursor(&self, cursor: Cursor) { let window_state = &mut self.window_state.lock().unwrap(); match cursor { Cursor::Icon(icon) => window_state.set_cursor(icon), Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor), } } #[inline] pub fn set_cursor_visible(&self, visible: bool) { self.window_state.lock().unwrap().set_cursor_visible(visible); } pub fn request_user_attention(&self, request_type: Option) { let xdg_activation = match self.xdg_activation.as_ref() { Some(xdg_activation) => xdg_activation, None => { warn!("`request_user_attention` isn't supported"); return; }, }; // Urgency is only removed by the compositor and there's no need to raise urgency when it // was already raised. if request_type.is_none() || self.attention_requested.load(Ordering::Relaxed) { return; } self.attention_requested.store(true, Ordering::Relaxed); let surface = self.surface().clone(); let data = XdgActivationTokenData::Attention(( surface.clone(), Arc::downgrade(&self.attention_requested), )); let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); xdg_activation_token.set_surface(&surface); xdg_activation_token.commit(); } pub fn request_activation_token(&self) -> Result { let xdg_activation = match self.xdg_activation.as_ref() { Some(xdg_activation) => xdg_activation, None => return Err(NotSupportedError::new()), }; let serial = AsyncRequestSerial::get(); let data = XdgActivationTokenData::Obtain((self.window_id, serial)); let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); xdg_activation_token.set_surface(self.surface()); xdg_activation_token.commit(); Ok(serial) } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { self.window_state.lock().unwrap().set_cursor_grab(mode) } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); self.window_state .lock() .unwrap() .set_cursor_position(position) // Request redraw on success, since the state is double buffered. .map(|_| self.request_redraw()) } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { self.window_state.lock().unwrap().drag_window() } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { let surface = self.window.wl_surface(); if hittest { surface.set_input_region(None); Ok(()) } else { let region = Region::new(&*self.compositor).map_err(|_| { ExternalError::Os(os_error!(OsError::Misc("failed to set input region."))) })?; region.add(0, 0, 0, 0); surface.set_input_region(Some(region.wl_region())); Ok(()) } } #[inline] pub fn set_ime_cursor_area(&self, position: Position, size: Size) { let window_state = self.window_state.lock().unwrap(); if window_state.ime_allowed() { let scale_factor = window_state.scale_factor(); let position = position.to_logical(scale_factor); let size = size.to_logical(scale_factor); window_state.set_ime_cursor_area(position, size); } } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { let mut window_state = self.window_state.lock().unwrap(); if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed) { let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled }); self.window_events_sink.lock().unwrap().push_window_event(event, self.window_id); self.event_loop_awakener.ping(); } } #[inline] pub fn set_ime_purpose(&self, purpose: ImePurpose) { self.window_state.lock().unwrap().set_ime_purpose(purpose); } #[inline] pub fn focus_window(&self) {} #[inline] pub fn surface(&self) -> &WlSurface { self.window.wl_surface() } #[inline] pub fn current_monitor(&self) -> Option { let data = self.window.wl_surface().data::()?; data.outputs().next().map(MonitorHandle::new) } #[inline] pub fn available_monitors(&self) -> Vec { self.monitors.lock().unwrap().clone() } #[inline] pub fn primary_monitor(&self) -> Option { // XXX there's no such concept on Wayland. None } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::WaylandHandle::empty(); window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _; window_handle.display = self.display.id().as_ptr() as *mut _; rwh_04::RawWindowHandle::Wayland(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::WaylandWindowHandle::empty(); window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _; rwh_05::RawWindowHandle::Wayland(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { let mut display_handle = rwh_05::WaylandDisplayHandle::empty(); display_handle.display = self.display.id().as_ptr() as *mut _; rwh_05::RawDisplayHandle::Wayland(display_handle) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { Ok(rwh_06::WaylandWindowHandle::new({ let ptr = self.window.wl_surface().id().as_ptr(); std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null") }) .into()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::WaylandDisplayHandle::new({ let ptr = self.display.id().as_ptr(); std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null") }) .into()) } #[inline] pub fn set_theme(&self, theme: Option) { self.window_state.lock().unwrap().set_theme(theme) } #[inline] pub fn theme(&self) -> Option { self.window_state.lock().unwrap().theme() } pub fn set_content_protected(&self, _protected: bool) {} #[inline] pub fn title(&self) -> String { self.window_state.lock().unwrap().title().to_owned() } } impl Drop for Window { fn drop(&mut self) { self.window_requests.closed.store(true, Ordering::Relaxed); self.event_loop_awakener.ping(); } } /// The request from the window to the event loop. #[derive(Debug)] pub struct WindowRequests { /// The window was closed. pub closed: AtomicBool, /// Redraw Requested. pub redraw_requested: AtomicBool, } impl WindowRequests { pub fn take_closed(&self) -> bool { self.closed.swap(false, Ordering::Relaxed) } pub fn take_redraw_requested(&self) -> bool { self.redraw_requested.swap(false, Ordering::Relaxed) } } impl TryFrom<&str> for Theme { type Error = (); /// ``` /// use winit::window::Theme; /// /// assert_eq!("dark".try_into(), Ok(Theme::Dark)); /// assert_eq!("lIghT".try_into(), Ok(Theme::Light)); /// ``` fn try_from(theme: &str) -> Result { if theme.eq_ignore_ascii_case("dark") { Ok(Self::Dark) } else if theme.eq_ignore_ascii_case("light") { Ok(Self::Light) } else { Err(()) } } } winit-0.30.9/src/platform_impl/linux/wayland/window/state.rs000064400000000000000000001162651046102023000223110ustar 00000000000000//! The state of the window, which is shared with the event-loop. use std::num::NonZeroU32; use std::sync::{Arc, Mutex, Weak}; use std::time::Duration; use ahash::HashSet; use tracing::{info, warn}; use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_shm::WlShm; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::csd_frame::{ DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowState as XdgWindowState, }; use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge; use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt}; use sctk::seat::pointer::{PointerDataExt, ThemedPointer}; use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure}; use sctk::shell::xdg::XdgSurface; use sctk::shell::WaylandSurface; use sctk::shm::slot::SlotPool; use sctk::shm::Shm; use sctk::subcompositor::SubcompositorState; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; use crate::cursor::CustomCursor as RootCustomCursor; use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size}; use crate::error::{ExternalError, NotSupportedError}; use crate::platform_impl::wayland::logical_to_physical_rounded; use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor}; use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager; use crate::platform_impl::{PlatformCustomCursor, WindowId}; use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme}; use crate::platform_impl::wayland::seat::{ PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext, }; use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState}; #[cfg(feature = "sctk-adwaita")] pub type WinitFrame = sctk_adwaita::AdwaitaFrame; #[cfg(not(feature = "sctk-adwaita"))] pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame; // Minimum window inner size. const MIN_WINDOW_SIZE: LogicalSize = LogicalSize::new(2, 1); /// The state of the window which is being updated from the [`WinitState`]. pub struct WindowState { /// The connection to Wayland server. pub connection: Connection, /// The `Shm` to set cursor. pub shm: WlShm, // A shared pool where to allocate custom cursors. custom_cursor_pool: Arc>, /// The last received configure. pub last_configure: Option, /// The pointers observed on the window. pub pointers: Vec>>, selected_cursor: SelectedCursor, /// Whether the cursor is visible. pub cursor_visible: bool, /// Pointer constraints to lock/confine pointer. pub pointer_constraints: Option>, /// Queue handle. pub queue_handle: QueueHandle, /// Theme variant. theme: Option, /// The current window title. title: String, /// Whether the frame is resizable. resizable: bool, // NOTE: we can't use simple counter, since it's racy when seat getting destroyed and new // is created, since add/removed stuff could be delivered a bit out of order. /// Seats that has keyboard focus on that window. seat_focus: HashSet, /// The scale factor of the window. scale_factor: f64, /// Whether the window is transparent. transparent: bool, /// The state of the compositor to create WlRegions. compositor: Arc, /// The current cursor grabbing mode. cursor_grab_mode: GrabState, /// Whether the IME input is allowed for that window. ime_allowed: bool, /// The current IME purpose. ime_purpose: ImePurpose, /// The text inputs observed on the window. text_inputs: Vec, /// The inner size of the window, as in without client side decorations. size: LogicalSize, /// Whether the CSD fail to create, so we don't try to create them on each iteration. csd_fails: bool, /// Whether we should decorate the frame. decorate: bool, /// Min size. min_inner_size: LogicalSize, max_inner_size: Option>, /// The size of the window when no states were applied to it. The primary use for it /// is to fallback to original window size, before it was maximized, if the compositor /// sends `None` for the new size in the configure. stateless_size: LogicalSize, /// Initial window size provided by the user. Removed on the first /// configure. initial_size: Option, /// The state of the frame callback. frame_callback_state: FrameCallbackState, viewport: Option, fractional_scale: Option, blur: Option, blur_manager: Option, /// Whether the client side decorations have pending move operations. /// /// The value is the serial of the event triggered moved. has_pending_move: Option, /// The underlying SCTK window. pub window: Window, // NOTE: The spec says that destroying parent(`window` in our case), will unmap the // subsurfaces. Thus to achieve atomic unmap of the client, drop the decorations // frame after the `window` is dropped. To achieve that we rely on rust's struct // field drop order guarantees. /// The window frame, which is created from the configure request. frame: Option, } impl WindowState { /// Create new window state. pub fn new( connection: Connection, queue_handle: &QueueHandle, winit_state: &WinitState, initial_size: Size, window: Window, theme: Option, ) -> Self { let compositor = winit_state.compositor_state.clone(); let pointer_constraints = winit_state.pointer_constraints.clone(); let viewport = winit_state .viewporter_state .as_ref() .map(|state| state.get_viewport(window.wl_surface(), queue_handle)); let fractional_scale = winit_state .fractional_scaling_manager .as_ref() .map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle)); Self { blur: None, blur_manager: winit_state.kwin_blur_manager.clone(), compositor, connection, csd_fails: false, cursor_grab_mode: GrabState::new(), selected_cursor: Default::default(), cursor_visible: true, decorate: true, fractional_scale, frame: None, frame_callback_state: FrameCallbackState::None, seat_focus: Default::default(), has_pending_move: None, ime_allowed: false, ime_purpose: ImePurpose::Normal, last_configure: None, max_inner_size: None, min_inner_size: MIN_WINDOW_SIZE, pointer_constraints, pointers: Default::default(), queue_handle: queue_handle.clone(), resizable: true, scale_factor: 1., shm: winit_state.shm.wl_shm().clone(), custom_cursor_pool: winit_state.custom_cursor_pool.clone(), size: initial_size.to_logical(1.), stateless_size: initial_size.to_logical(1.), initial_size: Some(initial_size), text_inputs: Vec::new(), theme, title: String::default(), transparent: false, viewport, window, } } /// Apply closure on the given pointer. fn apply_on_pointer, &WinitPointerData)>( &self, callback: F, ) { self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| { let data = pointer.pointer().winit_data(); callback(pointer.as_ref(), data); }) } /// Get the current state of the frame callback. pub fn frame_callback_state(&self) -> FrameCallbackState { self.frame_callback_state } /// The frame callback was received, but not yet sent to the user. pub fn frame_callback_received(&mut self) { self.frame_callback_state = FrameCallbackState::Received; } /// Reset the frame callbacks state. pub fn frame_callback_reset(&mut self) { self.frame_callback_state = FrameCallbackState::None; } /// Request a frame callback if we don't have one for this window in flight. pub fn request_frame_callback(&mut self) { let surface = self.window.wl_surface(); match self.frame_callback_state { FrameCallbackState::None | FrameCallbackState::Received => { self.frame_callback_state = FrameCallbackState::Requested; surface.frame(&self.queue_handle, surface.clone()); }, FrameCallbackState::Requested => (), } } pub fn configure( &mut self, configure: WindowConfigure, shm: &Shm, subcompositor: &Option>, ) -> bool { // NOTE: when using fractional scaling or wl_compositor@v6 the scaling // should be delivered before the first configure, thus apply it to // properly scale the physical sizes provided by the users. if let Some(initial_size) = self.initial_size.take() { self.size = initial_size.to_logical(self.scale_factor()); self.stateless_size = self.size; } if let Some(subcompositor) = subcompositor.as_ref().filter(|_| { configure.decoration_mode == DecorationMode::Client && self.frame.is_none() && !self.csd_fails }) { match WinitFrame::new( &self.window, shm, #[cfg(feature = "sctk-adwaita")] self.compositor.clone(), subcompositor.clone(), self.queue_handle.clone(), #[cfg(feature = "sctk-adwaita")] into_sctk_adwaita_config(self.theme), ) { Ok(mut frame) => { frame.set_title(&self.title); frame.set_scaling_factor(self.scale_factor); // Hide the frame if we were asked to not decorate. frame.set_hidden(!self.decorate); self.frame = Some(frame); }, Err(err) => { warn!("Failed to create client side decorations frame: {err}"); self.csd_fails = true; }, } } else if configure.decoration_mode == DecorationMode::Server { // Drop the frame for server side decorations to save resources. self.frame = None; } let stateless = Self::is_stateless(&configure); let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() { // Configure the window states. frame.update_state(configure.state); match configure.new_size { (Some(width), Some(height)) => { let (width, height) = frame.subtract_borders(width, height); let width = width.map(|w| w.get()).unwrap_or(1); let height = height.map(|h| h.get()).unwrap_or(1); ((width, height).into(), false) }, (..) if stateless => (self.stateless_size, true), _ => (self.size, true), } } else { match configure.new_size { (Some(width), Some(height)) => ((width.get(), height.get()).into(), false), _ if stateless => (self.stateless_size, true), _ => (self.size, true), } }; // Apply configure bounds only when compositor let the user decide what size to pick. if constrain { let bounds = self.inner_size_bounds(&configure); new_size.width = bounds.0.map(|bound_w| new_size.width.min(bound_w.get())).unwrap_or(new_size.width); new_size.height = bounds .1 .map(|bound_h| new_size.height.min(bound_h.get())) .unwrap_or(new_size.height); } let new_state = configure.state; let old_state = self.last_configure.as_ref().map(|configure| configure.state); let state_change_requires_resize = old_state .map(|old_state| { !old_state .symmetric_difference(new_state) .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED) .is_empty() }) // NOTE: `None` is present for the initial configure, thus we must always resize. .unwrap_or(true); // NOTE: Set the configure before doing a resize, since we query it during it. self.last_configure = Some(configure); if state_change_requires_resize || new_size != self.inner_size() { self.resize(new_size); true } else { false } } /// Compute the bounds for the inner size of the surface. fn inner_size_bounds( &self, configure: &WindowConfigure, ) -> (Option, Option) { let configure_bounds = match configure.suggested_bounds { Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)), None => (None, None), }; if let Some(frame) = self.frame.as_ref() { let (width, height) = frame.subtract_borders( configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()), configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()), ); (configure_bounds.0.and(width), configure_bounds.1.and(height)) } else { configure_bounds } } #[inline] fn is_stateless(configure: &WindowConfigure) -> bool { !(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled()) } /// Start interacting drag resize. pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { let xdg_toplevel = self.window.xdg_toplevel(); // TODO(kchibisov) handle touch serials. self.apply_on_pointer(|_, data| { let serial = data.latest_button_serial(); let seat = data.seat(); xdg_toplevel.resize(seat, serial, direction.into()); }); Ok(()) } /// Start the window drag. pub fn drag_window(&self) -> Result<(), ExternalError> { let xdg_toplevel = self.window.xdg_toplevel(); // TODO(kchibisov) handle touch serials. self.apply_on_pointer(|_, data| { let serial = data.latest_button_serial(); let seat = data.seat(); xdg_toplevel._move(seat, serial); }); Ok(()) } /// Tells whether the window should be closed. #[allow(clippy::too_many_arguments)] pub fn frame_click( &mut self, click: FrameClick, pressed: bool, seat: &WlSeat, serial: u32, timestamp: Duration, window_id: WindowId, updates: &mut Vec, ) -> Option { match self.frame.as_mut()?.on_click(timestamp, click, pressed)? { FrameAction::Minimize => self.window.set_minimized(), FrameAction::Maximize => self.window.set_maximized(), FrameAction::UnMaximize => self.window.unset_maximized(), FrameAction::Close => WinitState::queue_close(updates, window_id), FrameAction::Move => self.has_pending_move = Some(serial), FrameAction::Resize(edge) => { let edge = match edge { ResizeEdge::None => XdgResizeEdge::None, ResizeEdge::Top => XdgResizeEdge::Top, ResizeEdge::Bottom => XdgResizeEdge::Bottom, ResizeEdge::Left => XdgResizeEdge::Left, ResizeEdge::TopLeft => XdgResizeEdge::TopLeft, ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft, ResizeEdge::Right => XdgResizeEdge::Right, ResizeEdge::TopRight => XdgResizeEdge::TopRight, ResizeEdge::BottomRight => XdgResizeEdge::BottomRight, _ => return None, }; self.window.resize(seat, serial, edge); }, FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)), _ => (), }; Some(false) } pub fn frame_point_left(&mut self) { if let Some(frame) = self.frame.as_mut() { frame.click_point_left(); } } // Move the point over decorations. pub fn frame_point_moved( &mut self, seat: &WlSeat, surface: &WlSurface, timestamp: Duration, x: f64, y: f64, ) -> Option { // Take the serial if we had any, so it doesn't stick around. let serial = self.has_pending_move.take(); if let Some(frame) = self.frame.as_mut() { let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y); // If we have a cursor change, that means that cursor is over the decorations, // so try to apply move. if let Some(serial) = cursor.is_some().then_some(serial).flatten() { self.window.move_(seat, serial); None } else { cursor } } else { None } } /// Get the stored resizable state. #[inline] pub fn resizable(&self) -> bool { self.resizable } /// Set the resizable state on the window. /// /// Returns `true` when the state was applied. #[inline] pub fn set_resizable(&mut self, resizable: bool) -> bool { if self.resizable == resizable { return false; } self.resizable = resizable; if resizable { // Restore min/max sizes of the window. self.reload_min_max_hints(); } else { self.set_min_inner_size(Some(self.size)); self.set_max_inner_size(Some(self.size)); } // Reload the state on the frame as well. if let Some(frame) = self.frame.as_mut() { frame.set_resizable(resizable); } true } /// Whether the window is focused by any seat. #[inline] pub fn has_focus(&self) -> bool { !self.seat_focus.is_empty() } /// Whether the IME is allowed. #[inline] pub fn ime_allowed(&self) -> bool { self.ime_allowed } /// Get the size of the window. #[inline] pub fn inner_size(&self) -> LogicalSize { self.size } /// Whether the window received initial configure event from the compositor. #[inline] pub fn is_configured(&self) -> bool { self.last_configure.is_some() } #[inline] pub fn is_decorated(&mut self) -> bool { let csd = self .last_configure .as_ref() .map(|configure| configure.decoration_mode == DecorationMode::Client) .unwrap_or(false); if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() { !frame.is_hidden() } else { // Server side decorations. true } } /// Get the outer size of the window. #[inline] pub fn outer_size(&self) -> LogicalSize { self.frame .as_ref() .map(|frame| frame.add_borders(self.size.width, self.size.height).into()) .unwrap_or(self.size) } /// Register pointer on the top-level. pub fn pointer_entered(&mut self, added: Weak>) { self.pointers.push(added); self.reload_cursor_style(); let mode = self.cursor_grab_mode.user_grab_mode; let _ = self.set_cursor_grab_inner(mode); } /// Pointer has left the top-level. pub fn pointer_left(&mut self, removed: Weak>) { let mut new_pointers = Vec::new(); for pointer in self.pointers.drain(..) { if let Some(pointer) = pointer.upgrade() { if pointer.pointer() != removed.upgrade().unwrap().pointer() { new_pointers.push(Arc::downgrade(&pointer)); } } } self.pointers = new_pointers; } /// Refresh the decorations frame if it's present returning whether the client should redraw. pub fn refresh_frame(&mut self) -> bool { if let Some(frame) = self.frame.as_mut() { if !frame.is_hidden() && frame.is_dirty() { return frame.draw(); } } false } /// Reload the cursor style on the given window. pub fn reload_cursor_style(&mut self) { if self.cursor_visible { match &self.selected_cursor { SelectedCursor::Named(icon) => self.set_cursor(*icon), SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor), } } else { self.set_cursor_visible(self.cursor_visible); } } /// Reissue the transparency hint to the compositor. pub fn reload_transparency_hint(&self) { let surface = self.window.wl_surface(); if self.transparent { surface.set_opaque_region(None); } else if let Ok(region) = Region::new(&*self.compositor) { region.add(0, 0, i32::MAX, i32::MAX); surface.set_opaque_region(Some(region.wl_region())); } else { warn!("Failed to mark window opaque."); } } /// Try to resize the window when the user can do so. pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize { if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) { self.resize(inner_size.to_logical(self.scale_factor())) } logical_to_physical_rounded(self.inner_size(), self.scale_factor()) } /// Resize the window to the new inner size. fn resize(&mut self, inner_size: LogicalSize) { self.size = inner_size; // Update the stateless size. if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) { self.stateless_size = inner_size; } // Update the inner frame. let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() { // Resize only visible frame. if !frame.is_hidden() { frame.resize( NonZeroU32::new(self.size.width).unwrap(), NonZeroU32::new(self.size.height).unwrap(), ); } (frame.location(), frame.add_borders(self.size.width, self.size.height).into()) } else { ((0, 0), self.size) }; // Reload the hint. self.reload_transparency_hint(); // Set the window geometry. self.window.xdg_surface().set_window_geometry( x, y, outer_size.width as i32, outer_size.height as i32, ); // Update the target viewport, this is used if and only if fractional scaling is in use. if let Some(viewport) = self.viewport.as_ref() { // Set inner size without the borders. viewport.set_destination(self.size.width as _, self.size.height as _); } } /// Get the scale factor of the window. #[inline] pub fn scale_factor(&self) -> f64 { self.scale_factor } /// Set the cursor icon. pub fn set_cursor(&mut self, cursor_icon: CursorIcon) { self.selected_cursor = SelectedCursor::Named(cursor_icon); if !self.cursor_visible { return; } self.apply_on_pointer(|pointer, _| { if pointer.set_cursor(&self.connection, cursor_icon).is_err() { warn!("Failed to set cursor to {:?}", cursor_icon); } }) } /// Set the custom cursor icon. pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) { let cursor = match cursor { RootCustomCursor { inner: PlatformCustomCursor::Wayland(cursor) } => cursor.0, #[cfg(x11_platform)] RootCustomCursor { inner: PlatformCustomCursor::X(_) } => { tracing::error!("passed a X11 cursor to Wayland backend"); return; }, }; let cursor = { let mut pool = self.custom_cursor_pool.lock().unwrap(); CustomCursor::new(&mut pool, &cursor) }; if self.cursor_visible { self.apply_custom_cursor(&cursor); } self.selected_cursor = SelectedCursor::Custom(cursor); } fn apply_custom_cursor(&self, cursor: &CustomCursor) { self.apply_on_pointer(|pointer, _| { let surface = pointer.surface(); let scale = surface.data::().unwrap().surface_data().scale_factor(); surface.set_buffer_scale(scale); surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0); if surface.version() >= 4 { surface.damage_buffer(0, 0, cursor.w, cursor.h); } else { surface.damage(0, 0, cursor.w / scale, cursor.h / scale); } surface.commit(); let serial = pointer .pointer() .data::() .and_then(|data| data.pointer_data().latest_enter_serial()) .unwrap(); pointer.pointer().set_cursor( serial, Some(surface), cursor.hotspot_x / scale, cursor.hotspot_y / scale, ); }); } /// Set maximum inner window size. pub fn set_min_inner_size(&mut self, size: Option>) { // Ensure that the window has the right minimum size. let mut size = size.unwrap_or(MIN_WINDOW_SIZE); size.width = size.width.max(MIN_WINDOW_SIZE.width); size.height = size.height.max(MIN_WINDOW_SIZE.height); // Add the borders. let size = self .frame .as_ref() .map(|frame| frame.add_borders(size.width, size.height).into()) .unwrap_or(size); self.min_inner_size = size; self.window.set_min_size(Some(size.into())); } /// Set maximum inner window size. pub fn set_max_inner_size(&mut self, size: Option>) { let size = size.map(|size| { self.frame .as_ref() .map(|frame| frame.add_borders(size.width, size.height).into()) .unwrap_or(size) }); self.max_inner_size = size; self.window.set_max_size(size.map(Into::into)); } /// Set the CSD theme. pub fn set_theme(&mut self, theme: Option) { self.theme = theme; #[cfg(feature = "sctk-adwaita")] if let Some(frame) = self.frame.as_mut() { frame.set_config(into_sctk_adwaita_config(theme)) } } /// The current theme for CSD decorations. #[inline] pub fn theme(&self) -> Option { self.theme } /// Set the cursor grabbing state on the top-level. pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> { if self.cursor_grab_mode.user_grab_mode == mode { return Ok(()); } self.set_cursor_grab_inner(mode)?; // Update user grab on success. self.cursor_grab_mode.user_grab_mode = mode; Ok(()) } /// Reload the hints for minimum and maximum sizes. pub fn reload_min_max_hints(&mut self) { self.set_min_inner_size(Some(self.min_inner_size)); self.set_max_inner_size(self.max_inner_size); } /// Set the grabbing state on the surface. fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> { let pointer_constraints = match self.pointer_constraints.as_ref() { Some(pointer_constraints) => pointer_constraints, None if mode == CursorGrabMode::None => return Ok(()), None => return Err(ExternalError::NotSupported(NotSupportedError::new())), }; // Replace the current mode. let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode); match old_mode { CursorGrabMode::None => (), CursorGrabMode::Confined => self.apply_on_pointer(|_, data| { data.unconfine_pointer(); }), CursorGrabMode::Locked => { self.apply_on_pointer(|_, data| data.unlock_pointer()); }, } let surface = self.window.wl_surface(); match mode { CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| { let pointer = pointer.pointer(); data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle) }), CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| { let pointer = pointer.pointer(); data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle) }), CursorGrabMode::None => { // Current lock/confine was already removed. }, } Ok(()) } pub fn show_window_menu(&self, position: LogicalPosition) { // TODO(kchibisov) handle touch serials. self.apply_on_pointer(|_, data| { let serial = data.latest_button_serial(); let seat = data.seat(); self.window.show_window_menu(seat, serial, position.into()); }); } /// Set the position of the cursor. pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> { if self.pointer_constraints.is_none() { return Err(ExternalError::NotSupported(NotSupportedError::new())); } // Position can be set only for locked cursor. if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked { return Err(ExternalError::Os(os_error!(crate::platform_impl::OsError::Misc( "cursor position can be set only for locked cursor." )))); } self.apply_on_pointer(|_, data| { data.set_locked_cursor_position(position.x, position.y); }); Ok(()) } /// Set the visibility state of the cursor. pub fn set_cursor_visible(&mut self, cursor_visible: bool) { self.cursor_visible = cursor_visible; if self.cursor_visible { match &self.selected_cursor { SelectedCursor::Named(icon) => self.set_cursor(*icon), SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor), } } else { for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) { let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial(); pointer.pointer().set_cursor(latest_enter_serial, None, 0, 0); } } } /// Whether show or hide client side decorations. #[inline] pub fn set_decorate(&mut self, decorate: bool) { if decorate == self.decorate { return; } self.decorate = decorate; match self.last_configure.as_ref().map(|configure| configure.decoration_mode) { Some(DecorationMode::Server) if !self.decorate => { // To disable decorations we should request client and hide the frame. self.window.request_decoration_mode(Some(DecorationMode::Client)) }, _ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)), _ => (), } if let Some(frame) = self.frame.as_mut() { frame.set_hidden(!decorate); // Force the resize. self.resize(self.size); } } /// Add seat focus for the window. #[inline] pub fn add_seat_focus(&mut self, seat: ObjectId) { self.seat_focus.insert(seat); } /// Remove seat focus from the window. #[inline] pub fn remove_seat_focus(&mut self, seat: &ObjectId) { self.seat_focus.remove(seat); } /// Returns `true` if the requested state was applied. pub fn set_ime_allowed(&mut self, allowed: bool) -> bool { self.ime_allowed = allowed; let mut applied = false; for text_input in &self.text_inputs { applied = true; if allowed { text_input.enable(); text_input.set_content_type_by_purpose(self.ime_purpose); } else { text_input.disable(); } text_input.commit(); } applied } /// Set the IME position. pub fn set_ime_cursor_area(&self, position: LogicalPosition, size: LogicalSize) { // FIXME: This won't fly unless user will have a way to request IME window per seat, since // the ime windows will be overlapping, but winit doesn't expose API to specify for // which seat we're setting IME position. let (x, y) = (position.x as i32, position.y as i32); let (width, height) = (size.width as i32, size.height as i32); for text_input in self.text_inputs.iter() { text_input.set_cursor_rectangle(x, y, width, height); text_input.commit(); } } /// Set the IME purpose. pub fn set_ime_purpose(&mut self, purpose: ImePurpose) { self.ime_purpose = purpose; for text_input in &self.text_inputs { text_input.set_content_type_by_purpose(purpose); text_input.commit(); } } /// Get the IME purpose. pub fn ime_purpose(&self) -> ImePurpose { self.ime_purpose } /// Set the scale factor for the given window. #[inline] pub fn set_scale_factor(&mut self, scale_factor: f64) { self.scale_factor = scale_factor; // NOTE: When fractional scaling is not used update the buffer scale. if self.fractional_scale.is_none() { let _ = self.window.set_buffer_scale(self.scale_factor as _); } if let Some(frame) = self.frame.as_mut() { frame.set_scaling_factor(scale_factor); } } /// Make window background blurred #[inline] pub fn set_blur(&mut self, blurred: bool) { if blurred && self.blur.is_none() { if let Some(blur_manager) = self.blur_manager.as_ref() { let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle); blur.commit(); self.blur = Some(blur); } else { info!("Blur manager unavailable, unable to change blur") } } else if !blurred && self.blur.is_some() { self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface()); self.blur.take().unwrap().release(); } } /// Set the window title to a new value. /// /// This will automatically truncate the title to something meaningful. pub fn set_title(&mut self, mut title: String) { // Truncate the title to at most 1024 bytes, so that it does not blow up the protocol // messages if title.len() > 1024 { let mut new_len = 1024; while !title.is_char_boundary(new_len) { new_len -= 1; } title.truncate(new_len); } // Update the CSD title. if let Some(frame) = self.frame.as_mut() { frame.set_title(&title); } self.window.set_title(&title); self.title = title; } /// Mark the window as transparent. #[inline] pub fn set_transparent(&mut self, transparent: bool) { self.transparent = transparent; self.reload_transparency_hint(); } /// Register text input on the top-level. #[inline] pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) { if !self.text_inputs.iter().any(|t| t == text_input) { self.text_inputs.push(text_input.clone()); } } /// The text input left the top-level. #[inline] pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) { if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) { self.text_inputs.remove(position); } } /// Get the cached title. #[inline] pub fn title(&self) -> &str { &self.title } } impl Drop for WindowState { fn drop(&mut self) { if let Some(blur) = self.blur.take() { blur.release(); } if let Some(fs) = self.fractional_scale.take() { fs.destroy(); } if let Some(viewport) = self.viewport.take() { viewport.destroy(); } // NOTE: the wl_surface used by the window is being cleaned up when // dropping SCTK `Window`. } } /// The state of the cursor grabs. #[derive(Clone, Copy)] struct GrabState { /// The grab mode requested by the user. user_grab_mode: CursorGrabMode, /// The current grab mode. current_grab_mode: CursorGrabMode, } impl GrabState { fn new() -> Self { Self { user_grab_mode: CursorGrabMode::None, current_grab_mode: CursorGrabMode::None } } } /// The state of the frame callback. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum FrameCallbackState { /// No frame callback was requested. #[default] None, /// The frame callback was requested, but not yet arrived, the redraw events are throttled. Requested, /// The callback was marked as done, and user could receive redraw requested Received, } impl From for XdgResizeEdge { fn from(value: ResizeDirection) -> Self { match value { ResizeDirection::North => XdgResizeEdge::Top, ResizeDirection::West => XdgResizeEdge::Left, ResizeDirection::NorthWest => XdgResizeEdge::TopLeft, ResizeDirection::NorthEast => XdgResizeEdge::TopRight, ResizeDirection::East => XdgResizeEdge::Right, ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft, ResizeDirection::SouthEast => XdgResizeEdge::BottomRight, ResizeDirection::South => XdgResizeEdge::Bottom, } } } // NOTE: Rust doesn't allow `From>`. #[cfg(feature = "sctk-adwaita")] fn into_sctk_adwaita_config(theme: Option) -> sctk_adwaita::FrameConfig { match theme { Some(Theme::Light) => sctk_adwaita::FrameConfig::light(), Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(), None => sctk_adwaita::FrameConfig::auto(), } } winit-0.30.9/src/platform_impl/linux/x11/activation.rs000064400000000000000000000141541046102023000207670ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 //! X11 activation handling. //! //! X11 has a "startup notification" specification similar to Wayland's, see this URL: //! use super::atoms::*; use super::{VoidCookie, X11Error, XConnection}; use std::ffi::CString; use std::fmt::Write; use x11rb::protocol::xproto::{self, ConnectionExt as _}; impl XConnection { /// "Request" a new activation token from the server. pub(crate) fn request_activation_token(&self, window_title: &str) -> Result { // The specification recommends the format "hostname+pid+"_TIME"+current time" let uname = rustix::system::uname(); let pid = rustix::process::getpid(); let time = self.timestamp(); let activation_token = format!( "{}{}_TIME{}", uname.nodename().to_str().unwrap_or("winit"), pid.as_raw_nonzero(), time ); // Set up the new startup notification. let notification = { let mut buffer = Vec::new(); buffer.extend_from_slice(b"new: ID="); quote_string(&activation_token, &mut buffer); buffer.extend_from_slice(b" NAME="); quote_string(window_title, &mut buffer); buffer.extend_from_slice(b" SCREEN="); push_display(&mut buffer, &self.default_screen_index()); CString::new(buffer) .map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))? .into_bytes_with_nul() }; self.send_message(¬ification)?; Ok(activation_token) } /// Finish launching a window with the given startup ID. pub(crate) fn remove_activation_token( &self, window: xproto::Window, startup_id: &str, ) -> Result<(), X11Error> { let atoms = self.atoms(); // Set the _NET_STARTUP_ID property on the window. self.xcb_connection() .change_property( xproto::PropMode::REPLACE, window, atoms[_NET_STARTUP_ID], xproto::AtomEnum::STRING, 8, startup_id.len().try_into().unwrap(), startup_id.as_bytes(), )? .check()?; // Send the message indicating that the startup is over. let message = { const MESSAGE_ROOT: &str = "remove: ID="; let mut buffer = Vec::with_capacity( MESSAGE_ROOT .len() .checked_add(startup_id.len()) .and_then(|x| x.checked_add(1)) .unwrap(), ); buffer.extend_from_slice(MESSAGE_ROOT.as_bytes()); quote_string(startup_id, &mut buffer); CString::new(buffer) .map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))? .into_bytes_with_nul() }; self.send_message(&message) } /// Send a startup notification message to the window manager. fn send_message(&self, message: &[u8]) -> Result<(), X11Error> { let atoms = self.atoms(); // Create a new window to send the message over. let screen = self.default_root(); let window = xproto::WindowWrapper::create_window( self.xcb_connection(), screen.root_depth, screen.root, -100, -100, 1, 1, 0, xproto::WindowClass::INPUT_OUTPUT, screen.root_visual, &xproto::CreateWindowAux::new().override_redirect(1).event_mask( xproto::EventMask::STRUCTURE_NOTIFY | xproto::EventMask::PROPERTY_CHANGE, ), )?; // Serialize the messages in 20-byte chunks. let mut message_type = atoms[_NET_STARTUP_INFO_BEGIN]; message .chunks(20) .map(|chunk| { let mut buffer = [0u8; 20]; buffer[..chunk.len()].copy_from_slice(chunk); let event = xproto::ClientMessageEvent::new(8, window.window(), message_type, buffer); // Set the message type to the continuation atom for the next chunk. message_type = atoms[_NET_STARTUP_INFO]; event }) .try_for_each(|event| { // Send each event in order. self.xcb_connection() .send_event(false, screen.root, xproto::EventMask::PROPERTY_CHANGE, event) .map(VoidCookie::ignore_error) })?; Ok(()) } } /// Quote a literal string as per the startup notification specification. fn quote_string(s: &str, target: &mut Vec) { let total_len = s.len().checked_add(3).expect("quote string overflow"); target.reserve(total_len); // Add the opening quote. target.push(b'"'); // Iterate over the string split by literal quotes. s.as_bytes().split(|&b| b == b'"').for_each(|part| { // Add the part. target.extend_from_slice(part); // Escape the quote. target.push(b'\\'); target.push(b'"'); }); // Un-escape the last quote. target.remove(target.len() - 2); } /// Push a `Display` implementation to the buffer. fn push_display(buffer: &mut Vec, display: &impl std::fmt::Display) { struct Writer<'a> { buffer: &'a mut Vec, } impl std::fmt::Write for Writer<'_> { fn write_str(&mut self, s: &str) -> std::fmt::Result { self.buffer.extend_from_slice(s.as_bytes()); Ok(()) } } write!(Writer { buffer }, "{}", display).unwrap(); } #[cfg(test)] mod tests { use super::*; #[test] fn properly_escapes_x11_messages() { let assert_eq = |input: &str, output: &[u8]| { let mut buf = vec![]; quote_string(input, &mut buf); assert_eq!(buf, output); }; assert_eq("", b"\"\""); assert_eq("foo", b"\"foo\""); assert_eq("foo\"bar", b"\"foo\\\"bar\""); } } winit-0.30.9/src/platform_impl/linux/x11/atoms.rs000064400000000000000000000052131046102023000177450ustar 00000000000000//! Collects every atom used by the platform implementation. use core::ops::Index; macro_rules! atom_manager { ($($name:ident $(:$lit:literal)?),*) => { x11rb::atom_manager! { /// The atoms used by `winit` pub Atoms: AtomsCookie { $($name $(:$lit)?,)* } } /// Indices into the `Atoms` struct. #[derive(Copy, Clone, Debug)] #[allow(non_camel_case_types)] pub enum AtomName { $($name,)* } impl AtomName { pub(crate) fn atom_from( self, atoms: &Atoms ) -> &x11rb::protocol::xproto::Atom { match self { $(AtomName::$name => &atoms.$name,)* } } } }; } atom_manager! { // General Use Atoms CARD32, UTF8_STRING, WM_CHANGE_STATE, WM_CLIENT_MACHINE, WM_DELETE_WINDOW, WM_PROTOCOLS, WM_STATE, XIM_SERVERS, // Assorted ICCCM Atoms _NET_WM_ICON, _NET_WM_MOVERESIZE, _NET_WM_NAME, _NET_WM_PID, _NET_WM_PING, _NET_WM_STATE, _NET_WM_STATE_ABOVE, _NET_WM_STATE_BELOW, _NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_HIDDEN, _NET_WM_STATE_MAXIMIZED_HORZ, _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_WINDOW_TYPE, // Activation atoms. _NET_STARTUP_INFO_BEGIN, _NET_STARTUP_INFO, _NET_STARTUP_ID, // WM window types. _NET_WM_WINDOW_TYPE_DESKTOP, _NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_TOOLBAR, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_UTILITY, _NET_WM_WINDOW_TYPE_SPLASH, _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, _NET_WM_WINDOW_TYPE_POPUP_MENU, _NET_WM_WINDOW_TYPE_TOOLTIP, _NET_WM_WINDOW_TYPE_NOTIFICATION, _NET_WM_WINDOW_TYPE_COMBO, _NET_WM_WINDOW_TYPE_DND, _NET_WM_WINDOW_TYPE_NORMAL, // Drag-N-Drop Atoms XdndAware, XdndEnter, XdndLeave, XdndDrop, XdndPosition, XdndStatus, XdndActionPrivate, XdndSelection, XdndFinished, XdndTypeList, TextUriList: b"text/uri-list", None: b"None", // Miscellaneous Atoms _GTK_THEME_VARIANT, _MOTIF_WM_HINTS, _NET_ACTIVE_WINDOW, _NET_CLIENT_LIST, _NET_FRAME_EXTENTS, _NET_SUPPORTED, _NET_SUPPORTING_WM_CHECK, _XEMBED, _XSETTINGS_SETTINGS } impl Index for Atoms { type Output = x11rb::protocol::xproto::Atom; fn index(&self, index: AtomName) -> &Self::Output { index.atom_from(self) } } pub(crate) use AtomName::*; // Make sure `None` is still defined. pub(crate) use core::option::Option::None; winit-0.30.9/src/platform_impl/linux/x11/dnd.rs000064400000000000000000000126101046102023000173660ustar 00000000000000use std::io; use std::os::raw::*; use std::path::{Path, PathBuf}; use std::str::Utf8Error; use std::sync::Arc; use percent_encoding::percent_decode; use x11rb::protocol::xproto::{self, ConnectionExt}; use super::atoms::AtomName::None as DndNone; use super::atoms::*; use super::{util, CookieResultExt, X11Error, XConnection}; #[derive(Debug, Clone, Copy)] pub enum DndState { Accepted, Rejected, } #[derive(Debug)] pub enum DndDataParseError { EmptyData, InvalidUtf8(#[allow(dead_code)] Utf8Error), HostnameSpecified(#[allow(dead_code)] String), UnexpectedProtocol(#[allow(dead_code)] String), UnresolvablePath(#[allow(dead_code)] io::Error), } impl From for DndDataParseError { fn from(e: Utf8Error) -> Self { DndDataParseError::InvalidUtf8(e) } } impl From for DndDataParseError { fn from(e: io::Error) -> Self { DndDataParseError::UnresolvablePath(e) } } pub struct Dnd { xconn: Arc, // Populated by XdndEnter event handler pub version: Option, pub type_list: Option>, // Populated by XdndPosition event handler pub source_window: Option, // Populated by SelectionNotify event handler (triggered by XdndPosition event handler) pub result: Option, DndDataParseError>>, } impl Dnd { pub fn new(xconn: Arc) -> Result { Ok(Dnd { xconn, version: None, type_list: None, source_window: None, result: None }) } pub fn reset(&mut self) { self.version = None; self.type_list = None; self.source_window = None; self.result = None; } pub unsafe fn send_status( &self, this_window: xproto::Window, target_window: xproto::Window, state: DndState, ) -> Result<(), X11Error> { let atoms = self.xconn.atoms(); let (accepted, action) = match state { DndState::Accepted => (1, atoms[XdndActionPrivate]), DndState::Rejected => (0, atoms[DndNone]), }; self.xconn .send_client_msg(target_window, target_window, atoms[XdndStatus] as _, None, [ this_window, accepted, 0, 0, action as _, ])? .ignore_error(); Ok(()) } pub unsafe fn send_finished( &self, this_window: xproto::Window, target_window: xproto::Window, state: DndState, ) -> Result<(), X11Error> { let atoms = self.xconn.atoms(); let (accepted, action) = match state { DndState::Accepted => (1, atoms[XdndActionPrivate]), DndState::Rejected => (0, atoms[DndNone]), }; self.xconn .send_client_msg(target_window, target_window, atoms[XdndFinished] as _, None, [ this_window, accepted, action as _, 0, 0, ])? .ignore_error(); Ok(()) } pub unsafe fn get_type_list( &self, source_window: xproto::Window, ) -> Result, util::GetPropertyError> { let atoms = self.xconn.atoms(); self.xconn.get_property( source_window, atoms[XdndTypeList], xproto::Atom::from(xproto::AtomEnum::ATOM), ) } pub unsafe fn convert_selection(&self, window: xproto::Window, time: xproto::Timestamp) { let atoms = self.xconn.atoms(); self.xconn .xcb_connection() .convert_selection( window, atoms[XdndSelection], atoms[TextUriList], atoms[XdndSelection], time, ) .expect_then_ignore_error("Failed to send XdndSelection event") } pub unsafe fn read_data( &self, window: xproto::Window, ) -> Result, util::GetPropertyError> { let atoms = self.xconn.atoms(); self.xconn.get_property(window, atoms[XdndSelection], atoms[TextUriList]) } pub fn parse_data(&self, data: &mut [c_uchar]) -> Result, DndDataParseError> { if !data.is_empty() { let mut path_list = Vec::new(); let decoded = percent_decode(data).decode_utf8()?.into_owned(); for uri in decoded.split("\r\n").filter(|u| !u.is_empty()) { // The format is specified as protocol://host/path // However, it's typically simply protocol:///path let path_str = if uri.starts_with("file://") { let path_str = uri.replace("file://", ""); if !path_str.starts_with('/') { // A hostname is specified // Supporting this case is beyond the scope of my mental health return Err(DndDataParseError::HostnameSpecified(path_str)); } path_str } else { // Only the file protocol is supported return Err(DndDataParseError::UnexpectedProtocol(uri.to_owned())); }; let path = Path::new(&path_str).canonicalize()?; path_list.push(path); } Ok(path_list) } else { Err(DndDataParseError::EmptyData) } } } winit-0.30.9/src/platform_impl/linux/x11/event_processor.rs000064400000000000000000002173451046102023000220550ustar 00000000000000use std::cell::{Cell, RefCell}; use std::collections::{HashMap, VecDeque}; use std::os::raw::{c_char, c_int, c_long, c_ulong}; use std::slice; use std::sync::{Arc, Mutex}; use x11_dl::xinput2::{ self, XIDeviceEvent, XIEnterEvent, XIFocusInEvent, XIFocusOutEvent, XIHierarchyEvent, XILeaveEvent, XIModifierState, XIRawEvent, }; use x11_dl::xlib::{ self, Display as XDisplay, Window as XWindow, XAnyEvent, XClientMessageEvent, XConfigureEvent, XDestroyWindowEvent, XEvent, XExposeEvent, XKeyEvent, XMapEvent, XPropertyEvent, XReparentEvent, XSelectionEvent, XVisibilityEvent, XkbAnyEvent, XkbStateRec, }; use x11rb::protocol::xinput; use x11rb::protocol::xkb::ID as XkbId; use x11rb::protocol::xproto::{self, ConnectionExt as _, ModMask}; use x11rb::x11_utils::{ExtensionInformation, Serialize}; use xkbcommon_dl::xkb_mod_mask_t; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::event::{ DeviceEvent, ElementState, Event, Ime, InnerSizeWriter, MouseButton, MouseScrollDelta, RawKeyEvent, Touch, TouchPhase, WindowEvent, }; use crate::event_loop::ActiveEventLoop as RootAEL; use crate::keyboard::ModifiersState; use crate::platform_impl::common::xkb::{self, XkbState}; use crate::platform_impl::platform::common::xkb::Context; use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest}; use crate::platform_impl::platform::x11::ActiveEventLoop; use crate::platform_impl::platform::ActiveEventLoop as PlatformActiveEventLoop; use crate::platform_impl::x11::atoms::*; use crate::platform_impl::x11::util::cookie::GenericEventCookie; use crate::platform_impl::x11::{ mkdid, mkwid, util, CookieResultExt, Device, DeviceId, DeviceInfo, Dnd, DndState, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, }; /// The maximum amount of X modifiers to replay. pub const MAX_MOD_REPLAY_LEN: usize = 32; /// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`". const KEYCODE_OFFSET: u8 = 8; pub struct EventProcessor { pub dnd: Dnd, pub ime_receiver: ImeReceiver, pub ime_event_receiver: ImeEventReceiver, pub randr_event_offset: u8, pub devices: RefCell>, pub xi2ext: ExtensionInformation, pub xkbext: ExtensionInformation, pub target: RootAEL, pub xkb_context: Context, // Number of touch events currently in progress pub num_touch: u32, // This is the last pressed key that is repeatable (if it hasn't been // released). // // Used to detect key repeats. pub held_key_press: Option, pub first_touch: Option, // Currently focused window belonging to this process pub active_window: Option, /// Latest modifiers we've sent for the user to trigger change in event. pub modifiers: Cell, // Track modifiers based on keycodes. NOTE: that serials generally don't work for tracking // since they are not unique and could be duplicated in case of sequence of key events is // delivered at near the same time. pub xfiltered_modifiers: VecDeque, pub xmodmap: util::ModifierKeymap, pub is_composing: bool, } impl EventProcessor { pub fn process_event(&mut self, xev: &mut XEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { self.process_xevent(xev, &mut callback); let window_target = Self::window_target_mut(&mut self.target); // Handle IME requests. while let Ok(request) = self.ime_receiver.try_recv() { let ime = match window_target.ime.as_mut() { Some(ime) => ime, None => continue, }; let ime = ime.get_mut(); match request { ImeRequest::Position(window_id, x, y) => { ime.send_xim_spot(window_id, x, y); }, ImeRequest::Allow(window_id, allowed) => { ime.set_ime_allowed(window_id, allowed); }, } } // Drain IME events. while let Ok((window, event)) = self.ime_event_receiver.try_recv() { let window_id = mkwid(window as xproto::Window); let event = match event { ImeEvent::Enabled => WindowEvent::Ime(Ime::Enabled), ImeEvent::Start => { self.is_composing = true; WindowEvent::Ime(Ime::Preedit("".to_owned(), None)) }, ImeEvent::Update(text, position) if self.is_composing => { WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))) }, ImeEvent::End => { self.is_composing = false; // Issue empty preedit on `Done`. WindowEvent::Ime(Ime::Preedit(String::new(), None)) }, ImeEvent::Disabled => { self.is_composing = false; WindowEvent::Ime(Ime::Disabled) }, _ => continue, }; callback(&self.target, Event::WindowEvent { window_id, event }); } } /// XFilterEvent tells us when an event has been discarded by the input method. /// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences, /// along with an extra copy of the KeyRelease events. This also prevents backspace and /// arrow keys from being detected twice. #[must_use] fn filter_event(&mut self, xev: &mut XEvent) -> bool { let wt = Self::window_target(&self.target); unsafe { (wt.xconn.xlib.XFilterEvent)(xev, { let xev: &XAnyEvent = xev.as_ref(); xev.window }) == xlib::True } } fn process_xevent(&mut self, xev: &mut XEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let event_type = xev.get_type(); // If we have IME disabled, don't try to `filter_event`, since only IME can consume them // and forward back. This is not desired for e.g. games since some IMEs may delay the input // and game can toggle IME back when e.g. typing into some field where latency won't really // matter. let filtered = if event_type == xlib::KeyPress || event_type == xlib::KeyRelease { let wt = Self::window_target(&self.target); let ime = wt.ime.as_ref(); let window = self.active_window.map(|window| window as XWindow); let forward_to_ime = ime .and_then(|ime| window.map(|window| ime.borrow().is_ime_allowed(window))) .unwrap_or(false); let filtered = forward_to_ime && self.filter_event(xev); if filtered { let xev: &XKeyEvent = xev.as_ref(); if self.xmodmap.is_modifier(xev.keycode as u8) { // Don't grow the buffer past the `MAX_MOD_REPLAY_LEN`. This could happen // when the modifiers are consumed entirely. if self.xfiltered_modifiers.len() == MAX_MOD_REPLAY_LEN { self.xfiltered_modifiers.pop_back(); } self.xfiltered_modifiers.push_front(xev.keycode as u8); } } filtered } else { self.filter_event(xev) }; // Don't process event if it was filtered. if filtered { return; } match event_type { xlib::ClientMessage => self.client_message(xev.as_ref(), &mut callback), xlib::SelectionNotify => self.selection_notify(xev.as_ref(), &mut callback), xlib::ConfigureNotify => self.configure_notify(xev.as_ref(), &mut callback), xlib::ReparentNotify => self.reparent_notify(xev.as_ref()), xlib::MapNotify => self.map_notify(xev.as_ref(), &mut callback), xlib::DestroyNotify => self.destroy_notify(xev.as_ref(), &mut callback), xlib::PropertyNotify => self.property_notify(xev.as_ref(), &mut callback), xlib::VisibilityNotify => self.visibility_notify(xev.as_ref(), &mut callback), xlib::Expose => self.expose(xev.as_ref(), &mut callback), // Note that in compose/pre-edit sequences, we'll always receive KeyRelease events. ty @ xlib::KeyPress | ty @ xlib::KeyRelease => { let state = if ty == xlib::KeyPress { ElementState::Pressed } else { ElementState::Released }; self.xinput_key_input(xev.as_mut(), state, &mut callback); }, xlib::GenericEvent => { let wt = Self::window_target(&self.target); let xev: GenericEventCookie = match GenericEventCookie::from_event(wt.xconn.clone(), *xev) { Some(xev) if xev.extension() == self.xi2ext.major_opcode => xev, _ => return, }; let evtype = xev.evtype(); match evtype { ty @ xinput2::XI_ButtonPress | ty @ xinput2::XI_ButtonRelease => { let state = if ty == xinput2::XI_ButtonPress { ElementState::Pressed } else { ElementState::Released }; let xev: &XIDeviceEvent = unsafe { xev.as_event() }; self.update_mods_from_xinput2_event( &xev.mods, &xev.group, false, &mut callback, ); self.xinput2_button_input(xev, state, &mut callback); }, xinput2::XI_Motion => { let xev: &XIDeviceEvent = unsafe { xev.as_event() }; self.update_mods_from_xinput2_event( &xev.mods, &xev.group, false, &mut callback, ); self.xinput2_mouse_motion(xev, &mut callback); }, xinput2::XI_Enter => { let xev: &XIEnterEvent = unsafe { xev.as_event() }; self.xinput2_mouse_enter(xev, &mut callback); }, xinput2::XI_Leave => { let xev: &XILeaveEvent = unsafe { xev.as_event() }; self.update_mods_from_xinput2_event( &xev.mods, &xev.group, false, &mut callback, ); self.xinput2_mouse_left(xev, &mut callback); }, xinput2::XI_FocusIn => { let xev: &XIFocusInEvent = unsafe { xev.as_event() }; self.xinput2_focused(xev, &mut callback); }, xinput2::XI_FocusOut => { let xev: &XIFocusOutEvent = unsafe { xev.as_event() }; self.xinput2_unfocused(xev, &mut callback); }, xinput2::XI_TouchBegin | xinput2::XI_TouchUpdate | xinput2::XI_TouchEnd => { let phase = match evtype { xinput2::XI_TouchBegin => TouchPhase::Started, xinput2::XI_TouchUpdate => TouchPhase::Moved, xinput2::XI_TouchEnd => TouchPhase::Ended, _ => unreachable!(), }; let xev: &XIDeviceEvent = unsafe { xev.as_event() }; self.xinput2_touch(xev, phase, &mut callback); }, xinput2::XI_RawButtonPress | xinput2::XI_RawButtonRelease => { let state = match evtype { xinput2::XI_RawButtonPress => ElementState::Pressed, xinput2::XI_RawButtonRelease => ElementState::Released, _ => unreachable!(), }; let xev: &XIRawEvent = unsafe { xev.as_event() }; self.xinput2_raw_button_input(xev, state, &mut callback); }, xinput2::XI_RawMotion => { let xev: &XIRawEvent = unsafe { xev.as_event() }; self.xinput2_raw_mouse_motion(xev, &mut callback); }, xinput2::XI_RawKeyPress | xinput2::XI_RawKeyRelease => { let state = match evtype { xinput2::XI_RawKeyPress => ElementState::Pressed, xinput2::XI_RawKeyRelease => ElementState::Released, _ => unreachable!(), }; let xev: &xinput2::XIRawEvent = unsafe { xev.as_event() }; self.xinput2_raw_key_input(xev, state, &mut callback); }, xinput2::XI_HierarchyChanged => { let xev: &XIHierarchyEvent = unsafe { xev.as_event() }; self.xinput2_hierarchy_changed(xev, &mut callback); }, _ => {}, } }, _ => { if event_type == self.xkbext.first_event as _ { let xev: &XkbAnyEvent = unsafe { &*(xev as *const _ as *const XkbAnyEvent) }; self.xkb_event(xev, &mut callback); } if event_type == self.randr_event_offset as c_int { self.process_dpi_change(&mut callback); } }, } } pub fn poll(&self) -> bool { let window_target = Self::window_target(&self.target); let result = unsafe { (window_target.xconn.xlib.XPending)(window_target.xconn.display) }; result != 0 } pub unsafe fn poll_one_event(&mut self, event_ptr: *mut XEvent) -> bool { let window_target = Self::window_target(&self.target); // This function is used to poll and remove a single event // from the Xlib event queue in a non-blocking, atomic way. // XCheckIfEvent is non-blocking and removes events from queue. // XNextEvent can't be used because it blocks while holding the // global Xlib mutex. // XPeekEvent does not remove events from the queue. unsafe extern "C" fn predicate( _display: *mut XDisplay, _event: *mut XEvent, _arg: *mut c_char, ) -> c_int { // This predicate always returns "true" (1) to accept all events 1 } let result = unsafe { (window_target.xconn.xlib.XCheckIfEvent)( window_target.xconn.display, event_ptr, Some(predicate), std::ptr::null_mut(), ) }; result != 0 } pub fn init_device(&self, device: xinput::DeviceId) { let window_target = Self::window_target(&self.target); let mut devices = self.devices.borrow_mut(); if let Some(info) = DeviceInfo::get(&window_target.xconn, device as _) { for info in info.iter() { devices.insert(DeviceId(info.deviceid as _), Device::new(info)); } } } pub fn with_window(&self, window_id: xproto::Window, callback: F) -> Option where F: Fn(&Arc) -> Ret, { let mut deleted = false; let window_id = WindowId(window_id as _); let window_target = Self::window_target(&self.target); let result = window_target .windows .borrow() .get(&window_id) .and_then(|window| { let arc = window.upgrade(); deleted = arc.is_none(); arc }) .map(|window| callback(&window)); if deleted { // Garbage collection window_target.windows.borrow_mut().remove(&window_id); } result } // NOTE: we avoid `self` to not borrow the entire `self` as not mut. /// Get the platform window target. pub fn window_target(window_target: &RootAEL) -> &ActiveEventLoop { match &window_target.p { PlatformActiveEventLoop::X(target) => target, #[cfg(wayland_platform)] _ => unreachable!(), } } /// Get the platform window target. pub fn window_target_mut(window_target: &mut RootAEL) -> &mut ActiveEventLoop { match &mut window_target.p { PlatformActiveEventLoop::X(target) => target, #[cfg(wayland_platform)] _ => unreachable!(), } } fn client_message(&mut self, xev: &XClientMessageEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); let atoms = wt.xconn.atoms(); let window = xev.window as xproto::Window; let window_id = mkwid(window); if xev.data.get_long(0) as xproto::Atom == wt.wm_delete_window { let event = Event::WindowEvent { window_id, event: WindowEvent::CloseRequested }; callback(&self.target, event); return; } if xev.data.get_long(0) as xproto::Atom == wt.net_wm_ping { let client_msg = xproto::ClientMessageEvent { response_type: xproto::CLIENT_MESSAGE_EVENT, format: xev.format as _, sequence: xev.serial as _, window: wt.root, type_: xev.message_type as _, data: xproto::ClientMessageData::from({ let [a, b, c, d, e]: [c_long; 5] = xev.data.as_longs().try_into().unwrap(); [a as u32, b as u32, c as u32, d as u32, e as u32] }), }; wt.xconn .xcb_connection() .send_event( false, wt.root, xproto::EventMask::SUBSTRUCTURE_NOTIFY | xproto::EventMask::SUBSTRUCTURE_REDIRECT, client_msg.serialize(), ) .expect_then_ignore_error("Failed to send `ClientMessage` event."); return; } if xev.message_type == atoms[XdndEnter] as c_ulong { let source_window = xev.data.get_long(0) as xproto::Window; let flags = xev.data.get_long(1); let version = flags >> 24; self.dnd.version = Some(version); let has_more_types = flags - (flags & (c_long::MAX - 1)) == 1; if !has_more_types { let type_list = vec![ xev.data.get_long(2) as xproto::Atom, xev.data.get_long(3) as xproto::Atom, xev.data.get_long(4) as xproto::Atom, ]; self.dnd.type_list = Some(type_list); } else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) } { self.dnd.type_list = Some(more_types); } return; } if xev.message_type == atoms[XdndPosition] as c_ulong { // This event occurs every time the mouse moves while a file's being dragged // over our window. We emit HoveredFile in response; while the macOS backend // does that upon a drag entering, XDND doesn't have access to the actual drop // data until this event. For parity with other platforms, we only emit // `HoveredFile` the first time, though if winit's API is later extended to // supply position updates with `HoveredFile` or another event, implementing // that here would be trivial. let source_window = xev.data.get_long(0) as xproto::Window; // Equivalent to `(x << shift) | y` // where `shift = mem::size_of::() * 8` // Note that coordinates are in "desktop space", not "window space" // (in X11 parlance, they're root window coordinates) // let packed_coordinates = xev.data.get_long(2); // let shift = mem::size_of::() * 8; // let x = packed_coordinates >> shift; // let y = packed_coordinates & !(x << shift); // By our own state flow, `version` should never be `None` at this point. let version = self.dnd.version.unwrap_or(5); // Action is specified in versions 2 and up, though we don't need it anyway. // let action = xev.data.get_long(4); let accepted = if let Some(ref type_list) = self.dnd.type_list { type_list.contains(&atoms[TextUriList]) } else { false }; if !accepted { unsafe { self.dnd .send_status(window, source_window, DndState::Rejected) .expect("Failed to send `XdndStatus` message."); } self.dnd.reset(); return; } self.dnd.source_window = Some(source_window); if self.dnd.result.is_none() { let time = if version >= 1 { xev.data.get_long(3) as xproto::Timestamp } else { // In version 0, time isn't specified x11rb::CURRENT_TIME }; // Log this timestamp. wt.xconn.set_timestamp(time); // This results in the `SelectionNotify` event below unsafe { self.dnd.convert_selection(window, time); } } unsafe { self.dnd .send_status(window, source_window, DndState::Accepted) .expect("Failed to send `XdndStatus` message."); } return; } if xev.message_type == atoms[XdndDrop] as c_ulong { let (source_window, state) = if let Some(source_window) = self.dnd.source_window { if let Some(Ok(ref path_list)) = self.dnd.result { for path in path_list { let event = Event::WindowEvent { window_id, event: WindowEvent::DroppedFile(path.clone()), }; callback(&self.target, event); } } (source_window, DndState::Accepted) } else { // `source_window` won't be part of our DND state if we already rejected the drop in // our `XdndPosition` handler. let source_window = xev.data.get_long(0) as xproto::Window; (source_window, DndState::Rejected) }; unsafe { self.dnd .send_finished(window, source_window, state) .expect("Failed to send `XdndFinished` message."); } self.dnd.reset(); return; } if xev.message_type == atoms[XdndLeave] as c_ulong { self.dnd.reset(); let event = Event::WindowEvent { window_id, event: WindowEvent::HoveredFileCancelled }; callback(&self.target, event); } } fn selection_notify(&mut self, xev: &XSelectionEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); let atoms = wt.xconn.atoms(); let window = xev.requestor as xproto::Window; let window_id = mkwid(window); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); if xev.property != atoms[XdndSelection] as c_ulong { return; } // This is where we receive data from drag and drop self.dnd.result = None; if let Ok(mut data) = unsafe { self.dnd.read_data(window) } { let parse_result = self.dnd.parse_data(&mut data); if let Ok(ref path_list) = parse_result { for path in path_list { let event = Event::WindowEvent { window_id, event: WindowEvent::HoveredFile(path.clone()), }; callback(&self.target, event); } } self.dnd.result = Some(parse_result); } } fn configure_notify(&self, xev: &XConfigureEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); let xwindow = xev.window as xproto::Window; let window_id = mkwid(xwindow); let window = match self.with_window(xwindow, Arc::clone) { Some(window) => window, None => return, }; // So apparently... // `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root // `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent // https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.5 // We don't want to send `Moved` when this is false, since then every `Resized` // (whether the window moved or not) is accompanied by an extraneous `Moved` event // that has a position relative to the parent window. let is_synthetic = xev.send_event == xlib::True; // These are both in physical space. let new_inner_size = (xev.width as u32, xev.height as u32); let new_inner_position = (xev.x, xev.y); let (mut resized, moved) = { let mut shared_state_lock = window.shared_state_lock(); let resized = util::maybe_change(&mut shared_state_lock.size, new_inner_size); let moved = if is_synthetic { util::maybe_change(&mut shared_state_lock.inner_position, new_inner_position) } else { // Detect when frame extents change. // Since this isn't synthetic, as per the notes above, this position is relative to // the parent window. let rel_parent = new_inner_position; if util::maybe_change(&mut shared_state_lock.inner_position_rel_parent, rel_parent) { // This ensures we process the next `Moved`. shared_state_lock.inner_position = None; // Extra insurance against stale frame extents. shared_state_lock.frame_extents = None; } false }; (resized, moved) }; let position = window.shared_state_lock().position; let new_outer_position = if let (Some(position), false) = (position, moved) { position } else { let mut shared_state_lock = window.shared_state_lock(); // We need to convert client area position to window position. let frame_extents = shared_state_lock.frame_extents.as_ref().cloned().unwrap_or_else(|| { let frame_extents = wt.xconn.get_frame_extents_heuristic(xwindow, wt.root); shared_state_lock.frame_extents = Some(frame_extents.clone()); frame_extents }); let outer = frame_extents.inner_pos_to_outer(new_inner_position.0, new_inner_position.1); shared_state_lock.position = Some(outer); // Unlock shared state to prevent deadlock in callback below drop(shared_state_lock); if moved { callback(&self.target, Event::WindowEvent { window_id, event: WindowEvent::Moved(outer.into()), }); } outer }; if is_synthetic { let mut shared_state_lock = window.shared_state_lock(); // If we don't use the existing adjusted value when available, then the user can screw // up the resizing by dragging across monitors *without* dropping the // window. let (width, height) = shared_state_lock.dpi_adjusted.unwrap_or((xev.width as u32, xev.height as u32)); let last_scale_factor = shared_state_lock.last_monitor.scale_factor; let new_scale_factor = { let window_rect = util::AaRect::new(new_outer_position, new_inner_size); let monitor = wt .xconn .get_monitor_for_window(Some(window_rect)) .expect("Failed to find monitor for window"); if monitor.is_dummy() { // Avoid updating monitor using a dummy monitor handle last_scale_factor } else { shared_state_lock.last_monitor = monitor.clone(); monitor.scale_factor } }; if last_scale_factor != new_scale_factor { let (new_width, new_height) = window.adjust_for_dpi( last_scale_factor, new_scale_factor, width, height, &shared_state_lock, ); let old_inner_size = PhysicalSize::new(width, height); let new_inner_size = PhysicalSize::new(new_width, new_height); // Unlock shared state to prevent deadlock in callback below drop(shared_state_lock); let inner_size = Arc::new(Mutex::new(new_inner_size)); callback(&self.target, Event::WindowEvent { window_id, event: WindowEvent::ScaleFactorChanged { scale_factor: new_scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)), }, }); let new_inner_size = *inner_size.lock().unwrap(); drop(inner_size); if new_inner_size != old_inner_size { window.request_inner_size_physical(new_inner_size.width, new_inner_size.height); window.shared_state_lock().dpi_adjusted = Some(new_inner_size.into()); // if the DPI factor changed, force a resize event to ensure the logical // size is computed with the right DPI factor resized = true; } } } // NOTE: Ensure that the lock is dropped before handling the resized and // sending the event back to user. let hittest = { let mut shared_state_lock = window.shared_state_lock(); let hittest = shared_state_lock.cursor_hittest; // This is a hack to ensure that the DPI adjusted resize is actually // applied on all WMs. KWin doesn't need this, but Xfwm does. The hack // should not be run on other WMs, since tiling WMs constrain the window // size, making the resize fail. This would cause an endless stream of // XResizeWindow requests, making Xorg, the winit client, and the WM // consume 100% of CPU. if let Some(adjusted_size) = shared_state_lock.dpi_adjusted { if new_inner_size == adjusted_size || !util::wm_name_is_one_of(&["Xfwm4"]) { // When this finally happens, the event will not be synthetic. shared_state_lock.dpi_adjusted = None; } else { // Unlock shared state to prevent deadlock in callback below drop(shared_state_lock); window.request_inner_size_physical(adjusted_size.0, adjusted_size.1); } } hittest }; // Reload hittest. if hittest.unwrap_or(false) { let _ = window.set_cursor_hittest(true); } if resized { callback(&self.target, Event::WindowEvent { window_id, event: WindowEvent::Resized(new_inner_size.into()), }); } } /// This is generally a reliable way to detect when the window manager's been /// replaced, though this event is only fired by reparenting window managers /// (which is almost all of them). Failing to correctly update WM info doesn't /// really have much impact, since on the WMs affected (xmonad, dwm, etc.) the only /// effect is that we waste some time trying to query unsupported properties. fn reparent_notify(&self, xev: &XReparentEvent) { let wt = Self::window_target(&self.target); wt.xconn.update_cached_wm_info(wt.root); self.with_window(xev.window as xproto::Window, |window| { window.invalidate_cached_frame_extents(); }); } fn map_notify(&self, xev: &XMapEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let window = xev.window as xproto::Window; let window_id = mkwid(window); // NOTE: Re-issue the focus state when mapping the window. // // The purpose of it is to deliver initial focused state of the newly created // window, given that we can't rely on `CreateNotify`, due to it being not // sent. let focus = self.with_window(window, |window| window.has_focus()).unwrap_or_default(); let event = Event::WindowEvent { window_id, event: WindowEvent::Focused(focus) }; callback(&self.target, event); } fn destroy_notify(&self, xev: &XDestroyWindowEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); let window = xev.window as xproto::Window; let window_id = mkwid(window); // In the event that the window's been destroyed without being dropped first, we // cleanup again here. wt.windows.borrow_mut().remove(&WindowId(window as _)); // Since all XIM stuff needs to happen from the same thread, we destroy the input // context here instead of when dropping the window. if let Some(ime) = wt.ime.as_ref() { ime.borrow_mut() .remove_context(window as XWindow) .expect("Failed to destroy input context"); } callback(&self.target, Event::WindowEvent { window_id, event: WindowEvent::Destroyed }); } fn property_notify(&mut self, xev: &XPropertyEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); let atoms = wt.x_connection().atoms(); let atom = xev.atom as xproto::Atom; if atom == xproto::Atom::from(xproto::AtomEnum::RESOURCE_MANAGER) || atom == atoms[_XSETTINGS_SETTINGS] { self.process_dpi_change(&mut callback); } } fn visibility_notify(&self, xev: &XVisibilityEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let xwindow = xev.window as xproto::Window; let event = Event::WindowEvent { window_id: mkwid(xwindow), event: WindowEvent::Occluded(xev.state == xlib::VisibilityFullyObscured), }; callback(&self.target, event); self.with_window(xwindow, |window| { window.visibility_notify(); }); } fn expose(&self, xev: &XExposeEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { // Multiple Expose events may be received for subareas of a window. // We issue `RedrawRequested` only for the last event of such a series. if xev.count == 0 { let window = xev.window as xproto::Window; let window_id = mkwid(window); let event = Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested }; callback(&self.target, event); } } fn xinput_key_input( &mut self, xev: &mut XKeyEvent, state: ElementState, mut callback: F, ) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let window = match self.active_window { Some(window) => window, None => return, }; let window_id = mkwid(window); let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); let keycode = xev.keycode as _; // Update state to track key repeats and determine whether this key was a repeat. // // Note, when a key is held before focusing on this window the first // (non-synthetic) event will not be flagged as a repeat (also note that the // synthetic press event that is generated before this when the window gains focus // will also not be flagged as a repeat). // // Only keys that can repeat should change the held_key_press state since a // continuously held repeatable key may continue repeating after the press of a // non-repeatable key. let key_repeats = self.xkb_context.keymap_mut().map(|k| k.key_repeats(keycode)).unwrap_or(false); let repeat = if key_repeats { let is_latest_held = self.held_key_press == Some(keycode); if state == ElementState::Pressed { self.held_key_press = Some(keycode); is_latest_held } else { // Check that the released key is the latest repeatable key that has been // pressed, since repeats will continue for the latest key press if a // different previously pressed key is released. if is_latest_held { self.held_key_press = None; } false } } else { false }; // NOTE: When the modifier was captured by the XFilterEvents the modifiers for the modifier // itself are out of sync due to XkbState being delivered before XKeyEvent, since it's // being replayed by the XIM, thus we should replay ourselves. let replay = if let Some(position) = self.xfiltered_modifiers.iter().rev().position(|&s| s == xev.keycode as u8) { // We don't have to replay modifiers pressed before the current event if some events // were not forwarded to us, since their state is irrelevant. self.xfiltered_modifiers.resize(self.xfiltered_modifiers.len() - 1 - position, 0); true } else { false }; // Always update the modifiers when we're not replaying. if !replay { self.update_mods_from_core_event(window_id, xev.state as u16, &mut callback); } if keycode != 0 && !self.is_composing { // Don't alter the modifiers state from replaying. if replay { self.send_synthic_modifier_from_core(window_id, xev.state as u16, &mut callback); } if let Some(mut key_processor) = self.xkb_context.key_context() { let event = key_processor.process_key_event(keycode, state, repeat); let event = Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { device_id, event, is_synthetic: false }, }; callback(&self.target, event); } // Restore the client's modifiers state after replay. if replay { self.send_modifiers(window_id, self.modifiers.get(), true, &mut callback); } return; } let wt = Self::window_target(&self.target); if let Some(ic) = wt.ime.as_ref().and_then(|ime| ime.borrow().get_context(window as XWindow)) { let written = wt.xconn.lookup_utf8(ic, xev); if !written.is_empty() { let event = Event::WindowEvent { window_id, event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), }; callback(&self.target, event); let event = Event::WindowEvent { window_id, event: WindowEvent::Ime(Ime::Commit(written)) }; self.is_composing = false; callback(&self.target, event); } } } fn send_synthic_modifier_from_core( &mut self, window_id: crate::window::WindowId, state: u16, mut callback: F, ) where F: FnMut(&RootAEL, Event), { let keymap = match self.xkb_context.keymap_mut() { Some(keymap) => keymap, None => return, }; let wt = Self::window_target(&self.target); let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection(); // Use synthetic state since we're replaying the modifier. The user modifier state // will be restored later. let mut xkb_state = match XkbState::new_x11(xcb, keymap) { Some(xkb_state) => xkb_state, None => return, }; let mask = self.xkb_mod_mask_from_core(state); xkb_state.update_modifiers(mask, 0, 0, 0, 0, Self::core_keyboard_group(state)); let mods: ModifiersState = xkb_state.modifiers().into(); let event = Event::WindowEvent { window_id, event: WindowEvent::ModifiersChanged(mods.into()) }; callback(&self.target, event); } fn xinput2_button_input( &self, event: &XIDeviceEvent, state: ElementState, mut callback: F, ) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); let window_id = mkwid(event.event as xproto::Window); let device_id = mkdid(event.deviceid as xinput::DeviceId); // Set the timestamp. wt.xconn.set_timestamp(event.time as xproto::Timestamp); // Deliver multi-touch events instead of emulated mouse events. if (event.flags & xinput2::XIPointerEmulated) != 0 { return; } let event = match event.detail as u32 { xlib::Button1 => { WindowEvent::MouseInput { device_id, state, button: MouseButton::Left } }, xlib::Button2 => { WindowEvent::MouseInput { device_id, state, button: MouseButton::Middle } }, xlib::Button3 => { WindowEvent::MouseInput { device_id, state, button: MouseButton::Right } }, // Suppress emulated scroll wheel clicks, since we handle the real motion events for // those. In practice, even clicky scroll wheels appear to be reported by // evdev (and XInput2 in turn) as axis motion, so we don't otherwise // special-case these button presses. 4..=7 => WindowEvent::MouseWheel { device_id, delta: match event.detail { 4 => MouseScrollDelta::LineDelta(0.0, 1.0), 5 => MouseScrollDelta::LineDelta(0.0, -1.0), 6 => MouseScrollDelta::LineDelta(1.0, 0.0), 7 => MouseScrollDelta::LineDelta(-1.0, 0.0), _ => unreachable!(), }, phase: TouchPhase::Moved, }, 8 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Back }, 9 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Forward }, x => WindowEvent::MouseInput { device_id, state, button: MouseButton::Other(x as u16) }, }; let event = Event::WindowEvent { window_id, event }; callback(&self.target, event); } fn xinput2_mouse_motion(&self, event: &XIDeviceEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(event.time as xproto::Timestamp); let device_id = mkdid(event.deviceid as xinput::DeviceId); let window = event.event as xproto::Window; let window_id = mkwid(window); let new_cursor_pos = (event.event_x, event.event_y); let cursor_moved = self.with_window(window, |window| { let mut shared_state_lock = window.shared_state_lock(); util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) }); if cursor_moved == Some(true) { let position = PhysicalPosition::new(event.event_x, event.event_y); let event = Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { device_id, position }, }; callback(&self.target, event); } else if cursor_moved.is_none() { return; } // More gymnastics, for self.devices let mask = unsafe { slice::from_raw_parts(event.valuators.mask, event.valuators.mask_len as usize) }; let mut devices = self.devices.borrow_mut(); let physical_device = match devices.get_mut(&DeviceId(event.sourceid as xinput::DeviceId)) { Some(device) => device, None => return, }; let mut events = Vec::new(); let mut value = event.valuators.values; for i in 0..event.valuators.mask_len * 8 { if !xinput2::XIMaskIsSet(mask, i) { continue; } let x = unsafe { *value }; let event = if let Some(&mut (_, ref mut info)) = physical_device.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == i as _) { let delta = (x - info.position) / info.increment; info.position = x; // X11 vertical scroll coordinates are opposite to winit's let delta = match info.orientation { ScrollOrientation::Horizontal => { MouseScrollDelta::LineDelta(-delta as f32, 0.0) }, ScrollOrientation::Vertical => MouseScrollDelta::LineDelta(0.0, -delta as f32), }; WindowEvent::MouseWheel { device_id, delta, phase: TouchPhase::Moved } } else { WindowEvent::AxisMotion { device_id, axis: i as u32, value: unsafe { *value } } }; events.push(Event::WindowEvent { window_id, event }); value = unsafe { value.offset(1) }; } for event in events { callback(&self.target, event); } } fn xinput2_mouse_enter(&self, event: &XIEnterEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(event.time as xproto::Timestamp); let window = event.event as xproto::Window; let window_id = mkwid(window); let device_id = mkdid(event.deviceid as xinput::DeviceId); if let Some(all_info) = DeviceInfo::get(&wt.xconn, super::ALL_DEVICES.into()) { let mut devices = self.devices.borrow_mut(); for device_info in all_info.iter() { // The second expression is need for resetting to work correctly on i3, and // presumably some other WMs. On those, `XI_Enter` doesn't include the physical // device ID, so both `sourceid` and `deviceid` are the virtual device. if device_info.deviceid == event.sourceid || device_info.attachment == event.sourceid { let device_id = DeviceId(device_info.deviceid as _); if let Some(device) = devices.get_mut(&device_id) { device.reset_scroll_position(device_info); } } } } if self.window_exists(window) { let position = PhysicalPosition::new(event.event_x, event.event_y); let event = Event::WindowEvent { window_id, event: WindowEvent::CursorEntered { device_id } }; callback(&self.target, event); let event = Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { device_id, position }, }; callback(&self.target, event); } } fn xinput2_mouse_left(&self, event: &XILeaveEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); let window = event.event as xproto::Window; // Set the timestamp. wt.xconn.set_timestamp(event.time as xproto::Timestamp); // Leave, FocusIn, and FocusOut can be received by a window that's already // been destroyed, which the user presumably doesn't want to deal with. if self.window_exists(window) { let event = Event::WindowEvent { window_id: mkwid(window), event: WindowEvent::CursorLeft { device_id: mkdid(event.deviceid as xinput::DeviceId), }, }; callback(&self.target, event); } } fn xinput2_focused(&mut self, xev: &XIFocusInEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); let window = xev.event as xproto::Window; // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); if let Some(ime) = wt.ime.as_ref() { ime.borrow_mut().focus(xev.event).expect("Failed to focus input context"); } if self.active_window == Some(window) { return; } self.active_window = Some(window); wt.update_listen_device_events(true); let window_id = mkwid(window); let position = PhysicalPosition::new(xev.event_x, xev.event_y); if let Some(window) = self.with_window(window, Arc::clone) { window.shared_state_lock().has_focus = true; } let event = Event::WindowEvent { window_id, event: WindowEvent::Focused(true) }; callback(&self.target, event); // Issue key press events for all pressed keys Self::handle_pressed_keys( &self.target, window_id, ElementState::Pressed, &mut self.xkb_context, &mut callback, ); self.update_mods_from_query(window_id, &mut callback); // The deviceid for this event is for a keyboard instead of a pointer, // so we have to do a little extra work. let pointer_id = self .devices .borrow() .get(&DeviceId(xev.deviceid as xinput::DeviceId)) .map(|device| device.attachment) .unwrap_or(2); let event = Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { device_id: mkdid(pointer_id as _), position }, }; callback(&self.target, event); } fn xinput2_unfocused(&mut self, xev: &XIFocusOutEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); let window = xev.event as xproto::Window; // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); if !self.window_exists(window) { return; } if let Some(ime) = wt.ime.as_ref() { ime.borrow_mut().unfocus(xev.event).expect("Failed to unfocus input context"); } if self.active_window.take() == Some(window) { let window_id = mkwid(window); wt.update_listen_device_events(false); // Clear the modifiers when unfocusing the window. if let Some(xkb_state) = self.xkb_context.state_mut() { xkb_state.update_modifiers(0, 0, 0, 0, 0, 0); let mods = xkb_state.modifiers(); self.send_modifiers(window_id, mods.into(), true, &mut callback); } // Issue key release events for all pressed keys Self::handle_pressed_keys( &self.target, window_id, ElementState::Released, &mut self.xkb_context, &mut callback, ); // Clear this so detecting key repeats is consistently handled when the // window regains focus. self.held_key_press = None; if let Some(window) = self.with_window(window, Arc::clone) { window.shared_state_lock().has_focus = false; } let event = Event::WindowEvent { window_id, event: WindowEvent::Focused(false) }; callback(&self.target, event) } } fn xinput2_touch( &mut self, xev: &XIDeviceEvent, phase: TouchPhase, mut callback: F, ) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let window = xev.event as xproto::Window; if self.window_exists(window) { let window_id = mkwid(window); let id = xev.detail as u64; let location = PhysicalPosition::new(xev.event_x, xev.event_y); // Mouse cursor position changes when touch events are received. // Only the first concurrently active touch ID moves the mouse cursor. if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase) { let event = Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { device_id: mkdid(util::VIRTUAL_CORE_POINTER), position: location.cast(), }, }; callback(&self.target, event); } let event = Event::WindowEvent { window_id, event: WindowEvent::Touch(Touch { device_id: mkdid(xev.deviceid as xinput::DeviceId), phase, location, force: None, // TODO id, }), }; callback(&self.target, event) } } fn xinput2_raw_button_input( &self, xev: &XIRawEvent, state: ElementState, mut callback: F, ) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); if xev.flags & xinput2::XIPointerEmulated == 0 { let event = Event::DeviceEvent { device_id: mkdid(xev.deviceid as xinput::DeviceId), event: DeviceEvent::Button { state, button: xev.detail as u32 }, }; callback(&self.target, event); } } fn xinput2_raw_mouse_motion(&self, xev: &XIRawEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let did = mkdid(xev.deviceid as xinput::DeviceId); let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; let mut value = xev.raw_values; let mut mouse_delta = util::Delta::default(); let mut scroll_delta = util::Delta::default(); for i in 0..xev.valuators.mask_len * 8 { if !xinput2::XIMaskIsSet(mask, i) { continue; } let x = unsafe { value.read_unaligned() }; // We assume that every XInput2 device with analog axes is a pointing device emitting // relative coordinates. match i { 0 => mouse_delta.set_x(x), 1 => mouse_delta.set_y(x), 2 => scroll_delta.set_x(x as f32), 3 => scroll_delta.set_y(x as f32), _ => {}, } let event = Event::DeviceEvent { device_id: did, event: DeviceEvent::Motion { axis: i as u32, value: x }, }; callback(&self.target, event); value = unsafe { value.offset(1) }; } if let Some(mouse_delta) = mouse_delta.consume() { let event = Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseMotion { delta: mouse_delta }, }; callback(&self.target, event); } if let Some(scroll_delta) = scroll_delta.consume() { let event = Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(scroll_delta.0, scroll_delta.1), }, }; callback(&self.target, event); } } fn xinput2_raw_key_input( &mut self, xev: &XIRawEvent, state: ElementState, mut callback: F, ) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let device_id = mkdid(xev.sourceid as xinput::DeviceId); let keycode = xev.detail as u32; if keycode < KEYCODE_OFFSET as u32 { return; } let physical_key = xkb::raw_keycode_to_physicalkey(keycode); callback(&self.target, Event::DeviceEvent { device_id, event: DeviceEvent::Key(RawKeyEvent { physical_key, state }), }); } fn xinput2_hierarchy_changed(&mut self, xev: &XIHierarchyEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let infos = unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) }; for info in infos { if 0 != info.flags & (xinput2::XISlaveAdded | xinput2::XIMasterAdded) { self.init_device(info.deviceid as xinput::DeviceId); callback(&self.target, Event::DeviceEvent { device_id: mkdid(info.deviceid as xinput::DeviceId), event: DeviceEvent::Added, }); } else if 0 != info.flags & (xinput2::XISlaveRemoved | xinput2::XIMasterRemoved) { callback(&self.target, Event::DeviceEvent { device_id: mkdid(info.deviceid as xinput::DeviceId), event: DeviceEvent::Removed, }); let mut devices = self.devices.borrow_mut(); devices.remove(&DeviceId(info.deviceid as xinput::DeviceId)); } } } fn xkb_event(&mut self, xev: &XkbAnyEvent, mut callback: F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); match xev.xkb_type { xlib::XkbNewKeyboardNotify => { let xev = unsafe { &*(xev as *const _ as *const xlib::XkbNewKeyboardNotifyEvent) }; // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let keycodes_changed_flag = 0x1; let geometry_changed_flag = 0x1 << 1; let keycodes_changed = util::has_flag(xev.changed, keycodes_changed_flag); let geometry_changed = util::has_flag(xev.changed, geometry_changed_flag); if xev.device == self.xkb_context.core_keyboard_id && (keycodes_changed || geometry_changed) { let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection(); self.xkb_context.set_keymap_from_x11(xcb); self.xmodmap.reload_from_x_connection(&wt.xconn); let window_id = match self.active_window.map(super::mkwid) { Some(window_id) => window_id, None => return, }; if let Some(state) = self.xkb_context.state_mut() { let mods = state.modifiers().into(); self.send_modifiers(window_id, mods, true, &mut callback); } } }, xlib::XkbMapNotify => { let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection(); self.xkb_context.set_keymap_from_x11(xcb); self.xmodmap.reload_from_x_connection(&wt.xconn); let window_id = match self.active_window.map(super::mkwid) { Some(window_id) => window_id, None => return, }; if let Some(state) = self.xkb_context.state_mut() { let mods = state.modifiers().into(); self.send_modifiers(window_id, mods, true, &mut callback); } }, xlib::XkbStateNotify => { let xev = unsafe { &*(xev as *const _ as *const xlib::XkbStateNotifyEvent) }; // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); if let Some(state) = self.xkb_context.state_mut() { state.update_modifiers( xev.base_mods, xev.latched_mods, xev.locked_mods, xev.base_group as u32, xev.latched_group as u32, xev.locked_group as u32, ); let window_id = match self.active_window.map(super::mkwid) { Some(window_id) => window_id, None => return, }; let mods = state.modifiers().into(); self.send_modifiers(window_id, mods, true, &mut callback); } }, _ => {}, } } pub fn update_mods_from_xinput2_event( &mut self, mods: &XIModifierState, group: &XIModifierState, force: bool, mut callback: F, ) where F: FnMut(&RootAEL, Event), { if let Some(state) = self.xkb_context.state_mut() { state.update_modifiers( mods.base as u32, mods.latched as u32, mods.locked as u32, group.base as u32, group.latched as u32, group.locked as u32, ); // NOTE: we use active window since generally sub windows don't have keyboard input, // and winit assumes that unfocused window doesn't have modifiers. let window_id = match self.active_window.map(super::mkwid) { Some(window_id) => window_id, None => return, }; let mods = state.modifiers(); self.send_modifiers(window_id, mods.into(), force, &mut callback); } } fn update_mods_from_query( &mut self, window_id: crate::window::WindowId, mut callback: F, ) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); let xkb_state = match self.xkb_context.state_mut() { Some(xkb_state) => xkb_state, None => return, }; unsafe { let mut state: XkbStateRec = std::mem::zeroed(); if (wt.xconn.xlib.XkbGetState)(wt.xconn.display, XkbId::USE_CORE_KBD.into(), &mut state) == xlib::True { xkb_state.update_modifiers( state.base_mods as u32, state.latched_mods as u32, state.locked_mods as u32, state.base_group as u32, state.latched_group as u32, state.locked_group as u32, ); } } let mods = xkb_state.modifiers(); self.send_modifiers(window_id, mods.into(), true, &mut callback) } pub fn update_mods_from_core_event( &mut self, window_id: crate::window::WindowId, state: u16, mut callback: F, ) where F: FnMut(&RootAEL, Event), { let xkb_mask = self.xkb_mod_mask_from_core(state); let xkb_state = match self.xkb_context.state_mut() { Some(xkb_state) => xkb_state, None => return, }; // NOTE: this is inspired by Qt impl. let mut depressed = xkb_state.depressed_modifiers() & xkb_mask; let latched = xkb_state.latched_modifiers() & xkb_mask; let locked = xkb_state.locked_modifiers() & xkb_mask; // Set modifiers in depressed if they don't appear in any of the final masks. depressed |= !(depressed | latched | locked) & xkb_mask; xkb_state.update_modifiers( depressed, latched, locked, 0, 0, Self::core_keyboard_group(state), ); let mods = xkb_state.modifiers(); self.send_modifiers(window_id, mods.into(), false, &mut callback); } // Bits 13 and 14 report the state keyboard group. pub fn core_keyboard_group(state: u16) -> u32 { ((state >> 13) & 3) as u32 } pub fn xkb_mod_mask_from_core(&mut self, state: u16) -> xkb_mod_mask_t { let mods_indices = match self.xkb_context.keymap_mut() { Some(keymap) => keymap.mods_indices(), None => return 0, }; // Build the XKB modifiers from the regular state. let mut depressed = 0u32; if let Some(shift) = mods_indices.shift.filter(|_| ModMask::SHIFT.intersects(state)) { depressed |= 1 << shift; } if let Some(caps) = mods_indices.caps.filter(|_| ModMask::LOCK.intersects(state)) { depressed |= 1 << caps; } if let Some(ctrl) = mods_indices.ctrl.filter(|_| ModMask::CONTROL.intersects(state)) { depressed |= 1 << ctrl; } if let Some(alt) = mods_indices.alt.filter(|_| ModMask::M1.intersects(state)) { depressed |= 1 << alt; } if let Some(num) = mods_indices.num.filter(|_| ModMask::M2.intersects(state)) { depressed |= 1 << num; } if let Some(mod3) = mods_indices.mod3.filter(|_| ModMask::M3.intersects(state)) { depressed |= 1 << mod3; } if let Some(logo) = mods_indices.logo.filter(|_| ModMask::M4.intersects(state)) { depressed |= 1 << logo; } if let Some(mod5) = mods_indices.mod5.filter(|_| ModMask::M5.intersects(state)) { depressed |= 1 << mod5; } depressed } /// Send modifiers for the active window. /// /// The event won't be sent when the `modifiers` match the previously `sent` modifiers value, /// unless `force` is passed. The `force` should be passed when the active window changes. fn send_modifiers)>( &self, window_id: crate::window::WindowId, modifiers: ModifiersState, force: bool, callback: &mut F, ) { // NOTE: Always update the modifiers to account for case when they've changed // and forced was `true`. if self.modifiers.replace(modifiers) != modifiers || force { let event = Event::WindowEvent { window_id, event: WindowEvent::ModifiersChanged(self.modifiers.get().into()), }; callback(&self.target, event); } } fn handle_pressed_keys( target: &RootAEL, window_id: crate::window::WindowId, state: ElementState, xkb_context: &mut Context, callback: &mut F, ) where F: FnMut(&RootAEL, Event), { let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); // Update modifiers state and emit key events based on which keys are currently pressed. let window_target = Self::window_target(target); let xcb = window_target.xconn.xcb_connection().get_raw_xcb_connection(); let keymap = match xkb_context.keymap_mut() { Some(keymap) => keymap, None => return, }; // Send the keys using the synthetic state to not alter the main state. let mut xkb_state = match XkbState::new_x11(xcb, keymap) { Some(xkb_state) => xkb_state, None => return, }; let mut key_processor = match xkb_context.key_context_with_state(&mut xkb_state) { Some(key_processor) => key_processor, None => return, }; for keycode in window_target.xconn.query_keymap().into_iter().filter(|k| *k >= KEYCODE_OFFSET) { let event = key_processor.process_key_event(keycode as u32, state, false); let event = Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { device_id, event, is_synthetic: true }, }; callback(target, event); } } fn process_dpi_change(&self, callback: &mut F) where F: FnMut(&RootAEL, Event), { let wt = Self::window_target(&self.target); wt.xconn.reload_database().expect("failed to reload Xft database"); // In the future, it would be quite easy to emit monitor hotplug events. let prev_list = { let prev_list = wt.xconn.invalidate_cached_monitor_list(); match prev_list { Some(prev_list) => prev_list, None => return, } }; let new_list = wt.xconn.available_monitors().expect("Failed to get monitor list"); for new_monitor in new_list { // Previous list may be empty, in case of disconnecting and // reconnecting the only one monitor. We still need to emit events in // this case. let maybe_prev_scale_factor = prev_list .iter() .find(|prev_monitor| prev_monitor.name == new_monitor.name) .map(|prev_monitor| prev_monitor.scale_factor); if Some(new_monitor.scale_factor) != maybe_prev_scale_factor { for window in wt.windows.borrow().iter().filter_map(|(_, w)| w.upgrade()) { window.refresh_dpi_for_monitor(&new_monitor, maybe_prev_scale_factor, |event| { callback(&self.target, event); }) } } } } fn window_exists(&self, window_id: xproto::Window) -> bool { self.with_window(window_id, |_| ()).is_some() } } fn is_first_touch(first: &mut Option, num: &mut u32, id: u64, phase: TouchPhase) -> bool { match phase { TouchPhase::Started => { if *num == 0 { *first = Some(id); } *num += 1; }, TouchPhase::Cancelled | TouchPhase::Ended => { if *first == Some(id) { *first = None; } *num = num.saturating_sub(1); }, _ => (), } *first == Some(id) } winit-0.30.9/src/platform_impl/linux/x11/ffi.rs000064400000000000000000000002201046102023000173570ustar 00000000000000pub use x11_dl::error::OpenError; pub use x11_dl::xcursor::*; pub use x11_dl::xinput2::*; pub use x11_dl::xlib::*; pub use x11_dl::xlib_xcb::*; winit-0.30.9/src/platform_impl/linux/x11/ime/callbacks.rs000064400000000000000000000160201046102023000213110ustar 00000000000000use std::collections::HashMap; use std::os::raw::c_char; use std::ptr; use std::sync::Arc; use super::{ffi, XConnection, XError}; use super::context::{ImeContext, ImeContextCreationError}; use super::inner::{close_im, ImeInner}; use super::input_method::PotentialInputMethods; pub(crate) unsafe fn xim_set_callback( xconn: &Arc, xim: ffi::XIM, field: *const c_char, callback: *mut ffi::XIMCallback, ) -> Result<(), XError> { // It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize // access that isn't type-checked. unsafe { (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()) }; xconn.check_errors() } // Set a callback for when an input method matching the current locale modifiers becomes // available. Note that this has nothing to do with what input methods are open or able to be // opened, and simply uses the modifiers that are set when the callback is set. // * This is called per locale modifier, not per input method opened with that locale modifier. // * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt input // contexts would always silently fail to use the input method. pub(crate) unsafe fn set_instantiate_callback( xconn: &Arc, client_data: ffi::XPointer, ) -> Result<(), XError> { unsafe { (xconn.xlib.XRegisterIMInstantiateCallback)( xconn.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), Some(xim_instantiate_callback), client_data, ) }; xconn.check_errors() } pub(crate) unsafe fn unset_instantiate_callback( xconn: &Arc, client_data: ffi::XPointer, ) -> Result<(), XError> { unsafe { (xconn.xlib.XUnregisterIMInstantiateCallback)( xconn.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), Some(xim_instantiate_callback), client_data, ) }; xconn.check_errors() } pub(crate) unsafe fn set_destroy_callback( xconn: &Arc, im: ffi::XIM, inner: &ImeInner, ) -> Result<(), XError> { unsafe { xim_set_callback( xconn, im, ffi::XNDestroyCallback_0.as_ptr() as *const _, &inner.destroy_callback as *const _ as *mut _, ) } } #[derive(Debug)] #[allow(clippy::enum_variant_names)] enum ReplaceImError { // Boxed to prevent large error type MethodOpenFailed(#[allow(dead_code)] Box), ContextCreationFailed(#[allow(dead_code)] ImeContextCreationError), SetDestroyCallbackFailed(#[allow(dead_code)] XError), } // Attempt to replace current IM (which may or may not be presently valid) with a new one. This // includes replacing all existing input contexts and free'ing resources as necessary. This only // modifies existing state if all operations succeed. unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> { let xconn = unsafe { &(*inner).xconn }; let (new_im, is_fallback) = { let new_im = unsafe { (*inner).potential_input_methods.open_im(xconn, None) }; let is_fallback = new_im.is_fallback(); ( new_im.ok().ok_or_else(|| { ReplaceImError::MethodOpenFailed(Box::new(unsafe { (*inner).potential_input_methods.clone() })) })?, is_fallback, ) }; // It's important to always set a destroy callback, since there's otherwise potential for us // to try to use or free a resource that's already been destroyed on the server. { let result = unsafe { set_destroy_callback(xconn, new_im.im, &*inner) }; if result.is_err() { let _ = unsafe { close_im(xconn, new_im.im) }; } result } .map_err(ReplaceImError::SetDestroyCallbackFailed)?; let mut new_contexts = HashMap::new(); for (window, old_context) in unsafe { (*inner).contexts.iter() } { let spot = old_context.as_ref().map(|old_context| old_context.ic_spot); // Check if the IME was allowed on that context. let is_allowed = old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default(); let new_context = { let result = unsafe { ImeContext::new( xconn, &new_im, *window, spot, (*inner).event_sender.clone(), is_allowed, ) }; if result.is_err() { let _ = unsafe { close_im(xconn, new_im.im) }; } result.map_err(ReplaceImError::ContextCreationFailed)? }; new_contexts.insert(*window, Some(new_context)); } // If we've made it this far, everything succeeded. unsafe { let _ = (*inner).destroy_all_contexts_if_necessary(); let _ = (*inner).close_im_if_necessary(); (*inner).im = Some(new_im); (*inner).contexts = new_contexts; (*inner).is_destroyed = false; (*inner).is_fallback = is_fallback; } Ok(()) } pub unsafe extern "C" fn xim_instantiate_callback( _display: *mut ffi::Display, client_data: ffi::XPointer, // This field is unsupplied. _call_data: ffi::XPointer, ) { let inner: *mut ImeInner = client_data as _; if !inner.is_null() { let xconn = unsafe { &(*inner).xconn }; match unsafe { replace_im(inner) } { Ok(()) => unsafe { let _ = unset_instantiate_callback(xconn, client_data); (*inner).is_fallback = false; }, Err(err) => unsafe { if (*inner).is_destroyed { // We have no usable input methods! panic!("Failed to reopen input method: {err:?}"); } }, } } } // This callback is triggered when the input method is closed on the server end. When this // happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been // free'd (attempting to do so causes our connection to freeze). pub unsafe extern "C" fn xim_destroy_callback( _xim: ffi::XIM, client_data: ffi::XPointer, // This field is unsupplied. _call_data: ffi::XPointer, ) { let inner: *mut ImeInner = client_data as _; if !inner.is_null() { unsafe { (*inner).is_destroyed = true }; let xconn = unsafe { &(*inner).xconn }; if unsafe { !(*inner).is_fallback } { let _ = unsafe { set_instantiate_callback(xconn, client_data) }; // Attempt to open fallback input method. match unsafe { replace_im(inner) } { Ok(()) => unsafe { (*inner).is_fallback = true }, Err(err) => { // We have no usable input methods! panic!("Failed to open fallback input method: {err:?}"); }, } } } } winit-0.30.9/src/platform_impl/linux/x11/ime/context.rs000064400000000000000000000302651046102023000210650ustar 00000000000000use std::ffi::CStr; use std::os::raw::c_short; use std::sync::Arc; use std::{mem, ptr}; use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct}; use super::{ffi, util, XConnection, XError}; use crate::platform_impl::platform::x11::ime::input_method::{InputMethod, Style, XIMStyle}; use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender}; /// IME creation error. #[derive(Debug)] pub enum ImeContextCreationError { /// Got the error from Xlib. XError(XError), /// Got null pointer from Xlib but without exact reason. Null, } /// The callback used by XIM preedit functions. type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer); /// Wrapper for creating XIM callbacks. #[inline] fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback { XIMCallback { client_data, callback: Some(callback) } } /// The server started preedit. extern "C" fn preedit_start_callback( _xim: ffi::XIM, client_data: ffi::XPointer, _call_data: ffi::XPointer, ) -> i32 { let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; client_data.text.clear(); client_data.cursor_pos = 0; client_data .event_sender .send((client_data.window, ImeEvent::Start)) .expect("failed to send preedit start event"); -1 } /// Done callback is used when the preedit should be hidden. extern "C" fn preedit_done_callback( _xim: ffi::XIM, client_data: ffi::XPointer, _call_data: ffi::XPointer, ) { let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; // Drop text buffer and reset cursor position on done. client_data.text = Vec::new(); client_data.cursor_pos = 0; client_data .event_sender .send((client_data.window, ImeEvent::End)) .expect("failed to send preedit end event"); } fn calc_byte_position(text: &[char], pos: usize) -> usize { text.iter().take(pos).fold(0, |byte_pos, text| byte_pos + text.len_utf8()) } /// Preedit text information to be drawn inline by the client. extern "C" fn preedit_draw_callback( _xim: ffi::XIM, client_data: ffi::XPointer, call_data: ffi::XPointer, ) { let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) }; client_data.cursor_pos = call_data.caret as usize; let chg_range = call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize; if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() { tracing::warn!( "invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}", client_data.text.len(), call_data.chg_first, call_data.chg_length ); return; } // NULL indicate text deletion let mut new_chars = if call_data.text.is_null() { Vec::new() } else { let xim_text = unsafe { &mut *(call_data.text) }; if xim_text.encoding_is_wchar > 0 { return; } let new_text = unsafe { xim_text.string.multi_byte }; if new_text.is_null() { return; } let new_text = unsafe { CStr::from_ptr(new_text) }; String::from(new_text.to_str().expect("Invalid UTF-8 String from IME")).chars().collect() }; let mut old_text_tail = client_data.text.split_off(chg_range.end); client_data.text.truncate(chg_range.start); client_data.text.append(&mut new_chars); client_data.text.append(&mut old_text_tail); let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos); client_data .event_sender .send(( client_data.window, ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos), )) .expect("failed to send preedit update event"); } /// Handling of cursor movements in preedit text. extern "C" fn preedit_caret_callback( _xim: ffi::XIM, client_data: ffi::XPointer, call_data: ffi::XPointer, ) { let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) }; if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition { client_data.cursor_pos = call_data.position as usize; let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos); client_data .event_sender .send(( client_data.window, ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos), )) .expect("failed to send preedit update event"); } } /// Struct to simplify callback creation and latter passing into Xlib XIM. struct PreeditCallbacks { start_callback: ffi::XIMCallback, done_callback: ffi::XIMCallback, draw_callback: ffi::XIMCallback, caret_callback: ffi::XIMCallback, } impl PreeditCallbacks { pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks { let start_callback = create_xim_callback(client_data, unsafe { mem::transmute::( preedit_start_callback as usize, ) }); let done_callback = create_xim_callback(client_data, preedit_done_callback); let caret_callback = create_xim_callback(client_data, preedit_caret_callback); let draw_callback = create_xim_callback(client_data, preedit_draw_callback); PreeditCallbacks { start_callback, done_callback, caret_callback, draw_callback } } } struct ImeContextClientData { window: ffi::Window, event_sender: ImeEventSender, text: Vec, cursor_pos: usize, } // XXX: this struct doesn't destroy its XIC resource when dropped. // This is intentional, as it doesn't have enough information to know whether or not the context // still exists on the server. Since `ImeInner` has that awareness, destruction must be handled // through `ImeInner`. pub struct ImeContext { pub(crate) ic: ffi::XIC, pub(crate) ic_spot: ffi::XPoint, pub(crate) allowed: bool, // Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free // from there we keep the pointer to automatically deallocate it. _client_data: Box, } impl ImeContext { pub(crate) unsafe fn new( xconn: &Arc, im: &InputMethod, window: ffi::Window, ic_spot: Option, event_sender: ImeEventSender, allowed: bool, ) -> Result { let client_data = Box::into_raw(Box::new(ImeContextClientData { window, event_sender, text: Vec::new(), cursor_pos: 0, })); let style = if allowed { im.preedit_style } else { im.none_style }; let ic = match style as _ { Style::Preedit(style) => unsafe { ImeContext::create_preedit_ic( xconn, im.im, style, window, client_data as ffi::XPointer, ) }, Style::Nothing(style) => unsafe { ImeContext::create_nothing_ic(xconn, im.im, style, window) }, Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im.im, style, window) }, } .ok_or(ImeContextCreationError::Null)?; xconn.check_errors().map_err(ImeContextCreationError::XError)?; let mut context = ImeContext { ic, ic_spot: ffi::XPoint { x: 0, y: 0 }, allowed, _client_data: unsafe { Box::from_raw(client_data) }, }; // Set the spot location, if it's present. if let Some(ic_spot) = ic_spot { context.set_spot(xconn, ic_spot.x, ic_spot.y) } Ok(context) } unsafe fn create_none_ic( xconn: &Arc, im: ffi::XIM, style: XIMStyle, window: ffi::Window, ) -> Option { let ic = unsafe { (xconn.xlib.XCreateIC)( im, ffi::XNInputStyle_0.as_ptr() as *const _, style, ffi::XNClientWindow_0.as_ptr() as *const _, window, ptr::null_mut::<()>(), ) }; (!ic.is_null()).then_some(ic) } unsafe fn create_preedit_ic( xconn: &Arc, im: ffi::XIM, style: XIMStyle, window: ffi::Window, client_data: ffi::XPointer, ) -> Option { let preedit_callbacks = PreeditCallbacks::new(client_data); let preedit_attr = util::memory::XSmartPointer::new(xconn, unsafe { (xconn.xlib.XVaCreateNestedList)( 0, ffi::XNPreeditStartCallback_0.as_ptr() as *const _, &(preedit_callbacks.start_callback) as *const _, ffi::XNPreeditDoneCallback_0.as_ptr() as *const _, &(preedit_callbacks.done_callback) as *const _, ffi::XNPreeditCaretCallback_0.as_ptr() as *const _, &(preedit_callbacks.caret_callback) as *const _, ffi::XNPreeditDrawCallback_0.as_ptr() as *const _, &(preedit_callbacks.draw_callback) as *const _, ptr::null_mut::<()>(), ) }) .expect("XVaCreateNestedList returned NULL"); let ic = unsafe { (xconn.xlib.XCreateIC)( im, ffi::XNInputStyle_0.as_ptr() as *const _, style, ffi::XNClientWindow_0.as_ptr() as *const _, window, ffi::XNPreeditAttributes_0.as_ptr() as *const _, preedit_attr.ptr, ptr::null_mut::<()>(), ) }; (!ic.is_null()).then_some(ic) } unsafe fn create_nothing_ic( xconn: &Arc, im: ffi::XIM, style: XIMStyle, window: ffi::Window, ) -> Option { let ic = unsafe { (xconn.xlib.XCreateIC)( im, ffi::XNInputStyle_0.as_ptr() as *const _, style, ffi::XNClientWindow_0.as_ptr() as *const _, window, ptr::null_mut::<()>(), ) }; (!ic.is_null()).then_some(ic) } pub(crate) fn focus(&self, xconn: &Arc) -> Result<(), XError> { unsafe { (xconn.xlib.XSetICFocus)(self.ic); } xconn.check_errors() } pub(crate) fn unfocus(&self, xconn: &Arc) -> Result<(), XError> { unsafe { (xconn.xlib.XUnsetICFocus)(self.ic); } xconn.check_errors() } pub fn is_allowed(&self) -> bool { self.allowed } // Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks // are being used. Certain IMEs do show selection window, but it's placed in bottom left of the // window and couldn't be changed. // // For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580. pub(crate) fn set_spot(&mut self, xconn: &Arc, x: c_short, y: c_short) { if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y { return; } self.ic_spot = ffi::XPoint { x, y }; unsafe { let preedit_attr = util::memory::XSmartPointer::new( xconn, (xconn.xlib.XVaCreateNestedList)( 0, ffi::XNSpotLocation_0.as_ptr(), &self.ic_spot, ptr::null_mut::<()>(), ), ) .expect("XVaCreateNestedList returned NULL"); (xconn.xlib.XSetICValues)( self.ic, ffi::XNPreeditAttributes_0.as_ptr() as *const _, preedit_attr.ptr, ptr::null_mut::<()>(), ); } } } winit-0.30.9/src/platform_impl/linux/x11/ime/inner.rs000064400000000000000000000045141046102023000205120ustar 00000000000000use std::collections::HashMap; use std::mem; use std::sync::Arc; use super::{ffi, XConnection, XError}; use super::context::ImeContext; use super::input_method::{InputMethod, PotentialInputMethods}; use crate::platform_impl::platform::x11::ime::ImeEventSender; pub(crate) unsafe fn close_im(xconn: &Arc, im: ffi::XIM) -> Result<(), XError> { unsafe { (xconn.xlib.XCloseIM)(im) }; xconn.check_errors() } pub(crate) unsafe fn destroy_ic(xconn: &Arc, ic: ffi::XIC) -> Result<(), XError> { unsafe { (xconn.xlib.XDestroyIC)(ic) }; xconn.check_errors() } pub(crate) struct ImeInner { pub xconn: Arc, pub im: Option, pub potential_input_methods: PotentialInputMethods, pub contexts: HashMap>, // WARNING: this is initially zeroed! pub destroy_callback: ffi::XIMCallback, pub event_sender: ImeEventSender, // Indicates whether or not the input method was destroyed on the server end // (i.e. if ibus/fcitx/etc. was terminated/restarted) pub is_destroyed: bool, pub is_fallback: bool, } impl ImeInner { pub(crate) fn new( xconn: Arc, potential_input_methods: PotentialInputMethods, event_sender: ImeEventSender, ) -> Self { ImeInner { xconn, im: None, potential_input_methods, contexts: HashMap::new(), destroy_callback: unsafe { mem::zeroed() }, event_sender, is_destroyed: false, is_fallback: false, } } pub unsafe fn close_im_if_necessary(&self) -> Result { if !self.is_destroyed && self.im.is_some() { unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true) } else { Ok(false) } } pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result { if !self.is_destroyed { unsafe { destroy_ic(&self.xconn, ic) }.map(|_| true) } else { Ok(false) } } pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result { for context in self.contexts.values().flatten() { unsafe { self.destroy_ic_if_necessary(context.ic)? }; } Ok(!self.is_destroyed) } } winit-0.30.9/src/platform_impl/linux/x11/ime/input_method.rs000064400000000000000000000275011046102023000220770ustar 00000000000000use std::ffi::{CStr, CString, IntoStringError}; use std::os::raw::{c_char, c_ulong, c_ushort}; use std::sync::{Arc, Mutex}; use std::{env, fmt, ptr}; use super::super::atoms::*; use super::{ffi, util, XConnection, XError}; use x11rb::protocol::xproto; static GLOBAL_LOCK: Mutex<()> = Mutex::new(()); unsafe fn open_im(xconn: &Arc, locale_modifiers: &CStr) -> Option { let _lock = GLOBAL_LOCK.lock(); // XSetLocaleModifiers returns... // * The current locale modifiers if it's given a NULL pointer. // * The new locale modifiers if we succeeded in setting them. // * NULL if the locale modifiers string is malformed or if the current locale is not supported // by Xlib. unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) }; let im = unsafe { (xconn.xlib.XOpenIM)(xconn.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()) }; if im.is_null() { None } else { Some(im) } } #[derive(Debug)] pub struct InputMethod { pub im: ffi::XIM, pub preedit_style: Style, pub none_style: Style, _name: String, } impl InputMethod { fn new(xconn: &Arc, im: ffi::XIM, name: String) -> Option { let mut styles: *mut XIMStyles = std::ptr::null_mut(); // Query the styles supported by the XIM. unsafe { if !(xconn.xlib.XGetIMValues)( im, ffi::XNQueryInputStyle_0.as_ptr() as *const _, (&mut styles) as *mut _, std::ptr::null_mut::<()>(), ) .is_null() { return None; } } let mut preedit_style = None; let mut none_style = None; unsafe { std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _) .iter() .for_each(|style| match *style { XIM_PREEDIT_STYLE => { preedit_style = Some(Style::Preedit(*style)); }, XIM_NOTHING_STYLE if preedit_style.is_none() => { preedit_style = Some(Style::Nothing(*style)) }, XIM_NONE_STYLE => none_style = Some(Style::None(*style)), _ => (), }); (xconn.xlib.XFree)(styles.cast()); }; if preedit_style.is_none() && none_style.is_none() { return None; } let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap()); let none_style = none_style.unwrap_or(preedit_style); Some(InputMethod { im, _name: name, preedit_style, none_style }) } } const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle; const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle; const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle; /// Style of the IME context. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Style { /// Preedit callbacks. Preedit(XIMStyle), /// Nothing. Nothing(XIMStyle), /// No IME. None(XIMStyle), } impl Default for Style { fn default() -> Self { Style::None(XIM_NONE_STYLE) } } #[repr(C)] #[derive(Debug)] struct XIMStyles { count_styles: c_ushort, supported_styles: *const XIMStyle, } pub(crate) type XIMStyle = c_ulong; #[derive(Debug)] pub enum InputMethodResult { /// Input method used locale modifier from `XMODIFIERS` environment variable. XModifiers(InputMethod), /// Input method used internal fallback locale modifier. Fallback(InputMethod), /// Input method could not be opened using any locale modifier tried. Failure, } impl InputMethodResult { pub fn is_fallback(&self) -> bool { matches!(self, InputMethodResult::Fallback(_)) } pub fn ok(self) -> Option { use self::InputMethodResult::*; match self { XModifiers(im) | Fallback(im) => Some(im), Failure => None, } } } #[derive(Debug, Clone)] enum GetXimServersError { XError(#[allow(dead_code)] XError), GetPropertyError(#[allow(dead_code)] util::GetPropertyError), InvalidUtf8(#[allow(dead_code)] IntoStringError), } impl From for GetXimServersError { fn from(error: util::GetPropertyError) -> Self { GetXimServersError::GetPropertyError(error) } } // The root window has a property named XIM_SERVERS, which contains a list of atoms representing // the available XIM servers. For instance, if you're using ibus, it would contain an atom named // "@server=ibus". It's possible for this property to contain multiple atoms, though presumably // rare. Note that we replace "@server=" with "@im=" in order to match the format of locale // modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set // XMODIFIERS to `@server=ibus`?!?" unsafe fn get_xim_servers(xconn: &Arc) -> Result, GetXimServersError> { let atoms = xconn.atoms(); let servers_atom = atoms[XIM_SERVERS]; let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) }; let mut atoms: Vec = xconn .get_property::( root as xproto::Window, servers_atom, xproto::Atom::from(xproto::AtomEnum::ATOM), ) .map_err(GetXimServersError::GetPropertyError)? .into_iter() .map(ffi::Atom::from) .collect::>(); let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len()); unsafe { (xconn.xlib.XGetAtomNames)( xconn.display, atoms.as_mut_ptr(), atoms.len() as _, names.as_mut_ptr() as _, ) }; unsafe { names.set_len(atoms.len()) }; let mut formatted_names = Vec::with_capacity(names.len()); for name in names { let string = unsafe { CStr::from_ptr(name) } .to_owned() .into_string() .map_err(GetXimServersError::InvalidUtf8)?; unsafe { (xconn.xlib.XFree)(name as _) }; formatted_names.push(string.replace("@server=", "@im=")); } xconn.check_errors().map_err(GetXimServersError::XError)?; Ok(formatted_names) } #[derive(Clone)] struct InputMethodName { c_string: CString, string: String, } impl InputMethodName { pub fn from_string(string: String) -> Self { let c_string = CString::new(string.clone()) .expect("String used to construct CString contained null byte"); InputMethodName { c_string, string } } pub fn from_str(string: &str) -> Self { let c_string = CString::new(string).expect("String used to construct CString contained null byte"); InputMethodName { c_string, string: string.to_owned() } } } impl fmt::Debug for InputMethodName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.string.fmt(f) } } #[derive(Debug, Clone)] struct PotentialInputMethod { name: InputMethodName, successful: Option, } impl PotentialInputMethod { pub fn from_string(string: String) -> Self { PotentialInputMethod { name: InputMethodName::from_string(string), successful: None } } pub fn from_str(string: &str) -> Self { PotentialInputMethod { name: InputMethodName::from_str(string), successful: None } } pub fn reset(&mut self) { self.successful = None; } pub fn open_im(&mut self, xconn: &Arc) -> Option { let im = unsafe { open_im(xconn, &self.name.c_string) }; self.successful = Some(im.is_some()); im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone())) } } // By logging this struct, you get a sequential listing of every locale modifier tried, where it // came from, and if it succeeded. #[derive(Debug, Clone)] pub(crate) struct PotentialInputMethods { // On correctly configured systems, the XMODIFIERS environment variable tells us everything we // need to know. xmodifiers: Option, // We have some standard options at our disposal that should ostensibly always work. For users // who only need compose sequences, this ensures that the program launches without a hitch // For users who need more sophisticated IME features, this is more or less a silent failure. // Logging features should be added in the future to allow both audiences to be effectively // served. fallbacks: [PotentialInputMethod; 2], // For diagnostic purposes, we include the list of XIM servers that the server reports as // being available. _xim_servers: Result, GetXimServersError>, } impl PotentialInputMethods { pub fn new(xconn: &Arc) -> Self { let xmodifiers = env::var("XMODIFIERS").ok().map(PotentialInputMethod::from_string); PotentialInputMethods { // Since passing "" to XSetLocaleModifiers results in it defaulting to the value of // XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply // running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is // defined in the profile (or parent environment) then that parent XMODIFIERS is used. // If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then // XSetLocaleModifiers uses the default local input method. Note that defining // XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in // that case, we get `None` and end up skipping ahead to the next method. xmodifiers, fallbacks: [ // This is a standard input method that supports compose sequences, which should // always be available. `@im=none` appears to mean the same thing. PotentialInputMethod::from_str("@im=local"), // This explicitly specifies to use the implementation-dependent default, though // that seems to be equivalent to just using the local input method. PotentialInputMethod::from_str("@im="), ], // The XIM_SERVERS property can have surprising values. For instance, when I exited // ibus to run fcitx, it retained the value denoting ibus. Even more surprising is // that the fcitx input method could only be successfully opened using "@im=ibus". // Presumably due to this quirk, it's actually possible to alternate between ibus and // fcitx in a running application. _xim_servers: unsafe { get_xim_servers(xconn) }, } } // This resets the `successful` field of every potential input method, ensuring we have // accurate information when this struct is re-used by the destruction/instantiation callbacks. fn reset(&mut self) { if let Some(ref mut input_method) = self.xmodifiers { input_method.reset(); } for input_method in &mut self.fallbacks { input_method.reset(); } } pub fn open_im( &mut self, xconn: &Arc, callback: Option<&dyn Fn()>, ) -> InputMethodResult { use self::InputMethodResult::*; self.reset(); if let Some(ref mut input_method) = self.xmodifiers { let im = input_method.open_im(xconn); if let Some(im) = im { return XModifiers(im); } else if let Some(ref callback) = callback { callback(); } } for input_method in &mut self.fallbacks { let im = input_method.open_im(xconn); if let Some(im) = im { return Fallback(im); } } Failure } } winit-0.30.9/src/platform_impl/linux/x11/ime/mod.rs000064400000000000000000000161231046102023000201550ustar 00000000000000// Important: all XIM calls need to happen from the same thread! mod callbacks; mod context; mod inner; mod input_method; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use self::callbacks::*; use self::context::ImeContext; pub use self::context::ImeContextCreationError; use self::inner::{close_im, ImeInner}; use self::input_method::PotentialInputMethods; use super::{ffi, util, XConnection, XError}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ImeEvent { Enabled, Start, Update(String, usize), End, Disabled, } pub type ImeReceiver = Receiver; pub type ImeSender = Sender; pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>; pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>; /// Request to control XIM handler from the window. pub enum ImeRequest { /// Set IME spot position for given `window_id`. Position(ffi::Window, i16, i16), /// Allow IME input for the given `window_id`. Allow(ffi::Window, bool), } #[derive(Debug)] pub(crate) enum ImeCreationError { // Boxed to prevent large error type OpenFailure(Box), SetDestroyCallbackFailed(#[allow(dead_code)] XError), } pub(crate) struct Ime { xconn: Arc, // The actual meat of this struct is boxed away, since it needs to have a fixed location in // memory so we can pass a pointer to it around. inner: Box, } impl Ime { pub fn new( xconn: Arc, event_sender: ImeEventSender, ) -> Result { let potential_input_methods = PotentialInputMethods::new(&xconn); let (mut inner, client_data) = { let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender)); let inner_ptr = Box::into_raw(inner); let client_data = inner_ptr as _; let destroy_callback = ffi::XIMCallback { client_data, callback: Some(xim_destroy_callback) }; inner = unsafe { Box::from_raw(inner_ptr) }; inner.destroy_callback = destroy_callback; (inner, client_data) }; let xconn = Arc::clone(&inner.xconn); let input_method = inner.potential_input_methods.open_im( &xconn, Some(&|| { let _ = unsafe { set_instantiate_callback(&xconn, client_data) }; }), ); let is_fallback = input_method.is_fallback(); if let Some(input_method) = input_method.ok() { inner.is_fallback = is_fallback; unsafe { let result = set_destroy_callback(&xconn, input_method.im, &inner) .map_err(ImeCreationError::SetDestroyCallbackFailed); if result.is_err() { let _ = close_im(&xconn, input_method.im); } result?; } inner.im = Some(input_method); Ok(Ime { xconn, inner }) } else { Err(ImeCreationError::OpenFailure(Box::new(inner.potential_input_methods))) } } pub fn is_destroyed(&self) -> bool { self.inner.is_destroyed } // This pattern is used for various methods here: // Ok(_) indicates that nothing went wrong internally // Ok(true) indicates that the action was actually performed // Ok(false) indicates that the action is not presently applicable pub fn create_context( &mut self, window: ffi::Window, with_ime: bool, ) -> Result { let context = if self.is_destroyed() { // Create empty entry in map, so that when IME is rebuilt, this window has a context. None } else { let im = self.inner.im.as_ref().unwrap(); let context = unsafe { ImeContext::new( &self.inner.xconn, im, window, None, self.inner.event_sender.clone(), with_ime, )? }; let event = if context.is_allowed() { ImeEvent::Enabled } else { ImeEvent::Disabled }; self.inner.event_sender.send((window, event)).expect("Failed to send enabled event"); Some(context) }; self.inner.contexts.insert(window, context); Ok(!self.is_destroyed()) } pub fn get_context(&self, window: ffi::Window) -> Option { if self.is_destroyed() { return None; } if let Some(Some(context)) = self.inner.contexts.get(&window) { Some(context.ic) } else { None } } pub fn remove_context(&mut self, window: ffi::Window) -> Result { if let Some(Some(context)) = self.inner.contexts.remove(&window) { unsafe { self.inner.destroy_ic_if_necessary(context.ic)?; } Ok(true) } else { Ok(false) } } pub fn focus(&mut self, window: ffi::Window) -> Result { if self.is_destroyed() { return Ok(false); } if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { context.focus(&self.xconn).map(|_| true) } else { Ok(false) } } pub fn unfocus(&mut self, window: ffi::Window) -> Result { if self.is_destroyed() { return Ok(false); } if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { context.unfocus(&self.xconn).map(|_| true) } else { Ok(false) } } pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) { if self.is_destroyed() { return; } if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { context.set_spot(&self.xconn, x as _, y as _); } } pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) { if self.is_destroyed() { return; } if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { if allowed == context.is_allowed() { return; } } // Remove context for that window. let _ = self.remove_context(window); // Create new context supporting IME input. let _ = self.create_context(window, allowed); } pub fn is_ime_allowed(&self, window: ffi::Window) -> bool { if self.is_destroyed() { false } else if let Some(Some(context)) = self.inner.contexts.get(&window) { context.is_allowed() } else { false } } } impl Drop for Ime { fn drop(&mut self) { unsafe { let _ = self.inner.destroy_all_contexts_if_necessary(); let _ = self.inner.close_im_if_necessary(); } } } winit-0.30.9/src/platform_impl/linux/x11/mod.rs000064400000000000000000001037241046102023000174070ustar 00000000000000use std::cell::{Cell, RefCell}; use std::collections::{HashMap, HashSet, VecDeque}; use std::ffi::CStr; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::ops::Deref; use std::os::raw::*; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::sync::mpsc::{self, Receiver, Sender, TryRecvError}; use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; use std::{fmt, ptr, slice, str}; use calloop::generic::Generic; use calloop::ping::Ping; use calloop::{EventLoop as Loop, Readiness}; use libc::{setlocale, LC_CTYPE}; use tracing::warn; use x11rb::connection::RequestConnection; use x11rb::errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError}; use x11rb::protocol::xinput::{self, ConnectionExt as _}; use x11rb::protocol::xkb; use x11rb::protocol::xproto::{self, ConnectionExt as _}; use x11rb::x11_utils::X11Error as LogicalError; use x11rb::xcb_ffi::ReplyOrIdError; use crate::error::{EventLoopError, OsError as RootOsError}; use crate::event::{Event, StartCause, WindowEvent}; use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents, EventLoopClosed}; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::common::xkb::Context; use crate::platform_impl::platform::{min_timeout, WindowId}; use crate::platform_impl::{ ActiveEventLoop as PlatformActiveEventLoop, OsError, PlatformCustomCursor, }; use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, WindowAttributes}; mod activation; mod atoms; mod dnd; mod event_processor; pub mod ffi; mod ime; mod monitor; mod util; mod window; mod xdisplay; mod xsettings; pub use util::CustomCursor; use atoms::*; use dnd::{Dnd, DndState}; use event_processor::{EventProcessor, MAX_MOD_REPLAY_LEN}; use ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender}; pub(crate) use monitor::{MonitorHandle, VideoModeHandle}; use window::UnownedWindow; pub(crate) use xdisplay::{XConnection, XError, XNotSupported}; // Xinput constants not defined in x11rb const ALL_DEVICES: u16 = 0; const ALL_MASTER_DEVICES: u16 = 1; const ICONIC_STATE: u32 = 3; /// The underlying x11rb connection that we are using. type X11rbConnection = x11rb::xcb_ffi::XCBConnection; type X11Source = Generic>; struct WakeSender { sender: Sender, waker: Ping, } impl Clone for WakeSender { fn clone(&self) -> Self { Self { sender: self.sender.clone(), waker: self.waker.clone() } } } impl WakeSender { pub fn send(&self, t: T) -> Result<(), EventLoopClosed> { let res = self.sender.send(t).map_err(|e| EventLoopClosed(e.0)); if res.is_ok() { self.waker.ping(); } res } } struct PeekableReceiver { recv: Receiver, first: Option, } impl PeekableReceiver { pub fn from_recv(recv: Receiver) -> Self { Self { recv, first: None } } pub fn has_incoming(&mut self) -> bool { if self.first.is_some() { return true; } match self.recv.try_recv() { Ok(v) => { self.first = Some(v); true }, Err(TryRecvError::Empty) => false, Err(TryRecvError::Disconnected) => { warn!("Channel was disconnected when checking incoming"); false }, } } pub fn try_recv(&mut self) -> Result { if let Some(first) = self.first.take() { return Ok(first); } self.recv.try_recv() } } pub struct ActiveEventLoop { xconn: Arc, wm_delete_window: xproto::Atom, net_wm_ping: xproto::Atom, ime_sender: ImeSender, control_flow: Cell, exit: Cell>, root: xproto::Window, ime: Option>, windows: RefCell>>, redraw_sender: WakeSender, activation_sender: WakeSender, device_events: Cell, } pub struct EventLoop { loop_running: bool, event_loop: Loop<'static, EventLoopState>, waker: calloop::ping::Ping, event_processor: EventProcessor, redraw_receiver: PeekableReceiver, user_receiver: PeekableReceiver, activation_receiver: PeekableReceiver, user_sender: Sender, /// The current state of the event loop. state: EventLoopState, } type ActivationToken = (WindowId, crate::event_loop::AsyncRequestSerial); struct EventLoopState { /// The latest readiness state for the x11 file descriptor x11_readiness: Readiness, } pub struct EventLoopProxy { user_sender: WakeSender, } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy { user_sender: self.user_sender.clone() } } } impl EventLoop { pub(crate) fn new(xconn: Arc) -> EventLoop { let root = xconn.default_root().root; let atoms = xconn.atoms(); let wm_delete_window = atoms[WM_DELETE_WINDOW]; let net_wm_ping = atoms[_NET_WM_PING]; let dnd = Dnd::new(Arc::clone(&xconn)) .expect("Failed to call XInternAtoms when initializing drag and drop"); let (ime_sender, ime_receiver) = mpsc::channel(); let (ime_event_sender, ime_event_receiver) = mpsc::channel(); // Input methods will open successfully without setting the locale, but it won't be // possible to actually commit pre-edit sequences. unsafe { // Remember default locale to restore it if target locale is unsupported // by Xlib let default_locale = setlocale(LC_CTYPE, ptr::null()); setlocale(LC_CTYPE, b"\0".as_ptr() as *const _); // Check if set locale is supported by Xlib. // If not, calls to some Xlib functions like `XSetLocaleModifiers` // will fail. let locale_supported = (xconn.xlib.XSupportsLocale)() == 1; if !locale_supported { let unsupported_locale = setlocale(LC_CTYPE, ptr::null()); warn!( "Unsupported locale \"{}\". Restoring default locale \"{}\".", CStr::from_ptr(unsupported_locale).to_string_lossy(), CStr::from_ptr(default_locale).to_string_lossy() ); // Restore default locale setlocale(LC_CTYPE, default_locale); } } let ime = Ime::new(Arc::clone(&xconn), ime_event_sender); if let Err(ImeCreationError::OpenFailure(state)) = ime.as_ref() { warn!("Failed to open input method: {state:#?}"); } else if let Err(err) = ime.as_ref() { warn!("Failed to set input method destruction callback: {err:?}"); } let ime = ime.ok().map(RefCell::new); let randr_event_offset = xconn.select_xrandr_input(root).expect("Failed to query XRandR extension"); let xi2ext = xconn .xcb_connection() .extension_information(xinput::X11_EXTENSION_NAME) .expect("Failed to query XInput extension") .expect("X server missing XInput extension"); let xkbext = xconn .xcb_connection() .extension_information(xkb::X11_EXTENSION_NAME) .expect("Failed to query XKB extension") .expect("X server missing XKB extension"); // Check for XInput2 support. xconn .xcb_connection() .xinput_xi_query_version(2, 3) .expect("Failed to send XInput2 query version request") .reply() .expect("Error while checking for XInput2 query version reply"); xconn.update_cached_wm_info(root); // Create an event loop. let event_loop = Loop::::try_new().expect("Failed to initialize the event loop"); let handle = event_loop.handle(); // Create the X11 event dispatcher. let source = X11Source::new( // SAFETY: xcb owns the FD and outlives the source. unsafe { BorrowedFd::borrow_raw(xconn.xcb_connection().as_raw_fd()) }, calloop::Interest::READ, calloop::Mode::Level, ); handle .insert_source(source, |readiness, _, state| { state.x11_readiness = readiness; Ok(calloop::PostAction::Continue) }) .expect("Failed to register the X11 event dispatcher"); let (waker, waker_source) = calloop::ping::make_ping().expect("Failed to create event loop waker"); event_loop .handle() .insert_source(waker_source, move |_, _, _| { // No extra handling is required, we just need to wake-up. }) .expect("Failed to register the event loop waker source"); // Create a channel for handling redraw requests. let (redraw_sender, redraw_channel) = mpsc::channel(); // Create a channel for sending activation tokens. let (activation_token_sender, activation_token_channel) = mpsc::channel(); // Create a channel for sending user events. let (user_sender, user_channel) = mpsc::channel(); let xkb_context = Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap(); let mut xmodmap = util::ModifierKeymap::new(); xmodmap.reload_from_x_connection(&xconn); let window_target = ActiveEventLoop { ime, root, control_flow: Cell::new(ControlFlow::default()), exit: Cell::new(None), windows: Default::default(), ime_sender, xconn, wm_delete_window, net_wm_ping, redraw_sender: WakeSender { sender: redraw_sender, // not used again so no clone waker: waker.clone(), }, activation_sender: WakeSender { sender: activation_token_sender, // not used again so no clone waker: waker.clone(), }, device_events: Default::default(), }; // Set initial device event filter. window_target.update_listen_device_events(true); let root_window_target = RootAEL { p: PlatformActiveEventLoop::X(window_target), _marker: PhantomData }; let event_processor = EventProcessor { target: root_window_target, dnd, devices: Default::default(), randr_event_offset, ime_receiver, ime_event_receiver, xi2ext, xfiltered_modifiers: VecDeque::with_capacity(MAX_MOD_REPLAY_LEN), xmodmap, xkbext, xkb_context, num_touch: 0, held_key_press: None, first_touch: None, active_window: None, modifiers: Default::default(), is_composing: false, }; // Register for device hotplug events // (The request buffer is flushed during `init_device`) let xconn = &EventProcessor::window_target(&event_processor.target).xconn; xconn .select_xinput_events( root, ALL_DEVICES, x11rb::protocol::xinput::XIEventMask::HIERARCHY, ) .expect_then_ignore_error("Failed to register for XInput2 device hotplug events"); xconn .select_xkb_events( 0x100, // Use the "core keyboard device" xkb::EventType::NEW_KEYBOARD_NOTIFY | xkb::EventType::MAP_NOTIFY | xkb::EventType::STATE_NOTIFY, ) .unwrap(); event_processor.init_device(ALL_DEVICES); EventLoop { loop_running: false, event_loop, waker, event_processor, redraw_receiver: PeekableReceiver::from_recv(redraw_channel), activation_receiver: PeekableReceiver::from_recv(activation_token_channel), user_receiver: PeekableReceiver::from_recv(user_channel), user_sender, state: EventLoopState { x11_readiness: Readiness::EMPTY }, } } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { user_sender: WakeSender { sender: self.user_sender.clone(), waker: self.waker.clone() }, } } pub(crate) fn window_target(&self) -> &RootAEL { &self.event_processor.target } pub fn run_on_demand(&mut self, mut event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootAEL), { let exit = loop { match self.pump_events(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); }, PumpStatus::Exit(code) => { break Err(EventLoopError::ExitFailure(code)); }, _ => { continue; }, } }; // Applications aren't allowed to carry windows between separate // `run_on_demand` calls but if they have only just dropped their // windows we need to make sure those last requests are sent to the // X Server. let wt = EventProcessor::window_target(&self.event_processor.target); wt.x_connection().sync_with_server().map_err(|x_err| { EventLoopError::Os(os_error!(OsError::XError(Arc::new(X11Error::Xlib(x_err))))) })?; exit } pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus where F: FnMut(Event, &RootAEL), { if !self.loop_running { self.loop_running = true; // run the initial loop iteration self.single_iteration(&mut callback, StartCause::Init); } // Consider the possibility that the `StartCause::Init` iteration could // request to Exit. if !self.exiting() { self.poll_events_with_timeout(timeout, &mut callback); } if let Some(code) = self.exit_code() { self.loop_running = false; callback(Event::LoopExiting, self.window_target()); PumpStatus::Exit(code) } else { PumpStatus::Continue } } fn has_pending(&mut self) -> bool { self.event_processor.poll() || self.user_receiver.has_incoming() || self.redraw_receiver.has_incoming() } pub fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) where F: FnMut(Event, &RootAEL), { let start = Instant::now(); let has_pending = self.has_pending(); timeout = if has_pending { // If we already have work to do then we don't want to block on the next poll. Some(Duration::ZERO) } else { let control_flow_timeout = match self.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::WaitUntil(wait_deadline) => { Some(wait_deadline.saturating_duration_since(start)) }, }; min_timeout(control_flow_timeout, timeout) }; self.state.x11_readiness = Readiness::EMPTY; if let Err(error) = self.event_loop.dispatch(timeout, &mut self.state).map_err(std::io::Error::from) { tracing::error!("Failed to poll for events: {error:?}"); let exit_code = error.raw_os_error().unwrap_or(1); self.set_exit_code(exit_code); return; } // NB: `StartCause::Init` is handled as a special case and doesn't need // to be considered here let cause = match self.control_flow() { ControlFlow::Poll => StartCause::Poll, ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None }, ControlFlow::WaitUntil(deadline) => { if Instant::now() < deadline { StartCause::WaitCancelled { start, requested_resume: Some(deadline) } } else { StartCause::ResumeTimeReached { start, requested_resume: deadline } } }, }; // False positive / spurious wake ups could lead to us spamming // redundant iterations of the event loop with no new events to // dispatch. // // If there's no readable event source then we just double check if we // have any pending `_receiver` events and if not we return without // running a loop iteration. // If we don't have any pending `_receiver` if !self.has_pending() && !matches!(&cause, StartCause::ResumeTimeReached { .. } | StartCause::Poll) { return; } self.single_iteration(&mut callback, cause); } fn single_iteration(&mut self, callback: &mut F, cause: StartCause) where F: FnMut(Event, &RootAEL), { callback(Event::NewEvents(cause), &self.event_processor.target); // NB: For consistency all platforms must emit a 'resumed' event even though X11 // applications don't themselves have a formal suspend/resume lifecycle. if cause == StartCause::Init { callback(Event::Resumed, &self.event_processor.target); } // Process all pending events self.drain_events(callback); // Empty activation tokens. while let Ok((window_id, serial)) = self.activation_receiver.try_recv() { let token = self.event_processor.with_window(window_id.0 as xproto::Window, |window| { window.generate_activation_token() }); match token { Some(Ok(token)) => { let event = Event::WindowEvent { window_id: crate::window::WindowId(window_id), event: WindowEvent::ActivationTokenDone { serial, token: crate::window::ActivationToken::from_raw(token), }, }; callback(event, &self.event_processor.target) }, Some(Err(e)) => { tracing::error!("Failed to get activation token: {}", e); }, None => {}, } } // Empty the user event buffer { while let Ok(event) = self.user_receiver.try_recv() { callback(Event::UserEvent(event), &self.event_processor.target); } } // Empty the redraw requests { let mut windows = HashSet::new(); while let Ok(window_id) = self.redraw_receiver.try_recv() { windows.insert(window_id); } for window_id in windows { let window_id = crate::window::WindowId(window_id); callback( Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested }, &self.event_processor.target, ); } } // This is always the last event we dispatch before poll again { callback(Event::AboutToWait, &self.event_processor.target); } } fn drain_events(&mut self, callback: &mut F) where F: FnMut(Event, &RootAEL), { let mut xev = MaybeUninit::uninit(); while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } { let mut xev = unsafe { xev.assume_init() }; self.event_processor.process_event(&mut xev, |window_target, event| { if let Event::WindowEvent { window_id: crate::window::WindowId(wid), event: WindowEvent::RedrawRequested, } = event { let window_target = EventProcessor::window_target(window_target); window_target.redraw_sender.send(wid).unwrap(); } else { callback(event, window_target); } }); } } fn control_flow(&self) -> ControlFlow { let window_target = EventProcessor::window_target(&self.event_processor.target); window_target.control_flow() } fn exiting(&self) -> bool { let window_target = EventProcessor::window_target(&self.event_processor.target); window_target.exiting() } fn set_exit_code(&self, code: i32) { let window_target = EventProcessor::window_target(&self.event_processor.target); window_target.set_exit_code(code); } fn exit_code(&self) -> Option { let window_target = EventProcessor::window_target(&self.event_processor.target); window_target.exit_code() } } impl AsFd for EventLoop { fn as_fd(&self) -> BorrowedFd<'_> { self.event_loop.as_fd() } } impl AsRawFd for EventLoop { fn as_raw_fd(&self) -> RawFd { self.event_loop.as_raw_fd() } } impl ActiveEventLoop { /// Returns the `XConnection` of this events loop. #[inline] pub(crate) fn x_connection(&self) -> &Arc { &self.xconn } pub fn available_monitors(&self) -> impl Iterator { self.xconn.available_monitors().into_iter().flatten() } pub fn primary_monitor(&self) -> Option { self.xconn.primary_monitor().ok() } pub(crate) fn create_custom_cursor(&self, cursor: CustomCursorSource) -> RootCustomCursor { RootCustomCursor { inner: PlatformCustomCursor::X(CustomCursor::new(self, cursor.inner)) } } pub fn listen_device_events(&self, allowed: DeviceEvents) { self.device_events.set(allowed); } /// Update the device event based on window focus. pub fn update_listen_device_events(&self, focus: bool) { let device_events = self.device_events.get() == DeviceEvents::Always || (focus && self.device_events.get() == DeviceEvents::WhenFocused); let mut mask = xinput::XIEventMask::from(0u32); if device_events { mask = xinput::XIEventMask::RAW_MOTION | xinput::XIEventMask::RAW_BUTTON_PRESS | xinput::XIEventMask::RAW_BUTTON_RELEASE | xinput::XIEventMask::RAW_KEY_PRESS | xinput::XIEventMask::RAW_KEY_RELEASE; } self.xconn .select_xinput_events(self.root, ALL_MASTER_DEVICES, mask) .expect_then_ignore_error("Failed to update device event filter"); } #[cfg(feature = "rwh_05")] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { let mut display_handle = rwh_05::XlibDisplayHandle::empty(); display_handle.display = self.xconn.display as *mut _; display_handle.screen = self.xconn.default_screen_index() as c_int; display_handle.into() } #[cfg(feature = "rwh_06")] pub fn raw_display_handle_rwh_06( &self, ) -> Result { let display_handle = rwh_06::XlibDisplayHandle::new( // SAFETY: display will never be null Some( std::ptr::NonNull::new(self.xconn.display as *mut _) .expect("X11 display should never be null"), ), self.xconn.default_screen_index() as c_int, ); Ok(display_handle.into()) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.control_flow.set(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.control_flow.get() } pub(crate) fn exit(&self) { self.exit.set(Some(0)) } pub(crate) fn clear_exit(&self) { self.exit.set(None) } pub(crate) fn exiting(&self) -> bool { self.exit.get().is_some() } pub(crate) fn set_exit_code(&self, code: i32) { self.exit.set(Some(code)) } pub(crate) fn exit_code(&self) -> Option { self.exit.get() } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.user_sender.send(event).map_err(|e| EventLoopClosed(e.0)) } } struct DeviceInfo<'a> { xconn: &'a XConnection, info: *const ffi::XIDeviceInfo, count: usize, } impl<'a> DeviceInfo<'a> { fn get(xconn: &'a XConnection, device: c_int) -> Option { unsafe { let mut count = 0; let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count); xconn.check_errors().ok()?; if info.is_null() || count == 0 { None } else { Some(DeviceInfo { xconn, info, count: count as usize }) } } } } impl Drop for DeviceInfo<'_> { fn drop(&mut self) { assert!(!self.info.is_null()); unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) }; } } impl Deref for DeviceInfo<'_> { type Target = [ffi::XIDeviceInfo]; fn deref(&self) -> &Self::Target { unsafe { slice::from_raw_parts(self.info, self.count) } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(xinput::DeviceId); impl DeviceId { #[allow(unused)] pub const fn dummy() -> Self { DeviceId(0) } } pub(crate) struct Window(Arc); impl Deref for Window { type Target = UnownedWindow; #[inline] fn deref(&self) -> &UnownedWindow { &self.0 } } impl Window { pub(crate) fn new( event_loop: &ActiveEventLoop, attribs: WindowAttributes, ) -> Result { let window = Arc::new(UnownedWindow::new(event_loop, attribs)?); event_loop.windows.borrow_mut().insert(window.id(), Arc::downgrade(&window)); Ok(Window(window)) } } impl Drop for Window { fn drop(&mut self) { let window = self.deref(); let xconn = &window.xconn; if let Ok(c) = xconn.xcb_connection().destroy_window(window.id().0 as xproto::Window) { c.ignore_error(); } } } /// Generic sum error type for X11 errors. #[derive(Debug)] pub enum X11Error { /// An error from the Xlib library. Xlib(XError), /// An error that occurred while trying to connect to the X server. Connect(ConnectError), /// An error that occurred over the connection medium. Connection(ConnectionError), /// An error that occurred logically on the X11 end. X11(LogicalError), /// The XID range has been exhausted. XidsExhausted(IdsExhausted), /// Got `null` from an Xlib function without a reason. UnexpectedNull(&'static str), /// Got an invalid activation token. InvalidActivationToken(Vec), /// An extension that we rely on is not available. MissingExtension(&'static str), /// Could not find a matching X11 visual for this visualid NoSuchVisual(xproto::Visualid), /// Unable to parse xsettings. XsettingsParse(xsettings::ParserError), /// Failed to get property. GetProperty(util::GetPropertyError), } impl fmt::Display for X11Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { X11Error::Xlib(e) => write!(f, "Xlib error: {}", e), X11Error::Connect(e) => write!(f, "X11 connection error: {}", e), X11Error::Connection(e) => write!(f, "X11 connection error: {}", e), X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e), X11Error::GetProperty(e) => write!(f, "Failed to get X property {}", e), X11Error::X11(e) => write!(f, "X11 error: {:?}", e), X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s), X11Error::InvalidActivationToken(s) => write!( f, "Invalid activation token: {}", std::str::from_utf8(s).unwrap_or("") ), X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {}", s), X11Error::NoSuchVisual(visualid) => { write!(f, "Could not find a matching X11 visual for ID `{:x}`", visualid) }, X11Error::XsettingsParse(err) => { write!(f, "Failed to parse xsettings: {:?}", err) }, } } } impl std::error::Error for X11Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { X11Error::Xlib(e) => Some(e), X11Error::Connect(e) => Some(e), X11Error::Connection(e) => Some(e), X11Error::XidsExhausted(e) => Some(e), _ => None, } } } impl From for X11Error { fn from(e: XError) -> Self { X11Error::Xlib(e) } } impl From for X11Error { fn from(e: ConnectError) -> Self { X11Error::Connect(e) } } impl From for X11Error { fn from(e: ConnectionError) -> Self { X11Error::Connection(e) } } impl From for X11Error { fn from(e: LogicalError) -> Self { X11Error::X11(e) } } impl From for X11Error { fn from(value: ReplyError) -> Self { match value { ReplyError::ConnectionError(e) => e.into(), ReplyError::X11Error(e) => e.into(), } } } impl From for X11Error { fn from(value: ime::ImeContextCreationError) -> Self { match value { ime::ImeContextCreationError::XError(e) => e.into(), ime::ImeContextCreationError::Null => Self::UnexpectedNull("XOpenIM"), } } } impl From for X11Error { fn from(value: ReplyOrIdError) -> Self { match value { ReplyOrIdError::ConnectionError(e) => e.into(), ReplyOrIdError::X11Error(e) => e.into(), ReplyOrIdError::IdsExhausted => Self::XidsExhausted(IdsExhausted), } } } impl From for X11Error { fn from(value: xsettings::ParserError) -> Self { Self::XsettingsParse(value) } } impl From for X11Error { fn from(value: util::GetPropertyError) -> Self { Self::GetProperty(value) } } /// Type alias for a void cookie. type VoidCookie<'a> = x11rb::cookie::VoidCookie<'a, X11rbConnection>; /// Extension trait for `Result`. trait CookieResultExt { /// Unwrap the send error and ignore the result. fn expect_then_ignore_error(self, msg: &str); } impl CookieResultExt for Result, E> { fn expect_then_ignore_error(self, msg: &str) { self.expect(msg).ignore_error() } } fn mkwid(w: xproto::Window) -> crate::window::WindowId { crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _)) } fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId { crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w))) } #[derive(Debug)] pub struct Device { _name: String, scroll_axes: Vec<(i32, ScrollAxis)>, // For master devices, this is the paired device (pointer <-> keyboard). // For slave devices, this is the master. attachment: c_int, } #[derive(Debug, Copy, Clone)] struct ScrollAxis { increment: f64, orientation: ScrollOrientation, position: f64, } #[derive(Debug, Copy, Clone)] enum ScrollOrientation { Vertical, Horizontal, } impl Device { fn new(info: &ffi::XIDeviceInfo) -> Self { let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; let mut scroll_axes = Vec::new(); if Device::physical_device(info) { // Identify scroll axes for &class_ptr in Device::classes(info) { let ty = unsafe { (*class_ptr)._type }; if ty == ffi::XIScrollClass { let info = unsafe { &*(class_ptr as *const ffi::XIScrollClassInfo) }; scroll_axes.push((info.number, ScrollAxis { increment: info.increment, orientation: match info.scroll_type { ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal, ffi::XIScrollTypeVertical => ScrollOrientation::Vertical, _ => unreachable!(), }, position: 0.0, })); } } } let mut device = Device { _name: name.into_owned(), scroll_axes, attachment: info.attachment }; device.reset_scroll_position(info); device } fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) { if Device::physical_device(info) { for &class_ptr in Device::classes(info) { let ty = unsafe { (*class_ptr)._type }; if ty == ffi::XIValuatorClass { let info = unsafe { &*(class_ptr as *const ffi::XIValuatorClassInfo) }; if let Some(&mut (_, ref mut axis)) = self.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == info.number) { axis.position = info.value; } } } } } #[inline] fn physical_device(info: &ffi::XIDeviceInfo) -> bool { info._use == ffi::XISlaveKeyboard || info._use == ffi::XISlavePointer || info._use == ffi::XIFloatingSlave } #[inline] fn classes(info: &ffi::XIDeviceInfo) -> &[*const ffi::XIAnyClassInfo] { unsafe { slice::from_raw_parts( info.classes as *const *const ffi::XIAnyClassInfo, info.num_classes as usize, ) } } } /// Convert the raw X11 representation for a 32-bit floating point to a double. #[inline] fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 { (fp as f64) / ((1 << 16) as f64) } winit-0.30.9/src/platform_impl/linux/x11/monitor.rs000064400000000000000000000250241046102023000203130ustar 00000000000000use super::{util, X11Error, XConnection}; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::platform_impl::VideoModeHandle as PlatformVideoModeHandle; use x11rb::connection::RequestConnection; use x11rb::protocol::randr::{self, ConnectionExt as _}; use x11rb::protocol::xproto; // Used for testing. This should always be committed as false. const DISABLE_MONITOR_LIST_CACHING: bool = false; impl XConnection { pub fn invalidate_cached_monitor_list(&self) -> Option> { // We update this lazily. self.monitor_handles.lock().unwrap().take() } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VideoModeHandle { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, pub(crate) native_mode: randr::Mode, pub(crate) monitor: Option, } impl VideoModeHandle { #[inline] pub fn size(&self) -> PhysicalSize { self.size.into() } #[inline] pub fn bit_depth(&self) -> u16 { self.bit_depth } #[inline] pub fn refresh_rate_millihertz(&self) -> u32 { self.refresh_rate_millihertz } #[inline] pub fn monitor(&self) -> MonitorHandle { self.monitor.clone().unwrap() } } #[derive(Debug, Clone)] pub struct MonitorHandle { /// The actual id pub(crate) id: randr::Crtc, /// The name of the monitor pub(crate) name: String, /// The size of the monitor dimensions: (u32, u32), /// The position of the monitor in the X screen position: (i32, i32), /// If the monitor is the primary one primary: bool, /// The refresh rate used by monitor. refresh_rate_millihertz: Option, /// The DPI scale factor pub(crate) scale_factor: f64, /// Used to determine which windows are on this monitor pub(crate) rect: util::AaRect, /// Supported video modes on this monitor video_modes: Vec, } impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { self.id == other.id } } impl Eq for MonitorHandle {} impl PartialOrd for MonitorHandle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for MonitorHandle { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.id.cmp(&other.id) } } impl std::hash::Hash for MonitorHandle { fn hash(&self, state: &mut H) { self.id.hash(state); } } #[inline] pub fn mode_refresh_rate_millihertz(mode: &randr::ModeInfo) -> Option { if mode.dot_clock > 0 && mode.htotal > 0 && mode.vtotal > 0 { #[allow(clippy::unnecessary_cast)] Some((mode.dot_clock as u64 * 1000 / (mode.htotal as u64 * mode.vtotal as u64)) as u32) } else { None } } impl MonitorHandle { fn new( xconn: &XConnection, resources: &ScreenResources, id: randr::Crtc, crtc: &randr::GetCrtcInfoReply, primary: bool, ) -> Option { let (name, scale_factor, video_modes) = xconn.get_output_info(resources, crtc)?; let dimensions = (crtc.width as u32, crtc.height as u32); let position = (crtc.x as i32, crtc.y as i32); // Get the refresh rate of the current video mode. let current_mode = crtc.mode; let screen_modes = resources.modes(); let refresh_rate_millihertz = screen_modes .iter() .find(|mode| mode.id == current_mode) .and_then(mode_refresh_rate_millihertz); let rect = util::AaRect::new(position, dimensions); Some(MonitorHandle { id, name, refresh_rate_millihertz, scale_factor, dimensions, position, primary, rect, video_modes, }) } pub fn dummy() -> Self { MonitorHandle { id: 0, name: "".into(), scale_factor: 1.0, dimensions: (1, 1), position: (0, 0), refresh_rate_millihertz: None, primary: true, rect: util::AaRect::new((0, 0), (1, 1)), video_modes: Vec::new(), } } pub(crate) fn is_dummy(&self) -> bool { // Zero is an invalid XID value; no real monitor will have it self.id == 0 } pub fn name(&self) -> Option { Some(self.name.clone()) } #[inline] pub fn native_identifier(&self) -> u32 { self.id as _ } pub fn size(&self) -> PhysicalSize { self.dimensions.into() } pub fn position(&self) -> PhysicalPosition { self.position.into() } pub fn refresh_rate_millihertz(&self) -> Option { self.refresh_rate_millihertz } #[inline] pub fn scale_factor(&self) -> f64 { self.scale_factor } #[inline] pub fn video_modes(&self) -> impl Iterator { let monitor = self.clone(); self.video_modes.clone().into_iter().map(move |mut x| { x.monitor = Some(monitor.clone()); PlatformVideoModeHandle::X(x) }) } } impl XConnection { pub fn get_monitor_for_window( &self, window_rect: Option, ) -> Result { let monitors = self.available_monitors()?; if monitors.is_empty() { // Return a dummy monitor to avoid panicking return Ok(MonitorHandle::dummy()); } let default = monitors.first().unwrap(); let window_rect = match window_rect { Some(rect) => rect, None => return Ok(default.to_owned()), }; let mut largest_overlap = 0; let mut matched_monitor = default; for monitor in &monitors { let overlapping_area = window_rect.get_overlapping_area(&monitor.rect); if overlapping_area > largest_overlap { largest_overlap = overlapping_area; matched_monitor = monitor; } } Ok(matched_monitor.to_owned()) } fn query_monitor_list(&self) -> Result, X11Error> { let root = self.default_root(); let resources = ScreenResources::from_connection(self.xcb_connection(), root, self.randr_version())?; // Pipeline all of the get-crtc requests. let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len()); for &crtc in resources.crtcs() { crtc_cookies .push(self.xcb_connection().randr_get_crtc_info(crtc, x11rb::CURRENT_TIME)?); } // Do this here so we do all of our requests in one shot. let primary = self.xcb_connection().randr_get_output_primary(root.root)?.reply()?.output; let mut crtc_infos = Vec::with_capacity(crtc_cookies.len()); for cookie in crtc_cookies { let reply = cookie.reply()?; crtc_infos.push(reply); } let mut has_primary = false; let mut available_monitors = Vec::with_capacity(resources.crtcs().len()); for (crtc_id, crtc) in resources.crtcs().iter().zip(crtc_infos.iter()) { if crtc.width == 0 || crtc.height == 0 || crtc.outputs.is_empty() { continue; } let is_primary = crtc.outputs[0] == primary; has_primary |= is_primary; let monitor = MonitorHandle::new(self, &resources, *crtc_id, crtc, is_primary); available_monitors.extend(monitor); } // If we don't have a primary monitor, just pick one ourselves! if !has_primary { if let Some(ref mut fallback) = available_monitors.first_mut() { // Setting this here will come in handy if we ever add an `is_primary` method. fallback.primary = true; } } Ok(available_monitors) } pub fn available_monitors(&self) -> Result, X11Error> { let mut monitors_lock = self.monitor_handles.lock().unwrap(); match *monitors_lock { Some(ref monitors) => Ok(monitors.clone()), None => { let monitors = self.query_monitor_list()?; if !DISABLE_MONITOR_LIST_CACHING { *monitors_lock = Some(monitors.clone()); } Ok(monitors) }, } } #[inline] pub fn primary_monitor(&self) -> Result { Ok(self .available_monitors()? .into_iter() .find(|monitor| monitor.primary) .unwrap_or_else(MonitorHandle::dummy)) } pub fn select_xrandr_input(&self, root: xproto::Window) -> Result { use randr::NotifyMask; // Get extension info. let info = self .xcb_connection() .extension_information(randr::X11_EXTENSION_NAME)? .ok_or(X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?; // Select input data. let event_mask = NotifyMask::CRTC_CHANGE | NotifyMask::OUTPUT_PROPERTY | NotifyMask::SCREEN_CHANGE; self.xcb_connection().randr_select_input(root, event_mask)?; Ok(info.first_event) } } pub struct ScreenResources { /// List of attached modes. modes: Vec, /// List of attached CRTCs. crtcs: Vec, } impl ScreenResources { pub(crate) fn modes(&self) -> &[randr::ModeInfo] { &self.modes } pub(crate) fn crtcs(&self) -> &[randr::Crtc] { &self.crtcs } pub(crate) fn from_connection( conn: &impl x11rb::connection::Connection, root: &x11rb::protocol::xproto::Screen, (major_version, minor_version): (u32, u32), ) -> Result { if (major_version == 1 && minor_version >= 3) || major_version > 1 { let reply = conn.randr_get_screen_resources_current(root.root)?.reply()?; Ok(Self::from_get_screen_resources_current_reply(reply)) } else { let reply = conn.randr_get_screen_resources(root.root)?.reply()?; Ok(Self::from_get_screen_resources_reply(reply)) } } pub(crate) fn from_get_screen_resources_reply(reply: randr::GetScreenResourcesReply) -> Self { Self { modes: reply.modes, crtcs: reply.crtcs } } pub(crate) fn from_get_screen_resources_current_reply( reply: randr::GetScreenResourcesCurrentReply, ) -> Self { Self { modes: reply.modes, crtcs: reply.crtcs } } } winit-0.30.9/src/platform_impl/linux/x11/tests/xsettings.dat000064400000000000000000000152701046102023000221440ustar 000000000000000x6c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x00,0x00,0x0b,0x00,0x58,0x66,0x74,0x2f,0x48,0x69,0x6e,0x74,0x69,0x6e,0x67,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x47,0x74,0x6b,0x2f,0x44,0x69,0x61,0x6c,0x6f,0x67,0x73,0x55,0x73,0x65,0x48,0x65,0x61,0x64,0x65,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x0c,0x00,0x47,0x74,0x6b,0x2f,0x46,0x6f,0x6e,0x74,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x4e,0x6f,0x74,0x6f,0x20,0x53,0x61,0x6e,0x73,0x20,0x39,0x00,0x01,0x00,0x0d,0x00,0x58,0x66,0x74,0x2f,0x4c,0x63,0x64,0x66,0x69,0x6c,0x74,0x65,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x6c,0x63,0x64,0x64,0x65,0x66,0x61,0x75,0x6c,0x74,0x00,0x00,0x01,0x00,0x10,0x00,0x47,0x74,0x6b,0x2f,0x4b,0x65,0x79,0x54,0x68,0x65,0x6d,0x65,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x0d,0x00,0x58,0x66,0x74,0x2f,0x48,0x69,0x6e,0x74,0x53,0x74,0x79,0x6c,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x68,0x69,0x6e,0x74,0x73,0x6c,0x69,0x67,0x68,0x74,0x00,0x00,0x01,0x00,0x11,0x00,0x4e,0x65,0x74,0x2f,0x49,0x63,0x6f,0x6e,0x54,0x68,0x65,0x6d,0x65,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x65,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x61,0x72,0x79,0x2d,0x78,0x66,0x63,0x65,0x2d,0x64,0x61,0x72,0x6b,0x00,0x00,0x0d,0x00,0x58,0x66,0x74,0x2f,0x41,0x6e,0x74,0x69,0x61,0x6c,0x69,0x61,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x08,0x00,0x58,0x66,0x74,0x2f,0x52,0x47,0x42,0x41,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x72,0x67,0x62,0x00,0x00,0x00,0x13,0x00,0x4e,0x65,0x74,0x2f,0x43,0x75,0x72,0x73,0x6f,0x72,0x42,0x6c,0x69,0x6e,0x6b,0x54,0x69,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0xb0,0x04,0x00,0x00,0x00,0x00,0x13,0x00,0x47,0x74,0x6b,0x2f,0x43,0x75,0x72,0x73,0x6f,0x72,0x54,0x68,0x65,0x6d,0x65,0x53,0x69,0x7a,0x65,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x01,0x00,0x15,0x00,0x4e,0x65,0x74,0x2f,0x46,0x61,0x6c,0x6c,0x62,0x61,0x63,0x6b,0x49,0x63,0x6f,0x6e,0x54,0x68,0x65,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x67,0x6e,0x6f,0x6d,0x65,0x00,0x00,0x00,0x01,0x00,0x10,0x00,0x47,0x74,0x6b,0x2f,0x54,0x6f,0x6f,0x6c,0x62,0x61,0x72,0x53,0x74,0x79,0x6c,0x65,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x69,0x63,0x6f,0x6e,0x73,0x00,0x00,0x00,0x01,0x00,0x12,0x00,0x4e,0x65,0x74,0x2f,0x53,0x6f,0x75,0x6e,0x64,0x54,0x68,0x65,0x6d,0x65,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x64,0x65,0x66,0x61,0x75,0x6c,0x74,0x00,0x00,0x00,0x15,0x00,0x4e,0x65,0x74,0x2f,0x45,0x6e,0x61,0x62,0x6c,0x65,0x45,0x76,0x65,0x6e,0x74,0x53,0x6f,0x75,0x6e,0x64,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x00,0x4e,0x65,0x74,0x2f,0x43,0x75,0x72,0x73,0x6f,0x72,0x42,0x6c,0x69,0x6e,0x6b,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x10,0x00,0x47,0x74,0x6b,0x2f,0x43,0x6f,0x6c,0x6f,0x72,0x50,0x61,0x6c,0x65,0x74,0x74,0x65,0x00,0x00,0x00,0x00,0x94,0x00,0x00,0x00,0x62,0x6c,0x61,0x63,0x6b,0x3a,0x77,0x68,0x69,0x74,0x65,0x3a,0x67,0x72,0x61,0x79,0x35,0x30,0x3a,0x72,0x65,0x64,0x3a,0x70,0x75,0x72,0x70,0x6c,0x65,0x3a,0x62,0x6c,0x75,0x65,0x3a,0x6c,0x69,0x67,0x68,0x74,0x20,0x62,0x6c,0x75,0x65,0x3a,0x67,0x72,0x65,0x65,0x6e,0x3a,0x79,0x65,0x6c,0x6c,0x6f,0x77,0x3a,0x6f,0x72,0x61,0x6e,0x67,0x65,0x3a,0x6c,0x61,0x76,0x65,0x6e,0x64,0x65,0x72,0x3a,0x62,0x72,0x6f,0x77,0x6e,0x3a,0x67,0x6f,0x6c,0x64,0x65,0x6e,0x72,0x6f,0x64,0x34,0x3a,0x64,0x6f,0x64,0x67,0x65,0x72,0x20,0x62,0x6c,0x75,0x65,0x3a,0x70,0x69,0x6e,0x6b,0x3a,0x6c,0x69,0x67,0x68,0x74,0x20,0x67,0x72,0x65,0x65,0x6e,0x3a,0x67,0x72,0x61,0x79,0x31,0x30,0x3a,0x67,0x72,0x61,0x79,0x33,0x30,0x3a,0x67,0x72,0x61,0x79,0x37,0x35,0x3a,0x67,0x72,0x61,0x79,0x39,0x30,0x00,0x00,0x13,0x00,0x4e,0x65,0x74,0x2f,0x44,0x6f,0x75,0x62,0x6c,0x65,0x43,0x6c,0x69,0x63,0x6b,0x54,0x69,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x90,0x01,0x00,0x00,0x00,0x00,0x13,0x00,0x47,0x74,0x6b,0x2f,0x43,0x61,0x6e,0x43,0x68,0x61,0x6e,0x67,0x65,0x41,0x63,0x63,0x65,0x6c,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x10,0x00,0x47,0x74,0x6b,0x2f,0x4d,0x65,0x6e,0x75,0x42,0x61,0x72,0x41,0x63,0x63,0x65,0x6c,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x46,0x31,0x30,0x00,0x01,0x00,0x0d,0x00,0x4e,0x65,0x74,0x2f,0x54,0x68,0x65,0x6d,0x65,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x47,0x72,0x65,0x79,0x62,0x69,0x72,0x64,0x01,0x00,0x17,0x00,0x47,0x74,0x6b,0x2f,0x54,0x69,0x74,0x6c,0x65,0x62,0x61,0x72,0x4d,0x69,0x64,0x64,0x6c,0x65,0x43,0x6c,0x69,0x63,0x6b,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x6c,0x6f,0x77,0x65,0x72,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x47,0x74,0x6b,0x2f,0x42,0x75,0x74,0x74,0x6f,0x6e,0x49,0x6d,0x61,0x67,0x65,0x73,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x4e,0x65,0x74,0x2f,0x44,0x6f,0x75,0x62,0x6c,0x65,0x43,0x6c,0x69,0x63,0x6b,0x44,0x69,0x73,0x74,0x61,0x6e,0x63,0x65,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x01,0x00,0x15,0x00,0x47,0x74,0x6b,0x2f,0x4d,0x6f,0x6e,0x6f,0x73,0x70,0x61,0x63,0x65,0x46,0x6f,0x6e,0x74,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x4d,0x6f,0x6e,0x6f,0x73,0x70,0x61,0x63,0x65,0x20,0x31,0x30,0x00,0x00,0x07,0x00,0x58,0x66,0x74,0x2f,0x44,0x50,0x49,0x00,0x02,0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x01,0x00,0x13,0x00,0x47,0x74,0x6b,0x2f,0x43,0x75,0x72,0x73,0x6f,0x72,0x54,0x68,0x65,0x6d,0x65,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x44,0x4d,0x5a,0x2d,0x57,0x68,0x69,0x74,0x65,0x00,0x00,0x00,0x00,0x00,0x13,0x00,0x47,0x74,0x6b,0x2f,0x54,0x6f,0x6f,0x6c,0x62,0x61,0x72,0x49,0x63,0x6f,0x6e,0x53,0x69,0x7a,0x65,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x4e,0x65,0x74,0x2f,0x44,0x6e,0x64,0x44,0x72,0x61,0x67,0x54,0x68,0x72,0x65,0x73,0x68,0x6f,0x6c,0x64,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x01,0x00,0x14,0x00,0x47,0x74,0x6b,0x2f,0x44,0x65,0x63,0x6f,0x72,0x61,0x74,0x69,0x6f,0x6e,0x4c,0x61,0x79,0x6f,0x75,0x74,0x00,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x6d,0x65,0x6e,0x75,0x3a,0x6d,0x69,0x6e,0x69,0x6d,0x69,0x7a,0x65,0x2c,0x6d,0x61,0x78,0x69,0x6d,0x69,0x7a,0x65,0x2c,0x63,0x6c,0x6f,0x73,0x65,0x00,0x00,0x1d,0x00,0x4e,0x65,0x74,0x2f,0x45,0x6e,0x61,0x62,0x6c,0x65,0x49,0x6e,0x70,0x75,0x74,0x46,0x65,0x65,0x64,0x62,0x61,0x63,0x6b,0x53,0x6f,0x75,0x6e,0x64,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x47,0x64,0x6b,0x2f,0x57,0x69,0x6e,0x64,0x6f,0x77,0x53,0x63,0x61,0x6c,0x69,0x6e,0x67,0x46,0x61,0x63,0x74,0x6f,0x72,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x0d,0x00,0x47,0x74,0x6b,0x2f,0x49,0x63,0x6f,0x6e,0x53,0x69,0x7a,0x65,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x67,0x74,0x6b,0x2d,0x62,0x75,0x74,0x74,0x6f,0x6e,0x3d,0x31,0x36,0x2c,0x31,0x36,0x00,0x00,0x0e,0x00,0x47,0x74,0x6b,0x2f,0x4d,0x65,0x6e,0x75,0x49,0x6d,0x61,0x67,0x65,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00 winit-0.30.9/src/platform_impl/linux/x11/util/client_msg.rs000064400000000000000000000017071046102023000217270ustar 00000000000000use super::*; use x11rb::x11_utils::Serialize; impl XConnection { pub fn send_client_msg( &self, window: xproto::Window, // The window this is "about"; not necessarily this window target_window: xproto::Window, // The window we're sending to message_type: xproto::Atom, event_mask: Option, data: impl Into, ) -> Result, X11Error> { let event = xproto::ClientMessageEvent { response_type: xproto::CLIENT_MESSAGE_EVENT, window, format: 32, data: data.into(), sequence: 0, type_: message_type, }; self.xcb_connection() .send_event( false, target_window, event_mask.unwrap_or(xproto::EventMask::NO_EVENT), event.serialize(), ) .map_err(Into::into) } } winit-0.30.9/src/platform_impl/linux/x11/util/cookie.rs000064400000000000000000000027541046102023000210570ustar 00000000000000use std::ffi::c_int; use std::sync::Arc; use x11_dl::xlib::{self, XEvent, XGenericEventCookie}; use crate::platform_impl::x11::XConnection; /// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. /// This is a wrapper to extract the cookie from a GenericEvent XEvent and release the cookie data /// once it has been processed pub struct GenericEventCookie { cookie: XGenericEventCookie, xconn: Arc, } impl GenericEventCookie { pub fn from_event(xconn: Arc, event: XEvent) -> Option { unsafe { let mut cookie: XGenericEventCookie = From::from(event); if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == xlib::True { Some(GenericEventCookie { cookie, xconn }) } else { None } } } #[inline] pub fn extension(&self) -> u8 { self.cookie.extension as u8 } #[inline] pub fn evtype(&self) -> c_int { self.cookie.evtype } /// Borrow inner event data as `&T`. /// /// ## SAFETY /// /// The caller must ensure that the event has the `T` inside of it. #[inline] pub unsafe fn as_event(&self) -> &T { unsafe { &*(self.cookie.data as *const _) } } } impl Drop for GenericEventCookie { fn drop(&mut self) { unsafe { (self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie); } } } winit-0.30.9/src/platform_impl/linux/x11/util/cursor.rs000064400000000000000000000117061046102023000211200ustar 00000000000000use std::ffi::CString; use std::hash::{Hash, Hasher}; use std::sync::Arc; use std::{iter, slice}; use x11rb::connection::Connection; use crate::platform_impl::PlatformCustomCursorSource; use crate::window::CursorIcon; use super::super::ActiveEventLoop; use super::*; impl XConnection { pub fn set_cursor_icon(&self, window: xproto::Window, cursor: Option) { let cursor = *self .cursor_cache .lock() .unwrap() .entry(cursor) .or_insert_with(|| self.get_cursor(cursor)); self.update_cursor(window, cursor).expect("Failed to set cursor"); } pub(crate) fn set_custom_cursor(&self, window: xproto::Window, cursor: &CustomCursor) { self.update_cursor(window, cursor.inner.cursor).expect("Failed to set cursor"); } fn create_empty_cursor(&self) -> ffi::Cursor { let data = 0; let pixmap = unsafe { let screen = (self.xlib.XDefaultScreen)(self.display); let window = (self.xlib.XRootWindow)(self.display, screen); (self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1) }; if pixmap == 0 { panic!("failed to allocate pixmap for cursor"); } unsafe { // We don't care about this color, since it only fills bytes // in the pixmap which are not 0 in the mask. let mut dummy_color = MaybeUninit::uninit(); let cursor = (self.xlib.XCreatePixmapCursor)( self.display, pixmap, pixmap, dummy_color.as_mut_ptr(), dummy_color.as_mut_ptr(), 0, 0, ); (self.xlib.XFreePixmap)(self.display, pixmap); cursor } } fn get_cursor(&self, cursor: Option) -> ffi::Cursor { let cursor = match cursor { Some(cursor) => cursor, None => return self.create_empty_cursor(), }; let mut xcursor = 0; for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) { let name = CString::new(name).unwrap(); xcursor = unsafe { (self.xcursor.XcursorLibraryLoadCursor)( self.display, name.as_ptr() as *const c_char, ) }; if xcursor != 0 { break; } } xcursor } fn update_cursor(&self, window: xproto::Window, cursor: ffi::Cursor) -> Result<(), X11Error> { self.xcb_connection() .change_window_attributes( window, &xproto::ChangeWindowAttributesAux::new().cursor(cursor as xproto::Cursor), )? .ignore_error(); self.xcb_connection().flush()?; Ok(()) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum SelectedCursor { Custom(CustomCursor), Named(CursorIcon), } #[derive(Debug, Clone)] pub struct CustomCursor { inner: Arc, } impl Hash for CustomCursor { fn hash(&self, state: &mut H) { Arc::as_ptr(&self.inner).hash(state); } } impl PartialEq for CustomCursor { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.inner, &other.inner) } } impl Eq for CustomCursor {} impl CustomCursor { pub(crate) fn new( event_loop: &ActiveEventLoop, cursor: PlatformCustomCursorSource, ) -> CustomCursor { unsafe { let ximage = (event_loop.xconn.xcursor.XcursorImageCreate)( cursor.0.width as i32, cursor.0.height as i32, ); if ximage.is_null() { panic!("failed to allocate cursor image"); } (*ximage).xhot = cursor.0.hotspot_x as u32; (*ximage).yhot = cursor.0.hotspot_y as u32; (*ximage).delay = 0; let dst = slice::from_raw_parts_mut((*ximage).pixels, cursor.0.rgba.len() / 4); for (dst, chunk) in dst.iter_mut().zip(cursor.0.rgba.chunks_exact(4)) { *dst = (chunk[0] as u32) << 16 | (chunk[1] as u32) << 8 | (chunk[2] as u32) | (chunk[3] as u32) << 24; } let cursor = (event_loop.xconn.xcursor.XcursorImageLoadCursor)(event_loop.xconn.display, ximage); (event_loop.xconn.xcursor.XcursorImageDestroy)(ximage); Self { inner: Arc::new(CustomCursorInner { xconn: event_loop.xconn.clone(), cursor }) } } } } #[derive(Debug)] struct CustomCursorInner { xconn: Arc, cursor: ffi::Cursor, } impl Drop for CustomCursorInner { fn drop(&mut self) { unsafe { (self.xconn.xlib.XFreeCursor)(self.xconn.display, self.cursor); } } } impl Default for SelectedCursor { fn default() -> Self { SelectedCursor::Named(Default::default()) } } winit-0.30.9/src/platform_impl/linux/x11/util/geometry.rs000064400000000000000000000243271046102023000214410ustar 00000000000000use std::cmp; use super::*; // Friendly neighborhood axis-aligned rectangle #[derive(Debug, Clone, PartialEq, Eq)] pub struct AaRect { x: i64, y: i64, width: i64, height: i64, } impl AaRect { pub fn new((x, y): (i32, i32), (width, height): (u32, u32)) -> Self { let (x, y) = (x as i64, y as i64); let (width, height) = (width as i64, height as i64); AaRect { x, y, width, height } } pub fn contains_point(&self, x: i64, y: i64) -> bool { x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height } pub fn get_overlapping_area(&self, other: &Self) -> i64 { let x_overlap = cmp::max( 0, cmp::min(self.x + self.width, other.x + other.width) - cmp::max(self.x, other.x), ); let y_overlap = cmp::max( 0, cmp::min(self.y + self.height, other.y + other.height) - cmp::max(self.y, other.y), ); x_overlap * y_overlap } } #[derive(Debug, Clone)] pub struct FrameExtents { pub left: u32, pub right: u32, pub top: u32, pub bottom: u32, } impl FrameExtents { pub fn new(left: u32, right: u32, top: u32, bottom: u32) -> Self { FrameExtents { left, right, top, bottom } } pub fn from_border(border: u32) -> Self { Self::new(border, border, border, border) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum FrameExtentsHeuristicPath { Supported, UnsupportedNested, UnsupportedBordered, } #[derive(Debug, Clone)] pub struct FrameExtentsHeuristic { pub frame_extents: FrameExtents, pub heuristic_path: FrameExtentsHeuristicPath, } impl FrameExtentsHeuristic { pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) { use self::FrameExtentsHeuristicPath::*; if self.heuristic_path != UnsupportedBordered { (x - self.frame_extents.left as i32, y - self.frame_extents.top as i32) } else { (x, y) } } pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) { ( width.saturating_add( self.frame_extents.left.saturating_add(self.frame_extents.right) as _ ), height.saturating_add( self.frame_extents.top.saturating_add(self.frame_extents.bottom) as _ ), ) } } impl XConnection { // This is adequate for inner_position pub fn translate_coords( &self, window: xproto::Window, root: xproto::Window, ) -> Result { self.xcb_connection().translate_coordinates(window, root, 0, 0)?.reply().map_err(Into::into) } // This is adequate for inner_size pub fn get_geometry( &self, window: xproto::Window, ) -> Result { self.xcb_connection().get_geometry(window)?.reply().map_err(Into::into) } fn get_frame_extents(&self, window: xproto::Window) -> Option { let atoms = self.atoms(); let extents_atom = atoms[_NET_FRAME_EXTENTS]; if !hint_is_supported(extents_atom) { return None; } // Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't // support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to // be unsupported by many smaller WMs. let extents: Option> = self .get_property(window, extents_atom, xproto::Atom::from(xproto::AtomEnum::CARDINAL)) .ok(); extents.and_then(|extents| { if extents.len() >= 4 { Some(FrameExtents { left: extents[0], right: extents[1], top: extents[2], bottom: extents[3], }) } else { None } }) } pub fn is_top_level(&self, window: xproto::Window, root: xproto::Window) -> Option { let atoms = self.atoms(); let client_list_atom = atoms[_NET_CLIENT_LIST]; if !hint_is_supported(client_list_atom) { return None; } let client_list: Option> = self .get_property(root, client_list_atom, xproto::Atom::from(xproto::AtomEnum::WINDOW)) .ok(); client_list.map(|client_list| client_list.contains(&(window as xproto::Window))) } fn get_parent_window(&self, window: xproto::Window) -> Result { let parent = self.xcb_connection().query_tree(window)?.reply()?.parent; Ok(parent) } fn climb_hierarchy( &self, window: xproto::Window, root: xproto::Window, ) -> Result { let mut outer_window = window; loop { let candidate = self.get_parent_window(outer_window)?; if candidate == root { break; } outer_window = candidate; } Ok(outer_window) } pub fn get_frame_extents_heuristic( &self, window: xproto::Window, root: xproto::Window, ) -> FrameExtentsHeuristic { use self::FrameExtentsHeuristicPath::*; // Position relative to root window. // With rare exceptions, this is the position of a nested window. Cases where the window // isn't nested are outlined in the comments throughout this function, but in addition to // that, fullscreen windows often aren't nested. let (inner_y_rel_root, child) = { let coords = self .translate_coords(window, root) .expect("Failed to translate window coordinates"); (coords.dst_y, coords.child) }; let (width, height, border) = { let inner_geometry = self.get_geometry(window).expect("Failed to get inner window geometry"); (inner_geometry.width, inner_geometry.height, inner_geometry.border_width) }; // The first condition is only false for un-nested windows, but isn't always false for // un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy: // when y is on the range [0, 2] and if the window has been unfocused since being // undecorated (or was undecorated upon construction), the first condition is true, // requiring us to rely on the second condition. let nested = !(window == child || self.is_top_level(child, root) == Some(true)); // Hopefully the WM supports EWMH, allowing us to get exact info on the window frames. if let Some(mut frame_extents) = self.get_frame_extents(window) { // Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when // decorations are disabled, but since the window becomes un-nested, it's easy to // catch. if !nested { frame_extents = FrameExtents::new(0, 0, 0, 0); } // The difference between the nested window's position and the outermost window's // position is equivalent to the frame size. In most scenarios, this is equivalent to // manually climbing the hierarchy as is done in the case below. Here's a list of // known discrepancies: // * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in // addition to a 1px semi-transparent border. The margin can be easily observed by // using a screenshot tool to get a screenshot of a selected window, and is presumably // used for drawing drop shadows. Getting window geometry information via // hierarchy-climbing results in this margin being included in both the position and // outer size, so a window positioned at (0, 0) would be reported as having a position // (-10, -8). // * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px on // all sides, and there's no additional border. // * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root. Without // decorations, there's no difference. This is presumably related to Enlightenment's // fairly unique concept of window position; it interprets positions given to // XMoveWindow as a client area position rather than a position of the overall window. FrameExtentsHeuristic { frame_extents, heuristic_path: Supported } } else if nested { // If the position value we have is for a nested window used as the client area, we'll // just climb up the hierarchy and get the geometry of the outermost window we're // nested in. let outer_window = self.climb_hierarchy(window, root).expect("Failed to climb window hierarchy"); let (outer_y, outer_width, outer_height) = { let outer_geometry = self.get_geometry(outer_window).expect("Failed to get outer window geometry"); (outer_geometry.y, outer_geometry.width, outer_geometry.height) }; // Since we have the geometry of the outermost window and the geometry of the client // area, we can figure out what's in between. let diff_x = outer_width.saturating_sub(width) as u32; let diff_y = outer_height.saturating_sub(height) as u32; let offset_y = inner_y_rel_root.saturating_sub(outer_y) as u32; let left = diff_x / 2; let right = left; let top = offset_y; let bottom = diff_y.saturating_sub(offset_y); let frame_extents = FrameExtents::new(left, right, top, bottom); FrameExtentsHeuristic { frame_extents, heuristic_path: UnsupportedNested } } else { // This is the case for xmonad and dwm, AKA the only WMs tested that supplied a // border value. This is convenient, since we can use it to get an accurate frame. let frame_extents = FrameExtents::from_border(border.into()); FrameExtentsHeuristic { frame_extents, heuristic_path: UnsupportedBordered } } } } winit-0.30.9/src/platform_impl/linux/x11/util/hint.rs000064400000000000000000000114261046102023000205440ustar 00000000000000use crate::platform::x11::WindowType; use std::sync::Arc; use super::*; #[derive(Debug)] #[allow(dead_code)] pub enum StateOperation { Remove = 0, // _NET_WM_STATE_REMOVE Add = 1, // _NET_WM_STATE_ADD Toggle = 2, // _NET_WM_STATE_TOGGLE } impl From for StateOperation { fn from(op: bool) -> Self { if op { StateOperation::Add } else { StateOperation::Remove } } } impl WindowType { pub(crate) fn as_atom(&self, xconn: &Arc) -> xproto::Atom { use self::WindowType::*; let atom_name = match *self { Desktop => _NET_WM_WINDOW_TYPE_DESKTOP, Dock => _NET_WM_WINDOW_TYPE_DOCK, Toolbar => _NET_WM_WINDOW_TYPE_TOOLBAR, Menu => _NET_WM_WINDOW_TYPE_MENU, Utility => _NET_WM_WINDOW_TYPE_UTILITY, Splash => _NET_WM_WINDOW_TYPE_SPLASH, Dialog => _NET_WM_WINDOW_TYPE_DIALOG, DropdownMenu => _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, PopupMenu => _NET_WM_WINDOW_TYPE_POPUP_MENU, Tooltip => _NET_WM_WINDOW_TYPE_TOOLTIP, Notification => _NET_WM_WINDOW_TYPE_NOTIFICATION, Combo => _NET_WM_WINDOW_TYPE_COMBO, Dnd => _NET_WM_WINDOW_TYPE_DND, Normal => _NET_WM_WINDOW_TYPE_NORMAL, }; let atoms = xconn.atoms(); atoms[atom_name] } } pub struct MotifHints { hints: MwmHints, } struct MwmHints { flags: u32, functions: u32, decorations: u32, input_mode: u32, status: u32, } #[allow(dead_code)] mod mwm { // Motif WM hints are obsolete, but still widely supported. // https://stackoverflow.com/a/1909708 pub const MWM_HINTS_FUNCTIONS: u32 = 1 << 0; pub const MWM_HINTS_DECORATIONS: u32 = 1 << 1; pub const MWM_FUNC_ALL: u32 = 1 << 0; pub const MWM_FUNC_RESIZE: u32 = 1 << 1; pub const MWM_FUNC_MOVE: u32 = 1 << 2; pub const MWM_FUNC_MINIMIZE: u32 = 1 << 3; pub const MWM_FUNC_MAXIMIZE: u32 = 1 << 4; pub const MWM_FUNC_CLOSE: u32 = 1 << 5; } impl MotifHints { pub fn new() -> MotifHints { MotifHints { hints: MwmHints { flags: 0, functions: 0, decorations: 0, input_mode: 0, status: 0 }, } } pub fn set_decorations(&mut self, decorations: bool) { self.hints.flags |= mwm::MWM_HINTS_DECORATIONS; self.hints.decorations = decorations as u32; } pub fn set_maximizable(&mut self, maximizable: bool) { if maximizable { self.add_func(mwm::MWM_FUNC_MAXIMIZE); } else { self.remove_func(mwm::MWM_FUNC_MAXIMIZE); } } fn add_func(&mut self, func: u32) { if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS != 0 { if self.hints.functions & mwm::MWM_FUNC_ALL != 0 { self.hints.functions &= !func; } else { self.hints.functions |= func; } } } fn remove_func(&mut self, func: u32) { if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS == 0 { self.hints.flags |= mwm::MWM_HINTS_FUNCTIONS; self.hints.functions = mwm::MWM_FUNC_ALL; } if self.hints.functions & mwm::MWM_FUNC_ALL != 0 { self.hints.functions |= func; } else { self.hints.functions &= !func; } } } impl Default for MotifHints { fn default() -> Self { Self::new() } } impl XConnection { pub fn get_motif_hints(&self, window: xproto::Window) -> MotifHints { let atoms = self.atoms(); let motif_hints = atoms[_MOTIF_WM_HINTS]; let mut hints = MotifHints::new(); if let Ok(props) = self.get_property::(window, motif_hints, motif_hints) { hints.hints.flags = props.first().cloned().unwrap_or(0); hints.hints.functions = props.get(1).cloned().unwrap_or(0); hints.hints.decorations = props.get(2).cloned().unwrap_or(0); hints.hints.input_mode = props.get(3).cloned().unwrap_or(0); hints.hints.status = props.get(4).cloned().unwrap_or(0); } hints } #[allow(clippy::unnecessary_cast)] pub fn set_motif_hints( &self, window: xproto::Window, hints: &MotifHints, ) -> Result, X11Error> { let atoms = self.atoms(); let motif_hints = atoms[_MOTIF_WM_HINTS]; let hints_data: [u32; 5] = [ hints.hints.flags as u32, hints.hints.functions as u32, hints.hints.decorations as u32, hints.hints.input_mode as u32, hints.hints.status as u32, ]; self.change_property( window, motif_hints, motif_hints, xproto::PropMode::REPLACE, &hints_data, ) } } winit-0.30.9/src/platform_impl/linux/x11/util/icon.rs000064400000000000000000000022071046102023000205270ustar 00000000000000#![allow(clippy::assertions_on_constants)] use super::*; use crate::icon::{Pixel, RgbaIcon, PIXEL_SIZE}; impl Pixel { pub fn to_packed_argb(&self) -> Cardinal { let mut cardinal = 0; assert!(CARDINAL_SIZE >= PIXEL_SIZE); let as_bytes = &mut cardinal as *mut _ as *mut u8; unsafe { *as_bytes.offset(0) = self.b; *as_bytes.offset(1) = self.g; *as_bytes.offset(2) = self.r; *as_bytes.offset(3) = self.a; } cardinal } } impl RgbaIcon { pub(crate) fn to_cardinals(&self) -> Vec { assert_eq!(self.rgba.len() % PIXEL_SIZE, 0); let pixel_count = self.rgba.len() / PIXEL_SIZE; assert_eq!(pixel_count, (self.width * self.height) as usize); let mut data = Vec::with_capacity(pixel_count); data.push(self.width as Cardinal); data.push(self.height as Cardinal); let pixels = self.rgba.as_ptr() as *const Pixel; for pixel_index in 0..pixel_count { let pixel = unsafe { &*pixels.add(pixel_index) }; data.push(pixel.to_packed_argb()); } data } } winit-0.30.9/src/platform_impl/linux/x11/util/input.rs000064400000000000000000000066131046102023000207430ustar 00000000000000use std::{slice, str}; use x11rb::protocol::xinput::{self, ConnectionExt as _}; use x11rb::protocol::xkb; use super::*; pub const VIRTUAL_CORE_POINTER: u16 = 2; pub const VIRTUAL_CORE_KEYBOARD: u16 = 3; // A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to // re-allocate (and make another round-trip) in the *vast* majority of cases. // To test if `lookup_utf8` works correctly, set this to 1. const TEXT_BUFFER_SIZE: usize = 1024; impl XConnection { pub fn select_xinput_events( &self, window: xproto::Window, device_id: u16, mask: xinput::XIEventMask, ) -> Result, X11Error> { self.xcb_connection() .xinput_xi_select_events(window, &[xinput::EventMask { deviceid: device_id, mask: vec![mask], }]) .map_err(Into::into) } pub fn select_xkb_events( &self, device_id: xkb::DeviceSpec, mask: xkb::EventType, ) -> Result { let mask = u16::from(mask) as _; let status = unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id as _, mask, mask) }; if status == ffi::True { self.flush_requests()?; Ok(true) } else { tracing::error!("Could not select XKB events: The XKB extension is not initialized!"); Ok(false) } } pub fn query_pointer( &self, window: xproto::Window, device_id: u16, ) -> Result { self.xcb_connection() .xinput_xi_query_pointer(window, device_id)? .reply() .map_err(Into::into) } fn lookup_utf8_inner( &self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent, buffer: *mut u8, size: usize, ) -> (ffi::KeySym, ffi::Status, c_int) { let mut keysym: ffi::KeySym = 0; let mut status: ffi::Status = 0; let count = unsafe { (self.xlib.Xutf8LookupString)( ic, key_event, buffer as *mut c_char, size as c_int, &mut keysym, &mut status, ) }; (keysym, status, count) } pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String { // `assume_init` is safe here because the array consists of `MaybeUninit` values, // which do not require initialization. let mut buffer: [MaybeUninit; TEXT_BUFFER_SIZE] = unsafe { MaybeUninit::uninit().assume_init() }; // If the buffer overflows, we'll make a new one on the heap. let mut vec; let (_, status, count) = self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len()); let bytes = if status == ffi::XBufferOverflow { vec = Vec::with_capacity(count as usize); let (_, _, new_count) = self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity()); debug_assert_eq!(count, new_count); unsafe { vec.set_len(count as usize) }; &vec[..count as usize] } else { unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) } }; str::from_utf8(bytes).unwrap_or("").to_string() } } winit-0.30.9/src/platform_impl/linux/x11/util/keys.rs000064400000000000000000000031441046102023000205530ustar 00000000000000use std::iter::Enumerate; use std::slice::Iter; use super::*; pub struct Keymap { keys: [u8; 32], } pub struct KeymapIter<'a> { iter: Enumerate>, index: usize, item: Option, } impl Keymap { pub fn iter(&self) -> KeymapIter<'_> { KeymapIter { iter: self.keys.iter().enumerate(), index: 0, item: None } } } impl<'a> IntoIterator for &'a Keymap { type IntoIter = KeymapIter<'a>; type Item = ffi::KeyCode; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl Iterator for KeymapIter<'_> { type Item = ffi::KeyCode; fn next(&mut self) -> Option { if self.item.is_none() { for (index, &item) in self.iter.by_ref() { if item != 0 { self.index = index; self.item = Some(item); break; } } } self.item.take().map(|item| { debug_assert!(item != 0); let bit = first_bit(item); if item != bit { // Remove the first bit; save the rest for further iterations self.item = Some(item ^ bit); } let shift = bit.trailing_zeros() + (self.index * 8) as u32; shift as ffi::KeyCode }) } } impl XConnection { pub fn query_keymap(&self) -> Keymap { let mut keys = [0; 32]; unsafe { (self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char); } Keymap { keys } } } fn first_bit(b: u8) -> u8 { 1 << b.trailing_zeros() } winit-0.30.9/src/platform_impl/linux/x11/util/memory.rs000064400000000000000000000016131046102023000211070ustar 00000000000000use std::ops::{Deref, DerefMut}; use super::*; pub(crate) struct XSmartPointer<'a, T> { xconn: &'a XConnection, pub ptr: *mut T, } impl<'a, T> XSmartPointer<'a, T> { // You're responsible for only passing things to this that should be XFree'd. // Returns None if ptr is null. pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option { if !ptr.is_null() { Some(XSmartPointer { xconn, ptr }) } else { None } } } impl Deref for XSmartPointer<'_, T> { type Target = T; fn deref(&self) -> &T { unsafe { &*self.ptr } } } impl DerefMut for XSmartPointer<'_, T> { fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.ptr } } } impl Drop for XSmartPointer<'_, T> { fn drop(&mut self) { unsafe { (self.xconn.xlib.XFree)(self.ptr as *mut _); } } } winit-0.30.9/src/platform_impl/linux/x11/util/mod.rs000064400000000000000000000044071046102023000203620ustar 00000000000000// Welcome to the util module, where we try to keep you from shooting yourself in the foot. // *results may vary use std::mem::{self, MaybeUninit}; use std::ops::BitAnd; use std::os::raw::*; mod client_msg; pub mod cookie; mod cursor; mod geometry; mod hint; mod icon; mod input; pub mod keys; pub(crate) mod memory; mod mouse; mod randr; mod window_property; mod wm; mod xmodmap; pub use self::cursor::*; pub use self::geometry::*; pub use self::hint::*; pub use self::input::*; pub use self::mouse::*; pub use self::window_property::*; pub use self::wm::*; pub use self::xmodmap::ModifierKeymap; use super::atoms::*; use super::{ffi, VoidCookie, X11Error, XConnection, XError}; use x11rb::protocol::xproto::{self, ConnectionExt as _}; pub fn maybe_change(field: &mut Option, value: T) -> bool { let wrapped = Some(value); if *field != wrapped { *field = wrapped; true } else { false } } pub fn has_flag(bitset: T, flag: T) -> bool where T: Copy + PartialEq + BitAnd, { bitset & flag == flag } impl XConnection { // This is impoartant, so pay attention! // Xlib has an output buffer, and tries to hide the async nature of X from you. // This buffer contains the requests you make, and is flushed under various circumstances: // 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed" // 2. `XFlush` explicitly flushes // 3. `XSync` flushes and blocks until all requests are responded to // 4. Calls that have a return dependent on a response (i.e. `XGetWindowProperty`) sync // internally. When in doubt, check the X11 source; if a function calls `_XReply`, it flushes // and waits. // All util functions that abstract an async function will return a `Flusher`. pub fn flush_requests(&self) -> Result<(), XError> { unsafe { (self.xlib.XFlush)(self.display) }; // println!("XFlush"); // This isn't necessarily a useful time to check for errors (since our request hasn't // necessarily been processed yet) self.check_errors() } pub fn sync_with_server(&self) -> Result<(), XError> { unsafe { (self.xlib.XSync)(self.display, ffi::False) }; // println!("XSync"); self.check_errors() } } winit-0.30.9/src/platform_impl/linux/x11/util/modifiers.rs000064400000000000000000000126731046102023000215700ustar 00000000000000use std::{collections::HashMap, slice}; use super::*; use crate::event::{ElementState, ModifiersState}; // Offsets within XModifierKeymap to each set of keycodes. // We are only interested in Shift, Control, Alt, and Logo. // // There are 8 sets total. The order of keycode sets is: // Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5 // // https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html const SHIFT_OFFSET: usize = 0; const CONTROL_OFFSET: usize = 2; const ALT_OFFSET: usize = 3; const LOGO_OFFSET: usize = 6; const NUM_MODS: usize = 8; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Modifier { Alt, Ctrl, Shift, Logo, } #[derive(Debug, Default)] pub(crate) struct ModifierKeymap { // Maps keycodes to modifiers keys: HashMap, } #[derive(Clone, Debug, Default)] pub(crate) struct ModifierKeyState { // Contains currently pressed modifier keys and their corresponding modifiers keys: HashMap, state: ModifiersState, } impl ModifierKeymap { pub fn new() -> ModifierKeymap { ModifierKeymap::default() } pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option { self.keys.get(&keycode).cloned() } pub fn reset_from_x_connection(&mut self, xconn: &XConnection) { { let keymap = xconn.xcb_connection().get_modifier_mapping().expect("get_modifier_mapping failed").reply().expect("get_modifier_mapping failed"); if keymap.is_null() { panic!("failed to allocate XModifierKeymap"); } self.reset_from_x_keymap(&*keymap); (xconn.xlib.XFreeModifiermap)(keymap); } } pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) { let keys_per_mod = keymap.max_keypermod as usize; let keys = unsafe { slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS) }; self.keys.clear(); self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift); self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl); self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt); self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo); } fn read_x_keys( &mut self, keys: &[ffi::KeyCode], offset: usize, keys_per_mod: usize, modifier: Modifier, ) { let start = offset * keys_per_mod; let end = start + keys_per_mod; for &keycode in &keys[start..end] { if keycode != 0 { self.keys.insert(keycode, modifier); } } } } impl ModifierKeyState { pub fn update_keymap(&mut self, mods: &ModifierKeymap) { self.keys.retain(|k, v| { if let Some(m) = mods.get_modifier(*k) { *v = m; true } else { false } }); self.reset_state(); } pub fn update_state( &mut self, state: &ModifiersState, except: Option, ) -> Option { let mut new_state = *state; match except { Some(Modifier::Alt) => new_state.set(ModifiersState::ALT, self.state.alt()), Some(Modifier::Ctrl) => new_state.set(ModifiersState::CTRL, self.state.ctrl()), Some(Modifier::Shift) => new_state.set(ModifiersState::SHIFT, self.state.shift()), Some(Modifier::Logo) => new_state.set(ModifiersState::LOGO, self.state.logo()), None => (), } if self.state == new_state { None } else { self.keys.retain(|_k, v| get_modifier(&new_state, *v)); self.state = new_state; Some(new_state) } } pub fn modifiers(&self) -> ModifiersState { self.state } pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) { match state { ElementState::Pressed => self.key_press(keycode, modifier), ElementState::Released => self.key_release(keycode), } } pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) { self.keys.insert(keycode, modifier); set_modifier(&mut self.state, modifier, true); } pub fn key_release(&mut self, keycode: ffi::KeyCode) { if let Some(modifier) = self.keys.remove(&keycode) { if !self.keys.values().any(|&m| m == modifier) { set_modifier(&mut self.state, modifier, false); } } } fn reset_state(&mut self) { let mut new_state = ModifiersState::default(); for &m in self.keys.values() { set_modifier(&mut new_state, m, true); } self.state = new_state; } } fn get_modifier(state: &ModifiersState, modifier: Modifier) -> bool { match modifier { Modifier::Alt => state.alt(), Modifier::Ctrl => state.ctrl(), Modifier::Shift => state.shift(), Modifier::Logo => state.logo(), } } fn set_modifier(state: &mut ModifiersState, modifier: Modifier, value: bool) { match modifier { Modifier::Alt => state.set(ModifiersState::ALT, value), Modifier::Ctrl => state.set(ModifiersState::CTRL, value), Modifier::Shift => state.set(ModifiersState::SHIFT, value), Modifier::Logo => state.set(ModifiersState::LOGO, value), } } winit-0.30.9/src/platform_impl/linux/x11/util/mouse.rs000064400000000000000000000020501046102023000207230ustar 00000000000000//! Utilities for handling mouse events. /// Recorded mouse delta designed to filter out noise. pub struct Delta { x: T, y: T, } impl Default for Delta { fn default() -> Self { Self { x: Default::default(), y: Default::default() } } } impl Delta { pub(crate) fn set_x(&mut self, x: T) { self.x = x; } pub(crate) fn set_y(&mut self, y: T) { self.y = y; } } macro_rules! consume { ($this:expr, $ty:ty) => {{ let this = $this; let (x, y) = match (this.x.abs() < <$ty>::EPSILON, this.y.abs() < <$ty>::EPSILON) { (true, true) => return None, (false, true) => (this.x, 0.0), (true, false) => (0.0, this.y), (false, false) => (this.x, this.y), }; Some((x, y)) }}; } impl Delta { pub(crate) fn consume(self) -> Option<(f32, f32)> { consume!(self, f32) } } impl Delta { pub(crate) fn consume(self) -> Option<(f64, f64)> { consume!(self, f64) } } winit-0.30.9/src/platform_impl/linux/x11/util/randr.rs000064400000000000000000000144631046102023000207140ustar 00000000000000use std::str::FromStr; use std::{env, str}; use super::*; use crate::dpi::validate_scale_factor; use crate::platform_impl::platform::x11::{monitor, VideoModeHandle}; use tracing::warn; use x11rb::protocol::randr::{self, ConnectionExt as _}; /// Represents values of `WINIT_HIDPI_FACTOR`. pub enum EnvVarDPI { Randr, Scale(f64), NotSet, } pub fn calc_dpi_factor( (width_px, height_px): (u32, u32), (width_mm, height_mm): (u64, u64), ) -> f64 { // See http://xpra.org/trac/ticket/728 for more information. if width_mm == 0 || height_mm == 0 { warn!("XRandR reported that the display's 0mm in size, which is certifiably insane"); return 1.0; } let ppmm = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt(); // Quantize 1/12 step size let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0); assert!(validate_scale_factor(dpi_factor)); if dpi_factor <= 20. { dpi_factor } else { 1. } } impl XConnection { // Retrieve DPI from Xft.dpi property pub fn get_xft_dpi(&self) -> Option { // Try to get it from XSETTINGS first. if let Some(xsettings_screen) = self.xsettings_screen() { match self.xsettings_dpi(xsettings_screen) { Ok(Some(dpi)) => return Some(dpi), Ok(None) => {}, Err(err) => { tracing::warn!("failed to fetch XSettings: {err}"); }, } } self.database().get_string("Xft.dpi", "").and_then(|s| f64::from_str(s).ok()) } pub fn get_output_info( &self, resources: &monitor::ScreenResources, crtc: &randr::GetCrtcInfoReply, ) -> Option<(String, f64, Vec)> { let output_info = match self .xcb_connection() .randr_get_output_info(crtc.outputs[0], x11rb::CURRENT_TIME) .map_err(X11Error::from) .and_then(|r| r.reply().map_err(X11Error::from)) { Ok(output_info) => output_info, Err(err) => { warn!("Failed to get output info: {:?}", err); return None; }, }; let bit_depth = self.default_root().root_depth; let output_modes = &output_info.modes; let resource_modes = resources.modes(); let modes = resource_modes .iter() // XRROutputInfo contains an array of mode ids that correspond to // modes in the array in XRRScreenResources .filter(|x| output_modes.iter().any(|id| x.id == *id)) .map(|mode| { VideoModeHandle { size: (mode.width.into(), mode.height.into()), refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode) .unwrap_or(0), bit_depth: bit_depth as u16, native_mode: mode.id, // This is populated in `MonitorHandle::video_modes` as the // video mode is returned to the user monitor: None, } }) .collect(); let name = match str::from_utf8(&output_info.name) { Ok(name) => name.to_owned(), Err(err) => { warn!("Failed to get output name: {:?}", err); return None; }, }; // Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok(); if deprecated_dpi_override.is_some() { warn!( "The WINIT_HIDPI_FACTOR environment variable is deprecated; use \ WINIT_X11_SCALE_FACTOR" ) } let dpi_env = env::var("WINIT_X11_SCALE_FACTOR").ok().map_or_else( || EnvVarDPI::NotSet, |var| { if var.to_lowercase() == "randr" { EnvVarDPI::Randr } else if let Ok(dpi) = f64::from_str(&var) { EnvVarDPI::Scale(dpi) } else if var.is_empty() { EnvVarDPI::NotSet } else { panic!( "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal \ floats greater than 0, or `randr`. Got `{var}`" ); } }, ); let scale_factor = match dpi_env { EnvVarDPI::Randr => calc_dpi_factor( (crtc.width.into(), crtc.height.into()), (output_info.mm_width as _, output_info.mm_height as _), ), EnvVarDPI::Scale(dpi_override) => { if !validate_scale_factor(dpi_override) { panic!( "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal \ floats greater than 0, or `randr`. Got `{dpi_override}`", ); } dpi_override }, EnvVarDPI::NotSet => { if let Some(dpi) = self.get_xft_dpi() { dpi / 96. } else { calc_dpi_factor( (crtc.width.into(), crtc.height.into()), (output_info.mm_width as _, output_info.mm_height as _), ) } }, }; Some((name, scale_factor, modes)) } pub fn set_crtc_config( &self, crtc_id: randr::Crtc, mode_id: randr::Mode, ) -> Result<(), X11Error> { let crtc = self.xcb_connection().randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?.reply()?; self.xcb_connection() .randr_set_crtc_config( crtc_id, crtc.timestamp, x11rb::CURRENT_TIME, crtc.x, crtc.y, mode_id, crtc.rotation, &crtc.outputs, )? .reply() .map(|_| ()) .map_err(Into::into) } pub fn get_crtc_mode(&self, crtc_id: randr::Crtc) -> Result { Ok(self.xcb_connection().randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?.reply()?.mode) } } winit-0.30.9/src/platform_impl/linux/x11/util/window_property.rs000064400000000000000000000134721046102023000230600ustar 00000000000000use std::error::Error; use std::fmt; use std::sync::Arc; use bytemuck::{NoUninit, Pod}; use x11rb::connection::Connection; use x11rb::errors::ReplyError; use super::*; pub const CARDINAL_SIZE: usize = mem::size_of::(); pub type Cardinal = u32; #[derive(Debug, Clone)] pub enum GetPropertyError { X11rbError(Arc), TypeMismatch(xproto::Atom), FormatMismatch(c_int), } impl GetPropertyError { pub fn is_actual_property_type(&self, t: xproto::Atom) -> bool { if let GetPropertyError::TypeMismatch(actual_type) = *self { actual_type == t } else { false } } } impl> From for GetPropertyError { fn from(e: T) -> Self { Self::X11rbError(Arc::new(e.into())) } } impl fmt::Display for GetPropertyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { GetPropertyError::X11rbError(err) => err.fmt(f), GetPropertyError::TypeMismatch(err) => write!(f, "type mismatch: {err}"), GetPropertyError::FormatMismatch(err) => write!(f, "format mismatch: {err}"), } } } impl Error for GetPropertyError {} // Number of 32-bit chunks to retrieve per iteration of get_property's inner loop. // To test if `get_property` works correctly, set this to 1. const PROPERTY_BUFFER_SIZE: u32 = 1024; // 4k of RAM ought to be enough for anyone! impl XConnection { pub fn get_property( &self, window: xproto::Window, property: xproto::Atom, property_type: xproto::Atom, ) -> Result, GetPropertyError> { let mut iter = PropIterator::new(self.xcb_connection(), window, property, property_type); let mut data = vec![]; loop { if !iter.next_window(&mut data)? { break; } } Ok(data) } pub fn change_property<'a, T: NoUninit>( &'a self, window: xproto::Window, property: xproto::Atom, property_type: xproto::Atom, mode: xproto::PropMode, new_value: &[T], ) -> Result, X11Error> { assert!([1usize, 2, 4].contains(&mem::size_of::())); self.xcb_connection() .change_property( mode, window, property, property_type, (mem::size_of::() * 8) as u8, new_value.len().try_into().expect("too many items for property"), bytemuck::cast_slice::(new_value), ) .map_err(Into::into) } } /// An iterator over the "windows" of the property that we are fetching. struct PropIterator<'a, C: ?Sized, T> { /// Handle to the connection. conn: &'a C, /// The window that we're fetching the property from. window: xproto::Window, /// The property that we're fetching. property: xproto::Atom, /// The type of the property that we're fetching. property_type: xproto::Atom, /// The offset of the next window, in 32-bit chunks. offset: u32, /// The format of the type. format: u8, /// Keep a reference to `T`. _phantom: std::marker::PhantomData, } impl<'a, C: Connection + ?Sized, T: Pod> PropIterator<'a, C, T> { /// Create a new property iterator. fn new( conn: &'a C, window: xproto::Window, property: xproto::Atom, property_type: xproto::Atom, ) -> Self { let format = match mem::size_of::() { 1 => 8, 2 => 16, 4 => 32, _ => unreachable!(), }; Self { conn, window, property, property_type, offset: 0, format, _phantom: Default::default(), } } /// Get the next window and append it to `data`. /// /// Returns whether there are more windows to fetch. fn next_window(&mut self, data: &mut Vec) -> Result { // Send the request and wait for the reply. let reply = self .conn .get_property( false, self.window, self.property, self.property_type, self.offset, PROPERTY_BUFFER_SIZE, )? .reply()?; // Make sure that the reply is of the correct type. if reply.type_ != self.property_type { return Err(GetPropertyError::TypeMismatch(reply.type_)); } // Make sure that the reply is of the correct format. if reply.format != self.format { return Err(GetPropertyError::FormatMismatch(reply.format.into())); } // Append the data to the output. if mem::size_of::() == 1 && mem::align_of::() == 1 { // We can just do a bytewise append. data.extend_from_slice(bytemuck::cast_slice(&reply.value)); } else { // Rust's borrowing and types system makes this a bit tricky. // // We need to make sure that the data is properly aligned. Unfortunately the best // safe way to do this is to copy the data to another buffer and then append. // // TODO(notgull): It may be worth it to use `unsafe` to copy directly from // `reply.value` to `data`; check if this is faster. Use benchmarks! let old_len = data.len(); let added_len = reply.value.len() / mem::size_of::(); data.resize(old_len + added_len, T::zeroed()); bytemuck::cast_slice_mut::(&mut data[old_len..]).copy_from_slice(&reply.value); } // Check `bytes_after` to see if there are more windows to fetch. self.offset += PROPERTY_BUFFER_SIZE; Ok(reply.bytes_after != 0) } } winit-0.30.9/src/platform_impl/linux/x11/util/wm.rs000064400000000000000000000121521046102023000202220ustar 00000000000000use std::sync::Mutex; use super::*; // https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#idm46075117309248 pub const MOVERESIZE_TOPLEFT: isize = 0; pub const MOVERESIZE_TOP: isize = 1; pub const MOVERESIZE_TOPRIGHT: isize = 2; pub const MOVERESIZE_RIGHT: isize = 3; pub const MOVERESIZE_BOTTOMRIGHT: isize = 4; pub const MOVERESIZE_BOTTOM: isize = 5; pub const MOVERESIZE_BOTTOMLEFT: isize = 6; pub const MOVERESIZE_LEFT: isize = 7; pub const MOVERESIZE_MOVE: isize = 8; // This info is global to the window manager. static SUPPORTED_HINTS: Mutex> = Mutex::new(Vec::new()); static WM_NAME: Mutex> = Mutex::new(None); pub fn hint_is_supported(hint: xproto::Atom) -> bool { (*SUPPORTED_HINTS.lock().unwrap()).contains(&hint) } pub fn wm_name_is_one_of(names: &[&str]) -> bool { if let Some(ref name) = *WM_NAME.lock().unwrap() { names.contains(&name.as_str()) } else { false } } impl XConnection { pub fn update_cached_wm_info(&self, root: xproto::Window) { *SUPPORTED_HINTS.lock().unwrap() = self.get_supported_hints(root); *WM_NAME.lock().unwrap() = self.get_wm_name(root); } fn get_supported_hints(&self, root: xproto::Window) -> Vec { let atoms = self.atoms(); let supported_atom = atoms[_NET_SUPPORTED]; self.get_property(root, supported_atom, xproto::Atom::from(xproto::AtomEnum::ATOM)) .unwrap_or_else(|_| Vec::with_capacity(0)) } #[allow(clippy::useless_conversion)] fn get_wm_name(&self, root: xproto::Window) -> Option { let atoms = self.atoms(); let check_atom = atoms[_NET_SUPPORTING_WM_CHECK]; let wm_name_atom = atoms[_NET_WM_NAME]; // Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite // it working and being supported. This has been reported upstream, but due to the // inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK // regardless of whether or not the WM claims to support it. // // Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be // fixed in 0.72. // if !supported_hints.contains(&check_atom) { // return None; // } // IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless // provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine. // if !supported_hints.contains(&wm_name_atom) { // return None; // } // Of the WMs tested, only xmonad and dwm fail to provide a WM name. // Querying this property on the root window will give us the ID of a child window created // by the WM. let root_window_wm_check = { let result = self.get_property::( root, check_atom, xproto::Atom::from(xproto::AtomEnum::WINDOW), ); let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned()); wm_check? }; // Querying the same property on the child window we were given, we should get this child // window's ID again. let child_window_wm_check = { let result = self.get_property::( root_window_wm_check.into(), check_atom, xproto::Atom::from(xproto::AtomEnum::WINDOW), ); let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned()); wm_check? }; // These values should be the same. if root_window_wm_check != child_window_wm_check { return None; } // All of that work gives us a window ID that we can get the WM name from. let wm_name = { let atoms = self.atoms(); let utf8_string_atom = atoms[UTF8_STRING]; let result = self.get_property(root_window_wm_check.into(), wm_name_atom, utf8_string_atom); // IceWM requires this. IceWM was also the only WM tested that returns a null-terminated // string. For more fun trivia, IceWM is also unique in including version and uname // information in this string (this means you'll have to be careful if you want to match // against it, though). // The unofficial 1.4 fork of IceWM still includes the extra details, but properly // returns a UTF8 string that isn't null-terminated. let no_utf8 = if let Err(ref err) = result { err.is_actual_property_type(xproto::Atom::from(xproto::AtomEnum::STRING)) } else { false }; if no_utf8 { self.get_property( root_window_wm_check.into(), wm_name_atom, xproto::Atom::from(xproto::AtomEnum::STRING), ) } else { result } } .ok(); wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok()) } } winit-0.30.9/src/platform_impl/linux/x11/util/xmodmap.rs000064400000000000000000000031111046102023000212370ustar 00000000000000use std::collections::HashSet; use std::slice; use x11_dl::xlib::{KeyCode as XKeyCode, XModifierKeymap}; // Offsets within XModifierKeymap to each set of keycodes. // We are only interested in Shift, Control, Alt, and Logo. // // There are 8 sets total. The order of keycode sets is: // Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5 // // https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html const NUM_MODS: usize = 8; /// Track which keys are modifiers, so we can properly replay them when they were filtered. #[derive(Debug, Default)] pub struct ModifierKeymap { // Maps keycodes to modifiers modifiers: HashSet, } impl ModifierKeymap { pub fn new() -> ModifierKeymap { ModifierKeymap::default() } pub fn is_modifier(&self, keycode: XKeyCode) -> bool { self.modifiers.contains(&keycode) } pub fn reload_from_x_connection(&mut self, xconn: &super::XConnection) { unsafe { let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display); if keymap.is_null() { return; } self.reset_from_x_keymap(&*keymap); (xconn.xlib.XFreeModifiermap)(keymap); } } fn reset_from_x_keymap(&mut self, keymap: &XModifierKeymap) { let keys_per_mod = keymap.max_keypermod as usize; let keys = unsafe { slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS) }; self.modifiers.clear(); for key in keys { self.modifiers.insert(*key); } } } winit-0.30.9/src/platform_impl/linux/x11/window.rs000064400000000000000000002202131046102023000201300ustar 00000000000000use std::ffi::CString; use std::mem::replace; use std::os::raw::*; use std::path::Path; use std::sync::{Arc, Mutex, MutexGuard}; use std::{cmp, env}; use tracing::{debug, info, warn}; use x11rb::connection::Connection; use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification}; use x11rb::protocol::shape::SK; use x11rb::protocol::xfixes::{ConnectionExt, RegionWrapper}; use x11rb::protocol::xproto::{self, ConnectionExt as _, Rectangle}; use x11rb::protocol::{randr, xinput}; use crate::cursor::{Cursor, CustomCursor as RootCustomCursor}; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::event::{Event, InnerSizeWriter, WindowEvent}; use crate::event_loop::AsyncRequestSerial; use crate::platform::x11::WindowType; use crate::platform_impl::x11::atoms::*; use crate::platform_impl::x11::{ xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender, X11Error, }; use crate::platform_impl::{ Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor, PlatformIcon, VideoModeHandle as PlatformVideoModeHandle, }; use crate::window::{ CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }; use super::util::{self, SelectedCursor}; use super::{ ffi, ActiveEventLoop, CookieResultExt, ImeRequest, ImeSender, VoidCookie, WindowId, XConnection, }; #[derive(Debug)] pub struct SharedState { pub cursor_pos: Option<(f64, f64)>, pub size: Option<(u32, u32)>, pub position: Option<(i32, i32)>, pub inner_position: Option<(i32, i32)>, pub inner_position_rel_parent: Option<(i32, i32)>, pub is_resizable: bool, pub is_decorated: bool, pub last_monitor: X11MonitorHandle, pub dpi_adjusted: Option<(u32, u32)>, pub(crate) fullscreen: Option, // Set when application calls `set_fullscreen` when window is not visible pub(crate) desired_fullscreen: Option>, // Used to restore position after exiting fullscreen pub restore_position: Option<(i32, i32)>, // Used to restore video mode after exiting fullscreen pub desktop_video_mode: Option<(randr::Crtc, randr::Mode)>, pub frame_extents: Option, pub min_inner_size: Option, pub max_inner_size: Option, pub resize_increments: Option, pub base_size: Option, pub visibility: Visibility, pub has_focus: bool, // Use `Option` to not apply hittest logic when it was never requested. pub cursor_hittest: Option, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Visibility { No, Yes, // Waiting for VisibilityNotify YesWait, } impl SharedState { fn new(last_monitor: X11MonitorHandle, window_attributes: &WindowAttributes) -> Mutex { let visibility = if window_attributes.visible { Visibility::YesWait } else { Visibility::No }; Mutex::new(SharedState { last_monitor, visibility, is_resizable: window_attributes.resizable, is_decorated: window_attributes.decorations, cursor_pos: None, size: None, position: None, inner_position: None, inner_position_rel_parent: None, dpi_adjusted: None, fullscreen: None, desired_fullscreen: None, restore_position: None, desktop_video_mode: None, frame_extents: None, min_inner_size: None, max_inner_size: None, resize_increments: None, base_size: None, has_focus: false, cursor_hittest: None, }) } } unsafe impl Send for UnownedWindow {} unsafe impl Sync for UnownedWindow {} pub struct UnownedWindow { pub(crate) xconn: Arc, // never changes xwindow: xproto::Window, // never changes #[allow(dead_code)] visual: u32, // never changes root: xproto::Window, // never changes #[allow(dead_code)] screen_id: i32, // never changes selected_cursor: Mutex, cursor_grabbed_mode: Mutex, #[allow(clippy::mutex_atomic)] cursor_visible: Mutex, ime_sender: Mutex, pub shared_state: Mutex, redraw_sender: WakeSender, activation_sender: WakeSender, } macro_rules! leap { ($e:expr) => { match $e { Ok(x) => x, Err(err) => return Err(os_error!(OsError::XError(X11Error::from(err).into()))), } }; } impl UnownedWindow { #[allow(clippy::unnecessary_cast)] pub(crate) fn new( event_loop: &ActiveEventLoop, window_attrs: WindowAttributes, ) -> Result { let xconn = &event_loop.xconn; let atoms = xconn.atoms(); let screen_id = match window_attrs.platform_specific.x11.screen_id { Some(id) => id, None => xconn.default_screen_index() as c_int, }; let screen = { let screen_id_usize = usize::try_from(screen_id) .map_err(|_| os_error!(OsError::Misc("screen id must be non-negative")))?; xconn.xcb_connection().setup().roots.get(screen_id_usize).ok_or(os_error!( OsError::Misc("requested screen id not present in server's response") ))? }; #[cfg(feature = "rwh_06")] let root = match window_attrs.parent_window.as_ref().map(|handle| handle.0) { Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window, Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(), Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"), None => screen.root, }; #[cfg(not(feature = "rwh_06"))] let root = event_loop.root; let mut monitors = leap!(xconn.available_monitors()); let guessed_monitor = if monitors.is_empty() { X11MonitorHandle::dummy() } else { xconn .query_pointer(root, util::VIRTUAL_CORE_POINTER) .ok() .and_then(|pointer_state| { let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64); for i in 0..monitors.len() { if monitors[i].rect.contains_point(x, y) { return Some(monitors.swap_remove(i)); } } None }) .unwrap_or_else(|| monitors.swap_remove(0)) }; let scale_factor = guessed_monitor.scale_factor(); info!("Guessed window scale factor: {}", scale_factor); let max_inner_size: Option<(u32, u32)> = window_attrs.max_inner_size.map(|size| size.to_physical::(scale_factor).into()); let min_inner_size: Option<(u32, u32)> = window_attrs.min_inner_size.map(|size| size.to_physical::(scale_factor).into()); let position = window_attrs.position.map(|position| position.to_physical::(scale_factor)); let dimensions = { // x11 only applies constraints when the window is actively resized // by the user, so we have to manually apply the initial constraints let mut dimensions: (u32, u32) = window_attrs .inner_size .map(|size| size.to_physical::(scale_factor)) .or_else(|| Some((800, 600).into())) .map(Into::into) .unwrap(); if let Some(max) = max_inner_size { dimensions.0 = cmp::min(dimensions.0, max.0); dimensions.1 = cmp::min(dimensions.1, max.1); } if let Some(min) = min_inner_size { dimensions.0 = cmp::max(dimensions.0, min.0); dimensions.1 = cmp::max(dimensions.1, min.1); } debug!("Calculated physical dimensions: {}x{}", dimensions.0, dimensions.1); dimensions }; // An iterator over the visuals matching screen id combined with their depths. let mut all_visuals = screen .allowed_depths .iter() .flat_map(|depth| depth.visuals.iter().map(move |visual| (visual, depth.depth))); // creating let (visualtype, depth, require_colormap) = match window_attrs.platform_specific.x11.visual_id { Some(vi) => { // Find this specific visual. let (visualtype, depth) = all_visuals.find(|(visual, _)| visual.visual_id == vi).ok_or_else( || os_error!(OsError::XError(X11Error::NoSuchVisual(vi).into())), )?; (Some(visualtype), depth, true) }, None if window_attrs.transparent => { // Find a suitable visual, true color with 32 bits of depth. all_visuals .find_map(|(visual, depth)| { (depth == 32 && visual.class == xproto::VisualClass::TRUE_COLOR) .then_some((Some(visual), depth, true)) }) .unwrap_or_else(|| { debug!( "Could not set transparency, because XMatchVisualInfo returned \ zero for the required parameters" ); (None as _, x11rb::COPY_FROM_PARENT as _, false) }) }, _ => (None, x11rb::COPY_FROM_PARENT as _, false), }; let mut visual = visualtype.map_or(x11rb::COPY_FROM_PARENT, |v| v.visual_id); let window_attributes = { use xproto::EventMask; let mut aux = xproto::CreateWindowAux::new(); let event_mask = EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY | EventMask::VISIBILITY_CHANGE | EventMask::KEY_PRESS | EventMask::KEY_RELEASE | EventMask::KEYMAP_STATE | EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE | EventMask::POINTER_MOTION | EventMask::PROPERTY_CHANGE; aux = aux.event_mask(event_mask).border_pixel(0); if window_attrs.platform_specific.x11.override_redirect { aux = aux.override_redirect(true as u32); } // Add a colormap if needed. let colormap_visual = match window_attrs.platform_specific.x11.visual_id { Some(vi) => Some(vi), None if require_colormap => Some(visual), _ => None, }; if let Some(visual) = colormap_visual { let colormap = leap!(xconn.xcb_connection().generate_id()); leap!(xconn.xcb_connection().create_colormap( xproto::ColormapAlloc::NONE, colormap, root, visual, )); aux = aux.colormap(colormap); } else { aux = aux.colormap(0); } aux }; // Figure out the window's parent. let parent = window_attrs.platform_specific.x11.embed_window.unwrap_or(root); // finally creating the window let xwindow = { let (x, y) = position.map_or((0, 0), Into::into); let wid = leap!(xconn.xcb_connection().generate_id()); let result = xconn.xcb_connection().create_window( depth, wid, parent, x, y, dimensions.0.try_into().unwrap(), dimensions.1.try_into().unwrap(), 0, xproto::WindowClass::INPUT_OUTPUT, visual, &window_attributes, ); leap!(leap!(result).check()); wid }; // The COPY_FROM_PARENT is a special value for the visual used to copy // the visual from the parent window, thus we have to query the visual // we've got when we built the window above. if visual == x11rb::COPY_FROM_PARENT { visual = leap!(leap!(xconn .xcb_connection() .get_window_attributes(xwindow as xproto::Window)) .reply()) .visual; } #[allow(clippy::mutex_atomic)] let mut window = UnownedWindow { xconn: Arc::clone(xconn), xwindow: xwindow as xproto::Window, visual, root, screen_id, selected_cursor: Default::default(), cursor_grabbed_mode: Mutex::new(CursorGrabMode::None), cursor_visible: Mutex::new(true), ime_sender: Mutex::new(event_loop.ime_sender.clone()), shared_state: SharedState::new(guessed_monitor, &window_attrs), redraw_sender: event_loop.redraw_sender.clone(), activation_sender: event_loop.activation_sender.clone(), }; // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window // title to determine placement/etc., so doing this after mapping would cause the WM to // act on the wrong title state. leap!(window.set_title_inner(&window_attrs.title)).ignore_error(); leap!(window.set_decorations_inner(window_attrs.decorations)).ignore_error(); if let Some(theme) = window_attrs.preferred_theme { leap!(window.set_theme_inner(Some(theme))).ignore_error(); } // Embed the window if needed. if window_attrs.platform_specific.x11.embed_window.is_some() { window.embed_window()?; } { // Enable drag and drop (TODO: extend API to make this toggleable) { let dnd_aware_atom = atoms[XdndAware]; let version = &[5u32]; // Latest version; hasn't changed since 2002 leap!(xconn.change_property( window.xwindow, dnd_aware_atom, u32::from(xproto::AtomEnum::ATOM), xproto::PropMode::REPLACE, version, )) .ignore_error(); } // WM_CLASS must be set *before* mapping the window, as per ICCCM! { let (instance, class) = if let Some(name) = window_attrs.platform_specific.name { (name.instance, name.general) } else { let class = env::args_os() .next() .as_ref() // Default to the name of the binary (via argv[0]) .and_then(|path| Path::new(path).file_name()) .and_then(|bin_name| bin_name.to_str()) .map(|bin_name| bin_name.to_owned()) .unwrap_or_else(|| window_attrs.title.clone()); // This environment variable is extraordinarily unlikely to actually be used... let instance = env::var("RESOURCE_NAME").ok().unwrap_or_else(|| class.clone()); (instance, class) }; let class = format!("{instance}\0{class}\0"); leap!(xconn.change_property( window.xwindow, xproto::Atom::from(xproto::AtomEnum::WM_CLASS), xproto::Atom::from(xproto::AtomEnum::STRING), xproto::PropMode::REPLACE, class.as_bytes(), )) .ignore_error(); } if let Some(flusher) = leap!(window.set_pid()) { flusher.ignore_error() } leap!(window.set_window_types(window_attrs.platform_specific.x11.x11_window_types)) .ignore_error(); // Set size hints. let mut min_inner_size = window_attrs.min_inner_size.map(|size| size.to_physical::(scale_factor)); let mut max_inner_size = window_attrs.max_inner_size.map(|size| size.to_physical::(scale_factor)); if !window_attrs.resizable { if util::wm_name_is_one_of(&["Xfwm4"]) { warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4"); } else { max_inner_size = Some(dimensions.into()); min_inner_size = Some(dimensions.into()); } } let shared_state = window.shared_state.get_mut().unwrap(); shared_state.min_inner_size = min_inner_size.map(Into::into); shared_state.max_inner_size = max_inner_size.map(Into::into); shared_state.resize_increments = window_attrs.resize_increments; shared_state.base_size = window_attrs.platform_specific.x11.base_size; let normal_hints = WmSizeHints { position: position.map(|PhysicalPosition { x, y }| { (WmSizeHintsSpecification::UserSpecified, x, y) }), size: Some(( WmSizeHintsSpecification::UserSpecified, cast_dimension_to_hint(dimensions.0), cast_dimension_to_hint(dimensions.1), )), max_size: max_inner_size.map(cast_physical_size_to_hint), min_size: min_inner_size.map(cast_physical_size_to_hint), size_increment: window_attrs .resize_increments .map(|size| cast_size_to_hint(size, scale_factor)), base_size: window_attrs .platform_specific .x11 .base_size .map(|size| cast_size_to_hint(size, scale_factor)), aspect: None, win_gravity: None, }; leap!(leap!(normal_hints.set( xconn.xcb_connection(), window.xwindow as xproto::Window, xproto::AtomEnum::WM_NORMAL_HINTS, )) .check()); // Set window icons if let Some(icon) = window_attrs.window_icon { leap!(window.set_icon_inner(icon.inner)).ignore_error(); } // Opt into handling window close let result = xconn.xcb_connection().change_property( xproto::PropMode::REPLACE, window.xwindow, atoms[WM_PROTOCOLS], xproto::AtomEnum::ATOM, 32, 2, bytemuck::cast_slice::(&[ atoms[WM_DELETE_WINDOW], atoms[_NET_WM_PING], ]), ); leap!(result).ignore_error(); // Select XInput2 events let mask = xinput::XIEventMask::MOTION | xinput::XIEventMask::BUTTON_PRESS | xinput::XIEventMask::BUTTON_RELEASE | xinput::XIEventMask::ENTER | xinput::XIEventMask::LEAVE | xinput::XIEventMask::FOCUS_IN | xinput::XIEventMask::FOCUS_OUT | xinput::XIEventMask::TOUCH_BEGIN | xinput::XIEventMask::TOUCH_UPDATE | xinput::XIEventMask::TOUCH_END; leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask)) .ignore_error(); // Set visibility (map window) if window_attrs.visible { leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error(); leap!(xconn.xcb_connection().configure_window( xwindow, &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE) )) .ignore_error(); } // Attempt to make keyboard input repeat detectable unsafe { let mut supported_ptr = ffi::False; (xconn.xlib.XkbSetDetectableAutoRepeat)( xconn.display, ffi::True, &mut supported_ptr, ); if supported_ptr == ffi::False { return Err(os_error!(OsError::Misc("`XkbSetDetectableAutoRepeat` failed"))); } } // Try to create input context for the window. if let Some(ime) = event_loop.ime.as_ref() { let result = ime.borrow_mut().create_context(window.xwindow as ffi::Window, false); leap!(result); } // These properties must be set after mapping if window_attrs.maximized { leap!(window.set_maximized_inner(window_attrs.maximized)).ignore_error(); } if window_attrs.fullscreen.is_some() { if let Some(flusher) = leap!(window .set_fullscreen_inner(window_attrs.fullscreen.clone().map(Into::into))) { flusher.ignore_error() } if let Some(PhysicalPosition { x, y }) = position { let shared_state = window.shared_state.get_mut().unwrap(); shared_state.restore_position = Some((x, y)); } } leap!(window.set_window_level_inner(window_attrs.window_level)).ignore_error(); } window.set_cursor(window_attrs.cursor); // Remove the startup notification if we have one. if let Some(startup) = window_attrs.platform_specific.activation_token.as_ref() { leap!(xconn.remove_activation_token(xwindow, &startup.token)); } // We never want to give the user a broken window, since by then, it's too late to handle. let window = leap!(xconn.sync_with_server().map(|_| window)); Ok(window) } /// Embed this window into a parent window. pub(super) fn embed_window(&self) -> Result<(), RootOsError> { let atoms = self.xconn.atoms(); leap!(leap!(self.xconn.change_property( self.xwindow, atoms[_XEMBED], atoms[_XEMBED], xproto::PropMode::REPLACE, &[0u32, 1u32], )) .check()); Ok(()) } pub(super) fn shared_state_lock(&self) -> MutexGuard<'_, SharedState> { self.shared_state.lock().unwrap() } fn set_pid(&self) -> Result>, X11Error> { let atoms = self.xconn.atoms(); let pid_atom = atoms[_NET_WM_PID]; let client_machine_atom = atoms[WM_CLIENT_MACHINE]; // Get the hostname and the PID. let uname = rustix::system::uname(); let pid = rustix::process::getpid(); self.xconn .change_property( self.xwindow, pid_atom, xproto::Atom::from(xproto::AtomEnum::CARDINAL), xproto::PropMode::REPLACE, &[pid.as_raw_nonzero().get() as util::Cardinal], )? .ignore_error(); let flusher = self.xconn.change_property( self.xwindow, client_machine_atom, xproto::Atom::from(xproto::AtomEnum::STRING), xproto::PropMode::REPLACE, uname.nodename().to_bytes(), ); flusher.map(Some) } fn set_window_types(&self, window_types: Vec) -> Result, X11Error> { let atoms = self.xconn.atoms(); let hint_atom = atoms[_NET_WM_WINDOW_TYPE]; let atoms: Vec<_> = window_types.iter().map(|t| t.as_atom(&self.xconn)).collect(); self.xconn.change_property( self.xwindow, hint_atom, xproto::Atom::from(xproto::AtomEnum::ATOM), xproto::PropMode::REPLACE, &atoms, ) } pub fn set_theme_inner(&self, theme: Option) -> Result, X11Error> { let atoms = self.xconn.atoms(); let hint_atom = atoms[_GTK_THEME_VARIANT]; let utf8_atom = atoms[UTF8_STRING]; let variant = match theme { Some(Theme::Dark) => "dark", Some(Theme::Light) => "light", None => "dark", }; let variant = CString::new(variant).expect("`_GTK_THEME_VARIANT` contained null byte"); self.xconn.change_property( self.xwindow, hint_atom, utf8_atom, xproto::PropMode::REPLACE, variant.as_bytes(), ) } #[inline] pub fn set_theme(&self, theme: Option) { self.set_theme_inner(theme).expect("Failed to change window theme").ignore_error(); self.xconn.flush_requests().expect("Failed to change window theme"); } fn set_netwm( &self, operation: util::StateOperation, properties: (u32, u32, u32, u32), ) -> Result, X11Error> { let atoms = self.xconn.atoms(); let state_atom = atoms[_NET_WM_STATE]; self.xconn.send_client_msg( self.xwindow, self.root, state_atom, Some(xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY), [operation as u32, properties.0, properties.1, properties.2, properties.3], ) } fn set_fullscreen_hint(&self, fullscreen: bool) -> Result, X11Error> { let atoms = self.xconn.atoms(); let fullscreen_atom = atoms[_NET_WM_STATE_FULLSCREEN]; let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom, 0, 0, 0)); if fullscreen { // Ensure that the fullscreen window receives input focus to prevent // locking up the user's display. self.xconn .xcb_connection() .set_input_focus(xproto::InputFocus::PARENT, self.xwindow, x11rb::CURRENT_TIME)? .ignore_error(); } flusher } fn set_fullscreen_inner( &self, fullscreen: Option, ) -> Result>, X11Error> { let mut shared_state_lock = self.shared_state_lock(); match shared_state_lock.visibility { // Setting fullscreen on a window that is not visible will generate an error. Visibility::No | Visibility::YesWait => { shared_state_lock.desired_fullscreen = Some(fullscreen); return Ok(None); }, Visibility::Yes => (), } let old_fullscreen = shared_state_lock.fullscreen.clone(); if old_fullscreen == fullscreen { return Ok(None); } shared_state_lock.fullscreen.clone_from(&fullscreen); match (&old_fullscreen, &fullscreen) { // Store the desktop video mode before entering exclusive // fullscreen, so we can restore it upon exit, as XRandR does not // provide a mechanism to set this per app-session or restore this // to the desktop video mode as macOS and Windows do (&None, &Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode)))) | ( &Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode))), ) => { let monitor = video_mode.monitor.as_ref().unwrap(); shared_state_lock.desktop_video_mode = Some(( monitor.id, self.xconn.get_crtc_mode(monitor.id).expect("Failed to get desktop video mode"), )); }, // Restore desktop video mode upon exiting exclusive fullscreen (&Some(Fullscreen::Exclusive(_)), &None) | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap(); self.xconn .set_crtc_config(monitor_id, mode_id) .expect("failed to restore desktop video mode"); }, _ => (), } drop(shared_state_lock); match fullscreen { None => { let flusher = self.set_fullscreen_hint(false); let mut shared_state_lock = self.shared_state_lock(); if let Some(position) = shared_state_lock.restore_position.take() { drop(shared_state_lock); self.set_position_inner(position.0, position.1) .expect_then_ignore_error("Failed to restore window position"); } flusher.map(Some) }, Some(fullscreen) => { let (video_mode, monitor) = match fullscreen { Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode)) => { (Some(video_mode), video_mode.monitor.clone().unwrap()) }, Fullscreen::Borderless(Some(PlatformMonitorHandle::X(monitor))) => { (None, monitor) }, Fullscreen::Borderless(None) => { (None, self.shared_state_lock().last_monitor.clone()) }, #[cfg(wayland_platform)] _ => unreachable!(), }; // Don't set fullscreen on an invalid dummy monitor handle if monitor.is_dummy() { return Ok(None); } if let Some(video_mode) = video_mode { // FIXME: this is actually not correct if we're setting the // video mode to a resolution higher than the current // desktop resolution, because XRandR does not automatically // reposition the monitors to the right and below this // monitor. // // What ends up happening is we will get the fullscreen // window showing up on those monitors as well, because // their virtual position now overlaps with the monitor that // we just made larger.. // // It'd be quite a bit of work to handle this correctly (and // nobody else seems to bother doing this correctly either), // so we're just leaving this broken. Fixing this would // involve storing all CRTCs upon entering fullscreen, // restoring them upon exit, and after entering fullscreen, // repositioning displays to the right and below this // display. I think there would still be edge cases that are // difficult or impossible to handle correctly, e.g. what if // a new monitor was plugged in while in fullscreen? // // I think we might just want to disallow setting the video // mode higher than the current desktop video mode (I'm sure // this will make someone unhappy, but it's very unusual for // games to want to do this anyway). self.xconn .set_crtc_config(monitor.id, video_mode.native_mode) .expect("failed to set video mode"); } let window_position = self.outer_position_physical(); self.shared_state_lock().restore_position = Some(window_position); let monitor_origin: (i32, i32) = monitor.position().into(); self.set_position_inner(monitor_origin.0, monitor_origin.1) .expect_then_ignore_error("Failed to set window position"); self.set_fullscreen_hint(true).map(Some) }, } } #[inline] pub(crate) fn fullscreen(&self) -> Option { let shared_state = self.shared_state_lock(); shared_state.desired_fullscreen.clone().unwrap_or_else(|| shared_state.fullscreen.clone()) } #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { if let Some(flusher) = self.set_fullscreen_inner(fullscreen).expect("Failed to change window fullscreen state") { flusher.check().expect("Failed to change window fullscreen state"); self.invalidate_cached_frame_extents(); } } // Called by EventProcessor when a VisibilityNotify event is received pub(crate) fn visibility_notify(&self) { let mut shared_state = self.shared_state_lock(); match shared_state.visibility { Visibility::No => self .xconn .xcb_connection() .unmap_window(self.xwindow) .expect_then_ignore_error("Failed to unmap window"), Visibility::Yes => (), Visibility::YesWait => { shared_state.visibility = Visibility::Yes; if let Some(fullscreen) = shared_state.desired_fullscreen.take() { drop(shared_state); self.set_fullscreen(fullscreen); } }, } } pub fn current_monitor(&self) -> Option { Some(self.shared_state_lock().last_monitor.clone()) } pub fn available_monitors(&self) -> Vec { self.xconn.available_monitors().expect("Failed to get available monitors") } pub fn primary_monitor(&self) -> Option { Some(self.xconn.primary_monitor().expect("Failed to get primary monitor")) } #[inline] pub fn is_minimized(&self) -> Option { let atoms = self.xconn.atoms(); let state_atom = atoms[_NET_WM_STATE]; let state = self.xconn.get_property( self.xwindow, state_atom, xproto::Atom::from(xproto::AtomEnum::ATOM), ); let hidden_atom = atoms[_NET_WM_STATE_HIDDEN]; Some(match state { Ok(atoms) => { atoms.iter().any(|atom: &xproto::Atom| *atom as xproto::Atom == hidden_atom) }, _ => false, }) } /// Refresh the API for the given monitor. #[inline] pub(super) fn refresh_dpi_for_monitor( &self, new_monitor: &X11MonitorHandle, maybe_prev_scale_factor: Option, mut callback: impl FnMut(Event), ) { // Check if the self is on this monitor let monitor = self.shared_state_lock().last_monitor.clone(); if monitor.name == new_monitor.name { let (width, height) = self.inner_size_physical(); let (new_width, new_height) = self.adjust_for_dpi( // If we couldn't determine the previous scale // factor (e.g., because all monitors were closed // before), just pick whatever the current monitor // has set as a baseline. maybe_prev_scale_factor.unwrap_or(monitor.scale_factor), new_monitor.scale_factor, width, height, &self.shared_state_lock(), ); let window_id = crate::window::WindowId(self.id()); let old_inner_size = PhysicalSize::new(width, height); let inner_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height))); callback(Event::WindowEvent { window_id, event: WindowEvent::ScaleFactorChanged { scale_factor: new_monitor.scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)), }, }); let new_inner_size = *inner_size.lock().unwrap(); drop(inner_size); if new_inner_size != old_inner_size { let (new_width, new_height) = new_inner_size.into(); self.request_inner_size_physical(new_width, new_height); } } } fn set_minimized_inner(&self, minimized: bool) -> Result, X11Error> { let atoms = self.xconn.atoms(); if minimized { let root_window = self.xconn.default_root().root; self.xconn.send_client_msg( self.xwindow, root_window, atoms[WM_CHANGE_STATE], Some( xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, ), [3u32, 0, 0, 0, 0], ) } else { self.xconn.send_client_msg( self.xwindow, self.root, atoms[_NET_ACTIVE_WINDOW], Some( xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, ), [1, x11rb::CURRENT_TIME, 0, 0, 0], ) } } #[inline] pub fn set_minimized(&self, minimized: bool) { self.set_minimized_inner(minimized) .expect_then_ignore_error("Failed to change window minimization"); self.xconn.flush_requests().expect("Failed to change window minimization"); } #[inline] pub fn is_maximized(&self) -> bool { let atoms = self.xconn.atoms(); let state_atom = atoms[_NET_WM_STATE]; let state = self.xconn.get_property( self.xwindow, state_atom, xproto::Atom::from(xproto::AtomEnum::ATOM), ); let horz_atom = atoms[_NET_WM_STATE_MAXIMIZED_HORZ]; let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT]; match state { Ok(atoms) => { let horz_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == horz_atom); let vert_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == vert_atom); horz_maximized && vert_maximized }, _ => false, } } fn set_maximized_inner(&self, maximized: bool) -> Result, X11Error> { let atoms = self.xconn.atoms(); let horz_atom = atoms[_NET_WM_STATE_MAXIMIZED_HORZ]; let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT]; self.set_netwm(maximized.into(), (horz_atom, vert_atom, 0, 0)) } #[inline] pub fn set_maximized(&self, maximized: bool) { self.set_maximized_inner(maximized) .expect_then_ignore_error("Failed to change window maximization"); self.xconn.flush_requests().expect("Failed to change window maximization"); self.invalidate_cached_frame_extents(); } fn set_title_inner(&self, title: &str) -> Result, X11Error> { let atoms = self.xconn.atoms(); let title = CString::new(title).expect("Window title contained null byte"); self.xconn .change_property( self.xwindow, xproto::Atom::from(xproto::AtomEnum::WM_NAME), xproto::Atom::from(xproto::AtomEnum::STRING), xproto::PropMode::REPLACE, title.as_bytes(), )? .ignore_error(); self.xconn.change_property( self.xwindow, atoms[_NET_WM_NAME], atoms[UTF8_STRING], xproto::PropMode::REPLACE, title.as_bytes(), ) } #[inline] pub fn set_title(&self, title: &str) { self.set_title_inner(title).expect_then_ignore_error("Failed to set window title"); self.xconn.flush_requests().expect("Failed to set window title"); } #[inline] pub fn set_transparent(&self, _transparent: bool) {} #[inline] pub fn set_blur(&self, _blur: bool) {} fn set_decorations_inner(&self, decorations: bool) -> Result, X11Error> { self.shared_state_lock().is_decorated = decorations; let mut hints = self.xconn.get_motif_hints(self.xwindow); hints.set_decorations(decorations); self.xconn.set_motif_hints(self.xwindow, &hints) } #[inline] pub fn set_decorations(&self, decorations: bool) { self.set_decorations_inner(decorations) .expect_then_ignore_error("Failed to set decoration state"); self.xconn.flush_requests().expect("Failed to set decoration state"); self.invalidate_cached_frame_extents(); } #[inline] pub fn is_decorated(&self) -> bool { self.shared_state_lock().is_decorated } fn set_maximizable_inner(&self, maximizable: bool) -> Result, X11Error> { let mut hints = self.xconn.get_motif_hints(self.xwindow); hints.set_maximizable(maximizable); self.xconn.set_motif_hints(self.xwindow, &hints) } fn toggle_atom(&self, atom_name: AtomName, enable: bool) -> Result, X11Error> { let atoms = self.xconn.atoms(); let atom = atoms[atom_name]; self.set_netwm(enable.into(), (atom, 0, 0, 0)) } fn set_window_level_inner(&self, level: WindowLevel) -> Result, X11Error> { self.toggle_atom(_NET_WM_STATE_ABOVE, level == WindowLevel::AlwaysOnTop)?.ignore_error(); self.toggle_atom(_NET_WM_STATE_BELOW, level == WindowLevel::AlwaysOnBottom) } #[inline] pub fn set_window_level(&self, level: WindowLevel) { self.set_window_level_inner(level) .expect_then_ignore_error("Failed to set window-level state"); self.xconn.flush_requests().expect("Failed to set window-level state"); } fn set_icon_inner(&self, icon: PlatformIcon) -> Result, X11Error> { let atoms = self.xconn.atoms(); let icon_atom = atoms[_NET_WM_ICON]; let data = icon.to_cardinals(); self.xconn.change_property( self.xwindow, icon_atom, xproto::Atom::from(xproto::AtomEnum::CARDINAL), xproto::PropMode::REPLACE, data.as_slice(), ) } fn unset_icon_inner(&self) -> Result, X11Error> { let atoms = self.xconn.atoms(); let icon_atom = atoms[_NET_WM_ICON]; let empty_data: [util::Cardinal; 0] = []; self.xconn.change_property( self.xwindow, icon_atom, xproto::Atom::from(xproto::AtomEnum::CARDINAL), xproto::PropMode::REPLACE, &empty_data, ) } #[inline] pub(crate) fn set_window_icon(&self, icon: Option) { match icon { Some(icon) => self.set_icon_inner(icon), None => self.unset_icon_inner(), } .expect_then_ignore_error("Failed to set icons"); self.xconn.flush_requests().expect("Failed to set icons"); } #[inline] pub fn set_visible(&self, visible: bool) { let mut shared_state = self.shared_state_lock(); match (visible, shared_state.visibility) { (true, Visibility::Yes) | (true, Visibility::YesWait) | (false, Visibility::No) => { return }, _ => (), } if visible { self.xconn .xcb_connection() .map_window(self.xwindow) .expect_then_ignore_error("Failed to call `xcb_map_window`"); self.xconn .xcb_connection() .configure_window( self.xwindow, &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE), ) .expect_then_ignore_error("Failed to call `xcb_configure_window`"); self.xconn.flush_requests().expect("Failed to call XMapRaised"); shared_state.visibility = Visibility::YesWait; } else { self.xconn .xcb_connection() .unmap_window(self.xwindow) .expect_then_ignore_error("Failed to call `xcb_unmap_window`"); self.xconn.flush_requests().expect("Failed to call XUnmapWindow"); shared_state.visibility = Visibility::No; } } #[inline] pub fn is_visible(&self) -> Option { Some(self.shared_state_lock().visibility == Visibility::Yes) } fn update_cached_frame_extents(&self) { let extents = self.xconn.get_frame_extents_heuristic(self.xwindow, self.root); self.shared_state_lock().frame_extents = Some(extents); } pub(crate) fn invalidate_cached_frame_extents(&self) { self.shared_state_lock().frame_extents.take(); } pub(crate) fn outer_position_physical(&self) -> (i32, i32) { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { let (x, y) = self.inner_position_physical(); extents.inner_pos_to_outer(x, y) } else { self.update_cached_frame_extents(); self.outer_position_physical() } } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { let (x, y) = self.inner_position_physical(); Ok(extents.inner_pos_to_outer(x, y).into()) } else { self.update_cached_frame_extents(); self.outer_position() } } pub(crate) fn inner_position_physical(&self) -> (i32, i32) { // This should be okay to unwrap since the only error XTranslateCoordinates can return // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn .translate_coords(self.xwindow, self.root) .map(|coords| (coords.dst_x.into(), coords.dst_y.into())) .unwrap() } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { Ok(self.inner_position_physical().into()) } pub(crate) fn set_position_inner( &self, mut x: i32, mut y: i32, ) -> Result, X11Error> { // There are a few WMs that set client area position rather than window position, so // we'll translate for consistency. if util::wm_name_is_one_of(&["Enlightenment", "FVWM"]) { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { x += cast_dimension_to_hint(extents.frame_extents.left); y += cast_dimension_to_hint(extents.frame_extents.top); } else { self.update_cached_frame_extents(); return self.set_position_inner(x, y); } } self.xconn .xcb_connection() .configure_window(self.xwindow, &xproto::ConfigureWindowAux::new().x(x).y(y)) .map_err(Into::into) } pub(crate) fn set_position_physical(&self, x: i32, y: i32) { self.set_position_inner(x, y).expect_then_ignore_error("Failed to call `XMoveWindow`"); } #[inline] pub fn set_outer_position(&self, position: Position) { let (x, y) = position.to_physical::(self.scale_factor()).into(); self.set_position_physical(x, y); } pub(crate) fn inner_size_physical(&self) -> (u32, u32) { // This should be okay to unwrap since the only error XGetGeometry can return // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn .get_geometry(self.xwindow) .map(|geo| (geo.width.into(), geo.height.into())) .unwrap() } #[inline] pub fn inner_size(&self) -> PhysicalSize { self.inner_size_physical().into() } #[inline] pub fn outer_size(&self) -> PhysicalSize { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { let (width, height) = self.inner_size_physical(); extents.inner_size_to_outer(width, height).into() } else { self.update_cached_frame_extents(); self.outer_size() } } pub(crate) fn request_inner_size_physical(&self, width: u32, height: u32) { self.xconn .xcb_connection() .configure_window( self.xwindow, &xproto::ConfigureWindowAux::new().width(width).height(height), ) .expect_then_ignore_error("Failed to call `xcb_configure_window`"); self.xconn.flush_requests().expect("Failed to call XResizeWindow"); // cursor_hittest needs to be reapplied after each window resize. if self.shared_state_lock().cursor_hittest.unwrap_or(false) { let _ = self.set_cursor_hittest(true); } } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let scale_factor = self.scale_factor(); let size = size.to_physical::(scale_factor).into(); if !self.shared_state_lock().is_resizable { self.update_normal_hints(|normal_hints| { normal_hints.min_size = Some(size); normal_hints.max_size = Some(size); }) .expect("Failed to call `XSetWMNormalHints`"); } self.request_inner_size_physical(size.0 as u32, size.1 as u32); None } fn update_normal_hints(&self, callback: F) -> Result<(), X11Error> where F: FnOnce(&mut WmSizeHints), { let mut normal_hints = WmSizeHints::get( self.xconn.xcb_connection(), self.xwindow as xproto::Window, xproto::AtomEnum::WM_NORMAL_HINTS, )? .reply()? .unwrap_or_default(); callback(&mut normal_hints); normal_hints .set( self.xconn.xcb_connection(), self.xwindow as xproto::Window, xproto::AtomEnum::WM_NORMAL_HINTS, )? .ignore_error(); Ok(()) } pub(crate) fn set_min_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { self.update_normal_hints(|normal_hints| { normal_hints.min_size = dimensions.map(|(w, h)| (cast_dimension_to_hint(w), cast_dimension_to_hint(h))) }) .expect("Failed to call `XSetWMNormalHints`"); } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { self.shared_state_lock().min_inner_size = dimensions; let physical_dimensions = dimensions.map(|dimensions| dimensions.to_physical::(self.scale_factor()).into()); self.set_min_inner_size_physical(physical_dimensions); } pub(crate) fn set_max_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { self.update_normal_hints(|normal_hints| { normal_hints.max_size = dimensions.map(|(w, h)| (cast_dimension_to_hint(w), cast_dimension_to_hint(h))) }) .expect("Failed to call `XSetWMNormalHints`"); } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { self.shared_state_lock().max_inner_size = dimensions; let physical_dimensions = dimensions.map(|dimensions| dimensions.to_physical::(self.scale_factor()).into()); self.set_max_inner_size_physical(physical_dimensions); } #[inline] pub fn resize_increments(&self) -> Option> { WmSizeHints::get( self.xconn.xcb_connection(), self.xwindow as xproto::Window, xproto::AtomEnum::WM_NORMAL_HINTS, ) .ok() .and_then(|cookie| cookie.reply().ok()) .flatten() .and_then(|hints| hints.size_increment) .map(|(width, height)| (width as u32, height as u32).into()) } #[inline] pub fn set_resize_increments(&self, increments: Option) { self.shared_state_lock().resize_increments = increments; let physical_increments = increments.map(|increments| cast_size_to_hint(increments, self.scale_factor())); self.update_normal_hints(|hints| hints.size_increment = physical_increments) .expect("Failed to call `XSetWMNormalHints`"); } pub(crate) fn adjust_for_dpi( &self, old_scale_factor: f64, new_scale_factor: f64, width: u32, height: u32, shared_state: &SharedState, ) -> (u32, u32) { let scale_factor = new_scale_factor / old_scale_factor; self.update_normal_hints(|normal_hints| { let dpi_adjuster = |size: Size| -> (i32, i32) { cast_size_to_hint(size, scale_factor) }; let max_size = shared_state.max_inner_size.map(dpi_adjuster); let min_size = shared_state.min_inner_size.map(dpi_adjuster); let resize_increments = shared_state.resize_increments.map(dpi_adjuster); let base_size = shared_state.base_size.map(dpi_adjuster); normal_hints.max_size = max_size; normal_hints.min_size = min_size; normal_hints.size_increment = resize_increments; normal_hints.base_size = base_size; }) .expect("Failed to update normal hints"); let new_width = (width as f64 * scale_factor).round() as u32; let new_height = (height as f64 * scale_factor).round() as u32; (new_width, new_height) } pub fn set_resizable(&self, resizable: bool) { if util::wm_name_is_one_of(&["Xfwm4"]) { // Making the window unresizable on Xfwm prevents further changes to `WM_NORMAL_HINTS` // from being detected. This makes it impossible for resizing to be // re-enabled, and also breaks DPI scaling. As such, we choose the lesser of // two evils and do nothing. warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4"); return; } let (min_size, max_size) = if resizable { let shared_state_lock = self.shared_state_lock(); (shared_state_lock.min_inner_size, shared_state_lock.max_inner_size) } else { let window_size = Some(Size::from(self.inner_size())); (window_size, window_size) }; self.shared_state_lock().is_resizable = resizable; self.set_maximizable_inner(resizable) .expect_then_ignore_error("Failed to call `XSetWMNormalHints`"); let scale_factor = self.scale_factor(); let min_inner_size = min_size.map(|size| cast_size_to_hint(size, scale_factor)); let max_inner_size = max_size.map(|size| cast_size_to_hint(size, scale_factor)); self.update_normal_hints(|normal_hints| { normal_hints.min_size = min_inner_size; normal_hints.max_size = max_inner_size; }) .expect("Failed to call `XSetWMNormalHints`"); } #[inline] pub fn is_resizable(&self) -> bool { self.shared_state_lock().is_resizable } #[inline] pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} #[inline] pub fn enabled_buttons(&self) -> WindowButtons { WindowButtons::all() } #[allow(dead_code)] #[inline] pub fn xlib_display(&self) -> *mut c_void { self.xconn.display as _ } #[allow(dead_code)] #[inline] pub fn xlib_window(&self) -> c_ulong { self.xwindow as ffi::Window } #[inline] pub fn set_cursor(&self, cursor: Cursor) { match cursor { Cursor::Icon(icon) => { let old_cursor = replace( &mut *self.selected_cursor.lock().unwrap(), SelectedCursor::Named(icon), ); #[allow(clippy::mutex_atomic)] if SelectedCursor::Named(icon) != old_cursor && *self.cursor_visible.lock().unwrap() { self.xconn.set_cursor_icon(self.xwindow, Some(icon)); } }, Cursor::Custom(RootCustomCursor { inner: PlatformCustomCursor::X(cursor) }) => { #[allow(clippy::mutex_atomic)] if *self.cursor_visible.lock().unwrap() { self.xconn.set_custom_cursor(self.xwindow, &cursor); } *self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(cursor); }, #[cfg(wayland_platform)] Cursor::Custom(RootCustomCursor { inner: PlatformCustomCursor::Wayland(_) }) => { tracing::error!("passed a Wayland cursor to X11 backend") }, } } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { // We don't support the locked cursor yet, so ignore it early on. if mode == CursorGrabMode::Locked { return Err(ExternalError::NotSupported(NotSupportedError::new())); } let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap(); if mode == *grabbed_lock { return Ok(()); } // We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`. // Therefore, this is common to both codepaths. self.xconn .xcb_connection() .ungrab_pointer(x11rb::CURRENT_TIME) .expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`"); *grabbed_lock = CursorGrabMode::None; let result = match mode { CursorGrabMode::None => self.xconn.flush_requests().map_err(|err| { ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into()))) }), CursorGrabMode::Confined => { let result = self .xconn .xcb_connection() .grab_pointer( true as _, self.xwindow, xproto::EventMask::BUTTON_PRESS | xproto::EventMask::BUTTON_RELEASE | xproto::EventMask::ENTER_WINDOW | xproto::EventMask::LEAVE_WINDOW | xproto::EventMask::POINTER_MOTION | xproto::EventMask::POINTER_MOTION_HINT | xproto::EventMask::BUTTON1_MOTION | xproto::EventMask::BUTTON2_MOTION | xproto::EventMask::BUTTON3_MOTION | xproto::EventMask::BUTTON4_MOTION | xproto::EventMask::BUTTON5_MOTION | xproto::EventMask::KEYMAP_STATE, xproto::GrabMode::ASYNC, xproto::GrabMode::ASYNC, self.xwindow, 0u32, x11rb::CURRENT_TIME, ) .expect("Failed to call `grab_pointer`") .reply() .expect("Failed to receive reply from `grab_pointer`"); match result.status { xproto::GrabStatus::SUCCESS => Ok(()), xproto::GrabStatus::ALREADY_GRABBED => { Err("Cursor could not be confined: already confined by another client") }, xproto::GrabStatus::INVALID_TIME => { Err("Cursor could not be confined: invalid time") }, xproto::GrabStatus::NOT_VIEWABLE => { Err("Cursor could not be confined: confine location not viewable") }, xproto::GrabStatus::FROZEN => { Err("Cursor could not be confined: frozen by another client") }, _ => unreachable!(), } .map_err(|err| ExternalError::Os(os_error!(OsError::Misc(err)))) }, CursorGrabMode::Locked => return Ok(()), }; if result.is_ok() { *grabbed_lock = mode; } result } #[inline] pub fn set_cursor_visible(&self, visible: bool) { #[allow(clippy::mutex_atomic)] let mut visible_lock = self.cursor_visible.lock().unwrap(); if visible == *visible_lock { return; } let cursor = if visible { Some((*self.selected_cursor.lock().unwrap()).clone()) } else { None }; *visible_lock = visible; drop(visible_lock); match cursor { Some(SelectedCursor::Custom(cursor)) => { self.xconn.set_custom_cursor(self.xwindow, &cursor); }, Some(SelectedCursor::Named(cursor)) => { self.xconn.set_cursor_icon(self.xwindow, Some(cursor)); }, None => { self.xconn.set_cursor_icon(self.xwindow, None); }, } } #[inline] pub fn scale_factor(&self) -> f64 { self.shared_state_lock().last_monitor.scale_factor } pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> { { self.xconn .xcb_connection() .warp_pointer(x11rb::NONE, self.xwindow, 0, 0, 0, 0, x as _, y as _) .map_err(|e| { ExternalError::Os(os_error!(OsError::XError(X11Error::from(e).into()))) })?; self.xconn.flush_requests().map_err(|e| { ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(e).into()))) }) } } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { let (x, y) = position.to_physical::(self.scale_factor()).into(); self.set_cursor_position_physical(x, y) } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { let mut rectangles: Vec = Vec::new(); if hittest { let size = self.inner_size(); rectangles.push(Rectangle { x: 0, y: 0, width: size.width as u16, height: size.height as u16, }) } let region = RegionWrapper::create_region(self.xconn.xcb_connection(), &rectangles) .map_err(|_e| ExternalError::Ignored)?; self.xconn .xcb_connection() .xfixes_set_window_shape_region(self.xwindow, SK::INPUT, 0, 0, region.region()) .map_err(|_e| ExternalError::Ignored)?; self.shared_state_lock().cursor_hittest = Some(hittest); Ok(()) } /// Moves the window while it is being dragged. pub fn drag_window(&self) -> Result<(), ExternalError> { self.drag_initiate(util::MOVERESIZE_MOVE) } #[inline] pub fn show_window_menu(&self, _position: Position) {} /// Resizes the window while it is being dragged. pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { self.drag_initiate(match direction { ResizeDirection::East => util::MOVERESIZE_RIGHT, ResizeDirection::North => util::MOVERESIZE_TOP, ResizeDirection::NorthEast => util::MOVERESIZE_TOPRIGHT, ResizeDirection::NorthWest => util::MOVERESIZE_TOPLEFT, ResizeDirection::South => util::MOVERESIZE_BOTTOM, ResizeDirection::SouthEast => util::MOVERESIZE_BOTTOMRIGHT, ResizeDirection::SouthWest => util::MOVERESIZE_BOTTOMLEFT, ResizeDirection::West => util::MOVERESIZE_LEFT, }) } /// Initiates a drag operation while the left mouse button is pressed. fn drag_initiate(&self, action: isize) -> Result<(), ExternalError> { let pointer = self .xconn .query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER) .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?; let window = self.inner_position().map_err(ExternalError::NotSupported)?; let atoms = self.xconn.atoms(); let message = atoms[_NET_WM_MOVERESIZE]; // we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer` // if the cursor isn't currently grabbed let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap(); self.xconn .xcb_connection() .ungrab_pointer(x11rb::CURRENT_TIME) .map_err(|err| { ExternalError::Os(os_error!(OsError::XError(X11Error::from(err).into()))) })? .ignore_error(); self.xconn.flush_requests().map_err(|err| { ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into()))) })?; *grabbed_lock = CursorGrabMode::None; // we keep the lock until we are done self.xconn .send_client_msg( self.xwindow, self.root, message, Some( xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, ), [ (window.x + xinput_fp1616_to_float(pointer.win_x) as i32) as u32, (window.y + xinput_fp1616_to_float(pointer.win_y) as i32) as u32, action.try_into().unwrap(), 1, // Button 1 1, ], ) .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?; self.xconn.flush_requests().map_err(|err| { ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into()))) }) } #[inline] pub fn set_ime_cursor_area(&self, spot: Position, _size: Size) { let (x, y) = spot.to_physical::(self.scale_factor()).into(); let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Position( self.xwindow as ffi::Window, x, y, )); } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { let _ = self .ime_sender .lock() .unwrap() .send(ImeRequest::Allow(self.xwindow as ffi::Window, allowed)); } #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] pub fn focus_window(&self) { let atoms = self.xconn.atoms(); let state_atom = atoms[WM_STATE]; let state_type_atom = atoms[CARD32]; let is_minimized = if let Ok(state) = self.xconn.get_property::(self.xwindow, state_atom, state_type_atom) { state.contains(&super::ICONIC_STATE) } else { false }; let is_visible = match self.shared_state_lock().visibility { Visibility::Yes => true, Visibility::YesWait | Visibility::No => false, }; if is_visible && !is_minimized { self.xconn .send_client_msg( self.xwindow, self.root, atoms[_NET_ACTIVE_WINDOW], Some( xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, ), [1, x11rb::CURRENT_TIME, 0, 0, 0], ) .expect_then_ignore_error("Failed to send client message"); if let Err(e) = self.xconn.flush_requests() { tracing::error!( "`flush` returned an error when focusing the window. Error was: {}", e ); } } } #[inline] pub fn request_user_attention(&self, request_type: Option) { let mut wm_hints = WmHints::get(self.xconn.xcb_connection(), self.xwindow as xproto::Window) .ok() .and_then(|cookie| cookie.reply().ok()) .flatten() .unwrap_or_default(); wm_hints.urgent = request_type.is_some(); wm_hints .set(self.xconn.xcb_connection(), self.xwindow as xproto::Window) .expect_then_ignore_error("Failed to set WM hints"); } #[inline] pub(crate) fn generate_activation_token(&self) -> Result { // Get the title from the WM_NAME property. let atoms = self.xconn.atoms(); let title = { let title_bytes = self .xconn .get_property(self.xwindow, atoms[_NET_WM_NAME], atoms[UTF8_STRING]) .expect("Failed to get title"); String::from_utf8(title_bytes).expect("Bad title") }; // Get the activation token and then put it in the event queue. let token = self.xconn.request_activation_token(&title)?; Ok(token) } #[inline] pub fn request_activation_token(&self) -> Result { let serial = AsyncRequestSerial::get(); self.activation_sender .send((self.id(), serial)) .expect("activation token channel should never be closed"); Ok(serial) } #[inline] pub fn id(&self) -> WindowId { WindowId(self.xwindow as _) } #[inline] pub fn request_redraw(&self) { self.redraw_sender.send(WindowId(self.xwindow as _)).unwrap(); } #[inline] pub fn pre_present_notify(&self) { // TODO timer } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::XlibHandle::empty(); window_handle.display = self.xlib_display(); window_handle.window = self.xlib_window(); window_handle.visual_id = self.visual as c_ulong; rwh_04::RawWindowHandle::Xlib(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::XlibWindowHandle::empty(); window_handle.window = self.xlib_window(); window_handle.visual_id = self.visual as c_ulong; window_handle.into() } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { let mut display_handle = rwh_05::XlibDisplayHandle::empty(); display_handle.display = self.xlib_display(); display_handle.screen = self.screen_id; display_handle.into() } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { let mut window_handle = rwh_06::XlibWindowHandle::new(self.xlib_window()); window_handle.visual_id = self.visual as c_ulong; Ok(window_handle.into()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::XlibDisplayHandle::new( Some( std::ptr::NonNull::new(self.xlib_display()) .expect("display pointer should never be null"), ), self.screen_id, ) .into()) } #[inline] pub fn theme(&self) -> Option { None } pub fn set_content_protected(&self, _protected: bool) {} #[inline] pub fn has_focus(&self) -> bool { self.shared_state_lock().has_focus } pub fn title(&self) -> String { String::new() } } /// Cast a dimension value into a hinted dimension for `WmSizeHints`, clamping if too large. fn cast_dimension_to_hint(val: u32) -> i32 { val.try_into().unwrap_or(i32::MAX) } /// Use the above strategy to cast a physical size into a hinted size. fn cast_physical_size_to_hint(size: PhysicalSize) -> (i32, i32) { let PhysicalSize { width, height } = size; (cast_dimension_to_hint(width), cast_dimension_to_hint(height)) } /// Use the above strategy to cast a size into a hinted size. fn cast_size_to_hint(size: Size, scale_factor: f64) -> (i32, i32) { match size { Size::Physical(size) => cast_physical_size_to_hint(size), Size::Logical(size) => size.to_physical::(scale_factor).into(), } } winit-0.30.9/src/platform_impl/linux/x11/xdisplay.rs000064400000000000000000000257301046102023000204650ustar 00000000000000use std::collections::HashMap; use std::error::Error; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard}; use std::{fmt, ptr}; use crate::window::CursorIcon; use super::atoms::Atoms; use super::ffi; use super::monitor::MonitorHandle; use x11rb::connection::Connection; use x11rb::protocol::randr::ConnectionExt as _; use x11rb::protocol::xproto::{self, ConnectionExt}; use x11rb::resource_manager; use x11rb::xcb_ffi::XCBConnection; /// A connection to an X server. pub struct XConnection { pub xlib: ffi::Xlib, pub xcursor: ffi::Xcursor, // TODO(notgull): I'd like to remove this, but apparently Xlib and Xinput2 are tied together // for some reason. pub xinput2: ffi::XInput2, pub display: *mut ffi::Display, /// The manager for the XCB connection. /// /// The `Option` ensures that we can drop it before we close the `Display`. xcb: Option, /// The atoms used by `winit`. /// /// This is a large structure, so I've elected to Box it to make accessing the fields of /// this struct easier. Feel free to unbox it if you like kicking puppies. atoms: Box, /// The index of the default screen. default_screen: usize, /// The last timestamp received by this connection. timestamp: AtomicU32, /// List of monitor handles. pub monitor_handles: Mutex>>, /// The resource database. database: RwLock, /// RandR version. randr_version: (u32, u32), /// Atom for the XSettings screen. xsettings_screen: Option, pub latest_error: Mutex>, pub cursor_cache: Mutex, ffi::Cursor>>, } unsafe impl Send for XConnection {} unsafe impl Sync for XConnection {} pub type XErrorHandler = Option std::os::raw::c_int>; impl XConnection { pub fn new(error_handler: XErrorHandler) -> Result { // opening the libraries let xlib = ffi::Xlib::open()?; let xcursor = ffi::Xcursor::open()?; let xlib_xcb = ffi::Xlib_xcb::open()?; let xinput2 = ffi::XInput2::open()?; unsafe { (xlib.XInitThreads)() }; unsafe { (xlib.XSetErrorHandler)(error_handler) }; // calling XOpenDisplay let display = unsafe { let display = (xlib.XOpenDisplay)(ptr::null()); if display.is_null() { return Err(XNotSupported::XOpenDisplayFailed); } display }; // Open the x11rb XCB connection. let xcb = { // Get a pointer to the underlying XCB connection let xcb_connection = unsafe { (xlib_xcb.XGetXCBConnection)(display as *mut ffi::Display) }; assert!(!xcb_connection.is_null()); // Wrap the XCB connection in an x11rb XCB connection let conn = unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection.cast(), false) }; conn.map_err(|e| XNotSupported::XcbConversionError(Arc::new(WrapConnectError(e))))? }; // Get the default screen. let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize; // Load the database. let database = resource_manager::new_from_default(&xcb) .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; // Load the RandR version. let randr_version = xcb .randr_query_version(1, 3) .expect("failed to request XRandR version") .reply() .expect("failed to query XRandR version"); let xsettings_screen = Self::new_xsettings_screen(&xcb, default_screen); if xsettings_screen.is_none() { tracing::warn!("error setting XSETTINGS; Xft options won't reload automatically") } // Fetch atoms. let atoms = Atoms::new(&xcb) .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))? .reply() .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; Ok(XConnection { xlib, xcursor, xinput2, display, xcb: Some(xcb), atoms: Box::new(atoms), default_screen, timestamp: AtomicU32::new(0), latest_error: Mutex::new(None), monitor_handles: Mutex::new(None), database: RwLock::new(database), cursor_cache: Default::default(), randr_version: (randr_version.major_version, randr_version.minor_version), xsettings_screen, }) } fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option { // Fetch the _XSETTINGS_S[screen number] atom. let xsettings_screen = xcb .intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes()) .ok()? .reply() .ok()? .atom; // Get PropertyNotify events from the XSETTINGS window. // TODO: The XSETTINGS window here can change. In the future, listen for DestroyNotify on // this window in order to accommodate for a changed window here. let selector_window = xcb.get_selection_owner(xsettings_screen).ok()?.reply().ok()?.owner; xcb.change_window_attributes( selector_window, &xproto::ChangeWindowAttributesAux::new() .event_mask(xproto::EventMask::PROPERTY_CHANGE), ) .ok()? .check() .ok()?; Some(xsettings_screen) } /// Checks whether an error has been triggered by the previous function calls. #[inline] pub fn check_errors(&self) -> Result<(), XError> { let error = self.latest_error.lock().unwrap().take(); if let Some(error) = error { Err(error) } else { Ok(()) } } #[inline] pub fn randr_version(&self) -> (u32, u32) { self.randr_version } /// Get the underlying XCB connection. #[inline] pub fn xcb_connection(&self) -> &XCBConnection { self.xcb.as_ref().expect("xcb_connection somehow called after drop?") } /// Get the list of atoms. #[inline] pub fn atoms(&self) -> &Atoms { &self.atoms } /// Get the index of the default screen. #[inline] pub fn default_screen_index(&self) -> usize { self.default_screen } /// Get the default screen. #[inline] pub fn default_root(&self) -> &xproto::Screen { &self.xcb_connection().setup().roots[self.default_screen] } /// Get the resource database. #[inline] pub fn database(&self) -> RwLockReadGuard<'_, resource_manager::Database> { self.database.read().unwrap_or_else(|e| e.into_inner()) } /// Reload the resource database. #[inline] pub fn reload_database(&self) -> Result<(), super::X11Error> { let database = resource_manager::new_from_default(self.xcb_connection())?; *self.database.write().unwrap_or_else(|e| e.into_inner()) = database; Ok(()) } /// Get the latest timestamp. #[inline] pub fn timestamp(&self) -> u32 { self.timestamp.load(Ordering::Relaxed) } /// Set the last witnessed timestamp. #[inline] pub fn set_timestamp(&self, timestamp: u32) { // Store the timestamp in the slot if it's greater than the last one. let mut last_timestamp = self.timestamp.load(Ordering::Relaxed); loop { let wrapping_sub = |a: xproto::Timestamp, b: xproto::Timestamp| (a as i32) - (b as i32); if wrapping_sub(timestamp, last_timestamp) <= 0 { break; } match self.timestamp.compare_exchange( last_timestamp, timestamp, Ordering::Relaxed, Ordering::Relaxed, ) { Ok(_) => break, Err(x) => last_timestamp = x, } } } /// Get the atom for Xsettings. #[inline] pub fn xsettings_screen(&self) -> Option { self.xsettings_screen } } impl fmt::Debug for XConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.display.fmt(f) } } impl Drop for XConnection { #[inline] fn drop(&mut self) { self.xcb = None; unsafe { (self.xlib.XCloseDisplay)(self.display) }; } } /// Error triggered by xlib. #[derive(Debug, Clone)] pub struct XError { pub description: String, pub error_code: u8, pub request_code: u8, pub minor_code: u8, } impl Error for XError {} impl fmt::Display for XError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( formatter, "X error: {} (code: {}, request code: {}, minor code: {})", self.description, self.error_code, self.request_code, self.minor_code ) } } /// Error returned if this system doesn't have XLib or can't create an X connection. #[derive(Clone, Debug)] pub enum XNotSupported { /// Failed to load one or several shared libraries. LibraryOpenError(ffi::OpenError), /// Connecting to the X server with `XOpenDisplay` failed. XOpenDisplayFailed, // TODO: add better message. /// We encountered an error while converting the connection to XCB. XcbConversionError(Arc), } impl From for XNotSupported { #[inline] fn from(err: ffi::OpenError) -> XNotSupported { XNotSupported::LibraryOpenError(err) } } impl XNotSupported { fn description(&self) -> &'static str { match self { XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries", XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server", XNotSupported::XcbConversionError(_) => "Failed to convert Xlib connection to XCB", } } } impl Error for XNotSupported { #[inline] fn source(&self) -> Option<&(dyn Error + 'static)> { match *self { XNotSupported::LibraryOpenError(ref err) => Some(err), XNotSupported::XcbConversionError(ref err) => Some(&**err), _ => None, } } } impl fmt::Display for XNotSupported { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { formatter.write_str(self.description()) } } /// A newtype wrapper around a `ConnectError` that can't be accessed by downstream libraries. /// /// Without this, `x11rb` would become a public dependency. #[derive(Debug)] struct WrapConnectError(x11rb::rust_connection::ConnectError); impl fmt::Display for WrapConnectError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl Error for WrapConnectError { // We can't implement `source()` here or otherwise risk exposing `x11rb`. } winit-0.30.9/src/platform_impl/linux/x11/xsettings.rs000064400000000000000000000214661046102023000206620ustar 00000000000000//! Parser for the xsettings data format. //! //! Some of this code is referenced from [here]. //! //! [here]: https://github.com/derat/xsettingsd use std::iter; use std::num::NonZeroUsize; use x11rb::protocol::xproto::{self, ConnectionExt}; use super::atoms::*; use super::XConnection; type Result = core::result::Result; const DPI_NAME: &[u8] = b"Xft/DPI"; const DPI_MULTIPLIER: f64 = 1024.0; const LITTLE_ENDIAN: u8 = b'l'; const BIG_ENDIAN: u8 = b'B'; impl XConnection { /// Get the DPI from XSettings. pub(crate) fn xsettings_dpi( &self, xsettings_screen: xproto::Atom, ) -> core::result::Result, super::X11Error> { let atoms = self.atoms(); // Get the current owner of the screen's settings. let owner = self.xcb_connection().get_selection_owner(xsettings_screen)?.reply()?; // Read the _XSETTINGS_SETTINGS property. let data: Vec = self.get_property(owner.owner, atoms[_XSETTINGS_SETTINGS], atoms[_XSETTINGS_SETTINGS])?; // Parse the property. let dpi_setting = read_settings(&data)? .find(|res| res.as_ref().map_or(true, |s| s.name == DPI_NAME)) .transpose()?; if let Some(dpi_setting) = dpi_setting { let base_dpi = match dpi_setting.data { SettingData::Integer(dpi) => dpi as f64, SettingData::String(_) => { return Err(ParserError::BadType(SettingType::String).into()) }, SettingData::Color(_) => { return Err(ParserError::BadType(SettingType::Color).into()) }, }; Ok(Some(base_dpi / DPI_MULTIPLIER)) } else { Ok(None) } } } /// Read over the settings in the block of data. fn read_settings(data: &[u8]) -> Result>> + '_> { // Create a parser. This automatically parses the first 8 bytes for metadata. let mut parser = Parser::new(data)?; // Read the total number of settings. let total_settings = parser.i32()?; // Iterate over the settings. let iter = iter::repeat_with(move || Setting::parse(&mut parser)).take(total_settings as usize); Ok(iter) } /// A setting in the settings list. struct Setting<'a> { /// The name of the setting. name: &'a [u8], /// The data contained in the setting. data: SettingData<'a>, } /// The data contained in a setting. enum SettingData<'a> { Integer(i32), String(#[allow(dead_code)] &'a [u8]), Color(#[allow(dead_code)] [i16; 4]), } impl<'a> Setting<'a> { /// Parse a new `SettingData`. fn parse(parser: &mut Parser<'a>) -> Result { // Read the type. let ty: SettingType = parser.i8()?.try_into()?; // Read another byte of padding. parser.advance(1)?; // Read the name of the setting. let name_len = parser.i16()?; let name = parser.advance(name_len as usize)?; parser.pad(name.len(), 4)?; // Ignore the serial number. parser.advance(4)?; let data = match ty { SettingType::Integer => { // Read a 32-bit integer. SettingData::Integer(parser.i32()?) }, SettingType::String => { // Read the data. let data_len = parser.i32()?; let data = parser.advance(data_len as usize)?; parser.pad(data.len(), 4)?; SettingData::String(data) }, SettingType::Color => { // Read i16's of color. let (red, blue, green, alpha) = (parser.i16()?, parser.i16()?, parser.i16()?, parser.i16()?); SettingData::Color([red, blue, green, alpha]) }, }; Ok(Setting { name, data }) } } #[derive(Debug)] pub enum SettingType { Integer = 0, String = 1, Color = 2, } impl TryFrom for SettingType { type Error = ParserError; fn try_from(value: i8) -> Result { Ok(match value { 0 => Self::Integer, 1 => Self::String, 2 => Self::Color, x => return Err(ParserError::InvalidType(x)), }) } } /// Parser for the incoming byte stream. struct Parser<'a> { bytes: &'a [u8], endianness: Endianness, } impl<'a> Parser<'a> { /// Create a new parser. fn new(bytes: &'a [u8]) -> Result { let (endianness, bytes) = bytes.split_first().ok_or_else(|| ParserError::ran_out(1, 0))?; let endianness = match *endianness { BIG_ENDIAN => Endianness::Big, LITTLE_ENDIAN => Endianness::Little, _ => Endianness::native(), }; Ok(Self { // Ignore three bytes of padding and the four-byte serial. bytes: bytes.get(7..).ok_or_else(|| ParserError::ran_out(7, bytes.len()))?, endianness, }) } /// Get a slice of bytes. fn advance(&mut self, n: usize) -> Result<&'a [u8]> { if n == 0 { return Ok(&[]); } if n > self.bytes.len() { Err(ParserError::ran_out(n, self.bytes.len())) } else { let (part, rem) = self.bytes.split_at(n); self.bytes = rem; Ok(part) } } /// Skip some padding. fn pad(&mut self, size: usize, pad: usize) -> Result<()> { let advance = (pad - (size % pad)) % pad; self.advance(advance)?; Ok(()) } /// Get a single byte. fn i8(&mut self) -> Result { self.advance(1).map(|s| s[0] as i8) } /// Get two bytes. fn i16(&mut self) -> Result { self.advance(2).map(|s| { let bytes: &[u8; 2] = s.try_into().unwrap(); match self.endianness { Endianness::Big => i16::from_be_bytes(*bytes), Endianness::Little => i16::from_le_bytes(*bytes), } }) } /// Get four bytes. fn i32(&mut self) -> Result { self.advance(4).map(|s| { let bytes: &[u8; 4] = s.try_into().unwrap(); match self.endianness { Endianness::Big => i32::from_be_bytes(*bytes), Endianness::Little => i32::from_le_bytes(*bytes), } }) } } /// Endianness of the incoming data. enum Endianness { Little, Big, } impl Endianness { #[cfg(target_endian = "little")] fn native() -> Self { Endianness::Little } #[cfg(target_endian = "big")] fn native() -> Self { Endianness::Big } } /// Parser errors. #[allow(dead_code)] #[derive(Debug)] pub enum ParserError { /// Ran out of bytes. NoMoreBytes { expected: NonZeroUsize, found: usize }, /// Invalid type. InvalidType(i8), /// Bad setting type. BadType(SettingType), } impl ParserError { fn ran_out(expected: usize, found: usize) -> ParserError { let expected = NonZeroUsize::new(expected).unwrap(); Self::NoMoreBytes { expected, found } } } #[cfg(test)] /// Tests for the XSETTINGS parser. mod tests { use super::*; const XSETTINGS: &str = include_str!("tests/xsettings.dat"); #[test] fn empty() { let err = match read_settings(&[]) { Ok(_) => panic!(), Err(err) => err, }; match err { ParserError::NoMoreBytes { expected, found } => { assert_eq!(expected.get(), 1); assert_eq!(found, 0); }, _ => panic!(), } } #[test] fn parse_xsettings() { let data = XSETTINGS .trim() .split(',') .map(|tok| { let val = tok.strip_prefix("0x").unwrap(); u8::from_str_radix(val, 16).unwrap() }) .collect::>(); let settings = read_settings(&data).unwrap().collect::>>().unwrap(); let dpi = settings.iter().find(|s| s.name == b"Xft/DPI").unwrap(); assert_int(&dpi.data, 96 * 1024); let hinting = settings.iter().find(|s| s.name == b"Xft/Hinting").unwrap(); assert_int(&hinting.data, 1); let rgba = settings.iter().find(|s| s.name == b"Xft/RGBA").unwrap(); assert_string(&rgba.data, "rgb"); let lcd = settings.iter().find(|s| s.name == b"Xft/Lcdfilter").unwrap(); assert_string(&lcd.data, "lcddefault"); } fn assert_string(dat: &SettingData<'_>, s: &str) { match dat { SettingData::String(left) => assert_eq!(*left, s.as_bytes()), _ => panic!("invalid data type"), } } fn assert_int(dat: &SettingData<'_>, i: i32) { match dat { SettingData::Integer(left) => assert_eq!(*left, i), _ => panic!("invalid data type"), } } } winit-0.30.9/src/platform_impl/macos/app.rs000064400000000000000000000067671046102023000167530ustar 00000000000000#![allow(clippy::unnecessary_cast)] use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass}; use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; use objc2_foundation::{MainThreadMarker, NSObject}; use super::app_state::ApplicationDelegate; use crate::event::{DeviceEvent, ElementState}; declare_class!( pub(super) struct WinitApplication; unsafe impl ClassType for WinitApplication { #[inherits(NSResponder, NSObject)] type Super = NSApplication; type Mutability = mutability::MainThreadOnly; const NAME: &'static str = "WinitApplication"; } impl DeclaredClass for WinitApplication {} unsafe impl WinitApplication { // Normally, holding Cmd + any key never sends us a `keyUp` event for that key. // Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) // Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553) #[method(sendEvent:)] fn send_event(&self, event: &NSEvent) { // For posterity, there are some undocumented event types // (https://github.com/servo/cocoa-rs/issues/155) // but that doesn't really matter here. let event_type = unsafe { event.r#type() }; let modifier_flags = unsafe { event.modifierFlags() }; if event_type == NSEventType::KeyUp && modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand) { if let Some(key_window) = self.keyWindow() { key_window.sendEvent(event); } } else { let delegate = ApplicationDelegate::get(MainThreadMarker::from(self)); maybe_dispatch_device_event(&delegate, event); unsafe { msg_send![super(self), sendEvent: event] } } } } ); fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) { let event_type = unsafe { event.r#type() }; #[allow(non_upper_case_globals)] match event_type { NSEventType::MouseMoved | NSEventType::LeftMouseDragged | NSEventType::OtherMouseDragged | NSEventType::RightMouseDragged => { let delta_x = unsafe { event.deltaX() } as f64; let delta_y = unsafe { event.deltaY() } as f64; if delta_x != 0.0 { delegate.maybe_queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x }); } if delta_y != 0.0 { delegate.maybe_queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y }) } if delta_x != 0.0 || delta_y != 0.0 { delegate.maybe_queue_device_event(DeviceEvent::MouseMotion { delta: (delta_x, delta_y), }); } }, NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => { delegate.maybe_queue_device_event(DeviceEvent::Button { button: unsafe { event.buttonNumber() } as u32, state: ElementState::Pressed, }); }, NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => { delegate.maybe_queue_device_event(DeviceEvent::Button { button: unsafe { event.buttonNumber() } as u32, state: ElementState::Released, }); }, _ => (), } } winit-0.30.9/src/platform_impl/macos/app_state.rs000064400000000000000000000425321046102023000201410ustar 00000000000000use std::cell::{Cell, RefCell}; use std::mem; use std::rc::Weak; use std::time::Instant; use objc2::rc::Retained; use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; use objc2_app_kit::{ NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate, NSRunningApplication, }; use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol}; use super::event_handler::EventHandler; use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo}; use super::observer::{EventLoopWaker, RunLoop}; use super::{menu, WindowId, DEVICE_ID}; use crate::event::{DeviceEvent, Event, StartCause, WindowEvent}; use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow}; use crate::window::WindowId as RootWindowId; #[derive(Debug)] pub(super) struct AppState { activation_policy: Option, default_menu: bool, activate_ignoring_other_apps: bool, run_loop: RunLoop, event_handler: EventHandler, stop_on_launch: Cell, stop_before_wait: Cell, stop_after_wait: Cell, stop_on_redraw: Cell, /// Whether `applicationDidFinishLaunching:` has been run or not. is_launched: Cell, /// Whether an `EventLoop` is currently running. is_running: Cell, /// Whether the user has requested the event loop to exit. exit: Cell, control_flow: Cell, waker: RefCell, start_time: Cell>, wait_timeout: Cell>, pending_redraw: RefCell>, // NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and // as such should be careful to not add fields that, in turn, strongly reference those. } declare_class!( #[derive(Debug)] pub(super) struct ApplicationDelegate; unsafe impl ClassType for ApplicationDelegate { type Super = NSObject; type Mutability = mutability::MainThreadOnly; const NAME: &'static str = "WinitApplicationDelegate"; } impl DeclaredClass for ApplicationDelegate { type Ivars = AppState; } unsafe impl NSObjectProtocol for ApplicationDelegate {} unsafe impl NSApplicationDelegate for ApplicationDelegate { #[method(applicationDidFinishLaunching:)] fn app_did_finish_launching(&self, notification: &NSNotification) { self.did_finish_launching(notification) } #[method(applicationWillTerminate:)] fn app_will_terminate(&self, notification: &NSNotification) { self.will_terminate(notification) } } ); impl ApplicationDelegate { pub(super) fn new( mtm: MainThreadMarker, activation_policy: Option, default_menu: bool, activate_ignoring_other_apps: bool, ) -> Retained { let this = mtm.alloc().set_ivars(AppState { activation_policy, default_menu, activate_ignoring_other_apps, run_loop: RunLoop::main(mtm), event_handler: EventHandler::new(), stop_on_launch: Cell::new(false), stop_before_wait: Cell::new(false), stop_after_wait: Cell::new(false), stop_on_redraw: Cell::new(false), is_launched: Cell::new(false), is_running: Cell::new(false), exit: Cell::new(false), control_flow: Cell::new(ControlFlow::default()), waker: RefCell::new(EventLoopWaker::new()), start_time: Cell::new(None), wait_timeout: Cell::new(None), pending_redraw: RefCell::new(vec![]), }); unsafe { msg_send_id![super(this), init] } } // NOTE: This will, globally, only be run once, no matter how many // `EventLoop`s the user creates. fn did_finish_launching(&self, _notification: &NSNotification) { trace_scope!("applicationDidFinishLaunching:"); self.ivars().is_launched.set(true); let mtm = MainThreadMarker::from(self); let app = NSApplication::sharedApplication(mtm); // We need to delay setting the activation policy and activating the app // until `applicationDidFinishLaunching` has been called. Otherwise the // menu bar is initially unresponsive on macOS 10.15. // If no activation policy is explicitly provided, do not set it at all // to allow the package manifest to define behavior via LSUIElement. if let Some(activation_policy) = self.ivars().activation_policy { app.setActivationPolicy(activation_policy); } else { // If no activation policy is explicitly provided, and the application // is bundled, do not set the activation policy at all, to allow the // package manifest to define the behavior via LSUIElement. // // See: // - https://github.com/rust-windowing/winit/issues/261 // - https://github.com/rust-windowing/winit/issues/3958 let is_bundled = unsafe { NSRunningApplication::currentApplication().bundleIdentifier().is_some() }; if !is_bundled { app.setActivationPolicy(NSApplicationActivationPolicy::Regular); } } window_activation_hack(&app); #[allow(deprecated)] app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps); if self.ivars().default_menu { // The menubar initialization should be before the `NewEvents` event, to allow // overriding of the default menu even if it's created menu::initialize(&app); } self.ivars().waker.borrow_mut().start(); self.set_is_running(true); self.dispatch_init_events(); // If the application is being launched via `EventLoop::pump_app_events()` then we'll // want to stop the app once it is launched (and return to the external loop) // // In this case we still want to consider Winit's `EventLoop` to be "running", // so we call `start_running()` above. if self.ivars().stop_on_launch.get() { // NOTE: the original idea had been to only stop the underlying `RunLoop` // for the app but that didn't work as expected (`-[NSApplication run]` // effectively ignored the attempt to stop the RunLoop and re-started it). // // So we return from `pump_events` by stopping the application. let app = NSApplication::sharedApplication(mtm); stop_app_immediately(&app); } } fn will_terminate(&self, _notification: &NSNotification) { trace_scope!("applicationWillTerminate:"); // TODO: Notify every window that it will be destroyed, like done in iOS? self.internal_exit(); } pub fn get(mtm: MainThreadMarker) -> Retained { let app = NSApplication::sharedApplication(mtm); let delegate = unsafe { app.delegate() }.expect("a delegate was not configured on the application"); if delegate.is_kind_of::() { // SAFETY: Just checked that the delegate is an instance of `ApplicationDelegate` unsafe { Retained::cast(delegate) } } else { panic!("tried to get a delegate that was not the one Winit has registered") } } /// Place the event handler in the application delegate for the duration /// of the given closure. pub fn set_event_handler( &self, handler: impl FnMut(Event, &RootActiveEventLoop), closure: impl FnOnce() -> R, ) -> R { self.ivars().event_handler.set(handler, closure) } /// If `pump_events` is called to progress the event loop then we /// bootstrap the event loop via `-[NSApplication run]` but will use /// `CFRunLoopRunInMode` for subsequent calls to `pump_events`. pub fn set_stop_on_launch(&self) { self.ivars().stop_on_launch.set(true); } pub fn set_stop_before_wait(&self, value: bool) { self.ivars().stop_before_wait.set(value) } pub fn set_stop_after_wait(&self, value: bool) { self.ivars().stop_after_wait.set(value) } pub fn set_stop_on_redraw(&self, value: bool) { self.ivars().stop_on_redraw.set(value) } pub fn set_wait_timeout(&self, value: Option) { self.ivars().wait_timeout.set(value) } /// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits. /// /// NOTE: that if the `NSApplication` has been launched then that state is preserved, /// and we won't need to re-launch the app if subsequent EventLoops are run. pub fn internal_exit(&self) { self.handle_event(Event::LoopExiting); self.set_is_running(false); self.set_stop_on_redraw(false); self.set_stop_before_wait(false); self.set_stop_after_wait(false); self.set_wait_timeout(None); } pub fn is_launched(&self) -> bool { self.ivars().is_launched.get() } pub fn set_is_running(&self, value: bool) { self.ivars().is_running.set(value) } pub fn is_running(&self) -> bool { self.ivars().is_running.get() } pub fn exit(&self) { self.ivars().exit.set(true) } pub fn clear_exit(&self) { self.ivars().exit.set(false) } pub fn exiting(&self) -> bool { self.ivars().exit.get() } pub fn set_control_flow(&self, value: ControlFlow) { self.ivars().control_flow.set(value) } pub fn control_flow(&self) -> ControlFlow { self.ivars().control_flow.get() } pub fn maybe_queue_window_event(&self, window_id: WindowId, event: WindowEvent) { self.maybe_queue_event(Event::WindowEvent { window_id: RootWindowId(window_id), event }); } pub fn handle_window_event(&self, window_id: WindowId, event: WindowEvent) { self.handle_event(Event::WindowEvent { window_id: RootWindowId(window_id), event }); } pub fn maybe_queue_device_event(&self, event: DeviceEvent) { self.maybe_queue_event(Event::DeviceEvent { device_id: DEVICE_ID, event }); } pub fn handle_redraw(&self, window_id: WindowId) { let mtm = MainThreadMarker::from(self); // Redraw request might come out of order from the OS. // -> Don't go back into the event handler when our callstack originates from there if !self.ivars().event_handler.in_use() { self.handle_event(Event::WindowEvent { window_id: RootWindowId(window_id), event: WindowEvent::RedrawRequested, }); // `pump_events` will request to stop immediately _after_ dispatching RedrawRequested // events as a way to ensure that `pump_events` can't block an external loop // indefinitely if self.ivars().stop_on_redraw.get() { let app = NSApplication::sharedApplication(mtm); stop_app_immediately(&app); } } } pub fn queue_redraw(&self, window_id: WindowId) { let mut pending_redraw = self.ivars().pending_redraw.borrow_mut(); if !pending_redraw.contains(&window_id) { pending_redraw.push(window_id); } self.ivars().run_loop.wakeup(); } #[track_caller] fn maybe_queue_event(&self, event: Event) { // Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.) // result in an event being queued, and applied at a later point. // // However, it is not documented which actions do this, and which ones are done immediately, // so to make sure that we don't encounter re-entrancy issues, we first check if we're // currently handling another event, and if we are, we queue the event instead. if !self.ivars().event_handler.in_use() { self.handle_event(event); } else { tracing::debug!(?event, "had to queue event since another is currently being handled"); let this = self.retain(); self.ivars().run_loop.queue_closure(move || this.handle_event(event)); } } #[track_caller] fn handle_event(&self, event: Event) { self.ivars().event_handler.handle_event(event, &ActiveEventLoop::new_root(self.retain())) } /// dispatch `NewEvents(Init)` + `Resumed` pub fn dispatch_init_events(&self) { self.handle_event(Event::NewEvents(StartCause::Init)); // NB: For consistency all platforms must emit a 'resumed' event even though macOS // applications don't themselves have a formal suspend/resume lifecycle. self.handle_event(Event::Resumed); } // Called by RunLoopObserver after finishing waiting for new events pub fn wakeup(&self, panic_info: Weak) { let mtm = MainThreadMarker::from(self); let panic_info = panic_info .upgrade() .expect("The panic info must exist here. This failure indicates a developer error."); // Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779 if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() { return; } if self.ivars().stop_after_wait.get() { let app = NSApplication::sharedApplication(mtm); stop_app_immediately(&app); } let start = self.ivars().start_time.get().unwrap(); let cause = match self.control_flow() { ControlFlow::Poll => StartCause::Poll, ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None }, ControlFlow::WaitUntil(requested_resume) => { if Instant::now() >= requested_resume { StartCause::ResumeTimeReached { start, requested_resume } } else { StartCause::WaitCancelled { start, requested_resume: Some(requested_resume) } } }, }; self.handle_event(Event::NewEvents(cause)); } // Called by RunLoopObserver before waiting for new events pub fn cleared(&self, panic_info: Weak) { let mtm = MainThreadMarker::from(self); let panic_info = panic_info .upgrade() .expect("The panic info must exist here. This failure indicates a developer error."); // Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779 // XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if // we're about to return to the `CFRunLoop` to poll for new events? if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() { return; } self.handle_event(Event::UserEvent(HandlePendingUserEvents)); let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut()); for window_id in redraw { self.handle_event(Event::WindowEvent { window_id: RootWindowId(window_id), event: WindowEvent::RedrawRequested, }); } self.handle_event(Event::AboutToWait); if self.exiting() { let app = NSApplication::sharedApplication(mtm); stop_app_immediately(&app); } if self.ivars().stop_before_wait.get() { let app = NSApplication::sharedApplication(mtm); stop_app_immediately(&app); } self.ivars().start_time.set(Some(Instant::now())); let wait_timeout = self.ivars().wait_timeout.get(); // configured by pump_events let app_timeout = match self.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Instant::now()), ControlFlow::WaitUntil(instant) => Some(instant), }; self.ivars().waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout)); } } #[derive(Debug)] pub(crate) struct HandlePendingUserEvents; /// Returns the minimum `Option`, taking into account that `None` /// equates to an infinite timeout, not a zero timeout (so can't just use /// `Option::min`) fn min_timeout(a: Option, b: Option) -> Option { a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))) } /// A hack to make activation of multiple windows work when creating them before /// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`. /// /// Alternative to this would be the user calling `window.set_visible(true)` in /// `StartCause::Init`. /// /// If this becomes too bothersome to maintain, it can probably be removed /// without too much damage. fn window_activation_hack(app: &NSApplication) { // TODO: Proper ordering of the windows app.windows().into_iter().for_each(|window| { // Call `makeKeyAndOrderFront` if it was called on the window in `WinitWindow::new` // This way we preserve the user's desired initial visibility status // TODO: Also filter on the type/"level" of the window, and maybe other things? if window.isVisible() { tracing::trace!("Activating visible window"); window.makeKeyAndOrderFront(None); } else { tracing::trace!("Skipping activating invisible window"); } }) } winit-0.30.9/src/platform_impl/macos/cursor.rs000064400000000000000000000211501046102023000174670ustar 00000000000000use std::ffi::c_uchar; use std::slice; use std::sync::OnceLock; use objc2::rc::Retained; use objc2::runtime::Sel; use objc2::{msg_send, msg_send_id, sel, ClassType}; use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage}; use objc2_foundation::{ ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, NSString, }; use crate::cursor::{CursorImage, OnlyCursorImageSource}; use crate::window::CursorIcon; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct CustomCursor(pub(crate) Retained); // SAFETY: NSCursor is immutable and thread-safe // TODO(madsmtm): Put this logic in objc2-app-kit itself unsafe impl Send for CustomCursor {} unsafe impl Sync for CustomCursor {} impl CustomCursor { pub(crate) fn new(cursor: OnlyCursorImageSource) -> CustomCursor { Self(cursor_from_image(&cursor.0)) } } pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Retained { let width = cursor.width; let height = cursor.height; let bitmap = unsafe { NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel( NSBitmapImageRep::alloc(), std::ptr::null_mut::<*mut c_uchar>(), width as isize, height as isize, 8, 4, true, false, NSDeviceRGBColorSpace, width as isize * 4, 32, ).unwrap() }; let bitmap_data = unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.rgba.len()) }; bitmap_data.copy_from_slice(&cursor.rgba); let image = unsafe { NSImage::initWithSize(NSImage::alloc(), NSSize::new(width.into(), height.into())) }; unsafe { image.addRepresentation(&bitmap) }; let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64); NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot) } pub(crate) fn default_cursor() -> Retained { NSCursor::arrowCursor() } unsafe fn try_cursor_from_selector(sel: Sel) -> Option> { let cls = NSCursor::class(); if msg_send![cls, respondsToSelector: sel] { let cursor: Retained = unsafe { msg_send_id![cls, performSelector: sel] }; Some(cursor) } else { tracing::warn!("cursor `{sel}` appears to be invalid"); None } } macro_rules! def_undocumented_cursor { {$( $(#[$($m:meta)*])* fn $name:ident(); )*} => {$( $(#[$($m)*])* #[allow(non_snake_case)] fn $name() -> Retained { unsafe { try_cursor_from_selector(sel!($name)).unwrap_or_else(|| default_cursor()) } } )*}; } def_undocumented_cursor!( // Undocumented cursors: https://stackoverflow.com/a/46635398/5435443 fn _helpCursor(); fn _zoomInCursor(); fn _zoomOutCursor(); fn _windowResizeNorthEastCursor(); fn _windowResizeNorthWestCursor(); fn _windowResizeSouthEastCursor(); fn _windowResizeSouthWestCursor(); fn _windowResizeNorthEastSouthWestCursor(); fn _windowResizeNorthWestSouthEastCursor(); // While these two are available, the former just loads a white arrow, // and the latter loads an ugly deflated beachball! // pub fn _moveCursor(); // pub fn _waitCursor(); // An even more undocumented cursor... // https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349 fn busyButClickableCursor(); ); // Note that loading `busybutclickable` with this code won't animate // the frames; instead you'll just get them all in a column. unsafe fn load_webkit_cursor(name: &NSString) -> Retained { // Snatch a cursor from WebKit; They fit the style of the native // cursors, and will seem completely standard to macOS users. // // https://stackoverflow.com/a/21786835/5435443 let root = ns_string!( "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/\ HIServices.framework/Versions/A/Resources/cursors" ); let cursor_path = root.stringByAppendingPathComponent(name); let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf")); let image = NSImage::initByReferencingFile(NSImage::alloc(), &pdf_path).unwrap(); // TODO: Handle PLists better let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist")); let info: Retained> = unsafe { msg_send_id![ >::class(), dictionaryWithContentsOfFile: &*info_path, ] }; let mut x = 0.0; if let Some(n) = info.get(&*ns_string!("hotx")) { if n.is_kind_of::() { let ptr: *const NSObject = n; let ptr: *const NSNumber = ptr.cast(); x = unsafe { &*ptr }.as_cgfloat() } } let mut y = 0.0; if let Some(n) = info.get(&*ns_string!("hotx")) { if n.is_kind_of::() { let ptr: *const NSObject = n; let ptr: *const NSNumber = ptr.cast(); y = unsafe { &*ptr }.as_cgfloat() } } let hotspot = NSPoint::new(x, y); NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot) } fn webkit_move() -> Retained { unsafe { load_webkit_cursor(ns_string!("move")) } } fn webkit_cell() -> Retained { unsafe { load_webkit_cursor(ns_string!("cell")) } } pub(crate) fn invisible_cursor() -> Retained { // 16x16 GIF data for invisible cursor // You can reproduce this via ImageMagick. // $ convert -size 16x16 xc:none cursor.gif static CURSOR_BYTES: &[u8] = &[ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0e, 0x84, 0x8f, 0xa9, 0xcb, 0xed, 0x0f, 0xa3, 0x9c, 0xb4, 0xda, 0x8b, 0xb3, 0x3e, 0x05, 0x00, 0x3b, ]; fn new_invisible() -> Retained { // TODO: Consider using `dataWithBytesNoCopy:` let data = NSData::with_bytes(CURSOR_BYTES); let image = NSImage::initWithData(NSImage::alloc(), &data).unwrap(); let hotspot = NSPoint::new(0.0, 0.0); NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot) } // Cache this for efficiency static CURSOR: OnceLock = OnceLock::new(); CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone() } pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained { match icon { CursorIcon::Default => default_cursor(), CursorIcon::Pointer => NSCursor::pointingHandCursor(), CursorIcon::Grab => NSCursor::openHandCursor(), CursorIcon::Grabbing => NSCursor::closedHandCursor(), CursorIcon::Text => NSCursor::IBeamCursor(), CursorIcon::VerticalText => NSCursor::IBeamCursorForVerticalLayout(), CursorIcon::Copy => NSCursor::dragCopyCursor(), CursorIcon::Alias => NSCursor::dragLinkCursor(), CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(), CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(), CursorIcon::Crosshair => NSCursor::crosshairCursor(), CursorIcon::EResize => NSCursor::resizeRightCursor(), CursorIcon::NResize => NSCursor::resizeUpCursor(), CursorIcon::WResize => NSCursor::resizeLeftCursor(), CursorIcon::SResize => NSCursor::resizeDownCursor(), CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(), CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(), CursorIcon::Help => _helpCursor(), CursorIcon::ZoomIn => _zoomInCursor(), CursorIcon::ZoomOut => _zoomOutCursor(), CursorIcon::NeResize => _windowResizeNorthEastCursor(), CursorIcon::NwResize => _windowResizeNorthWestCursor(), CursorIcon::SeResize => _windowResizeSouthEastCursor(), CursorIcon::SwResize => _windowResizeSouthWestCursor(), CursorIcon::NeswResize => _windowResizeNorthEastSouthWestCursor(), CursorIcon::NwseResize => _windowResizeNorthWestSouthEastCursor(), // This is the wrong semantics for `Wait`, but it's the same as // what's used in Safari and Chrome. CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(), CursorIcon::Move | CursorIcon::AllScroll => webkit_move(), CursorIcon::Cell => webkit_cell(), _ => default_cursor(), } } winit-0.30.9/src/platform_impl/macos/event.rs000064400000000000000000000561201046102023000173000ustar 00000000000000use std::ffi::c_void; use core_foundation::base::CFRelease; use core_foundation::data::{CFDataGetBytePtr, CFDataRef}; use objc2::rc::Retained; use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType}; use objc2_foundation::{run_on_main, NSPoint}; use smol_str::SmolStr; use crate::event::{ElementState, KeyEvent, Modifiers}; use crate::keyboard::{ Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode, PhysicalKey, }; use crate::platform_impl::platform::ffi; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct KeyEventExtra { pub text_with_all_modifiers: Option, pub key_without_modifiers: Key, } /// Ignores ALL modifiers. pub fn get_modifierless_char(scancode: u16) -> Key { let mut string = [0; 16]; let input_source; let layout; unsafe { input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource(); if input_source.is_null() { tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr"); return Key::Unidentified(NativeKey::MacOS(scancode)); } let layout_data = ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData); if layout_data.is_null() { CFRelease(input_source as *mut c_void); tracing::error!("`TISGetInputSourceProperty` returned null ptr"); return Key::Unidentified(NativeKey::MacOS(scancode)); } layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout; } let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() }); let mut result_len = 0; let mut dead_keys = 0; let modifiers = 0; let translate_result = unsafe { ffi::UCKeyTranslate( layout, scancode, ffi::kUCKeyActionDisplay, modifiers, keyboard_type as u32, ffi::kUCKeyTranslateNoDeadKeysMask, &mut dead_keys, string.len() as ffi::UniCharCount, &mut result_len, string.as_mut_ptr(), ) }; unsafe { CFRelease(input_source as *mut c_void); } if translate_result != 0 { tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result); return Key::Unidentified(NativeKey::MacOS(scancode)); } if result_len == 0 { // This is fine - not all keys have text representation. // For instance, users that have mapped the `Fn` key to toggle // keyboard layouts will hit this code path. return Key::Unidentified(NativeKey::MacOS(scancode)); } let chars = String::from_utf16_lossy(&string[0..result_len as usize]); Key::Character(SmolStr::new(chars)) } // Ignores all modifiers except for SHIFT (yes, even ALT is ignored). fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key { let string = unsafe { ns_event.charactersIgnoringModifiers() } .map(|s| s.to_string()) .unwrap_or_default(); if string.is_empty() { // Probably a dead key let first_char = modifierless_chars.chars().next(); return Key::Dead(first_char); } Key::Character(SmolStr::new(string)) } /// Create `KeyEvent` for the given `NSEvent`. /// /// This function shouldn't be called when the IME input is in process. pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent { use ElementState::{Pressed, Released}; let state = if is_press { Pressed } else { Released }; let scancode = unsafe { ns_event.keyCode() }; let mut physical_key = scancode_to_physicalkey(scancode as u32); // NOTE: The logical key should heed both SHIFT and ALT if possible. // For instance: // * Pressing the A key: logical key should be "a" // * Pressing SHIFT A: logical key should be "A" // * Pressing CTRL SHIFT A: logical key should also be "A" // This is not easy to tease out of `NSEvent`, but we do our best. let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default(); let text_with_all_modifiers = if characters.is_empty() { None } else { if matches!(physical_key, PhysicalKey::Unidentified(_)) { // The key may be one of the funky function keys physical_key = extra_function_key_to_code(scancode, &characters); } Some(SmolStr::new(characters)) }; let key_from_code = code_to_key(physical_key, scancode); let (logical_key, key_without_modifiers) = if matches!(key_from_code, Key::Unidentified(_)) { // `get_modifierless_char/key_without_modifiers` ignores ALL modifiers. let key_without_modifiers = get_modifierless_char(scancode); let modifiers = unsafe { ns_event.modifierFlags() }; let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl); let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand); let logical_key = match text_with_all_modifiers.as_ref() { // Only checking for ctrl and cmd here, not checking for alt because we DO want to // include its effect in the key. For example if -on the Germay layout- one // presses alt+8, the logical key should be "{" // Also not checking if this is a release event because then this issue would // still affect the key release. Some(text) if !has_ctrl && !has_cmd => { // Character heeding both SHIFT and ALT. Key::Character(text.clone()) }, _ => match key_without_modifiers.as_ref() { // Character heeding just SHIFT, ignoring ALT. Key::Character(ch) => get_logical_key_char(ns_event, ch), // Character ignoring ALL modifiers. _ => key_without_modifiers.clone(), }, }; (logical_key, key_without_modifiers) } else { (key_from_code.clone(), key_from_code) }; let text = if is_press { logical_key.to_text().map(SmolStr::new) } else { None }; let location = code_to_location(physical_key); KeyEvent { location, logical_key, physical_key, repeat: is_repeat, state, text, platform_specific: KeyEventExtra { text_with_all_modifiers, key_without_modifiers }, } } pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key { let code = match key { PhysicalKey::Code(code) => code, PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()), }; Key::Named(match code { KeyCode::Enter => NamedKey::Enter, KeyCode::Tab => NamedKey::Tab, KeyCode::Space => NamedKey::Space, KeyCode::Backspace => NamedKey::Backspace, KeyCode::Escape => NamedKey::Escape, KeyCode::SuperRight => NamedKey::Super, KeyCode::SuperLeft => NamedKey::Super, KeyCode::ShiftLeft => NamedKey::Shift, KeyCode::AltLeft => NamedKey::Alt, KeyCode::ControlLeft => NamedKey::Control, KeyCode::ShiftRight => NamedKey::Shift, KeyCode::AltRight => NamedKey::Alt, KeyCode::ControlRight => NamedKey::Control, KeyCode::NumLock => NamedKey::NumLock, KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp, KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown, // Other numpad keys all generate text on macOS (if I understand correctly) KeyCode::NumpadEnter => NamedKey::Enter, KeyCode::F1 => NamedKey::F1, KeyCode::F2 => NamedKey::F2, KeyCode::F3 => NamedKey::F3, KeyCode::F4 => NamedKey::F4, KeyCode::F5 => NamedKey::F5, KeyCode::F6 => NamedKey::F6, KeyCode::F7 => NamedKey::F7, KeyCode::F8 => NamedKey::F8, KeyCode::F9 => NamedKey::F9, KeyCode::F10 => NamedKey::F10, KeyCode::F11 => NamedKey::F11, KeyCode::F12 => NamedKey::F12, KeyCode::F13 => NamedKey::F13, KeyCode::F14 => NamedKey::F14, KeyCode::F15 => NamedKey::F15, KeyCode::F16 => NamedKey::F16, KeyCode::F17 => NamedKey::F17, KeyCode::F18 => NamedKey::F18, KeyCode::F19 => NamedKey::F19, KeyCode::F20 => NamedKey::F20, KeyCode::Insert => NamedKey::Insert, KeyCode::Home => NamedKey::Home, KeyCode::PageUp => NamedKey::PageUp, KeyCode::Delete => NamedKey::Delete, KeyCode::End => NamedKey::End, KeyCode::PageDown => NamedKey::PageDown, KeyCode::ArrowLeft => NamedKey::ArrowLeft, KeyCode::ArrowRight => NamedKey::ArrowRight, KeyCode::ArrowDown => NamedKey::ArrowDown, KeyCode::ArrowUp => NamedKey::ArrowUp, _ => return Key::Unidentified(NativeKey::MacOS(scancode)), }) } pub fn code_to_location(key: PhysicalKey) -> KeyLocation { let code = match key { PhysicalKey::Code(code) => code, PhysicalKey::Unidentified(_) => return KeyLocation::Standard, }; match code { KeyCode::SuperRight => KeyLocation::Right, KeyCode::SuperLeft => KeyLocation::Left, KeyCode::ShiftLeft => KeyLocation::Left, KeyCode::AltLeft => KeyLocation::Left, KeyCode::ControlLeft => KeyLocation::Left, KeyCode::ShiftRight => KeyLocation::Right, KeyCode::AltRight => KeyLocation::Right, KeyCode::ControlRight => KeyLocation::Right, KeyCode::NumLock => KeyLocation::Numpad, KeyCode::NumpadDecimal => KeyLocation::Numpad, KeyCode::NumpadMultiply => KeyLocation::Numpad, KeyCode::NumpadAdd => KeyLocation::Numpad, KeyCode::NumpadDivide => KeyLocation::Numpad, KeyCode::NumpadEnter => KeyLocation::Numpad, KeyCode::NumpadSubtract => KeyLocation::Numpad, KeyCode::NumpadEqual => KeyLocation::Numpad, KeyCode::Numpad0 => KeyLocation::Numpad, KeyCode::Numpad1 => KeyLocation::Numpad, KeyCode::Numpad2 => KeyLocation::Numpad, KeyCode::Numpad3 => KeyLocation::Numpad, KeyCode::Numpad4 => KeyLocation::Numpad, KeyCode::Numpad5 => KeyLocation::Numpad, KeyCode::Numpad6 => KeyLocation::Numpad, KeyCode::Numpad7 => KeyLocation::Numpad, KeyCode::Numpad8 => KeyLocation::Numpad, KeyCode::Numpad9 => KeyLocation::Numpad, _ => KeyLocation::Standard, } } // While F1-F20 have scancodes we can match on, we have to check against UTF-16 // constants for the rest. // https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ pub fn extra_function_key_to_code(scancode: u16, string: &str) -> PhysicalKey { if let Some(ch) = string.encode_utf16().next() { match ch { 0xf718 => PhysicalKey::Code(KeyCode::F21), 0xf719 => PhysicalKey::Code(KeyCode::F22), 0xf71a => PhysicalKey::Code(KeyCode::F23), 0xf71b => PhysicalKey::Code(KeyCode::F24), _ => PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode)), } } else { PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode)) } } // The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259 const NX_DEVICELCTLKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000001); const NX_DEVICELSHIFTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000002); const NX_DEVICERSHIFTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000004); const NX_DEVICELCMDKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000008); const NX_DEVICERCMDKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000010); const NX_DEVICELALTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000020); const NX_DEVICERALTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000040); const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00002000); pub(super) fn lalt_pressed(event: &NSEvent) -> bool { unsafe { event.modifierFlags() }.contains(NX_DEVICELALTKEYMASK) } pub(super) fn ralt_pressed(event: &NSEvent) -> bool { unsafe { event.modifierFlags() }.contains(NX_DEVICERALTKEYMASK) } pub(super) fn event_mods(event: &NSEvent) -> Modifiers { let flags = unsafe { event.modifierFlags() }; let mut state = ModifiersState::empty(); let mut pressed_mods = ModifiersKeys::empty(); state .set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift)); pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK)); pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK)); state.set( ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::NSEventModifierFlagControl), ); pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK)); pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK)); state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSEventModifierFlagOption)); pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK)); pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK)); state.set( ModifiersState::SUPER, flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand), ); pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK)); pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK)); Modifiers { state, pressed_mods } } pub(super) fn dummy_event() -> Option> { unsafe { NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2( NSEventType::ApplicationDefined, NSPoint::new(0.0, 0.0), NSEventModifierFlags(0), 0.0, 0, None, NSEventSubtype::WindowExposed.0, 0, 0, ) } } pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option { let code = match physical_key { PhysicalKey::Code(code) => code, PhysicalKey::Unidentified(_) => return None, }; match code { KeyCode::KeyA => Some(0x00), KeyCode::KeyS => Some(0x01), KeyCode::KeyD => Some(0x02), KeyCode::KeyF => Some(0x03), KeyCode::KeyH => Some(0x04), KeyCode::KeyG => Some(0x05), KeyCode::KeyZ => Some(0x06), KeyCode::KeyX => Some(0x07), KeyCode::KeyC => Some(0x08), KeyCode::KeyV => Some(0x09), KeyCode::KeyB => Some(0x0b), KeyCode::KeyQ => Some(0x0c), KeyCode::KeyW => Some(0x0d), KeyCode::KeyE => Some(0x0e), KeyCode::KeyR => Some(0x0f), KeyCode::KeyY => Some(0x10), KeyCode::KeyT => Some(0x11), KeyCode::Digit1 => Some(0x12), KeyCode::Digit2 => Some(0x13), KeyCode::Digit3 => Some(0x14), KeyCode::Digit4 => Some(0x15), KeyCode::Digit6 => Some(0x16), KeyCode::Digit5 => Some(0x17), KeyCode::Equal => Some(0x18), KeyCode::Digit9 => Some(0x19), KeyCode::Digit7 => Some(0x1a), KeyCode::Minus => Some(0x1b), KeyCode::Digit8 => Some(0x1c), KeyCode::Digit0 => Some(0x1d), KeyCode::BracketRight => Some(0x1e), KeyCode::KeyO => Some(0x1f), KeyCode::KeyU => Some(0x20), KeyCode::BracketLeft => Some(0x21), KeyCode::KeyI => Some(0x22), KeyCode::KeyP => Some(0x23), KeyCode::Enter => Some(0x24), KeyCode::KeyL => Some(0x25), KeyCode::KeyJ => Some(0x26), KeyCode::Quote => Some(0x27), KeyCode::KeyK => Some(0x28), KeyCode::Semicolon => Some(0x29), KeyCode::Backslash => Some(0x2a), KeyCode::Comma => Some(0x2b), KeyCode::Slash => Some(0x2c), KeyCode::KeyN => Some(0x2d), KeyCode::KeyM => Some(0x2e), KeyCode::Period => Some(0x2f), KeyCode::Tab => Some(0x30), KeyCode::Space => Some(0x31), KeyCode::Backquote => Some(0x32), KeyCode::Backspace => Some(0x33), KeyCode::Escape => Some(0x35), KeyCode::SuperRight => Some(0x36), KeyCode::SuperLeft => Some(0x37), KeyCode::ShiftLeft => Some(0x38), KeyCode::AltLeft => Some(0x3a), KeyCode::ControlLeft => Some(0x3b), KeyCode::ShiftRight => Some(0x3c), KeyCode::AltRight => Some(0x3d), KeyCode::ControlRight => Some(0x3e), KeyCode::F17 => Some(0x40), KeyCode::NumpadDecimal => Some(0x41), KeyCode::NumpadMultiply => Some(0x43), KeyCode::NumpadAdd => Some(0x45), KeyCode::NumLock => Some(0x47), KeyCode::AudioVolumeUp => Some(0x49), KeyCode::AudioVolumeDown => Some(0x4a), KeyCode::NumpadDivide => Some(0x4b), KeyCode::NumpadEnter => Some(0x4c), KeyCode::NumpadSubtract => Some(0x4e), KeyCode::F18 => Some(0x4f), KeyCode::F19 => Some(0x50), KeyCode::NumpadEqual => Some(0x51), KeyCode::Numpad0 => Some(0x52), KeyCode::Numpad1 => Some(0x53), KeyCode::Numpad2 => Some(0x54), KeyCode::Numpad3 => Some(0x55), KeyCode::Numpad4 => Some(0x56), KeyCode::Numpad5 => Some(0x57), KeyCode::Numpad6 => Some(0x58), KeyCode::Numpad7 => Some(0x59), KeyCode::F20 => Some(0x5a), KeyCode::Numpad8 => Some(0x5b), KeyCode::Numpad9 => Some(0x5c), KeyCode::IntlYen => Some(0x5d), KeyCode::F5 => Some(0x60), KeyCode::F6 => Some(0x61), KeyCode::F7 => Some(0x62), KeyCode::F3 => Some(0x63), KeyCode::F8 => Some(0x64), KeyCode::F9 => Some(0x65), KeyCode::F11 => Some(0x67), KeyCode::F13 => Some(0x69), KeyCode::F16 => Some(0x6a), KeyCode::F14 => Some(0x6b), KeyCode::F10 => Some(0x6d), KeyCode::F12 => Some(0x6f), KeyCode::F15 => Some(0x71), KeyCode::Insert => Some(0x72), KeyCode::Home => Some(0x73), KeyCode::PageUp => Some(0x74), KeyCode::Delete => Some(0x75), KeyCode::F4 => Some(0x76), KeyCode::End => Some(0x77), KeyCode::F2 => Some(0x78), KeyCode::PageDown => Some(0x79), KeyCode::F1 => Some(0x7a), KeyCode::ArrowLeft => Some(0x7b), KeyCode::ArrowRight => Some(0x7c), KeyCode::ArrowDown => Some(0x7d), KeyCode::ArrowUp => Some(0x7e), _ => None, } } pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { PhysicalKey::Code(match scancode { 0x00 => KeyCode::KeyA, 0x01 => KeyCode::KeyS, 0x02 => KeyCode::KeyD, 0x03 => KeyCode::KeyF, 0x04 => KeyCode::KeyH, 0x05 => KeyCode::KeyG, 0x06 => KeyCode::KeyZ, 0x07 => KeyCode::KeyX, 0x08 => KeyCode::KeyC, 0x09 => KeyCode::KeyV, // 0x0a => World 1, 0x0b => KeyCode::KeyB, 0x0c => KeyCode::KeyQ, 0x0d => KeyCode::KeyW, 0x0e => KeyCode::KeyE, 0x0f => KeyCode::KeyR, 0x10 => KeyCode::KeyY, 0x11 => KeyCode::KeyT, 0x12 => KeyCode::Digit1, 0x13 => KeyCode::Digit2, 0x14 => KeyCode::Digit3, 0x15 => KeyCode::Digit4, 0x16 => KeyCode::Digit6, 0x17 => KeyCode::Digit5, 0x18 => KeyCode::Equal, 0x19 => KeyCode::Digit9, 0x1a => KeyCode::Digit7, 0x1b => KeyCode::Minus, 0x1c => KeyCode::Digit8, 0x1d => KeyCode::Digit0, 0x1e => KeyCode::BracketRight, 0x1f => KeyCode::KeyO, 0x20 => KeyCode::KeyU, 0x21 => KeyCode::BracketLeft, 0x22 => KeyCode::KeyI, 0x23 => KeyCode::KeyP, 0x24 => KeyCode::Enter, 0x25 => KeyCode::KeyL, 0x26 => KeyCode::KeyJ, 0x27 => KeyCode::Quote, 0x28 => KeyCode::KeyK, 0x29 => KeyCode::Semicolon, 0x2a => KeyCode::Backslash, 0x2b => KeyCode::Comma, 0x2c => KeyCode::Slash, 0x2d => KeyCode::KeyN, 0x2e => KeyCode::KeyM, 0x2f => KeyCode::Period, 0x30 => KeyCode::Tab, 0x31 => KeyCode::Space, 0x32 => KeyCode::Backquote, 0x33 => KeyCode::Backspace, // 0x34 => unknown, 0x35 => KeyCode::Escape, 0x36 => KeyCode::SuperRight, 0x37 => KeyCode::SuperLeft, 0x38 => KeyCode::ShiftLeft, 0x39 => KeyCode::CapsLock, 0x3a => KeyCode::AltLeft, 0x3b => KeyCode::ControlLeft, 0x3c => KeyCode::ShiftRight, 0x3d => KeyCode::AltRight, 0x3e => KeyCode::ControlRight, 0x3f => KeyCode::Fn, 0x40 => KeyCode::F17, 0x41 => KeyCode::NumpadDecimal, // 0x42 -> unknown, 0x43 => KeyCode::NumpadMultiply, // 0x44 => unknown, 0x45 => KeyCode::NumpadAdd, // 0x46 => unknown, 0x47 => KeyCode::NumLock, // 0x48 => KeyCode::NumpadClear, // TODO: (Artur) for me, kVK_VolumeUp is 0x48 // macOS 10.11 // /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/ // Versions/A/Headers/Events.h 0x49 => KeyCode::AudioVolumeUp, 0x4a => KeyCode::AudioVolumeDown, 0x4b => KeyCode::NumpadDivide, 0x4c => KeyCode::NumpadEnter, // 0x4d => unknown, 0x4e => KeyCode::NumpadSubtract, 0x4f => KeyCode::F18, 0x50 => KeyCode::F19, 0x51 => KeyCode::NumpadEqual, 0x52 => KeyCode::Numpad0, 0x53 => KeyCode::Numpad1, 0x54 => KeyCode::Numpad2, 0x55 => KeyCode::Numpad3, 0x56 => KeyCode::Numpad4, 0x57 => KeyCode::Numpad5, 0x58 => KeyCode::Numpad6, 0x59 => KeyCode::Numpad7, 0x5a => KeyCode::F20, 0x5b => KeyCode::Numpad8, 0x5c => KeyCode::Numpad9, 0x5d => KeyCode::IntlYen, // 0x5e => JIS Ro, // 0x5f => unknown, 0x60 => KeyCode::F5, 0x61 => KeyCode::F6, 0x62 => KeyCode::F7, 0x63 => KeyCode::F3, 0x64 => KeyCode::F8, 0x65 => KeyCode::F9, // 0x66 => JIS Eisuu (macOS), 0x67 => KeyCode::F11, // 0x68 => JIS Kanna (macOS), 0x69 => KeyCode::F13, 0x6a => KeyCode::F16, 0x6b => KeyCode::F14, // 0x6c => unknown, 0x6d => KeyCode::F10, // 0x6e => unknown, 0x6f => KeyCode::F12, // 0x70 => unknown, 0x71 => KeyCode::F15, 0x72 => KeyCode::Insert, 0x73 => KeyCode::Home, 0x74 => KeyCode::PageUp, 0x75 => KeyCode::Delete, 0x76 => KeyCode::F4, 0x77 => KeyCode::End, 0x78 => KeyCode::F2, 0x79 => KeyCode::PageDown, 0x7a => KeyCode::F1, 0x7b => KeyCode::ArrowLeft, 0x7c => KeyCode::ArrowRight, 0x7d => KeyCode::ArrowDown, 0x7e => KeyCode::ArrowUp, // 0x7f => unknown, // 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as // backquote (`) on Windows' US layout. 0xa => KeyCode::Backquote, _ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)), }) } winit-0.30.9/src/platform_impl/macos/event_handler.rs000064400000000000000000000122671046102023000210010ustar 00000000000000use std::cell::RefCell; use std::{fmt, mem}; use super::app_state::HandlePendingUserEvents; use crate::event::Event; use crate::event_loop::ActiveEventLoop as RootActiveEventLoop; struct EventHandlerData { #[allow(clippy::type_complexity)] handler: Box, &RootActiveEventLoop) + 'static>, } impl fmt::Debug for EventHandlerData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("EventHandlerData").finish_non_exhaustive() } } #[derive(Debug)] pub(crate) struct EventHandler { /// This can be in the following states: /// - Not registered by the event loop (None). /// - Present (Some(handler)). /// - Currently executing the handler / in use (RefCell borrowed). inner: RefCell>, } impl EventHandler { pub(crate) const fn new() -> Self { Self { inner: RefCell::new(None) } } /// Set the event loop handler for the duration of the given closure. /// /// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates /// to store the handler in a thread local, such that it can be accessed /// from within the closure. pub(crate) fn set<'handler, R>( &self, handler: impl FnMut(Event, &RootActiveEventLoop) + 'handler, closure: impl FnOnce() -> R, ) -> R { // SAFETY: We extend the lifetime of the handler here so that we can // store it in `EventHandler`'s `RefCell`. // // This is sound, since we make sure to unset the handler again at the // end of this function, and as such the lifetime isn't actually // extended beyond `'handler`. let handler = unsafe { mem::transmute::< Box, &RootActiveEventLoop) + 'handler>, Box, &RootActiveEventLoop) + 'static>, >(Box::new(handler)) }; match self.inner.try_borrow_mut().as_deref_mut() { Ok(Some(_)) => { unreachable!("tried to set handler while another was already set"); }, Ok(data @ None) => { *data = Some(EventHandlerData { handler }); }, Err(_) => { unreachable!("tried to set handler that is currently in use"); }, } struct ClearOnDrop<'a>(&'a EventHandler); impl Drop for ClearOnDrop<'_> { fn drop(&mut self) { match self.0.inner.try_borrow_mut().as_deref_mut() { Ok(data @ Some(_)) => { *data = None; }, Ok(None) => { tracing::error!("tried to clear handler, but no handler was set"); }, Err(_) => { // Note: This is not expected to ever happen, this // module generally controls the `RefCell`, and // prevents it from ever being borrowed outside of it. // // But if it _does_ happen, it is a serious error, and // we must abort the process, it'd be unsound if we // weren't able to unset the handler. eprintln!("tried to clear handler that is currently in use"); std::process::abort(); }, } } } let _clear_on_drop = ClearOnDrop(self); // Note: The RefCell should not be borrowed while executing the // closure, that'd defeat the whole point. closure() // `_clear_on_drop` will be dropped here, or when unwinding, ensuring // soundness. } pub(crate) fn in_use(&self) -> bool { self.inner.try_borrow().is_err() } pub(crate) fn ready(&self) -> bool { matches!(self.inner.try_borrow().as_deref(), Ok(Some(_))) } pub(crate) fn handle_event( &self, event: Event, event_loop: &RootActiveEventLoop, ) { match self.inner.try_borrow_mut().as_deref_mut() { Ok(Some(EventHandlerData { handler })) => { // It is important that we keep the reference borrowed here, // so that `in_use` can properly detect that the handler is // still in use. // // If the handler unwinds, the `RefMut` will ensure that the // handler is no longer borrowed. (handler)(event, event_loop); }, Ok(None) => { // `NSApplication`, our app delegate and this handler are all // global state and so it's not impossible that we could get // an event after the application has exited the `EventLoop`. tracing::error!("tried to run event handler, but no handler was set"); }, Err(_) => { // Prevent re-entrancy. panic!("tried to handle event while another event is currently being handled"); }, } } } winit-0.30.9/src/platform_impl/macos/event_loop.rs000064400000000000000000000430311046102023000203260ustar 00000000000000use std::any::Any; use std::cell::Cell; use std::collections::VecDeque; use std::marker::PhantomData; use std::os::raw::c_void; use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe}; use std::ptr; use std::rc::{Rc, Weak}; use std::sync::mpsc; use std::time::{Duration, Instant}; use core_foundation::base::{CFIndex, CFRelease}; use core_foundation::runloop::{ kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, }; use objc2::rc::{autoreleasepool, Retained}; use objc2::runtime::ProtocolObject; use objc2::{msg_send_id, sel, ClassType}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow}; use objc2_foundation::{MainThreadMarker, NSObjectProtocol}; use super::app::WinitApplication; use super::app_state::{ApplicationDelegate, HandlePendingUserEvents}; use super::event::dummy_event; use super::monitor::{self, MonitorHandle}; use super::observer::setup_control_flow_observers; use crate::error::EventLoopError; use crate::event::Event; use crate::event_loop::{ ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents, EventLoopClosed, }; use crate::platform::macos::ActivationPolicy; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::platform::cursor::CustomCursor; use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme}; #[derive(Default)] pub struct PanicInfo { inner: Cell>>, } // WARNING: // As long as this struct is used through its `impl`, it is UnwindSafe. // (If `get_mut` is called on `inner`, unwind safety may get broken.) impl UnwindSafe for PanicInfo {} impl RefUnwindSafe for PanicInfo {} impl PanicInfo { pub fn is_panicking(&self) -> bool { let inner = self.inner.take(); let result = inner.is_some(); self.inner.set(inner); result } /// Overwrites the current state if the current state is not panicking pub fn set_panic(&self, p: Box) { if !self.is_panicking() { self.inner.set(Some(p)); } } pub fn take(&self) -> Option> { self.inner.take() } } #[derive(Debug)] pub struct ActiveEventLoop { delegate: Retained, pub(super) mtm: MainThreadMarker, } impl ActiveEventLoop { pub(super) fn new_root(delegate: Retained) -> RootWindowTarget { let mtm = MainThreadMarker::from(&*delegate); let p = Self { delegate, mtm }; RootWindowTarget { p, _marker: PhantomData } } pub(super) fn app_delegate(&self) -> &ApplicationDelegate { &self.delegate } pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor { RootCustomCursor { inner: CustomCursor::new(source.inner) } } #[inline] pub fn available_monitors(&self) -> VecDeque { monitor::available_monitors() } #[inline] pub fn primary_monitor(&self) -> Option { let monitor = monitor::primary_monitor(); Some(monitor) } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty()) } #[inline] pub fn system_theme(&self) -> Option { let app = NSApplication::sharedApplication(self.mtm); if app.respondsToSelector(sel!(effectiveAppearance)) { Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) } else { Some(Theme::Light) } } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new())) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.delegate.set_control_flow(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.delegate.control_flow() } pub(crate) fn exit(&self) { self.delegate.exit() } pub(crate) fn clear_exit(&self) { self.delegate.clear_exit() } pub(crate) fn exiting(&self) -> bool { self.delegate.exiting() } pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle { OwnedDisplayHandle } pub(crate) fn hide_application(&self) { NSApplication::sharedApplication(self.mtm).hide(None) } pub(crate) fn hide_other_applications(&self) { NSApplication::sharedApplication(self.mtm).hideOtherApplications(None) } pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) { NSWindow::setAllowsAutomaticWindowTabbing(enabled, self.mtm) } pub(crate) fn allows_automatic_window_tabbing(&self) -> bool { NSWindow::allowsAutomaticWindowTabbing(self.mtm) } } fn map_user_event( mut handler: impl FnMut(Event, &RootWindowTarget), receiver: Rc>, ) -> impl FnMut(Event, &RootWindowTarget) { move |event, window_target| match event.map_nonuser_event() { Ok(event) => (handler)(event, window_target), Err(_) => { for event in receiver.try_iter() { (handler)(Event::UserEvent(event), window_target); } }, } } pub struct EventLoop { /// Store a reference to the application for convenience. /// /// We intentionally don't store `WinitApplication` since we want to have /// the possibility of swapping that out at some point. app: Retained, /// The application delegate that we've registered. /// /// The delegate is only weakly referenced by NSApplication, so we must /// keep it around here as well. delegate: Retained, // Event sender and receiver, used for EventLoopProxy. sender: mpsc::Sender, receiver: Rc>, window_target: RootWindowTarget, panic_info: Rc, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) activation_policy: Option, pub(crate) default_menu: bool, pub(crate) activate_ignoring_other_apps: bool, } impl Default for PlatformSpecificEventLoopAttributes { fn default() -> Self { Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true } } } impl EventLoop { pub(crate) fn new( attributes: &PlatformSpecificEventLoopAttributes, ) -> Result { let mtm = MainThreadMarker::new() .expect("on macOS, `EventLoop` must be created on the main thread!"); let app: Retained = unsafe { msg_send_id![WinitApplication::class(), sharedApplication] }; if !app.is_kind_of::() { panic!( "`winit` requires control over the principal class. You must create the event \ loop before other parts of your application initialize NSApplication" ); } let activation_policy = match attributes.activation_policy { None => None, Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular), Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory), Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited), }; let delegate = ApplicationDelegate::new( mtm, activation_policy, attributes.default_menu, attributes.activate_ignoring_other_apps, ); autoreleasepool(|_| { app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); }); let panic_info: Rc = Default::default(); setup_control_flow_observers(mtm, Rc::downgrade(&panic_info)); let (sender, receiver) = mpsc::channel(); Ok(EventLoop { app, delegate: delegate.clone(), sender, receiver: Rc::new(receiver), window_target: RootWindowTarget { p: ActiveEventLoop { delegate, mtm }, _marker: PhantomData, }, panic_info, }) } pub fn window_target(&self) -> &RootWindowTarget { &self.window_target } pub fn run(mut self, handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootWindowTarget), { self.run_on_demand(handler) } // NB: we don't base this on `pump_events` because for `MacOs` we can't support // `pump_events` elegantly (we just ask to run the loop for a "short" amount of // time and so a layered implementation would end up using a lot of CPU due to // redundant wake ups. pub fn run_on_demand(&mut self, handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootWindowTarget), { let handler = map_user_event(handler, self.receiver.clone()); self.delegate.set_event_handler(handler, || { autoreleasepool(|_| { // clear / normalize pump_events state self.delegate.set_wait_timeout(None); self.delegate.set_stop_before_wait(false); self.delegate.set_stop_after_wait(false); self.delegate.set_stop_on_redraw(false); if self.delegate.is_launched() { debug_assert!(!self.delegate.is_running()); self.delegate.set_is_running(true); self.delegate.dispatch_init_events(); } // SAFETY: We do not run the application re-entrantly unsafe { self.app.run() }; // While the app is running it's possible that we catch a panic // to avoid unwinding across an objective-c ffi boundary, which // will lead to us stopping the `NSApplication` and saving the // `PanicInfo` so that we can resume the unwind at a controlled, // safe point in time. if let Some(panic) = self.panic_info.take() { resume_unwind(panic); } self.delegate.internal_exit() }) }); Ok(()) } pub fn pump_events(&mut self, timeout: Option, handler: F) -> PumpStatus where F: FnMut(Event, &RootWindowTarget), { let handler = map_user_event(handler, self.receiver.clone()); self.delegate.set_event_handler(handler, || { autoreleasepool(|_| { // As a special case, if the application hasn't been launched yet then we at least // run the loop until it has fully launched. if !self.delegate.is_launched() { debug_assert!(!self.delegate.is_running()); self.delegate.set_stop_on_launch(); // SAFETY: We do not run the application re-entrantly unsafe { self.app.run() }; // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application // has launched } else if !self.delegate.is_running() { // Even though the application may have been launched, it's possible we aren't // running if the `EventLoop` was run before and has since // exited. This indicates that we just starting to re-run // the same `EventLoop` again. self.delegate.set_is_running(true); self.delegate.dispatch_init_events(); } else { // Only run for as long as the given `Duration` allows so we don't block the // external loop. match timeout { Some(Duration::ZERO) => { self.delegate.set_wait_timeout(None); self.delegate.set_stop_before_wait(true); }, Some(duration) => { self.delegate.set_stop_before_wait(false); let timeout = Instant::now() + duration; self.delegate.set_wait_timeout(Some(timeout)); self.delegate.set_stop_after_wait(true); }, None => { self.delegate.set_wait_timeout(None); self.delegate.set_stop_before_wait(false); self.delegate.set_stop_after_wait(true); }, } self.delegate.set_stop_on_redraw(true); // SAFETY: We do not run the application re-entrantly unsafe { self.app.run() }; } // While the app is running it's possible that we catch a panic // to avoid unwinding across an objective-c ffi boundary, which // will lead to us stopping the application and saving the // `PanicInfo` so that we can resume the unwind at a controlled, // safe point in time. if let Some(panic) = self.panic_info.take() { resume_unwind(panic); } if self.delegate.exiting() { self.delegate.internal_exit(); PumpStatus::Exit(0) } else { PumpStatus::Continue } }) }) } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy::new(self.sender.clone()) } } #[derive(Clone)] pub(crate) struct OwnedDisplayHandle; impl OwnedDisplayHandle { #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::AppKitDisplayHandle::empty().into() } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::AppKitDisplayHandle::new().into()) } } pub(super) fn stop_app_immediately(app: &NSApplication) { autoreleasepool(|_| { app.stop(None); // To stop event loop immediately, we need to post some event here. // See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752 app.postEvent_atStart(&dummy_event().unwrap(), true); }); } /// Catches panics that happen inside `f` and when a panic /// happens, stops the `sharedApplication` #[inline] pub fn stop_app_on_panic R + UnwindSafe, R>( mtm: MainThreadMarker, panic_info: Weak, f: F, ) -> Option { match catch_unwind(f) { Ok(r) => Some(r), Err(e) => { // It's important that we set the panic before requesting a `stop` // because some callback are still called during the `stop` message // and we need to know in those callbacks if the application is currently // panicking { let panic_info = panic_info.upgrade().unwrap(); panic_info.set_panic(e); } let app = NSApplication::sharedApplication(mtm); stop_app_immediately(&app); None }, } } pub struct EventLoopProxy { sender: mpsc::Sender, source: CFRunLoopSourceRef, } unsafe impl Send for EventLoopProxy {} unsafe impl Sync for EventLoopProxy {} impl Drop for EventLoopProxy { fn drop(&mut self) { unsafe { CFRelease(self.source as _); } } } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy::new(self.sender.clone()) } } impl EventLoopProxy { fn new(sender: mpsc::Sender) -> Self { unsafe { // just wake up the eventloop extern "C" fn event_loop_proxy_handler(_: *const c_void) {} // adding a Source to the main CFRunLoop lets us wake it up and // process user events through the normal OS EventLoop mechanisms. let rl = CFRunLoopGetMain(); let mut context = CFRunLoopSourceContext { version: 0, info: ptr::null_mut(), retain: None, release: None, copyDescription: None, equal: None, hash: None, schedule: None, cancel: None, perform: event_loop_proxy_handler, }; let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); CFRunLoopWakeUp(rl); EventLoopProxy { sender, source } } } pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.sender.send(event).map_err(|mpsc::SendError(x)| EventLoopClosed(x))?; unsafe { // let the main thread know there's a new event CFRunLoopSourceSignal(self.source); let rl = CFRunLoopGetMain(); CFRunLoopWakeUp(rl); } Ok(()) } } winit-0.30.9/src/platform_impl/macos/ffi.rs000064400000000000000000000215111046102023000167170ustar 00000000000000// TODO: Upstream these #![allow(dead_code, non_snake_case, non_upper_case_globals)] use std::ffi::c_void; use core_foundation::array::CFArrayRef; use core_foundation::dictionary::CFDictionaryRef; use core_foundation::string::CFStringRef; use core_foundation::uuid::CFUUIDRef; use core_graphics::base::CGError; use core_graphics::display::{CGDirectDisplayID, CGDisplayConfigRef}; use objc2::ffi::NSInteger; use objc2::runtime::AnyObject; pub type CGDisplayFadeInterval = f32; pub type CGDisplayReservationInterval = f32; pub type CGDisplayBlendFraction = f32; pub const kCGDisplayBlendNormal: f32 = 0.0; pub const kCGDisplayBlendSolidColor: f32 = 1.0; pub type CGDisplayFadeReservationToken = u32; pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0; pub type Boolean = u8; pub const FALSE: Boolean = 0; pub const TRUE: Boolean = 1; pub const kCGErrorSuccess: i32 = 0; pub const kCGErrorFailure: i32 = 1000; pub const kCGErrorIllegalArgument: i32 = 1001; pub const kCGErrorInvalidConnection: i32 = 1002; pub const kCGErrorInvalidContext: i32 = 1003; pub const kCGErrorCannotComplete: i32 = 1004; pub const kCGErrorNotImplemented: i32 = 1006; pub const kCGErrorRangeCheck: i32 = 1007; pub const kCGErrorTypeCheck: i32 = 1008; pub const kCGErrorInvalidOperation: i32 = 1010; pub const kCGErrorNoneAvailable: i32 = 1011; pub const IO1BitIndexedPixels: &str = "P"; pub const IO2BitIndexedPixels: &str = "PP"; pub const IO4BitIndexedPixels: &str = "PPPP"; pub const IO8BitIndexedPixels: &str = "PPPPPPPP"; pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB"; pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB"; pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB"; pub const kIO64BitDirectPixels: &str = "-16R16G16B16"; pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16"; pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32"; pub const IOYUV422Pixels: &str = "Y4U2V2"; pub const IO8BitOverlayPixels: &str = "O8"; pub type CGWindowLevel = i32; pub type CGDisplayModeRef = *mut c_void; // `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework. // However, that framework was only introduced "publicly" in macOS 10.13. // // Since we want to support older versions, we can't link to `ColorSync` // directly. Fortunately, it has always been available as a subframework of // `ApplicationServices`, see: // https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG #[link(name = "ApplicationServices", kind = "framework")] extern "C" { pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; } #[link(name = "CoreGraphics", kind = "framework")] extern "C" { pub fn CGRestorePermanentDisplayConfiguration(); pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError; pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError; pub fn CGConfigureDisplayFadeEffect( config: CGDisplayConfigRef, fadeOutSeconds: CGDisplayFadeInterval, fadeInSeconds: CGDisplayFadeInterval, fadeRed: f32, fadeGreen: f32, fadeBlue: f32, ) -> CGError; pub fn CGAcquireDisplayFadeReservation( seconds: CGDisplayReservationInterval, token: *mut CGDisplayFadeReservationToken, ) -> CGError; pub fn CGDisplayFade( token: CGDisplayFadeReservationToken, duration: CGDisplayFadeInterval, startBlend: CGDisplayBlendFraction, endBlend: CGDisplayBlendFraction, redBlend: f32, greenBlend: f32, blueBlend: f32, synchronous: Boolean, ) -> CGError; pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError; pub fn CGShieldingWindowLevel() -> CGWindowLevel; pub fn CGDisplaySetDisplayMode( display: CGDirectDisplayID, mode: CGDisplayModeRef, options: CFDictionaryRef, ) -> CGError; pub fn CGDisplayCopyAllDisplayModes( display: CGDirectDisplayID, options: CFDictionaryRef, ) -> CFArrayRef; pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize; pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize; pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64; pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef; pub fn CGDisplayModeRetain(mode: CGDisplayModeRef); pub fn CGDisplayModeRelease(mode: CGDisplayModeRef); // Wildly used private APIs; Apple uses them for their Terminal.app. pub fn CGSMainConnectionID() -> *mut AnyObject; pub fn CGSSetWindowBackgroundBlurRadius( connection_id: *mut AnyObject, window_id: NSInteger, radius: i64, ) -> i32; } mod core_video { use super::*; #[link(name = "CoreVideo", kind = "framework")] extern "C" {} // CVBase.h pub type CVTimeFlags = i32; // int32_t pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0; #[repr(C)] #[derive(Debug, Clone)] pub struct CVTime { pub time_value: i64, // int64_t pub time_scale: i32, // int32_t pub flags: i32, // int32_t } // CVReturn.h pub type CVReturn = i32; // int32_t pub const kCVReturnSuccess: CVReturn = 0; // CVDisplayLink.h pub type CVDisplayLinkRef = *mut c_void; extern "C" { pub fn CVDisplayLinkCreateWithCGDisplay( displayID: CGDirectDisplayID, displayLinkOut: *mut CVDisplayLinkRef, ) -> CVReturn; pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod( displayLink: CVDisplayLinkRef, ) -> CVTime; pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef); } } pub use core_video::*; #[repr(transparent)] pub struct TISInputSource(std::ffi::c_void); pub type TISInputSourceRef = *mut TISInputSource; #[repr(transparent)] pub struct UCKeyboardLayout(std::ffi::c_void); pub type OptionBits = u32; pub type UniCharCount = std::os::raw::c_ulong; pub type UniChar = std::os::raw::c_ushort; pub type OSStatus = i32; #[allow(non_upper_case_globals)] pub const kUCKeyActionDisplay: u16 = 3; #[allow(non_upper_case_globals)] pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1; #[link(name = "Carbon", kind = "framework")] extern "C" { pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef; #[allow(non_snake_case)] pub fn TISGetInputSourceProperty( inputSource: TISInputSourceRef, propertyKey: CFStringRef, ) -> *mut c_void; pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef; pub fn LMGetKbdType() -> u8; #[allow(non_snake_case)] pub fn UCKeyTranslate( keyLayoutPtr: *const UCKeyboardLayout, virtualKeyCode: u16, keyAction: u16, modifierKeyState: u32, keyboardType: u32, keyTranslateOptions: OptionBits, deadKeyState: *mut u32, maxStringLength: UniCharCount, actualStringLength: *mut UniCharCount, unicodeString: *mut UniChar, ) -> OSStatus; } // CGWindowLevel.h // // Note: There are two different things at play in this header: // `CGWindowLevel` and `CGWindowLevelKey`. // // It seems like there was a push towards using "key" values instead of the // raw window level values, and then you were supposed to use // `CGWindowLevelForKey` to get the actual level. // // But the values that `NSWindowLevel` has are compiled in, and as such has // to remain ABI compatible, so they're safe for us to hardcode as well. #[allow(dead_code, non_upper_case_globals)] mod window_level { const kCGNumReservedWindowLevels: i32 = 16; const kCGNumReservedBaseWindowLevels: i32 = 5; pub const kCGBaseWindowLevel: i32 = i32::MIN; pub const kCGMinimumWindowLevel: i32 = kCGBaseWindowLevel + kCGNumReservedBaseWindowLevels; pub const kCGMaximumWindowLevel: i32 = i32::MAX - kCGNumReservedWindowLevels; pub const kCGDesktopWindowLevel: i32 = kCGMinimumWindowLevel + 20; pub const kCGDesktopIconWindowLevel: i32 = kCGDesktopWindowLevel + 20; pub const kCGBackstopMenuLevel: i32 = -20; pub const kCGNormalWindowLevel: i32 = 0; pub const kCGFloatingWindowLevel: i32 = 3; pub const kCGTornOffMenuWindowLevel: i32 = 3; pub const kCGModalPanelWindowLevel: i32 = 8; pub const kCGUtilityWindowLevel: i32 = 19; pub const kCGDockWindowLevel: i32 = 20; pub const kCGMainMenuWindowLevel: i32 = 24; pub const kCGStatusWindowLevel: i32 = 25; pub const kCGPopUpMenuWindowLevel: i32 = 101; pub const kCGOverlayWindowLevel: i32 = 102; pub const kCGHelpWindowLevel: i32 = 200; pub const kCGDraggingWindowLevel: i32 = 500; pub const kCGScreenSaverWindowLevel: i32 = 1000; pub const kCGAssistiveTechHighWindowLevel: i32 = 1500; pub const kCGCursorWindowLevel: i32 = kCGMaximumWindowLevel - 1; } pub use window_level::*; winit-0.30.9/src/platform_impl/macos/menu.rs000064400000000000000000000065421046102023000171260ustar 00000000000000use objc2::rc::Retained; use objc2::runtime::Sel; use objc2::sel; use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem}; use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString}; struct KeyEquivalent<'a> { key: &'a NSString, masks: Option, } pub fn initialize(app: &NSApplication) { let mtm = MainThreadMarker::from(app); let menubar = NSMenu::new(mtm); let app_menu_item = NSMenuItem::new(mtm); menubar.addItem(&app_menu_item); let app_menu = NSMenu::new(mtm); let process_name = NSProcessInfo::processInfo().processName(); // About menu item let about_item_title = ns_string!("About ").stringByAppendingString(&process_name); let about_item = menu_item(mtm, &about_item_title, Some(sel!(orderFrontStandardAboutPanel:)), None); // Services menu item let services_menu = NSMenu::new(mtm); let services_item = menu_item(mtm, ns_string!("Services"), None, None); services_item.setSubmenu(Some(&services_menu)); // Separator menu item let sep_first = NSMenuItem::separatorItem(mtm); // Hide application menu item let hide_item_title = ns_string!("Hide ").stringByAppendingString(&process_name); let hide_item = menu_item( mtm, &hide_item_title, Some(sel!(hide:)), Some(KeyEquivalent { key: ns_string!("h"), masks: None }), ); // Hide other applications menu item let hide_others_item_title = ns_string!("Hide Others"); let hide_others_item = menu_item( mtm, hide_others_item_title, Some(sel!(hideOtherApplications:)), Some(KeyEquivalent { key: ns_string!("h"), masks: Some( NSEventModifierFlags::NSEventModifierFlagOption | NSEventModifierFlags::NSEventModifierFlagCommand, ), }), ); // Show applications menu item let show_all_item_title = ns_string!("Show All"); let show_all_item = menu_item(mtm, show_all_item_title, Some(sel!(unhideAllApplications:)), None); // Separator menu item let sep = NSMenuItem::separatorItem(mtm); // Quit application menu item let quit_item_title = ns_string!("Quit ").stringByAppendingString(&process_name); let quit_item = menu_item( mtm, &quit_item_title, Some(sel!(terminate:)), Some(KeyEquivalent { key: ns_string!("q"), masks: None }), ); app_menu.addItem(&about_item); app_menu.addItem(&sep_first); app_menu.addItem(&services_item); app_menu.addItem(&hide_item); app_menu.addItem(&hide_others_item); app_menu.addItem(&show_all_item); app_menu.addItem(&sep); app_menu.addItem(&quit_item); app_menu_item.setSubmenu(Some(&app_menu)); unsafe { app.setServicesMenu(Some(&services_menu)) }; app.setMainMenu(Some(&menubar)); } fn menu_item( mtm: MainThreadMarker, title: &NSString, selector: Option, key_equivalent: Option>, ) -> Retained { let (key, masks) = match key_equivalent { Some(ke) => (ke.key, ke.masks), None => (ns_string!(""), None), }; let item = unsafe { NSMenuItem::initWithTitle_action_keyEquivalent(mtm.alloc(), title, selector, key) }; if let Some(masks) = masks { item.setKeyEquivalentModifierMask(masks) } item } winit-0.30.9/src/platform_impl/macos/mod.rs000064400000000000000000000031671046102023000167410ustar 00000000000000#[macro_use] mod util; mod app; mod app_state; mod cursor; mod event; mod event_handler; mod event_loop; mod ffi; mod menu; mod monitor; mod observer; mod view; mod window; mod window_delegate; use std::fmt; pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra}; pub(crate) use self::event_loop::{ ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle, PlatformSpecificEventLoopAttributes, }; pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle}; pub(crate) use self::window::WindowId; pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes; use crate::event::DeviceId as RootDeviceId; pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor; pub(crate) use self::window::Window; pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; impl DeviceId { pub const fn dummy() -> Self { DeviceId } } // Constant device ID; to be removed when if backend is updated to report real device IDs. pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); #[derive(Debug)] pub enum OsError { CGError(core_graphics::base::CGError), CreationError(&'static str), } impl fmt::Display for OsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { OsError::CGError(e) => f.pad(&format!("CGError {e}")), OsError::CreationError(e) => f.pad(e), } } } winit-0.30.9/src/platform_impl/macos/monitor.rs000064400000000000000000000306501046102023000176460ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::collections::VecDeque; use std::fmt; use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex}; use core_foundation::base::{CFRelease, TCFType}; use core_foundation::string::CFString; use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUID}; use core_graphics::display::{ CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode, }; use objc2::rc::Retained; use objc2::runtime::AnyObject; use objc2_app_kit::NSScreen; use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect}; use super::ffi; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; #[derive(Clone)] pub struct VideoModeHandle { size: PhysicalSize, bit_depth: u16, refresh_rate_millihertz: u32, pub(crate) monitor: MonitorHandle, pub(crate) native_mode: NativeDisplayMode, } impl PartialEq for VideoModeHandle { fn eq(&self, other: &Self) -> bool { self.size == other.size && self.bit_depth == other.bit_depth && self.refresh_rate_millihertz == other.refresh_rate_millihertz && self.monitor == other.monitor } } impl Eq for VideoModeHandle {} impl std::hash::Hash for VideoModeHandle { fn hash(&self, state: &mut H) { self.size.hash(state); self.bit_depth.hash(state); self.refresh_rate_millihertz.hash(state); self.monitor.hash(state); } } impl std::fmt::Debug for VideoModeHandle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("VideoModeHandle") .field("size", &self.size) .field("bit_depth", &self.bit_depth) .field("refresh_rate_millihertz", &self.refresh_rate_millihertz) .field("monitor", &self.monitor) .finish() } } pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef); unsafe impl Send for NativeDisplayMode {} unsafe impl Sync for NativeDisplayMode {} impl Drop for NativeDisplayMode { fn drop(&mut self) { unsafe { ffi::CGDisplayModeRelease(self.0); } } } impl Clone for NativeDisplayMode { fn clone(&self) -> Self { unsafe { ffi::CGDisplayModeRetain(self.0); } NativeDisplayMode(self.0) } } impl VideoModeHandle { pub fn size(&self) -> PhysicalSize { self.size } pub fn bit_depth(&self) -> u16 { self.bit_depth } pub fn refresh_rate_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } } #[derive(Clone)] pub struct MonitorHandle(CGDirectDisplayID); type MonitorUuid = [u8; 16]; impl MonitorHandle { /// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle. fn uuid(&self) -> MonitorUuid { let cf_uuid = unsafe { CFUUID::wrap_under_create_rule(ffi::CGDisplayCreateUUIDFromDisplayID(self.0)) }; let uuid = unsafe { CFUUIDGetUUIDBytes(cf_uuid.as_concrete_TypeRef()) }; MonitorUuid::from([ uuid.byte0, uuid.byte1, uuid.byte2, uuid.byte3, uuid.byte4, uuid.byte5, uuid.byte6, uuid.byte7, uuid.byte8, uuid.byte9, uuid.byte10, uuid.byte11, uuid.byte12, uuid.byte13, uuid.byte14, uuid.byte15, ]) } } // `CGDirectDisplayID` changes on video mode change, so we cannot rely on that // for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an // unique identifier that persists even across system reboots impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { self.uuid() == other.uuid() } } impl Eq for MonitorHandle {} impl PartialOrd for MonitorHandle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for MonitorHandle { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.uuid().cmp(&other.uuid()) } } impl std::hash::Hash for MonitorHandle { fn hash(&self, state: &mut H) { self.uuid().hash(state); } } pub fn available_monitors() -> VecDeque { if let Ok(displays) = CGDisplay::active_displays() { let mut monitors = VecDeque::with_capacity(displays.len()); for display in displays { monitors.push_back(MonitorHandle(display)); } monitors } else { VecDeque::with_capacity(0) } } pub fn primary_monitor() -> MonitorHandle { MonitorHandle(CGDisplay::main().id) } impl fmt::Debug for MonitorHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MonitorHandle") .field("name", &self.name()) .field("native_identifier", &self.native_identifier()) .field("size", &self.size()) .field("position", &self.position()) .field("scale_factor", &self.scale_factor()) .field("refresh_rate_millihertz", &self.refresh_rate_millihertz()) .finish_non_exhaustive() } } impl MonitorHandle { pub fn new(id: CGDirectDisplayID) -> Self { MonitorHandle(id) } // TODO: Be smarter about this: // pub fn name(&self) -> Option { let MonitorHandle(display_id) = *self; let screen_num = CGDisplay::new(display_id).model_number(); Some(format!("Monitor #{screen_num}")) } #[inline] pub fn native_identifier(&self) -> u32 { self.0 } pub fn size(&self) -> PhysicalSize { let MonitorHandle(display_id) = *self; let display = CGDisplay::new(display_id); let height = display.pixels_high(); let width = display.pixels_wide(); PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.scale_factor()) } #[inline] pub fn position(&self) -> PhysicalPosition { // This is already in screen coordinates. If we were using `NSScreen`, // then a conversion would've been needed: // flip_window_screen_coordinates(self.ns_screen(mtm)?.frame()) let bounds = unsafe { CGDisplayBounds(self.native_identifier()) }; let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y); position.to_physical(self.scale_factor()) } pub fn scale_factor(&self) -> f64 { run_on_main(|mtm| { match self.ns_screen(mtm) { Some(screen) => screen.backingScaleFactor() as f64, None => 1.0, // default to 1.0 when we can't find the screen } }) } pub fn refresh_rate_millihertz(&self) -> Option { unsafe { let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _); let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0); if refresh_rate > 0.0 { return Some((refresh_rate * 1000.0).round() as u32); } let mut display_link = std::ptr::null_mut(); if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link) != ffi::kCVReturnSuccess { return None; } let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link); ffi::CVDisplayLinkRelease(display_link); // This value is indefinite if an invalid display link was specified if time.flags & ffi::kCVTimeIsIndefinite != 0 { return None; } (time.time_scale as i64).checked_div(time.time_value).map(|v| (v * 1000) as u32) } } pub fn video_modes(&self) -> impl Iterator { let refresh_rate_millihertz = self.refresh_rate_millihertz().unwrap_or(0); let monitor = self.clone(); unsafe { let modes = { let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null()); assert!(!array.is_null(), "failed to get list of display modes"); let array_count = CFArrayGetCount(array); let modes: Vec<_> = (0..array_count) .map(move |i| { let mode = CFArrayGetValueAtIndex(array, i) as *mut _; ffi::CGDisplayModeRetain(mode); mode }) .collect(); CFRelease(array as *const _); modes }; modes.into_iter().map(move |mode| { let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; // CGDisplayModeGetRefreshRate returns 0.0 for any display that // isn't a CRT let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 { (cg_refresh_rate_hertz * 1000) as u32 } else { refresh_rate_millihertz }; let pixel_encoding = CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode)) .to_string(); let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) { 32 } else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) { 16 } else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) { 30 } else { unimplemented!() }; VideoModeHandle { size: PhysicalSize::new( ffi::CGDisplayModeGetPixelWidth(mode) as u32, ffi::CGDisplayModeGetPixelHeight(mode) as u32, ), refresh_rate_millihertz, bit_depth, monitor: monitor.clone(), native_mode: NativeDisplayMode(mode), } }) } } pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option> { let uuid = self.uuid(); NSScreen::screens(mtm).into_iter().find(|screen| { let other_native_id = get_display_id(screen); let other = MonitorHandle::new(other_native_id); uuid == other.uuid() }) } } pub(crate) fn get_display_id(screen: &NSScreen) -> u32 { let key = ns_string!("NSScreenNumber"); objc2::rc::autoreleasepool(|_| { let device_description = screen.deviceDescription(); // Retrieve the CGDirectDisplayID associated with this screen // // SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed // to be an NSNumber. See documentation for `deviceDescription` for details: // let obj = device_description .get(key) .expect("failed getting screen display id from device description"); let obj: *const AnyObject = obj; let obj: *const NSNumber = obj.cast(); let obj: &NSNumber = unsafe { &*obj }; obj.as_u32() }) } /// Core graphics screen coordinates are relative to the top-left corner of /// the so-called "main" display, with y increasing downwards - which is /// exactly what we want in Winit. /// /// However, `NSWindow` and `NSScreen` changes these coordinates to: /// 1. Be relative to the bottom-left corner of the "main" screen. /// 2. Be relative to the bottom-left corner of the window/screen itself. /// 3. Have y increasing upwards. /// /// This conversion happens to be symmetric, so we only need this one function /// to convert between the two coordinate systems. pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint { // It is intentional that we use `CGMainDisplayID` (as opposed to // `NSScreen::mainScreen`), because that's what the screen coordinates // are relative to, no matter which display the window is currently on. let main_screen_height = CGDisplay::main().bounds().size.height; let y = main_screen_height - frame.size.height - frame.origin.y; NSPoint::new(frame.origin.x, y) } winit-0.30.9/src/platform_impl/macos/observer.rs000064400000000000000000000276531046102023000200170ustar 00000000000000//! Utilities for working with `CFRunLoop`. //! //! See Apple's documentation on Run Loops for details: //! use std::cell::Cell; use std::ffi::c_void; use std::panic::{AssertUnwindSafe, UnwindSafe}; use std::ptr; use std::rc::Weak; use std::time::Instant; use block2::Block; use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef}; use core_foundation::date::CFAbsoluteTimeGetCurrent; use core_foundation::runloop::{ kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode, kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp, }; use objc2_foundation::MainThreadMarker; use tracing::error; use super::app_state::ApplicationDelegate; use super::event_loop::{stop_app_on_panic, PanicInfo}; use super::ffi; unsafe fn control_flow_handler(panic_info: *mut c_void, f: F) where F: FnOnce(Weak) + UnwindSafe, { let info_from_raw = unsafe { Weak::from_raw(panic_info as *mut PanicInfo) }; // Asserting unwind safety on this type should be fine because `PanicInfo` is // `RefUnwindSafe` and `Rc` is `UnwindSafe` if `T` is `RefUnwindSafe`. let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw)); // `from_raw` takes ownership of the data behind the pointer. // But if this scope takes ownership of the weak pointer, then // the weak pointer will get free'd at the end of the scope. // However we want to keep that weak reference around after the function. std::mem::forget(info_from_raw); let mtm = MainThreadMarker::new().unwrap(); stop_app_on_panic(mtm, Weak::clone(&panic_info), move || { let _ = &panic_info; f(panic_info.0) }); } // begin is queued with the highest priority to ensure it is processed before other observers extern "C" fn control_flow_begin_handler( _: CFRunLoopObserverRef, activity: CFRunLoopActivity, panic_info: *mut c_void, ) { unsafe { control_flow_handler(panic_info, |panic_info| { #[allow(non_upper_case_globals)] match activity { kCFRunLoopAfterWaiting => { // trace!("Triggered `CFRunLoopAfterWaiting`"); ApplicationDelegate::get(MainThreadMarker::new().unwrap()).wakeup(panic_info); // trace!("Completed `CFRunLoopAfterWaiting`"); }, _ => unreachable!(), } }); } } // end is queued with the lowest priority to ensure it is processed after other observers // without that, LoopExiting would get sent after AboutToWait extern "C" fn control_flow_end_handler( _: CFRunLoopObserverRef, activity: CFRunLoopActivity, panic_info: *mut c_void, ) { unsafe { control_flow_handler(panic_info, |panic_info| { #[allow(non_upper_case_globals)] match activity { kCFRunLoopBeforeWaiting => { // trace!("Triggered `CFRunLoopBeforeWaiting`"); ApplicationDelegate::get(MainThreadMarker::new().unwrap()).cleared(panic_info); // trace!("Completed `CFRunLoopBeforeWaiting`"); }, kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen _ => unreachable!(), } }); } } #[derive(Debug)] pub struct RunLoop(CFRunLoopRef); impl Default for RunLoop { fn default() -> Self { Self(ptr::null_mut()) } } impl RunLoop { pub fn main(mtm: MainThreadMarker) -> Self { // SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so // scheduling (and scheduling a non-`Send` block) to that thread is allowed. let _ = mtm; RunLoop(unsafe { CFRunLoopGetMain() }) } pub fn wakeup(&self) { unsafe { CFRunLoopWakeUp(self.0) } } unsafe fn add_observer( &self, flags: CFOptionFlags, priority: CFIndex, handler: CFRunLoopObserverCallBack, context: *mut CFRunLoopObserverContext, ) { let observer = unsafe { CFRunLoopObserverCreate( ptr::null_mut(), flags, ffi::TRUE, // Indicates we want this to run repeatedly priority, // The lower the value, the sooner this will run handler, context, ) }; unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) }; } /// Submit a closure to run on the main thread as the next step in the run loop, before other /// event sources are processed. /// /// This is used for running event handlers, as those are not allowed to run re-entrantly. /// /// # Implementation /// /// This queuing could be implemented in the following several ways with subtle differences in /// timing. This list is sorted in rough order in which they are run: /// /// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`. /// /// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the /// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both /// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop). /// /// a. `atStart = true`. /// /// b. `atStart = false`. /// /// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not /// respect the ordering that runloop events have. /// /// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we /// want the event to be queued in a way that preserves the order the events originally arrived /// in. /// /// As an example, let's assume that we receive two events from the user, a mouse click which we /// handled by queuing it, and a window resize which we handled immediately. If we allowed /// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of /// the queue, and the events would appear out of order to the user of Winit. So we must instead /// put the event at the very front of the queue, to be handled as soon as possible after /// handling whatever event it's currently handling. pub fn queue_closure(&self, closure: impl FnOnce() + 'static) { extern "C" { fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block); } // Convert `FnOnce()` to `Block`. let closure = Cell::new(Some(closure)); let block = block2::RcBlock::new(move || { if let Some(closure) = closure.take() { closure() } else { error!("tried to execute queued closure on main thread twice"); } }); // There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa: // - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`. // - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window. // - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop. // - `NSConnectionReplyMode`: TODO. // // We only want to run event handlers in the default mode, as we support running a blocking // modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and // resizing such panel window enters the event tracking run loop mode, so we can't directly // trigger events inside that mode either. // // Any events that are queued while running a modal or when live-resizing will instead wait, // and be delivered to the application afterwards. // // [#1779]: https://github.com/rust-windowing/winit/issues/1779 let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef }; // SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`. unsafe { CFRunLoopPerformBlock(self.0, mode, &block) } } } pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak) { let run_loop = RunLoop::main(mtm); unsafe { let mut context = CFRunLoopObserverContext { info: Weak::into_raw(panic_info) as *mut _, version: 0, retain: None, release: None, copyDescription: None, }; run_loop.add_observer( kCFRunLoopAfterWaiting, CFIndex::MIN, control_flow_begin_handler, &mut context as *mut _, ); run_loop.add_observer( kCFRunLoopExit | kCFRunLoopBeforeWaiting, CFIndex::MAX, control_flow_end_handler, &mut context as *mut _, ); } } #[derive(Debug)] pub struct EventLoopWaker { timer: CFRunLoopTimerRef, /// An arbitrary instant in the past, that will trigger an immediate wake /// We save this as the `next_fire_date` for consistency so we can /// easily check if the next_fire_date needs updating. start_instant: Instant, /// This is what the `NextFireDate` has been set to. /// `None` corresponds to `waker.stop()` and `start_instant` is used /// for `waker.start()` next_fire_date: Option, } impl Drop for EventLoopWaker { fn drop(&mut self) { unsafe { CFRunLoopTimerInvalidate(self.timer); CFRelease(self.timer as _); } } } impl EventLoopWaker { pub(crate) fn new() -> Self { extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} unsafe { // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. // It is initially setup with a first fire time really far into the // future, but that gets changed to fire immediately in did_finish_launching let timer = CFRunLoopTimerCreate( ptr::null_mut(), f64::MAX, 0.000_000_1, 0, 0, wakeup_main_loop, ptr::null_mut(), ); CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); Self { timer, start_instant: Instant::now(), next_fire_date: None } } } pub fn stop(&mut self) { if self.next_fire_date.is_some() { self.next_fire_date = None; unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } } } pub fn start(&mut self) { if self.next_fire_date != Some(self.start_instant) { self.next_fire_date = Some(self.start_instant); unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } } } pub fn start_at(&mut self, instant: Option) { let now = Instant::now(); match instant { Some(instant) if now >= instant => { self.start(); }, Some(instant) => { if self.next_fire_date != Some(instant) { self.next_fire_date = Some(instant); unsafe { let current = CFAbsoluteTimeGetCurrent(); let duration = instant - now; let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) } } }, None => { self.stop(); }, } } } winit-0.30.9/src/platform_impl/macos/util.rs000064400000000000000000000012471046102023000171340ustar 00000000000000use tracing::trace; macro_rules! trace_scope { ($s:literal) => { let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s); }; } pub(crate) struct TraceGuard { module_path: &'static str, called_from_fn: &'static str, } impl TraceGuard { #[inline] pub(crate) fn new(module_path: &'static str, called_from_fn: &'static str) -> Self { trace!(target = module_path, "Triggered `{}`", called_from_fn); Self { module_path, called_from_fn } } } impl Drop for TraceGuard { #[inline] fn drop(&mut self) { trace!(target = self.module_path, "Completed `{}`", self.called_from_fn); } } winit-0.30.9/src/platform_impl/macos/view.rs000064400000000000000000001275551046102023000171440ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::cell::{Cell, RefCell}; use std::collections::{HashMap, VecDeque}; use std::ptr; use objc2::rc::{Retained, WeakId}; use objc2::runtime::{AnyObject, Sel}; use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass}; use objc2_app_kit::{ NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification, }; use objc2_foundation::{ MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, }; use super::app_state::ApplicationDelegate; use super::cursor::{default_cursor, invisible_cursor}; use super::event::{ code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed, scancode_to_physicalkey, KeyEventExtra, }; use super::window::WinitWindow; use super::DEVICE_ID; use crate::dpi::{LogicalPosition, LogicalSize}; use crate::event::{ DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent, }; use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey}; use crate::platform::macos::OptionAsAlt; #[derive(Debug)] struct CursorState { visible: bool, cursor: Retained, } impl Default for CursorState { fn default() -> Self { Self { visible: true, cursor: default_cursor() } } } #[derive(Debug, Eq, PartialEq, Clone, Copy, Default)] enum ImeState { #[default] /// The IME events are disabled, so only `ReceivedCharacter` is being sent to the user. Disabled, /// The ground state of enabled IME input. It means that both Preedit and regular keyboard /// input could be start from it. Ground, /// The IME is in preedit. Preedit, /// The text was just committed, so the next input from the keyboard must be ignored. Committed, } bitflags::bitflags! { #[derive(Debug, Clone, Copy, PartialEq)] struct ModLocationMask: u8 { const LEFT = 0b0001; const RIGHT = 0b0010; } } impl ModLocationMask { fn from_location(loc: KeyLocation) -> ModLocationMask { match loc { KeyLocation::Left => ModLocationMask::LEFT, KeyLocation::Right => ModLocationMask::RIGHT, _ => unreachable!(), } } } fn key_to_modifier(key: &Key) -> Option { match key { Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT), Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL), Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER), Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT), _ => None, } } fn get_right_modifier_code(key: &Key) -> KeyCode { match key { Key::Named(NamedKey::Alt) => KeyCode::AltRight, Key::Named(NamedKey::Control) => KeyCode::ControlRight, Key::Named(NamedKey::Shift) => KeyCode::ShiftRight, Key::Named(NamedKey::Super) => KeyCode::SuperRight, _ => unreachable!(), } } fn get_left_modifier_code(key: &Key) -> KeyCode { match key { Key::Named(NamedKey::Alt) => KeyCode::AltLeft, Key::Named(NamedKey::Control) => KeyCode::ControlLeft, Key::Named(NamedKey::Shift) => KeyCode::ShiftLeft, Key::Named(NamedKey::Super) => KeyCode::SuperLeft, _ => unreachable!(), } } #[derive(Debug)] pub struct ViewState { /// Strong reference to the global application state. app_delegate: Retained, cursor_state: RefCell, ime_position: Cell, ime_size: Cell, modifiers: Cell, phys_modifiers: RefCell>, tracking_rect: Cell>, ime_state: Cell, input_source: RefCell, /// True iff the application wants IME events. /// /// Can be set using `set_ime_allowed` ime_allowed: Cell, /// True if the current key event should be forwarded /// to the application, even during IME forward_key_to_app: Cell, marked_text: RefCell>, accepts_first_mouse: bool, // Weak reference because the window keeps a strong reference to the view _ns_window: WeakId, /// The state of the `Option` as `Alt`. option_as_alt: Cell, } declare_class!( pub(super) struct WinitView; unsafe impl ClassType for WinitView { #[inherits(NSResponder, NSObject)] type Super = NSView; type Mutability = mutability::MainThreadOnly; const NAME: &'static str = "WinitView"; } impl DeclaredClass for WinitView { type Ivars = ViewState; } unsafe impl WinitView { #[method(isFlipped)] fn is_flipped(&self) -> bool { // `winit` uses the upper-left corner as the origin. true } #[method(viewDidMoveToWindow)] fn view_did_move_to_window(&self) { trace_scope!("viewDidMoveToWindow"); if let Some(tracking_rect) = self.ivars().tracking_rect.take() { self.removeTrackingRect(tracking_rect); } let rect = self.frame(); let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) }; assert_ne!(tracking_rect, 0, "failed adding tracking rect"); self.ivars().tracking_rect.set(Some(tracking_rect)); } #[method(frameDidChange:)] fn frame_did_change(&self, _event: &NSEvent) { trace_scope!("frameDidChange:"); if let Some(tracking_rect) = self.ivars().tracking_rect.take() { self.removeTrackingRect(tracking_rect); } let rect = self.frame(); let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) }; assert_ne!(tracking_rect, 0, "failed adding tracking rect"); self.ivars().tracking_rect.set(Some(tracking_rect)); // Emit resize event here rather than from windowDidResize because: // 1. When a new window is created as a tab, the frame size may change without a window resize occurring. // 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height). let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); let size = logical_size.to_physical::(self.scale_factor()); self.queue_event(WindowEvent::Resized(size)); } #[method(drawRect:)] fn draw_rect(&self, _rect: NSRect) { trace_scope!("drawRect:"); // It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`. if let Some(window) = self.ivars()._ns_window.load() { self.ivars().app_delegate.handle_redraw(window.id()); } // This is a direct subclass of NSView, no need to call superclass' drawRect: } #[method(acceptsFirstResponder)] fn accepts_first_responder(&self) -> bool { trace_scope!("acceptsFirstResponder"); true } // This is necessary to prevent a beefy terminal error on MacBook Pros: // IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem // TODO: Add an API extension for using `NSTouchBar` #[method_id(touchBar)] fn touch_bar(&self) -> Option> { trace_scope!("touchBar"); None } #[method(resetCursorRects)] fn reset_cursor_rects(&self) { trace_scope!("resetCursorRects"); let bounds = self.bounds(); let cursor_state = self.ivars().cursor_state.borrow(); // We correctly invoke `addCursorRect` only from inside `resetCursorRects` if cursor_state.visible { self.addCursorRect_cursor(bounds, &cursor_state.cursor); } else { self.addCursorRect_cursor(bounds, &invisible_cursor()); } } } unsafe impl NSTextInputClient for WinitView { #[method(hasMarkedText)] fn has_marked_text(&self) -> bool { trace_scope!("hasMarkedText"); self.ivars().marked_text.borrow().length() > 0 } #[method(markedRange)] fn marked_range(&self) -> NSRange { trace_scope!("markedRange"); let length = self.ivars().marked_text.borrow().length(); if length > 0 { NSRange::new(0, length) } else { // Documented to return `{NSNotFound, 0}` if there is no marked range. NSRange::new(NSNotFound as NSUInteger, 0) } } #[method(selectedRange)] fn selected_range(&self) -> NSRange { trace_scope!("selectedRange"); // Documented to return `{NSNotFound, 0}` if there is no selection. NSRange::new(NSNotFound as NSUInteger, 0) } #[method(setMarkedText:selectedRange:replacementRange:)] fn set_marked_text( &self, string: &NSObject, selected_range: NSRange, _replacement_range: NSRange, ) { // TODO: Use _replacement_range, requires changing the event to report surrounding text. trace_scope!("setMarkedText:selectedRange:replacementRange:"); // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. let (marked_text, string) = if string.is_kind_of::() { let string: *const NSObject = string; let string: *const NSAttributedString = string.cast(); let string = unsafe { &*string }; ( NSMutableAttributedString::from_attributed_nsstring(string), string.string(), ) } else { let string: *const NSObject = string; let string: *const NSString = string.cast(); let string = unsafe { &*string }; ( NSMutableAttributedString::from_nsstring(string), string.copy(), ) }; // Update marked text. *self.ivars().marked_text.borrow_mut() = marked_text; // Notify IME is active if application still doesn't know it. if self.ivars().ime_state.get() == ImeState::Disabled { *self.ivars().input_source.borrow_mut() = self.current_input_source(); self.queue_event(WindowEvent::Ime(Ime::Enabled)); } if unsafe { self.hasMarkedText() } { self.ivars().ime_state.set(ImeState::Preedit); } else { // In case the preedit was cleared, set IME into the Ground state. self.ivars().ime_state.set(ImeState::Ground); } let cursor_range = if string.is_empty() { // An empty string basically means that there's no preedit, so indicate that by // sending a `None` cursor range. None } else { // Convert the selected range from UTF-16 indices to UTF-8 indices. let sub_string_a = unsafe { string.substringToIndex(selected_range.location) }; let sub_string_b = unsafe { string.substringToIndex(selected_range.end()) }; let lowerbound_utf8 = sub_string_a.len(); let upperbound_utf8 = sub_string_b.len(); Some((lowerbound_utf8, upperbound_utf8)) }; // Send WindowEvent for updating marked text self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range))); } #[method(unmarkText)] fn unmark_text(&self) { trace_scope!("unmarkText"); *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); let input_context = self.inputContext().expect("input context"); input_context.discardMarkedText(); self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); if self.is_ime_enabled() { // Leave the Preedit self.ivars() self.ivars().ime_state.set(ImeState::Ground); } else { tracing::warn!("Expected to have IME enabled when receiving unmarkText"); } } #[method_id(validAttributesForMarkedText)] fn valid_attributes_for_marked_text(&self) -> Retained> { trace_scope!("validAttributesForMarkedText"); NSArray::new() } #[method_id(attributedSubstringForProposedRange:actualRange:)] fn attributed_substring_for_proposed_range( &self, _range: NSRange, _actual_range: *mut NSRange, ) -> Option> { trace_scope!("attributedSubstringForProposedRange:actualRange:"); None } #[method(characterIndexForPoint:)] fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger { trace_scope!("characterIndexForPoint:"); 0 } #[method(firstRectForCharacterRange:actualRange:)] fn first_rect_for_character_range( &self, _range: NSRange, _actual_range: *mut NSRange, ) -> NSRect { trace_scope!("firstRectForCharacterRange:actualRange:"); let rect = NSRect::new( self.ivars().ime_position.get(), self.ivars().ime_size.get() ); // Return value is expected to be in screen coordinates, so we need a conversion here self.window() .convertRectToScreen(self.convertRect_toView(rect, None)) } #[method(insertText:replacementRange:)] fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) { // TODO: Use _replacement_range, requires changing the event to report surrounding text. trace_scope!("insertText:replacementRange:"); // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. let string = if string.is_kind_of::() { let string: *const NSObject = string; let string: *const NSAttributedString = string.cast(); unsafe { &*string }.string().to_string() } else { let string: *const NSObject = string; let string: *const NSString = string.cast(); unsafe { &*string }.to_string() }; let is_control = string.chars().next().is_some_and(|c| c.is_control()); // Commit only if we have marked text. if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control { self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); self.queue_event(WindowEvent::Ime(Ime::Commit(string))); self.ivars().ime_state.set(ImeState::Committed); } } // Basically, we're sent this message whenever a keyboard event that doesn't generate a "human // readable" character happens, i.e. newlines, tabs, and Ctrl+C. #[method(doCommandBySelector:)] fn do_command_by_selector(&self, _command: Sel) { trace_scope!("doCommandBySelector:"); // We shouldn't forward any character from just committed text, since we'll end up sending // it twice with some IMEs like Korean one. We'll also always send `Enter` in that case, // which is not desired given it was used to confirm IME input. if self.ivars().ime_state.get() == ImeState::Committed { return; } self.ivars().forward_key_to_app.set(true); if unsafe { self.hasMarkedText() } && self.ivars().ime_state.get() == ImeState::Preedit { // Leave preedit so that we also report the key-up for this key. self.ivars().ime_state.set(ImeState::Ground); } } } unsafe impl WinitView { #[method(keyDown:)] fn key_down(&self, event: &NSEvent) { trace_scope!("keyDown:"); { let mut prev_input_source = self.ivars().input_source.borrow_mut(); let current_input_source = self.current_input_source(); if *prev_input_source != current_input_source && self.is_ime_enabled() { *prev_input_source = current_input_source; drop(prev_input_source); self.ivars().ime_state.set(ImeState::Disabled); self.queue_event(WindowEvent::Ime(Ime::Disabled)); } } // Get the characters from the event. let old_ime_state = self.ivars().ime_state.get(); self.ivars().forward_key_to_app.set(false); let event = replace_event(event, self.option_as_alt()); // The `interpretKeyEvents` function might call // `setMarkedText`, `insertText`, and `doCommandBySelector`. // It's important that we call this before queuing the KeyboardInput, because // we must send the `KeyboardInput` event during IME if it triggered // `doCommandBySelector`. (doCommandBySelector means that the keyboard input // is not handled by IME and should be handled by the application) if self.ivars().ime_allowed.get() { let events_for_nsview = NSArray::from_slice(&[&*event]); unsafe { self.interpretKeyEvents(&events_for_nsview) }; // If the text was committed we must treat the next keyboard event as IME related. if self.ivars().ime_state.get() == ImeState::Committed { // Remove any marked text, so normal input can continue. *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); } } self.update_modifiers(&event, false); let had_ime_input = match self.ivars().ime_state.get() { ImeState::Committed => { // Allow normal input after the commit. self.ivars().ime_state.set(ImeState::Ground); true } ImeState::Preedit => true, // `key_down` could result in preedit clear, so compare old and current state. _ => old_ime_state != self.ivars().ime_state.get(), }; if !had_ime_input || self.ivars().forward_key_to_app.get() { let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }); self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event: key_event, is_synthetic: false, }); } } #[method(keyUp:)] fn key_up(&self, event: &NSEvent) { trace_scope!("keyUp:"); let event = replace_event(event, self.option_as_alt()); self.update_modifiers(&event, false); // We want to send keyboard input when we are currently in the ground state. if matches!( self.ivars().ime_state.get(), ImeState::Ground | ImeState::Disabled ) { self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event: create_key_event(&event, false, false), is_synthetic: false, }); } } #[method(flagsChanged:)] fn flags_changed(&self, event: &NSEvent) { trace_scope!("flagsChanged:"); self.update_modifiers(event, true); } #[method(insertTab:)] fn insert_tab(&self, _sender: Option<&AnyObject>) { trace_scope!("insertTab:"); let window = self.window(); if let Some(first_responder) = window.firstResponder() { if *first_responder == ***self { window.selectNextKeyView(Some(self)) } } } #[method(insertBackTab:)] fn insert_back_tab(&self, _sender: Option<&AnyObject>) { trace_scope!("insertBackTab:"); let window = self.window(); if let Some(first_responder) = window.firstResponder() { if *first_responder == ***self { window.selectPreviousKeyView(Some(self)) } } } // Allows us to receive Cmd-. (the shortcut for closing a dialog) // https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 #[method(cancelOperation:)] fn cancel_operation(&self, _sender: Option<&AnyObject>) { let mtm = MainThreadMarker::from(self); trace_scope!("cancelOperation:"); let event = NSApplication::sharedApplication(mtm) .currentEvent() .expect("could not find current event"); self.update_modifiers(&event, false); let event = create_key_event(&event, true, unsafe { event.isARepeat() }); self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event, is_synthetic: false, }); } // In the past (?), `mouseMoved:` events were not generated when the // user hovered over a window from a separate window, and as such the // application might not know the location of the mouse in the event. // // To fix this, we emit `mouse_motion` inside of mouse click, mouse // scroll, magnify and other gesture event handlers, to ensure that // the application's state of where the mouse click was located is up // to date. // // See https://github.com/rust-windowing/winit/pull/1490 for history. #[method(mouseDown:)] fn mouse_down(&self, event: &NSEvent) { trace_scope!("mouseDown:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } #[method(mouseUp:)] fn mouse_up(&self, event: &NSEvent) { trace_scope!("mouseUp:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Released); } #[method(rightMouseDown:)] fn right_mouse_down(&self, event: &NSEvent) { trace_scope!("rightMouseDown:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } #[method(rightMouseUp:)] fn right_mouse_up(&self, event: &NSEvent) { trace_scope!("rightMouseUp:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Released); } #[method(otherMouseDown:)] fn other_mouse_down(&self, event: &NSEvent) { trace_scope!("otherMouseDown:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } #[method(otherMouseUp:)] fn other_mouse_up(&self, event: &NSEvent) { trace_scope!("otherMouseUp:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Released); } // No tracing on these because that would be overly verbose #[method(mouseMoved:)] fn mouse_moved(&self, event: &NSEvent) { self.mouse_motion(event); } #[method(mouseDragged:)] fn mouse_dragged(&self, event: &NSEvent) { self.mouse_motion(event); } #[method(rightMouseDragged:)] fn right_mouse_dragged(&self, event: &NSEvent) { self.mouse_motion(event); } #[method(otherMouseDragged:)] fn other_mouse_dragged(&self, event: &NSEvent) { self.mouse_motion(event); } #[method(mouseEntered:)] fn mouse_entered(&self, _event: &NSEvent) { trace_scope!("mouseEntered:"); self.queue_event(WindowEvent::CursorEntered { device_id: DEVICE_ID, }); } #[method(mouseExited:)] fn mouse_exited(&self, _event: &NSEvent) { trace_scope!("mouseExited:"); self.queue_event(WindowEvent::CursorLeft { device_id: DEVICE_ID, }); } #[method(scrollWheel:)] fn scroll_wheel(&self, event: &NSEvent) { trace_scope!("scrollWheel:"); self.mouse_motion(event); let delta = { let (x, y) = unsafe { (event.scrollingDeltaX(), event.scrollingDeltaY()) }; if unsafe { event.hasPreciseScrollingDeltas() } { let delta = LogicalPosition::new(x, y).to_physical(self.scale_factor()); MouseScrollDelta::PixelDelta(delta) } else { MouseScrollDelta::LineDelta(x as f32, y as f32) } }; // The "momentum phase," if any, has higher priority than touch phase (the two should // be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum // phase is recorded (or rather, the started/ended cases of the momentum phase) then we // report the touch phase. #[allow(non_upper_case_globals)] let phase = match unsafe { event.momentumPhase() } { NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started, NSEventPhase::Ended | NSEventPhase::Cancelled => TouchPhase::Ended, _ => match unsafe { event.phase() } { NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started, NSEventPhase::Ended | NSEventPhase::Cancelled => TouchPhase::Ended, _ => TouchPhase::Moved, }, }; self.update_modifiers(event, false); self.ivars().app_delegate.maybe_queue_device_event(DeviceEvent::MouseWheel { delta }); self.queue_event(WindowEvent::MouseWheel { device_id: DEVICE_ID, delta, phase, }); } #[method(magnifyWithEvent:)] fn magnify_with_event(&self, event: &NSEvent) { trace_scope!("magnifyWithEvent:"); self.mouse_motion(event); #[allow(non_upper_case_globals)] let phase = match unsafe { event.phase() } { NSEventPhase::Began => TouchPhase::Started, NSEventPhase::Changed => TouchPhase::Moved, NSEventPhase::Cancelled => TouchPhase::Cancelled, NSEventPhase::Ended => TouchPhase::Ended, _ => return, }; self.queue_event(WindowEvent::PinchGesture { device_id: DEVICE_ID, delta: unsafe { event.magnification() }, phase, }); } #[method(smartMagnifyWithEvent:)] fn smart_magnify_with_event(&self, event: &NSEvent) { trace_scope!("smartMagnifyWithEvent:"); self.mouse_motion(event); self.queue_event(WindowEvent::DoubleTapGesture { device_id: DEVICE_ID, }); } #[method(rotateWithEvent:)] fn rotate_with_event(&self, event: &NSEvent) { trace_scope!("rotateWithEvent:"); self.mouse_motion(event); #[allow(non_upper_case_globals)] let phase = match unsafe { event.phase() } { NSEventPhase::Began => TouchPhase::Started, NSEventPhase::Changed => TouchPhase::Moved, NSEventPhase::Cancelled => TouchPhase::Cancelled, NSEventPhase::Ended => TouchPhase::Ended, _ => return, }; self.queue_event(WindowEvent::RotationGesture { device_id: DEVICE_ID, delta: unsafe { event.rotation() }, phase, }); } #[method(pressureChangeWithEvent:)] fn pressure_change_with_event(&self, event: &NSEvent) { trace_scope!("pressureChangeWithEvent:"); self.queue_event(WindowEvent::TouchpadPressure { device_id: DEVICE_ID, pressure: unsafe { event.pressure() }, stage: unsafe { event.stage() } as i64, }); } // Allows us to receive Ctrl-Tab and Ctrl-Esc. // Note that this *doesn't* help with any missing Cmd inputs. // https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816 #[method(_wantsKeyDownForEvent:)] fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool { trace_scope!("_wantsKeyDownForEvent:"); true } #[method(acceptsFirstMouse:)] fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { trace_scope!("acceptsFirstMouse:"); self.ivars().accepts_first_mouse } } ); impl WinitView { pub(super) fn new( app_delegate: &ApplicationDelegate, window: &WinitWindow, accepts_first_mouse: bool, option_as_alt: OptionAsAlt, ) -> Retained { let mtm = MainThreadMarker::from(window); let this = mtm.alloc().set_ivars(ViewState { app_delegate: app_delegate.retain(), cursor_state: Default::default(), ime_position: Default::default(), ime_size: Default::default(), modifiers: Default::default(), phys_modifiers: Default::default(), tracking_rect: Default::default(), ime_state: Default::default(), input_source: Default::default(), ime_allowed: Default::default(), forward_key_to_app: Default::default(), marked_text: Default::default(), accepts_first_mouse, _ns_window: WeakId::new(&window.retain()), option_as_alt: Cell::new(option_as_alt), }); let this: Retained = unsafe { msg_send_id![super(this), init] }; this.setPostsFrameChangedNotifications(true); let notification_center = unsafe { NSNotificationCenter::defaultCenter() }; unsafe { notification_center.addObserver_selector_name_object( &this, sel!(frameDidChange:), Some(NSViewFrameDidChangeNotification), Some(&this), ) } *this.ivars().input_source.borrow_mut() = this.current_input_source(); this } fn window(&self) -> Retained { // TODO: Simply use `window` property on `NSView`. // That only returns a window _after_ the view has been attached though! // (which is incompatible with `frameDidChange:`) // // unsafe { msg_send_id![self, window] } self.ivars()._ns_window.load().expect("view to have a window") } fn queue_event(&self, event: WindowEvent) { self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event); } fn scale_factor(&self) -> f64 { self.window().backingScaleFactor() as f64 } fn is_ime_enabled(&self) -> bool { !matches!(self.ivars().ime_state.get(), ImeState::Disabled) } fn current_input_source(&self) -> String { self.inputContext() .expect("input context") .selectedKeyboardInputSource() .map(|input_source| input_source.to_string()) .unwrap_or_default() } pub(super) fn cursor_icon(&self) -> Retained { self.ivars().cursor_state.borrow().cursor.clone() } pub(super) fn set_cursor_icon(&self, icon: Retained) { let mut cursor_state = self.ivars().cursor_state.borrow_mut(); cursor_state.cursor = icon; } /// Set whether the cursor should be visible or not. /// /// Returns whether the state changed. pub(super) fn set_cursor_visible(&self, visible: bool) -> bool { let mut cursor_state = self.ivars().cursor_state.borrow_mut(); if visible != cursor_state.visible { cursor_state.visible = visible; true } else { false } } pub(super) fn set_ime_allowed(&self, ime_allowed: bool) { if self.ivars().ime_allowed.get() == ime_allowed { return; } self.ivars().ime_allowed.set(ime_allowed); if self.ivars().ime_allowed.get() { return; } // Clear markedText *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); if self.ivars().ime_state.get() != ImeState::Disabled { self.ivars().ime_state.set(ImeState::Disabled); self.queue_event(WindowEvent::Ime(Ime::Disabled)); } } pub(super) fn set_ime_cursor_area(&self, position: NSPoint, size: NSSize) { self.ivars().ime_position.set(position); self.ivars().ime_size.set(size); let input_context = self.inputContext().expect("input context"); input_context.invalidateCharacterCoordinates(); } /// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary. pub(super) fn reset_modifiers(&self) { if !self.ivars().modifiers.get().state().is_empty() { self.ivars().modifiers.set(Modifiers::default()); self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get())); } } pub(super) fn set_option_as_alt(&self, value: OptionAsAlt) { self.ivars().option_as_alt.set(value) } pub(super) fn option_as_alt(&self) -> OptionAsAlt { self.ivars().option_as_alt.get() } /// Update modifiers if `event` has something different fn update_modifiers(&self, ns_event: &NSEvent, is_flags_changed_event: bool) { use ElementState::{Pressed, Released}; let current_modifiers = event_mods(ns_event); let prev_modifiers = self.ivars().modifiers.get(); self.ivars().modifiers.set(current_modifiers); // This function was called form the flagsChanged event, which is triggered // when the user presses/releases a modifier even if the same kind of modifier // has already been pressed. // // When flags changed event has key code of zero it means that event doesn't carry any key // event, thus we can't generate regular presses based on that. The `ModifiersChanged` // later will work though, since the flags are attached to the event and contain valid // information. 'send_event: { if is_flags_changed_event && unsafe { ns_event.keyCode() } != 0 { let scancode = unsafe { ns_event.keyCode() }; let physical_key = scancode_to_physicalkey(scancode as u32); let logical_key = code_to_key(physical_key, scancode); // Ignore processing of unknown modifiers because we can't determine whether // it was pressed or release reliably. // // Furthermore, sometimes normal keys are reported inside flagsChanged:, such as // when holding Caps Lock while pressing another key, see: // https://github.com/alacritty/alacritty/issues/8268 let Some(event_modifier) = key_to_modifier(&logical_key) else { break 'send_event; }; let mut event = KeyEvent { location: code_to_location(physical_key), logical_key: logical_key.clone(), physical_key, repeat: false, // We'll correct this later. state: Pressed, text: None, platform_specific: KeyEventExtra { text_with_all_modifiers: None, key_without_modifiers: logical_key.clone(), }, }; let location_mask = ModLocationMask::from_location(event.location); let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut(); let phys_mod = phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty()); let is_active = current_modifiers.state().contains(event_modifier); let mut events = VecDeque::with_capacity(2); // There is no API for getting whether the button was pressed or released // during this event. For this reason we have to do a bit of magic below // to come up with a good guess whether this key was pressed or released. // (This is not trivial because there are multiple buttons that may affect // the same modifier) if !is_active { event.state = Released; if phys_mod.contains(ModLocationMask::LEFT) { let mut event = event.clone(); event.location = KeyLocation::Left; event.physical_key = get_left_modifier_code(&event.logical_key).into(); events.push_back(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event, is_synthetic: false, }); } if phys_mod.contains(ModLocationMask::RIGHT) { event.location = KeyLocation::Right; event.physical_key = get_right_modifier_code(&event.logical_key).into(); events.push_back(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event, is_synthetic: false, }); } *phys_mod = ModLocationMask::empty(); } else { if *phys_mod == location_mask { // Here we hit a contradiction: // The modifier state was "changed" to active, // yet the only pressed modifier key was the one that we // just got a change event for. // This seemingly means that the only pressed modifier is now released, // but at the same time the modifier became active. // // But this scenario is possible if we released modifiers // while the application was not in focus. (Because we don't // get informed of modifier key events while the application // is not focused) // In this case we prioritize the information // about the current modifier state which means // that the button was pressed. event.state = Pressed; } else { phys_mod.toggle(location_mask); let is_pressed = phys_mod.contains(location_mask); event.state = if is_pressed { Pressed } else { Released }; } events.push_back(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event, is_synthetic: false, }); } drop(phys_mod_state); for event in events { self.queue_event(event); } } } if prev_modifiers == current_modifiers { return; } self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get())); } fn mouse_click(&self, event: &NSEvent, button_state: ElementState) { let button = mouse_button(event); self.update_modifiers(event, false); self.queue_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: button_state, button, }); } fn mouse_motion(&self, event: &NSEvent) { let window_point = unsafe { event.locationInWindow() }; let view_point = self.convertPoint_fromView(window_point, None); let frame = self.frame(); if view_point.x.is_sign_negative() || view_point.y.is_sign_negative() || view_point.x > frame.size.width || view_point.y > frame.size.height { let mouse_buttons_down = unsafe { NSEvent::pressedMouseButtons() }; if mouse_buttons_down == 0 { // Point is outside of the client area (view) and no buttons are pressed return; } } let view_point = LogicalPosition::new(view_point.x, view_point.y); self.update_modifiers(event, false); self.queue_event(WindowEvent::CursorMoved { device_id: DEVICE_ID, position: view_point.to_physical(self.scale_factor()), }); } } /// Get the mouse button from the NSEvent. fn mouse_button(event: &NSEvent) -> MouseButton { // The buttonNumber property only makes sense for the mouse events: // NSLeftMouse.../NSRightMouse.../NSOtherMouse... // For the other events, it's always set to 0. // MacOS only defines the left, right and middle buttons, 3..=31 are left as generic buttons, // but 3 and 4 are very commonly used as Back and Forward by hardware vendors and applications. match unsafe { event.buttonNumber() } { 0 => MouseButton::Left, 1 => MouseButton::Right, 2 => MouseButton::Middle, 3 => MouseButton::Back, 4 => MouseButton::Forward, n => MouseButton::Other(n as u16), } } // NOTE: to get option as alt working we need to rewrite events // we're getting from the operating system, which makes it // impossible to provide such events as extra in `KeyEvent`. fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained { let ev_mods = event_mods(event).state; let ignore_alt_characters = match option_as_alt { OptionAsAlt::OnlyLeft if lalt_pressed(event) => true, OptionAsAlt::OnlyRight if ralt_pressed(event) => true, OptionAsAlt::Both if ev_mods.alt_key() => true, _ => false, } && !ev_mods.control_key() && !ev_mods.super_key(); if ignore_alt_characters { let ns_chars = unsafe { event.charactersIgnoringModifiers().expect("expected characters to be non-null") }; unsafe { NSEvent::keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode( event.r#type(), event.locationInWindow(), event.modifierFlags(), event.timestamp(), event.windowNumber(), None, &ns_chars, &ns_chars, event.isARepeat(), event.keyCode(), ) .unwrap() } } else { event.copy() } } winit-0.30.9/src/platform_impl/macos/window.rs000064400000000000000000000067271046102023000174760ustar 00000000000000#![allow(clippy::unnecessary_cast)] use objc2::rc::{autoreleasepool, Retained}; use objc2::{declare_class, mutability, ClassType, DeclaredClass}; use objc2_app_kit::{NSResponder, NSWindow}; use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject}; use super::event_loop::ActiveEventLoop; use super::window_delegate::WindowDelegate; use crate::error::OsError as RootOsError; use crate::window::WindowAttributes; pub(crate) struct Window { window: MainThreadBound>, /// The window only keeps a weak reference to this, so we must keep it around here. delegate: MainThreadBound>, } impl Drop for Window { fn drop(&mut self) { self.window.get_on_main(|window| autoreleasepool(|_| window.close())) } } impl Window { pub(crate) fn new( window_target: &ActiveEventLoop, attributes: WindowAttributes, ) -> Result { let mtm = window_target.mtm; let delegate = autoreleasepool(|_| { WindowDelegate::new(window_target.app_delegate(), attributes, mtm) })?; Ok(Window { window: MainThreadBound::new(delegate.window().retain(), mtm), delegate: MainThreadBound::new(delegate, mtm), }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&WindowDelegate) + Send + 'static) { // For now, don't actually do queuing, since it may be less predictable self.maybe_wait_on_main(f) } pub(crate) fn maybe_wait_on_main( &self, f: impl FnOnce(&WindowDelegate) -> R + Send, ) -> R { self.delegate.get_on_main(|delegate| f(delegate)) } #[cfg(feature = "rwh_06")] #[inline] pub(crate) fn raw_window_handle_rwh_06( &self, ) -> Result { if let Some(mtm) = MainThreadMarker::new() { Ok(self.delegate.get(mtm).raw_window_handle_rwh_06()) } else { Err(rwh_06::HandleError::Unavailable) } } #[cfg(feature = "rwh_06")] #[inline] pub(crate) fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new())) } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(pub usize); impl WindowId { pub const fn dummy() -> Self { Self(0) } } impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.0 as u64 } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id as usize) } } declare_class!( #[derive(Debug)] pub struct WinitWindow; unsafe impl ClassType for WinitWindow { #[inherits(NSResponder, NSObject)] type Super = NSWindow; type Mutability = mutability::MainThreadOnly; const NAME: &'static str = "WinitWindow"; } impl DeclaredClass for WinitWindow {} unsafe impl WinitWindow { #[method(canBecomeMainWindow)] fn can_become_main_window(&self) -> bool { trace_scope!("canBecomeMainWindow"); true } #[method(canBecomeKeyWindow)] fn can_become_key_window(&self) -> bool { trace_scope!("canBecomeKeyWindow"); true } } ); impl WinitWindow { pub(super) fn id(&self) -> WindowId { WindowId(self as *const Self as usize) } } winit-0.30.9/src/platform_impl/macos/window_delegate.rs000064400000000000000000002232341046102023000213220ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::cell::{Cell, RefCell}; use std::collections::VecDeque; use std::ffi::c_void; use std::ptr; use std::sync::{Arc, Mutex}; use core_graphics::display::{CGDisplay, CGPoint}; use monitor::VideoModeHandle; use objc2::rc::{autoreleasepool, Retained}; use objc2::runtime::{AnyObject, ProtocolObject}; use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass}; use objc2_app_kit::{ NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, }; use objc2_foundation::{ ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSObject, NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString, }; use tracing::{trace, warn}; use super::app_state::ApplicationDelegate; use super::cursor::cursor_from_icon; use super::monitor::{self, flip_window_screen_coordinates, get_display_id}; use super::observer::RunLoop; use super::view::WinitView; use super::window::WinitWindow; use super::{ffi, Fullscreen, MonitorHandle, OsError, WindowId}; use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::event::{InnerSizeWriter, WindowEvent}; use crate::platform::macos::{OptionAsAlt, WindowExtMacOS}; use crate::window::{ Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }; #[derive(Clone, Debug)] pub struct PlatformSpecificWindowAttributes { pub movable_by_window_background: bool, pub titlebar_transparent: bool, pub title_hidden: bool, pub titlebar_hidden: bool, pub titlebar_buttons_hidden: bool, pub fullsize_content_view: bool, pub disallow_hidpi: bool, pub has_shadow: bool, pub accepts_first_mouse: bool, pub tabbing_identifier: Option, pub option_as_alt: OptionAsAlt, pub borderless_game: bool, } impl Default for PlatformSpecificWindowAttributes { #[inline] fn default() -> Self { Self { movable_by_window_background: false, titlebar_transparent: false, title_hidden: false, titlebar_hidden: false, titlebar_buttons_hidden: false, fullsize_content_view: false, disallow_hidpi: false, has_shadow: true, accepts_first_mouse: true, tabbing_identifier: None, option_as_alt: Default::default(), borderless_game: false, } } } #[derive(Debug)] pub(crate) struct State { /// Strong reference to the global application state. app_delegate: Retained, window: Retained, // During `windowDidResize`, we use this to only send Moved if the position changed. // // This is expressed in desktop coordinates, and flipped to match Winit's coordinate system. previous_position: Cell, // Used to prevent redundant events. previous_scale_factor: Cell, /// The current resize increments for the window content. resize_increments: Cell, /// Whether the window is showing decorations. decorations: Cell, resizable: Cell, maximized: Cell, /// Presentation options saved before entering `set_simple_fullscreen`, and /// restored upon exiting it. Also used when transitioning from Borderless to /// Exclusive fullscreen in `set_fullscreen` because we need to disable the menu /// bar in exclusive fullscreen but want to restore the original options when /// transitioning back to borderless fullscreen. save_presentation_opts: Cell>, // This is set when WindowAttributes::with_fullscreen was set, // see comments of `window_did_fail_to_enter_fullscreen` initial_fullscreen: Cell, /// This field tracks the current fullscreen state of the window /// (as seen by `WindowDelegate`). fullscreen: RefCell>, // If it is attempted to toggle fullscreen when in_fullscreen_transition is true, // Set target_fullscreen and do after fullscreen transition is end. target_fullscreen: RefCell>>, // This is true between windowWillEnterFullScreen and windowDidEnterFullScreen // or windowWillExitFullScreen and windowDidExitFullScreen. // We must not toggle fullscreen when this is true. in_fullscreen_transition: Cell, standard_frame: Cell>, is_simple_fullscreen: Cell, saved_style: Cell>, is_borderless_game: Cell, } declare_class!( pub(crate) struct WindowDelegate; unsafe impl ClassType for WindowDelegate { type Super = NSObject; type Mutability = mutability::MainThreadOnly; const NAME: &'static str = "WinitWindowDelegate"; } impl DeclaredClass for WindowDelegate { type Ivars = State; } unsafe impl NSObjectProtocol for WindowDelegate {} unsafe impl NSWindowDelegate for WindowDelegate { #[method(windowShouldClose:)] fn window_should_close(&self, _: Option<&AnyObject>) -> bool { trace_scope!("windowShouldClose:"); self.queue_event(WindowEvent::CloseRequested); false } #[method(windowWillClose:)] fn window_will_close(&self, _: Option<&AnyObject>) { trace_scope!("windowWillClose:"); // `setDelegate:` retains the previous value and then autoreleases it autoreleasepool(|_| { // Since El Capitan, we need to be careful that delegate methods can't // be called after the window closes. self.window().setDelegate(None); }); self.queue_event(WindowEvent::Destroyed); } #[method(windowDidResize:)] fn window_did_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowDidResize:"); // NOTE: WindowEvent::Resized is reported in frameDidChange. self.emit_move_event(); } #[method(windowWillStartLiveResize:)] fn window_will_start_live_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowWillStartLiveResize:"); let increments = self.ivars().resize_increments.get(); self.set_resize_increments_inner(increments); } #[method(windowDidEndLiveResize:)] fn window_did_end_live_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowDidEndLiveResize:"); self.set_resize_increments_inner(NSSize::new(1., 1.)); } // This won't be triggered if the move was part of a resize. #[method(windowDidMove:)] fn window_did_move(&self, _: Option<&AnyObject>) { trace_scope!("windowDidMove:"); self.emit_move_event(); } #[method(windowDidChangeBackingProperties:)] fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) { trace_scope!("windowDidChangeBackingProperties:"); let scale_factor = self.scale_factor(); if scale_factor == self.ivars().previous_scale_factor.get() { return; }; self.ivars().previous_scale_factor.set(scale_factor); let mtm = MainThreadMarker::from(self); let this = self.retain(); RunLoop::main(mtm).queue_closure(move || { this.handle_scale_factor_changed(scale_factor); }); } #[method(windowDidBecomeKey:)] fn window_did_become_key(&self, _: Option<&AnyObject>) { trace_scope!("windowDidBecomeKey:"); // TODO: center the cursor if the window had mouse grab when it // lost focus self.queue_event(WindowEvent::Focused(true)); } #[method(windowDidResignKey:)] fn window_did_resign_key(&self, _: Option<&AnyObject>) { trace_scope!("windowDidResignKey:"); // It happens rather often, e.g. when the user is Cmd+Tabbing, that the // NSWindowDelegate will receive a didResignKey event despite no event // being received when the modifiers are released. This is because // flagsChanged events are received by the NSView instead of the // NSWindowDelegate, and as a result a tracked modifiers state can quite // easily fall out of synchrony with reality. This requires us to emit // a synthetic ModifiersChanged event when we lose focus. self.view().reset_modifiers(); self.queue_event(WindowEvent::Focused(false)); } /// Invoked when before enter fullscreen #[method(windowWillEnterFullScreen:)] fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowWillEnterFullScreen:"); self.ivars().maximized.set(self.is_zoomed()); let mut fullscreen = self.ivars().fullscreen.borrow_mut(); match &*fullscreen { // Exclusive mode sets the state in `set_fullscreen` as the user // can't enter exclusive mode by other means (like the // fullscreen button on the window decorations) Some(Fullscreen::Exclusive(_)) => (), // `window_will_enter_fullscreen` was triggered and we're already // in fullscreen, so we must've reached here by `set_fullscreen` // as it updates the state Some(Fullscreen::Borderless(_)) => (), // Otherwise, we must've reached fullscreen by the user clicking // on the green fullscreen button. Update state! None => { let current_monitor = self.current_monitor_inner(); *fullscreen = Some(Fullscreen::Borderless(current_monitor)); }, } self.ivars().in_fullscreen_transition.set(true); } /// Invoked when before exit fullscreen #[method(windowWillExitFullScreen:)] fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowWillExitFullScreen:"); self.ivars().in_fullscreen_transition.set(true); } #[method(window:willUseFullScreenPresentationOptions:)] fn window_will_use_fullscreen_presentation_options( &self, _: Option<&AnyObject>, proposed_options: NSApplicationPresentationOptions, ) -> NSApplicationPresentationOptions { trace_scope!("window:willUseFullScreenPresentationOptions:"); // Generally, games will want to disable the menu bar and the dock. Ideally, // this would be configurable by the user. Unfortunately because of our // `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is // placed on top of the menu bar in exclusive fullscreen mode. This looks // broken so we always disable the menu bar in exclusive fullscreen. We may // still want to make this configurable for borderless fullscreen. Right now // we don't, for consistency. If we do, it should be documented that the // user-provided options are ignored in exclusive fullscreen. let mut options = proposed_options; let fullscreen = self.ivars().fullscreen.borrow(); if let Some(Fullscreen::Exclusive(_)) = &*fullscreen { options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::NSApplicationPresentationHideDock | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; } options } /// Invoked when entered fullscreen #[method(windowDidEnterFullScreen:)] fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidEnterFullScreen:"); self.ivars().initial_fullscreen.set(false); self.ivars().in_fullscreen_transition.set(false); if let Some(target_fullscreen) = self.ivars().target_fullscreen.take() { self.set_fullscreen(target_fullscreen); } } /// Invoked when exited fullscreen #[method(windowDidExitFullScreen:)] fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidExitFullScreen:"); self.restore_state_from_fullscreen(); self.ivars().in_fullscreen_transition.set(false); if let Some(target_fullscreen) = self.ivars().target_fullscreen.take() { self.set_fullscreen(target_fullscreen); } } /// Invoked when fail to enter fullscreen /// /// When this window launch from a fullscreen app (e.g. launch from VS Code /// terminal), it creates a new virtual desktop and a transition animation. /// This animation takes one second and cannot be disable without /// elevated privileges. In this animation time, all toggleFullscreen events /// will be failed. In this implementation, we will try again by using /// performSelector:withObject:afterDelay: until window_did_enter_fullscreen. /// It should be fine as we only do this at initialization (i.e with_fullscreen /// was set). /// /// From Apple doc: /// In some cases, the transition to enter full-screen mode can fail, /// due to being in the midst of handling some other animation or user gesture. /// This method indicates that there was an error, and you should clean up any /// work you may have done to prepare to enter full-screen mode. #[method(windowDidFailToEnterFullScreen:)] fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidFailToEnterFullScreen:"); self.ivars().in_fullscreen_transition.set(false); self.ivars().target_fullscreen.replace(None); if self.ivars().initial_fullscreen.get() { unsafe { self.window().performSelector_withObject_afterDelay( sel!(toggleFullScreen:), None, 0.5, ) }; } else { self.restore_state_from_fullscreen(); } } // Invoked when the occlusion state of the window changes #[method(windowDidChangeOcclusionState:)] fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) { trace_scope!("windowDidChangeOcclusionState:"); let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible); self.queue_event(WindowEvent::Occluded(!visible)); } #[method(windowDidChangeScreen:)] fn window_did_change_screen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidChangeScreen:"); let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get(); if is_simple_fullscreen { if let Some(screen) = self.window().screen() { self.window().setFrame_display(screen.frame(), true); } } } } unsafe impl NSDraggingDestination for WindowDelegate { /// Invoked when the dragged image enters destination bounds or frame #[method(draggingEntered:)] fn dragging_entered(&self, sender: &NSObject) -> bool { trace_scope!("draggingEntered:"); use std::path::PathBuf; let pb: Retained = unsafe { msg_send_id![sender, draggingPasteboard] }; let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap(); let filenames: Retained> = unsafe { Retained::cast(filenames) }; filenames.into_iter().for_each(|file| { let path = PathBuf::from(file.to_string()); self.queue_event(WindowEvent::HoveredFile(path)); }); true } /// Invoked when the image is released #[method(prepareForDragOperation:)] fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool { trace_scope!("prepareForDragOperation:"); true } /// Invoked after the released image has been removed from the screen #[method(performDragOperation:)] fn perform_drag_operation(&self, sender: &NSObject) -> bool { trace_scope!("performDragOperation:"); use std::path::PathBuf; let pb: Retained = unsafe { msg_send_id![sender, draggingPasteboard] }; let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap(); let filenames: Retained> = unsafe { Retained::cast(filenames) }; filenames.into_iter().for_each(|file| { let path = PathBuf::from(file.to_string()); self.queue_event(WindowEvent::DroppedFile(path)); }); true } /// Invoked when the dragging operation is complete #[method(concludeDragOperation:)] fn conclude_drag_operation(&self, _sender: Option<&NSObject>) { trace_scope!("concludeDragOperation:"); } /// Invoked when the dragging operation is cancelled #[method(draggingExited:)] fn dragging_exited(&self, _sender: Option<&NSObject>) { trace_scope!("draggingExited:"); self.queue_event(WindowEvent::HoveredFileCancelled); } } // Key-Value Observing unsafe impl WindowDelegate { #[method(observeValueForKeyPath:ofObject:change:context:)] fn observe_value( &self, key_path: Option<&NSString>, _object: Option<&AnyObject>, change: Option<&NSDictionary>, _context: *mut c_void, ) { trace_scope!("observeValueForKeyPath:ofObject:change:context:"); // NOTE: We don't _really_ need to check the key path, as there should only be one, but // in the future we might want to observe other key paths. if key_path == Some(ns_string!("effectiveAppearance")) { let change = change.expect("requested a change dictionary in `addObserver`, but none was provided"); let old = change.get(unsafe { NSKeyValueChangeOldKey }).expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`"); let new = change.get(unsafe { NSKeyValueChangeNewKey }).expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`"); // SAFETY: The value of `effectiveAppearance` is `NSAppearance` let old: *const AnyObject = old; let old: *const NSAppearance = old.cast(); let old: &NSAppearance = unsafe { &*old }; let new: *const AnyObject = new; let new: *const NSAppearance = new.cast(); let new: &NSAppearance = unsafe { &*new }; trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed"); // Ignore the change if the window's theme is customized by the user (since in that // case the `effectiveAppearance` is only emitted upon said customization, and then // it's triggered directly by a user action, and we don't want to emit the event). if unsafe { self.window().appearance() }.is_some() { return; } let old = appearance_to_theme(old); let new = appearance_to_theme(new); // Check that the theme changed in Winit's terms (the theme might have changed on // other parameters, such as level of contrast, but the event should not be emitted // in those cases). if old == new { return; } self.queue_event(WindowEvent::ThemeChanged(new)); } else { panic!("unknown observed keypath {key_path:?}"); } } } ); impl Drop for WindowDelegate { fn drop(&mut self) { unsafe { self.window().removeObserver_forKeyPath(self, ns_string!("effectiveAppearance")); } } } fn new_window( app_delegate: &ApplicationDelegate, attrs: &WindowAttributes, mtm: MainThreadMarker, ) -> Option> { autoreleasepool(|_| { let screen = match attrs.fullscreen.clone().map(Into::into) { Some(Fullscreen::Borderless(Some(monitor))) | Some(Fullscreen::Exclusive(VideoModeHandle { monitor, .. })) => { monitor.ns_screen(mtm).or_else(|| NSScreen::mainScreen(mtm)) }, Some(Fullscreen::Borderless(None)) => NSScreen::mainScreen(mtm), None => None, }; let frame = match &screen { Some(screen) => screen.frame(), None => { let scale_factor = NSScreen::mainScreen(mtm) .map(|screen| screen.backingScaleFactor() as f64) .unwrap_or(1.0); let size = match attrs.inner_size { Some(size) => { let size = size.to_logical(scale_factor); NSSize::new(size.width, size.height) }, None => NSSize::new(800.0, 600.0), }; let position = match attrs.position { Some(position) => { let position = position.to_logical(scale_factor); flip_window_screen_coordinates(NSRect::new( NSPoint::new(position.x, position.y), size, )) }, // This value is ignored by calling win.center() below None => NSPoint::new(0.0, 0.0), }; NSRect::new(position, size) }, }; let mut masks = if (!attrs.decorations && screen.is_none()) || attrs.platform_specific.titlebar_hidden { // Resizable without a titlebar or borders // if decorations is set to false, ignore pl_attrs // // if the titlebar is hidden, ignore other pl_attrs NSWindowStyleMask::Borderless | NSWindowStyleMask::Resizable | NSWindowStyleMask::Miniaturizable } else { // default case, resizable window with titlebar and titlebar buttons NSWindowStyleMask::Closable | NSWindowStyleMask::Miniaturizable | NSWindowStyleMask::Resizable | NSWindowStyleMask::Titled }; if !attrs.resizable { masks &= !NSWindowStyleMask::Resizable; } if !attrs.enabled_buttons.contains(WindowButtons::MINIMIZE) { masks &= !NSWindowStyleMask::Miniaturizable; } if !attrs.enabled_buttons.contains(WindowButtons::CLOSE) { masks &= !NSWindowStyleMask::Closable; } if attrs.platform_specific.fullsize_content_view { masks |= NSWindowStyleMask::FullSizeContentView; } let window: Option> = unsafe { msg_send_id![ super(mtm.alloc().set_ivars(())), initWithContentRect: frame, styleMask: masks, backing: NSBackingStoreType::NSBackingStoreBuffered, defer: false, ] }; let window = window?; // It is very important for correct memory management that we // disable the extra release that would otherwise happen when // calling `close` on the window. unsafe { window.setReleasedWhenClosed(false) }; window.setTitle(&NSString::from_str(&attrs.title)); window.setAcceptsMouseMovedEvents(true); if let Some(identifier) = &attrs.platform_specific.tabbing_identifier { window.setTabbingIdentifier(&NSString::from_str(identifier)); window.setTabbingMode(NSWindowTabbingMode::Preferred); } if attrs.content_protected { window.setSharingType(NSWindowSharingType::NSWindowSharingNone); } if attrs.platform_specific.titlebar_transparent { window.setTitlebarAppearsTransparent(true); } if attrs.platform_specific.title_hidden { window.setTitleVisibility(NSWindowTitleVisibility::NSWindowTitleHidden); } if attrs.platform_specific.titlebar_buttons_hidden { for titlebar_button in &[ #[allow(deprecated)] NSWindowFullScreenButton, NSWindowButton::NSWindowMiniaturizeButton, NSWindowButton::NSWindowCloseButton, NSWindowButton::NSWindowZoomButton, ] { if let Some(button) = window.standardWindowButton(*titlebar_button) { button.setHidden(true); } } } if attrs.platform_specific.movable_by_window_background { window.setMovableByWindowBackground(true); } if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) { if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) { button.setEnabled(false); } } if !attrs.platform_specific.has_shadow { window.setHasShadow(false); } if attrs.position.is_none() { window.center(); } let view = WinitView::new( app_delegate, &window, attrs.platform_specific.accepts_first_mouse, attrs.platform_specific.option_as_alt, ); // The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until // macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid // always the default system value in favour of the user's code #[allow(deprecated)] view.setWantsBestResolutionOpenGLSurface(!attrs.platform_specific.disallow_hidpi); // On Mojave, views automatically become layer-backed shortly after being added to // a window. Changing the layer-backedness of a view breaks the association between // the view and its associated OpenGL context. To work around this, on Mojave we // explicitly make the view layer-backed up front so that AppKit doesn't do it // itself and break the association with its context. if unsafe { NSAppKitVersionNumber }.floor() > NSAppKitVersionNumber10_12 { view.setWantsLayer(true); } // Configure the new view as the "key view" for the window window.setContentView(Some(&view)); window.setInitialFirstResponder(Some(&view)); if attrs.transparent { window.setOpaque(false); // See `set_transparent` for details on why we do this. window.setBackgroundColor(unsafe { Some(&NSColor::clearColor()) }); } // register for drag and drop operations. window .registerForDraggedTypes(&NSArray::from_id_slice(&[ unsafe { NSFilenamesPboardType }.copy() ])); Some(window) }) } impl WindowDelegate { pub(super) fn new( app_delegate: &ApplicationDelegate, attrs: WindowAttributes, mtm: MainThreadMarker, ) -> Result, RootOsError> { let window = new_window(app_delegate, &attrs, mtm) .ok_or_else(|| os_error!(OsError::CreationError("couldn't create `NSWindow`")))?; #[cfg(feature = "rwh_06")] match attrs.parent_window.map(|handle| handle.0) { Some(rwh_06::RawWindowHandle::AppKit(handle)) => { // SAFETY: Caller ensures the pointer is valid or NULL // Unwrap is fine, since the pointer comes from `NonNull`. let parent_view: Retained = unsafe { Retained::retain(handle.ns_view.as_ptr().cast()) }.unwrap(); let parent = parent_view.window().ok_or_else(|| { os_error!(OsError::CreationError("parent view should be installed in a window")) })?; // SAFETY: We know that there are no parent -> child -> parent cycles since the only // place in `winit` where we allow making a window a child window is // right here, just after it's been created. unsafe { parent.addChildWindow_ordered(&window, NSWindowOrderingMode::NSWindowAbove) }; }, Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"), None => (), } let resize_increments = match attrs.resize_increments.map(|i| i.to_logical(window.backingScaleFactor() as _)) { Some(LogicalSize { width, height }) if width >= 1. && height >= 1. => { NSSize::new(width, height) }, _ => NSSize::new(1., 1.), }; let scale_factor = window.backingScaleFactor() as _; if let Some(appearance) = theme_to_appearance(attrs.preferred_theme) { unsafe { window.setAppearance(Some(&appearance)) }; } let delegate = mtm.alloc().set_ivars(State { app_delegate: app_delegate.retain(), window: window.retain(), previous_position: Cell::new(flip_window_screen_coordinates(window.frame())), previous_scale_factor: Cell::new(scale_factor), resize_increments: Cell::new(resize_increments), decorations: Cell::new(attrs.decorations), resizable: Cell::new(attrs.resizable), maximized: Cell::new(attrs.maximized), save_presentation_opts: Cell::new(None), initial_fullscreen: Cell::new(attrs.fullscreen.is_some()), fullscreen: RefCell::new(None), target_fullscreen: RefCell::new(None), in_fullscreen_transition: Cell::new(false), standard_frame: Cell::new(None), is_simple_fullscreen: Cell::new(false), saved_style: Cell::new(None), is_borderless_game: Cell::new(attrs.platform_specific.borderless_game), }); let delegate: Retained = unsafe { msg_send_id![super(delegate), init] }; if scale_factor != 1.0 { let delegate = delegate.clone(); RunLoop::main(mtm).queue_closure(move || { delegate.handle_scale_factor_changed(scale_factor); }); } window.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); // Listen for theme change event. // // SAFETY: The observer is un-registered in the `Drop` of the delegate. unsafe { window.addObserver_forKeyPath_options_context( &delegate, ns_string!("effectiveAppearance"), NSKeyValueObservingOptions::NSKeyValueObservingOptionNew | NSKeyValueObservingOptions::NSKeyValueObservingOptionOld, ptr::null_mut(), ) }; if attrs.blur { delegate.set_blur(attrs.blur); } if let Some(dim) = attrs.min_inner_size { delegate.set_min_inner_size(Some(dim)); } if let Some(dim) = attrs.max_inner_size { delegate.set_max_inner_size(Some(dim)); } delegate.set_window_level(attrs.window_level); delegate.set_cursor(attrs.cursor); // XXX Send `Focused(false)` right after creating the window delegate, so we won't // obscure the real focused events on the startup. delegate.queue_event(WindowEvent::Focused(false)); // Set fullscreen mode after we setup everything delegate.set_fullscreen(attrs.fullscreen.map(Into::into)); // Setting the window as key has to happen *after* we set the fullscreen // state, since otherwise we'll briefly see the window at normal size // before it transitions. if attrs.visible { if attrs.active { // Tightly linked with `app_state::window_activation_hack` window.makeKeyAndOrderFront(None); } else { window.orderFront(None); } } if attrs.maximized { delegate.set_maximized(attrs.maximized); } Ok(delegate) } #[track_caller] pub(super) fn view(&self) -> Retained { // SAFETY: The view inside WinitWindow is always `WinitView` unsafe { Retained::cast(self.window().contentView().unwrap()) } } #[track_caller] pub(super) fn window(&self) -> &WinitWindow { &self.ivars().window } #[track_caller] pub(crate) fn id(&self) -> WindowId { self.window().id() } pub(crate) fn queue_event(&self, event: WindowEvent) { self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event); } fn handle_scale_factor_changed(&self, scale_factor: CGFloat) { let app_delegate = &self.ivars().app_delegate; let window = self.window(); let content_size = window.contentRectForFrameRect(window.frame()).size; let content_size = LogicalSize::new(content_size.width, content_size.height); let suggested_size = content_size.to_physical(scale_factor); let new_inner_size = Arc::new(Mutex::new(suggested_size)); app_delegate.handle_window_event(window.id(), WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), }); let physical_size = *new_inner_size.lock().unwrap(); drop(new_inner_size); if physical_size != suggested_size { let logical_size = physical_size.to_logical(scale_factor); let size = NSSize::new(logical_size.width, logical_size.height); window.setContentSize(size); } app_delegate.handle_window_event(window.id(), WindowEvent::Resized(physical_size)); } fn emit_move_event(&self) { let position = flip_window_screen_coordinates(self.window().frame()); if self.ivars().previous_position.get() == position { return; } self.ivars().previous_position.set(position); let position = LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor()); self.queue_event(WindowEvent::Moved(position)); } fn set_style_mask(&self, mask: NSWindowStyleMask) { self.window().setStyleMask(mask); // If we don't do this, key handling will break // (at least until the window is clicked again/etc.) let _ = self.window().makeFirstResponder(Some(&self.view())); } pub fn set_title(&self, title: &str) { self.window().setTitle(&NSString::from_str(title)) } pub fn set_transparent(&self, transparent: bool) { // This is just a hint for Quartz, it doesn't actually speculate with window alpha. // Providing a wrong value here could result in visual artifacts, when the window is // transparent. self.window().setOpaque(!transparent); // AppKit draws the window with a background color by default, which is usually really // nice, but gets in the way when we want to allow the contents of the window to be // transparent, as in that case, the transparent contents will just be drawn on top of // the background color. As such, to allow the window to be transparent, we must also set // the background color to one with an empty alpha channel. let color = if transparent { unsafe { NSColor::clearColor() } } else { unsafe { NSColor::windowBackgroundColor() } }; self.window().setBackgroundColor(Some(&color)); } pub fn set_blur(&self, blur: bool) { // NOTE: in general we want to specify the blur radius, but the choice of 80 // should be a reasonable default. let radius = if blur { 80 } else { 0 }; let window_number = unsafe { self.window().windowNumber() }; unsafe { ffi::CGSSetWindowBackgroundBlurRadius( ffi::CGSMainConnectionID(), window_number, radius, ); } } pub fn set_visible(&self, visible: bool) { match visible { true => self.window().makeKeyAndOrderFront(None), false => self.window().orderOut(None), } } #[inline] pub fn is_visible(&self) -> Option { Some(self.window().isVisible()) } pub fn request_redraw(&self) { self.ivars().app_delegate.queue_redraw(self.window().id()); } #[inline] pub fn pre_present_notify(&self) {} pub fn outer_position(&self) -> Result, NotSupportedError> { let position = flip_window_screen_coordinates(self.window().frame()); Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor())) } pub fn inner_position(&self) -> Result, NotSupportedError> { let content_rect = self.window().contentRectForFrameRect(self.window().frame()); let position = flip_window_screen_coordinates(content_rect); Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor())) } pub fn set_outer_position(&self, position: Position) { let position = position.to_logical(self.scale_factor()); let point = flip_window_screen_coordinates(NSRect::new( NSPoint::new(position.x, position.y), self.window().frame().size, )); unsafe { self.window().setFrameOrigin(point) }; } #[inline] pub fn inner_size(&self) -> PhysicalSize { let content_rect = self.window().contentRectForFrameRect(self.window().frame()); let logical = LogicalSize::new(content_rect.size.width, content_rect.size.height); logical.to_physical(self.scale_factor()) } #[inline] pub fn outer_size(&self) -> PhysicalSize { let frame = self.window().frame(); let logical = LogicalSize::new(frame.size.width, frame.size.height); logical.to_physical(self.scale_factor()) } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let scale_factor = self.scale_factor(); let size = size.to_logical(scale_factor); self.window().setContentSize(NSSize::new(size.width, size.height)); None } pub fn set_min_inner_size(&self, dimensions: Option) { let dimensions = dimensions.unwrap_or(Size::Logical(LogicalSize { width: 0.0, height: 0.0 })); let min_size = dimensions.to_logical::(self.scale_factor()); let min_size = NSSize::new(min_size.width, min_size.height); unsafe { self.window().setContentMinSize(min_size) }; // If necessary, resize the window to match constraint let mut current_size = self.window().contentRectForFrameRect(self.window().frame()).size; if current_size.width < min_size.width { current_size.width = min_size.width; } if current_size.height < min_size.height { current_size.height = min_size.height; } self.window().setContentSize(current_size); } pub fn set_max_inner_size(&self, dimensions: Option) { let dimensions = dimensions.unwrap_or(Size::Logical(LogicalSize { width: f32::MAX as f64, height: f32::MAX as f64, })); let scale_factor = self.scale_factor(); let max_size = dimensions.to_logical::(scale_factor); let max_size = NSSize::new(max_size.width, max_size.height); unsafe { self.window().setContentMaxSize(max_size) }; // If necessary, resize the window to match constraint let mut current_size = self.window().contentRectForFrameRect(self.window().frame()).size; if max_size.width < current_size.width { current_size.width = max_size.width; } if max_size.height < current_size.height { current_size.height = max_size.height; } self.window().setContentSize(current_size); } pub fn resize_increments(&self) -> Option> { let increments = self.ivars().resize_increments.get(); let (w, h) = (increments.width, increments.height); if w > 1.0 || h > 1.0 { Some(LogicalSize::new(w, h).to_physical(self.scale_factor())) } else { None } } pub fn set_resize_increments(&self, increments: Option) { // XXX the resize increments are only used during live resizes. self.ivars().resize_increments.set( increments .map(|increments| { let logical = increments.to_logical::(self.scale_factor()); NSSize::new(logical.width.max(1.0), logical.height.max(1.0)) }) .unwrap_or_else(|| NSSize::new(1.0, 1.0)), ); } pub(crate) fn set_resize_increments_inner(&self, size: NSSize) { // It was concluded (#2411) that there is never a use-case for // "outer" resize increments, hence we set "inner" ones here. // ("outer" in macOS being just resizeIncrements, and "inner" - contentResizeIncrements) // This is consistent with X11 size hints behavior self.window().setContentResizeIncrements(size); } #[inline] pub fn set_resizable(&self, resizable: bool) { self.ivars().resizable.set(resizable); let fullscreen = self.ivars().fullscreen.borrow().is_some(); if !fullscreen { let mut mask = self.window().styleMask(); if resizable { mask |= NSWindowStyleMask::Resizable; } else { mask &= !NSWindowStyleMask::Resizable; } self.set_style_mask(mask); } // Otherwise, we don't change the mask until we exit fullscreen. } #[inline] pub fn is_resizable(&self) -> bool { self.window().isResizable() } #[inline] pub fn set_enabled_buttons(&self, buttons: WindowButtons) { let mut mask = self.window().styleMask(); if buttons.contains(WindowButtons::CLOSE) { mask |= NSWindowStyleMask::Closable; } else { mask &= !NSWindowStyleMask::Closable; } if buttons.contains(WindowButtons::MINIMIZE) { mask |= NSWindowStyleMask::Miniaturizable; } else { mask &= !NSWindowStyleMask::Miniaturizable; } // This must happen before the button's "enabled" status has been set, // hence we do it synchronously. self.set_style_mask(mask); // We edit the button directly instead of using `NSResizableWindowMask`, // since that mask also affect the resizability of the window (which is // controllable by other means in `winit`). if let Some(button) = self.window().standardWindowButton(NSWindowButton::NSWindowZoomButton) { button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE)); } } #[inline] pub fn enabled_buttons(&self) -> WindowButtons { let mut buttons = WindowButtons::empty(); if self.window().isMiniaturizable() { buttons |= WindowButtons::MINIMIZE; } if self .window() .standardWindowButton(NSWindowButton::NSWindowZoomButton) .map(|b| b.isEnabled()) .unwrap_or(true) { buttons |= WindowButtons::MAXIMIZE; } if self.window().hasCloseBox() { buttons |= WindowButtons::CLOSE; } buttons } pub fn set_cursor(&self, cursor: Cursor) { let view = self.view(); let cursor = match cursor { Cursor::Icon(icon) => cursor_from_icon(icon), Cursor::Custom(cursor) => cursor.inner.0, }; if view.cursor_icon() == cursor { return; } view.set_cursor_icon(cursor); self.window().invalidateCursorRectsForView(&view); } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { let associate_mouse_cursor = match mode { CursorGrabMode::Locked => false, CursorGrabMode::None => true, CursorGrabMode::Confined => { return Err(ExternalError::NotSupported(NotSupportedError::new())) }, }; // TODO: Do this for real https://stackoverflow.com/a/40922095/5435443 CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor) .map_err(|status| ExternalError::Os(os_error!(OsError::CGError(status)))) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { let view = self.view(); let state_changed = view.set_cursor_visible(visible); if state_changed { self.window().invalidateCursorRectsForView(&view); } } #[inline] pub fn scale_factor(&self) -> f64 { self.window().backingScaleFactor() as _ } #[inline] pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> { let physical_window_position = self.inner_position().unwrap(); let scale_factor = self.scale_factor(); let window_position = physical_window_position.to_logical::(scale_factor); let logical_cursor_position = cursor_position.to_logical::(scale_factor); let point = CGPoint { x: logical_cursor_position.x + window_position.x, y: logical_cursor_position.y + window_position.y, }; CGDisplay::warp_mouse_cursor_position(point) .map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?; CGDisplay::associate_mouse_and_mouse_cursor_position(true) .map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?; Ok(()) } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { let mtm = MainThreadMarker::from(self); let event = NSApplication::sharedApplication(mtm).currentEvent().ok_or(ExternalError::Ignored)?; self.window().performWindowDragWithEvent(&event); Ok(()) } #[inline] pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn show_window_menu(&self, _position: Position) {} #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { self.window().setIgnoresMouseEvents(!hittest); Ok(()) } pub(crate) fn is_zoomed(&self) -> bool { // because `isZoomed` doesn't work if the window's borderless, // we make it resizable temporarily. let curr_mask = self.window().styleMask(); let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable; let needs_temp_mask = !curr_mask.contains(required); if needs_temp_mask { self.set_style_mask(required); } let is_zoomed = self.window().isZoomed(); // Roll back temp styles if needs_temp_mask { self.set_style_mask(curr_mask); } is_zoomed } fn saved_style(&self) -> NSWindowStyleMask { let base_mask = self.ivars().saved_style.take().unwrap_or_else(|| self.window().styleMask()); if self.ivars().resizable.get() { base_mask | NSWindowStyleMask::Resizable } else { base_mask & !NSWindowStyleMask::Resizable } } /// This is called when the window is exiting fullscreen, whether by the /// user clicking on the green fullscreen button or programmatically by /// `toggleFullScreen:` pub(crate) fn restore_state_from_fullscreen(&self) { self.ivars().fullscreen.replace(None); let maximized = self.ivars().maximized.get(); let mask = self.saved_style(); self.set_style_mask(mask); self.set_maximized(maximized); } #[inline] pub fn set_minimized(&self, minimized: bool) { let is_minimized = self.window().isMiniaturized(); if is_minimized == minimized { return; } if minimized { self.window().miniaturize(Some(self)); } else { unsafe { self.window().deminiaturize(Some(self)) }; } } #[inline] pub fn is_minimized(&self) -> Option { Some(self.window().isMiniaturized()) } #[inline] pub fn set_maximized(&self, maximized: bool) { let mtm = MainThreadMarker::from(self); let is_zoomed = self.is_zoomed(); if is_zoomed == maximized { return; }; // Save the standard frame sized if it is not zoomed if !is_zoomed { self.ivars().standard_frame.set(Some(self.window().frame())); } self.ivars().maximized.set(maximized); if self.ivars().fullscreen.borrow().is_some() { // Handle it in window_did_exit_fullscreen return; } if self.window().styleMask().contains(NSWindowStyleMask::Resizable) { // Just use the native zoom if resizable self.window().zoom(None); } else { // if it's not resizable, we set the frame directly let new_rect = if maximized { let screen = NSScreen::mainScreen(mtm).expect("no screen found"); screen.visibleFrame() } else { self.ivars().standard_frame.get().unwrap_or(DEFAULT_STANDARD_FRAME) }; self.window().setFrame_display(new_rect, false); } } #[inline] pub(crate) fn fullscreen(&self) -> Option { self.ivars().fullscreen.borrow().clone() } #[inline] pub fn is_maximized(&self) -> bool { self.is_zoomed() } #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { let mtm = MainThreadMarker::from(self); let app = NSApplication::sharedApplication(mtm); if self.ivars().is_simple_fullscreen.get() { return; } if self.ivars().in_fullscreen_transition.get() { // We can't set fullscreen here. // Set fullscreen after transition. self.ivars().target_fullscreen.replace(Some(fullscreen)); return; } let old_fullscreen = self.ivars().fullscreen.borrow().clone(); if fullscreen == old_fullscreen { return; } // If the fullscreen is on a different monitor, we must move the window // to that monitor before we toggle fullscreen (as `toggleFullScreen` // does not take a screen parameter, but uses the current screen) if let Some(ref fullscreen) = fullscreen { let new_screen = match fullscreen { Fullscreen::Borderless(Some(monitor)) => monitor.clone(), Fullscreen::Borderless(None) => { if let Some(monitor) = self.current_monitor_inner() { monitor } else { return; } }, Fullscreen::Exclusive(video_mode) => video_mode.monitor(), } .ns_screen(mtm) .unwrap(); let old_screen = self.window().screen().unwrap(); if old_screen != new_screen { unsafe { self.window().setFrameOrigin(new_screen.frame().origin) }; } } if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen { // Note: `enterFullScreenMode:withOptions:` seems to do the exact // same thing as we're doing here (captures the display, sets the // video mode, and hides the menu bar and dock), with the exception // of that I couldn't figure out how to set the display mode with // it. I think `enterFullScreenMode:withOptions:` is still using the // older display mode API where display modes were of the type // `CFDictionary`, but this has changed, so we can't obtain the // correct parameter for this any longer. Apple's code samples for // this function seem to just pass in "YES" for the display mode // parameter, which is not consistent with the docs saying that it // takes a `NSDictionary`.. let display_id = video_mode.monitor().native_identifier(); let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken; if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) { self.ivars().save_presentation_opts.replace(Some(app.presentationOptions())); } unsafe { // Fade to black (and wait for the fade to complete) to hide the // flicker from capturing the display and switching display mode if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token) == ffi::kCGErrorSuccess { ffi::CGDisplayFade( fade_token, 0.3, ffi::kCGDisplayBlendNormal, ffi::kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, ffi::TRUE, ); } assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess); } unsafe { let result = ffi::CGDisplaySetDisplayMode( display_id, video_mode.native_mode.0, std::ptr::null(), ); assert!(result == ffi::kCGErrorSuccess, "failed to set video mode"); // After the display has been configured, fade back in // asynchronously if fade_token != ffi::kCGDisplayFadeReservationInvalidToken { ffi::CGDisplayFade( fade_token, 0.6, ffi::kCGDisplayBlendSolidColor, ffi::kCGDisplayBlendNormal, 0.0, 0.0, 0.0, ffi::FALSE, ); ffi::CGReleaseDisplayFadeReservation(fade_token); } } } self.ivars().fullscreen.replace(fullscreen.clone()); fn toggle_fullscreen(window: &WinitWindow) { // Window level must be restored from `CGShieldingWindowLevel() // + 1` back to normal in order for `toggleFullScreen` to do // anything window.setLevel(ffi::kCGNormalWindowLevel as NSWindowLevel); window.toggleFullScreen(None); } match (old_fullscreen, fullscreen) { (None, Some(fullscreen)) => { // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we // set a normal style temporarily. The previous state will be // restored in `WindowDelegate::window_did_exit_fullscreen`. let curr_mask = self.window().styleMask(); let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable; if !curr_mask.contains(required) { self.set_style_mask(required); self.ivars().saved_style.set(Some(curr_mask)); } // In borderless games, we want to disable the dock and menu bar // by setting the presentation options. We do this here rather than in // `window:willUseFullScreenPresentationOptions` because for some reason // the menu bar remains interactable despite being hidden. if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) { let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; app.setPresentationOptions(presentation_options); } toggle_fullscreen(self.window()); }, (Some(Fullscreen::Borderless(_)), None) => { // State is restored by `window_did_exit_fullscreen` toggle_fullscreen(self.window()); }, (Some(Fullscreen::Exclusive(ref video_mode)), None) => { restore_and_release_display(&video_mode.monitor()); toggle_fullscreen(self.window()); }, (Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => { // If we're already in fullscreen mode, calling // `CGDisplayCapture` will place the shielding window on top of // our window, which results in a black display and is not what // we want. So, we must place our window on top of the shielding // window. Unfortunately, this also makes our window be on top // of the menu bar, and this looks broken, so we must make sure // that the menu bar is disabled. This is done in the window // delegate in `window:willUseFullScreenPresentationOptions:`. self.ivars().save_presentation_opts.set(Some(app.presentationOptions())); let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::NSApplicationPresentationHideDock | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; app.setPresentationOptions(presentation_options); let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1; self.window().setLevel(window_level); }, (Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => { let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or( NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar ); app.setPresentationOptions(presentation_options); restore_and_release_display(&video_mode.monitor()); // Restore the normal window level following the Borderless fullscreen // `CGShieldingWindowLevel() + 1` hack. self.window().setLevel(ffi::kCGNormalWindowLevel as NSWindowLevel); }, _ => {}, }; } #[inline] pub fn set_decorations(&self, decorations: bool) { if decorations == self.ivars().decorations.get() { return; } self.ivars().decorations.set(decorations); let fullscreen = self.ivars().fullscreen.borrow().is_some(); let resizable = self.ivars().resizable.get(); // If we're in fullscreen mode, we wait to apply decoration changes // until we're in `window_did_exit_fullscreen`. if fullscreen { return; } let new_mask = { let mut new_mask = if decorations { NSWindowStyleMask::Closable | NSWindowStyleMask::Miniaturizable | NSWindowStyleMask::Resizable | NSWindowStyleMask::Titled } else { NSWindowStyleMask::Borderless | NSWindowStyleMask::Resizable }; if !resizable { new_mask &= !NSWindowStyleMask::Resizable; } new_mask }; self.set_style_mask(new_mask); } #[inline] pub fn is_decorated(&self) -> bool { self.ivars().decorations.get() } #[inline] pub fn set_window_level(&self, level: WindowLevel) { let level = match level { WindowLevel::AlwaysOnTop => ffi::kCGFloatingWindowLevel as NSWindowLevel, WindowLevel::AlwaysOnBottom => (ffi::kCGNormalWindowLevel - 1) as NSWindowLevel, WindowLevel::Normal => ffi::kCGNormalWindowLevel as NSWindowLevel, }; self.window().setLevel(level); } #[inline] pub fn set_window_icon(&self, _icon: Option) { // macOS doesn't have window icons. Though, there is // `setRepresentedFilename`, but that's semantically distinct and should // only be used when the window is in some way representing a specific // file/directory. For instance, Terminal.app uses this for the CWD. // Anyway, that should eventually be implemented as // `WindowAttributesExt::with_represented_file` or something, and doesn't // have anything to do with `set_window_icon`. // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html } #[inline] pub fn set_ime_cursor_area(&self, spot: Position, size: Size) { let scale_factor = self.scale_factor(); let logical_spot = spot.to_logical(scale_factor); let logical_spot = NSPoint::new(logical_spot.x, logical_spot.y); let size = size.to_logical(scale_factor); let size = NSSize::new(size.width, size.height); self.view().set_ime_cursor_area(logical_spot, size); } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { self.view().set_ime_allowed(allowed); } #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] pub fn focus_window(&self) { let mtm = MainThreadMarker::from(self); let is_minimized = self.window().isMiniaturized(); let is_visible = self.window().isVisible(); if !is_minimized && is_visible { #[allow(deprecated)] NSApplication::sharedApplication(mtm).activateIgnoringOtherApps(true); self.window().makeKeyAndOrderFront(None); } } #[inline] pub fn request_user_attention(&self, request_type: Option) { let mtm = MainThreadMarker::from(self); let ns_request_type = request_type.map(|ty| match ty { UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest, UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest, }); if let Some(ty) = ns_request_type { NSApplication::sharedApplication(mtm).requestUserAttention(ty); } } #[inline] // Allow directly accessing the current monitor internally without unwrapping. pub(crate) fn current_monitor_inner(&self) -> Option { let display_id = get_display_id(&*self.window().screen()?); Some(MonitorHandle::new(display_id)) } #[inline] pub fn current_monitor(&self) -> Option { self.current_monitor_inner() } #[inline] pub fn available_monitors(&self) -> VecDeque { monitor::available_monitors() } #[inline] pub fn primary_monitor(&self) -> Option { let monitor = monitor::primary_monitor(); Some(monitor) } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::AppKitHandle::empty(); window_handle.ns_window = self.window() as *const WinitWindow as *mut _; window_handle.ns_view = Retained::as_ptr(&self.view()) as *mut _; rwh_04::RawWindowHandle::AppKit(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::AppKitWindowHandle::empty(); window_handle.ns_window = self.window() as *const WinitWindow as *mut _; window_handle.ns_view = Retained::as_ptr(&self.view()) as *mut _; rwh_05::RawWindowHandle::AppKit(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle { let window_handle = rwh_06::AppKitWindowHandle::new({ let ptr = Retained::as_ptr(&self.view()) as *mut _; std::ptr::NonNull::new(ptr).expect("Retained should never be null") }); rwh_06::RawWindowHandle::AppKit(window_handle) } fn toggle_style_mask(&self, mask: NSWindowStyleMask, on: bool) { let current_style_mask = self.window().styleMask(); if on { self.set_style_mask(current_style_mask | mask); } else { self.set_style_mask(current_style_mask & !mask); } } #[inline] pub fn has_focus(&self) -> bool { self.window().isKeyWindow() } pub fn theme(&self) -> Option { unsafe { self.window().appearance() } .map(|appearance| appearance_to_theme(&appearance)) .or_else(|| { let mtm = MainThreadMarker::from(self); let app = NSApplication::sharedApplication(mtm); if app.respondsToSelector(sel!(effectiveAppearance)) { Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) } else { Some(Theme::Light) } }) } pub fn set_theme(&self, theme: Option) { unsafe { self.window().setAppearance(theme_to_appearance(theme).as_deref()) }; } #[inline] pub fn set_content_protected(&self, protected: bool) { self.window().setSharingType(if protected { NSWindowSharingType::NSWindowSharingNone } else { NSWindowSharingType::NSWindowSharingReadOnly }) } pub fn title(&self) -> String { self.window().title().to_string() } pub fn reset_dead_keys(&self) { // (Artur) I couldn't find a way to implement this. } } fn restore_and_release_display(monitor: &MonitorHandle) { let available_monitors = monitor::available_monitors(); if available_monitors.contains(monitor) { unsafe { ffi::CGRestorePermanentDisplayConfiguration(); assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess); }; } else { warn!( monitor = monitor.name(), "Tried to restore exclusive fullscreen on a monitor that is no longer available" ); } } impl WindowExtMacOS for WindowDelegate { #[inline] fn simple_fullscreen(&self) -> bool { self.ivars().is_simple_fullscreen.get() } #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { let mtm = MainThreadMarker::from(self); let app = NSApplication::sharedApplication(mtm); let is_native_fullscreen = self.ivars().fullscreen.borrow().is_some(); let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get(); // Do nothing if native fullscreen is active. if is_native_fullscreen || (fullscreen && is_simple_fullscreen) || (!fullscreen && !is_simple_fullscreen) { return false; } if fullscreen { // Remember the original window's settings // Exclude title bar self.ivars() .standard_frame .set(Some(self.window().contentRectForFrameRect(self.window().frame()))); self.ivars().saved_style.set(Some(self.window().styleMask())); self.ivars().save_presentation_opts.set(Some(app.presentationOptions())); // Tell our window's state that we're in fullscreen self.ivars().is_simple_fullscreen.set(true); // Simulate pre-Lion fullscreen by hiding the dock and menu bar let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar; app.setPresentationOptions(presentation_options); // Hide the titlebar self.toggle_style_mask(NSWindowStyleMask::Titled, false); // Set the window frame to the screen frame size let screen = self.window().screen().expect("expected screen to be available"); self.window().setFrame_display(screen.frame(), true); // Fullscreen windows can't be resized, minimized, or moved self.toggle_style_mask(NSWindowStyleMask::Miniaturizable, false); self.toggle_style_mask(NSWindowStyleMask::Resizable, false); self.window().setMovable(false); true } else { let new_mask = self.saved_style(); self.set_style_mask(new_mask); self.ivars().is_simple_fullscreen.set(false); let save_presentation_opts = self.ivars().save_presentation_opts.get(); let frame = self.ivars().standard_frame.get().unwrap_or(DEFAULT_STANDARD_FRAME); if let Some(presentation_opts) = save_presentation_opts { app.setPresentationOptions(presentation_opts); } self.window().setFrame_display(frame, true); self.window().setMovable(true); true } } #[inline] fn has_shadow(&self) -> bool { self.window().hasShadow() } #[inline] fn set_has_shadow(&self, has_shadow: bool) { self.window().setHasShadow(has_shadow) } #[inline] fn set_tabbing_identifier(&self, identifier: &str) { self.window().setTabbingIdentifier(&NSString::from_str(identifier)) } #[inline] fn tabbing_identifier(&self) -> String { self.window().tabbingIdentifier().to_string() } #[inline] fn select_next_tab(&self) { self.window().selectNextTab(None) } #[inline] fn select_previous_tab(&self) { unsafe { self.window().selectPreviousTab(None) } } #[inline] fn select_tab_at_index(&self, index: usize) { if let Some(group) = self.window().tabGroup() { if let Some(windows) = unsafe { self.window().tabbedWindows() } { if index < windows.len() { group.setSelectedWindow(Some(&windows[index])); } } } } #[inline] fn num_tabs(&self) -> usize { unsafe { self.window().tabbedWindows() }.map(|windows| windows.len()).unwrap_or(1) } fn is_document_edited(&self) -> bool { self.window().isDocumentEdited() } fn set_document_edited(&self, edited: bool) { self.window().setDocumentEdited(edited) } fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { self.view().set_option_as_alt(option_as_alt); } fn option_as_alt(&self) -> OptionAsAlt { self.view().option_as_alt() } fn set_borderless_game(&self, borderless_game: bool) { self.ivars().is_borderless_game.set(borderless_game); } fn is_borderless_game(&self) -> bool { self.ivars().is_borderless_game.get() } } const DEFAULT_STANDARD_FRAME: NSRect = NSRect::new(NSPoint::new(50.0, 50.0), NSSize::new(800.0, 600.0)); fn dark_appearance_name() -> &'static NSString { // Don't use the static `NSAppearanceNameDarkAqua` to allow linking on macOS < 10.14 ns_string!("NSAppearanceNameDarkAqua") } pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme { let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[ unsafe { NSAppearanceNameAqua.copy() }, dark_appearance_name().copy(), ])); if let Some(best_match) = best_match { if *best_match == *dark_appearance_name() { Theme::Dark } else { Theme::Light } } else { warn!(?appearance, "failed to determine the theme of the appearance"); // Default to light in this case Theme::Light } } fn theme_to_appearance(theme: Option) -> Option> { let appearance = match theme? { Theme::Light => unsafe { NSAppearance::appearanceNamed(NSAppearanceNameAqua) }, Theme::Dark => NSAppearance::appearanceNamed(dark_appearance_name()), }; if let Some(appearance) = appearance { Some(appearance) } else { warn!(?theme, "could not find appearance for theme"); // Assume system appearance in this case None } } winit-0.30.9/src/platform_impl/mod.rs000064400000000000000000000041531046102023000156330ustar 00000000000000use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoModeHandle as RootVideoModeHandle}; use crate::window::Fullscreen as RootFullscreen; #[cfg(android_platform)] mod android; #[cfg(ios_platform)] mod ios; #[cfg(any(x11_platform, wayland_platform))] mod linux; #[cfg(macos_platform)] mod macos; #[cfg(orbital_platform)] mod orbital; #[cfg(web_platform)] mod web; #[cfg(windows_platform)] mod windows; #[cfg(android_platform)] use android as platform; #[cfg(ios_platform)] use ios as platform; #[cfg(any(x11_platform, wayland_platform))] use linux as platform; #[cfg(macos_platform)] use macos as platform; #[cfg(orbital_platform)] use orbital as platform; #[cfg(web_platform)] use web as platform; #[cfg(windows_platform)] use windows as platform; pub use self::platform::*; /// Helper for converting between platform-specific and generic /// [`VideoModeHandle`]/[`MonitorHandle`] #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum Fullscreen { Exclusive(VideoModeHandle), Borderless(Option), } impl From for Fullscreen { fn from(f: RootFullscreen) -> Self { match f { RootFullscreen::Exclusive(mode) => Self::Exclusive(mode.video_mode), RootFullscreen::Borderless(Some(handle)) => Self::Borderless(Some(handle.inner)), RootFullscreen::Borderless(None) => Self::Borderless(None), } } } impl From for RootFullscreen { fn from(f: Fullscreen) -> Self { match f { Fullscreen::Exclusive(video_mode) => { Self::Exclusive(RootVideoModeHandle { video_mode }) }, Fullscreen::Borderless(Some(inner)) => { Self::Borderless(Some(RootMonitorHandle { inner })) }, Fullscreen::Borderless(None) => Self::Borderless(None), } } } #[cfg(all( not(ios_platform), not(windows_platform), not(macos_platform), not(android_platform), not(x11_platform), not(wayland_platform), not(web_platform), not(orbital_platform), ))] compile_error!("The platform you're compiling for is not supported by winit"); winit-0.30.9/src/platform_impl/orbital/event_loop.rs000064400000000000000000001011601046102023000206560ustar 00000000000000use std::cell::Cell; use std::collections::VecDeque; use std::marker::PhantomData; use std::sync::{mpsc, Arc, Mutex}; use std::time::Instant; use std::{mem, slice}; use bitflags::bitflags; use orbclient::{ ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent, MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent, }; use smol_str::SmolStr; use crate::error::EventLoopError; use crate::event::{self, Ime, Modifiers, StartCause}; use crate::event_loop::{self, ControlFlow, DeviceEvents}; use crate::keyboard::{ Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode, PhysicalKey, }; use crate::window::{ CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId, }; use super::{ DeviceId, KeyEventExtra, MonitorHandle, OsError, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket, WindowId, WindowProperties, }; fn convert_scancode(scancode: u8) -> (PhysicalKey, Option) { // Key constants from https://docs.rs/orbclient/latest/orbclient/event/index.html let (key_code, named_key_opt) = match scancode { orbclient::K_A => (KeyCode::KeyA, None), orbclient::K_B => (KeyCode::KeyB, None), orbclient::K_C => (KeyCode::KeyC, None), orbclient::K_D => (KeyCode::KeyD, None), orbclient::K_E => (KeyCode::KeyE, None), orbclient::K_F => (KeyCode::KeyF, None), orbclient::K_G => (KeyCode::KeyG, None), orbclient::K_H => (KeyCode::KeyH, None), orbclient::K_I => (KeyCode::KeyI, None), orbclient::K_J => (KeyCode::KeyJ, None), orbclient::K_K => (KeyCode::KeyK, None), orbclient::K_L => (KeyCode::KeyL, None), orbclient::K_M => (KeyCode::KeyM, None), orbclient::K_N => (KeyCode::KeyN, None), orbclient::K_O => (KeyCode::KeyO, None), orbclient::K_P => (KeyCode::KeyP, None), orbclient::K_Q => (KeyCode::KeyQ, None), orbclient::K_R => (KeyCode::KeyR, None), orbclient::K_S => (KeyCode::KeyS, None), orbclient::K_T => (KeyCode::KeyT, None), orbclient::K_U => (KeyCode::KeyU, None), orbclient::K_V => (KeyCode::KeyV, None), orbclient::K_W => (KeyCode::KeyW, None), orbclient::K_X => (KeyCode::KeyX, None), orbclient::K_Y => (KeyCode::KeyY, None), orbclient::K_Z => (KeyCode::KeyZ, None), orbclient::K_0 => (KeyCode::Digit0, None), orbclient::K_1 => (KeyCode::Digit1, None), orbclient::K_2 => (KeyCode::Digit2, None), orbclient::K_3 => (KeyCode::Digit3, None), orbclient::K_4 => (KeyCode::Digit4, None), orbclient::K_5 => (KeyCode::Digit5, None), orbclient::K_6 => (KeyCode::Digit6, None), orbclient::K_7 => (KeyCode::Digit7, None), orbclient::K_8 => (KeyCode::Digit8, None), orbclient::K_9 => (KeyCode::Digit9, None), orbclient::K_ALT => (KeyCode::AltLeft, Some(NamedKey::Alt)), orbclient::K_ALT_GR => (KeyCode::AltRight, Some(NamedKey::AltGraph)), orbclient::K_BACKSLASH => (KeyCode::Backslash, None), orbclient::K_BKSP => (KeyCode::Backspace, Some(NamedKey::Backspace)), orbclient::K_BRACE_CLOSE => (KeyCode::BracketRight, None), orbclient::K_BRACE_OPEN => (KeyCode::BracketLeft, None), orbclient::K_CAPS => (KeyCode::CapsLock, Some(NamedKey::CapsLock)), orbclient::K_COMMA => (KeyCode::Comma, None), orbclient::K_CTRL => (KeyCode::ControlLeft, Some(NamedKey::Control)), orbclient::K_DEL => (KeyCode::Delete, Some(NamedKey::Delete)), orbclient::K_DOWN => (KeyCode::ArrowDown, Some(NamedKey::ArrowDown)), orbclient::K_END => (KeyCode::End, Some(NamedKey::End)), orbclient::K_ENTER => (KeyCode::Enter, Some(NamedKey::Enter)), orbclient::K_EQUALS => (KeyCode::Equal, None), orbclient::K_ESC => (KeyCode::Escape, Some(NamedKey::Escape)), orbclient::K_F1 => (KeyCode::F1, Some(NamedKey::F1)), orbclient::K_F2 => (KeyCode::F2, Some(NamedKey::F2)), orbclient::K_F3 => (KeyCode::F3, Some(NamedKey::F3)), orbclient::K_F4 => (KeyCode::F4, Some(NamedKey::F4)), orbclient::K_F5 => (KeyCode::F5, Some(NamedKey::F5)), orbclient::K_F6 => (KeyCode::F6, Some(NamedKey::F6)), orbclient::K_F7 => (KeyCode::F7, Some(NamedKey::F7)), orbclient::K_F8 => (KeyCode::F8, Some(NamedKey::F8)), orbclient::K_F9 => (KeyCode::F9, Some(NamedKey::F9)), orbclient::K_F10 => (KeyCode::F10, Some(NamedKey::F10)), orbclient::K_F11 => (KeyCode::F11, Some(NamedKey::F11)), orbclient::K_F12 => (KeyCode::F12, Some(NamedKey::F12)), orbclient::K_HOME => (KeyCode::Home, Some(NamedKey::Home)), orbclient::K_LEFT => (KeyCode::ArrowLeft, Some(NamedKey::ArrowLeft)), orbclient::K_LEFT_SHIFT => (KeyCode::ShiftLeft, Some(NamedKey::Shift)), orbclient::K_MINUS => (KeyCode::Minus, None), orbclient::K_NUM_0 => (KeyCode::Numpad0, None), orbclient::K_NUM_1 => (KeyCode::Numpad1, None), orbclient::K_NUM_2 => (KeyCode::Numpad2, None), orbclient::K_NUM_3 => (KeyCode::Numpad3, None), orbclient::K_NUM_4 => (KeyCode::Numpad4, None), orbclient::K_NUM_5 => (KeyCode::Numpad5, None), orbclient::K_NUM_6 => (KeyCode::Numpad6, None), orbclient::K_NUM_7 => (KeyCode::Numpad7, None), orbclient::K_NUM_8 => (KeyCode::Numpad8, None), orbclient::K_NUM_9 => (KeyCode::Numpad9, None), orbclient::K_PERIOD => (KeyCode::Period, None), orbclient::K_PGDN => (KeyCode::PageDown, Some(NamedKey::PageDown)), orbclient::K_PGUP => (KeyCode::PageUp, Some(NamedKey::PageUp)), orbclient::K_QUOTE => (KeyCode::Quote, None), orbclient::K_RIGHT => (KeyCode::ArrowRight, Some(NamedKey::ArrowRight)), orbclient::K_RIGHT_SHIFT => (KeyCode::ShiftRight, Some(NamedKey::Shift)), orbclient::K_SEMICOLON => (KeyCode::Semicolon, None), orbclient::K_SLASH => (KeyCode::Slash, None), orbclient::K_SPACE => (KeyCode::Space, Some(NamedKey::Space)), orbclient::K_SUPER => (KeyCode::SuperLeft, Some(NamedKey::Super)), orbclient::K_TAB => (KeyCode::Tab, Some(NamedKey::Tab)), orbclient::K_TICK => (KeyCode::Backquote, None), orbclient::K_UP => (KeyCode::ArrowUp, Some(NamedKey::ArrowUp)), orbclient::K_VOLUME_DOWN => (KeyCode::AudioVolumeDown, Some(NamedKey::AudioVolumeDown)), orbclient::K_VOLUME_TOGGLE => (KeyCode::AudioVolumeMute, Some(NamedKey::AudioVolumeMute)), orbclient::K_VOLUME_UP => (KeyCode::AudioVolumeUp, Some(NamedKey::AudioVolumeUp)), _ => return (PhysicalKey::Unidentified(NativeKeyCode::Unidentified), None), }; (PhysicalKey::Code(key_code), named_key_opt) } fn element_state(pressed: bool) -> event::ElementState { if pressed { event::ElementState::Pressed } else { event::ElementState::Released } } bitflags! { #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] struct KeyboardModifierState: u8 { const LSHIFT = 1 << 0; const RSHIFT = 1 << 1; const LCTRL = 1 << 2; const RCTRL = 1 << 3; const LALT = 1 << 4; const RALT = 1 << 5; const LSUPER = 1 << 6; const RSUPER = 1 << 7; } } bitflags! { #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] struct MouseButtonState: u8 { const LEFT = 1 << 0; const MIDDLE = 1 << 1; const RIGHT = 1 << 2; } } #[derive(Default)] struct EventState { keyboard: KeyboardModifierState, mouse: MouseButtonState, resize_opt: Option<(u32, u32)>, } impl EventState { fn character_all_modifiers(&self, character: char) -> char { // Modify character if Ctrl is pressed #[allow(clippy::collapsible_if)] if self.keyboard.contains(KeyboardModifierState::LCTRL) || self.keyboard.contains(KeyboardModifierState::RCTRL) { if character.is_ascii_lowercase() { return ((character as u8 - b'a') + 1) as char; } // TODO: more control key variants? } // Return character as-is if no special handling required character } fn key(&mut self, key: PhysicalKey, pressed: bool) { let code = match key { PhysicalKey::Code(code) => code, _ => return, }; match code { KeyCode::ShiftLeft => self.keyboard.set(KeyboardModifierState::LSHIFT, pressed), KeyCode::ShiftRight => self.keyboard.set(KeyboardModifierState::RSHIFT, pressed), KeyCode::ControlLeft => self.keyboard.set(KeyboardModifierState::LCTRL, pressed), KeyCode::ControlRight => self.keyboard.set(KeyboardModifierState::RCTRL, pressed), KeyCode::AltLeft => self.keyboard.set(KeyboardModifierState::LALT, pressed), KeyCode::AltRight => self.keyboard.set(KeyboardModifierState::RALT, pressed), KeyCode::SuperLeft => self.keyboard.set(KeyboardModifierState::LSUPER, pressed), KeyCode::SuperRight => self.keyboard.set(KeyboardModifierState::RSUPER, pressed), _ => (), } } fn mouse( &mut self, left: bool, middle: bool, right: bool, ) -> Option<(event::MouseButton, event::ElementState)> { if self.mouse.contains(MouseButtonState::LEFT) != left { self.mouse.set(MouseButtonState::LEFT, left); return Some((event::MouseButton::Left, element_state(left))); } if self.mouse.contains(MouseButtonState::MIDDLE) != middle { self.mouse.set(MouseButtonState::MIDDLE, middle); return Some((event::MouseButton::Middle, element_state(middle))); } if self.mouse.contains(MouseButtonState::RIGHT) != right { self.mouse.set(MouseButtonState::RIGHT, right); return Some((event::MouseButton::Right, element_state(right))); } None } fn modifiers(&self) -> Modifiers { let mut state = ModifiersState::empty(); let mut pressed_mods = ModifiersKeys::empty(); if self.keyboard.intersects(KeyboardModifierState::LSHIFT | KeyboardModifierState::RSHIFT) { state |= ModifiersState::SHIFT; } pressed_mods .set(ModifiersKeys::LSHIFT, self.keyboard.contains(KeyboardModifierState::LSHIFT)); pressed_mods .set(ModifiersKeys::RSHIFT, self.keyboard.contains(KeyboardModifierState::RSHIFT)); if self.keyboard.intersects(KeyboardModifierState::LCTRL | KeyboardModifierState::RCTRL) { state |= ModifiersState::CONTROL; } pressed_mods .set(ModifiersKeys::LCONTROL, self.keyboard.contains(KeyboardModifierState::LCTRL)); pressed_mods .set(ModifiersKeys::RCONTROL, self.keyboard.contains(KeyboardModifierState::RCTRL)); if self.keyboard.intersects(KeyboardModifierState::LALT | KeyboardModifierState::RALT) { state |= ModifiersState::ALT; } pressed_mods.set(ModifiersKeys::LALT, self.keyboard.contains(KeyboardModifierState::LALT)); pressed_mods.set(ModifiersKeys::RALT, self.keyboard.contains(KeyboardModifierState::RALT)); if self.keyboard.intersects(KeyboardModifierState::LSUPER | KeyboardModifierState::RSUPER) { state |= ModifiersState::SUPER } pressed_mods .set(ModifiersKeys::LSUPER, self.keyboard.contains(KeyboardModifierState::LSUPER)); pressed_mods .set(ModifiersKeys::RSUPER, self.keyboard.contains(KeyboardModifierState::RSUPER)); Modifiers { state, pressed_mods } } } pub struct EventLoop { windows: Vec<(Arc, EventState)>, window_target: event_loop::ActiveEventLoop, user_events_sender: mpsc::Sender, user_events_receiver: mpsc::Receiver, } impl EventLoop { pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result { let (user_events_sender, user_events_receiver) = mpsc::channel(); let event_socket = Arc::new( RedoxSocket::event() .map_err(OsError::new) .map_err(|error| EventLoopError::Os(os_error!(error)))?, ); let wake_socket = Arc::new( TimeSocket::open() .map_err(OsError::new) .map_err(|error| EventLoopError::Os(os_error!(error)))?, ); event_socket .write(&syscall::Event { id: wake_socket.0.fd, flags: syscall::EventFlags::EVENT_READ, data: wake_socket.0.fd, }) .map_err(OsError::new) .map_err(|error| EventLoopError::Os(os_error!(error)))?; Ok(Self { windows: Vec::new(), window_target: event_loop::ActiveEventLoop { p: ActiveEventLoop { control_flow: Cell::new(ControlFlow::default()), exit: Cell::new(false), creates: Mutex::new(VecDeque::new()), redraws: Arc::new(Mutex::new(VecDeque::new())), destroys: Arc::new(Mutex::new(VecDeque::new())), event_socket, wake_socket, }, _marker: PhantomData, }, user_events_sender, user_events_receiver, }) } fn process_event( window_id: WindowId, event_option: EventOption, event_state: &mut EventState, mut event_handler: F, ) where F: FnMut(event::Event), { match event_option { EventOption::Key(KeyEvent { character, scancode, pressed }) => { // Convert scancode let (physical_key, named_key_opt) = convert_scancode(scancode); // Get previous modifiers and update modifiers based on physical key let modifiers_before = event_state.keyboard; event_state.key(physical_key, pressed); // Default to unidentified key with no text let mut logical_key = Key::Unidentified(NativeKey::Unidentified); let mut key_without_modifiers = logical_key.clone(); let mut text = None; let mut text_with_all_modifiers = None; // Set key and text based on character if character != '\0' { let mut tmp = [0u8; 4]; let character_str = character.encode_utf8(&mut tmp); // The key with Shift and Caps Lock applied (but not Ctrl) logical_key = Key::Character(character_str.into()); // The key without Shift or Caps Lock applied key_without_modifiers = Key::Character(SmolStr::from_iter(character.to_lowercase())); if pressed { // The key with Shift and Caps Lock applied (but not Ctrl) text = Some(character_str.into()); // The key with Shift, Caps Lock, and Ctrl applied let character_all_modifiers = event_state.character_all_modifiers(character); text_with_all_modifiers = Some(character_all_modifiers.encode_utf8(&mut tmp).into()) } }; // Override key if a named key was found (this is to allow Enter to replace '\n') if let Some(named_key) = named_key_opt { logical_key = Key::Named(named_key); key_without_modifiers = logical_key.clone(); } event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::KeyboardInput { device_id: event::DeviceId(DeviceId), event: event::KeyEvent { logical_key, physical_key, location: KeyLocation::Standard, state: element_state(pressed), repeat: false, text, platform_specific: KeyEventExtra { key_without_modifiers, text_with_all_modifiers, }, }, is_synthetic: false, }, }); // If the state of the modifiers has changed, send the event. if modifiers_before != event_state.keyboard { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::ModifiersChanged(event_state.modifiers()), }) } }, EventOption::TextInput(TextInputEvent { character }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Ime(Ime::Preedit("".into(), None)), }); event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Ime(Ime::Commit(character.into())), }); }, EventOption::Mouse(MouseEvent { x, y }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::CursorMoved { device_id: event::DeviceId(DeviceId), position: (x, y).into(), }, }); }, EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => { event_handler(event::Event::DeviceEvent { device_id: event::DeviceId(DeviceId), event: event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64) }, }); }, EventOption::Button(ButtonEvent { left, middle, right }) => { while let Some((button, state)) = event_state.mouse(left, middle, right) { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::MouseInput { device_id: event::DeviceId(DeviceId), state, button, }, }); } }, EventOption::Scroll(ScrollEvent { x, y }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::MouseWheel { device_id: event::DeviceId(DeviceId), delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32), phase: event::TouchPhase::Moved, }, }); }, EventOption::Quit(QuitEvent {}) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::CloseRequested, }); }, EventOption::Focus(FocusEvent { focused }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Focused(focused), }); }, EventOption::Move(MoveEvent { x, y }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Moved((x, y).into()), }); }, EventOption::Resize(ResizeEvent { width, height }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Resized((width, height).into()), }); // Acknowledge resize after event loop. event_state.resize_opt = Some((width, height)); }, // TODO: Screen, Clipboard, Drop EventOption::Hover(HoverEvent { entered }) => { if entered { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::CursorEntered { device_id: event::DeviceId(DeviceId), }, }); } else { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::CursorLeft { device_id: event::DeviceId(DeviceId), }, }); } }, other => { tracing::warn!("unhandled event: {:?}", other); }, } } pub fn run(mut self, mut event_handler_inner: F) -> Result<(), EventLoopError> where F: FnMut(event::Event, &event_loop::ActiveEventLoop), { let mut event_handler = move |event: event::Event, window_target: &event_loop::ActiveEventLoop| { event_handler_inner(event, window_target); }; let mut start_cause = StartCause::Init; loop { event_handler(event::Event::NewEvents(start_cause), &self.window_target); if start_cause == StartCause::Init { event_handler(event::Event::Resumed, &self.window_target); } // Handle window creates. while let Some(window) = { let mut creates = self.window_target.p.creates.lock().unwrap(); creates.pop_front() } { let window_id = WindowId { fd: window.fd as u64 }; let mut buf: [u8; 4096] = [0; 4096]; let path = window.fpath(&mut buf).expect("failed to read properties"); let properties = WindowProperties::new(path); self.windows.push((window, EventState::default())); // Send resize event on create to indicate first size. event_handler( event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Resized((properties.w, properties.h).into()), }, &self.window_target, ); // Send resize event on create to indicate first position. event_handler( event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Moved((properties.x, properties.y).into()), }, &self.window_target, ); } // Handle window destroys. while let Some(destroy_id) = { let mut destroys = self.window_target.p.destroys.lock().unwrap(); destroys.pop_front() } { event_handler( event::Event::WindowEvent { window_id: RootWindowId(destroy_id), event: event::WindowEvent::Destroyed, }, &self.window_target, ); self.windows.retain(|(window, _event_state)| window.fd as u64 != destroy_id.fd); } // Handle window events. let mut i = 0; // While loop is used here because the same window may be processed more than once. while let Some((window, event_state)) = self.windows.get_mut(i) { let window_id = WindowId { fd: window.fd as u64 }; let mut event_buf = [0u8; 16 * mem::size_of::()]; let count = syscall::read(window.fd, &mut event_buf).expect("failed to read window events"); // Safety: orbclient::Event is a packed struct designed to be transferred over a // socket. let events = unsafe { slice::from_raw_parts( event_buf.as_ptr() as *const orbclient::Event, count / mem::size_of::(), ) }; for orbital_event in events { Self::process_event( window_id, orbital_event.to_option(), event_state, |event| event_handler(event, &self.window_target), ); } if count == event_buf.len() { // If event buf was full, process same window again to ensure all events are // drained. continue; } // Acknowledge the latest resize event. if let Some((w, h)) = event_state.resize_opt.take() { window .write(format!("S,{w},{h}").as_bytes()) .expect("failed to acknowledge resize"); // Require redraw after resize. let mut redraws = self.window_target.p.redraws.lock().unwrap(); if !redraws.contains(&window_id) { redraws.push_back(window_id); } } // Move to next window. i += 1; } while let Ok(event) = self.user_events_receiver.try_recv() { event_handler(event::Event::UserEvent(event), &self.window_target); } // To avoid deadlocks the redraws lock is not held during event processing. while let Some(window_id) = { let mut redraws = self.window_target.p.redraws.lock().unwrap(); redraws.pop_front() } { event_handler( event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::RedrawRequested, }, &self.window_target, ); } event_handler(event::Event::AboutToWait, &self.window_target); if self.window_target.p.exiting() { break; } let requested_resume = match self.window_target.p.control_flow() { ControlFlow::Poll => { start_cause = StartCause::Poll; continue; }, ControlFlow::Wait => None, ControlFlow::WaitUntil(instant) => Some(instant), }; // Re-using wake socket caused extra wake events before because there were leftover // timeouts, and then new timeouts were added each time a spurious timeout expired. let timeout_socket = TimeSocket::open().unwrap(); self.window_target .p .event_socket .write(&syscall::Event { id: timeout_socket.0.fd, flags: syscall::EventFlags::EVENT_READ, data: 0, }) .unwrap(); let start = Instant::now(); if let Some(instant) = requested_resume { let mut time = timeout_socket.current_time().unwrap(); if let Some(duration) = instant.checked_duration_since(start) { time.tv_sec += duration.as_secs() as i64; time.tv_nsec += duration.subsec_nanos() as i32; // Normalize timespec so tv_nsec is not greater than one second. while time.tv_nsec >= 1_000_000_000 { time.tv_sec += 1; time.tv_nsec -= 1_000_000_000; } } timeout_socket.timeout(&time).unwrap(); } // Wait for event if needed. let mut event = syscall::Event::default(); self.window_target.p.event_socket.read(&mut event).unwrap(); // TODO: handle spurious wakeups (redraw caused wakeup but redraw already handled) match requested_resume { Some(requested_resume) if event.id == timeout_socket.0.fd => { // If the event is from the special timeout socket, report that resume // time was reached. start_cause = StartCause::ResumeTimeReached { start, requested_resume }; }, _ => { // Normal window event or spurious timeout. start_cause = StartCause::WaitCancelled { start, requested_resume }; }, } } event_handler(event::Event::LoopExiting, &self.window_target); Ok(()) } pub fn window_target(&self) -> &event_loop::ActiveEventLoop { &self.window_target } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { user_events_sender: self.user_events_sender.clone(), wake_socket: self.window_target.p.wake_socket.clone(), } } } pub struct EventLoopProxy { user_events_sender: mpsc::Sender, wake_socket: Arc, } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { self.user_events_sender .send(event) .map_err(|mpsc::SendError(x)| event_loop::EventLoopClosed(x))?; self.wake_socket.wake().unwrap(); Ok(()) } } impl Clone for EventLoopProxy { fn clone(&self) -> Self { Self { user_events_sender: self.user_events_sender.clone(), wake_socket: self.wake_socket.clone(), } } } impl Unpin for EventLoopProxy {} pub struct ActiveEventLoop { control_flow: Cell, exit: Cell, pub(super) creates: Mutex>>, pub(super) redraws: Arc>>, pub(super) destroys: Arc>>, pub(super) event_socket: Arc, pub(super) wake_socket: Arc, } impl ActiveEventLoop { pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor { let _ = source.inner; RootCustomCursor { inner: super::PlatformCustomCursor } } pub fn primary_monitor(&self) -> Option { Some(MonitorHandle) } pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); v.push_back(MonitorHandle); v } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Orbital(rwh_05::OrbitalDisplayHandle::empty()) } #[inline] pub fn system_theme(&self) -> Option { None } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Orbital(rwh_06::OrbitalDisplayHandle::new())) } pub fn set_control_flow(&self, control_flow: ControlFlow) { self.control_flow.set(control_flow) } pub fn control_flow(&self) -> ControlFlow { self.control_flow.get() } pub(crate) fn exit(&self) { self.exit.set(true); } pub(crate) fn exiting(&self) -> bool { self.exit.get() } pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle { OwnedDisplayHandle } } #[derive(Clone)] pub(crate) struct OwnedDisplayHandle; impl OwnedDisplayHandle { #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::OrbitalDisplayHandle::empty().into() } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::OrbitalDisplayHandle::new().into()) } } winit-0.30.9/src/platform_impl/orbital/mod.rs000064400000000000000000000147061046102023000172740ustar 00000000000000#![cfg(target_os = "redox")] use std::fmt::{self, Display, Formatter}; use std::str; use std::sync::Arc; use smol_str::SmolStr; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::keyboard::Key; pub(crate) use self::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle}; mod event_loop; pub use self::window::Window; mod window; struct RedoxSocket { fd: usize, } impl RedoxSocket { fn event() -> syscall::Result { Self::open_raw("event:") } fn orbital(properties: &WindowProperties<'_>) -> syscall::Result { Self::open_raw(&format!("{properties}")) } // Paths should be checked to ensure they are actually sockets and not normal files. If a // non-socket path is used, it could cause read and write to not function as expected. For // example, the seek would change in a potentially unpredictable way if either read or write // were called at the same time by multiple threads. fn open_raw(path: &str) -> syscall::Result { let fd = syscall::open(path, syscall::O_RDWR | syscall::O_CLOEXEC)?; Ok(Self { fd }) } fn read(&self, buf: &mut [u8]) -> syscall::Result<()> { let count = syscall::read(self.fd, buf)?; if count == buf.len() { Ok(()) } else { Err(syscall::Error::new(syscall::EINVAL)) } } fn write(&self, buf: &[u8]) -> syscall::Result<()> { let count = syscall::write(self.fd, buf)?; if count == buf.len() { Ok(()) } else { Err(syscall::Error::new(syscall::EINVAL)) } } fn fpath<'a>(&self, buf: &'a mut [u8]) -> syscall::Result<&'a str> { let count = syscall::fpath(self.fd, buf)?; str::from_utf8(&buf[..count]).map_err(|_err| syscall::Error::new(syscall::EINVAL)) } } impl Drop for RedoxSocket { fn drop(&mut self) { let _ = syscall::close(self.fd); } } pub struct TimeSocket(RedoxSocket); impl TimeSocket { fn open() -> syscall::Result { RedoxSocket::open_raw("time:4").map(Self) } // Read current time. fn current_time(&self) -> syscall::Result { let mut timespec = syscall::TimeSpec::default(); self.0.read(&mut timespec)?; Ok(timespec) } // Write a timeout. fn timeout(&self, timespec: &syscall::TimeSpec) -> syscall::Result<()> { self.0.write(timespec) } // Wake immediately. fn wake(&self) -> syscall::Result<()> { // Writing a default TimeSpec will always trigger a time event. self.timeout(&syscall::TimeSpec::default()) } } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WindowId { fd: u64, } impl WindowId { pub const fn dummy() -> Self { WindowId { fd: u64::MAX } } } impl From for u64 { fn from(id: WindowId) -> Self { id.fd } } impl From for WindowId { fn from(fd: u64) -> Self { Self { fd } } } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct DeviceId; impl DeviceId { pub const fn dummy() -> Self { DeviceId } } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PlatformSpecificWindowAttributes; struct WindowProperties<'a> { flags: &'a str, x: i32, y: i32, w: u32, h: u32, title: &'a str, } impl<'a> WindowProperties<'a> { fn new(path: &'a str) -> Self { // orbital:flags/x/y/w/h/t let mut parts = path.splitn(6, '/'); let flags = parts.next().unwrap_or(""); let x = parts.next().map_or(0, |part| part.parse::().unwrap_or(0)); let y = parts.next().map_or(0, |part| part.parse::().unwrap_or(0)); let w = parts.next().map_or(0, |part| part.parse::().unwrap_or(0)); let h = parts.next().map_or(0, |part| part.parse::().unwrap_or(0)); let title = parts.next().unwrap_or(""); Self { flags, x, y, w, h, title } } } impl fmt::Display for WindowProperties<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "orbital:{}/{}/{}/{}/{}/{}", self.flags, self.x, self.y, self.w, self.h, self.title ) } } #[derive(Clone, Debug)] pub struct OsError(Arc); impl OsError { fn new(error: syscall::Error) -> Self { Self(Arc::new(error)) } } impl Display for OsError { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { self.0.fmt(fmt) } } pub(crate) use crate::cursor::{ NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource, }; pub(crate) use crate::icon::NoIcon as PlatformIcon; #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct MonitorHandle; impl MonitorHandle { pub fn name(&self) -> Option { Some("Redox Device".to_owned()) } pub fn size(&self) -> PhysicalSize { PhysicalSize::new(0, 0) // TODO } pub fn position(&self) -> PhysicalPosition { (0, 0).into() } pub fn scale_factor(&self) -> f64 { 1.0 // TODO } pub fn refresh_rate_millihertz(&self) -> Option { // FIXME no way to get real refresh rate for now. None } pub fn video_modes(&self) -> impl Iterator { let size = self.size().into(); // FIXME this is not the real refresh rate // (it is guaranteed to support 32 bit color though) std::iter::once(VideoModeHandle { size, bit_depth: 32, refresh_rate_millihertz: 60000, monitor: self.clone(), }) } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct VideoModeHandle { size: (u32, u32), bit_depth: u16, refresh_rate_millihertz: u32, monitor: MonitorHandle, } impl VideoModeHandle { pub fn size(&self) -> PhysicalSize { self.size.into() } pub fn bit_depth(&self) -> u16 { self.bit_depth } pub fn refresh_rate_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } } #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct KeyEventExtra { pub key_without_modifiers: Key, pub text_with_all_modifiers: Option, } winit-0.30.9/src/platform_impl/orbital/window.rs000064400000000000000000000357451046102023000200320ustar 00000000000000use std::collections::VecDeque; use std::sync::{Arc, Mutex}; use crate::cursor::Cursor; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::platform_impl::Fullscreen; use crate::window::ImePurpose; use crate::{error, window}; use super::{ ActiveEventLoop, MonitorHandle, OsError, RedoxSocket, TimeSocket, WindowId, WindowProperties, }; // These values match the values uses in the `window_new` function in orbital: // https://gitlab.redox-os.org/redox-os/orbital/-/blob/master/src/scheme.rs const ORBITAL_FLAG_ASYNC: char = 'a'; const ORBITAL_FLAG_BACK: char = 'b'; const ORBITAL_FLAG_FRONT: char = 'f'; const ORBITAL_FLAG_HIDDEN: char = 'h'; const ORBITAL_FLAG_BORDERLESS: char = 'l'; const ORBITAL_FLAG_MAXIMIZED: char = 'm'; const ORBITAL_FLAG_RESIZABLE: char = 'r'; const ORBITAL_FLAG_TRANSPARENT: char = 't'; pub struct Window { window_socket: Arc, redraws: Arc>>, destroys: Arc>>, wake_socket: Arc, } impl Window { pub(crate) fn new( el: &ActiveEventLoop, attrs: window::WindowAttributes, ) -> Result { let scale = MonitorHandle.scale_factor(); let (x, y) = if let Some(pos) = attrs.position { pos.to_physical::(scale).into() } else { // These coordinates are a special value to center the window. (-1, -1) }; let (w, h): (u32, u32) = if let Some(size) = attrs.inner_size { size.to_physical::(scale).into() } else { (1024, 768) }; // TODO: min/max inner_size // Async by default. let mut flag_str = ORBITAL_FLAG_ASYNC.to_string(); if attrs.maximized { flag_str.push(ORBITAL_FLAG_MAXIMIZED); } if attrs.resizable { flag_str.push(ORBITAL_FLAG_RESIZABLE); } // TODO: fullscreen if attrs.transparent { flag_str.push(ORBITAL_FLAG_TRANSPARENT); } if !attrs.decorations { flag_str.push(ORBITAL_FLAG_BORDERLESS); } if !attrs.visible { flag_str.push(ORBITAL_FLAG_HIDDEN); } match attrs.window_level { window::WindowLevel::AlwaysOnBottom => { flag_str.push(ORBITAL_FLAG_BACK); }, window::WindowLevel::Normal => {}, window::WindowLevel::AlwaysOnTop => { flag_str.push(ORBITAL_FLAG_FRONT); }, } // TODO: window_icon // Open window. let window = RedoxSocket::orbital(&WindowProperties { flags: &flag_str, x, y, w, h, title: &attrs.title, }) .expect("failed to open window"); // Add to event socket. el.event_socket .write(&syscall::Event { id: window.fd, flags: syscall::EventFlags::EVENT_READ, data: window.fd, }) .unwrap(); let window_socket = Arc::new(window); // Notify event thread that this window was created, it will send some default events. { let mut creates = el.creates.lock().unwrap(); creates.push_back(window_socket.clone()); } el.wake_socket.wake().unwrap(); Ok(Self { window_socket, redraws: el.redraws.clone(), destroys: el.destroys.clone(), wake_socket: el.wake_socket.clone(), }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { f(self) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { f(self) } fn get_flag(&self, flag: char) -> Result { let mut buf: [u8; 4096] = [0; 4096]; let path = self .window_socket .fpath(&mut buf) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; let properties = WindowProperties::new(path); Ok(properties.flags.contains(flag)) } fn set_flag(&self, flag: char, value: bool) -> Result<(), error::ExternalError> { self.window_socket .write(format!("F,{flag},{}", if value { 1 } else { 0 }).as_bytes()) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; Ok(()) } #[inline] pub fn id(&self) -> WindowId { WindowId { fd: self.window_socket.fd as u64 } } #[inline] pub fn primary_monitor(&self) -> Option { Some(MonitorHandle) } #[inline] pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); v.push_back(MonitorHandle); v } #[inline] pub fn current_monitor(&self) -> Option { Some(MonitorHandle) } #[inline] pub fn scale_factor(&self) -> f64 { MonitorHandle.scale_factor() } #[inline] pub fn request_redraw(&self) { let window_id = self.id(); let mut redraws = self.redraws.lock().unwrap(); if !redraws.contains(&window_id) { redraws.push_back(window_id); self.wake_socket.wake().unwrap(); } } #[inline] pub fn pre_present_notify(&self) {} #[inline] pub fn reset_dead_keys(&self) { // TODO? } #[inline] pub fn inner_position(&self) -> Result, error::NotSupportedError> { let mut buf: [u8; 4096] = [0; 4096]; let path = self.window_socket.fpath(&mut buf).expect("failed to read properties"); let properties = WindowProperties::new(path); Ok((properties.x, properties.y).into()) } #[inline] pub fn outer_position(&self) -> Result, error::NotSupportedError> { // TODO: adjust for window decorations self.inner_position() } #[inline] pub fn set_outer_position(&self, position: Position) { // TODO: adjust for window decorations let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); self.window_socket.write(format!("P,{x},{y}").as_bytes()).expect("failed to set position"); } #[inline] pub fn inner_size(&self) -> PhysicalSize { let mut buf: [u8; 4096] = [0; 4096]; let path = self.window_socket.fpath(&mut buf).expect("failed to read properties"); let properties = WindowProperties::new(path); (properties.w, properties.h).into() } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let (w, h): (u32, u32) = size.to_physical::(self.scale_factor()).into(); self.window_socket.write(format!("S,{w},{h}").as_bytes()).expect("failed to set size"); None } #[inline] pub fn outer_size(&self) -> PhysicalSize { // TODO: adjust for window decorations self.inner_size() } #[inline] pub fn set_min_inner_size(&self, _: Option) {} #[inline] pub fn set_max_inner_size(&self, _: Option) {} #[inline] pub fn title(&self) -> String { let mut buf: [u8; 4096] = [0; 4096]; let path = self.window_socket.fpath(&mut buf).expect("failed to read properties"); let properties = WindowProperties::new(path); properties.title.to_string() } #[inline] pub fn set_title(&self, title: &str) { self.window_socket.write(format!("T,{title}").as_bytes()).expect("failed to set title"); } #[inline] pub fn set_transparent(&self, transparent: bool) { let _ = self.set_flag(ORBITAL_FLAG_TRANSPARENT, transparent); } #[inline] pub fn set_blur(&self, _blur: bool) {} #[inline] pub fn set_visible(&self, visible: bool) { let _ = self.set_flag(ORBITAL_FLAG_HIDDEN, !visible); } #[inline] pub fn is_visible(&self) -> Option { Some(!self.get_flag(ORBITAL_FLAG_HIDDEN).unwrap_or(false)) } #[inline] pub fn resize_increments(&self) -> Option> { None } #[inline] pub fn set_resize_increments(&self, _increments: Option) {} #[inline] pub fn set_resizable(&self, resizeable: bool) { let _ = self.set_flag(ORBITAL_FLAG_RESIZABLE, resizeable); } #[inline] pub fn is_resizable(&self) -> bool { self.get_flag(ORBITAL_FLAG_RESIZABLE).unwrap_or(false) } #[inline] pub fn set_minimized(&self, _minimized: bool) {} #[inline] pub fn is_minimized(&self) -> Option { None } #[inline] pub fn set_maximized(&self, maximized: bool) { let _ = self.set_flag(ORBITAL_FLAG_MAXIMIZED, maximized); } #[inline] pub fn is_maximized(&self) -> bool { self.get_flag(ORBITAL_FLAG_MAXIMIZED).unwrap_or(false) } #[inline] pub(crate) fn set_fullscreen(&self, _monitor: Option) {} #[inline] pub(crate) fn fullscreen(&self) -> Option { None } #[inline] pub fn set_decorations(&self, decorations: bool) { let _ = self.set_flag(ORBITAL_FLAG_BORDERLESS, !decorations); } #[inline] pub fn is_decorated(&self) -> bool { !self.get_flag(ORBITAL_FLAG_BORDERLESS).unwrap_or(false) } #[inline] pub fn set_window_level(&self, level: window::WindowLevel) { match level { window::WindowLevel::AlwaysOnBottom => { let _ = self.set_flag(ORBITAL_FLAG_BACK, true); }, window::WindowLevel::Normal => { let _ = self.set_flag(ORBITAL_FLAG_BACK, false); let _ = self.set_flag(ORBITAL_FLAG_FRONT, false); }, window::WindowLevel::AlwaysOnTop => { let _ = self.set_flag(ORBITAL_FLAG_FRONT, true); }, } } #[inline] pub fn set_window_icon(&self, _window_icon: Option) {} #[inline] pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {} #[inline] pub fn set_ime_allowed(&self, _allowed: bool) {} #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] pub fn focus_window(&self) {} #[inline] pub fn request_user_attention(&self, _request_type: Option) {} #[inline] pub fn set_cursor(&self, _: Cursor) {} #[inline] pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported(error::NotSupportedError::new())) } #[inline] pub fn set_cursor_grab( &self, mode: window::CursorGrabMode, ) -> Result<(), error::ExternalError> { let (grab, relative) = match mode { window::CursorGrabMode::None => (false, false), window::CursorGrabMode::Confined => (true, false), window::CursorGrabMode::Locked => (true, true), }; self.window_socket .write(format!("M,G,{}", if grab { 1 } else { 0 }).as_bytes()) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; self.window_socket .write(format!("M,R,{}", if relative { 1 } else { 0 }).as_bytes()) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; Ok(()) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { let _ = self.window_socket.write(format!("M,C,{}", if visible { 1 } else { 0 }).as_bytes()); } #[inline] pub fn drag_window(&self) -> Result<(), error::ExternalError> { self.window_socket .write(b"D") .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; Ok(()) } #[inline] pub fn drag_resize_window( &self, direction: window::ResizeDirection, ) -> Result<(), error::ExternalError> { let arg = match direction { window::ResizeDirection::East => "R", window::ResizeDirection::North => "T", window::ResizeDirection::NorthEast => "T,R", window::ResizeDirection::NorthWest => "T,L", window::ResizeDirection::South => "B", window::ResizeDirection::SouthEast => "B,R", window::ResizeDirection::SouthWest => "B,L", window::ResizeDirection::West => "L", }; self.window_socket .write(format!("D,{}", arg).as_bytes()) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; Ok(()) } #[inline] pub fn show_window_menu(&self, _position: Position) {} #[inline] pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported(error::NotSupportedError::new())) } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut handle = rwh_04::OrbitalHandle::empty(); handle.window = self.window_socket.fd as *mut _; rwh_04::RawWindowHandle::Orbital(handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut handle = rwh_05::OrbitalWindowHandle::empty(); handle.window = self.window_socket.fd as *mut _; rwh_05::RawWindowHandle::Orbital(handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Orbital(rwh_05::OrbitalDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { let handle = rwh_06::OrbitalWindowHandle::new({ let window = self.window_socket.fd as *mut _; std::ptr::NonNull::new(window).expect("orbital fd should never be null") }); Ok(rwh_06::RawWindowHandle::Orbital(handle)) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Orbital(rwh_06::OrbitalDisplayHandle::new())) } #[inline] pub fn set_enabled_buttons(&self, _buttons: window::WindowButtons) {} #[inline] pub fn enabled_buttons(&self) -> window::WindowButtons { window::WindowButtons::all() } #[inline] pub fn theme(&self) -> Option { None } #[inline] pub fn has_focus(&self) -> bool { false } #[inline] pub fn set_theme(&self, _theme: Option) {} pub fn set_content_protected(&self, _protected: bool) {} } impl Drop for Window { fn drop(&mut self) { { let mut destroys = self.destroys.lock().unwrap(); destroys.push_back(self.id()); } self.wake_socket.wake().unwrap(); } } winit-0.30.9/src/platform_impl/web/async/abortable.rs000064400000000000000000000040641046102023000207020ustar 00000000000000use std::error::Error; use std::fmt::{self, Display, Formatter}; use std::future::Future; use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::task::{Context, Poll}; use pin_project::pin_project; use super::AtomicWaker; #[pin_project] pub struct Abortable { #[pin] future: F, shared: Arc, } impl Abortable { pub fn new(handle: AbortHandle, future: F) -> Self { Self { future, shared: handle.0 } } } impl Future for Abortable { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.shared.aborted.load(Ordering::Relaxed) { return Poll::Ready(Err(Aborted)); } if let Poll::Ready(value) = self.as_mut().project().future.poll(cx) { return Poll::Ready(Ok(value)); } self.shared.waker.register(cx.waker()); if self.shared.aborted.load(Ordering::Relaxed) { return Poll::Ready(Err(Aborted)); } Poll::Pending } } #[derive(Debug)] struct Shared { waker: AtomicWaker, aborted: AtomicBool, } #[derive(Clone, Debug)] pub struct AbortHandle(Arc); impl AbortHandle { pub fn new() -> Self { Self(Arc::new(Shared { waker: AtomicWaker::new(), aborted: AtomicBool::new(false) })) } pub fn abort(&self) { self.0.aborted.store(true, Ordering::Relaxed); self.0.waker.wake() } } #[derive(Debug)] pub struct DropAbortHandle(AbortHandle); impl DropAbortHandle { pub fn new(handle: AbortHandle) -> Self { Self(handle) } pub fn handle(&self) -> AbortHandle { self.0.clone() } } impl Drop for DropAbortHandle { fn drop(&mut self) { self.0.abort() } } #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub struct Aborted; impl Display for Aborted { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "`Abortable` future has been aborted") } } impl Error for Aborted {} winit-0.30.9/src/platform_impl/web/async/atomic_waker.rs000064400000000000000000000015161046102023000214130ustar 00000000000000use std::cell::RefCell; use std::ops::Deref; use std::task::Waker; #[derive(Debug)] pub struct AtomicWaker(RefCell>); impl AtomicWaker { pub const fn new() -> Self { Self(RefCell::new(None)) } pub fn register(&self, waker: &Waker) { let mut this = self.0.borrow_mut(); if let Some(old_waker) = this.deref() { if old_waker.will_wake(waker) { return; } } *this = Some(waker.clone()); } pub fn wake(&self) { if let Some(waker) = self.0.borrow_mut().take() { waker.wake(); } } } // SAFETY: Wasm without the `atomics` target feature is single-threaded. unsafe impl Send for AtomicWaker {} // SAFETY: Wasm without the `atomics` target feature is single-threaded. unsafe impl Sync for AtomicWaker {} winit-0.30.9/src/platform_impl/web/async/channel.rs000064400000000000000000000062001046102023000203510ustar 00000000000000use std::future; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{self, RecvError, SendError, TryRecvError}; use std::sync::{Arc, Mutex}; use std::task::Poll; use super::AtomicWaker; pub fn channel() -> (Sender, Receiver) { let (sender, receiver) = mpsc::channel(); let shared = Arc::new(Shared { closed: AtomicBool::new(false), waker: AtomicWaker::new() }); let sender = Sender(Arc::new(SenderInner { sender: Mutex::new(sender), shared: Arc::clone(&shared) })); let receiver = Receiver { receiver: Rc::new(receiver), shared }; (sender, receiver) } pub struct Sender(Arc>); struct SenderInner { // We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't // be accessed on the main thread, as it could block. Additionally we need // to wrap `Sender` in an `Arc` to make it cloneable on the main thread without // having to block. sender: Mutex>, shared: Arc, } impl Sender { pub fn send(&self, event: T) -> Result<(), SendError> { self.0.sender.lock().unwrap().send(event)?; self.0.shared.waker.wake(); Ok(()) } } impl SenderInner { fn close(&self) { self.shared.closed.store(true, Ordering::Relaxed); self.shared.waker.wake(); } } impl Clone for Sender { fn clone(&self) -> Self { Self(Arc::clone(&self.0)) } } impl Drop for SenderInner { fn drop(&mut self) { self.close(); } } pub struct Receiver { receiver: Rc>, shared: Arc, } impl Receiver { pub async fn next(&self) -> Result { future::poll_fn(|cx| match self.receiver.try_recv() { Ok(event) => Poll::Ready(Ok(event)), Err(TryRecvError::Empty) => { self.shared.waker.register(cx.waker()); match self.receiver.try_recv() { Ok(event) => Poll::Ready(Ok(event)), Err(TryRecvError::Empty) => { if self.shared.closed.load(Ordering::Relaxed) { Poll::Ready(Err(RecvError)) } else { Poll::Pending } }, Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)), } }, Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)), }) .await } pub fn try_recv(&self) -> Result, RecvError> { match self.receiver.try_recv() { Ok(value) => Ok(Some(value)), Err(TryRecvError::Empty) => Ok(None), Err(TryRecvError::Disconnected) => Err(RecvError), } } } impl Clone for Receiver { fn clone(&self) -> Self { Self { receiver: Rc::clone(&self.receiver), shared: Arc::clone(&self.shared) } } } impl Drop for Receiver { fn drop(&mut self) { self.shared.closed.store(true, Ordering::Relaxed); } } struct Shared { closed: AtomicBool, waker: AtomicWaker, } winit-0.30.9/src/platform_impl/web/async/concurrent_queue.rs000064400000000000000000000022561046102023000223360ustar 00000000000000use std::cell::{Cell, RefCell}; #[derive(Debug)] pub struct ConcurrentQueue { queue: RefCell>, closed: Cell, } pub enum PushError { #[allow(dead_code)] Full(T), Closed(T), } pub enum PopError { Empty, Closed, } impl ConcurrentQueue { pub fn unbounded() -> Self { Self { queue: RefCell::new(Vec::new()), closed: Cell::new(false) } } pub fn push(&self, value: T) -> Result<(), PushError> { if self.closed.get() { return Err(PushError::Closed(value)); } self.queue.borrow_mut().push(value); Ok(()) } pub fn pop(&self) -> Result { self.queue.borrow_mut().pop().ok_or_else(|| { if self.closed.get() { PopError::Closed } else { PopError::Empty } }) } pub fn close(&self) -> bool { !self.closed.replace(true) } } // SAFETY: Wasm without the `atomics` target feature is single-threaded. unsafe impl Send for ConcurrentQueue {} // SAFETY: Wasm without the `atomics` target feature is single-threaded. unsafe impl Sync for ConcurrentQueue {} winit-0.30.9/src/platform_impl/web/async/dispatcher.rs000064400000000000000000000073641046102023000211030ustar 00000000000000use super::super::main_thread::MainThreadMarker; use super::{channel, Receiver, Sender, Wrapper}; use std::cell::Ref; use std::sync::{Arc, Condvar, Mutex}; pub struct Dispatcher(Wrapper>, Closure>); struct Closure(Box); impl Dispatcher { #[track_caller] pub fn new(main_thread: MainThreadMarker, value: T) -> Option<(Self, DispatchRunner)> { let (sender, receiver) = channel::>(); Wrapper::new( main_thread, value, |value, Closure(closure)| { // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do // anything funny with it here. See `Self::queue()`. closure(value.borrow().as_ref().unwrap()) }, { let receiver = receiver.clone(); move |value| async move { while let Ok(Closure(closure)) = receiver.next().await { // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't // do anything funny with it here. See // `Self::queue()`. closure(value.borrow().as_ref().unwrap()) } } }, sender, |sender, closure| { // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do // anything funny with it here. See `Self::queue()`. sender.send(closure).unwrap() }, ) .map(|wrapper| (Self(wrapper.clone()), DispatchRunner { wrapper, receiver })) } pub fn value(&self) -> Option> { self.0.value() } pub fn dispatch(&self, f: impl 'static + FnOnce(&T) + Send) { if let Some(value) = self.0.value() { f(&value) } else { self.0.send(Closure(Box::new(f))) } } pub fn queue(&self, f: impl FnOnce(&T) -> R + Send) -> R { if let Some(value) = self.0.value() { f(&value) } else { let pair = Arc::new((Mutex::new(None), Condvar::new())); let closure = Box::new({ let pair = pair.clone(); move |value: &T| { *pair.0.lock().unwrap() = Some(f(value)); pair.1.notify_one(); } }) as Box; // SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is // safe because this function won't return until `f` has finished executing. See // `Self::new()`. let closure = Closure(unsafe { std::mem::transmute::< Box, Box, >(closure) }); self.0.send(closure); let mut started = pair.0.lock().unwrap(); while started.is_none() { started = pair.1.wait(started).unwrap(); } started.take().unwrap() } } } pub struct DispatchRunner { wrapper: Wrapper>, Closure>, receiver: Receiver>, } impl DispatchRunner { pub fn run(&self) { while let Some(Closure(closure)) = self.receiver.try_recv().expect("should only be closed when `Dispatcher` is dropped") { // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything // funny with it here. See `Self::queue()`. closure(&self.wrapper.value().expect("don't call this outside the main thread")) } } } winit-0.30.9/src/platform_impl/web/async/mod.rs000064400000000000000000000011041046102023000175160ustar 00000000000000mod abortable; #[cfg(not(target_feature = "atomics"))] mod atomic_waker; mod channel; #[cfg(not(target_feature = "atomics"))] mod concurrent_queue; mod dispatcher; mod notifier; mod waker; mod wrapper; pub use self::abortable::{AbortHandle, Abortable, DropAbortHandle}; pub use self::channel::{channel, Receiver, Sender}; pub use self::dispatcher::{DispatchRunner, Dispatcher}; pub use self::notifier::{Notified, Notifier}; pub use self::waker::{Waker, WakerSpawner}; use self::wrapper::Wrapper; use atomic_waker::AtomicWaker; use concurrent_queue::{ConcurrentQueue, PushError}; winit-0.30.9/src/platform_impl/web/async/notifier.rs000064400000000000000000000036631046102023000205720ustar 00000000000000use std::future::Future; use std::pin::Pin; use std::sync::{Arc, OnceLock}; use std::task::{Context, Poll, Waker}; use super::{ConcurrentQueue, PushError}; #[derive(Debug)] pub struct Notifier(Arc>); impl Notifier { pub fn new() -> Self { Self(Arc::new(Inner { queue: ConcurrentQueue::unbounded(), value: OnceLock::new() })) } pub fn notify(self, value: T) { if self.0.value.set(value).is_err() { unreachable!("value set before") } self.0.queue.close(); while let Ok(waker) = self.0.queue.pop() { waker.wake() } } pub fn notified(&self) -> Notified { Notified(Some(Arc::clone(&self.0))) } } #[derive(Clone, Debug)] pub struct Notified(Option>>); impl Future for Notified { type Output = T; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.0.take().expect("`Receiver` polled after completion"); if this.value.get().is_none() { match this.queue.push(cx.waker().clone()) { Ok(()) => { if this.value.get().is_none() { self.0 = Some(this); return Poll::Pending; } }, Err(PushError::Closed(_)) => (), Err(PushError::Full(_)) => { unreachable!("found full queue despite using unbounded queue") }, } } let (Ok(Some(value)) | Err(Some(value))) = Arc::try_unwrap(this) .map(|mut inner| inner.value.take()) .map_err(|this| this.value.get().cloned()) else { unreachable!("found no value despite being ready") }; Poll::Ready(value) } } #[derive(Debug)] struct Inner { queue: ConcurrentQueue, value: OnceLock, } winit-0.30.9/src/platform_impl/web/async/waker.rs000064400000000000000000000073101046102023000200550ustar 00000000000000use std::future; use std::num::NonZeroUsize; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; use std::task::Poll; use super::super::main_thread::MainThreadMarker; use super::{AtomicWaker, Wrapper}; pub struct WakerSpawner(Wrapper, Sender, NonZeroUsize>); pub struct Waker(Wrapper, Sender, NonZeroUsize>); struct Handler { value: T, handler: fn(&T, NonZeroUsize, bool), } #[derive(Clone)] struct Sender(Arc); impl WakerSpawner { #[track_caller] pub fn new( main_thread: MainThreadMarker, value: T, handler: fn(&T, NonZeroUsize, bool), ) -> Option { let inner = Arc::new(Inner { counter: AtomicUsize::new(0), waker: AtomicWaker::new(), closed: AtomicBool::new(false), }); let handler = Handler { value, handler }; let sender = Sender(Arc::clone(&inner)); let wrapper = Wrapper::new( main_thread, handler, |handler, count| { let handler = handler.borrow(); let handler = handler.as_ref().unwrap(); (handler.handler)(&handler.value, count, true); }, { let inner = Arc::clone(&inner); move |handler| async move { while let Some(count) = future::poll_fn(|cx| { let count = inner.counter.swap(0, Ordering::Relaxed); match NonZeroUsize::new(count) { Some(count) => Poll::Ready(Some(count)), None => { inner.waker.register(cx.waker()); let count = inner.counter.swap(0, Ordering::Relaxed); match NonZeroUsize::new(count) { Some(count) => Poll::Ready(Some(count)), None => { if inner.closed.load(Ordering::Relaxed) { return Poll::Ready(None); } Poll::Pending }, } }, } }) .await { let handler = handler.borrow(); let handler = handler.as_ref().unwrap(); (handler.handler)(&handler.value, count, false); } } }, sender, |inner, _| { inner.0.counter.fetch_add(1, Ordering::Relaxed); inner.0.waker.wake(); }, )?; Some(Self(wrapper)) } pub fn waker(&self) -> Waker { Waker(self.0.clone()) } pub fn fetch(&self) -> usize { debug_assert!( MainThreadMarker::new().is_some(), "this should only be called from the main thread" ); self.0.with_sender_data(|inner| inner.0.counter.swap(0, Ordering::Relaxed)) } } impl Drop for WakerSpawner { fn drop(&mut self) { self.0.with_sender_data(|inner| { inner.0.closed.store(true, Ordering::Relaxed); inner.0.waker.wake(); }); } } impl Waker { pub fn wake(&self) { self.0.send(NonZeroUsize::MIN) } } impl Clone for Waker { fn clone(&self) -> Self { Self(self.0.clone()) } } struct Inner { counter: AtomicUsize, waker: AtomicWaker, closed: AtomicBool, } winit-0.30.9/src/platform_impl/web/async/wrapper.rs000064400000000000000000000054321046102023000204270ustar 00000000000000use super::super::main_thread::MainThreadMarker; use std::cell::{Ref, RefCell}; use std::future::Future; use std::marker::PhantomData; use std::sync::Arc; // Unsafe wrapper type that allows us to use `T` when it's not `Send` from other threads. // `value` **must** only be accessed on the main thread. pub struct Wrapper { value: Value, handler: fn(&RefCell>, E), sender_data: S, sender_handler: fn(&S, E), } struct Value { // SAFETY: // This value must not be accessed if not on the main thread. // // - We wrap this in an `Arc` to allow it to be safely cloned without accessing the value. // - The `RefCell` lets us mutably access in the main thread but is safe to drop in any thread // because it has no `Drop` behavior. // - The `Option` lets us safely drop `T` only in the main thread. value: Arc>>, // Prevent's `Send` or `Sync` to be automatically implemented. local: PhantomData<*const ()>, } // SAFETY: See `Self::value`. unsafe impl Send for Value {} // SAFETY: See `Self::value`. unsafe impl Sync for Value {} impl Wrapper { #[track_caller] pub fn new>( _: MainThreadMarker, value: V, handler: fn(&RefCell>, E), receiver: impl 'static + FnOnce(Arc>>) -> R, sender_data: S, sender_handler: fn(&S, E), ) -> Option { let value = Arc::new(RefCell::new(Some(value))); wasm_bindgen_futures::spawn_local({ let value = Arc::clone(&value); async move { receiver(Arc::clone(&value)).await; drop(value.borrow_mut().take().unwrap()); } }); Some(Self { value: Value { value, local: PhantomData }, handler, sender_data, sender_handler, }) } pub fn send(&self, event: E) { if MainThreadMarker::new().is_some() { (self.handler)(&self.value.value, event) } else { (self.sender_handler)(&self.sender_data, event) } } pub fn value(&self) -> Option> { MainThreadMarker::new() .map(|_| Ref::map(self.value.value.borrow(), |value| value.as_ref().unwrap())) } pub fn with_sender_data(&self, f: impl FnOnce(&S) -> T) -> T { f(&self.sender_data) } } impl Clone for Wrapper { fn clone(&self) -> Self { Self { value: Value { value: self.value.value.clone(), local: PhantomData }, handler: self.handler, sender_data: self.sender_data.clone(), sender_handler: self.sender_handler, } } } winit-0.30.9/src/platform_impl/web/cursor.rs000064400000000000000000000575561046102023000171650ustar 00000000000000use std::cell::RefCell; use std::future::{self, Future}; use std::hash::{Hash, Hasher}; use std::mem; use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; use std::task::{ready, Context, Poll, Waker}; use std::time::Duration; use cursor_icon::CursorIcon; use js_sys::{Array, Object}; use wasm_bindgen::closure::Closure; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; use web_sys::{ Blob, Document, DomException, HtmlCanvasElement, HtmlImageElement, ImageBitmap, ImageBitmapOptions, ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window, }; use super::backend::Style; use super::main_thread::{MainThreadMarker, MainThreadSafe}; use super::r#async::{AbortHandle, Abortable, DropAbortHandle, Notified, Notifier}; use super::ActiveEventLoop; use crate::cursor::{BadImage, Cursor, CursorImage, CustomCursor as RootCustomCursor}; use crate::platform::web::CustomCursorError; #[derive(Debug)] pub(crate) enum CustomCursorSource { Image(CursorImage), Url { url: String, hotspot_x: u16, hotspot_y: u16 }, Animation { duration: Duration, cursors: Vec }, } impl CustomCursorSource { pub fn from_rgba( rgba: Vec, width: u16, height: u16, hotspot_x: u16, hotspot_y: u16, ) -> Result { Ok(CustomCursorSource::Image(CursorImage::from_rgba( rgba, width, height, hotspot_x, hotspot_y, )?)) } } #[derive(Clone, Debug)] pub struct CustomCursor { pub(crate) animation: bool, state: Arc>>, } impl Hash for CustomCursor { fn hash(&self, state: &mut H) { Arc::as_ptr(&self.state).hash(state); } } impl PartialEq for CustomCursor { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.state, &other.state) } } impl Eq for CustomCursor {} impl CustomCursor { pub(crate) fn new(event_loop: &ActiveEventLoop, source: CustomCursorSource) -> Self { match source { CustomCursorSource::Image(image) => Self::build_spawn( event_loop, from_rgba(event_loop.runner.window(), event_loop.runner.document().clone(), &image), false, ), CustomCursorSource::Url { url, hotspot_x, hotspot_y } => Self::build_spawn( event_loop, from_url(UrlType::Plain(url), hotspot_x, hotspot_y), false, ), CustomCursorSource::Animation { duration, cursors } => Self::build_spawn( event_loop, from_animation( event_loop.runner.main_thread(), duration, cursors.into_iter().map(|cursor| cursor.inner), ), true, ), } } fn build_spawn(window_target: &ActiveEventLoop, task: F, animation: bool) -> CustomCursor where F: 'static + Future>, S: Into, { let handle = AbortHandle::new(); let this = CustomCursor { animation, state: Arc::new(MainThreadSafe::new( window_target.runner.main_thread(), RefCell::new(ImageState::Loading { notifier: Notifier::new(), _handle: DropAbortHandle::new(handle.clone()), }), )), }; let weak = Arc::downgrade(&this.state); let main_thread = window_target.runner.main_thread(); let task = Abortable::new(handle, { async move { let result = task.await; let this = weak.upgrade().expect("`CursorHandler` invalidated without aborting"); let mut this = this.get(main_thread).borrow_mut(); match result { Ok(new_state) => { let ImageState::Loading { notifier, .. } = mem::replace(this.deref_mut(), new_state.into()) else { unreachable!("found invalid state"); }; notifier.notify(Ok(())); }, Err(error) => { let ImageState::Loading { notifier, .. } = mem::replace(this.deref_mut(), ImageState::Failed(error.clone())) else { unreachable!("found invalid state"); }; notifier.notify(Err(error)); }, } } }); wasm_bindgen_futures::spawn_local(async move { let _ = task.await; }); this } pub(crate) fn new_async( event_loop: &ActiveEventLoop, source: CustomCursorSource, ) -> CustomCursorFuture { let CustomCursor { animation, state } = Self::new(event_loop, source); let binding = state.get(event_loop.runner.main_thread()).borrow(); let ImageState::Loading { notifier, .. } = binding.deref() else { unreachable!("found invalid state") }; let notified = notifier.notified(); drop(binding); CustomCursorFuture { notified, animation, state: Some(state) } } } #[derive(Debug)] pub struct CustomCursorFuture { notified: Notified>, animation: bool, state: Option>>>, } impl Future for CustomCursorFuture { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if self.state.is_none() { panic!("`CustomCursorFuture` polled after completion") } let result = ready!(Pin::new(&mut self.notified).poll(cx)); let state = self.state.take().expect("`CustomCursorFuture` polled after completion"); Poll::Ready(result.map(|_| CustomCursor { animation: self.animation, state })) } } #[derive(Debug)] pub struct CursorHandler(Rc>); #[derive(Debug)] struct Inner { main_thread: MainThreadMarker, canvas: HtmlCanvasElement, style: Style, visible: bool, cursor: SelectedCursor, } impl CursorHandler { pub(crate) fn new( main_thread: MainThreadMarker, canvas: HtmlCanvasElement, style: Style, ) -> Self { Self(Rc::new(RefCell::new(Inner { main_thread, canvas, style, visible: true, cursor: SelectedCursor::default(), }))) } pub fn set_cursor(&self, cursor: Cursor) { let mut this = self.0.borrow_mut(); match cursor { Cursor::Icon(icon) => { if let SelectedCursor::Icon(old_icon) | SelectedCursor::Loading { previous: Previous::Icon(old_icon), .. } = &this.cursor { if *old_icon == icon { return; } } this.cursor = SelectedCursor::Icon(icon); this.set_style(); }, Cursor::Custom(cursor) => { let cursor = cursor.inner; if let SelectedCursor::Loading { cursor: old_cursor, .. } | SelectedCursor::Image(old_cursor) | SelectedCursor::Animation { cursor: old_cursor, .. } = &this.cursor { if *old_cursor == cursor { return; } } let state = cursor.state.get(this.main_thread).borrow(); match state.deref() { ImageState::Loading { notifier, .. } => { let notified = notifier.notified(); let handle = DropAbortHandle::new(AbortHandle::new()); let task = Abortable::new(handle.handle(), { let weak = Rc::downgrade(&self.0); async move { let _ = notified.await; let handler = weak .upgrade() .expect("`CursorHandler` invalidated without aborting"); handler.borrow_mut().notify(); } }); wasm_bindgen_futures::spawn_local(async move { let _ = task.await; }); drop(state); this.cursor = SelectedCursor::Loading { cursor, previous: mem::take(&mut this.cursor).into(), _handle: handle, }; }, ImageState::Failed(error) => { tracing::error!( "trying to load custom cursor that has failed to load: {error}" ) }, ImageState::Image(_) => { drop(state); this.cursor = SelectedCursor::Image(cursor); this.set_style(); }, ImageState::Animation(animation) => { let canvas: &CanvasAnimateExt = this.canvas.unchecked_ref(); let animation = canvas.animate_with_keyframe_animation_options( Some(&animation.keyframes), &animation.options, ); drop(state); if !this.visible { animation.cancel(); } this.cursor = SelectedCursor::Animation { animation: AnimationDropper(animation), cursor, }; this.set_style(); }, }; }, } } pub fn set_cursor_visible(&self, visible: bool) { let mut this = self.0.borrow_mut(); if !visible && this.visible { this.visible = false; this.style.set("cursor", "none"); if let SelectedCursor::Animation { animation, .. } = &this.cursor { animation.0.cancel(); } } else if visible && !this.visible { this.visible = true; this.set_style(); } } } impl Inner { fn set_style(&self) { if self.visible { match &self.cursor { SelectedCursor::Icon(icon) | SelectedCursor::Loading { previous: Previous::Icon(icon), .. } => { if let CursorIcon::Default = icon { self.style.remove("cursor") } else { self.style.set("cursor", icon.name()) } }, SelectedCursor::Loading { previous: Previous::Image(cursor), .. } | SelectedCursor::Image(cursor) => { match cursor.state.get(self.main_thread).borrow().deref() { ImageState::Image(Image { style, .. }) => self.style.set("cursor", style), _ => unreachable!("found invalid saved state"), } }, SelectedCursor::Loading { previous: Previous::Animation { animation, .. }, .. } | SelectedCursor::Animation { animation, .. } => { self.style.remove("cursor"); animation.0.play() }, } } } fn notify(&mut self) { let SelectedCursor::Loading { cursor, previous, .. } = mem::take(&mut self.cursor) else { unreachable!("found wrong state") }; let state = cursor.state.get(self.main_thread).borrow(); match state.deref() { ImageState::Image(_) => { drop(state); self.cursor = SelectedCursor::Image(cursor); self.set_style(); }, ImageState::Animation(animation) => { let canvas: &CanvasAnimateExt = self.canvas.unchecked_ref(); let animation = canvas.animate_with_keyframe_animation_options( Some(&animation.keyframes), &animation.options, ); drop(state); if !self.visible { animation.cancel(); } self.cursor = SelectedCursor::Animation { animation: AnimationDropper(animation), cursor }; self.set_style(); }, ImageState::Failed(error) => { tracing::error!("custom cursor failed to load: {error}"); self.cursor = previous.into() }, ImageState::Loading { .. } => unreachable!("notified without being ready"), } } } #[derive(Debug)] enum SelectedCursor { Icon(CursorIcon), Loading { cursor: CustomCursor, previous: Previous, _handle: DropAbortHandle }, Image(CustomCursor), Animation { cursor: CustomCursor, animation: AnimationDropper }, } impl Default for SelectedCursor { fn default() -> Self { Self::Icon(Default::default()) } } impl From for SelectedCursor { fn from(previous: Previous) -> Self { match previous { Previous::Icon(icon) => Self::Icon(icon), Previous::Image(cursor) => Self::Image(cursor), Previous::Animation { cursor, animation } => Self::Animation { cursor, animation }, } } } #[derive(Debug)] enum Previous { Icon(CursorIcon), Image(CustomCursor), Animation { cursor: CustomCursor, animation: AnimationDropper }, } impl From for Previous { fn from(value: SelectedCursor) -> Self { match value { SelectedCursor::Icon(icon) => Self::Icon(icon), SelectedCursor::Loading { previous, .. } => previous, SelectedCursor::Image(image) => Self::Image(image), SelectedCursor::Animation { cursor, animation } => { Self::Animation { cursor, animation } }, } } } #[derive(Debug)] enum ImageState { Loading { notifier: Notifier>, _handle: DropAbortHandle }, Failed(CustomCursorError), Image(Image), Animation(Animation), } #[derive(Debug)] struct Image { style: String, _object_url: Option, _image: HtmlImageElement, } impl From for ImageState { fn from(image: Image) -> Self { Self::Image(image) } } #[derive(Debug)] struct Animation { keyframes: Array, options: KeyframeAnimationOptions, _images: Vec, } impl From for ImageState { fn from(animation: Animation) -> Self { Self::Animation(animation) } } #[derive(Debug)] enum UrlType { Plain(String), Object(ObjectUrl), } impl UrlType { fn url(&self) -> &str { match &self { UrlType::Plain(url) => url, UrlType::Object(object_url) => &object_url.0, } } } #[derive(Debug)] struct ObjectUrl(String); impl Drop for ObjectUrl { fn drop(&mut self) { Url::revoke_object_url(&self.0).expect("unexpected exception in `URL.revokeObjectURL()`"); } } #[derive(Debug)] struct AnimationDropper(WebAnimation); impl Drop for AnimationDropper { fn drop(&mut self) { self.0.cancel() } } fn from_rgba( window: &Window, document: Document, image: &CursorImage, ) -> impl Future> { // 1. Create an `ImageData` from the RGBA data. // 2. Create an `ImageBitmap` from the `ImageData`. // 3. Draw `ImageBitmap` on an `HTMLCanvasElement`. // 4. Create a `Blob` from the `HTMLCanvasElement`. // 5. Create an object URL from the `Blob`. // 6. Decode the image on an `HTMLImageElement` from the URL. // 1. Create an `ImageData` from the RGBA data. // Adapted from https://github.com/rust-windowing/softbuffer/blob/ab7688e2ed2e2eca51b3c4e1863a5bd7fe85800e/src/web.rs#L196-L223 #[cfg(target_feature = "atomics")] // Can't share `SharedArrayBuffer` with `ImageData`. let result = { use js_sys::{Uint8Array, Uint8ClampedArray}; use wasm_bindgen::JsValue; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = ImageData)] type ImageDataExt; #[wasm_bindgen(catch, constructor, js_class = ImageData)] fn new(array: Uint8ClampedArray, sw: u32) -> Result; } let array = Uint8Array::new_with_length(image.rgba.len() as u32); array.copy_from(&image.rgba); let array = Uint8ClampedArray::new(&array); ImageDataExt::new(array, image.width as u32) .map(JsValue::from) .map(ImageData::unchecked_from_js) }; #[cfg(not(target_feature = "atomics"))] let result = ImageData::new_with_u8_clamped_array( wasm_bindgen::Clamped(&image.rgba), image.width as u32, ); let image_data = result.expect("found wrong image size"); // 2. Create an `ImageBitmap` from the `ImageData`. // // We call `createImageBitmap()` before spawning the future, // to not have to clone the image buffer. let options = ImageBitmapOptions::new(); options.set_premultiply_alpha(PremultiplyAlpha::None); let bitmap = JsFuture::from( window .create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options) .expect("unexpected exception in `createImageBitmap()`"), ); let CursorImage { width, height, hotspot_x, hotspot_y, .. } = *image; async move { let bitmap: ImageBitmap = bitmap.await.expect("found invalid state in `ImageData`").unchecked_into(); let canvas: HtmlCanvasElement = document.create_element("canvas").expect("invalid tag name").unchecked_into(); #[allow(clippy::disallowed_methods)] canvas.set_width(width as u32); #[allow(clippy::disallowed_methods)] canvas.set_height(height as u32); // 3. Draw `ImageBitmap` on an `HTMLCanvasElement`. let context: ImageBitmapRenderingContext = canvas .get_context("bitmaprenderer") .expect("unexpected exception in `HTMLCanvasElement.getContext()`") .expect("`bitmaprenderer` context unsupported") .unchecked_into(); context.transfer_from_image_bitmap(&bitmap); drop(bitmap); drop(context); // 4. Create a `Blob` from the `HTMLCanvasElement`. // // To keep the `Closure` alive until `HTMLCanvasElement.toBlob()` is done, // we do the whole `Waker` strategy. Commonly on `Drop` the callback is aborted, // but it would increase complexity and isn't possible in this case. // Keep in mind that `HTMLCanvasElement.toBlob()` can call the callback immediately. let value = Rc::new(RefCell::new(None)); let waker = Rc::new(RefCell::>::new(None)); let callback = Closure::once({ let value = value.clone(); let waker = waker.clone(); move |blob: Option| { *value.borrow_mut() = Some(blob); if let Some(waker) = waker.borrow_mut().take() { waker.wake(); } } }); canvas .to_blob(callback.as_ref().unchecked_ref()) .expect("failed with `SecurityError` despite only source coming from memory"); let blob = future::poll_fn(|cx| { if let Some(blob) = value.borrow_mut().take() { Poll::Ready(blob) } else { *waker.borrow_mut() = Some(cx.waker().clone()); Poll::Pending } }) .await; drop(canvas); let Some(blob) = blob else { return Err(CustomCursorError::Blob); }; // 5. Create an object URL from the `Blob`. let url = Url::create_object_url_with_blob(&blob) .expect("unexpected exception in `URL.createObjectURL()`"); let url = UrlType::Object(ObjectUrl(url)); from_url(url, hotspot_x, hotspot_y).await } } async fn from_url( url: UrlType, hotspot_x: u16, hotspot_y: u16, ) -> Result { // 6. Decode the image on an `HTMLImageElement` from the URL. let image = HtmlImageElement::new().expect("unexpected exception in `new HtmlImageElement`"); image.set_src(url.url()); let result = JsFuture::from(image.decode()).await; if let Err(error) = result { debug_assert!(error.has_type::()); let error: DomException = error.unchecked_into(); debug_assert_eq!(error.name(), "EncodingError"); let error = error.message(); return Err(CustomCursorError::Decode(error)); } Ok(Image { style: format!("url({}) {hotspot_x} {hotspot_y}, auto", url.url()), _object_url: match url { UrlType::Plain(_) => None, UrlType::Object(object_url) => Some(object_url), }, _image: image, }) } #[allow(clippy::await_holding_refcell_ref)] // false-positive async fn from_animation( main_thread: MainThreadMarker, duration: Duration, cursors: impl ExactSizeIterator, ) -> Result { let keyframes = Array::new(); let mut images = Vec::with_capacity(cursors.len()); for cursor in cursors { let state = cursor.state.get(main_thread).borrow(); match state.deref() { ImageState::Loading { notifier, .. } => { let notified = notifier.notified(); drop(state); notified.await?; }, ImageState::Failed(error) => return Err(error.clone()), ImageState::Image(_) => drop(state), ImageState::Animation(_) => unreachable!("check in `CustomCursorSource` failed"), } let state = cursor.state.get(main_thread).borrow(); let style = match state.deref() { ImageState::Image(Image { style, .. }) => style, _ => unreachable!("found invalid state"), }; let keyframe: Keyframe = Object::new().unchecked_into(); keyframe.set_cursor(style); keyframes.push(&keyframe); drop(state); images.push(cursor); } keyframes.push(&keyframes.get(0)); let options: KeyframeAnimationOptions = Object::new().unchecked_into(); options.set_duration(duration.as_millis() as f64); options.set_iterations(f64::INFINITY); Ok(Animation { keyframes, options, _images: images }) } #[wasm_bindgen] extern "C" { type CanvasAnimateExt; #[wasm_bindgen(method, js_name = animate)] fn animate_with_keyframe_animation_options( this: &CanvasAnimateExt, keyframes: Option<&Object>, options: &KeyframeAnimationOptions, ) -> WebAnimation; #[derive(Debug)] type WebAnimation; #[wasm_bindgen(method)] fn cancel(this: &WebAnimation); #[wasm_bindgen(method)] fn play(this: &WebAnimation); #[wasm_bindgen(extends = Object)] type Keyframe; #[wasm_bindgen(method, setter, js_name = cursor)] fn set_cursor(this: &Keyframe, value: &str); #[derive(Debug)] #[wasm_bindgen(extends = Object)] type KeyframeAnimationOptions; #[wasm_bindgen(method, setter, js_name = duration)] fn set_duration(this: &KeyframeAnimationOptions, value: f64); #[wasm_bindgen(method, setter, js_name = iterations)] fn set_iterations(this: &KeyframeAnimationOptions, value: f64); } winit-0.30.9/src/platform_impl/web/device.rs000064400000000000000000000002561046102023000170700ustar 00000000000000#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(pub i32); impl DeviceId { pub const fn dummy() -> Self { Self(0) } } winit-0.30.9/src/platform_impl/web/error.rs000064400000000000000000000003101046102023000167510ustar 00000000000000use std::fmt; #[derive(Debug)] pub struct OsError(pub String); impl fmt::Display for OsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } winit-0.30.9/src/platform_impl/web/event_loop/mod.rs000064400000000000000000000101531046102023000205570ustar 00000000000000use std::marker::PhantomData; use std::sync::mpsc::{self, Receiver, Sender}; use crate::error::EventLoopError; use crate::event::Event; use crate::event_loop::ActiveEventLoop as RootActiveEventLoop; use crate::platform::web::{ActiveEventLoopExtWebSys, PollStrategy, WaitUntilStrategy}; use super::{backend, device, window}; mod proxy; pub(crate) mod runner; mod state; mod window_target; pub(crate) use proxy::EventLoopProxy; pub(crate) use window_target::{ActiveEventLoop, OwnedDisplayHandle}; pub struct EventLoop { elw: RootActiveEventLoop, user_event_sender: Sender, user_event_receiver: Receiver, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} impl EventLoop { pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result { let (user_event_sender, user_event_receiver) = mpsc::channel(); let elw = RootActiveEventLoop { p: ActiveEventLoop::new(), _marker: PhantomData }; Ok(EventLoop { elw, user_event_sender, user_event_receiver }) } pub fn run(self, mut event_handler: F) -> ! where F: FnMut(Event, &RootActiveEventLoop), { let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData }; // SAFETY: Don't use `move` to make sure we leak the `event_handler` and `target`. let handler: Box)> = Box::new(|event| { let event = match event.map_nonuser_event() { Ok(event) => event, Err(Event::UserEvent(())) => Event::UserEvent( self.user_event_receiver .try_recv() .expect("handler woken up without user event"), ), Err(_) => unreachable!(), }; event_handler(event, &target) }); // SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe // because this function will never return and all resources not cleaned up by the point we // `throw` will leak, making this actually `'static`. let handler = unsafe { std::mem::transmute::)>, Box) + 'static>>( handler, ) }; self.elw.p.run(handler, false); // Throw an exception to break out of Rust execution and use unreachable to tell the // compiler this function won't return, giving it a return type of '!' backend::throw( "Using exceptions for control flow, don't mind me. This isn't actually an error!", ); unreachable!(); } pub fn spawn(self, mut event_handler: F) where F: 'static + FnMut(Event, &RootActiveEventLoop), { let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData }; self.elw.p.run( Box::new(move |event| { let event = match event.map_nonuser_event() { Ok(event) => event, Err(Event::UserEvent(())) => Event::UserEvent( self.user_event_receiver .try_recv() .expect("handler woken up without user event"), ), Err(_) => unreachable!(), }; event_handler(event, &target) }), true, ); } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy::new(self.elw.p.waker(), self.user_event_sender.clone()) } pub fn window_target(&self) -> &RootActiveEventLoop { &self.elw } pub fn set_poll_strategy(&self, strategy: PollStrategy) { self.elw.set_poll_strategy(strategy); } pub fn poll_strategy(&self) -> PollStrategy { self.elw.poll_strategy() } pub fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { self.elw.set_wait_until_strategy(strategy); } pub fn wait_until_strategy(&self) -> WaitUntilStrategy { self.elw.wait_until_strategy() } } winit-0.30.9/src/platform_impl/web/event_loop/proxy.rs000064400000000000000000000014521046102023000211630ustar 00000000000000use std::rc::Weak; use std::sync::mpsc::{SendError, Sender}; use super::runner::Execution; use crate::event_loop::EventLoopClosed; use crate::platform_impl::platform::r#async::Waker; pub struct EventLoopProxy { runner: Waker>, sender: Sender, } impl EventLoopProxy { pub fn new(runner: Waker>, sender: Sender) -> Self { Self { runner, sender } } pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.sender.send(event).map_err(|SendError(event)| EventLoopClosed(event))?; self.runner.wake(); Ok(()) } } impl Clone for EventLoopProxy { fn clone(&self) -> Self { Self { runner: self.runner.clone(), sender: self.sender.clone() } } } winit-0.30.9/src/platform_impl/web/event_loop/runner.rs000064400000000000000000000755371046102023000213320ustar 00000000000000use std::cell::{Cell, RefCell}; use std::collections::{HashSet, VecDeque}; use std::iter; use std::num::NonZeroUsize; use std::ops::Deref; use std::rc::{Rc, Weak}; use wasm_bindgen::prelude::Closure; use wasm_bindgen::JsCast; use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent}; use web_time::{Duration, Instant}; use super::super::main_thread::MainThreadMarker; use super::super::DeviceId; use super::backend; use super::state::State; use crate::dpi::PhysicalSize; use crate::event::{ DeviceEvent, DeviceId as RootDeviceId, ElementState, Event, RawKeyEvent, StartCause, WindowEvent, }; use crate::event_loop::{ControlFlow, DeviceEvents}; use crate::platform::web::{PollStrategy, WaitUntilStrategy}; use crate::platform_impl::platform::backend::EventListenerHandle; use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner}; use crate::platform_impl::platform::window::Inner; use crate::window::WindowId; pub struct Shared(Rc); pub(super) type EventHandler = dyn FnMut(Event<()>); impl Clone for Shared { fn clone(&self) -> Self { Shared(self.0.clone()) } } type OnEventHandle = RefCell>>; pub struct Execution { main_thread: MainThreadMarker, proxy_spawner: WakerSpawner>, control_flow: Cell, poll_strategy: Cell, wait_until_strategy: Cell, exit: Cell, runner: RefCell, suspended: Cell, event_loop_recreation: Cell, events: RefCell>, id: RefCell, window: web_sys::Window, document: Document, #[allow(clippy::type_complexity)] all_canvases: RefCell>, DispatchRunner)>>, redraw_pending: RefCell>, destroy_pending: RefCell>, page_transition_event_handle: RefCell>, device_events: Cell, on_mouse_move: OnEventHandle, on_wheel: OnEventHandle, on_mouse_press: OnEventHandle, on_mouse_release: OnEventHandle, on_key_press: OnEventHandle, on_key_release: OnEventHandle, on_visibility_change: OnEventHandle, } enum RunnerEnum { /// The `EventLoop` is created but not being run. Pending, /// The `EventLoop` is being run. Running(Runner), /// The `EventLoop` is exited after being started with `EventLoop::run_app`. Since /// `EventLoop::run_app` takes ownership of the `EventLoop`, we can be certain /// that this event loop will never be run again. Destroyed, } impl RunnerEnum { fn maybe_runner(&self) -> Option<&Runner> { match self { RunnerEnum::Running(runner) => Some(runner), _ => None, } } } struct Runner { state: State, event_handler: Box, } impl Runner { pub fn new(event_handler: Box) -> Self { Runner { state: State::Init, event_handler } } /// Returns the corresponding `StartCause` for the current `state`, or `None` /// when in `Exit` state. fn maybe_start_cause(&self) -> Option { Some(match self.state { State::Init => StartCause::Init, State::Poll { .. } => StartCause::Poll, State::Wait { start } => StartCause::WaitCancelled { start, requested_resume: None }, State::WaitUntil { start, end, .. } => { StartCause::WaitCancelled { start, requested_resume: Some(end) } }, State::Exit => return None, }) } fn handle_single_event(&mut self, runner: &Shared, event: impl Into) { match event.into() { EventWrapper::Event(event) => (self.event_handler)(event), EventWrapper::ScaleChange { canvas, size, scale } => { if let Some(canvas) = canvas.upgrade() { canvas.borrow().handle_scale_change( runner, |event| (self.event_handler)(event), size, scale, ) } }, } } } impl Shared { pub fn new() -> Self { let main_thread = MainThreadMarker::new().expect("only callable from inside the `Window`"); #[allow(clippy::disallowed_methods)] let window = web_sys::window().expect("only callable from inside the `Window`"); #[allow(clippy::disallowed_methods)] let document = window.document().expect("Failed to obtain document"); Shared(Rc::::new_cyclic(|weak| { let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, count, local| { if let Some(runner) = runner.upgrade() { Shared(runner).send_user_events(count, local) } }) .expect("`EventLoop` has to be created in the main thread"); Execution { main_thread, proxy_spawner, control_flow: Cell::new(ControlFlow::default()), poll_strategy: Cell::new(PollStrategy::default()), wait_until_strategy: Cell::new(WaitUntilStrategy::default()), exit: Cell::new(false), runner: RefCell::new(RunnerEnum::Pending), suspended: Cell::new(false), event_loop_recreation: Cell::new(false), events: RefCell::new(VecDeque::new()), window, document, id: RefCell::new(0), all_canvases: RefCell::new(Vec::new()), redraw_pending: RefCell::new(HashSet::new()), destroy_pending: RefCell::new(VecDeque::new()), page_transition_event_handle: RefCell::new(None), device_events: Cell::default(), on_mouse_move: RefCell::new(None), on_wheel: RefCell::new(None), on_mouse_press: RefCell::new(None), on_mouse_release: RefCell::new(None), on_key_press: RefCell::new(None), on_key_release: RefCell::new(None), on_visibility_change: RefCell::new(None), } })) } pub fn main_thread(&self) -> MainThreadMarker { self.0.main_thread } pub fn window(&self) -> &web_sys::Window { &self.0.window } pub fn document(&self) -> &Document { &self.0.document } pub fn add_canvas( &self, id: WindowId, canvas: Weak>, runner: DispatchRunner, ) { self.0.all_canvases.borrow_mut().push((id, canvas, runner)); } pub fn notify_destroy_window(&self, id: WindowId) { self.0.destroy_pending.borrow_mut().push_back(id); } // Set the event callback to use for the event loop runner // This the event callback is a fairly thin layer over the user-provided callback that closes // over a RootActiveEventLoop reference pub fn set_listener(&self, event_handler: Box) { { let mut runner = self.0.runner.borrow_mut(); assert!(matches!(*runner, RunnerEnum::Pending)); *runner = RunnerEnum::Running(Runner::new(event_handler)); } self.init(); *self.0.page_transition_event_handle.borrow_mut() = Some(backend::on_page_transition( self.window().clone(), { let runner = self.clone(); move |event: PageTransitionEvent| { if event.persisted() { runner.0.suspended.set(false); runner.send_event(Event::Resumed); } } }, { let runner = self.clone(); move |event: PageTransitionEvent| { runner.0.suspended.set(true); if event.persisted() { runner.send_event(Event::Suspended); } else { runner.handle_unload(); } } }, )); let runner = self.clone(); let window = self.window().clone(); *self.0.on_mouse_move.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "pointermove", Closure::new(move |event: PointerEvent| { if !runner.device_events() { return; } // chorded button event let device_id = RootDeviceId(DeviceId(event.pointer_id())); if let Some(button) = backend::event::mouse_button(&event) { let state = if backend::event::mouse_buttons(&event).contains(button.into()) { ElementState::Pressed } else { ElementState::Released }; runner.send_event(Event::DeviceEvent { device_id, event: DeviceEvent::Button { button: button.to_id(), state }, }); return; } // pointer move event let mut delta = backend::event::MouseDelta::init(&window, &event); runner.send_events(backend::event::pointer_move_event(event).flat_map(|event| { let delta = delta.delta(&event).to_physical(backend::scale_factor(&window)); let x_motion = (delta.x != 0.0).then_some(Event::DeviceEvent { device_id, event: DeviceEvent::Motion { axis: 0, value: delta.x }, }); let y_motion = (delta.y != 0.0).then_some(Event::DeviceEvent { device_id, event: DeviceEvent::Motion { axis: 1, value: delta.y }, }); x_motion.into_iter().chain(y_motion).chain(iter::once(Event::DeviceEvent { device_id, event: DeviceEvent::MouseMotion { delta: (delta.x, delta.y) }, })) })); }), )); let runner = self.clone(); let window = self.window().clone(); *self.0.on_wheel.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "wheel", Closure::new(move |event: WheelEvent| { if !runner.device_events() { return; } if let Some(delta) = backend::event::mouse_scroll_delta(&window, &event) { runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(0)), event: DeviceEvent::MouseWheel { delta }, }); } }), )); let runner = self.clone(); *self.0.on_mouse_press.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "pointerdown", Closure::new(move |event: PointerEvent| { if !runner.device_events() { return; } let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(event.pointer_id())), event: DeviceEvent::Button { button: button.to_id(), state: ElementState::Pressed, }, }); }), )); let runner = self.clone(); *self.0.on_mouse_release.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "pointerup", Closure::new(move |event: PointerEvent| { if !runner.device_events() { return; } let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(event.pointer_id())), event: DeviceEvent::Button { button: button.to_id(), state: ElementState::Released, }, }); }), )); let runner = self.clone(); *self.0.on_key_press.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "keydown", Closure::new(move |event: KeyboardEvent| { if !runner.device_events() { return; } runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId::dummy()), event: DeviceEvent::Key(RawKeyEvent { physical_key: backend::event::key_code(&event), state: ElementState::Pressed, }), }); }), )); let runner = self.clone(); *self.0.on_key_release.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "keyup", Closure::new(move |event: KeyboardEvent| { if !runner.device_events() { return; } runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId::dummy()), event: DeviceEvent::Key(RawKeyEvent { physical_key: backend::event::key_code(&event), state: ElementState::Released, }), }); }), )); let runner = self.clone(); *self.0.on_visibility_change.borrow_mut() = Some(EventListenerHandle::new( // Safari <14 doesn't support the `visibilitychange` event on `Window`. self.document().clone(), "visibilitychange", Closure::new(move |_| { if !runner.0.suspended.get() { for (id, canvas, _) in &*runner.0.all_canvases.borrow() { if let Some(canvas) = canvas.upgrade() { let is_visible = backend::is_visible(runner.document()); // only fire if: // - not visible and intersects // - not visible and we don't know if it intersects yet // - visible and intersects if let (false, Some(true) | None) | (true, Some(true)) = (is_visible, canvas.borrow().is_intersecting) { runner.send_event(Event::WindowEvent { window_id: *id, event: WindowEvent::Occluded(!is_visible), }); } } } } }), )); } // Generate a strictly increasing ID // This is used to differentiate windows when handling events pub fn generate_id(&self) -> u32 { let mut id = self.0.id.borrow_mut(); *id += 1; *id } pub fn request_redraw(&self, id: WindowId) { self.0.redraw_pending.borrow_mut().insert(id); self.send_events::(iter::empty()); } pub fn init(&self) { // NB: For consistency all platforms must emit a 'resumed' event even though web // applications don't themselves have a formal suspend/resume lifecycle. self.run_until_cleared([Event::NewEvents(StartCause::Init), Event::Resumed].into_iter()); } // Run the polling logic for the Poll ControlFlow, which involves clearing the queue pub fn poll(&self) { let start_cause = Event::NewEvents(StartCause::Poll); self.run_until_cleared(iter::once(start_cause)); } // Run the logic for waking from a WaitUntil, which involves clearing the queue // Generally there shouldn't be events built up when this is called pub fn resume_time_reached(&self, start: Instant, requested_resume: Instant) { let start_cause = Event::NewEvents(StartCause::ResumeTimeReached { start, requested_resume }); self.run_until_cleared(iter::once(start_cause)); } // Add an event to the event loop runner, from the user or an event handler // // It will determine if the event should be immediately sent to the user or buffered for later pub(crate) fn send_event>(&self, event: E) { self.send_events(iter::once(event)); } // Add a series of user events to the event loop runner // // This will schedule the event loop to wake up instead of waking it up immediately if its not // running. pub(crate) fn send_user_events(&self, count: NonZeroUsize, local: bool) { // If the event loop is closed, it should discard any new events if self.is_closed() { return; } if local { // If the loop is not running and triggered locally, queue on next microtick. if let Ok(RunnerEnum::Running(_)) = self.0.runner.try_borrow().as_ref().map(Deref::deref) { self.window().queue_microtask( &Closure::once_into_js({ let this = Rc::downgrade(&self.0); move || { if let Some(shared) = this.upgrade() { Shared(shared).send_events( iter::repeat(Event::UserEvent(())).take(count.get()), ) } } }) .unchecked_into(), ); return; } } self.send_events(iter::repeat(Event::UserEvent(())).take(count.get())) } // Add a series of events to the event loop runner // // It will determine if the event should be immediately sent to the user or buffered for later pub(crate) fn send_events>(&self, events: impl IntoIterator) { // If the event loop is closed, it should discard any new events if self.is_closed() { return; } // If we can run the event processing right now, or need to queue this and wait for later let mut process_immediately = true; match self.0.runner.try_borrow().as_ref().map(Deref::deref) { Ok(RunnerEnum::Running(ref runner)) => { // If we're currently polling, queue this and wait for the poll() method to be // called. if let State::Poll { .. } = runner.state { process_immediately = false; } }, Ok(RunnerEnum::Pending) => { // The runner still hasn't been attached: queue this event and wait for it to be process_immediately = false; }, // Some other code is mutating the runner, which most likely means // the event loop is running and busy. So we queue this event for // it to be processed later. Err(_) => { process_immediately = false; }, // This is unreachable since `self.is_closed() == true`. Ok(RunnerEnum::Destroyed) => unreachable!(), } if !process_immediately { // Queue these events to look at later self.0.events.borrow_mut().extend(events.into_iter().map(Into::into)); return; } // At this point, we know this is a fresh set of events // Now we determine why new events are incoming, and handle the events let start_cause = match (self.0.runner.borrow().maybe_runner()) .unwrap_or_else(|| { unreachable!("The runner cannot process events when it is not attached") }) .maybe_start_cause() { Some(c) => c, // If we're in the exit state, don't do event processing None => return, }; // Take the start event, then the events provided to this function, and run an iteration of // the event loop let start_event = Event::NewEvents(start_cause); let events = iter::once(EventWrapper::from(start_event)).chain(events.into_iter().map(Into::into)); self.run_until_cleared(events); } // Process the destroy-pending windows. This should only be called from // `run_until_cleared`, somewhere between emitting `NewEvents` and `AboutToWait`. fn process_destroy_pending_windows(&self) { while let Some(id) = self.0.destroy_pending.borrow_mut().pop_front() { self.0.all_canvases.borrow_mut().retain(|&(item_id, ..)| item_id != id); self.handle_event(Event::WindowEvent { window_id: id, event: crate::event::WindowEvent::Destroyed, }); self.0.redraw_pending.borrow_mut().remove(&id); } } // Given the set of new events, run the event loop until the main events and redraw events are // cleared // // This will also process any events that have been queued or that are queued during processing fn run_until_cleared>(&self, events: impl Iterator) { for event in events { self.handle_event(event.into()); } self.process_destroy_pending_windows(); // Collect all of the redraw events to avoid double-locking the RefCell let redraw_events: Vec = self.0.redraw_pending.borrow_mut().drain().collect(); for window_id in redraw_events { self.handle_event(Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested, }); } self.handle_event(Event::AboutToWait); self.apply_control_flow(); // If the event loop is closed, it has been closed this iteration and now the closing // event should be emitted if self.is_closed() { self.handle_loop_destroyed(); } } fn handle_unload(&self) { self.exit(); self.apply_control_flow(); // We don't call `handle_loop_destroyed` here because we don't need to // perform cleanup when the web browser is going to destroy the page. self.handle_event(Event::LoopExiting); } // handle_event takes in events and either queues them or applies a callback // // It should only ever be called from `run_until_cleared`. fn handle_event(&self, event: impl Into) { if self.is_closed() { self.exit(); } match *self.0.runner.borrow_mut() { RunnerEnum::Running(ref mut runner) => { runner.handle_single_event(self, event); }, // If an event is being handled without a runner somehow, add it to the event queue so // it will eventually be processed RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event.into()), // If the Runner has been destroyed, there is nothing to do. RunnerEnum::Destroyed => return, } let is_closed = self.exiting(); // Don't take events out of the queue if the loop is closed or the runner doesn't exist // If the runner doesn't exist and this method recurses, it will recurse infinitely if !is_closed && self.0.runner.borrow().maybe_runner().is_some() { // Pre-fetch window commands to avoid having to wait until the next event loop cycle // and potentially block other threads in the meantime. for (_, window, runner) in self.0.all_canvases.borrow().iter() { if let Some(window) = window.upgrade() { runner.run(); drop(window) } } // Take an event out of the queue and handle it // Make sure not to let the borrow_mut live during the next handle_event let event = { let mut events = self.0.events.borrow_mut(); // Pre-fetch `UserEvent`s to avoid having to wait until the next event loop cycle. events.extend( iter::repeat(Event::UserEvent(())) .take(self.0.proxy_spawner.fetch()) .map(EventWrapper::from), ); events.pop_front() }; if let Some(event) = event { self.handle_event(event); } } } // Apply the new ControlFlow that has been selected by the user // Start any necessary timeouts etc fn apply_control_flow(&self) { let new_state = if self.exiting() { State::Exit } else { match self.control_flow() { ControlFlow::Poll => { let cloned = self.clone(); State::Poll { _request: backend::Schedule::new( self.poll_strategy(), self.window(), move || cloned.poll(), ), } }, ControlFlow::Wait => State::Wait { start: Instant::now() }, ControlFlow::WaitUntil(end) => { let start = Instant::now(); let delay = if end <= start { Duration::from_millis(0) } else { end - start }; let cloned = self.clone(); State::WaitUntil { start, end, _timeout: backend::Schedule::new_with_duration( self.wait_until_strategy(), self.window(), move || cloned.resume_time_reached(start, end), delay, ), } }, } }; if let RunnerEnum::Running(ref mut runner) = *self.0.runner.borrow_mut() { runner.state = new_state; } } fn handle_loop_destroyed(&self) { self.handle_event(Event::LoopExiting); let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut()); *self.0.page_transition_event_handle.borrow_mut() = None; *self.0.on_mouse_move.borrow_mut() = None; *self.0.on_wheel.borrow_mut() = None; *self.0.on_mouse_press.borrow_mut() = None; *self.0.on_mouse_release.borrow_mut() = None; *self.0.on_key_press.borrow_mut() = None; *self.0.on_key_release.borrow_mut() = None; *self.0.on_visibility_change.borrow_mut() = None; // Dropping the `Runner` drops the event handler closure, which will in // turn drop all `Window`s moved into the closure. *self.0.runner.borrow_mut() = RunnerEnum::Destroyed; for (_, canvas, _) in all_canvases { // In case any remaining `Window`s are still not dropped, we will need // to explicitly remove the event handlers associated with their canvases. if let Some(canvas) = canvas.upgrade() { let mut canvas = canvas.borrow_mut(); canvas.remove_listeners(); } } // At this point, the `self.0` `Rc` should only be strongly referenced // by the following: // * `self`, i.e. the item which triggered this event loop wakeup, which is usually a // `wasm-bindgen` `Closure`, which will be dropped after returning to the JS glue code. // * The `ActiveEventLoop` leaked inside `EventLoop::run_app` due to the JS exception thrown // at the end. // * For each undropped `Window`: // * The `register_redraw_request` closure. // * The `destroy_fn` closure. if self.0.event_loop_recreation.get() { crate::event_loop::EventLoopBuilder::<()>::allow_event_loop_recreation(); } } // Check if the event loop is currently closed fn is_closed(&self) -> bool { match self.0.runner.try_borrow().as_ref().map(Deref::deref) { Ok(RunnerEnum::Running(runner)) => runner.state.exiting(), // The event loop is not closed since it is not initialized. Ok(RunnerEnum::Pending) => false, // The event loop is closed since it has been destroyed. Ok(RunnerEnum::Destroyed) => true, // Some other code is mutating the runner, which most likely means // the event loop is running and busy. Err(_) => false, } } pub fn listen_device_events(&self, allowed: DeviceEvents) { self.0.device_events.set(allowed) } fn device_events(&self) -> bool { match self.0.device_events.get() { DeviceEvents::Always => true, DeviceEvents::WhenFocused => { self.0.all_canvases.borrow().iter().any(|(_, canvas, _)| { if let Some(canvas) = canvas.upgrade() { canvas.borrow().has_focus.get() } else { false } }) }, DeviceEvents::Never => false, } } pub fn event_loop_recreation(&self, allow: bool) { self.0.event_loop_recreation.set(allow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.0.control_flow.get() } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.0.control_flow.set(control_flow) } pub(crate) fn exit(&self) { self.0.exit.set(true) } pub(crate) fn exiting(&self) -> bool { self.0.exit.get() } pub(crate) fn set_poll_strategy(&self, strategy: PollStrategy) { self.0.poll_strategy.set(strategy) } pub(crate) fn poll_strategy(&self) -> PollStrategy { self.0.poll_strategy.get() } pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { self.0.wait_until_strategy.set(strategy) } pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy { self.0.wait_until_strategy.get() } pub(crate) fn waker(&self) -> Waker> { self.0.proxy_spawner.waker() } } pub(crate) enum EventWrapper { Event(Event<()>), ScaleChange { canvas: Weak>, size: PhysicalSize, scale: f64 }, } impl From> for EventWrapper { fn from(value: Event<()>) -> Self { Self::Event(value) } } winit-0.30.9/src/platform_impl/web/event_loop/state.rs000064400000000000000000000005271046102023000211240ustar 00000000000000use super::backend; use web_time::Instant; #[derive(Debug)] pub enum State { Init, WaitUntil { _timeout: backend::Schedule, start: Instant, end: Instant }, Wait { start: Instant }, Poll { _request: backend::Schedule }, Exit, } impl State { pub fn exiting(&self) -> bool { matches!(self, State::Exit) } } winit-0.30.9/src/platform_impl/web/event_loop/window_target.rs000064400000000000000000000633151046102023000226650ustar 00000000000000use std::cell::{Cell, RefCell}; use std::clone::Clone; use std::collections::vec_deque::IntoIter as VecDequeIter; use std::collections::VecDeque; use std::iter; use std::rc::{Rc, Weak}; use web_sys::Element; use super::super::monitor::MonitorHandle; use super::super::KeyEventExtra; use super::device::DeviceId; use super::runner::{EventWrapper, Execution}; use super::window::WindowId; use super::{backend, runner}; use crate::event::{ DeviceId as RootDeviceId, ElementState, Event, KeyEvent, Touch, TouchPhase, WindowEvent, }; use crate::event_loop::{ControlFlow, DeviceEvents}; use crate::keyboard::ModifiersState; use crate::platform::web::{CustomCursorFuture, PollStrategy, WaitUntilStrategy}; use crate::platform_impl::platform::cursor::CustomCursor; use crate::platform_impl::platform::r#async::Waker; use crate::window::{ CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId, }; #[derive(Default)] struct ModifiersShared(Rc>); impl ModifiersShared { fn set(&self, new: ModifiersState) { self.0.set(new) } fn get(&self) -> ModifiersState { self.0.get() } } impl Clone for ModifiersShared { fn clone(&self) -> Self { Self(Rc::clone(&self.0)) } } #[derive(Clone)] pub struct ActiveEventLoop { pub(crate) runner: runner::Shared, modifiers: ModifiersShared, } impl ActiveEventLoop { pub fn new() -> Self { Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() } } pub fn run(&self, event_handler: Box, event_loop_recreation: bool) { self.runner.event_loop_recreation(event_loop_recreation); self.runner.set_listener(event_handler); } pub fn generate_id(&self) -> WindowId { WindowId(self.runner.generate_id()) } pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor { RootCustomCursor { inner: CustomCursor::new(self, source.inner) } } pub fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture { CustomCursorFuture(CustomCursor::new_async(self, source.inner)) } pub fn register(&self, canvas: &Rc>, id: WindowId) { let canvas_clone = canvas.clone(); let mut canvas = canvas.borrow_mut(); #[cfg(any(feature = "rwh_04", feature = "rwh_05"))] canvas.set_attribute("data-raw-handle", &id.0.to_string()); canvas.on_touch_start(); let runner = self.runner.clone(); let has_focus = canvas.has_focus.clone(); let modifiers = self.modifiers.clone(); canvas.on_blur(move || { has_focus.set(false); let clear_modifiers = (!modifiers.get().is_empty()).then(|| { modifiers.set(ModifiersState::empty()); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(ModifiersState::empty().into()), } }); runner.send_events(clear_modifiers.into_iter().chain(iter::once(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Focused(false), }))); }); let runner = self.runner.clone(); let has_focus = canvas.has_focus.clone(); canvas.on_focus(move || { if !has_focus.replace(true) { runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Focused(true), }); } }); // It is possible that at this point the canvas has // been focused before the callback can be called. let focused = canvas .document() .active_element() .filter(|element| { let canvas: &Element = canvas.raw(); element == canvas }) .is_some(); if focused { canvas.has_focus.set(true); self.runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Focused(true), }) } let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); canvas.on_keyboard_press( move |physical_key, logical_key, text, location, repeat, active_modifiers| { let modifiers_changed = (modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let device_id = RootDeviceId(DeviceId::dummy()); runner.send_events( iter::once(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::KeyboardInput { device_id, event: KeyEvent { physical_key, logical_key, text, location, state: ElementState::Pressed, repeat, platform_specific: KeyEventExtra, }, is_synthetic: false, }, }) .chain(modifiers_changed), ); }, ); let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); canvas.on_keyboard_release( move |physical_key, logical_key, text, location, repeat, active_modifiers| { let modifiers_changed = (modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let device_id = RootDeviceId(DeviceId::dummy()); runner.send_events( iter::once(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::KeyboardInput { device_id, event: KeyEvent { physical_key, logical_key, text, location, state: ElementState::Released, repeat, platform_specific: KeyEventExtra, }, is_synthetic: false, }, }) .chain(modifiers_changed), ) }, ); let has_focus = canvas.has_focus.clone(); canvas.on_cursor_leave({ let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id| { let focus = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let pointer = pointer_id.map(|pointer_id| Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorLeft { device_id: RootDeviceId(DeviceId(pointer_id)), }, }); if focus.is_some() || pointer.is_some() { runner.send_events(focus.into_iter().chain(pointer)) } } }); canvas.on_cursor_enter({ let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id| { let focus = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let pointer = pointer_id.map(|pointer_id| Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorEntered { device_id: RootDeviceId(DeviceId(pointer_id)), }, }); if focus.is_some() || pointer.is_some() { runner.send_events(focus.into_iter().chain(pointer)) } } }); canvas.on_cursor_move( { let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id, events| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); runner.send_events(modifiers.into_iter().chain(events.flat_map(|position| { let device_id = RootDeviceId(DeviceId(pointer_id)); iter::once(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { device_id, position }, }) }))); } }, { let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, device_id, events| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); runner.send_events(modifiers.into_iter().chain(events.map( |(location, force)| Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Touch(Touch { id: device_id as u64, device_id: RootDeviceId(DeviceId(device_id)), phase: TouchPhase::Moved, force: Some(force), location, }), }, ))); } }, { let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id, position: crate::dpi::PhysicalPosition, buttons, button| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let device_id = RootDeviceId(DeviceId(pointer_id)); let state = if buttons.contains(button.into()) { ElementState::Pressed } else { ElementState::Released }; // A chorded button event may come in without any prior CursorMoved events, // therefore we should send a CursorMoved event to make sure that the // user code has the correct cursor position. runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { device_id, position }, }, Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { device_id, state, button }, }, ])); } }, ); canvas.on_mouse_press( { let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id, position, button| { let modifiers = (modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let device_id: RootDeviceId = RootDeviceId(DeviceId(pointer_id)); // A mouse down event may come in without any prior CursorMoved events, // therefore we should send a CursorMoved event to make sure that the // user code has the correct cursor position. runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { device_id, position }, }, Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { device_id, state: ElementState::Pressed, button, }, }, ])); } }, { let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, device_id, location, force| { let modifiers = (modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); runner.send_events(modifiers.into_iter().chain(iter::once( Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Touch(Touch { id: device_id as u64, device_id: RootDeviceId(DeviceId(device_id)), phase: TouchPhase::Started, force: Some(force), location, }), }, ))) } }, ); canvas.on_mouse_release( { let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id, position, button| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let device_id: RootDeviceId = RootDeviceId(DeviceId(pointer_id)); // A mouse up event may come in without any prior CursorMoved events, // therefore we should send a CursorMoved event to make sure that the // user code has the correct cursor position. runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { device_id, position }, }, Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { device_id, state: ElementState::Released, button, }, }, ])); } }, { let runner_touch = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, device_id, location, force| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); runner_touch.send_events(modifiers.into_iter().chain(iter::once( Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Touch(Touch { id: device_id as u64, device_id: RootDeviceId(DeviceId(device_id)), phase: TouchPhase::Ended, force: Some(force), location, }), }, ))); } }, ); let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); canvas.on_mouse_wheel(move |pointer_id, delta, active_modifiers| { let modifiers_changed = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); runner.send_events(modifiers_changed.into_iter().chain(iter::once( Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseWheel { device_id: RootDeviceId(DeviceId(pointer_id)), delta, phase: TouchPhase::Moved, }, }, ))); }); let runner = self.runner.clone(); canvas.on_touch_cancel(move |device_id, location, force| { runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Touch(Touch { id: device_id as u64, device_id: RootDeviceId(DeviceId(device_id)), phase: TouchPhase::Cancelled, force: Some(force), location, }), }); }); let runner = self.runner.clone(); canvas.on_dark_mode(move |is_dark_mode| { let theme = if is_dark_mode { Theme::Dark } else { Theme::Light }; runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ThemeChanged(theme), }); }); canvas.on_resize_scale( { let runner = self.runner.clone(); let canvas = canvas_clone.clone(); move |size, scale| { runner.send_event(EventWrapper::ScaleChange { canvas: Rc::downgrade(&canvas), size, scale, }) } }, { let runner = self.runner.clone(); let canvas = canvas_clone.clone(); move |new_size| { let canvas = canvas.borrow(); canvas.set_current_size(new_size); if canvas.old_size() != new_size { canvas.set_old_size(new_size); runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Resized(new_size), }); canvas.request_animation_frame(); } } }, ); let runner = self.runner.clone(); canvas.on_intersection(move |is_intersecting| { // only fire if visible while skipping the first event if it's intersecting if backend::is_visible(runner.document()) && !(is_intersecting && canvas_clone.borrow().is_intersecting.is_none()) { runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Occluded(!is_intersecting), }); } canvas_clone.borrow_mut().is_intersecting = Some(is_intersecting); }); let runner = self.runner.clone(); canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id))); canvas.on_context_menu(); } pub fn available_monitors(&self) -> VecDequeIter { VecDeque::new().into_iter() } pub fn primary_monitor(&self) -> Option { None } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Web(rwh_05::WebDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Web(rwh_06::WebDisplayHandle::new())) } pub fn listen_device_events(&self, allowed: DeviceEvents) { self.runner.listen_device_events(allowed) } pub fn system_theme(&self) -> Option { backend::is_dark_mode(self.runner.window()).map(|is_dark_mode| { if is_dark_mode { Theme::Dark } else { Theme::Light } }) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.runner.set_control_flow(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.runner.control_flow() } pub(crate) fn exit(&self) { self.runner.exit() } pub(crate) fn exiting(&self) -> bool { self.runner.exiting() } pub(crate) fn set_poll_strategy(&self, strategy: PollStrategy) { self.runner.set_poll_strategy(strategy) } pub(crate) fn poll_strategy(&self) -> PollStrategy { self.runner.poll_strategy() } pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { self.runner.set_wait_until_strategy(strategy) } pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy { self.runner.wait_until_strategy() } pub(crate) fn waker(&self) -> Waker> { self.runner.waker() } pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle { OwnedDisplayHandle } } #[derive(Clone)] pub(crate) struct OwnedDisplayHandle; impl OwnedDisplayHandle { #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::WebDisplayHandle::empty().into() } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::WebDisplayHandle::new().into()) } } winit-0.30.9/src/platform_impl/web/keyboard.rs000064400000000000000000000567341046102023000174450ustar 00000000000000use smol_str::SmolStr; use crate::keyboard::{Key, KeyCode, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub(crate) struct KeyEventExtra; impl Key { pub(crate) fn from_key_attribute_value(kav: &str) -> Self { Key::Named(match kav { "Unidentified" => return Key::Unidentified(NativeKey::Web(SmolStr::new(kav))), "Dead" => return Key::Dead(None), "Alt" => NamedKey::Alt, "AltGraph" => NamedKey::AltGraph, "CapsLock" => NamedKey::CapsLock, "Control" => NamedKey::Control, "Fn" => NamedKey::Fn, "FnLock" => NamedKey::FnLock, "NumLock" => NamedKey::NumLock, "ScrollLock" => NamedKey::ScrollLock, "Shift" => NamedKey::Shift, "Symbol" => NamedKey::Symbol, "SymbolLock" => NamedKey::SymbolLock, "Hyper" => NamedKey::Hyper, "Meta" => NamedKey::Super, "Enter" => NamedKey::Enter, "Tab" => NamedKey::Tab, " " => NamedKey::Space, "ArrowDown" => NamedKey::ArrowDown, "ArrowLeft" => NamedKey::ArrowLeft, "ArrowRight" => NamedKey::ArrowRight, "ArrowUp" => NamedKey::ArrowUp, "End" => NamedKey::End, "Home" => NamedKey::Home, "PageDown" => NamedKey::PageDown, "PageUp" => NamedKey::PageUp, "Backspace" => NamedKey::Backspace, "Clear" => NamedKey::Clear, "Copy" => NamedKey::Copy, "CrSel" => NamedKey::CrSel, "Cut" => NamedKey::Cut, "Delete" => NamedKey::Delete, "EraseEof" => NamedKey::EraseEof, "ExSel" => NamedKey::ExSel, "Insert" => NamedKey::Insert, "Paste" => NamedKey::Paste, "Redo" => NamedKey::Redo, "Undo" => NamedKey::Undo, "Accept" => NamedKey::Accept, "Again" => NamedKey::Again, "Attn" => NamedKey::Attn, "Cancel" => NamedKey::Cancel, "ContextMenu" => NamedKey::ContextMenu, "Escape" => NamedKey::Escape, "Execute" => NamedKey::Execute, "Find" => NamedKey::Find, "Help" => NamedKey::Help, "Pause" => NamedKey::Pause, "Play" => NamedKey::Play, "Props" => NamedKey::Props, "Select" => NamedKey::Select, "ZoomIn" => NamedKey::ZoomIn, "ZoomOut" => NamedKey::ZoomOut, "BrightnessDown" => NamedKey::BrightnessDown, "BrightnessUp" => NamedKey::BrightnessUp, "Eject" => NamedKey::Eject, "LogOff" => NamedKey::LogOff, "Power" => NamedKey::Power, "PowerOff" => NamedKey::PowerOff, "PrintScreen" => NamedKey::PrintScreen, "Hibernate" => NamedKey::Hibernate, "Standby" => NamedKey::Standby, "WakeUp" => NamedKey::WakeUp, "AllCandidates" => NamedKey::AllCandidates, "Alphanumeric" => NamedKey::Alphanumeric, "CodeInput" => NamedKey::CodeInput, "Compose" => NamedKey::Compose, "Convert" => NamedKey::Convert, "FinalMode" => NamedKey::FinalMode, "GroupFirst" => NamedKey::GroupFirst, "GroupLast" => NamedKey::GroupLast, "GroupNext" => NamedKey::GroupNext, "GroupPrevious" => NamedKey::GroupPrevious, "ModeChange" => NamedKey::ModeChange, "NextCandidate" => NamedKey::NextCandidate, "NonConvert" => NamedKey::NonConvert, "PreviousCandidate" => NamedKey::PreviousCandidate, "Process" => NamedKey::Process, "SingleCandidate" => NamedKey::SingleCandidate, "HangulMode" => NamedKey::HangulMode, "HanjaMode" => NamedKey::HanjaMode, "JunjaMode" => NamedKey::JunjaMode, "Eisu" => NamedKey::Eisu, "Hankaku" => NamedKey::Hankaku, "Hiragana" => NamedKey::Hiragana, "HiraganaKatakana" => NamedKey::HiraganaKatakana, "KanaMode" => NamedKey::KanaMode, "KanjiMode" => NamedKey::KanjiMode, "Katakana" => NamedKey::Katakana, "Romaji" => NamedKey::Romaji, "Zenkaku" => NamedKey::Zenkaku, "ZenkakuHankaku" => NamedKey::ZenkakuHankaku, "Soft1" => NamedKey::Soft1, "Soft2" => NamedKey::Soft2, "Soft3" => NamedKey::Soft3, "Soft4" => NamedKey::Soft4, "ChannelDown" => NamedKey::ChannelDown, "ChannelUp" => NamedKey::ChannelUp, "Close" => NamedKey::Close, "MailForward" => NamedKey::MailForward, "MailReply" => NamedKey::MailReply, "MailSend" => NamedKey::MailSend, "MediaClose" => NamedKey::MediaClose, "MediaFastForward" => NamedKey::MediaFastForward, "MediaPause" => NamedKey::MediaPause, "MediaPlay" => NamedKey::MediaPlay, "MediaPlayPause" => NamedKey::MediaPlayPause, "MediaRecord" => NamedKey::MediaRecord, "MediaRewind" => NamedKey::MediaRewind, "MediaStop" => NamedKey::MediaStop, "MediaTrackNext" => NamedKey::MediaTrackNext, "MediaTrackPrevious" => NamedKey::MediaTrackPrevious, "New" => NamedKey::New, "Open" => NamedKey::Open, "Print" => NamedKey::Print, "Save" => NamedKey::Save, "SpellCheck" => NamedKey::SpellCheck, "Key11" => NamedKey::Key11, "Key12" => NamedKey::Key12, "AudioBalanceLeft" => NamedKey::AudioBalanceLeft, "AudioBalanceRight" => NamedKey::AudioBalanceRight, "AudioBassBoostDown" => NamedKey::AudioBassBoostDown, "AudioBassBoostToggle" => NamedKey::AudioBassBoostToggle, "AudioBassBoostUp" => NamedKey::AudioBassBoostUp, "AudioFaderFront" => NamedKey::AudioFaderFront, "AudioFaderRear" => NamedKey::AudioFaderRear, "AudioSurroundModeNext" => NamedKey::AudioSurroundModeNext, "AudioTrebleDown" => NamedKey::AudioTrebleDown, "AudioTrebleUp" => NamedKey::AudioTrebleUp, "AudioVolumeDown" => NamedKey::AudioVolumeDown, "AudioVolumeUp" => NamedKey::AudioVolumeUp, "AudioVolumeMute" => NamedKey::AudioVolumeMute, "MicrophoneToggle" => NamedKey::MicrophoneToggle, "MicrophoneVolumeDown" => NamedKey::MicrophoneVolumeDown, "MicrophoneVolumeUp" => NamedKey::MicrophoneVolumeUp, "MicrophoneVolumeMute" => NamedKey::MicrophoneVolumeMute, "SpeechCorrectionList" => NamedKey::SpeechCorrectionList, "SpeechInputToggle" => NamedKey::SpeechInputToggle, "LaunchApplication1" => NamedKey::LaunchApplication1, "LaunchApplication2" => NamedKey::LaunchApplication2, "LaunchCalendar" => NamedKey::LaunchCalendar, "LaunchContacts" => NamedKey::LaunchContacts, "LaunchMail" => NamedKey::LaunchMail, "LaunchMediaPlayer" => NamedKey::LaunchMediaPlayer, "LaunchMusicPlayer" => NamedKey::LaunchMusicPlayer, "LaunchPhone" => NamedKey::LaunchPhone, "LaunchScreenSaver" => NamedKey::LaunchScreenSaver, "LaunchSpreadsheet" => NamedKey::LaunchSpreadsheet, "LaunchWebBrowser" => NamedKey::LaunchWebBrowser, "LaunchWebCam" => NamedKey::LaunchWebCam, "LaunchWordProcessor" => NamedKey::LaunchWordProcessor, "BrowserBack" => NamedKey::BrowserBack, "BrowserFavorites" => NamedKey::BrowserFavorites, "BrowserForward" => NamedKey::BrowserForward, "BrowserHome" => NamedKey::BrowserHome, "BrowserRefresh" => NamedKey::BrowserRefresh, "BrowserSearch" => NamedKey::BrowserSearch, "BrowserStop" => NamedKey::BrowserStop, "AppSwitch" => NamedKey::AppSwitch, "Call" => NamedKey::Call, "Camera" => NamedKey::Camera, "CameraFocus" => NamedKey::CameraFocus, "EndCall" => NamedKey::EndCall, "GoBack" => NamedKey::GoBack, "GoHome" => NamedKey::GoHome, "HeadsetHook" => NamedKey::HeadsetHook, "LastNumberRedial" => NamedKey::LastNumberRedial, "Notification" => NamedKey::Notification, "MannerMode" => NamedKey::MannerMode, "VoiceDial" => NamedKey::VoiceDial, "TV" => NamedKey::TV, "TV3DMode" => NamedKey::TV3DMode, "TVAntennaCable" => NamedKey::TVAntennaCable, "TVAudioDescription" => NamedKey::TVAudioDescription, "TVAudioDescriptionMixDown" => NamedKey::TVAudioDescriptionMixDown, "TVAudioDescriptionMixUp" => NamedKey::TVAudioDescriptionMixUp, "TVContentsMenu" => NamedKey::TVContentsMenu, "TVDataService" => NamedKey::TVDataService, "TVInput" => NamedKey::TVInput, "TVInputComponent1" => NamedKey::TVInputComponent1, "TVInputComponent2" => NamedKey::TVInputComponent2, "TVInputComposite1" => NamedKey::TVInputComposite1, "TVInputComposite2" => NamedKey::TVInputComposite2, "TVInputHDMI1" => NamedKey::TVInputHDMI1, "TVInputHDMI2" => NamedKey::TVInputHDMI2, "TVInputHDMI3" => NamedKey::TVInputHDMI3, "TVInputHDMI4" => NamedKey::TVInputHDMI4, "TVInputVGA1" => NamedKey::TVInputVGA1, "TVMediaContext" => NamedKey::TVMediaContext, "TVNetwork" => NamedKey::TVNetwork, "TVNumberEntry" => NamedKey::TVNumberEntry, "TVPower" => NamedKey::TVPower, "TVRadioService" => NamedKey::TVRadioService, "TVSatellite" => NamedKey::TVSatellite, "TVSatelliteBS" => NamedKey::TVSatelliteBS, "TVSatelliteCS" => NamedKey::TVSatelliteCS, "TVSatelliteToggle" => NamedKey::TVSatelliteToggle, "TVTerrestrialAnalog" => NamedKey::TVTerrestrialAnalog, "TVTerrestrialDigital" => NamedKey::TVTerrestrialDigital, "TVTimer" => NamedKey::TVTimer, "AVRInput" => NamedKey::AVRInput, "AVRPower" => NamedKey::AVRPower, "ColorF0Red" => NamedKey::ColorF0Red, "ColorF1Green" => NamedKey::ColorF1Green, "ColorF2Yellow" => NamedKey::ColorF2Yellow, "ColorF3Blue" => NamedKey::ColorF3Blue, "ColorF4Grey" => NamedKey::ColorF4Grey, "ColorF5Brown" => NamedKey::ColorF5Brown, "ClosedCaptionToggle" => NamedKey::ClosedCaptionToggle, "Dimmer" => NamedKey::Dimmer, "DisplaySwap" => NamedKey::DisplaySwap, "DVR" => NamedKey::DVR, "Exit" => NamedKey::Exit, "FavoriteClear0" => NamedKey::FavoriteClear0, "FavoriteClear1" => NamedKey::FavoriteClear1, "FavoriteClear2" => NamedKey::FavoriteClear2, "FavoriteClear3" => NamedKey::FavoriteClear3, "FavoriteRecall0" => NamedKey::FavoriteRecall0, "FavoriteRecall1" => NamedKey::FavoriteRecall1, "FavoriteRecall2" => NamedKey::FavoriteRecall2, "FavoriteRecall3" => NamedKey::FavoriteRecall3, "FavoriteStore0" => NamedKey::FavoriteStore0, "FavoriteStore1" => NamedKey::FavoriteStore1, "FavoriteStore2" => NamedKey::FavoriteStore2, "FavoriteStore3" => NamedKey::FavoriteStore3, "Guide" => NamedKey::Guide, "GuideNextDay" => NamedKey::GuideNextDay, "GuidePreviousDay" => NamedKey::GuidePreviousDay, "Info" => NamedKey::Info, "InstantReplay" => NamedKey::InstantReplay, "Link" => NamedKey::Link, "ListProgram" => NamedKey::ListProgram, "LiveContent" => NamedKey::LiveContent, "Lock" => NamedKey::Lock, "MediaApps" => NamedKey::MediaApps, "MediaAudioTrack" => NamedKey::MediaAudioTrack, "MediaLast" => NamedKey::MediaLast, "MediaSkipBackward" => NamedKey::MediaSkipBackward, "MediaSkipForward" => NamedKey::MediaSkipForward, "MediaStepBackward" => NamedKey::MediaStepBackward, "MediaStepForward" => NamedKey::MediaStepForward, "MediaTopMenu" => NamedKey::MediaTopMenu, "NavigateIn" => NamedKey::NavigateIn, "NavigateNext" => NamedKey::NavigateNext, "NavigateOut" => NamedKey::NavigateOut, "NavigatePrevious" => NamedKey::NavigatePrevious, "NextFavoriteChannel" => NamedKey::NextFavoriteChannel, "NextUserProfile" => NamedKey::NextUserProfile, "OnDemand" => NamedKey::OnDemand, "Pairing" => NamedKey::Pairing, "PinPDown" => NamedKey::PinPDown, "PinPMove" => NamedKey::PinPMove, "PinPToggle" => NamedKey::PinPToggle, "PinPUp" => NamedKey::PinPUp, "PlaySpeedDown" => NamedKey::PlaySpeedDown, "PlaySpeedReset" => NamedKey::PlaySpeedReset, "PlaySpeedUp" => NamedKey::PlaySpeedUp, "RandomToggle" => NamedKey::RandomToggle, "RcLowBattery" => NamedKey::RcLowBattery, "RecordSpeedNext" => NamedKey::RecordSpeedNext, "RfBypass" => NamedKey::RfBypass, "ScanChannelsToggle" => NamedKey::ScanChannelsToggle, "ScreenModeNext" => NamedKey::ScreenModeNext, "Settings" => NamedKey::Settings, "SplitScreenToggle" => NamedKey::SplitScreenToggle, "STBInput" => NamedKey::STBInput, "STBPower" => NamedKey::STBPower, "Subtitle" => NamedKey::Subtitle, "Teletext" => NamedKey::Teletext, "VideoModeNext" => NamedKey::VideoModeNext, "Wink" => NamedKey::Wink, "ZoomToggle" => NamedKey::ZoomToggle, "F1" => NamedKey::F1, "F2" => NamedKey::F2, "F3" => NamedKey::F3, "F4" => NamedKey::F4, "F5" => NamedKey::F5, "F6" => NamedKey::F6, "F7" => NamedKey::F7, "F8" => NamedKey::F8, "F9" => NamedKey::F9, "F10" => NamedKey::F10, "F11" => NamedKey::F11, "F12" => NamedKey::F12, "F13" => NamedKey::F13, "F14" => NamedKey::F14, "F15" => NamedKey::F15, "F16" => NamedKey::F16, "F17" => NamedKey::F17, "F18" => NamedKey::F18, "F19" => NamedKey::F19, "F20" => NamedKey::F20, "F21" => NamedKey::F21, "F22" => NamedKey::F22, "F23" => NamedKey::F23, "F24" => NamedKey::F24, "F25" => NamedKey::F25, "F26" => NamedKey::F26, "F27" => NamedKey::F27, "F28" => NamedKey::F28, "F29" => NamedKey::F29, "F30" => NamedKey::F30, "F31" => NamedKey::F31, "F32" => NamedKey::F32, "F33" => NamedKey::F33, "F34" => NamedKey::F34, "F35" => NamedKey::F35, string => return Key::Character(SmolStr::new(string)), }) } } impl PhysicalKey { pub fn from_key_code_attribute_value(kcav: &str) -> Self { PhysicalKey::Code(match kcav { "Backquote" => KeyCode::Backquote, "Backslash" => KeyCode::Backslash, "BracketLeft" => KeyCode::BracketLeft, "BracketRight" => KeyCode::BracketRight, "Comma" => KeyCode::Comma, "Digit0" => KeyCode::Digit0, "Digit1" => KeyCode::Digit1, "Digit2" => KeyCode::Digit2, "Digit3" => KeyCode::Digit3, "Digit4" => KeyCode::Digit4, "Digit5" => KeyCode::Digit5, "Digit6" => KeyCode::Digit6, "Digit7" => KeyCode::Digit7, "Digit8" => KeyCode::Digit8, "Digit9" => KeyCode::Digit9, "Equal" => KeyCode::Equal, "IntlBackslash" => KeyCode::IntlBackslash, "IntlRo" => KeyCode::IntlRo, "IntlYen" => KeyCode::IntlYen, "KeyA" => KeyCode::KeyA, "KeyB" => KeyCode::KeyB, "KeyC" => KeyCode::KeyC, "KeyD" => KeyCode::KeyD, "KeyE" => KeyCode::KeyE, "KeyF" => KeyCode::KeyF, "KeyG" => KeyCode::KeyG, "KeyH" => KeyCode::KeyH, "KeyI" => KeyCode::KeyI, "KeyJ" => KeyCode::KeyJ, "KeyK" => KeyCode::KeyK, "KeyL" => KeyCode::KeyL, "KeyM" => KeyCode::KeyM, "KeyN" => KeyCode::KeyN, "KeyO" => KeyCode::KeyO, "KeyP" => KeyCode::KeyP, "KeyQ" => KeyCode::KeyQ, "KeyR" => KeyCode::KeyR, "KeyS" => KeyCode::KeyS, "KeyT" => KeyCode::KeyT, "KeyU" => KeyCode::KeyU, "KeyV" => KeyCode::KeyV, "KeyW" => KeyCode::KeyW, "KeyX" => KeyCode::KeyX, "KeyY" => KeyCode::KeyY, "KeyZ" => KeyCode::KeyZ, "Minus" => KeyCode::Minus, "Period" => KeyCode::Period, "Quote" => KeyCode::Quote, "Semicolon" => KeyCode::Semicolon, "Slash" => KeyCode::Slash, "AltLeft" => KeyCode::AltLeft, "AltRight" => KeyCode::AltRight, "Backspace" => KeyCode::Backspace, "CapsLock" => KeyCode::CapsLock, "ContextMenu" => KeyCode::ContextMenu, "ControlLeft" => KeyCode::ControlLeft, "ControlRight" => KeyCode::ControlRight, "Enter" => KeyCode::Enter, "MetaLeft" => KeyCode::SuperLeft, "MetaRight" => KeyCode::SuperRight, "ShiftLeft" => KeyCode::ShiftLeft, "ShiftRight" => KeyCode::ShiftRight, "Space" => KeyCode::Space, "Tab" => KeyCode::Tab, "Convert" => KeyCode::Convert, "KanaMode" => KeyCode::KanaMode, "Lang1" => KeyCode::Lang1, "Lang2" => KeyCode::Lang2, "Lang3" => KeyCode::Lang3, "Lang4" => KeyCode::Lang4, "Lang5" => KeyCode::Lang5, "NonConvert" => KeyCode::NonConvert, "Delete" => KeyCode::Delete, "End" => KeyCode::End, "Help" => KeyCode::Help, "Home" => KeyCode::Home, "Insert" => KeyCode::Insert, "PageDown" => KeyCode::PageDown, "PageUp" => KeyCode::PageUp, "ArrowDown" => KeyCode::ArrowDown, "ArrowLeft" => KeyCode::ArrowLeft, "ArrowRight" => KeyCode::ArrowRight, "ArrowUp" => KeyCode::ArrowUp, "NumLock" => KeyCode::NumLock, "Numpad0" => KeyCode::Numpad0, "Numpad1" => KeyCode::Numpad1, "Numpad2" => KeyCode::Numpad2, "Numpad3" => KeyCode::Numpad3, "Numpad4" => KeyCode::Numpad4, "Numpad5" => KeyCode::Numpad5, "Numpad6" => KeyCode::Numpad6, "Numpad7" => KeyCode::Numpad7, "Numpad8" => KeyCode::Numpad8, "Numpad9" => KeyCode::Numpad9, "NumpadAdd" => KeyCode::NumpadAdd, "NumpadBackspace" => KeyCode::NumpadBackspace, "NumpadClear" => KeyCode::NumpadClear, "NumpadClearEntry" => KeyCode::NumpadClearEntry, "NumpadComma" => KeyCode::NumpadComma, "NumpadDecimal" => KeyCode::NumpadDecimal, "NumpadDivide" => KeyCode::NumpadDivide, "NumpadEnter" => KeyCode::NumpadEnter, "NumpadEqual" => KeyCode::NumpadEqual, "NumpadHash" => KeyCode::NumpadHash, "NumpadMemoryAdd" => KeyCode::NumpadMemoryAdd, "NumpadMemoryClear" => KeyCode::NumpadMemoryClear, "NumpadMemoryRecall" => KeyCode::NumpadMemoryRecall, "NumpadMemoryStore" => KeyCode::NumpadMemoryStore, "NumpadMemorySubtract" => KeyCode::NumpadMemorySubtract, "NumpadMultiply" => KeyCode::NumpadMultiply, "NumpadParenLeft" => KeyCode::NumpadParenLeft, "NumpadParenRight" => KeyCode::NumpadParenRight, "NumpadStar" => KeyCode::NumpadStar, "NumpadSubtract" => KeyCode::NumpadSubtract, "Escape" => KeyCode::Escape, "Fn" => KeyCode::Fn, "FnLock" => KeyCode::FnLock, "PrintScreen" => KeyCode::PrintScreen, "ScrollLock" => KeyCode::ScrollLock, "Pause" => KeyCode::Pause, "BrowserBack" => KeyCode::BrowserBack, "BrowserFavorites" => KeyCode::BrowserFavorites, "BrowserForward" => KeyCode::BrowserForward, "BrowserHome" => KeyCode::BrowserHome, "BrowserRefresh" => KeyCode::BrowserRefresh, "BrowserSearch" => KeyCode::BrowserSearch, "BrowserStop" => KeyCode::BrowserStop, "Eject" => KeyCode::Eject, "LaunchApp1" => KeyCode::LaunchApp1, "LaunchApp2" => KeyCode::LaunchApp2, "LaunchMail" => KeyCode::LaunchMail, "MediaPlayPause" => KeyCode::MediaPlayPause, "MediaSelect" => KeyCode::MediaSelect, "MediaStop" => KeyCode::MediaStop, "MediaTrackNext" => KeyCode::MediaTrackNext, "MediaTrackPrevious" => KeyCode::MediaTrackPrevious, "Power" => KeyCode::Power, "Sleep" => KeyCode::Sleep, "AudioVolumeDown" => KeyCode::AudioVolumeDown, "AudioVolumeMute" => KeyCode::AudioVolumeMute, "AudioVolumeUp" => KeyCode::AudioVolumeUp, "WakeUp" => KeyCode::WakeUp, "Hyper" => KeyCode::Hyper, "Turbo" => KeyCode::Turbo, "Abort" => KeyCode::Abort, "Resume" => KeyCode::Resume, "Suspend" => KeyCode::Suspend, "Again" => KeyCode::Again, "Copy" => KeyCode::Copy, "Cut" => KeyCode::Cut, "Find" => KeyCode::Find, "Open" => KeyCode::Open, "Paste" => KeyCode::Paste, "Props" => KeyCode::Props, "Select" => KeyCode::Select, "Undo" => KeyCode::Undo, "Hiragana" => KeyCode::Hiragana, "Katakana" => KeyCode::Katakana, "F1" => KeyCode::F1, "F2" => KeyCode::F2, "F3" => KeyCode::F3, "F4" => KeyCode::F4, "F5" => KeyCode::F5, "F6" => KeyCode::F6, "F7" => KeyCode::F7, "F8" => KeyCode::F8, "F9" => KeyCode::F9, "F10" => KeyCode::F10, "F11" => KeyCode::F11, "F12" => KeyCode::F12, "F13" => KeyCode::F13, "F14" => KeyCode::F14, "F15" => KeyCode::F15, "F16" => KeyCode::F16, "F17" => KeyCode::F17, "F18" => KeyCode::F18, "F19" => KeyCode::F19, "F20" => KeyCode::F20, "F21" => KeyCode::F21, "F22" => KeyCode::F22, "F23" => KeyCode::F23, "F24" => KeyCode::F24, "F25" => KeyCode::F25, "F26" => KeyCode::F26, "F27" => KeyCode::F27, "F28" => KeyCode::F28, "F29" => KeyCode::F29, "F30" => KeyCode::F30, "F31" => KeyCode::F31, "F32" => KeyCode::F32, "F33" => KeyCode::F33, "F34" => KeyCode::F34, "F35" => KeyCode::F35, _ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified), }) } } winit-0.30.9/src/platform_impl/web/main_thread.rs000064400000000000000000000050431046102023000201030ustar 00000000000000use std::fmt::{self, Debug, Formatter}; use std::marker::PhantomData; use std::mem; use std::sync::OnceLock; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use super::r#async::{self, Sender}; thread_local! { static MAIN_THREAD: bool = { #[wasm_bindgen] extern "C" { #[derive(Clone)] type Global; #[wasm_bindgen(method, getter, js_name = Window)] fn window(this: &Global) -> JsValue; } let global: Global = js_sys::global().unchecked_into(); !global.window().is_undefined() }; } #[derive(Clone, Copy, Debug)] pub struct MainThreadMarker(PhantomData<*const ()>); impl MainThreadMarker { pub fn new() -> Option { MAIN_THREAD.with(|is| is.then_some(Self(PhantomData))) } } pub struct MainThreadSafe(Option); impl MainThreadSafe { pub fn new(_: MainThreadMarker, value: T) -> Self { DROP_HANDLER.get_or_init(|| { let (sender, receiver) = r#async::channel(); wasm_bindgen_futures::spawn_local( async move { while receiver.next().await.is_ok() {} }, ); sender }); Self(Some(value)) } pub fn into_inner(mut self, _: MainThreadMarker) -> T { self.0.take().expect("already taken or dropped") } pub fn get(&self, _: MainThreadMarker) -> &T { self.0.as_ref().expect("already taken or dropped") } } impl Debug for MainThreadSafe { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if MainThreadMarker::new().is_some() { f.debug_tuple("MainThreadSafe").field(&self.0).finish() } else { f.debug_struct("MainThreadSafe").finish_non_exhaustive() } } } impl Drop for MainThreadSafe { fn drop(&mut self) { if let Some(value) = self.0.take() { if mem::needs_drop::() && MainThreadMarker::new().is_none() { DROP_HANDLER .get() .expect("drop handler not initialized when setting canvas") .send(DropBox(Box::new(value))) .expect("sender dropped in main thread") } } } } unsafe impl Send for MainThreadSafe {} unsafe impl Sync for MainThreadSafe {} static DROP_HANDLER: OnceLock> = OnceLock::new(); struct DropBox(#[allow(dead_code)] Box); unsafe impl Send for DropBox {} unsafe impl Sync for DropBox {} trait Any {} impl Any for T {} winit-0.30.9/src/platform_impl/web/mod.rs000064400000000000000000000042661046102023000164150ustar 00000000000000// Brief introduction to the internals of the web backend: // The web backend used to support both wasm-bindgen and stdweb as methods of binding to the // environment. Because they are both supporting the same underlying APIs, the actual web bindings // are cordoned off into backend abstractions, which present the thinnest unifying layer possible. // // When adding support for new events or interactions with the browser, first consult trusted // documentation (such as MDN) to ensure it is well-standardised and supported across many browsers. // Once you have decided on the relevant web APIs, add support to both backends. // // The backend is used by the rest of the module to implement Winit's business logic, which forms // the rest of the code. 'device', 'error', 'monitor', and 'window' define web-specific structures // for winit's cross-platform structures. They are all relatively simple translations. // // The event_loop module handles listening for and processing events. 'Proxy' implements // EventLoopProxy and 'WindowTarget' implements ActiveEventLoop. WindowTarget also handles // registering the event handlers. The 'Execution' struct in the 'runner' module handles taking // incoming events (from the registered handlers) and ensuring they are passed to the user in a // compliant way. // TODO: FP, remove when is fixed. #![allow(clippy::empty_docs)] mod r#async; mod cursor; mod device; mod error; mod event_loop; mod keyboard; mod main_thread; mod monitor; mod web_sys; mod window; pub use self::device::DeviceId; pub use self::error::OsError; pub(crate) use self::event_loop::{ ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle, PlatformSpecificEventLoopAttributes, }; pub use self::monitor::{MonitorHandle, VideoModeHandle}; pub use self::window::{PlatformSpecificWindowAttributes, Window, WindowId}; pub(crate) use self::keyboard::KeyEventExtra; use self::web_sys as backend; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use cursor::{ CustomCursor as PlatformCustomCursor, CustomCursorFuture, CustomCursorSource as PlatformCustomCursorSource, }; winit-0.30.9/src/platform_impl/web/monitor.rs000064400000000000000000000020611046102023000173140ustar 00000000000000use std::iter::Empty; use crate::dpi::{PhysicalPosition, PhysicalSize}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MonitorHandle; impl MonitorHandle { pub fn scale_factor(&self) -> f64 { unreachable!() } pub fn position(&self) -> PhysicalPosition { unreachable!() } pub fn name(&self) -> Option { unreachable!() } pub fn refresh_rate_millihertz(&self) -> Option { unreachable!() } pub fn size(&self) -> PhysicalSize { unreachable!() } pub fn video_modes(&self) -> Empty { unreachable!() } } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct VideoModeHandle; impl VideoModeHandle { pub fn size(&self) -> PhysicalSize { unreachable!(); } pub fn bit_depth(&self) -> u16 { unreachable!(); } pub fn refresh_rate_millihertz(&self) -> u32 { unreachable!(); } pub fn monitor(&self) -> MonitorHandle { unreachable!(); } } winit-0.30.9/src/platform_impl/web/web_sys/animation_frame.rs000064400000000000000000000031501046102023000224310ustar 00000000000000use std::cell::Cell; use std::rc::Rc; use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; pub struct AnimationFrameHandler { window: web_sys::Window, closure: Closure, handle: Rc>>, } impl AnimationFrameHandler { pub fn new(window: web_sys::Window) -> Self { let handle = Rc::new(Cell::new(None)); let closure = Closure::new({ let handle = handle.clone(); move || handle.set(None) }); Self { window, closure, handle } } pub fn on_animation_frame(&mut self, mut f: F) where F: 'static + FnMut(), { let handle = self.handle.clone(); self.closure = Closure::new(move || { handle.set(None); f(); }) } pub fn request(&self) { if let Some(handle) = self.handle.take() { self.window.cancel_animation_frame(handle).expect("Failed to cancel animation frame"); } let handle = self .window .request_animation_frame(self.closure.as_ref().unchecked_ref()) .expect("Failed to request animation frame"); self.handle.set(Some(handle)); } pub fn cancel(&mut self) { if let Some(handle) = self.handle.take() { self.window.cancel_animation_frame(handle).expect("Failed to cancel animation frame"); } } } impl Drop for AnimationFrameHandler { fn drop(&mut self) { if let Some(handle) = self.handle.take() { self.window.cancel_animation_frame(handle).expect("Failed to cancel animation frame"); } } } winit-0.30.9/src/platform_impl/web/web_sys/canvas.rs000064400000000000000000000456621046102023000205710ustar 00000000000000use std::cell::Cell; use std::ops::Deref; use std::rc::Rc; use std::sync::{Arc, Mutex}; use smol_str::SmolStr; use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; use web_sys::{ CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent, }; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{Force, InnerSizeWriter, MouseButton, MouseScrollDelta}; use crate::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey}; use crate::platform_impl::OsError; use crate::window::{WindowAttributes, WindowId as RootWindowId}; use super::super::cursor::CursorHandler; use super::super::main_thread::MainThreadMarker; use super::super::WindowId; use super::animation_frame::AnimationFrameHandler; use super::event_handle::EventListenerHandle; use super::intersection_handle::IntersectionObserverHandle; use super::media_query_handle::MediaQueryListHandle; use super::pointer::PointerHandler; use super::{event, fullscreen, ButtonsState, ResizeScaleHandle}; #[allow(dead_code)] pub struct Canvas { common: Common, id: WindowId, pub has_focus: Rc>, pub prevent_default: Rc>, pub is_intersecting: Option, on_touch_start: Option>, on_focus: Option>, on_blur: Option>, on_keyboard_release: Option>, on_keyboard_press: Option>, on_mouse_wheel: Option>, on_dark_mode: Option, pointer_handler: PointerHandler, on_resize_scale: Option, on_intersect: Option, animation_frame_handler: AnimationFrameHandler, on_touch_end: Option>, on_context_menu: Option>, pub cursor: CursorHandler, } pub struct Common { pub window: web_sys::Window, pub document: Document, /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure /// the DPI factor is maintained. Note: this is read-only because we use a pointer to this /// for [`WindowHandle`][rwh_06::WindowHandle]. raw: Rc, style: Style, old_size: Rc>>, current_size: Rc>>, } #[derive(Clone, Debug)] pub struct Style { read: CssStyleDeclaration, write: CssStyleDeclaration, } impl Canvas { pub(crate) fn create( main_thread: MainThreadMarker, id: WindowId, window: web_sys::Window, document: Document, attr: &mut WindowAttributes, ) -> Result { let canvas = match attr.platform_specific.canvas.take().map(|canvas| { Arc::try_unwrap(canvas) .map(|canvas| canvas.into_inner(main_thread)) .unwrap_or_else(|canvas| canvas.get(main_thread).clone()) }) { Some(canvas) => canvas, None => document .create_element("canvas") .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? .unchecked_into(), }; if attr.platform_specific.append && !document.contains(Some(&canvas)) { document .body() .expect("Failed to get body from document") .append_child(&canvas) .expect("Failed to append canvas to body"); } // A tabindex is needed in order to capture local keyboard events. // A "0" value means that the element should be focusable in // sequential keyboard navigation, but its order is defined by the // document's source order. // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex if attr.platform_specific.focusable { canvas .set_attribute("tabindex", "0") .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; } let style = Style::new(&window, &canvas); let cursor = CursorHandler::new(main_thread, canvas.clone(), style.clone()); let common = Common { window: window.clone(), document: document.clone(), raw: Rc::new(canvas.clone()), style, old_size: Rc::default(), current_size: Rc::default(), }; if let Some(size) = attr.inner_size { let size = size.to_logical(super::scale_factor(&common.window)); super::set_canvas_size(&common.document, &common.raw, &common.style, size); } if let Some(size) = attr.min_inner_size { let size = size.to_logical(super::scale_factor(&common.window)); super::set_canvas_min_size(&common.document, &common.raw, &common.style, Some(size)); } if let Some(size) = attr.max_inner_size { let size = size.to_logical(super::scale_factor(&common.window)); super::set_canvas_max_size(&common.document, &common.raw, &common.style, Some(size)); } if let Some(position) = attr.position { let position = position.to_logical(super::scale_factor(&common.window)); super::set_canvas_position(&common.document, &common.raw, &common.style, position); } if attr.fullscreen.is_some() { fullscreen::request_fullscreen(&document, &canvas); } if attr.active { let _ = common.raw.focus(); } Ok(Canvas { common, id, has_focus: Rc::new(Cell::new(false)), prevent_default: Rc::new(Cell::new(attr.platform_specific.prevent_default)), is_intersecting: None, on_touch_start: None, on_blur: None, on_focus: None, on_keyboard_release: None, on_keyboard_press: None, on_mouse_wheel: None, on_dark_mode: None, pointer_handler: PointerHandler::new(), on_resize_scale: None, on_intersect: None, animation_frame_handler: AnimationFrameHandler::new(window), on_touch_end: None, on_context_menu: None, cursor, }) } pub fn set_cursor_lock(&self, lock: bool) -> Result<(), RootOE> { if lock { self.raw().request_pointer_lock(); } else { self.common.document.exit_pointer_lock(); } Ok(()) } pub fn set_attribute(&self, attribute: &str, value: &str) { self.common .raw .set_attribute(attribute, value) .unwrap_or_else(|err| panic!("error: {err:?}\nSet attribute: {attribute}")) } pub fn position(&self) -> LogicalPosition { let bounds = self.common.raw.get_bounding_client_rect(); let mut position = LogicalPosition { x: bounds.x(), y: bounds.y() }; if self.document().contains(Some(self.raw())) && self.style().get("display") != "none" { position.x += super::style_size_property(self.style(), "border-left-width") + super::style_size_property(self.style(), "padding-left"); position.y += super::style_size_property(self.style(), "border-top-width") + super::style_size_property(self.style(), "padding-top"); } position } #[inline] pub fn old_size(&self) -> PhysicalSize { self.common.old_size.get() } #[inline] pub fn inner_size(&self) -> PhysicalSize { self.common.current_size.get() } #[inline] pub fn set_old_size(&self, size: PhysicalSize) { self.common.old_size.set(size) } #[inline] pub fn set_current_size(&self, size: PhysicalSize) { self.common.current_size.set(size) } #[inline] pub fn window(&self) -> &web_sys::Window { &self.common.window } #[inline] pub fn document(&self) -> &Document { &self.common.document } #[inline] pub fn raw(&self) -> &HtmlCanvasElement { &self.common.raw } #[inline] pub fn style(&self) -> &Style { &self.common.style } pub fn on_touch_start(&mut self) { let prevent_default = Rc::clone(&self.prevent_default); self.on_touch_start = Some(self.common.add_event("touchstart", move |event: Event| { if prevent_default.get() { event.prevent_default(); } })); } pub fn on_blur(&mut self, mut handler: F) where F: 'static + FnMut(), { self.on_blur = Some(self.common.add_event("blur", move |_: FocusEvent| { handler(); })); } pub fn on_focus(&mut self, mut handler: F) where F: 'static + FnMut(), { self.on_focus = Some(self.common.add_event("focus", move |_: FocusEvent| { handler(); })); } pub fn on_keyboard_release(&mut self, mut handler: F) where F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, ModifiersState), { let prevent_default = Rc::clone(&self.prevent_default); self.on_keyboard_release = Some(self.common.add_event("keyup", move |event: KeyboardEvent| { if prevent_default.get() { event.prevent_default(); } let key = event::key(&event); let modifiers = event::keyboard_modifiers(&event); handler( event::key_code(&event), key, event::key_text(&event), event::key_location(&event), event.repeat(), modifiers, ); })); } pub fn on_keyboard_press(&mut self, mut handler: F) where F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, ModifiersState), { let prevent_default = Rc::clone(&self.prevent_default); self.on_keyboard_press = Some(self.common.add_event("keydown", move |event: KeyboardEvent| { if prevent_default.get() { event.prevent_default(); } let key = event::key(&event); let modifiers = event::keyboard_modifiers(&event); handler( event::key_code(&event), key, event::key_text(&event), event::key_location(&event), event.repeat(), modifiers, ); })); } pub fn on_cursor_leave(&mut self, handler: F) where F: 'static + FnMut(ModifiersState, Option), { self.pointer_handler.on_cursor_leave(&self.common, handler) } pub fn on_cursor_enter(&mut self, handler: F) where F: 'static + FnMut(ModifiersState, Option), { self.pointer_handler.on_cursor_enter(&self.common, handler) } pub fn on_mouse_release(&mut self, mouse_handler: M, touch_handler: T) where M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { self.pointer_handler.on_mouse_release(&self.common, mouse_handler, touch_handler) } pub fn on_mouse_press(&mut self, mouse_handler: M, touch_handler: T) where M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { self.pointer_handler.on_mouse_press( &self.common, mouse_handler, touch_handler, Rc::clone(&self.prevent_default), ) } pub fn on_cursor_move(&mut self, mouse_handler: M, touch_handler: T, button_handler: B) where M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), B: 'static + FnMut(ModifiersState, i32, PhysicalPosition, ButtonsState, MouseButton), { self.pointer_handler.on_cursor_move( &self.common, mouse_handler, touch_handler, button_handler, Rc::clone(&self.prevent_default), ) } pub fn on_touch_cancel(&mut self, handler: F) where F: 'static + FnMut(i32, PhysicalPosition, Force), { self.pointer_handler.on_touch_cancel(&self.common, handler) } pub fn on_mouse_wheel(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { let window = self.common.window.clone(); let prevent_default = Rc::clone(&self.prevent_default); self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { if prevent_default.get() { event.prevent_default(); } if let Some(delta) = event::mouse_scroll_delta(&window, &event) { let modifiers = event::mouse_modifiers(&event); handler(0, delta, modifiers); } })); } pub fn on_dark_mode(&mut self, mut handler: F) where F: 'static + FnMut(bool), { self.on_dark_mode = Some(MediaQueryListHandle::new( &self.common.window, "(prefers-color-scheme: dark)", move |mql| handler(mql.matches()), )); } pub(crate) fn on_resize_scale(&mut self, scale_handler: S, size_handler: R) where S: 'static + Fn(PhysicalSize, f64), R: 'static + Fn(PhysicalSize), { self.on_resize_scale = Some(ResizeScaleHandle::new( self.window().clone(), self.document().clone(), self.raw().clone(), self.style().clone(), scale_handler, size_handler, )); } pub(crate) fn on_intersection(&mut self, handler: F) where F: 'static + FnMut(bool), { self.on_intersect = Some(IntersectionObserverHandle::new(self.raw(), handler)); } pub(crate) fn on_animation_frame(&mut self, f: F) where F: 'static + FnMut(), { self.animation_frame_handler.on_animation_frame(f) } pub(crate) fn on_context_menu(&mut self) { let prevent_default = Rc::clone(&self.prevent_default); self.on_context_menu = Some(self.common.add_event("contextmenu", move |event: PointerEvent| { if prevent_default.get() { event.prevent_default(); } })); } pub fn request_fullscreen(&self) { fullscreen::request_fullscreen(self.document(), self.raw()); } pub fn exit_fullscreen(&self) { fullscreen::exit_fullscreen(self.document(), self.raw()); } pub fn is_fullscreen(&self) -> bool { fullscreen::is_fullscreen(self.document(), self.raw()) } pub fn request_animation_frame(&self) { self.animation_frame_handler.request(); } pub(crate) fn handle_scale_change( &self, runner: &super::super::event_loop::runner::Shared, event_handler: impl FnOnce(crate::event::Event<()>), current_size: PhysicalSize, scale: f64, ) { // First, we send the `ScaleFactorChanged` event: self.set_current_size(current_size); let new_size = { let new_size = Arc::new(Mutex::new(current_size)); event_handler(crate::event::Event::WindowEvent { window_id: RootWindowId(self.id), event: crate::event::WindowEvent::ScaleFactorChanged { scale_factor: scale, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_size)), }, }); let new_size = *new_size.lock().unwrap(); new_size }; if current_size != new_size { // Then we resize the canvas to the new size, a new // `Resized` event will be sent by the `ResizeObserver`: let new_size = new_size.to_logical(scale); super::set_canvas_size(self.document(), self.raw(), self.style(), new_size); // Set the size might not trigger the event because the calculation is inaccurate. self.on_resize_scale .as_ref() .expect("expected Window to still be active") .notify_resize(); } else if self.old_size() != new_size { // Then we at least send a resized event. self.set_old_size(new_size); runner.send_event(crate::event::Event::WindowEvent { window_id: RootWindowId(self.id), event: crate::event::WindowEvent::Resized(new_size), }) } } pub fn remove_listeners(&mut self) { self.on_touch_start = None; self.on_focus = None; self.on_blur = None; self.on_keyboard_release = None; self.on_keyboard_press = None; self.on_mouse_wheel = None; self.on_dark_mode = None; self.pointer_handler.remove_listeners(); self.on_resize_scale = None; self.on_intersect = None; self.animation_frame_handler.cancel(); self.on_touch_end = None; self.on_context_menu = None; } } impl Common { pub fn add_event( &self, event_name: &'static str, handler: F, ) -> EventListenerHandle where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, F: 'static + FnMut(E), { EventListenerHandle::new(self.raw.deref().clone(), event_name, Closure::new(handler)) } pub fn raw(&self) -> &HtmlCanvasElement { &self.raw } } impl Style { fn new(window: &web_sys::Window, canvas: &HtmlCanvasElement) -> Self { #[allow(clippy::disallowed_methods)] let read = window .get_computed_style(canvas) .expect("Failed to obtain computed style") // this can't fail: we aren't using a pseudo-element .expect("Invalid pseudo-element"); #[allow(clippy::disallowed_methods)] let write = canvas.style(); Self { read, write } } pub(crate) fn get(&self, property: &str) -> String { self.read.get_property_value(property).expect("Invalid property") } pub(crate) fn remove(&self, property: &str) { self.write.remove_property(property).expect("Property is read only"); } pub(crate) fn set(&self, property: &str, value: &str) { self.write.set_property(property, value).expect("Property is read only"); } } winit-0.30.9/src/platform_impl/web/web_sys/event.rs000064400000000000000000000212161046102023000204240ustar 00000000000000use crate::event::{MouseButton, MouseScrollDelta}; use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey}; use dpi::{LogicalPosition, PhysicalPosition, Position}; use smol_str::SmolStr; use std::cell::OnceCell; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent}; use super::Engine; bitflags::bitflags! { // https://www.w3.org/TR/pointerevents3/#the-buttons-property #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ButtonsState: u16 { const LEFT = 0b00001; const RIGHT = 0b00010; const MIDDLE = 0b00100; const BACK = 0b01000; const FORWARD = 0b10000; } } impl From for MouseButton { fn from(value: ButtonsState) -> Self { match value { ButtonsState::LEFT => MouseButton::Left, ButtonsState::RIGHT => MouseButton::Right, ButtonsState::MIDDLE => MouseButton::Middle, ButtonsState::BACK => MouseButton::Back, ButtonsState::FORWARD => MouseButton::Forward, _ => MouseButton::Other(value.bits()), } } } impl From for ButtonsState { fn from(value: MouseButton) -> Self { match value { MouseButton::Left => ButtonsState::LEFT, MouseButton::Right => ButtonsState::RIGHT, MouseButton::Middle => ButtonsState::MIDDLE, MouseButton::Back => ButtonsState::BACK, MouseButton::Forward => ButtonsState::FORWARD, MouseButton::Other(value) => ButtonsState::from_bits_retain(value), } } } pub fn mouse_buttons(event: &MouseEvent) -> ButtonsState { ButtonsState::from_bits_retain(event.buttons()) } pub fn mouse_button(event: &MouseEvent) -> Option { // https://www.w3.org/TR/pointerevents3/#the-button-property match event.button() { -1 => None, 0 => Some(MouseButton::Left), 1 => Some(MouseButton::Middle), 2 => Some(MouseButton::Right), 3 => Some(MouseButton::Back), 4 => Some(MouseButton::Forward), i => { Some(MouseButton::Other(i.try_into().expect("unexpected negative mouse button value"))) }, } } impl MouseButton { pub fn to_id(self) -> u32 { match self { MouseButton::Left => 0, MouseButton::Right => 1, MouseButton::Middle => 2, MouseButton::Back => 3, MouseButton::Forward => 4, MouseButton::Other(value) => value.into(), } } } pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { #[wasm_bindgen] extern "C" { type MouseEventExt; #[wasm_bindgen(method, getter, js_name = offsetX)] fn offset_x(this: &MouseEventExt) -> f64; #[wasm_bindgen(method, getter, js_name = offsetY)] fn offset_y(this: &MouseEventExt) -> f64; } let event: &MouseEventExt = event.unchecked_ref(); LogicalPosition { x: event.offset_x(), y: event.offset_y() } } // TODO: Remove this when Firefox supports correct movement values in coalesced events and browsers // have agreed on what coordinate space `movementX/Y` is using. // See . // See . pub enum MouseDelta { Chromium, Gecko { old_position: LogicalPosition, old_delta: LogicalPosition }, Other, } impl MouseDelta { pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self { match super::engine(window) { Some(Engine::Chromium) => Self::Chromium, // Firefox has wrong movement values in coalesced events. Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko { old_position: mouse_position(event), old_delta: LogicalPosition::new( event.movement_x() as f64, event.movement_y() as f64, ), }, _ => Self::Other, } } pub fn delta(&mut self, event: &MouseEvent) -> Position { match self { MouseDelta::Chromium => { PhysicalPosition::new(event.movement_x(), event.movement_y()).into() }, MouseDelta::Gecko { old_position, old_delta } => { let new_position = mouse_position(event); let x = new_position.x - old_position.x + old_delta.x; let y = new_position.y - old_position.y + old_delta.y; *old_position = new_position; *old_delta = LogicalPosition::new(0., 0.); LogicalPosition::new(x, y).into() }, MouseDelta::Other => { LogicalPosition::new(event.movement_x(), event.movement_y()).into() }, } } } pub fn mouse_scroll_delta( window: &web_sys::Window, event: &WheelEvent, ) -> Option { let x = -event.delta_x(); let y = -event.delta_y(); match event.delta_mode() { WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), WheelEvent::DOM_DELTA_PIXEL => { let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor(window)); Some(MouseScrollDelta::PixelDelta(delta)) }, _ => None, } } pub fn key_code(event: &KeyboardEvent) -> PhysicalKey { let code = event.code(); PhysicalKey::from_key_code_attribute_value(&code) } pub fn key(event: &KeyboardEvent) -> Key { Key::from_key_attribute_value(&event.key()) } pub fn key_text(event: &KeyboardEvent) -> Option { let key = event.key(); let key = Key::from_key_attribute_value(&key); match &key { Key::Character(text) => Some(text.clone()), Key::Named(NamedKey::Tab) => Some(SmolStr::new("\t")), Key::Named(NamedKey::Enter) => Some(SmolStr::new("\r")), Key::Named(NamedKey::Space) => Some(SmolStr::new(" ")), _ => None, } .map(SmolStr::new) } pub fn key_location(event: &KeyboardEvent) -> KeyLocation { match event.location() { KeyboardEvent::DOM_KEY_LOCATION_LEFT => KeyLocation::Left, KeyboardEvent::DOM_KEY_LOCATION_RIGHT => KeyLocation::Right, KeyboardEvent::DOM_KEY_LOCATION_NUMPAD => KeyLocation::Numpad, KeyboardEvent::DOM_KEY_LOCATION_STANDARD => KeyLocation::Standard, location => { tracing::warn!("Unexpected key location: {location}"); KeyLocation::Standard }, } } pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState { let mut state = ModifiersState::empty(); if event.shift_key() { state |= ModifiersState::SHIFT; } if event.ctrl_key() { state |= ModifiersState::CONTROL; } if event.alt_key() { state |= ModifiersState::ALT; } if event.meta_key() { state |= ModifiersState::SUPER; } state } pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState { let mut state = ModifiersState::empty(); if event.shift_key() { state |= ModifiersState::SHIFT; } if event.ctrl_key() { state |= ModifiersState::CONTROL; } if event.alt_key() { state |= ModifiersState::ALT; } if event.meta_key() { state |= ModifiersState::SUPER; } state } pub fn pointer_move_event(event: PointerEvent) -> impl Iterator { // make a single iterator depending on the availability of coalesced events if has_coalesced_events_support(&event) { None.into_iter().chain( Some(event.get_coalesced_events().into_iter().map(PointerEvent::unchecked_from_js)) .into_iter() .flatten(), ) } else { Some(event).into_iter().chain(None.into_iter().flatten()) } } // TODO: Remove when Safari supports `getCoalescedEvents`. // See . pub fn has_coalesced_events_support(event: &PointerEvent) -> bool { thread_local! { static COALESCED_EVENTS_SUPPORT: OnceCell = const { OnceCell::new() }; } COALESCED_EVENTS_SUPPORT.with(|support| { *support.get_or_init(|| { #[wasm_bindgen] extern "C" { type PointerCoalescedEventsSupport; #[wasm_bindgen(method, getter, js_name = getCoalescedEvents)] fn has_get_coalesced_events(this: &PointerCoalescedEventsSupport) -> JsValue; } let support: &PointerCoalescedEventsSupport = event.unchecked_ref(); !support.has_get_coalesced_events().is_undefined() }) }) } winit-0.30.9/src/platform_impl/web/web_sys/event_handle.rs000064400000000000000000000021611046102023000217350ustar 00000000000000use wasm_bindgen::prelude::Closure; use wasm_bindgen::JsCast; use web_sys::EventTarget; pub struct EventListenerHandle { target: EventTarget, event_type: &'static str, listener: Closure, } impl EventListenerHandle { pub fn new(target: U, event_type: &'static str, listener: Closure) -> Self where U: Into, { let target = target.into(); target .add_event_listener_with_callback(event_type, listener.as_ref().unchecked_ref()) .expect("Failed to add event listener"); EventListenerHandle { target, event_type, listener } } } impl Drop for EventListenerHandle { fn drop(&mut self) { self.target .remove_event_listener_with_callback( self.event_type, self.listener.as_ref().unchecked_ref(), ) .unwrap_or_else(|e| { web_sys::console::error_2( &format!("Error removing event listener {}", self.event_type).into(), &e, ) }); } } winit-0.30.9/src/platform_impl/web/web_sys/fullscreen.rs000064400000000000000000000060771046102023000214550ustar 00000000000000use std::cell::OnceCell; use js_sys::Promise; use wasm_bindgen::closure::Closure; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{Document, Element, HtmlCanvasElement}; pub fn request_fullscreen(document: &Document, canvas: &HtmlCanvasElement) { if is_fullscreen(document, canvas) { return; } #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = HtmlCanvasElement)] type RequestFullscreen; #[wasm_bindgen(method, js_name = requestFullscreen)] fn request_fullscreen(this: &RequestFullscreen) -> Promise; #[wasm_bindgen(method, js_name = webkitRequestFullscreen)] fn webkit_request_fullscreen(this: &RequestFullscreen); } let canvas: &RequestFullscreen = canvas.unchecked_ref(); if has_fullscreen_api_support(canvas) { thread_local! { static REJECT_HANDLER: Closure = Closure::new(|_| ()); } REJECT_HANDLER.with(|handler| { let _ = canvas.request_fullscreen().catch(handler); }); } else { canvas.webkit_request_fullscreen(); } } pub fn is_fullscreen(document: &Document, canvas: &HtmlCanvasElement) -> bool { #[wasm_bindgen] extern "C" { type FullscreenElement; #[wasm_bindgen(method, getter, js_name = webkitFullscreenElement)] fn webkit_fullscreen_element(this: &FullscreenElement) -> Option; } let element = if has_fullscreen_api_support(canvas) { #[allow(clippy::disallowed_methods)] document.fullscreen_element() } else { let document: &FullscreenElement = document.unchecked_ref(); document.webkit_fullscreen_element() }; match element { Some(element) => { let canvas: &Element = canvas; canvas == &element }, None => false, } } pub fn exit_fullscreen(document: &Document, canvas: &HtmlCanvasElement) { #[wasm_bindgen] extern "C" { type ExitFullscreen; #[wasm_bindgen(method, js_name = webkitExitFullscreen)] fn webkit_exit_fullscreen(this: &ExitFullscreen); } if has_fullscreen_api_support(canvas) { #[allow(clippy::disallowed_methods)] document.exit_fullscreen() } else { let document: &ExitFullscreen = document.unchecked_ref(); document.webkit_exit_fullscreen() } } fn has_fullscreen_api_support(canvas: &HtmlCanvasElement) -> bool { thread_local! { static FULLSCREEN_API_SUPPORT: OnceCell = const { OnceCell::new() }; } FULLSCREEN_API_SUPPORT.with(|support| { *support.get_or_init(|| { #[wasm_bindgen] extern "C" { type CanvasFullScreenApiSupport; #[wasm_bindgen(method, getter, js_name = requestFullscreen)] fn has_request_fullscreen(this: &CanvasFullScreenApiSupport) -> JsValue; } let support: &CanvasFullScreenApiSupport = canvas.unchecked_ref(); !support.has_request_fullscreen().is_undefined() }) }) } winit-0.30.9/src/platform_impl/web/web_sys/intersection_handle.rs000064400000000000000000000017521046102023000233270ustar 00000000000000use js_sys::Array; use wasm_bindgen::prelude::Closure; use wasm_bindgen::JsCast; use web_sys::{Element, IntersectionObserver, IntersectionObserverEntry}; pub(super) struct IntersectionObserverHandle { observer: IntersectionObserver, _closure: Closure, } impl IntersectionObserverHandle { pub fn new(element: &Element, mut callback: F) -> Self where F: 'static + FnMut(bool), { let closure = Closure::new(move |entries: Array| { let entry: IntersectionObserverEntry = entries.get(0).unchecked_into(); callback(entry.is_intersecting()); }); let observer = IntersectionObserver::new(closure.as_ref().unchecked_ref()) // we don't provide any `options` .expect("Invalid `options`"); observer.observe(element); Self { observer, _closure: closure } } } impl Drop for IntersectionObserverHandle { fn drop(&mut self) { self.observer.disconnect() } } winit-0.30.9/src/platform_impl/web/web_sys/media_query_handle.rs000064400000000000000000000027011046102023000231200ustar 00000000000000use wasm_bindgen::prelude::Closure; use wasm_bindgen::JsCast; use web_sys::MediaQueryList; pub(super) struct MediaQueryListHandle { mql: MediaQueryList, closure: Closure, } impl MediaQueryListHandle { pub fn new(window: &web_sys::Window, media_query: &str, mut listener: F) -> Self where F: 'static + FnMut(&MediaQueryList), { let mql = window .match_media(media_query) .expect("Failed to parse media query") .expect("Found empty media query"); let closure = Closure::new({ let mql = mql.clone(); move || listener(&mql) }); // TODO: Replace obsolete `addListener()` with `addEventListener()` and use // `MediaQueryListEvent` instead of cloning the `MediaQueryList`. // Requires Safari v14. mql.add_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref())) .expect("Invalid listener"); Self { mql, closure } } pub fn mql(&self) -> &MediaQueryList { &self.mql } } impl Drop for MediaQueryListHandle { fn drop(&mut self) { remove_listener(&self.mql, &self.closure); } } fn remove_listener(mql: &MediaQueryList, listener: &Closure) { mql.remove_listener_with_opt_callback(Some(listener.as_ref().unchecked_ref())).unwrap_or_else( |e| web_sys::console::error_2(&"Error removing media query listener".into(), &e), ); } winit-0.30.9/src/platform_impl/web/web_sys/mod.rs000064400000000000000000000162311046102023000200630ustar 00000000000000mod animation_frame; mod canvas; pub mod event; mod event_handle; mod fullscreen; mod intersection_handle; mod media_query_handle; mod pointer; mod resize_scaling; mod schedule; use std::sync::OnceLock; pub use self::canvas::{Canvas, Style}; pub use self::event::ButtonsState; pub use self::event_handle::EventListenerHandle; pub use self::resize_scaling::ResizeScaleHandle; pub use self::schedule::Schedule; use crate::dpi::{LogicalPosition, LogicalSize}; use js_sys::Array; use wasm_bindgen::closure::Closure; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsCast; use web_sys::{ Document, HtmlCanvasElement, Navigator, PageTransitionEvent, VisibilityState, Window, }; pub fn throw(msg: &str) { wasm_bindgen::throw_str(msg); } pub struct PageTransitionEventHandle { _show_listener: event_handle::EventListenerHandle, _hide_listener: event_handle::EventListenerHandle, } pub fn on_page_transition( window: web_sys::Window, show_handler: impl FnMut(PageTransitionEvent) + 'static, hide_handler: impl FnMut(PageTransitionEvent) + 'static, ) -> PageTransitionEventHandle { let show_closure = Closure::new(show_handler); let hide_closure = Closure::new(hide_handler); let show_listener = event_handle::EventListenerHandle::new(window.clone(), "pageshow", show_closure); let hide_listener = event_handle::EventListenerHandle::new(window, "pagehide", hide_closure); PageTransitionEventHandle { _show_listener: show_listener, _hide_listener: hide_listener } } pub fn scale_factor(window: &web_sys::Window) -> f64 { window.device_pixel_ratio() } fn fix_canvas_size(style: &Style, mut size: LogicalSize) -> LogicalSize { if style.get("box-sizing") == "border-box" { size.width += style_size_property(style, "border-left-width") + style_size_property(style, "border-right-width") + style_size_property(style, "padding-left") + style_size_property(style, "padding-right"); size.height += style_size_property(style, "border-top-width") + style_size_property(style, "border-bottom-width") + style_size_property(style, "padding-top") + style_size_property(style, "padding-bottom"); } size } pub fn set_canvas_size( document: &Document, raw: &HtmlCanvasElement, style: &Style, new_size: LogicalSize, ) { if !document.contains(Some(raw)) || style.get("display") == "none" { return; } let new_size = fix_canvas_size(style, new_size); style.set("width", &format!("{}px", new_size.width)); style.set("height", &format!("{}px", new_size.height)); } pub fn set_canvas_min_size( document: &Document, raw: &HtmlCanvasElement, style: &Style, dimensions: Option>, ) { if let Some(dimensions) = dimensions { if !document.contains(Some(raw)) || style.get("display") == "none" { return; } let new_size = fix_canvas_size(style, dimensions); style.set("min-width", &format!("{}px", new_size.width)); style.set("min-height", &format!("{}px", new_size.height)); } else { style.remove("min-width"); style.remove("min-height"); } } pub fn set_canvas_max_size( document: &Document, raw: &HtmlCanvasElement, style: &Style, dimensions: Option>, ) { if let Some(dimensions) = dimensions { if !document.contains(Some(raw)) || style.get("display") == "none" { return; } let new_size = fix_canvas_size(style, dimensions); style.set("max-width", &format!("{}px", new_size.width)); style.set("max-height", &format!("{}px", new_size.height)); } else { style.remove("max-width"); style.remove("max-height"); } } pub fn set_canvas_position( document: &Document, raw: &HtmlCanvasElement, style: &Style, mut position: LogicalPosition, ) { if document.contains(Some(raw)) && style.get("display") != "none" { position.x -= style_size_property(style, "margin-left") + style_size_property(style, "border-left-width") + style_size_property(style, "padding-left"); position.y -= style_size_property(style, "margin-top") + style_size_property(style, "border-top-width") + style_size_property(style, "padding-top"); } style.set("position", "fixed"); style.set("left", &format!("{}px", position.x)); style.set("top", &format!("{}px", position.y)); } /// This function will panic if the element is not inserted in the DOM /// or is not a CSS property that represents a size in pixel. pub fn style_size_property(style: &Style, property: &str) -> f64 { let prop = style.get(property); prop.strip_suffix("px") .expect("Element was not inserted into the DOM or is not a size in pixel") .parse() .expect("CSS property is not a size in pixel") } pub fn is_dark_mode(window: &web_sys::Window) -> Option { window.match_media("(prefers-color-scheme: dark)").ok().flatten().map(|media| media.matches()) } pub fn is_visible(document: &Document) -> bool { document.visibility_state() == VisibilityState::Visible } pub type RawCanvasType = HtmlCanvasElement; #[derive(Clone, Copy)] pub enum Engine { Chromium, Gecko, WebKit, } pub fn engine(window: &Window) -> Option { static ENGINE: OnceLock> = OnceLock::new(); #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = Navigator)] type NavigatorExt; #[wasm_bindgen(method, getter, js_name = userAgentData)] fn user_agent_data(this: &NavigatorExt) -> Option; type NavigatorUaData; #[wasm_bindgen(method, getter)] fn brands(this: &NavigatorUaData) -> Array; type NavigatorUaBrandVersion; #[wasm_bindgen(method, getter)] fn brand(this: &NavigatorUaBrandVersion) -> String; } *ENGINE.get_or_init(|| { let navigator: NavigatorExt = window.navigator().unchecked_into(); if let Some(data) = navigator.user_agent_data() { for brand in data .brands() .iter() .map(NavigatorUaBrandVersion::unchecked_from_js) .map(|brand| brand.brand()) { match brand.as_str() { "Chromium" => return Some(Engine::Chromium), // TODO: verify when Firefox actually implements it. "Gecko" => return Some(Engine::Gecko), // TODO: verify when Safari actually implements it. "WebKit" => return Some(Engine::WebKit), _ => (), } } None } else { let data = navigator.user_agent().ok()?; if data.contains("Chrome/") { Some(Engine::Chromium) } else if data.contains("Gecko/") { Some(Engine::Gecko) } else if data.contains("AppleWebKit/") { Some(Engine::WebKit) } else { None } } }) } winit-0.30.9/src/platform_impl/web/web_sys/pointer.rs000064400000000000000000000226051046102023000207660ustar 00000000000000use std::cell::Cell; use std::rc::Rc; use super::canvas::Common; use super::event; use super::event_handle::EventListenerHandle; use crate::dpi::PhysicalPosition; use crate::event::{Force, MouseButton}; use crate::keyboard::ModifiersState; use event::ButtonsState; use web_sys::PointerEvent; #[allow(dead_code)] pub(super) struct PointerHandler { on_cursor_leave: Option>, on_cursor_enter: Option>, on_cursor_move: Option>, on_pointer_press: Option>, on_pointer_release: Option>, on_touch_cancel: Option>, } impl PointerHandler { pub fn new() -> Self { Self { on_cursor_leave: None, on_cursor_enter: None, on_cursor_move: None, on_pointer_press: None, on_pointer_release: None, on_touch_cancel: None, } } pub fn on_cursor_leave(&mut self, canvas_common: &Common, mut handler: F) where F: 'static + FnMut(ModifiersState, Option), { self.on_cursor_leave = Some(canvas_common.add_event("pointerout", move |event: PointerEvent| { let modifiers = event::mouse_modifiers(&event); // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. let pointer_id = (event.pointer_type() != "touch").then(|| event.pointer_id()); handler(modifiers, pointer_id); })); } pub fn on_cursor_enter(&mut self, canvas_common: &Common, mut handler: F) where F: 'static + FnMut(ModifiersState, Option), { self.on_cursor_enter = Some(canvas_common.add_event("pointerover", move |event: PointerEvent| { let modifiers = event::mouse_modifiers(&event); // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. let pointer_id = (event.pointer_type() != "touch").then(|| event.pointer_id()); handler(modifiers, pointer_id); })); } pub fn on_mouse_release( &mut self, canvas_common: &Common, mut mouse_handler: M, mut touch_handler: T, ) where M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { let window = canvas_common.window.clone(); self.on_pointer_release = Some(canvas_common.add_event("pointerup", move |event: PointerEvent| { let modifiers = event::mouse_modifiers(&event); match event.pointer_type().as_str() { "touch" => touch_handler( modifiers, event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ), _ => mouse_handler( modifiers, event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), event::mouse_button(&event).expect("no mouse button released"), ), } })); } pub fn on_mouse_press( &mut self, canvas_common: &Common, mut mouse_handler: M, mut touch_handler: T, prevent_default: Rc>, ) where M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { let window = canvas_common.window.clone(); let canvas = canvas_common.raw().clone(); self.on_pointer_press = Some(canvas_common.add_event("pointerdown", move |event: PointerEvent| { if prevent_default.get() { // prevent text selection event.prevent_default(); // but still focus element let _ = canvas.focus(); } let modifiers = event::mouse_modifiers(&event); let pointer_type = &event.pointer_type(); match pointer_type.as_str() { "touch" => { touch_handler( modifiers, event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ); }, _ => { mouse_handler( modifiers, event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), event::mouse_button(&event).expect("no mouse button pressed"), ); if pointer_type == "mouse" { // Error is swallowed here since the error would occur every time the // mouse is clicked when the cursor is // grabbed, and there is probably not a // situation where this could fail, that we // care if it fails. let _e = canvas.set_pointer_capture(event.pointer_id()); } }, } })); } pub fn on_cursor_move( &mut self, canvas_common: &Common, mut mouse_handler: M, mut touch_handler: T, mut button_handler: B, prevent_default: Rc>, ) where M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), B: 'static + FnMut(ModifiersState, i32, PhysicalPosition, ButtonsState, MouseButton), { let window = canvas_common.window.clone(); let canvas = canvas_common.raw().clone(); self.on_cursor_move = Some(canvas_common.add_event("pointermove", move |event: PointerEvent| { let modifiers = event::mouse_modifiers(&event); let id = event.pointer_id(); // chorded button event if let Some(button) = event::mouse_button(&event) { if prevent_default.get() { // prevent text selection event.prevent_default(); // but still focus element let _ = canvas.focus(); } button_handler( modifiers, id, event::mouse_position(&event).to_physical(super::scale_factor(&window)), event::mouse_buttons(&event), button, ); return; } // pointer move event let scale = super::scale_factor(&window); match event.pointer_type().as_str() { "touch" => touch_handler( modifiers, id, &mut event::pointer_move_event(event).map(|event| { ( event::mouse_position(&event).to_physical(scale), Force::Normalized(event.pressure() as f64), ) }), ), _ => mouse_handler( modifiers, id, &mut event::pointer_move_event(event) .map(|event| event::mouse_position(&event).to_physical(scale)), ), }; })); } pub fn on_touch_cancel(&mut self, canvas_common: &Common, mut handler: F) where F: 'static + FnMut(i32, PhysicalPosition, Force), { let window = canvas_common.window.clone(); self.on_touch_cancel = Some(canvas_common.add_event("pointercancel", move |event: PointerEvent| { if event.pointer_type() == "touch" { handler( event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ); } })); } pub fn remove_listeners(&mut self) { self.on_cursor_leave = None; self.on_cursor_enter = None; self.on_cursor_move = None; self.on_pointer_press = None; self.on_pointer_release = None; self.on_touch_cancel = None; } } winit-0.30.9/src/platform_impl/web/web_sys/resize_scaling.rs000064400000000000000000000237441046102023000223140ustar 00000000000000use js_sys::{Array, Object}; use tracing::warn; use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{ Document, HtmlCanvasElement, MediaQueryList, ResizeObserver, ResizeObserverBoxOptions, ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize, Window, }; use crate::dpi::{LogicalSize, PhysicalSize}; use super::super::backend; use super::canvas::Style; use super::media_query_handle::MediaQueryListHandle; use std::cell::{Cell, RefCell}; use std::rc::Rc; pub struct ResizeScaleHandle(Rc); impl ResizeScaleHandle { pub(crate) fn new( window: Window, document: Document, canvas: HtmlCanvasElement, style: Style, scale_handler: S, resize_handler: R, ) -> Self where S: 'static + Fn(PhysicalSize, f64), R: 'static + Fn(PhysicalSize), { Self(ResizeScaleInternal::new( window, document, canvas, style, scale_handler, resize_handler, )) } pub(crate) fn notify_resize(&self) { self.0.notify() } } /// This is a helper type to help manage the `MediaQueryList` used for detecting /// changes of the `devicePixelRatio`. struct ResizeScaleInternal { window: Window, document: Document, canvas: HtmlCanvasElement, style: Style, mql: RefCell, observer: ResizeObserver, _observer_closure: Closure, scale_handler: Box, f64)>, resize_handler: Box)>, notify_scale: Cell, } impl ResizeScaleInternal { fn new( window: Window, document: Document, canvas: HtmlCanvasElement, style: Style, scale_handler: S, resize_handler: R, ) -> Rc where S: 'static + Fn(PhysicalSize, f64), R: 'static + Fn(PhysicalSize), { Rc::::new_cyclic(|weak_self| { let mql = Self::create_mql(&window, { let weak_self = weak_self.clone(); move |mql| { if let Some(rc_self) = weak_self.upgrade() { Self::handle_scale(rc_self, mql); } } }); let weak_self = weak_self.clone(); let observer_closure = Closure::new(move |entries: Array, _| { if let Some(this) = weak_self.upgrade() { let size = this.process_entry(entries); if this.notify_scale.replace(false) { let scale = backend::scale_factor(&this.window); (this.scale_handler)(size, scale) } else { (this.resize_handler)(size) } } }); let observer = Self::create_observer(&canvas, observer_closure.as_ref()); Self { window, document, canvas, style, mql: RefCell::new(mql), observer, _observer_closure: observer_closure, scale_handler: Box::new(scale_handler), resize_handler: Box::new(resize_handler), notify_scale: Cell::new(false), } }) } fn create_mql(window: &Window, closure: F) -> MediaQueryListHandle where F: 'static + FnMut(&MediaQueryList), { let current_scale = super::scale_factor(window); // TODO: Remove `-webkit-device-pixel-ratio`. Requires Safari v16. let media_query = format!( "(resolution: {current_scale}dppx), (-webkit-device-pixel-ratio: {current_scale})", ); let mql = MediaQueryListHandle::new(window, &media_query, closure); debug_assert!( mql.mql().matches(), "created media query doesn't match, {current_scale} != {}", super::scale_factor(window) ); mql } fn create_observer(canvas: &HtmlCanvasElement, closure: &JsValue) -> ResizeObserver { let observer = ResizeObserver::new(closure.as_ref().unchecked_ref()) .expect("Failed to create `ResizeObserver`"); // Safari doesn't support `devicePixelContentBoxSize` if has_device_pixel_support() { let options = ResizeObserverOptions::new(); options.set_box(ResizeObserverBoxOptions::DevicePixelContentBox); observer.observe_with_options(canvas, &options); } else { observer.observe(canvas); } observer } fn notify(&self) { if !self.document.contains(Some(&self.canvas)) || self.style.get("display") == "none" { let size = PhysicalSize::new(0, 0); if self.notify_scale.replace(false) { let scale = backend::scale_factor(&self.window); (self.scale_handler)(size, scale) } else { (self.resize_handler)(size) } return; } // Safari doesn't support `devicePixelContentBoxSize` if has_device_pixel_support() { self.observer.unobserve(&self.canvas); self.observer.observe(&self.canvas); return; } let mut size = LogicalSize::new( backend::style_size_property(&self.style, "width"), backend::style_size_property(&self.style, "height"), ); if self.style.get("box-sizing") == "border-box" { size.width -= backend::style_size_property(&self.style, "border-left-width") + backend::style_size_property(&self.style, "border-right-width") + backend::style_size_property(&self.style, "padding-left") + backend::style_size_property(&self.style, "padding-right"); size.height -= backend::style_size_property(&self.style, "border-top-width") + backend::style_size_property(&self.style, "border-bottom-width") + backend::style_size_property(&self.style, "padding-top") + backend::style_size_property(&self.style, "padding-bottom"); } let size = size.to_physical(backend::scale_factor(&self.window)); if self.notify_scale.replace(false) { let scale = backend::scale_factor(&self.window); (self.scale_handler)(size, scale) } else { (self.resize_handler)(size) } } fn handle_scale(self: Rc, mql: &MediaQueryList) { let weak_self = Rc::downgrade(&self); let scale = super::scale_factor(&self.window); // TODO: confirm/reproduce this problem, see: // . // This should never happen, but if it does then apparently the scale factor didn't change. if mql.matches() { warn!( "media query tracking scale factor was triggered without a change:\nMedia Query: \ {}\nCurrent Scale: {scale}", mql.media(), ); return; } let new_mql = Self::create_mql(&self.window, move |mql| { if let Some(rc_self) = weak_self.upgrade() { Self::handle_scale(rc_self, mql); } }); self.mql.replace(new_mql); self.notify_scale.set(true); self.notify(); } fn process_entry(&self, entries: Array) -> PhysicalSize { let entry: ResizeObserverEntry = entries.get(0).unchecked_into(); // Safari doesn't support `devicePixelContentBoxSize` if !has_device_pixel_support() { let rect = entry.content_rect(); return LogicalSize::new(rect.width(), rect.height()) .to_physical(backend::scale_factor(&self.window)); } let entry: ResizeObserverSize = entry.device_pixel_content_box_size().get(0).unchecked_into(); let writing_mode = self.style.get("writing-mode"); // means the canvas is not inserted into the DOM if writing_mode.is_empty() { debug_assert_eq!(entry.inline_size(), 0.); debug_assert_eq!(entry.block_size(), 0.); return PhysicalSize::new(0, 0); } let horizontal = match writing_mode.as_str() { _ if writing_mode.starts_with("horizontal") => true, _ if writing_mode.starts_with("vertical") | writing_mode.starts_with("sideways") => { false }, // deprecated values "lr" | "lr-tb" | "rl" => true, "tb" | "tb-lr" | "tb-rl" => false, _ => { warn!("unrecognized `writing-mode`, assuming horizontal"); true }, }; if horizontal { PhysicalSize::new(entry.inline_size() as u32, entry.block_size() as u32) } else { PhysicalSize::new(entry.block_size() as u32, entry.inline_size() as u32) } } } impl Drop for ResizeScaleInternal { fn drop(&mut self) { self.observer.disconnect(); } } // TODO: Remove when Safari supports `devicePixelContentBoxSize`. // See . pub fn has_device_pixel_support() -> bool { thread_local! { static DEVICE_PIXEL_SUPPORT: bool = { #[wasm_bindgen] extern "C" { type ResizeObserverEntryExt; #[wasm_bindgen(js_class = ResizeObserverEntry, static_method_of = ResizeObserverEntryExt, getter)] fn prototype() -> Object; } let prototype = ResizeObserverEntryExt::prototype(); let descriptor = Object::get_own_property_descriptor( &prototype, &JsValue::from_str("devicePixelContentBoxSize"), ); !descriptor.is_undefined() }; } DEVICE_PIXEL_SUPPORT.with(|support| *support) } winit-0.30.9/src/platform_impl/web/web_sys/schedule.rs000064400000000000000000000254601046102023000211040ustar 00000000000000use js_sys::{Array, Function, Object, Promise, Reflect}; use std::cell::OnceCell; use std::time::Duration; use wasm_bindgen::closure::Closure; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{ AbortController, AbortSignal, Blob, BlobPropertyBag, MessageChannel, MessagePort, Url, Worker, }; use crate::platform::web::{PollStrategy, WaitUntilStrategy}; #[derive(Debug)] pub struct Schedule { _closure: Closure, inner: Inner, } #[derive(Debug)] enum Inner { Scheduler { controller: AbortController, }, IdleCallback { window: web_sys::Window, handle: u32, }, Timeout { window: web_sys::Window, handle: i32, port: MessagePort, _timeout_closure: Closure, }, Worker(MessagePort), } impl Schedule { pub fn new(strategy: PollStrategy, window: &web_sys::Window, f: F) -> Schedule where F: 'static + FnMut(), { if strategy == PollStrategy::Scheduler && has_scheduler_support(window) { Self::new_scheduler(window, f, None) } else if strategy == PollStrategy::IdleCallback && has_idle_callback_support(window) { Self::new_idle_callback(window.clone(), f) } else { Self::new_timeout(window.clone(), f, None) } } pub fn new_with_duration( strategy: WaitUntilStrategy, window: &web_sys::Window, f: F, duration: Duration, ) -> Schedule where F: 'static + FnMut(), { match strategy { WaitUntilStrategy::Scheduler => { if has_scheduler_support(window) { Self::new_scheduler(window, f, Some(duration)) } else { Self::new_timeout(window.clone(), f, Some(duration)) } }, WaitUntilStrategy::Worker => Self::new_worker(f, duration), } } fn new_scheduler(window: &web_sys::Window, f: F, duration: Option) -> Schedule where F: 'static + FnMut(), { let window: &WindowSupportExt = window.unchecked_ref(); let scheduler = window.scheduler(); let closure = Closure::new(f); let mut options = SchedulerPostTaskOptions::new(); let controller = AbortController::new().expect("Failed to create `AbortController`"); options.signal(&controller.signal()); if let Some(duration) = duration { // `Duration::as_millis()` always rounds down (because of truncation), we want to round // up instead. This makes sure that the we never wake up **before** the given time. let duration = duration .as_secs() .checked_mul(1000) .and_then(|secs| secs.checked_add(duration_millis_ceil(duration).into())) .unwrap_or(u64::MAX); options.delay(duration as f64); } thread_local! { static REJECT_HANDLER: Closure = Closure::new(|_| ()); } REJECT_HANDLER.with(|handler| { let _ = scheduler .post_task_with_options(closure.as_ref().unchecked_ref(), &options) .catch(handler); }); Schedule { _closure: closure, inner: Inner::Scheduler { controller } } } fn new_idle_callback(window: web_sys::Window, f: F) -> Schedule where F: 'static + FnMut(), { let closure = Closure::new(f); let handle = window .request_idle_callback(closure.as_ref().unchecked_ref()) .expect("Failed to request idle callback"); Schedule { _closure: closure, inner: Inner::IdleCallback { window, handle } } } fn new_timeout(window: web_sys::Window, f: F, duration: Option) -> Schedule where F: 'static + FnMut(), { let channel = MessageChannel::new().unwrap(); let closure = Closure::new(f); let port_1 = channel.port1(); port_1.set_onmessage(Some(closure.as_ref().unchecked_ref())); port_1.start(); let port_2 = channel.port2(); let timeout_closure = Closure::new(move || { port_2.post_message(&JsValue::UNDEFINED).expect("Failed to send message") }); let handle = if let Some(duration) = duration { // `Duration::as_millis()` always rounds down (because of truncation), we want to round // up instead. This makes sure that the we never wake up **before** the given time. let duration = duration .as_secs() .try_into() .ok() .and_then(|secs: i32| secs.checked_mul(1000)) .and_then(|secs: i32| { let millis: i32 = duration_millis_ceil(duration) .try_into() .expect("millis are somehow bigger then 1K"); secs.checked_add(millis) }) .unwrap_or(i32::MAX); window.set_timeout_with_callback_and_timeout_and_arguments_0( timeout_closure.as_ref().unchecked_ref(), duration, ) } else { window.set_timeout_with_callback(timeout_closure.as_ref().unchecked_ref()) } .expect("Failed to set timeout"); Schedule { _closure: closure, inner: Inner::Timeout { window, handle, port: port_1, _timeout_closure: timeout_closure, }, } } fn new_worker(f: F, duration: Duration) -> Schedule where F: 'static + FnMut(), { thread_local! { static URL: ScriptUrl = ScriptUrl::new(include_str!("worker.min.js")); static WORKER: Worker = URL.with(|url| Worker::new(&url.0)).expect("`new Worker()` is not expected to fail with a local script"); } let channel = MessageChannel::new().unwrap(); let closure = Closure::new(f); let port_1 = channel.port1(); port_1.set_onmessage(Some(closure.as_ref().unchecked_ref())); port_1.start(); // `Duration::as_millis()` always rounds down (because of truncation), we want to round // up instead. This makes sure that the we never wake up **before** the given time. let duration = duration .as_secs() .try_into() .ok() .and_then(|secs: u32| secs.checked_mul(1000)) .and_then(|secs| secs.checked_add(duration_millis_ceil(duration))) .unwrap_or(u32::MAX); WORKER .with(|worker| { let port_2 = channel.port2(); worker.post_message_with_transfer( &Array::of2(&port_2, &duration.into()), &Array::of1(&port_2).into(), ) }) .expect("`Worker.postMessage()` is not expected to fail"); Schedule { _closure: closure, inner: Inner::Worker(port_1) } } } impl Drop for Schedule { fn drop(&mut self) { match &self.inner { Inner::Scheduler { controller, .. } => controller.abort(), Inner::IdleCallback { window, handle, .. } => window.cancel_idle_callback(*handle), Inner::Timeout { window, handle, port, .. } => { window.clear_timeout_with_handle(*handle); port.close(); port.set_onmessage(None); }, Inner::Worker(port) => { port.close(); port.set_onmessage(None); }, } } } // TODO: Replace with `u32::div_ceil()` when we hit Rust v1.73. fn duration_millis_ceil(duration: Duration) -> u32 { let micros = duration.subsec_micros(); // From . let d = micros / 1000; let r = micros % 1000; if r > 0 && 1000 > 0 { d + 1 } else { d } } fn has_scheduler_support(window: &web_sys::Window) -> bool { thread_local! { static SCHEDULER_SUPPORT: OnceCell = const { OnceCell::new() }; } SCHEDULER_SUPPORT.with(|support| { *support.get_or_init(|| { #[wasm_bindgen] extern "C" { type SchedulerSupport; #[wasm_bindgen(method, getter, js_name = scheduler)] fn has_scheduler(this: &SchedulerSupport) -> JsValue; } let support: &SchedulerSupport = window.unchecked_ref(); !support.has_scheduler().is_undefined() }) }) } fn has_idle_callback_support(window: &web_sys::Window) -> bool { thread_local! { static IDLE_CALLBACK_SUPPORT: OnceCell = const { OnceCell::new() }; } IDLE_CALLBACK_SUPPORT.with(|support| { *support.get_or_init(|| { #[wasm_bindgen] extern "C" { type IdleCallbackSupport; #[wasm_bindgen(method, getter, js_name = requestIdleCallback)] fn has_request_idle_callback(this: &IdleCallbackSupport) -> JsValue; } let support: &IdleCallbackSupport = window.unchecked_ref(); !support.has_request_idle_callback().is_undefined() }) }) } struct ScriptUrl(String); impl ScriptUrl { fn new(script: &str) -> Self { let sequence = Array::of1(&script.into()); let property = BlobPropertyBag::new(); property.set_type("text/javascript"); let blob = Blob::new_with_str_sequence_and_options(&sequence, &property) .expect("`new Blob()` should never throw"); let url = Url::create_object_url_with_blob(&blob) .expect("`URL.createObjectURL()` should never throw"); Self(url) } } impl Drop for ScriptUrl { fn drop(&mut self) { Url::revoke_object_url(&self.0).expect("`URL.revokeObjectURL()` should never throw"); } } #[wasm_bindgen] extern "C" { type WindowSupportExt; #[wasm_bindgen(method, getter)] fn scheduler(this: &WindowSupportExt) -> Scheduler; type Scheduler; #[wasm_bindgen(method, js_name = postTask)] fn post_task_with_options( this: &Scheduler, callback: &Function, options: &SchedulerPostTaskOptions, ) -> Promise; type SchedulerPostTaskOptions; } impl SchedulerPostTaskOptions { fn new() -> Self { Object::new().unchecked_into() } fn delay(&mut self, val: f64) -> &mut Self { let r = Reflect::set(self, &JsValue::from("delay"), &val.into()); debug_assert!(r.is_ok(), "Failed to set `delay` property"); self } fn signal(&mut self, val: &AbortSignal) -> &mut Self { let r = Reflect::set(self, &JsValue::from("signal"), &val.into()); debug_assert!(r.is_ok(), "Failed to set `signal` property"); self } } winit-0.30.9/src/platform_impl/web/web_sys/worker.js000064400000000000000000000003641046102023000206050ustar 00000000000000onmessage = event => { const [port, timeout] = event.data const f = () => port.postMessage(undefined) if ('scheduler' in this) { scheduler.postTask(f, { delay: timeout }) } else { setTimeout(f, timeout) } } winit-0.30.9/src/platform_impl/web/web_sys/worker.min.js000064400000000000000000000001771046102023000213710ustar 00000000000000onmessage=e=>{let[s,t]=e.data,a=()=>s.postMessage(void 0);"scheduler"in this?scheduler.postTask(a,{delay:t}):setTimeout(a,t)}; winit-0.30.9/src/platform_impl/web/window.rs000064400000000000000000000327621046102023000171470ustar 00000000000000use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::icon::Icon; use crate::window::{ Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId as RootWI, WindowLevel, }; use super::main_thread::{MainThreadMarker, MainThreadSafe}; use super::monitor::MonitorHandle; use super::r#async::Dispatcher; use super::{backend, ActiveEventLoop, Fullscreen}; use web_sys::HtmlCanvasElement; use std::cell::RefCell; use std::collections::VecDeque; use std::rc::Rc; use std::sync::Arc; pub struct Window { inner: Dispatcher, } pub struct Inner { id: WindowId, pub window: web_sys::Window, canvas: Rc>, destroy_fn: Option>, } impl Window { pub(crate) fn new( target: &ActiveEventLoop, mut attr: WindowAttributes, ) -> Result { let id = target.generate_id(); let window = target.runner.window(); let document = target.runner.document(); let canvas = backend::Canvas::create( target.runner.main_thread(), id, window.clone(), document.clone(), &mut attr, )?; let canvas = Rc::new(RefCell::new(canvas)); target.register(&canvas, id); let runner = target.runner.clone(); let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id))); let inner = Inner { id, window: window.clone(), canvas, destroy_fn: Some(destroy_fn) }; inner.set_title(&attr.title); inner.set_maximized(attr.maximized); inner.set_visible(attr.visible); inner.set_window_icon(attr.window_icon); inner.set_cursor(attr.cursor); let canvas = Rc::downgrade(&inner.canvas); let (dispatcher, runner) = Dispatcher::new(target.runner.main_thread(), inner).unwrap(); target.runner.add_canvas(RootWI(id), canvas, runner); Ok(Window { inner: dispatcher }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) { self.inner.dispatch(f) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Inner) -> R + Send) -> R { self.inner.queue(f) } pub fn canvas(&self) -> Option { self.inner.value().map(|inner| inner.canvas.borrow().raw().clone()) } pub(crate) fn prevent_default(&self) -> bool { self.inner.queue(|inner| inner.canvas.borrow().prevent_default.get()) } pub(crate) fn set_prevent_default(&self, prevent_default: bool) { self.inner.dispatch(move |inner| inner.canvas.borrow().prevent_default.set(prevent_default)) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { self.inner .value() .map(|inner| { let canvas = inner.canvas.borrow(); // SAFETY: This will only work if the reference to `HtmlCanvasElement` stays valid. let canvas: &wasm_bindgen::JsValue = canvas.raw(); let window_handle = rwh_06::WebCanvasWindowHandle::new(std::ptr::NonNull::from(canvas).cast()); rwh_06::RawWindowHandle::WebCanvas(window_handle) }) .ok_or(rwh_06::HandleError::Unavailable) } #[cfg(feature = "rwh_06")] #[inline] pub(crate) fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Web(rwh_06::WebDisplayHandle::new())) } } impl Inner { pub fn set_title(&self, title: &str) { self.canvas.borrow().set_attribute("alt", title) } pub fn set_transparent(&self, _transparent: bool) {} pub fn set_blur(&self, _blur: bool) {} pub fn set_visible(&self, _visible: bool) { // Intentionally a no-op } #[inline] pub fn is_visible(&self) -> Option { None } pub fn request_redraw(&self) { self.canvas.borrow().request_animation_frame(); } pub fn pre_present_notify(&self) {} pub fn outer_position(&self) -> Result, NotSupportedError> { Ok(self.canvas.borrow().position().to_physical(self.scale_factor())) } pub fn inner_position(&self) -> Result, NotSupportedError> { // Note: the canvas element has no window decorations, so this is equal to `outer_position`. self.outer_position() } pub fn set_outer_position(&self, position: Position) { let canvas = self.canvas.borrow(); let position = position.to_logical::(self.scale_factor()); backend::set_canvas_position(canvas.document(), canvas.raw(), canvas.style(), position) } #[inline] pub fn inner_size(&self) -> PhysicalSize { self.canvas.borrow().inner_size() } #[inline] pub fn outer_size(&self) -> PhysicalSize { // Note: the canvas element has no window decorations, so this is equal to `inner_size`. self.inner_size() } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let size = size.to_logical(self.scale_factor()); let canvas = self.canvas.borrow(); backend::set_canvas_size(canvas.document(), canvas.raw(), canvas.style(), size); None } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { let dimensions = dimensions.map(|dimensions| dimensions.to_logical(self.scale_factor())); let canvas = self.canvas.borrow(); backend::set_canvas_min_size(canvas.document(), canvas.raw(), canvas.style(), dimensions) } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { let dimensions = dimensions.map(|dimensions| dimensions.to_logical(self.scale_factor())); let canvas = self.canvas.borrow(); backend::set_canvas_max_size(canvas.document(), canvas.raw(), canvas.style(), dimensions) } #[inline] pub fn resize_increments(&self) -> Option> { None } #[inline] pub fn set_resize_increments(&self, _increments: Option) { // Intentionally a no-op: users can't resize canvas elements } #[inline] pub fn set_resizable(&self, _resizable: bool) { // Intentionally a no-op: users can't resize canvas elements } pub fn is_resizable(&self) -> bool { true } #[inline] pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} #[inline] pub fn enabled_buttons(&self) -> WindowButtons { WindowButtons::all() } #[inline] pub fn scale_factor(&self) -> f64 { super::backend::scale_factor(&self.window) } #[inline] pub fn set_cursor(&self, cursor: Cursor) { self.canvas.borrow_mut().cursor.set_cursor(cursor) } #[inline] pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { let lock = match mode { CursorGrabMode::None => false, CursorGrabMode::Locked => true, CursorGrabMode::Confined => { return Err(ExternalError::NotSupported(NotSupportedError::new())) }, }; self.canvas.borrow().set_cursor_lock(lock).map_err(ExternalError::Os) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { self.canvas.borrow_mut().cursor.set_cursor_visible(visible) } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn show_window_menu(&self, _position: Position) {} #[inline] pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn set_minimized(&self, _minimized: bool) { // Intentionally a no-op, as canvases cannot be 'minimized' } #[inline] pub fn is_minimized(&self) -> Option { // Canvas cannot be 'minimized' Some(false) } #[inline] pub fn set_maximized(&self, _maximized: bool) { // Intentionally a no-op, as canvases cannot be 'maximized' } #[inline] pub fn is_maximized(&self) -> bool { // Canvas cannot be 'maximized' false } #[inline] pub(crate) fn fullscreen(&self) -> Option { if self.canvas.borrow().is_fullscreen() { Some(Fullscreen::Borderless(None)) } else { None } } #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { let canvas = &self.canvas.borrow(); if fullscreen.is_some() { canvas.request_fullscreen(); } else { canvas.exit_fullscreen() } } #[inline] pub fn set_decorations(&self, _decorations: bool) { // Intentionally a no-op, no canvas decorations } pub fn is_decorated(&self) -> bool { true } #[inline] pub fn set_window_level(&self, _level: WindowLevel) { // Intentionally a no-op, no window ordering } #[inline] pub fn set_window_icon(&self, _window_icon: Option) { // Currently an intentional no-op } #[inline] pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) { // Currently a no-op as it does not seem there is good support for this on web } #[inline] pub fn set_ime_allowed(&self, _allowed: bool) { // Currently not implemented } #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) { // Currently not implemented } #[inline] pub fn focus_window(&self) { let _ = self.canvas.borrow().raw().focus(); } #[inline] pub fn request_user_attention(&self, _request_type: Option) { // Currently an intentional no-op } #[inline] pub fn current_monitor(&self) -> Option { None } #[inline] pub fn available_monitors(&self) -> VecDeque { VecDeque::new() } #[inline] pub fn primary_monitor(&self) -> Option { None } #[inline] pub fn id(&self) -> WindowId { self.id } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::WebHandle::empty(); window_handle.id = self.id.0; rwh_04::RawWindowHandle::Web(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::WebWindowHandle::empty(); window_handle.id = self.id.0; rwh_05::RawWindowHandle::Web(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Web(rwh_05::WebDisplayHandle::empty()) } #[inline] pub fn set_theme(&self, _theme: Option) {} #[inline] pub fn theme(&self) -> Option { backend::is_dark_mode(&self.window).map(|is_dark_mode| { if is_dark_mode { Theme::Dark } else { Theme::Light } }) } pub fn set_content_protected(&self, _protected: bool) {} #[inline] pub fn has_focus(&self) -> bool { self.canvas.borrow().has_focus.get() } pub fn title(&self) -> String { String::new() } pub fn reset_dead_keys(&self) { // Not supported } } impl Drop for Inner { fn drop(&mut self) { if let Some(destroy_fn) = self.destroy_fn.take() { destroy_fn(); } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(pub(crate) u32); impl WindowId { pub const fn dummy() -> Self { Self(0) } } impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.0 as u64 } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id as u32) } } #[derive(Clone, Debug)] pub struct PlatformSpecificWindowAttributes { pub(crate) canvas: Option>>, pub(crate) prevent_default: bool, pub(crate) focusable: bool, pub(crate) append: bool, } impl PlatformSpecificWindowAttributes { pub(crate) fn set_canvas(&mut self, canvas: Option) { let Some(canvas) = canvas else { self.canvas = None; return; }; let main_thread = MainThreadMarker::new() .expect("received a `HtmlCanvasElement` outside the window context"); self.canvas = Some(Arc::new(MainThreadSafe::new(main_thread, canvas))); } } impl Default for PlatformSpecificWindowAttributes { fn default() -> Self { Self { canvas: None, prevent_default: true, focusable: true, append: false } } } winit-0.30.9/src/platform_impl/windows/dark_mode.rs000064400000000000000000000126331046102023000204750ustar 00000000000000/// This is a simple implementation of support for Windows Dark Mode, /// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode use std::{ffi::c_void, ptr}; use crate::utils::Lazy; use windows_sys::core::PCSTR; use windows_sys::Win32::Foundation::{BOOL, HWND, NTSTATUS, S_OK}; use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW; use windows_sys::Win32::UI::Accessibility::{HCF_HIGHCONTRASTON, HIGHCONTRASTA}; use windows_sys::Win32::UI::Controls::SetWindowTheme; use windows_sys::Win32::UI::WindowsAndMessaging::{SystemParametersInfoA, SPI_GETHIGHCONTRAST}; use crate::window::Theme; use super::util; static WIN10_BUILD_VERSION: Lazy> = Lazy::new(|| { type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS; let handle = get_function!("ntdll.dll", RtlGetVersion); if let Some(rtl_get_version) = handle { unsafe { let mut vi = OSVERSIONINFOW { dwOSVersionInfoSize: 0, dwMajorVersion: 0, dwMinorVersion: 0, dwBuildNumber: 0, dwPlatformId: 0, szCSDVersion: [0; 128], }; let status = (rtl_get_version)(&mut vi); if status >= 0 && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 { Some(vi.dwBuildNumber) } else { None } } } else { None } }); static DARK_MODE_SUPPORTED: Lazy = Lazy::new(|| { // We won't try to do anything for windows versions < 17763 // (Windows 10 October 2018 update) match *WIN10_BUILD_VERSION { Some(v) => v >= 17763, None => false, } }); static DARK_THEME_NAME: Lazy> = Lazy::new(|| util::encode_wide("DarkMode_Explorer")); static LIGHT_THEME_NAME: Lazy> = Lazy::new(|| util::encode_wide("")); /// Attempt to set a theme on a window, if necessary. /// Returns the theme that was picked pub fn try_theme(hwnd: HWND, preferred_theme: Option) -> Theme { if *DARK_MODE_SUPPORTED { let is_dark_mode = match preferred_theme { Some(theme) => theme == Theme::Dark, None => should_use_dark_mode(), }; let theme = if is_dark_mode { Theme::Dark } else { Theme::Light }; let theme_name = match theme { Theme::Dark => DARK_THEME_NAME.as_ptr(), Theme::Light => LIGHT_THEME_NAME.as_ptr(), }; let status = unsafe { SetWindowTheme(hwnd, theme_name, ptr::null()) }; if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) { return theme; } } Theme::Light } fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool { // Uses Windows undocumented API SetWindowCompositionAttribute, // as seen in win32-darkmode example linked at top of file. type SetWindowCompositionAttribute = unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL; #[allow(clippy::upper_case_acronyms)] type WINDOWCOMPOSITIONATTRIB = u32; const WCA_USEDARKMODECOLORS: WINDOWCOMPOSITIONATTRIB = 26; #[allow(non_snake_case)] #[allow(clippy::upper_case_acronyms)] #[repr(C)] struct WINDOWCOMPOSITIONATTRIBDATA { Attrib: WINDOWCOMPOSITIONATTRIB, pvData: *mut c_void, cbData: usize, } static SET_WINDOW_COMPOSITION_ATTRIBUTE: Lazy> = Lazy::new(|| get_function!("user32.dll", SetWindowCompositionAttribute)); if let Some(set_window_composition_attribute) = *SET_WINDOW_COMPOSITION_ATTRIBUTE { unsafe { // SetWindowCompositionAttribute needs a bigbool (i32), not bool. let mut is_dark_mode_bigbool = BOOL::from(is_dark_mode); let mut data = WINDOWCOMPOSITIONATTRIBDATA { Attrib: WCA_USEDARKMODECOLORS, pvData: &mut is_dark_mode_bigbool as *mut _ as _, cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _, }; let status = set_window_composition_attribute(hwnd, &mut data); status != false.into() } } else { false } } pub fn should_use_dark_mode() -> bool { should_apps_use_dark_mode() && !is_high_contrast() } fn should_apps_use_dark_mode() -> bool { type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool; static SHOULD_APPS_USE_DARK_MODE: Lazy> = Lazy::new(|| unsafe { const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: PCSTR = 132 as PCSTR; let module = LoadLibraryA("uxtheme.dll\0".as_ptr()); if module == 0 { return None; } let handle = GetProcAddress(module, UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL); handle.map(|handle| std::mem::transmute(handle)) }); SHOULD_APPS_USE_DARK_MODE .map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() }) .unwrap_or(false) } fn is_high_contrast() -> bool { let mut hc = HIGHCONTRASTA { cbSize: 0, dwFlags: 0, lpszDefaultScheme: ptr::null_mut() }; let ok = unsafe { SystemParametersInfoA( SPI_GETHIGHCONTRAST, std::mem::size_of_val(&hc) as _, &mut hc as *mut _ as _, 0, ) }; ok != false.into() && util::has_flag(hc.dwFlags, HCF_HIGHCONTRASTON) } winit-0.30.9/src/platform_impl/windows/definitions.rs000064400000000000000000000106161046102023000210620ustar 00000000000000#![allow(non_snake_case)] #![allow(non_upper_case_globals)] use std::ffi::c_void; use windows_sys::core::{IUnknown, GUID, HRESULT}; use windows_sys::Win32::Foundation::{BOOL, HWND, POINTL}; use windows_sys::Win32::System::Com::{ IAdviseSink, IDataObject, IEnumFORMATETC, IEnumSTATDATA, FORMATETC, STGMEDIUM, }; #[repr(C)] pub struct IUnknownVtbl { pub QueryInterface: unsafe extern "system" fn( This: *mut IUnknown, riid: *const GUID, ppvObject: *mut *mut c_void, ) -> HRESULT, pub AddRef: unsafe extern "system" fn(This: *mut IUnknown) -> u32, pub Release: unsafe extern "system" fn(This: *mut IUnknown) -> u32, } #[repr(C)] pub struct IDataObjectVtbl { pub parent: IUnknownVtbl, pub GetData: unsafe extern "system" fn( This: *mut IDataObject, pformatetcIn: *const FORMATETC, pmedium: *mut STGMEDIUM, ) -> HRESULT, pub GetDataHere: unsafe extern "system" fn( This: *mut IDataObject, pformatetc: *const FORMATETC, pmedium: *mut STGMEDIUM, ) -> HRESULT, QueryGetData: unsafe extern "system" fn(This: *mut IDataObject, pformatetc: *const FORMATETC) -> HRESULT, pub GetCanonicalFormatEtc: unsafe extern "system" fn( This: *mut IDataObject, pformatetcIn: *const FORMATETC, pformatetcOut: *mut FORMATETC, ) -> HRESULT, pub SetData: unsafe extern "system" fn( This: *mut IDataObject, pformatetc: *const FORMATETC, pformatetcOut: *const FORMATETC, fRelease: BOOL, ) -> HRESULT, pub EnumFormatEtc: unsafe extern "system" fn( This: *mut IDataObject, dwDirection: u32, ppenumFormatEtc: *mut *mut IEnumFORMATETC, ) -> HRESULT, pub DAdvise: unsafe extern "system" fn( This: *mut IDataObject, pformatetc: *const FORMATETC, advf: u32, pAdvSInk: *const IAdviseSink, pdwConnection: *mut u32, ) -> HRESULT, pub DUnadvise: unsafe extern "system" fn(This: *mut IDataObject, dwConnection: u32) -> HRESULT, pub EnumDAdvise: unsafe extern "system" fn( This: *mut IDataObject, ppenumAdvise: *const *const IEnumSTATDATA, ) -> HRESULT, } #[repr(C)] pub struct IDropTargetVtbl { pub parent: IUnknownVtbl, pub DragEnter: unsafe extern "system" fn( This: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: u32, pt: *const POINTL, pdwEffect: *mut u32, ) -> HRESULT, pub DragOver: unsafe extern "system" fn( This: *mut IDropTarget, grfKeyState: u32, pt: *const POINTL, pdwEffect: *mut u32, ) -> HRESULT, pub DragLeave: unsafe extern "system" fn(This: *mut IDropTarget) -> HRESULT, pub Drop: unsafe extern "system" fn( This: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: u32, pt: *const POINTL, pdwEffect: *mut u32, ) -> HRESULT, } #[repr(C)] pub struct IDropTarget { pub lpVtbl: *const IDropTargetVtbl, } #[repr(C)] pub struct ITaskbarListVtbl { pub parent: IUnknownVtbl, pub HrInit: unsafe extern "system" fn(This: *mut ITaskbarList) -> HRESULT, pub AddTab: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT, pub DeleteTab: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT, pub ActivateTab: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT, pub SetActiveAlt: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT, } #[repr(C)] pub struct ITaskbarList { pub lpVtbl: *const ITaskbarListVtbl, } #[repr(C)] pub struct ITaskbarList2Vtbl { pub parent: ITaskbarListVtbl, pub MarkFullscreenWindow: unsafe extern "system" fn( This: *mut ITaskbarList2, hwnd: HWND, fFullscreen: BOOL, ) -> HRESULT, } #[repr(C)] pub struct ITaskbarList2 { pub lpVtbl: *const ITaskbarList2Vtbl, } pub const CLSID_TaskbarList: GUID = GUID { data1: 0x56fdf344, data2: 0xfd6d, data3: 0x11d0, data4: [0x95, 0x8a, 0x00, 0x60, 0x97, 0xc9, 0xa0, 0x90], }; pub const IID_ITaskbarList: GUID = GUID { data1: 0x56fdf342, data2: 0xfd6d, data3: 0x11d0, data4: [0x95, 0x8a, 0x00, 0x60, 0x97, 0xc9, 0xa0, 0x90], }; pub const IID_ITaskbarList2: GUID = GUID { data1: 0x602d4995, data2: 0xb13a, data3: 0x429b, data4: [0xa6, 0x6e, 0x19, 0x35, 0xe4, 0x4f, 0x43, 0x17], }; winit-0.30.9/src/platform_impl/windows/dpi.rs000064400000000000000000000104041046102023000173160ustar 00000000000000#![allow(non_snake_case, unused_unsafe)] use std::sync::Once; use windows_sys::Win32::Foundation::{HWND, S_OK}; use windows_sys::Win32::Graphics::Gdi::{ GetDC, GetDeviceCaps, MonitorFromWindow, HMONITOR, LOGPIXELSX, MONITOR_DEFAULTTONEAREST, }; use windows_sys::Win32::UI::HiDpi::{ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, MDT_EFFECTIVE_DPI, PROCESS_PER_MONITOR_DPI_AWARE, }; use windows_sys::Win32::UI::WindowsAndMessaging::IsProcessDPIAware; use crate::platform_impl::platform::util::{ ENABLE_NON_CLIENT_DPI_SCALING, GET_DPI_FOR_MONITOR, GET_DPI_FOR_WINDOW, SET_PROCESS_DPI_AWARE, SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT, }; pub fn become_dpi_aware() { static ENABLE_DPI_AWARENESS: Once = Once::new(); ENABLE_DPI_AWARENESS.call_once(|| { unsafe { if let Some(SetProcessDpiAwarenessContext) = *SET_PROCESS_DPI_AWARENESS_CONTEXT { // We are on Windows 10 Anniversary Update (1607) or later. if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) == false.into() { // V2 only works with Windows 10 Creators Update (1703). Try using the older // V1 if we can't set V2. SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); } } else if let Some(SetProcessDpiAwareness) = *SET_PROCESS_DPI_AWARENESS { // We are on Windows 8.1 or later. SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); } else if let Some(SetProcessDPIAware) = *SET_PROCESS_DPI_AWARE { // We are on Vista or later. SetProcessDPIAware(); } } }); } pub fn enable_non_client_dpi_scaling(hwnd: HWND) { unsafe { if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING { EnableNonClientDpiScaling(hwnd); } } } pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option { unsafe { if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { // We are on Windows 8.1 or later. let mut dpi_x = 0; let mut dpi_y = 0; if GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK { // MSDN says that "the values of *dpiX and *dpiY are identical. You only need to // record one of the values to determine the DPI and respond appropriately". // https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx return Some(dpi_x); } } } None } pub const BASE_DPI: u32 = 96; pub fn dpi_to_scale_factor(dpi: u32) -> f64 { dpi as f64 / BASE_DPI as f64 } pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { let hdc = unsafe { GetDC(hwnd) }; if hdc == 0 { panic!("[winit] `GetDC` returned null!"); } if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { // We are on Windows 10 Anniversary Update (1607) or later. match unsafe { GetDpiForWindow(hwnd) } { 0 => BASE_DPI, // 0 is returned if hwnd is invalid dpi => dpi, } } else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { // We are on Windows 8.1 or later. let monitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }; if monitor == 0 { return BASE_DPI; } let mut dpi_x = 0; let mut dpi_y = 0; if unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) } == S_OK { dpi_x } else { BASE_DPI } } else { // We are on Vista or later. if unsafe { IsProcessDPIAware() } != false.into() { // If the process is DPI aware, then scaling must be handled by the application using // this DPI value. unsafe { GetDeviceCaps(hdc, LOGPIXELSX as i32) as u32 } } else { // If the process is DPI unaware, then scaling is performed by the OS; we thus return // 96 (scale factor 1.0) to prevent the window from being re-scaled by both the // application and the WM. BASE_DPI } } } winit-0.30.9/src/platform_impl/windows/drop_handler.rs000064400000000000000000000202511046102023000212040ustar 00000000000000use std::ffi::{c_void, OsString}; use std::os::windows::ffi::OsStringExt; use std::path::PathBuf; use std::ptr; use std::sync::atomic::{AtomicUsize, Ordering}; use windows_sys::core::{IUnknown, GUID, HRESULT}; use windows_sys::Win32::Foundation::{DV_E_FORMATETC, HWND, POINTL, S_OK}; use windows_sys::Win32::System::Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}; use windows_sys::Win32::System::Ole::{CF_HDROP, DROPEFFECT_COPY, DROPEFFECT_NONE}; use windows_sys::Win32::UI::Shell::{DragFinish, DragQueryFileW, HDROP}; use tracing::debug; use crate::platform_impl::platform::definitions::{ IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknownVtbl, }; use crate::platform_impl::platform::WindowId; use crate::event::Event; use crate::window::WindowId as RootWindowId; #[repr(C)] pub struct FileDropHandlerData { pub interface: IDropTarget, refcount: AtomicUsize, window: HWND, send_event: Box)>, cursor_effect: u32, hovered_is_valid: bool, /* If the currently hovered item is not valid there must not be any * `HoveredFileCancelled` emitted */ } pub struct FileDropHandler { pub data: *mut FileDropHandlerData, } #[allow(non_snake_case)] impl FileDropHandler { pub fn new(window: HWND, send_event: Box)>) -> FileDropHandler { let data = Box::new(FileDropHandlerData { interface: IDropTarget { lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl }, refcount: AtomicUsize::new(1), window, send_event, cursor_effect: DROPEFFECT_NONE, hovered_is_valid: false, }); FileDropHandler { data: Box::into_raw(data) } } // Implement IUnknown pub unsafe extern "system" fn QueryInterface( _this: *mut IUnknown, _riid: *const GUID, _ppvObject: *mut *mut c_void, ) -> HRESULT { // This function doesn't appear to be required for an `IDropTarget`. // An implementation would be nice however. unimplemented!(); } pub unsafe extern "system" fn AddRef(this: *mut IUnknown) -> u32 { let drop_handler_data = unsafe { Self::from_interface(this) }; let count = drop_handler_data.refcount.fetch_add(1, Ordering::Release) + 1; count as u32 } pub unsafe extern "system" fn Release(this: *mut IUnknown) -> u32 { let drop_handler = unsafe { Self::from_interface(this) }; let count = drop_handler.refcount.fetch_sub(1, Ordering::Release) - 1; if count == 0 { // Destroy the underlying data drop(unsafe { Box::from_raw(drop_handler as *mut FileDropHandlerData) }); } count as u32 } pub unsafe extern "system" fn DragEnter( this: *mut IDropTarget, pDataObj: *const IDataObject, _grfKeyState: u32, _pt: *const POINTL, pdwEffect: *mut u32, ) -> HRESULT { use crate::event::WindowEvent::HoveredFile; let drop_handler = unsafe { Self::from_interface(this) }; let hdrop = unsafe { Self::iterate_filenames(pDataObj, |filename| { drop_handler.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(drop_handler.window)), event: HoveredFile(filename), }); }) }; drop_handler.hovered_is_valid = hdrop.is_some(); drop_handler.cursor_effect = if drop_handler.hovered_is_valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE }; unsafe { *pdwEffect = drop_handler.cursor_effect; } S_OK } pub unsafe extern "system" fn DragOver( this: *mut IDropTarget, _grfKeyState: u32, _pt: *const POINTL, pdwEffect: *mut u32, ) -> HRESULT { let drop_handler = unsafe { Self::from_interface(this) }; unsafe { *pdwEffect = drop_handler.cursor_effect; } S_OK } pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT { use crate::event::WindowEvent::HoveredFileCancelled; let drop_handler = unsafe { Self::from_interface(this) }; if drop_handler.hovered_is_valid { drop_handler.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(drop_handler.window)), event: HoveredFileCancelled, }); } S_OK } pub unsafe extern "system" fn Drop( this: *mut IDropTarget, pDataObj: *const IDataObject, _grfKeyState: u32, _pt: *const POINTL, _pdwEffect: *mut u32, ) -> HRESULT { use crate::event::WindowEvent::DroppedFile; let drop_handler = unsafe { Self::from_interface(this) }; let hdrop = unsafe { Self::iterate_filenames(pDataObj, |filename| { drop_handler.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(drop_handler.window)), event: DroppedFile(filename), }); }) }; if let Some(hdrop) = hdrop { unsafe { DragFinish(hdrop) }; } S_OK } unsafe fn from_interface<'a, InterfaceT>(this: *mut InterfaceT) -> &'a mut FileDropHandlerData { unsafe { &mut *(this as *mut _) } } unsafe fn iterate_filenames(data_obj: *const IDataObject, callback: F) -> Option where F: Fn(PathBuf), { let drop_format = FORMATETC { cfFormat: CF_HDROP, ptd: ptr::null_mut(), dwAspect: DVASPECT_CONTENT, lindex: -1, tymed: TYMED_HGLOBAL as u32, }; let mut medium = unsafe { std::mem::zeroed() }; let get_data_fn = unsafe { (*(*data_obj).cast::()).GetData }; let get_data_result = unsafe { get_data_fn(data_obj as *mut _, &drop_format, &mut medium) }; if get_data_result >= 0 { let hdrop = unsafe { medium.u.hGlobal as HDROP }; // The second parameter (0xFFFFFFFF) instructs the function to return the item count let item_count = unsafe { DragQueryFileW(hdrop, 0xffffffff, ptr::null_mut(), 0) }; for i in 0..item_count { // Get the length of the path string NOT including the terminating null character. // Previously, this was using a fixed size array of MAX_PATH length, but the // Windows API allows longer paths under certain circumstances. let character_count = unsafe { DragQueryFileW(hdrop, i, ptr::null_mut(), 0) as usize }; let str_len = character_count + 1; // Fill path_buf with the null-terminated file name let mut path_buf = Vec::with_capacity(str_len); unsafe { DragQueryFileW(hdrop, i, path_buf.as_mut_ptr(), str_len as u32); path_buf.set_len(str_len); } callback(OsString::from_wide(&path_buf[0..character_count]).into()); } Some(hdrop) } else if get_data_result == DV_E_FORMATETC { // If the dropped item is not a file this error will occur. // In this case it is OK to return without taking further action. debug!("Error occurred while processing dropped/hovered item: item is not a file."); None } else { debug!("Unexpected error occurred while processing dropped/hovered item."); None } } } impl FileDropHandlerData { fn send_event(&self, event: Event<()>) { (self.send_event)(event); } } impl Drop for FileDropHandler { fn drop(&mut self) { unsafe { FileDropHandler::Release(self.data as *mut IUnknown); } } } static DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl { parent: IUnknownVtbl { QueryInterface: FileDropHandler::QueryInterface, AddRef: FileDropHandler::AddRef, Release: FileDropHandler::Release, }, DragEnter: FileDropHandler::DragEnter, DragOver: FileDropHandler::DragOver, DragLeave: FileDropHandler::DragLeave, Drop: FileDropHandler::Drop, }; winit-0.30.9/src/platform_impl/windows/event_loop/runner.rs000064400000000000000000000350371046102023000222360ustar 00000000000000use std::any::Any; use std::cell::{Cell, RefCell}; use std::collections::VecDeque; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::Instant; use std::{mem, panic}; use windows_sys::Win32::Foundation::HWND; use crate::dpi::PhysicalSize; use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent}; use crate::platform_impl::platform::event_loop::{WindowData, GWL_USERDATA}; use crate::platform_impl::platform::get_window_long; use crate::window::WindowId; use super::ControlFlow; pub(crate) type EventLoopRunnerShared = Rc>; type EventHandler = Cell)>>>; pub(crate) struct EventLoopRunner { // The event loop's win32 handles pub(super) thread_msg_target: HWND, // Setting this will ensure pump_events will return to the external // loop asap. E.g. set after each RedrawRequested to ensure pump_events // can't stall an external loop beyond a frame pub(super) interrupt_msg_dispatch: Cell, control_flow: Cell, exit: Cell>, runner_state: Cell, last_events_cleared: Cell, event_handler: EventHandler, event_buffer: RefCell>>, panic_error: Cell>, } pub type PanicError = Box; /// See `move_state_to` function for details on how the state loop works. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) enum RunnerState { /// The event loop has just been created, and an `Init` event must be sent. Uninitialized, /// The event loop is idling. Idle, /// The event loop is handling the OS's events and sending them to the user's callback. /// `NewEvents` has been sent, and `AboutToWait` hasn't. HandlingMainEvents, /// The event loop has been destroyed. No other events will be emitted. Destroyed, } enum BufferedEvent { Event(Event), ScaleFactorChanged(WindowId, f64, PhysicalSize), } impl EventLoopRunner { pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner { EventLoopRunner { thread_msg_target, interrupt_msg_dispatch: Cell::new(false), runner_state: Cell::new(RunnerState::Uninitialized), control_flow: Cell::new(ControlFlow::default()), exit: Cell::new(None), panic_error: Cell::new(None), last_events_cleared: Cell::new(Instant::now()), event_handler: Cell::new(None), event_buffer: RefCell::new(VecDeque::new()), } } /// Associate the application's event handler with the runner /// /// # Safety /// This is ignoring the lifetime of the application handler (which may not /// outlive the EventLoopRunner) and can lead to undefined behaviour if /// the handler is not cleared before the end of real lifetime. /// /// All public APIs that take an event handler (`run`, `run_on_demand`, /// `pump_events`) _must_ pair a call to `set_event_handler` with /// a call to `clear_event_handler` before returning to avoid /// undefined behaviour. pub(crate) unsafe fn set_event_handler(&self, f: F) where F: FnMut(Event), { // Erase closure lifetime. // SAFETY: Caller upholds that the lifetime of the closure is upheld. let f = unsafe { mem::transmute::)>, Box)>>(Box::new(f)) }; let old_event_handler = self.event_handler.replace(Some(f)); assert!(old_event_handler.is_none()); } pub(crate) fn clear_event_handler(&self) { self.event_handler.set(None); } pub(crate) fn reset_runner(&self) { let EventLoopRunner { thread_msg_target: _, interrupt_msg_dispatch, runner_state, panic_error, control_flow: _, exit, last_events_cleared: _, event_handler, event_buffer: _, } = self; interrupt_msg_dispatch.set(false); runner_state.set(RunnerState::Uninitialized); panic_error.set(None); exit.set(None); event_handler.set(None); } } /// State retrieval functions. impl EventLoopRunner { #[allow(unused)] pub fn thread_msg_target(&self) -> HWND { self.thread_msg_target } pub fn take_panic_error(&self) -> Result<(), PanicError> { match self.panic_error.take() { Some(err) => Err(err), None => Ok(()), } } pub fn set_control_flow(&self, control_flow: ControlFlow) { self.control_flow.set(control_flow) } pub fn control_flow(&self) -> ControlFlow { self.control_flow.get() } pub fn set_exit_code(&self, code: i32) { self.exit.set(Some(code)) } pub fn exit_code(&self) -> Option { self.exit.get() } pub fn clear_exit(&self) { self.exit.set(None); } pub fn should_buffer(&self) -> bool { let handler = self.event_handler.take(); let should_buffer = handler.is_none(); self.event_handler.set(handler); should_buffer } } /// Misc. functions impl EventLoopRunner { pub fn catch_unwind(&self, f: impl FnOnce() -> R) -> Option { let panic_error = self.panic_error.take(); if panic_error.is_none() { let result = panic::catch_unwind(panic::AssertUnwindSafe(f)); // Check to see if the panic error was set in a re-entrant call to catch_unwind inside // of `f`. If it was, that error takes priority. If it wasn't, check if our call to // catch_unwind caught any panics and set panic_error appropriately. match self.panic_error.take() { None => match result { Ok(r) => Some(r), Err(e) => { self.panic_error.set(Some(e)); None }, }, Some(e) => { self.panic_error.set(Some(e)); None }, } } else { self.panic_error.set(panic_error); None } } } /// Event dispatch functions. impl EventLoopRunner { pub(crate) fn prepare_wait(&self) { self.move_state_to(RunnerState::Idle); } pub(crate) fn wakeup(&self) { self.move_state_to(RunnerState::HandlingMainEvents); } pub(crate) fn send_event(&self, event: Event) { if let Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } = event { self.call_event_handler(event); // As a rule, to ensure that `pump_events` can't block an external event loop // for too long, we always guarantee that `pump_events` will return control to // the external loop asap after a `RedrawRequested` event is dispatched. self.interrupt_msg_dispatch.set(true); } else if self.should_buffer() { // If the runner is already borrowed, we're in the middle of an event loop invocation. // Add the event to a buffer to be processed later. self.event_buffer.borrow_mut().push_back(BufferedEvent::from_event(event)) } else { self.call_event_handler(event); self.dispatch_buffered_events(); } } pub(crate) fn loop_destroyed(&self) { self.move_state_to(RunnerState::Destroyed); } fn call_event_handler(&self, event: Event) { self.catch_unwind(|| { let mut event_handler = self.event_handler.take().expect( "either event handler is re-entrant (likely), or no event handler is registered \ (very unlikely)", ); event_handler(event); assert!(self.event_handler.replace(Some(event_handler)).is_none()); }); } fn dispatch_buffered_events(&self) { loop { // We do this instead of using a `while let` loop because if we use a `while let` // loop the reference returned `borrow_mut()` doesn't get dropped until the end // of the loop's body and attempts to add events to the event buffer while in // `process_event` will fail. let buffered_event_opt = self.event_buffer.borrow_mut().pop_front(); match buffered_event_opt { Some(e) => e.dispatch_event(|e| self.call_event_handler(e)), None => break, } } } /// Dispatch control flow events (`NewEvents`, `AboutToWait`, and /// `LoopExiting`) as necessary to bring the internal `RunnerState` to the /// new runner state. /// /// The state transitions are defined as follows: /// /// ```text /// Uninitialized /// | /// V /// Idle /// ^ | /// | V /// HandlingMainEvents /// | /// V /// Destroyed /// ``` /// /// Attempting to transition back to `Uninitialized` will result in a panic. Attempting to /// transition *from* `Destroyed` will also result in a panic. Transitioning to the current /// state is a no-op. Even if the `new_runner_state` isn't the immediate next state in the /// runner state machine (e.g. `self.runner_state == HandlingMainEvents` and /// `new_runner_state == Idle`), the intermediate state transitions will still be executed. fn move_state_to(&self, new_runner_state: RunnerState) { use RunnerState::{Destroyed, HandlingMainEvents, Idle, Uninitialized}; match (self.runner_state.replace(new_runner_state), new_runner_state) { (Uninitialized, Uninitialized) | (Idle, Idle) | (HandlingMainEvents, HandlingMainEvents) | (Destroyed, Destroyed) => (), // State transitions that initialize the event loop. (Uninitialized, HandlingMainEvents) => { self.call_new_events(true); }, (Uninitialized, Idle) => { self.call_new_events(true); self.call_event_handler(Event::AboutToWait); self.last_events_cleared.set(Instant::now()); }, (Uninitialized, Destroyed) => { self.call_new_events(true); self.call_event_handler(Event::AboutToWait); self.last_events_cleared.set(Instant::now()); self.call_event_handler(Event::LoopExiting); }, (_, Uninitialized) => panic!("cannot move state to Uninitialized"), // State transitions that start the event handling process. (Idle, HandlingMainEvents) => { self.call_new_events(false); }, (Idle, Destroyed) => { self.call_event_handler(Event::LoopExiting); }, (HandlingMainEvents, Idle) => { // This is always the last event we dispatch before waiting for new events self.call_event_handler(Event::AboutToWait); self.last_events_cleared.set(Instant::now()); }, (HandlingMainEvents, Destroyed) => { self.call_event_handler(Event::AboutToWait); self.last_events_cleared.set(Instant::now()); self.call_event_handler(Event::LoopExiting); }, (Destroyed, _) => panic!("cannot move state from Destroyed"), } } fn call_new_events(&self, init: bool) { let start_cause = match (init, self.control_flow(), self.exit.get()) { (true, ..) => StartCause::Init, (false, ControlFlow::Poll, None) => StartCause::Poll, (false, _, Some(_)) | (false, ControlFlow::Wait, None) => StartCause::WaitCancelled { requested_resume: None, start: self.last_events_cleared.get(), }, (false, ControlFlow::WaitUntil(requested_resume), None) => { if Instant::now() < requested_resume { StartCause::WaitCancelled { requested_resume: Some(requested_resume), start: self.last_events_cleared.get(), } } else { StartCause::ResumeTimeReached { requested_resume, start: self.last_events_cleared.get(), } } }, }; self.call_event_handler(Event::NewEvents(start_cause)); // NB: For consistency all platforms must emit a 'resumed' event even though Windows // applications don't themselves have a formal suspend/resume lifecycle. if init { self.call_event_handler(Event::Resumed); } self.dispatch_buffered_events(); } } impl BufferedEvent { pub fn from_event(event: Event) -> BufferedEvent { match event { Event::WindowEvent { event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer }, window_id, } => BufferedEvent::ScaleFactorChanged( window_id, scale_factor, *inner_size_writer.new_inner_size.upgrade().unwrap().lock().unwrap(), ), event => BufferedEvent::Event(event), } } pub fn dispatch_event(self, dispatch: impl FnOnce(Event)) { match self { Self::Event(event) => dispatch(event), Self::ScaleFactorChanged(window_id, scale_factor, new_inner_size) => { let user_new_inner_size = Arc::new(Mutex::new(new_inner_size)); dispatch(Event::WindowEvent { window_id, event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade( &user_new_inner_size, )), }, }); let inner_size = *user_new_inner_size.lock().unwrap(); drop(user_new_inner_size); if inner_size != new_inner_size { let window_flags = unsafe { let userdata = get_window_long(window_id.0.into(), GWL_USERDATA) as *mut WindowData; (*userdata).window_state_lock().window_flags }; window_flags.set_size((window_id.0).0, inner_size); } }, } } } winit-0.30.9/src/platform_impl/windows/event_loop.rs000064400000000000000000003227441046102023000207310ustar 00000000000000#![allow(non_snake_case)] mod runner; use std::cell::Cell; use std::collections::VecDeque; use std::ffi::c_void; use std::marker::PhantomData; use std::os::windows::io::{AsRawHandle as _, FromRawHandle as _, OwnedHandle, RawHandle}; use std::rc::Rc; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::{Arc, Mutex, MutexGuard}; use std::time::{Duration, Instant}; use std::{mem, panic, ptr}; use crate::utils::Lazy; use windows_sys::Win32::Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE; use windows_sys::Win32::Foundation::{ GetLastError, FALSE, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WAIT_FAILED, WPARAM, }; use windows_sys::Win32::Graphics::Gdi::{ GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE, }; use windows_sys::Win32::System::Ole::RevokeDragDrop; use windows_sys::Win32::System::Threading::{ CreateWaitableTimerExW, GetCurrentThreadId, SetWaitableTimer, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, INFINITE, TIMER_ALL_ACCESS, }; use windows_sys::Win32::UI::Controls::{HOVER_DEFAULT, WM_MOUSELEAVE}; use windows_sys::Win32::UI::Input::Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW}; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ ReleaseCapture, SetCapture, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT, }; use windows_sys::Win32::UI::Input::Pointer::{ POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE, }; use windows_sys::Win32::UI::Input::Touch::{ CloseTouchInputHandle, GetTouchInputInfo, TOUCHEVENTF_DOWN, TOUCHEVENTF_MOVE, TOUCHEVENTF_UP, TOUCHINPUT, }; use windows_sys::Win32::UI::Input::{RAWINPUT, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE}; use windows_sys::Win32::UI::WindowsAndMessaging::{ CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetClientRect, GetCursorPos, GetMenu, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW, RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLINPUT, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE, }; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::error::EventLoopError; use crate::event::{ DeviceEvent, Event, Force, Ime, InnerSizeWriter, RawKeyEvent, Touch, TouchPhase, WindowEvent, }; use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents, EventLoopClosed}; use crate::keyboard::ModifiersState; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::platform::dark_mode::try_theme; use crate::platform_impl::platform::dpi::{become_dpi_aware, dpi_to_scale_factor}; use crate::platform_impl::platform::drop_handler::FileDropHandler; use crate::platform_impl::platform::icon::WinCursor; use crate::platform_impl::platform::ime::ImeContext; use crate::platform_impl::platform::keyboard::KeyEventBuilder; use crate::platform_impl::platform::keyboard_layout::LAYOUT_CACHE; use crate::platform_impl::platform::monitor::{self, MonitorHandle}; use crate::platform_impl::platform::window::InitData; use crate::platform_impl::platform::window_state::{ CursorFlags, ImeState, WindowFlags, WindowState, }; use crate::platform_impl::platform::{ raw_input, util, wrap_device_id, Fullscreen, WindowId, DEVICE_ID, }; use crate::window::{ CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId, }; use runner::{EventLoopRunner, EventLoopRunnerShared}; use super::window::set_skip_taskbar; use super::SelectedCursor; /// some backends like macos uses an uninhabited `Never` type, /// on windows, `UserEvent`s are also dispatched through the /// WNDPROC callback, and due to the re-entrant nature of the /// callback, recursively delivered events must be queued in a /// buffer, the current implementation put this queue in /// `EventLoopRunner`, which is shared between the event pumping /// loop and the callback. because it's hard to decide from the /// outside whether a event needs to be buffered, I decided not /// use `Event` for the shared runner state, but use unit /// as a placeholder so user events can be buffered as usual, /// the real `UserEvent` is pulled from the mpsc channel directly /// when the placeholder event is delivered to the event handler pub(crate) struct UserEventPlaceholder; // here below, the generic `EventLoopRunnerShared` is replaced with // `EventLoopRunnerShared` so we can get rid // of the generic parameter T in types which don't depend on T. // this is the approach which requires minimum changes to current // backend implementation. it should be considered transitional // and should be refactored and cleaned up eventually, I hope. pub(crate) struct WindowData { pub window_state: Arc>, pub event_loop_runner: EventLoopRunnerShared, pub key_event_builder: KeyEventBuilder, pub _file_drop_handler: Option, pub userdata_removed: Cell, pub recurse_depth: Cell, } impl WindowData { fn send_event(&self, event: Event) { self.event_loop_runner.send_event(event); } fn window_state_lock(&self) -> MutexGuard<'_, WindowState> { self.window_state.lock().unwrap() } } struct ThreadMsgTargetData { event_loop_runner: EventLoopRunnerShared, } impl ThreadMsgTargetData { fn send_event(&self, event: Event) { self.event_loop_runner.send_event(event); } } /// The result of a subclass procedure (the message handling callback) #[derive(Clone, Copy)] pub(crate) enum ProcResult { DefWindowProc(WPARAM), Value(isize), } pub struct EventLoop { user_event_sender: Sender, user_event_receiver: Receiver, window_target: RootAEL, msg_hook: Option bool + 'static>>, // It is a timer used on timed waits. // It is created lazily in case if we have `ControlFlow::WaitUntil`. // Keep it as a field to avoid recreating it on every `ControlFlow::WaitUntil`. high_resolution_timer: Option, } pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) any_thread: bool, pub(crate) dpi_aware: bool, pub(crate) msg_hook: Option bool + 'static>>, } impl Default for PlatformSpecificEventLoopAttributes { fn default() -> Self { Self { any_thread: false, dpi_aware: true, msg_hook: None } } } pub struct ActiveEventLoop { thread_id: u32, thread_msg_target: HWND, pub(crate) runner_shared: EventLoopRunnerShared, } impl EventLoop { pub(crate) fn new( attributes: &mut PlatformSpecificEventLoopAttributes, ) -> Result { let thread_id = unsafe { GetCurrentThreadId() }; if !attributes.any_thread && thread_id != main_thread_id() { panic!( "Initializing the event loop outside of the main thread is a significant \ cross-platform compatibility hazard. If you absolutely need to create an \ EventLoop on a different thread, you can use the \ `EventLoopBuilderExtWindows::any_thread` function." ); } if attributes.dpi_aware { become_dpi_aware(); } let thread_msg_target = create_event_target_window(); let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target)); let (user_event_sender, user_event_receiver) = mpsc::channel(); insert_event_target_window_data(thread_msg_target, runner_shared.clone()); raw_input::register_all_mice_and_keyboards_for_raw_input( thread_msg_target, Default::default(), ); Ok(EventLoop { user_event_sender, user_event_receiver, window_target: RootAEL { p: ActiveEventLoop { thread_id, thread_msg_target, runner_shared }, _marker: PhantomData, }, msg_hook: attributes.msg_hook.take(), high_resolution_timer: None, }) } pub fn window_target(&self) -> &RootAEL { &self.window_target } pub fn run(mut self, event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootAEL), { self.run_on_demand(event_handler) } pub fn run_on_demand(&mut self, mut event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootAEL), { { let runner = &self.window_target.p.runner_shared; let event_loop_windows_ref = &self.window_target; let user_event_receiver = &self.user_event_receiver; // # Safety // We make sure to call runner.clear_event_handler() before // returning unsafe { runner.set_event_handler(move |event| { // the shared `EventLoopRunner` is not parameterized // `EventLoopProxy::send_event()` calls `PostMessage` // to wakeup and dispatch a placeholder `UserEvent`, // when we received the placeholder event here, the // real UserEvent(T) should already be put in the // mpsc channel and ready to be pulled let event = match event.map_nonuser_event() { Ok(non_user_event) => non_user_event, Err(_user_event_placeholder) => Event::UserEvent( user_event_receiver .try_recv() .expect("user event signaled but not received"), ), }; event_handler(event, event_loop_windows_ref) }); } } let exit_code = loop { self.wait_for_messages(None); // wait_for_messages calls user application before and after waiting // so it may have decided to exit. if let Some(code) = self.exit_code() { break code; } self.dispatch_peeked_messages(); if let Some(code) = self.exit_code() { break code; } }; let runner = &self.window_target.p.runner_shared; runner.loop_destroyed(); // # Safety // We assume that this will effectively call `runner.clear_event_handler()` // to meet the safety requirements for calling `runner.set_event_handler()` above. runner.reset_runner(); if exit_code == 0 { Ok(()) } else { Err(EventLoopError::ExitFailure(exit_code)) } } pub fn pump_events(&mut self, timeout: Option, mut event_handler: F) -> PumpStatus where F: FnMut(Event, &RootAEL), { { let runner = &self.window_target.p.runner_shared; let event_loop_windows_ref = &self.window_target; let user_event_receiver = &self.user_event_receiver; // # Safety // We make sure to call runner.clear_event_handler() before // returning // // Note: we're currently assuming nothing can panic and unwind // to leave the runner in an unsound state with an associated // event handler. unsafe { runner.set_event_handler(move |event| { let event = match event.map_nonuser_event() { Ok(non_user_event) => non_user_event, Err(_user_event_placeholder) => Event::UserEvent( user_event_receiver .recv() .expect("user event signaled but not received"), ), }; event_handler(event, event_loop_windows_ref) }); runner.wakeup(); } } if self.exit_code().is_none() { self.wait_for_messages(timeout); } // wait_for_messages calls user application before and after waiting // so it may have decided to exit. if self.exit_code().is_none() { self.dispatch_peeked_messages(); } let runner = &self.window_target.p.runner_shared; let status = if let Some(code) = runner.exit_code() { runner.loop_destroyed(); // Immediately reset the internal state for the loop to allow // the loop to be run more than once. runner.reset_runner(); PumpStatus::Exit(code) } else { runner.prepare_wait(); PumpStatus::Continue }; // We wait until we've checked for an exit status before clearing the // application callback, in case we need to dispatch a LoopExiting event // // # Safety // This pairs up with our call to `runner.set_event_handler` and ensures // the application's callback can't be held beyond its lifetime. runner.clear_event_handler(); status } /// Waits until new event messages arrive to be peeked. /// Doesn't peek messages itself. /// /// Parameter timeout is optional. This method would wait for the smaller timeout /// between the argument and a timeout from control flow. fn wait_for_messages(&mut self, timeout: Option) { let runner = &self.window_target.p.runner_shared; // We aim to be consistent with the MacOS backend which has a RunLoop // observer that will dispatch AboutToWait when about to wait for // events, and NewEvents after the RunLoop wakes up. // // We emulate similar behaviour by treating `MsgWaitForMultipleObjectsEx` as our wait // point and wake up point (when it returns) and we drain all other // pending messages via `PeekMessage` until we come back to "wait" via // `MsgWaitForMultipleObjectsEx`. // runner.prepare_wait(); wait_for_messages_impl(&mut self.high_resolution_timer, runner.control_flow(), timeout); // Before we potentially exit, make sure to consistently emit an event for the wake up runner.wakeup(); } /// Dispatch all queued messages via `PeekMessageW` fn dispatch_peeked_messages(&mut self) { let runner = &self.window_target.p.runner_shared; // We generally want to continue dispatching all pending messages // but we also allow dispatching to be interrupted as a means to // ensure the `pump_events` won't indefinitely block an external // event loop if there are too many pending events. This interrupt // flag will be set after dispatching `RedrawRequested` events. runner.interrupt_msg_dispatch.set(false); // # Safety // The Windows API has no documented requirement for bitwise // initializing a `MSG` struct (it can be uninitialized memory for the C // API) and there's no API to construct or initialize a `MSG`. This // is the simplest way avoid uninitialized memory in Rust let mut msg: MSG = unsafe { mem::zeroed() }; loop { unsafe { if PeekMessageW(&mut msg, 0, 0, 0, PM_REMOVE) == false.into() { break; } let handled = if let Some(callback) = self.msg_hook.as_deref_mut() { callback(&mut msg as *mut _ as *mut _) } else { false }; if !handled { TranslateMessage(&msg); DispatchMessageW(&msg); } } if let Err(payload) = runner.take_panic_error() { runner.reset_runner(); panic::resume_unwind(payload); } if let Some(_code) = runner.exit_code() { break; } if runner.interrupt_msg_dispatch.get() { break; } } } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { target_window: self.window_target.p.thread_msg_target, event_send: self.user_event_sender.clone(), } } fn exit_code(&self) -> Option { self.window_target.p.exit_code() } } impl ActiveEventLoop { #[inline(always)] pub(crate) fn create_thread_executor(&self) -> EventLoopThreadExecutor { EventLoopThreadExecutor { thread_id: self.thread_id, target_window: self.thread_msg_target } } pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor { let inner = match WinCursor::new(&source.inner.0) { Ok(cursor) => cursor, Err(err) => { tracing::warn!("Failed to create custom cursor: {err}"); WinCursor::Failed }, }; RootCustomCursor { inner } } // TODO: Investigate opportunities for caching pub fn available_monitors(&self) -> VecDeque { monitor::available_monitors() } pub fn primary_monitor(&self) -> Option { let monitor = monitor::primary_monitor(); Some(monitor) } #[cfg(feature = "rwh_05")] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Windows(rwh_05::WindowsDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Windows(rwh_06::WindowsDisplayHandle::new())) } pub fn listen_device_events(&self, allowed: DeviceEvents) { raw_input::register_all_mice_and_keyboards_for_raw_input(self.thread_msg_target, allowed); } pub fn system_theme(&self) -> Option { Some(if super::dark_mode::should_use_dark_mode() { Theme::Dark } else { Theme::Light }) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.runner_shared.set_control_flow(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.runner_shared.control_flow() } pub(crate) fn exit(&self) { self.runner_shared.set_exit_code(0) } pub(crate) fn exiting(&self) -> bool { self.runner_shared.exit_code().is_some() } pub(crate) fn clear_exit(&self) { self.runner_shared.clear_exit(); } pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle { OwnedDisplayHandle } fn exit_code(&self) -> Option { self.runner_shared.exit_code() } } #[derive(Clone)] pub(crate) struct OwnedDisplayHandle; impl OwnedDisplayHandle { #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::WindowsDisplayHandle::empty().into() } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::WindowsDisplayHandle::new().into()) } } /// Returns the id of the main thread. /// /// Windows has no real API to check if the current executing thread is the "main thread", unlike /// macOS. /// /// Windows will let us look up the current thread's id, but there's no API that lets us check what /// the id of the main thread is. We would somehow need to get the main thread's id before a /// developer could spin off any other threads inside of the main entrypoint in order to emulate the /// capabilities of other platforms. /// /// We can get the id of the main thread by using CRT initialization. CRT initialization can be used /// to setup global state within a program. The OS will call a list of function pointers which /// assign values to a static variable. To have get a hold of the main thread id, we need to place /// our function pointer inside of the `.CRT$XCU` section so it is called before the main /// entrypoint. /// /// Full details of CRT initialization can be found here: /// fn main_thread_id() -> u32 { static mut MAIN_THREAD_ID: u32 = 0; // Function pointer used in CRT initialization section to set the above static field's value. // Mark as used so this is not removable. #[used] #[allow(non_upper_case_globals)] // Place the function pointer inside of CRT initialization section so it is loaded before // main entrypoint. // // See: https://doc.rust-lang.org/stable/reference/abi.html#the-link_section-attribute #[link_section = ".CRT$XCU"] static INIT_MAIN_THREAD_ID: unsafe fn() = { unsafe fn initer() { unsafe { MAIN_THREAD_ID = GetCurrentThreadId() }; } initer }; unsafe { MAIN_THREAD_ID } } /// Returns the minimum `Option`, taking into account that `None` /// equates to an infinite timeout, not a zero timeout (so can't just use /// `Option::min`) fn min_timeout(a: Option, b: Option) -> Option { a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))) } // Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs fn dur2timeout(dur: Duration) -> u32 { // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the // timeouts in windows APIs are typically u32 milliseconds. To translate, we // have two pieces to take care of: // // * Nanosecond precision is rounded up // * Greater than u32::MAX milliseconds (50 days) is rounded up to INFINITE (never time out). dur.as_secs() .checked_mul(1000) .and_then(|ms| ms.checked_add((dur.subsec_nanos() as u64) / 1_000_000)) .and_then( |ms| { if dur.subsec_nanos() % 1_000_000 > 0 { ms.checked_add(1) } else { Some(ms) } }, ) .map(|ms| if ms > u32::MAX as u64 { INFINITE } else { ms as u32 }) .unwrap_or(INFINITE) } impl Drop for EventLoop { fn drop(&mut self) { unsafe { DestroyWindow(self.window_target.p.thread_msg_target); } } } /// Set upper limit for waiting time to avoid overflows. /// I chose 50 days as a limit because it is used in dur2timeout. const FIFTY_DAYS: Duration = Duration::from_secs(50_u64 * 24 * 60 * 60); /// Waitable timers use 100 ns intervals to indicate due time. /// /// And there is no point waiting using other ways for such small timings /// because they are even less precise (can overshoot by few ms). const MIN_WAIT: Duration = Duration::from_nanos(100); fn create_high_resolution_timer() -> Option { unsafe { let handle: HANDLE = CreateWaitableTimerExW( ptr::null(), ptr::null(), CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS, ); // CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is supported only after // Win10 1803 but it is already default option for rustc // (std uses it to implement `std::thread::sleep`). if handle == 0 { None } else { Some(OwnedHandle::from_raw_handle(handle as *mut c_void)) } } } /// This function should not return error if parameters are valid /// but there is no guarantee about that at MSDN docs /// so we return result of GetLastError if fail. /// /// ## Safety /// /// timer must be a valid timer handle created by [create_high_resolution_timer]. /// timeout divided by 100 nanoseconds must be more than 0 and less than i64::MAX. unsafe fn set_high_resolution_timer(timer: RawHandle, timeout: Duration) -> Result<(), u32> { const INTERVAL_NS: u32 = MIN_WAIT.subsec_nanos(); const INTERVALS_IN_SEC: u64 = (Duration::from_secs(1).as_nanos() / INTERVAL_NS as u128) as u64; let intervals_to_wait: u64 = timeout.as_secs() * INTERVALS_IN_SEC + u64::from(timeout.subsec_nanos() / INTERVAL_NS); debug_assert!(intervals_to_wait < i64::MAX as u64, "Must be called with smaller duration",); // Use negative time to indicate relative time. let due_time: i64 = -(intervals_to_wait as i64); unsafe { let set_result = SetWaitableTimer(timer as HANDLE, &due_time, 0, None, ptr::null(), FALSE); if set_result != FALSE { Ok(()) } else { Err(GetLastError()) } } } /// Implementation detail of [EventLoop::wait_for_messages]. /// /// Does actual system-level waiting and doesn't process any messages itself, /// including winits internal notifications about waiting and new messages arrival. fn wait_for_messages_impl( high_resolution_timer: &mut Option, control_flow: ControlFlow, timeout: Option, ) { let timeout = { let control_flow_timeout = match control_flow { ControlFlow::Wait => None, ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::WaitUntil(wait_deadline) => { let start = Instant::now(); Some(wait_deadline.saturating_duration_since(start)) }, }; let timeout = min_timeout(timeout, control_flow_timeout); if timeout == Some(Duration::ZERO) { // Do not wait if we don't have time. return; } // Now we decided to wait so need to do some clamping // to avoid problems with overflow and calling WinAPI with invalid parameters. timeout .map(|t| t.min(FIFTY_DAYS)) // If timeout is less than minimally supported by Windows, // increase it to that minimum. Who want less than microsecond delays anyway? .map(|t| t.max(MIN_WAIT)) }; if timeout.is_some() && high_resolution_timer.is_none() { *high_resolution_timer = create_high_resolution_timer(); } let high_resolution_timer: Option = high_resolution_timer.as_ref().map(OwnedHandle::as_raw_handle); let use_timer: bool; if let (Some(handle), Some(timeout)) = (high_resolution_timer, timeout) { let res = unsafe { // Safety: handle can be Some only if we succeeded in creating high resolution // timer. We properly clamped timeout so it can be used as argument // to timer. set_high_resolution_timer(handle, timeout) }; if let Err(error_code) = res { // We successfully got timer but failed to set it? // Should be some bug in our code. tracing::trace!("Failed to set high resolution timer: last error {}", error_code); use_timer = false; } else { use_timer = true; } } else { use_timer = false; } unsafe { // Either: // 1. User wants to wait indefinely if timeout is not set. // 2. We failed to get and set high resolution timer and we need something instead of it. let wait_duration_ms = timeout.map(dur2timeout).unwrap_or(INFINITE); let (num_handles, raw_handles) = if use_timer { (1, [high_resolution_timer.unwrap()]) } else { (0, [ptr::null_mut()]) }; // We must use `QS_ALLINPUT` to wake on accessibility messages. let result = MsgWaitForMultipleObjectsEx( num_handles, raw_handles.as_ptr() as *const _, wait_duration_ms, QS_ALLINPUT, MWMO_INPUTAVAILABLE, ); if result == WAIT_FAILED { // Well, nothing smart to do in such case. // Treat it as spurious wake up. tracing::warn!("Failed to MsgWaitForMultipleObjectsEx: error code {}", GetLastError(),); } } } pub(crate) struct EventLoopThreadExecutor { thread_id: u32, target_window: HWND, } unsafe impl Send for EventLoopThreadExecutor {} unsafe impl Sync for EventLoopThreadExecutor {} impl EventLoopThreadExecutor { /// Check to see if we're in the parent event loop's thread. pub(super) fn in_event_loop_thread(&self) -> bool { let cur_thread_id = unsafe { GetCurrentThreadId() }; self.thread_id == cur_thread_id } /// Executes a function in the event loop thread. If we're already in the event loop thread, /// we just call the function directly. /// /// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is /// removed automatically if the callback receives a `WM_CLOSE` message for the window. /// /// Note that if you are using this to change some property of a window and updating /// `WindowState` then you should call this within the lock of `WindowState`. Otherwise the /// events may be sent to the other thread in different order to the one in which you set /// `WindowState`, leaving them out of sync. /// /// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent /// to the unstable FnBox. pub(super) fn execute_in_thread(&self, mut function: F) where F: FnMut() + Send + 'static, { unsafe { if self.in_event_loop_thread() { function(); } else { // We double-box because the first box is a fat pointer. let boxed2: ThreadExecFn = Box::new(Box::new(function)); let raw = Box::into_raw(boxed2); let res = PostMessageW(self.target_window, EXEC_MSG_ID.get(), raw as usize, 0); assert!(res != false.into(), "PostMessage failed; is the messages queue full?"); } } } } type ThreadExecFn = Box>; pub struct EventLoopProxy { target_window: HWND, event_send: Sender, } unsafe impl Send for EventLoopProxy {} impl Clone for EventLoopProxy { fn clone(&self) -> Self { Self { target_window: self.target_window, event_send: self.event_send.clone() } } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.event_send .send(event) .map(|result| { unsafe { PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) }; result }) .map_err(|e| EventLoopClosed(e.0)) } } /// A lazily-initialized window message ID. pub struct LazyMessageId { /// The ID. id: AtomicU32, /// The name of the message. name: &'static str, } /// An invalid custom window ID. const INVALID_ID: u32 = 0x0; impl LazyMessageId { /// Create a new `LazyId`. const fn new(name: &'static str) -> Self { Self { id: AtomicU32::new(INVALID_ID), name } } /// Get the message ID. pub fn get(&self) -> u32 { // Load the ID. let id = self.id.load(Ordering::Relaxed); if id != INVALID_ID { return id; } // Register the message. // SAFETY: We are sure that the pointer is a valid C string ending with '\0'. assert!(self.name.ends_with('\0')); let new_id = unsafe { RegisterWindowMessageA(self.name.as_ptr()) }; assert_ne!( new_id, 0, "RegisterWindowMessageA returned zero for '{}': {}", self.name, std::io::Error::last_os_error() ); // Store the new ID. Since `RegisterWindowMessageA` returns the same value for any given // string, the target value will always either be a). `INVALID_ID` or b). the // correct ID. Therefore a compare-and-swap operation here (or really any // consideration) is never necessary. self.id.store(new_id, Ordering::Relaxed); new_id } } // Message sent by the `EventLoopProxy` when we want to wake up the thread. // WPARAM and LPARAM are unused. static USER_EVENT_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::WakeupMsg\0"); // Message sent when we want to execute a closure in the thread. // WPARAM contains a Box> that must be retrieved with `Box::from_raw`, // and LPARAM is unused. static EXEC_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::ExecMsg\0"); // Message sent by a `Window` when it wants to be destroyed by the main thread. // WPARAM and LPARAM are unused. pub(crate) static DESTROY_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::DestroyMsg\0"); // WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the // documentation in the `window_state` module for more information. pub(crate) static SET_RETAIN_STATE_ON_SIZE_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::SetRetainMaximized\0"); static THREAD_EVENT_TARGET_WINDOW_CLASS: Lazy> = Lazy::new(|| util::encode_wide("Winit Thread Event Target")); /// When the taskbar is created, it registers a message with the "TaskbarCreated" string and then /// broadcasts this message to all top-level windows pub(crate) static TASKBAR_CREATED: LazyMessageId = LazyMessageId::new("TaskbarCreated\0"); fn create_event_target_window() -> HWND { use windows_sys::Win32::UI::WindowsAndMessaging::{CS_HREDRAW, CS_VREDRAW}; unsafe { let class = WNDCLASSEXW { cbSize: mem::size_of::() as u32, style: CS_HREDRAW | CS_VREDRAW, lpfnWndProc: Some(thread_event_target_callback), cbClsExtra: 0, cbWndExtra: 0, hInstance: util::get_instance_handle(), hIcon: 0, hCursor: 0, // must be null in order for cursor state to work properly hbrBackground: 0, lpszMenuName: ptr::null(), lpszClassName: THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(), hIconSm: 0, }; RegisterClassExW(&class); } unsafe { // WS_EX_TOOLWINDOW prevents this window from ever showing up in the taskbar, which // we want to avoid. If you remove this style, this window won't show up in the // taskbar *initially*, but it can show up at some later point. This can sometimes // happen on its own after several hours have passed, although this has proven // difficult to reproduce. Alternatively, it can be manually triggered by killing // `explorer.exe` and then starting the process back up. // It is unclear why the bug is triggered by waiting for several hours. let window = CreateWindowExW( WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW, THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(), ptr::null(), WS_OVERLAPPED, 0, 0, 0, 0, 0, 0, util::get_instance_handle(), ptr::null(), ); super::set_window_long( window, GWL_STYLE, // The window technically has to be visible to receive WM_PAINT messages (which are // used for delivering events during resizes), but it isn't displayed to // the user because of the LAYERED style. (WS_VISIBLE | WS_POPUP) as isize, ); window } } fn insert_event_target_window_data( thread_msg_target: HWND, event_loop_runner: EventLoopRunnerShared, ) { let userdata = ThreadMsgTargetData { event_loop_runner }; let input_ptr = Box::into_raw(Box::new(userdata)); unsafe { super::set_window_long(thread_msg_target, GWL_USERDATA, input_ptr as isize) }; } /// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of /// the window. unsafe fn capture_mouse(window: HWND, window_state: &mut WindowState) { window_state.mouse.capture_count += 1; unsafe { SetCapture(window) }; } /// Release mouse input, stopping windows on this thread from receiving mouse input when the cursor /// is outside the window. unsafe fn release_mouse(mut window_state: MutexGuard<'_, WindowState>) { window_state.mouse.capture_count = window_state.mouse.capture_count.saturating_sub(1); if window_state.mouse.capture_count == 0 { // ReleaseCapture() causes a WM_CAPTURECHANGED where we lock the window_state. drop(window_state); unsafe { ReleaseCapture() }; } } fn normalize_pointer_pressure(pressure: u32) -> Option { match pressure { 1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)), _ => None, } } /// Emit a `ModifiersChanged` event whenever modifiers have changed. /// Returns the current modifier state fn update_modifiers(window: HWND, userdata: &WindowData) { use crate::event::WindowEvent::ModifiersChanged; let modifiers = { let mut layouts = LAYOUT_CACHE.lock().unwrap(); layouts.get_agnostic_mods() }; let mut window_state = userdata.window_state.lock().unwrap(); if window_state.modifiers_state != modifiers { window_state.modifiers_state = modifiers; // Drop lock drop(window_state); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ModifiersChanged(modifiers.into()), }); } } unsafe fn gain_active_focus(window: HWND, userdata: &WindowData) { use crate::event::WindowEvent::Focused; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Focused(true), }); } unsafe fn lose_active_focus(window: HWND, userdata: &WindowData) { use crate::event::WindowEvent::{Focused, ModifiersChanged}; userdata.window_state_lock().modifiers_state = ModifiersState::empty(); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ModifiersChanged(ModifiersState::empty().into()), }); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Focused(false), }); } /// Any window whose callback is configured to this function will have its events propagated /// through the events loop of the thread the window was created in. // This is the callback that is called by `DispatchMessage` in the events loop. // // Returning 0 tells the Win32 API that the message has been processed. // FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary pub(super) unsafe extern "system" fn public_window_callback( window: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM, ) -> LRESULT { let userdata = unsafe { super::get_window_long(window, GWL_USERDATA) }; let userdata_ptr = match (userdata, msg) { (0, WM_NCCREATE) => { let createstruct = unsafe { &mut *(lparam as *mut CREATESTRUCTW) }; let initdata = unsafe { &mut *(createstruct.lpCreateParams as *mut InitData<'_>) }; let result = match unsafe { initdata.on_nccreate(window) } { Some(userdata) => unsafe { super::set_window_long(window, GWL_USERDATA, userdata as _); DefWindowProcW(window, msg, wparam, lparam) }, None => -1, // failed to create the window }; return result; }, // Getting here should quite frankly be impossible, // but we'll make window creation fail here just in case. (0, WM_CREATE) => return -1, (_, WM_CREATE) => unsafe { let createstruct = &mut *(lparam as *mut CREATESTRUCTW); let initdata = createstruct.lpCreateParams; let initdata = &mut *(initdata as *mut InitData<'_>); initdata.on_create(); return DefWindowProcW(window, msg, wparam, lparam); }, (0, _) => return unsafe { DefWindowProcW(window, msg, wparam, lparam) }, _ => userdata as *mut WindowData, }; let (result, userdata_removed, recurse_depth) = { let userdata = unsafe { &*(userdata_ptr) }; userdata.recurse_depth.set(userdata.recurse_depth.get() + 1); let result = unsafe { public_window_callback_inner(window, msg, wparam, lparam, userdata) }; let userdata_removed = userdata.userdata_removed.get(); let recurse_depth = userdata.recurse_depth.get() - 1; userdata.recurse_depth.set(recurse_depth); (result, userdata_removed, recurse_depth) }; if userdata_removed && recurse_depth == 0 { drop(unsafe { Box::from_raw(userdata_ptr) }); } result } unsafe fn public_window_callback_inner( window: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM, userdata: &WindowData, ) -> LRESULT { let mut result = ProcResult::DefWindowProc(wparam); // Send new modifiers before sending key events. let mods_changed_callback = || match msg { WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP => { update_modifiers(window, userdata); result = ProcResult::Value(0); }, _ => (), }; userdata .event_loop_runner .catch_unwind(mods_changed_callback) .unwrap_or_else(|| result = ProcResult::Value(-1)); let keyboard_callback = || { use crate::event::WindowEvent::KeyboardInput; let events = userdata.key_event_builder.process_message(window, msg, wparam, lparam, &mut result); for event in events { userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: KeyboardInput { device_id: DEVICE_ID, event: event.event, is_synthetic: event.is_synthetic, }, }); } }; userdata .event_loop_runner .catch_unwind(keyboard_callback) .unwrap_or_else(|| result = ProcResult::Value(-1)); // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing // the closure to catch_unwind directly so that the match body indentation wouldn't change and // the git blame and history would be preserved. let callback = || match msg { WM_NCCALCSIZE => { let window_flags = userdata.window_state_lock().window_flags; if wparam == 0 || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { result = ProcResult::DefWindowProc(wparam); return; } let params = unsafe { &mut *(lparam as *mut NCCALCSIZE_PARAMS) }; if util::is_maximized(window) { // Limit the window size when maximized to the current monitor. // Otherwise it would include the non-existent decorations. // // Use `MonitorFromRect` instead of `MonitorFromWindow` to select // the correct monitor here. // See https://github.com/MicrosoftEdge/WebView2Feedback/issues/2549 let monitor = unsafe { MonitorFromRect(¶ms.rgrc[0], MONITOR_DEFAULTTONULL) }; if let Ok(monitor_info) = monitor::get_monitor_info(monitor) { params.rgrc[0] = monitor_info.monitorInfo.rcWork; } } else if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) { // Extend the client area to cover the whole non-client area. // https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize#remarks // // HACK(msiglreith): To add the drop shadow we slightly tweak the non-client area. // This leads to a small black 1px border on the top. Adding a margin manually // on all 4 borders would result in the caption getting drawn by the DWM. // // Another option would be to allow the DWM to paint inside the client area. // Unfortunately this results in janky resize behavior, where the compositor is // ahead of the window surface. Currently, there seems no option to achieve this // with the Windows API. params.rgrc[0].top += 1; params.rgrc[0].bottom += 1; } result = ProcResult::Value(0); }, WM_ENTERSIZEMOVE => { userdata .window_state_lock() .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); result = ProcResult::Value(0); }, WM_EXITSIZEMOVE => { let mut state = userdata.window_state_lock(); if state.dragging { state.dragging = false; unsafe { PostMessageW(window, WM_LBUTTONUP, 0, lparam) }; } state.set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_IN_SIZE_MOVE)); result = ProcResult::Value(0); }, WM_NCLBUTTONDOWN => { if wparam == HTCAPTION as _ { unsafe { PostMessageW(window, WM_MOUSEMOVE, 0, lparam) }; } result = ProcResult::DefWindowProc(wparam); }, WM_CLOSE => { use crate::event::WindowEvent::CloseRequested; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CloseRequested, }); result = ProcResult::Value(0); }, WM_DESTROY => { use crate::event::WindowEvent::Destroyed; unsafe { RevokeDragDrop(window) }; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Destroyed, }); result = ProcResult::Value(0); }, WM_NCDESTROY => { unsafe { super::set_window_long(window, GWL_USERDATA, 0) }; userdata.userdata_removed.set(true); result = ProcResult::Value(0); }, WM_PAINT => { userdata.window_state_lock().redraw_requested = userdata.event_loop_runner.should_buffer(); // We'll buffer only in response to `UpdateWindow`, if win32 decides to redraw the // window outside the normal flow of the event loop. This way mark event as handled // and request a normal redraw with `RedrawWindow`. if !userdata.event_loop_runner.should_buffer() { userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::RedrawRequested, }); } // NOTE: calling `RedrawWindow` during `WM_PAINT` does nothing, since to mark // `WM_PAINT` as handled we should call the `DefWindowProcW`. Call it and check whether // user asked for redraw during `RedrawRequested` event handling and request it again // after marking `WM_PAINT` as handled. result = ProcResult::Value(unsafe { DefWindowProcW(window, msg, wparam, lparam) }); if std::mem::take(&mut userdata.window_state_lock().redraw_requested) { unsafe { RedrawWindow(window, ptr::null(), 0, RDW_INTERNALPAINT) }; } }, WM_WINDOWPOSCHANGING => { let mut window_state = userdata.window_state_lock(); if let Some(ref mut fullscreen) = window_state.fullscreen { let window_pos = unsafe { &mut *(lparam as *mut WINDOWPOS) }; let new_rect = RECT { left: window_pos.x, top: window_pos.y, right: window_pos.x + window_pos.cx, bottom: window_pos.y + window_pos.cy, }; const NOMOVE_OR_NOSIZE: u32 = SWP_NOMOVE | SWP_NOSIZE; let new_rect = if window_pos.flags & NOMOVE_OR_NOSIZE != 0 { let cur_rect = util::WindowArea::Outer.get_rect(window).expect( "Unexpected GetWindowRect failure; please report this error to \ rust-windowing/winit", ); match window_pos.flags & NOMOVE_OR_NOSIZE { NOMOVE_OR_NOSIZE => None, SWP_NOMOVE => Some(RECT { left: cur_rect.left, top: cur_rect.top, right: cur_rect.left + window_pos.cx, bottom: cur_rect.top + window_pos.cy, }), SWP_NOSIZE => Some(RECT { left: window_pos.x, top: window_pos.y, right: window_pos.x - cur_rect.left + cur_rect.right, bottom: window_pos.y - cur_rect.top + cur_rect.bottom, }), _ => unreachable!(), } } else { Some(new_rect) }; if let Some(new_rect) = new_rect { let new_monitor = unsafe { MonitorFromRect(&new_rect, MONITOR_DEFAULTTONULL) }; match fullscreen { Fullscreen::Borderless(ref mut fullscreen_monitor) => { if new_monitor != 0 && fullscreen_monitor .as_ref() .map(|monitor| new_monitor != monitor.hmonitor()) .unwrap_or(true) { if let Ok(new_monitor_info) = monitor::get_monitor_info(new_monitor) { let new_monitor_rect = new_monitor_info.monitorInfo.rcMonitor; window_pos.x = new_monitor_rect.left; window_pos.y = new_monitor_rect.top; window_pos.cx = new_monitor_rect.right - new_monitor_rect.left; window_pos.cy = new_monitor_rect.bottom - new_monitor_rect.top; } *fullscreen_monitor = Some(MonitorHandle::new(new_monitor)); } }, Fullscreen::Exclusive(ref video_mode) => { let old_monitor = video_mode.monitor.hmonitor(); if let Ok(old_monitor_info) = monitor::get_monitor_info(old_monitor) { let old_monitor_rect = old_monitor_info.monitorInfo.rcMonitor; window_pos.x = old_monitor_rect.left; window_pos.y = old_monitor_rect.top; window_pos.cx = old_monitor_rect.right - old_monitor_rect.left; window_pos.cy = old_monitor_rect.bottom - old_monitor_rect.top; } }, } } } result = ProcResult::Value(0); }, // WM_MOVE supplies client area positions, so we send Moved here instead. WM_WINDOWPOSCHANGED => { use crate::event::WindowEvent::Moved; let windowpos = lparam as *const WINDOWPOS; if unsafe { (*windowpos).flags & SWP_NOMOVE != SWP_NOMOVE } { let physical_position = unsafe { PhysicalPosition::new((*windowpos).x, (*windowpos).y) }; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Moved(physical_position), }); } // This is necessary for us to still get sent WM_SIZE. result = ProcResult::DefWindowProc(wparam); }, WM_SIZE => { use crate::event::WindowEvent::Resized; let w = super::loword(lparam as u32) as u32; let h = super::hiword(lparam as u32) as u32; let physical_size = PhysicalSize::new(w, h); let event = Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Resized(physical_size), }; { let mut w = userdata.window_state_lock(); // See WindowFlags::MARKER_RETAIN_STATE_ON_SIZE docs for info on why this `if` check // exists. if !w.window_flags().contains(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE) { let maximized = wparam == SIZE_MAXIMIZED as usize; w.set_window_flags_in_place(|f| f.set(WindowFlags::MAXIMIZED, maximized)); } } userdata.send_event(event); result = ProcResult::Value(0); }, WM_SIZING => { /// Calculate the amount to add to round `value` to the nearest multiple of `increment`. fn snap_to_nearest_increment_delta(value: i32, increment: i32) -> i32 { let half_one = increment / 2; let half_two = increment - half_one; half_one - (value - half_two) % increment } let scale_factor = userdata.window_state_lock().scale_factor; let Some(inc) = userdata .window_state_lock() .resize_increments .map(|inc| inc.to_physical(scale_factor)) .filter(|inc| inc.width > 0 && inc.height > 0) else { result = ProcResult::Value(0); return; }; let side = wparam as u32; // The desired new size of the window, decorations included. let rect = unsafe { &mut *(lparam as *mut RECT) }; // We need to calculate the dimensions of the window decorations to get the true // size of the window's contents let adj_rect = userdata .window_state_lock() .window_flags .adjust_rect(window, *rect) .unwrap_or(*rect); let deco_width = rect.left - adj_rect.left + adj_rect.right - rect.right; let deco_height = rect.top - adj_rect.top + adj_rect.bottom - rect.bottom; let width = rect.right - rect.left - deco_width; let height = rect.bottom - rect.top - deco_height; let mut width_delta = snap_to_nearest_increment_delta(width, inc.width); let mut height_delta = snap_to_nearest_increment_delta(height, inc.height); // Windows won't bound check the value of `rect` after we're done here, so we // have to check manually. If the width/height we snap to would go out of bounds, just // set it equal to the min/max bound. let min_size = userdata.window_state_lock().min_size.map(|size| size.to_physical(scale_factor)); let max_size = userdata.window_state_lock().max_size.map(|size| size.to_physical(scale_factor)); let final_width = width + width_delta; let final_height = height + height_delta; if let Some(min_size) = min_size { if final_width < min_size.width { width_delta += min_size.width - final_width; } if final_height < min_size.height { height_delta += min_size.height - final_height; } } if let Some(max_size) = max_size { if final_width > max_size.width { width_delta -= final_width - max_size.width; } if final_height > max_size.height { height_delta -= final_height - max_size.height; } } match side { WMSZ_LEFT | WMSZ_BOTTOMLEFT | WMSZ_TOPLEFT => { rect.left -= width_delta; }, WMSZ_RIGHT | WMSZ_BOTTOMRIGHT | WMSZ_TOPRIGHT => { rect.right += width_delta; }, _ => {}, } match side { WMSZ_TOP | WMSZ_TOPLEFT | WMSZ_TOPRIGHT => { rect.top -= height_delta; }, WMSZ_BOTTOM | WMSZ_BOTTOMLEFT | WMSZ_BOTTOMRIGHT => { rect.bottom += height_delta; }, _ => {}, } result = ProcResult::DefWindowProc(wparam); }, WM_MENUCHAR => { result = ProcResult::Value((MNC_CLOSE << 16) as isize); }, WM_IME_STARTCOMPOSITION => { let ime_allowed = userdata.window_state_lock().ime_allowed; if ime_allowed { userdata.window_state_lock().ime_state = ImeState::Enabled; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Enabled), }); } result = ProcResult::DefWindowProc(wparam); }, WM_IME_COMPOSITION => { let ime_allowed_and_composing = { let w = userdata.window_state_lock(); w.ime_allowed && w.ime_state != ImeState::Disabled }; // Windows Hangul IME sends WM_IME_COMPOSITION after WM_IME_ENDCOMPOSITION, so // check whether composing. if ime_allowed_and_composing { let ime_context = unsafe { ImeContext::current(window) }; if lparam == 0 { userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), }); } // Google Japanese Input and ATOK have both flags, so // first, receive composing result if exist. if (lparam as u32 & GCS_RESULTSTR) != 0 { if let Some(text) = unsafe { ime_context.get_composed_text() } { userdata.window_state_lock().ime_state = ImeState::Enabled; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), }); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Commit(text)), }); } } // Next, receive preedit range for next composing if exist. if (lparam as u32 & GCS_COMPSTR) != 0 { if let Some((text, first, last)) = unsafe { ime_context.get_composing_text_and_cursor() } { userdata.window_state_lock().ime_state = ImeState::Preedit; let cursor_range = first.map(|f| (f, last.unwrap_or(f))); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Preedit(text, cursor_range)), }); } } } // Not calling DefWindowProc to hide composing text drawn by IME. result = ProcResult::Value(0); }, WM_IME_ENDCOMPOSITION => { let ime_allowed_or_composing = { let w = userdata.window_state_lock(); w.ime_allowed || w.ime_state != ImeState::Disabled }; if ime_allowed_or_composing { if userdata.window_state_lock().ime_state == ImeState::Preedit { // Windows Hangul IME sends WM_IME_COMPOSITION after WM_IME_ENDCOMPOSITION, so // trying receiving composing result and commit if exists. let ime_context = unsafe { ImeContext::current(window) }; if let Some(text) = unsafe { ime_context.get_composed_text() } { userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), }); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Commit(text)), }); } } userdata.window_state_lock().ime_state = ImeState::Disabled; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Disabled), }); } result = ProcResult::DefWindowProc(wparam); }, WM_IME_SETCONTEXT => { // Hide composing text drawn by IME. let wparam = wparam & (!ISC_SHOWUICOMPOSITIONWINDOW as usize); result = ProcResult::DefWindowProc(wparam); }, // this is necessary for us to maintain minimize/restore state WM_SYSCOMMAND => { if wparam == SC_RESTORE as usize { let mut w = userdata.window_state_lock(); w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, false)); } if wparam == SC_MINIMIZE as usize { let mut w = userdata.window_state_lock(); w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, true)); } // Send `WindowEvent::Minimized` here if we decide to implement one if wparam == SC_SCREENSAVE as usize { let window_state = userdata.window_state_lock(); if window_state.fullscreen.is_some() { result = ProcResult::Value(0); return; } } result = ProcResult::DefWindowProc(wparam); }, WM_MOUSEMOVE => { use crate::event::WindowEvent::{CursorEntered, CursorLeft, CursorMoved}; let x = super::get_x_lparam(lparam as u32) as i32; let y = super::get_y_lparam(lparam as u32) as i32; let position = PhysicalPosition::new(x as f64, y as f64); let cursor_moved; { let mut w = userdata.window_state_lock(); let mouse_was_inside_window = w.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW); match get_pointer_move_kind(window, mouse_was_inside_window, x, y) { PointerMoveKind::Enter => { w.mouse .set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, true)) .ok(); drop(w); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorEntered { device_id: DEVICE_ID }, }); // Calling TrackMouseEvent in order to receive mouse leave events. unsafe { TrackMouseEvent(&mut TRACKMOUSEEVENT { cbSize: mem::size_of::() as u32, dwFlags: TME_LEAVE, hwndTrack: window, dwHoverTime: HOVER_DEFAULT, }) }; }, PointerMoveKind::Leave => { w.mouse .set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false)) .ok(); drop(w); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorLeft { device_id: DEVICE_ID }, }); }, PointerMoveKind::None => drop(w), } // handle spurious WM_MOUSEMOVE messages // see https://devblogs.microsoft.com/oldnewthing/20031001-00/?p=42343 // and http://debugandconquer.blogspot.com/2015/08/the-cause-of-spurious-mouse-move.html let mut w = userdata.window_state_lock(); cursor_moved = w.mouse.last_position != Some(position); w.mouse.last_position = Some(position); } if cursor_moved { update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorMoved { device_id: DEVICE_ID, position }, }); } result = ProcResult::Value(0); }, WM_MOUSELEAVE => { use crate::event::WindowEvent::CursorLeft; { let mut w = userdata.window_state_lock(); w.mouse.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false)).ok(); } userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorLeft { device_id: DEVICE_ID }, }); result = ProcResult::Value(0); }, WM_MOUSEWHEEL => { use crate::event::MouseScrollDelta::LineDelta; let value = (wparam >> 16) as i16; let value = value as f32 / WHEEL_DELTA as f32; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, }, }); result = ProcResult::Value(0); }, WM_MOUSEHWHEEL => { use crate::event::MouseScrollDelta::LineDelta; let value = (wparam >> 16) as i16; let value = -value as f32 / WHEEL_DELTA as f32; // NOTE: inverted! See https://github.com/rust-windowing/winit/pull/2105/ update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, }, }); result = ProcResult::Value(0); }, WM_KEYDOWN | WM_SYSKEYDOWN => { if msg == WM_SYSKEYDOWN { result = ProcResult::DefWindowProc(wparam); } }, WM_KEYUP | WM_SYSKEYUP => { if msg == WM_SYSKEYUP && unsafe { GetMenu(window) != 0 } { // let Windows handle event if the window has a native menu, a modal event loop // is started here on Alt key up. result = ProcResult::DefWindowProc(wparam); } }, WM_LBUTTONDOWN => { use crate::event::ElementState::Pressed; use crate::event::MouseButton::Left; use crate::event::WindowEvent::MouseInput; unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left }, }); result = ProcResult::Value(0); }, WM_LBUTTONUP => { use crate::event::ElementState::Released; use crate::event::MouseButton::Left; use crate::event::WindowEvent::MouseInput; unsafe { release_mouse(userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Released, button: Left }, }); result = ProcResult::Value(0); }, WM_RBUTTONDOWN => { use crate::event::ElementState::Pressed; use crate::event::MouseButton::Right; use crate::event::WindowEvent::MouseInput; unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right }, }); result = ProcResult::Value(0); }, WM_RBUTTONUP => { use crate::event::ElementState::Released; use crate::event::MouseButton::Right; use crate::event::WindowEvent::MouseInput; unsafe { release_mouse(userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Released, button: Right }, }); result = ProcResult::Value(0); }, WM_MBUTTONDOWN => { use crate::event::ElementState::Pressed; use crate::event::MouseButton::Middle; use crate::event::WindowEvent::MouseInput; unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle }, }); result = ProcResult::Value(0); }, WM_MBUTTONUP => { use crate::event::ElementState::Released; use crate::event::MouseButton::Middle; use crate::event::WindowEvent::MouseInput; unsafe { release_mouse(userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Released, button: Middle }, }); result = ProcResult::Value(0); }, WM_XBUTTONDOWN => { use crate::event::ElementState::Pressed; use crate::event::MouseButton::{Back, Forward, Other}; use crate::event::WindowEvent::MouseInput; let xbutton = super::get_xbutton_wparam(wparam as u32); unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: match xbutton { 1 => Back, 2 => Forward, _ => Other(xbutton), }, }, }); result = ProcResult::Value(0); }, WM_XBUTTONUP => { use crate::event::ElementState::Released; use crate::event::MouseButton::{Back, Forward, Other}; use crate::event::WindowEvent::MouseInput; let xbutton = super::get_xbutton_wparam(wparam as u32); unsafe { release_mouse(userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Released, button: match xbutton { 1 => Back, 2 => Forward, _ => Other(xbutton), }, }, }); result = ProcResult::Value(0); }, WM_CAPTURECHANGED => { // lparam here is a handle to the window which is gaining mouse capture. // If it is the same as our window, then we're essentially retaining the capture. This // can happen if `SetCapture` is called on our window when it already has the mouse // capture. if lparam != window { userdata.window_state_lock().mouse.capture_count = 0; } result = ProcResult::Value(0); }, WM_TOUCH => { let pcount = super::loword(wparam as u32) as usize; let mut inputs = Vec::with_capacity(pcount); let htouch = lparam; if unsafe { GetTouchInputInfo( htouch, pcount as u32, inputs.as_mut_ptr(), mem::size_of::() as i32, ) > 0 } { unsafe { inputs.set_len(pcount) }; for input in &inputs { let mut location = POINT { x: input.x / 100, y: input.y / 100 }; if unsafe { ScreenToClient(window, &mut location) } == false.into() { continue; } let x = location.x as f64 + (input.x % 100) as f64 / 100f64; let y = location.y as f64 + (input.y % 100) as f64 / 100f64; let location = PhysicalPosition::new(x, y); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Touch(Touch { phase: if util::has_flag(input.dwFlags, TOUCHEVENTF_DOWN) { TouchPhase::Started } else if util::has_flag(input.dwFlags, TOUCHEVENTF_UP) { TouchPhase::Ended } else if util::has_flag(input.dwFlags, TOUCHEVENTF_MOVE) { TouchPhase::Moved } else { continue; }, location, force: None, // WM_TOUCH doesn't support pressure information id: input.dwID as u64, device_id: DEVICE_ID, }), }); } } unsafe { CloseTouchInputHandle(htouch) }; result = ProcResult::Value(0); }, WM_POINTERDOWN | WM_POINTERUPDATE | WM_POINTERUP => { if let ( Some(GetPointerFrameInfoHistory), Some(SkipPointerFrameMessages), Some(GetPointerDeviceRects), ) = ( *util::GET_POINTER_FRAME_INFO_HISTORY, *util::SKIP_POINTER_FRAME_MESSAGES, *util::GET_POINTER_DEVICE_RECTS, ) { let pointer_id = super::loword(wparam as u32) as u32; let mut entries_count = 0u32; let mut pointers_count = 0u32; if unsafe { GetPointerFrameInfoHistory( pointer_id, &mut entries_count, &mut pointers_count, ptr::null_mut(), ) } == false.into() { result = ProcResult::Value(0); return; } let pointer_info_count = (entries_count * pointers_count) as usize; let mut pointer_infos = Vec::with_capacity(pointer_info_count); if unsafe { GetPointerFrameInfoHistory( pointer_id, &mut entries_count, &mut pointers_count, pointer_infos.as_mut_ptr(), ) } == false.into() { result = ProcResult::Value(0); return; } unsafe { pointer_infos.set_len(pointer_info_count) }; // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getpointerframeinfohistory // The information retrieved appears in reverse chronological order, with the most // recent entry in the first row of the returned array for pointer_info in pointer_infos.iter().rev() { let mut device_rect = mem::MaybeUninit::uninit(); let mut display_rect = mem::MaybeUninit::uninit(); if unsafe { GetPointerDeviceRects( pointer_info.sourceDevice, device_rect.as_mut_ptr(), display_rect.as_mut_ptr(), ) } == false.into() { continue; } let device_rect = unsafe { device_rect.assume_init() }; let display_rect = unsafe { display_rect.assume_init() }; // For the most precise himetric to pixel conversion we calculate the ratio // between the resolution of the display device (pixel) and // the touch device (himetric). let himetric_to_pixel_ratio_x = (display_rect.right - display_rect.left) as f64 / (device_rect.right - device_rect.left) as f64; let himetric_to_pixel_ratio_y = (display_rect.bottom - display_rect.top) as f64 / (device_rect.bottom - device_rect.top) as f64; // ptHimetricLocation's origin is 0,0 even on multi-monitor setups. // On multi-monitor setups we need to translate the himetric location to the // rect of the display device it's attached to. let x = display_rect.left as f64 + pointer_info.ptHimetricLocation.x as f64 * himetric_to_pixel_ratio_x; let y = display_rect.top as f64 + pointer_info.ptHimetricLocation.y as f64 * himetric_to_pixel_ratio_y; let mut location = POINT { x: x.floor() as i32, y: y.floor() as i32 }; if unsafe { ScreenToClient(window, &mut location) } == false.into() { continue; } let force = match pointer_info.pointerType { PT_TOUCH => { let mut touch_info = mem::MaybeUninit::uninit(); util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| { match unsafe { GetPointerTouchInfo( pointer_info.pointerId, touch_info.as_mut_ptr(), ) } { 0 => None, _ => normalize_pointer_pressure(unsafe { touch_info.assume_init().pressure }), } }) }, PT_PEN => { let mut pen_info = mem::MaybeUninit::uninit(); util::GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| { match unsafe { GetPointerPenInfo(pointer_info.pointerId, pen_info.as_mut_ptr()) } { 0 => None, _ => normalize_pointer_pressure(unsafe { pen_info.assume_init().pressure }), } }) }, _ => None, }; let x = location.x as f64 + x.fract(); let y = location.y as f64 + y.fract(); let location = PhysicalPosition::new(x, y); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Touch(Touch { phase: if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_DOWN) { TouchPhase::Started } else if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UP) { TouchPhase::Ended } else if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UPDATE) { TouchPhase::Moved } else { continue; }, location, force, id: pointer_info.pointerId as u64, device_id: DEVICE_ID, }), }); } unsafe { SkipPointerFrameMessages(pointer_id) }; } result = ProcResult::Value(0); }, WM_NCACTIVATE => { let is_active = wparam != false.into(); let active_focus_changed = userdata.window_state_lock().set_active(is_active); if active_focus_changed { if is_active { unsafe { gain_active_focus(window, userdata) }; } else { unsafe { lose_active_focus(window, userdata) }; } } result = ProcResult::DefWindowProc(wparam); }, WM_SETFOCUS => { let active_focus_changed = userdata.window_state_lock().set_focused(true); if active_focus_changed { unsafe { gain_active_focus(window, userdata) }; } result = ProcResult::Value(0); }, WM_KILLFOCUS => { let active_focus_changed = userdata.window_state_lock().set_focused(false); if active_focus_changed { unsafe { lose_active_focus(window, userdata) }; } result = ProcResult::Value(0); }, WM_SETCURSOR => { let set_cursor_to = { let window_state = userdata.window_state_lock(); // The return value for the preceding `WM_NCHITTEST` message is conveniently // provided through the low-order word of lParam. We use that here since // `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement. let in_client_area = super::loword(lparam as u32) as u32 == HTCLIENT; if in_client_area { Some(window_state.mouse.selected_cursor.clone()) } else { None } }; match set_cursor_to { Some(selected_cursor) => { let hcursor = match selected_cursor { SelectedCursor::Named(cursor_icon) => unsafe { LoadCursorW(0, util::to_windows_cursor(cursor_icon)) }, SelectedCursor::Custom(cursor) => cursor.as_raw_handle(), }; unsafe { SetCursor(hcursor) }; result = ProcResult::Value(0); }, None => result = ProcResult::DefWindowProc(wparam), } }, WM_GETMINMAXINFO => { let mmi = lparam as *mut MINMAXINFO; let window_state = userdata.window_state_lock(); let window_flags = window_state.window_flags; if window_state.min_size.is_some() || window_state.max_size.is_some() { if let Some(min_size) = window_state.min_size { let min_size = min_size.to_physical(window_state.scale_factor); let (width, height): (u32, u32) = window_flags.adjust_size(window, min_size).into(); unsafe { (*mmi).ptMinTrackSize = POINT { x: width as i32, y: height as i32 } }; } if let Some(max_size) = window_state.max_size { let max_size = max_size.to_physical(window_state.scale_factor); let (width, height): (u32, u32) = window_flags.adjust_size(window, max_size).into(); unsafe { (*mmi).ptMaxTrackSize = POINT { x: width as i32, y: height as i32 } }; } } result = ProcResult::Value(0); }, // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change // DPI, therefore all applications are closed while DPI is changing. WM_DPICHANGED => { use crate::event::WindowEvent::ScaleFactorChanged; // This message actually provides two DPI values - x and y. However MSDN says that // "you only need to use either the X-axis or the Y-axis value when scaling your // application since they are the same". // https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx let new_dpi_x = super::loword(wparam as u32) as u32; let new_scale_factor = dpi_to_scale_factor(new_dpi_x); let old_scale_factor: f64; let (allow_resize, window_flags) = { let mut window_state = userdata.window_state_lock(); old_scale_factor = window_state.scale_factor; window_state.scale_factor = new_scale_factor; if new_scale_factor == old_scale_factor { result = ProcResult::Value(0); return; } let allow_resize = window_state.fullscreen.is_none() && !window_state.window_flags().contains(WindowFlags::MAXIMIZED); (allow_resize, window_state.window_flags) }; // New size as suggested by Windows. let suggested_rect = unsafe { *(lparam as *const RECT) }; // The window rect provided is the window's outer size, not it's inner size. However, // win32 doesn't provide an `UnadjustWindowRectEx` function to get the client rect from // the outer rect, so we instead adjust the window rect to get the decoration margins // and remove them from the outer size. let margin_left: i32; let margin_top: i32; // let margin_right: i32; // let margin_bottom: i32; { let adjusted_rect = window_flags.adjust_rect(window, suggested_rect).unwrap_or(suggested_rect); margin_left = suggested_rect.left - adjusted_rect.left; margin_top = suggested_rect.top - adjusted_rect.top; // margin_right = adjusted_rect.right - suggested_rect.right; // margin_bottom = adjusted_rect.bottom - suggested_rect.bottom; } let old_physical_inner_rect = util::WindowArea::Inner .get_rect(window) .expect("failed to query (old) inner window area"); let old_physical_inner_size = PhysicalSize::new( (old_physical_inner_rect.right - old_physical_inner_rect.left) as u32, (old_physical_inner_rect.bottom - old_physical_inner_rect.top) as u32, ); // `allow_resize` prevents us from re-applying DPI adjustment to the restored size after // exiting fullscreen (the restored size is already DPI adjusted). let new_physical_inner_size = match allow_resize { // We calculate our own size because the default suggested rect doesn't do a great // job of preserving the window's logical size. true => old_physical_inner_size .to_logical::(old_scale_factor) .to_physical::(new_scale_factor), false => old_physical_inner_size, }; let new_inner_size = Arc::new(Mutex::new(new_physical_inner_size)); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ScaleFactorChanged { scale_factor: new_scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), }, }); let new_physical_inner_size = *new_inner_size.lock().unwrap(); drop(new_inner_size); let dragging_window: bool; { let window_state = userdata.window_state_lock(); dragging_window = window_state.window_flags().contains(WindowFlags::MARKER_IN_SIZE_MOVE); // Unset maximized if we're changing the window's size. if new_physical_inner_size != old_physical_inner_size { WindowState::set_window_flags(window_state, window, |f| { f.set(WindowFlags::MAXIMIZED, false) }); } } let new_outer_rect: RECT; { let suggested_ul = (suggested_rect.left + margin_left, suggested_rect.top + margin_top); let mut conservative_rect = RECT { left: suggested_ul.0, top: suggested_ul.1, right: suggested_ul.0 + new_physical_inner_size.width as i32, bottom: suggested_ul.1 + new_physical_inner_size.height as i32, }; conservative_rect = window_flags .adjust_rect(window, conservative_rect) .unwrap_or(conservative_rect); // If we're dragging the window, offset the window so that the cursor's // relative horizontal position in the title bar is preserved. if dragging_window { let bias = { let cursor_pos = { let mut pos = unsafe { mem::zeroed() }; unsafe { GetCursorPos(&mut pos) }; pos }; let suggested_cursor_horizontal_ratio = (cursor_pos.x - suggested_rect.left) as f64 / (suggested_rect.right - suggested_rect.left) as f64; (cursor_pos.x - (suggested_cursor_horizontal_ratio * (conservative_rect.right - conservative_rect.left) as f64) as i32) - conservative_rect.left }; conservative_rect.left += bias; conservative_rect.right += bias; } // Check to see if the new window rect is on the monitor with the new DPI factor. // If it isn't, offset the window so that it is. let new_dpi_monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) }; let conservative_rect_monitor = unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) }; new_outer_rect = if conservative_rect_monitor == new_dpi_monitor { conservative_rect } else { let get_monitor_rect = |monitor| { let mut monitor_info = MONITORINFO { cbSize: mem::size_of::() as _, ..unsafe { mem::zeroed() } }; unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }; monitor_info.rcMonitor }; let wrong_monitor = conservative_rect_monitor; let wrong_monitor_rect = get_monitor_rect(wrong_monitor); let new_monitor_rect = get_monitor_rect(new_dpi_monitor); // The direction to nudge the window in to get the window onto the monitor with // the new DPI factor. We calculate this by seeing which monitor edges are // shared and nudging away from the wrong monitor based on those. #[allow(clippy::bool_to_int_with_if)] let delta_nudge_to_dpi_monitor = ( if wrong_monitor_rect.left == new_monitor_rect.right { -1 } else if wrong_monitor_rect.right == new_monitor_rect.left { 1 } else { 0 }, if wrong_monitor_rect.bottom == new_monitor_rect.top { 1 } else if wrong_monitor_rect.top == new_monitor_rect.bottom { -1 } else { 0 }, ); let abort_after_iterations = new_monitor_rect.right - new_monitor_rect.left + new_monitor_rect.bottom - new_monitor_rect.top; for _ in 0..abort_after_iterations { conservative_rect.left += delta_nudge_to_dpi_monitor.0; conservative_rect.right += delta_nudge_to_dpi_monitor.0; conservative_rect.top += delta_nudge_to_dpi_monitor.1; conservative_rect.bottom += delta_nudge_to_dpi_monitor.1; if unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) } == new_dpi_monitor { break; } } conservative_rect }; } unsafe { SetWindowPos( window, 0, new_outer_rect.left, new_outer_rect.top, new_outer_rect.right - new_outer_rect.left, new_outer_rect.bottom - new_outer_rect.top, SWP_NOZORDER | SWP_NOACTIVATE, ) }; result = ProcResult::Value(0); }, WM_SETTINGCHANGE => { use crate::event::WindowEvent::ThemeChanged; let preferred_theme = userdata.window_state_lock().preferred_theme; if preferred_theme.is_none() { let new_theme = try_theme(window, preferred_theme); let mut window_state = userdata.window_state_lock(); if window_state.current_theme != new_theme { window_state.current_theme = new_theme; drop(window_state); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ThemeChanged(new_theme), }); } } result = ProcResult::DefWindowProc(wparam); }, _ => { if msg == DESTROY_MSG_ID.get() { unsafe { DestroyWindow(window) }; result = ProcResult::Value(0); } else if msg == SET_RETAIN_STATE_ON_SIZE_MSG_ID.get() { let mut window_state = userdata.window_state_lock(); window_state.set_window_flags_in_place(|f| { f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam != 0) }); result = ProcResult::Value(0); } else if msg == TASKBAR_CREATED.get() { let window_state = userdata.window_state_lock(); unsafe { set_skip_taskbar(window, window_state.skip_taskbar) }; result = ProcResult::DefWindowProc(wparam); } else { result = ProcResult::DefWindowProc(wparam); } }, }; userdata .event_loop_runner .catch_unwind(callback) .unwrap_or_else(|| result = ProcResult::Value(-1)); match result { ProcResult::DefWindowProc(wparam) => unsafe { DefWindowProcW(window, msg, wparam, lparam) }, ProcResult::Value(val) => val, } } unsafe extern "system" fn thread_event_target_callback( window: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM, ) -> LRESULT { let userdata_ptr = unsafe { super::get_window_long(window, GWL_USERDATA) } as *mut ThreadMsgTargetData; if userdata_ptr.is_null() { // `userdata_ptr` will always be null for the first `WM_GETMINMAXINFO`, as well as // `WM_NCCREATE` and `WM_CREATE`. return unsafe { DefWindowProcW(window, msg, wparam, lparam) }; } let userdata = unsafe { Box::from_raw(userdata_ptr) }; if msg != WM_PAINT { unsafe { RedrawWindow(window, ptr::null(), 0, RDW_INTERNALPAINT) }; } let mut userdata_removed = false; // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing // the closure to catch_unwind directly so that the match body indentation wouldn't change and // the git blame and history would be preserved. let callback = || match msg { WM_NCDESTROY => { unsafe { super::set_window_long(window, GWL_USERDATA, 0) }; userdata_removed = true; 0 }, WM_PAINT => unsafe { ValidateRect(window, ptr::null()); // Default WM_PAINT behaviour. This makes sure modals and popups are shown immediately // when opening them. DefWindowProcW(window, msg, wparam, lparam) }, WM_INPUT_DEVICE_CHANGE => { let event = match wparam as u32 { GIDC_ARRIVAL => DeviceEvent::Added, GIDC_REMOVAL => DeviceEvent::Removed, _ => unreachable!(), }; userdata .send_event(Event::DeviceEvent { device_id: wrap_device_id(lparam as u32), event }); 0 }, WM_INPUT => { if let Some(data) = raw_input::get_raw_input_data(lparam as _) { unsafe { handle_raw_input(&userdata, data) }; } unsafe { DefWindowProcW(window, msg, wparam, lparam) } }, _ if msg == USER_EVENT_MSG_ID.get() => { // synthesis a placeholder UserEvent, so that if the callback is // re-entered it can be buffered for later delivery. the real // user event is still in the mpsc channel and will be pulled // once the placeholder event is delivered to the wrapper // `event_handler` userdata.send_event(Event::UserEvent(UserEventPlaceholder)); 0 }, _ if msg == EXEC_MSG_ID.get() => { let mut function: ThreadExecFn = unsafe { Box::from_raw(wparam as *mut _) }; function(); 0 }, _ => unsafe { DefWindowProcW(window, msg, wparam, lparam) }, }; let result = userdata.event_loop_runner.catch_unwind(callback).unwrap_or(-1); if userdata_removed { drop(userdata); } else { Box::leak(userdata); } result } unsafe fn handle_raw_input(userdata: &ThreadMsgTargetData, data: RAWINPUT) { use crate::event::DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}; use crate::event::ElementState::{Pressed, Released}; use crate::event::MouseScrollDelta::LineDelta; let device_id = wrap_device_id(data.header.hDevice as _); if data.header.dwType == RIM_TYPEMOUSE { let mouse = unsafe { data.data.mouse }; if util::has_flag(mouse.usFlags as u32, MOUSE_MOVE_RELATIVE) { let x = mouse.lLastX as f64; let y = mouse.lLastY as f64; if x != 0.0 { userdata.send_event(Event::DeviceEvent { device_id, event: Motion { axis: 0, value: x }, }); } if y != 0.0 { userdata.send_event(Event::DeviceEvent { device_id, event: Motion { axis: 1, value: y }, }); } if x != 0.0 || y != 0.0 { userdata.send_event(Event::DeviceEvent { device_id, event: MouseMotion { delta: (x, y) }, }); } } let button_flags = unsafe { mouse.Anonymous.Anonymous.usButtonFlags }; if util::has_flag(button_flags as u32, RI_MOUSE_WHEEL) { let button_data = unsafe { mouse.Anonymous.Anonymous.usButtonData } as i16; let delta = button_data as f32 / WHEEL_DELTA as f32; userdata.send_event(Event::DeviceEvent { device_id, event: MouseWheel { delta: LineDelta(0.0, delta) }, }); } if util::has_flag(button_flags as u32, RI_MOUSE_HWHEEL) { let button_data = unsafe { mouse.Anonymous.Anonymous.usButtonData } as i16; let delta = -button_data as f32 / WHEEL_DELTA as f32; userdata.send_event(Event::DeviceEvent { device_id, event: MouseWheel { delta: LineDelta(delta, 0.0) }, }); } let button_state = raw_input::get_raw_mouse_button_state(button_flags as u32); for (button, state) in button_state.iter().enumerate() { if let Some(state) = *state { userdata.send_event(Event::DeviceEvent { device_id, event: Button { button: button as _, state }, }); } } } else if data.header.dwType == RIM_TYPEKEYBOARD { let keyboard = unsafe { data.data.keyboard }; let pressed = keyboard.Message == WM_KEYDOWN || keyboard.Message == WM_SYSKEYDOWN; let released = keyboard.Message == WM_KEYUP || keyboard.Message == WM_SYSKEYUP; if !pressed && !released { return; } if let Some(physical_key) = raw_input::get_keyboard_physical_key(keyboard) { let state = if pressed { Pressed } else { Released }; userdata.send_event(Event::DeviceEvent { device_id, event: Key(RawKeyEvent { physical_key, state }), }); } } } enum PointerMoveKind { /// Pointer enterd to the window. Enter, /// Pointer leaved the window client area. Leave, /// Pointer is inside the window or `GetClientRect` failed. None, } fn get_pointer_move_kind( window: HWND, mouse_was_inside_window: bool, x: i32, y: i32, ) -> PointerMoveKind { let rect: RECT = unsafe { let mut rect: RECT = mem::zeroed(); if GetClientRect(window, &mut rect) == false.into() { return PointerMoveKind::None; // exit early if GetClientRect failed } rect }; let x = (rect.left..rect.right).contains(&x); let y = (rect.top..rect.bottom).contains(&y); if !mouse_was_inside_window && x && y { PointerMoveKind::Enter } else if mouse_was_inside_window && !(x && y) { PointerMoveKind::Leave } else { PointerMoveKind::None } } winit-0.30.9/src/platform_impl/windows/icon.rs000064400000000000000000000156311046102023000175010ustar 00000000000000use std::ffi::c_void; use std::path::Path; use std::sync::Arc; use std::{fmt, io, mem}; use cursor_icon::CursorIcon; use windows_sys::core::PCWSTR; use windows_sys::Win32::Foundation::HWND; use windows_sys::Win32::Graphics::Gdi::{ CreateBitmap, CreateCompatibleBitmap, DeleteObject, GetDC, ReleaseDC, SetBitmapBits, }; use windows_sys::Win32::UI::WindowsAndMessaging::{ CreateIcon, CreateIconIndirect, DestroyCursor, DestroyIcon, LoadImageW, SendMessageW, HCURSOR, HICON, ICONINFO, ICON_BIG, ICON_SMALL, IMAGE_ICON, LR_DEFAULTSIZE, LR_LOADFROMFILE, WM_SETICON, }; use crate::cursor::CursorImage; use crate::dpi::PhysicalSize; use crate::icon::*; use super::util; impl Pixel { fn convert_to_bgra(&mut self) { mem::swap(&mut self.r, &mut self.b); } } impl RgbaIcon { fn into_windows_icon(self) -> Result { let rgba = self.rgba; let pixel_count = rgba.len() / PIXEL_SIZE; let mut and_mask = Vec::with_capacity(pixel_count); let pixels = unsafe { std::slice::from_raw_parts_mut(rgba.as_ptr() as *mut Pixel, pixel_count) }; for pixel in pixels { and_mask.push(pixel.a.wrapping_sub(u8::MAX)); // invert alpha channel pixel.convert_to_bgra(); } assert_eq!(and_mask.len(), pixel_count); let handle = unsafe { CreateIcon( 0, self.width as i32, self.height as i32, 1, (PIXEL_SIZE * 8) as u8, and_mask.as_ptr(), rgba.as_ptr(), ) }; if handle != 0 { Ok(WinIcon::from_handle(handle)) } else { Err(BadIcon::OsError(io::Error::last_os_error())) } } } #[derive(Debug)] pub enum IconType { Small = ICON_SMALL as isize, Big = ICON_BIG as isize, } #[derive(Debug)] struct RaiiIcon { handle: HICON, } #[derive(Clone)] pub struct WinIcon { inner: Arc, } unsafe impl Send for WinIcon {} impl WinIcon { pub fn as_raw_handle(&self) -> HICON { self.inner.handle } pub fn from_path>( path: P, size: Option>, ) -> Result { // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size let (width, height) = size.map(Into::into).unwrap_or((0, 0)); let wide_path = util::encode_wide(path.as_ref()); let handle = unsafe { LoadImageW( 0, wide_path.as_ptr(), IMAGE_ICON, width, height, LR_DEFAULTSIZE | LR_LOADFROMFILE, ) }; if handle != 0 { Ok(WinIcon::from_handle(handle as HICON)) } else { Err(BadIcon::OsError(io::Error::last_os_error())) } } pub fn from_resource( resource_id: u16, size: Option>, ) -> Result { // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size let (width, height) = size.map(Into::into).unwrap_or((0, 0)); let handle = unsafe { LoadImageW( util::get_instance_handle(), resource_id as PCWSTR, IMAGE_ICON, width, height, LR_DEFAULTSIZE, ) }; if handle != 0 { Ok(WinIcon::from_handle(handle as HICON)) } else { Err(BadIcon::OsError(io::Error::last_os_error())) } } pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { let rgba_icon = RgbaIcon::from_rgba(rgba, width, height)?; rgba_icon.into_windows_icon() } pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) { unsafe { SendMessageW(hwnd, WM_SETICON, icon_type as usize, self.as_raw_handle()); } } fn from_handle(handle: HICON) -> Self { Self { inner: Arc::new(RaiiIcon { handle }) } } } impl Drop for RaiiIcon { fn drop(&mut self) { unsafe { DestroyIcon(self.handle) }; } } impl fmt::Debug for WinIcon { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { (*self.inner).fmt(formatter) } } pub fn unset_for_window(hwnd: HWND, icon_type: IconType) { unsafe { SendMessageW(hwnd, WM_SETICON, icon_type as usize, 0); } } #[derive(Debug, Clone)] pub enum SelectedCursor { Named(CursorIcon), Custom(Arc), } impl Default for SelectedCursor { fn default() -> Self { Self::Named(Default::default()) } } #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub enum WinCursor { Cursor(Arc), Failed, } impl WinCursor { pub(crate) fn new(image: &CursorImage) -> Result { let mut bgra = image.rgba.clone(); bgra.chunks_exact_mut(4).for_each(|chunk| chunk.swap(0, 2)); let w = image.width as i32; let h = image.height as i32; unsafe { let hdc_screen = GetDC(0); if hdc_screen == 0 { return Err(io::Error::last_os_error()); } let hbm_color = CreateCompatibleBitmap(hdc_screen, w, h); ReleaseDC(0, hdc_screen); if hbm_color == 0 { return Err(io::Error::last_os_error()); } if SetBitmapBits(hbm_color, bgra.len() as u32, bgra.as_ptr() as *const c_void) == 0 { DeleteObject(hbm_color); return Err(io::Error::last_os_error()); }; // Mask created according to https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createbitmap#parameters let mask_bits: Vec = vec![0xff; ((((w + 15) >> 4) << 1) * h) as usize]; let hbm_mask = CreateBitmap(w, h, 1, 1, mask_bits.as_ptr() as *const _); if hbm_mask == 0 { DeleteObject(hbm_color); return Err(io::Error::last_os_error()); } let icon_info = ICONINFO { fIcon: 0, xHotspot: image.hotspot_x as u32, yHotspot: image.hotspot_y as u32, hbmMask: hbm_mask, hbmColor: hbm_color, }; let handle = CreateIconIndirect(&icon_info as *const _); DeleteObject(hbm_color); DeleteObject(hbm_mask); if handle == 0 { return Err(io::Error::last_os_error()); } Ok(Self::Cursor(Arc::new(RaiiCursor { handle }))) } } } #[derive(Debug, Hash, Eq, PartialEq)] pub struct RaiiCursor { handle: HCURSOR, } impl Drop for RaiiCursor { fn drop(&mut self) { unsafe { DestroyCursor(self.handle) }; } } impl RaiiCursor { pub fn as_raw_handle(&self) -> HICON { self.handle } } winit-0.30.9/src/platform_impl/windows/ime.rs000064400000000000000000000122601046102023000173160ustar 00000000000000use std::ffi::{c_void, OsString}; use std::os::windows::prelude::OsStringExt; use std::ptr::null_mut; use windows_sys::Win32::Foundation::{POINT, RECT}; use windows_sys::Win32::Globalization::HIMC; use windows_sys::Win32::UI::Input::Ime::{ ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext, ImmSetCandidateWindow, ImmSetCompositionWindow, ATTR_TARGET_CONVERTED, ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT, COMPOSITIONFORM, GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT, }; use windows_sys::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED}; use crate::dpi::{Position, Size}; use crate::platform::windows::HWND; pub struct ImeContext { hwnd: HWND, himc: HIMC, } impl ImeContext { pub unsafe fn current(hwnd: HWND) -> Self { let himc = unsafe { ImmGetContext(hwnd) }; ImeContext { hwnd, himc } } pub unsafe fn get_composing_text_and_cursor( &self, ) -> Option<(String, Option, Option)> { let text = unsafe { self.get_composition_string(GCS_COMPSTR) }?; let attrs = unsafe { self.get_composition_data(GCS_COMPATTR) }.unwrap_or_default(); let mut first = None; let mut last = None; let mut boundary_before_char = 0; for (attr, chr) in attrs.into_iter().zip(text.chars()) { let char_is_targeted = attr as u32 == ATTR_TARGET_CONVERTED || attr as u32 == ATTR_TARGET_NOTCONVERTED; if first.is_none() && char_is_targeted { first = Some(boundary_before_char); } else if first.is_some() && last.is_none() && !char_is_targeted { last = Some(boundary_before_char); } boundary_before_char += chr.len_utf8(); } if first.is_some() && last.is_none() { last = Some(text.len()); } else if first.is_none() { // IME haven't split words and select any clause yet, so trying to retrieve normal // cursor. let cursor = unsafe { self.get_composition_cursor(&text) }; first = cursor; last = cursor; } Some((text, first, last)) } pub unsafe fn get_composed_text(&self) -> Option { unsafe { self.get_composition_string(GCS_RESULTSTR) } } unsafe fn get_composition_cursor(&self, text: &str) -> Option { let cursor = unsafe { ImmGetCompositionStringW(self.himc, GCS_CURSORPOS, null_mut(), 0) }; (cursor >= 0).then(|| text.chars().take(cursor as _).map(|c| c.len_utf8()).sum()) } unsafe fn get_composition_string(&self, gcs_mode: u32) -> Option { let data = unsafe { self.get_composition_data(gcs_mode) }?; let (prefix, shorts, suffix) = unsafe { data.align_to::() }; if prefix.is_empty() && suffix.is_empty() { OsString::from_wide(shorts).into_string().ok() } else { None } } unsafe fn get_composition_data(&self, gcs_mode: u32) -> Option> { let size = match unsafe { ImmGetCompositionStringW(self.himc, gcs_mode, null_mut(), 0) } { 0 => return Some(Vec::new()), size if size < 0 => return None, size => size, }; let mut buf = Vec::::with_capacity(size as _); let size = unsafe { ImmGetCompositionStringW( self.himc, gcs_mode, buf.as_mut_ptr() as *mut c_void, size as _, ) }; if size < 0 { None } else { unsafe { buf.set_len(size as _) }; Some(buf) } } pub unsafe fn set_ime_cursor_area(&self, spot: Position, size: Size, scale_factor: f64) { if !unsafe { ImeContext::system_has_ime() } { return; } let (x, y) = spot.to_physical::(scale_factor).into(); let (width, height): (i32, i32) = size.to_physical::(scale_factor).into(); let rc_area = RECT { left: x, top: y, right: x + width, bottom: y + height }; let candidate_form = CANDIDATEFORM { dwIndex: 0, dwStyle: CFS_EXCLUDE, ptCurrentPos: POINT { x, y }, rcArea: rc_area, }; let composition_form = COMPOSITIONFORM { dwStyle: CFS_POINT, ptCurrentPos: POINT { x, y: y + height }, rcArea: rc_area, }; unsafe { ImmSetCompositionWindow(self.himc, &composition_form); ImmSetCandidateWindow(self.himc, &candidate_form); } } pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) { if !unsafe { ImeContext::system_has_ime() } { return; } if allowed { unsafe { ImmAssociateContextEx(hwnd, 0, IACE_DEFAULT) }; } else { unsafe { ImmAssociateContextEx(hwnd, 0, IACE_CHILDREN) }; } } unsafe fn system_has_ime() -> bool { unsafe { GetSystemMetrics(SM_IMMENABLED) != 0 } } } impl Drop for ImeContext { fn drop(&mut self) { unsafe { ImmReleaseContext(self.hwnd, self.himc) }; } } winit-0.30.9/src/platform_impl/windows/keyboard.rs000064400000000000000000001447561046102023000203640ustar 00000000000000use std::char; use std::ffi::OsString; use std::mem::MaybeUninit; use std::os::windows::ffi::OsStringExt; use std::sync::atomic::AtomicU32; use std::sync::atomic::Ordering::Relaxed; use std::sync::{Mutex, MutexGuard}; use windows_sys::Win32::Foundation::{HWND, LPARAM, WPARAM}; use windows_sys::Win32::System::SystemServices::LANG_KOREAN; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ GetAsyncKeyState, GetKeyState, GetKeyboardLayout, GetKeyboardState, MapVirtualKeyExW, MAPVK_VK_TO_VSC_EX, MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY, VK_ABNT_C2, VK_ADD, VK_CAPITAL, VK_CLEAR, VK_CONTROL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_F4, VK_HOME, VK_INSERT, VK_LCONTROL, VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MENU, VK_MULTIPLY, VK_NEXT, VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_PRIOR, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT, VK_RWIN, VK_SCROLL, VK_SHIFT, VK_SUBTRACT, VK_UP, }; use windows_sys::Win32::UI::TextServices::HKL; use windows_sys::Win32::UI::WindowsAndMessaging::{ PeekMessageW, MSG, PM_NOREMOVE, WM_CHAR, WM_DEADCHAR, WM_KEYDOWN, WM_KEYFIRST, WM_KEYLAST, WM_KEYUP, WM_KILLFOCUS, WM_SETFOCUS, WM_SYSCHAR, WM_SYSDEADCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, }; use smol_str::SmolStr; use tracing::{trace, warn}; use unicode_segmentation::UnicodeSegmentation; use crate::event::{ElementState, KeyEvent}; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; use crate::platform_impl::platform::event_loop::ProcResult; use crate::platform_impl::platform::keyboard_layout::{ Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE, }; use crate::platform_impl::platform::{loword, primarylangid, KeyEventExtra}; pub type ExScancode = u16; pub struct MessageAsKeyEvent { pub event: KeyEvent, pub is_synthetic: bool, } /// Stores information required to make `KeyEvent`s. /// /// A single Winit `KeyEvent` contains information which the Windows API passes to the application /// in multiple window messages. In other words: a Winit `KeyEvent` cannot be built from a single /// window message. Therefore, this type keeps track of certain information from previous events so /// that a `KeyEvent` can be constructed when the last event related to a keypress is received. /// /// `PeekMessage` is sometimes used to determine whether the next window message still belongs to /// the current keypress. If it doesn't and the current state represents a key event waiting to be /// dispatched, then said event is considered complete and is dispatched. /// /// The sequence of window messages for a key press event is the following: /// - Exactly one WM_KEYDOWN / WM_SYSKEYDOWN /// - Zero or one WM_DEADCHAR / WM_SYSDEADCHAR /// - Zero or more WM_CHAR / WM_SYSCHAR. These messages each come with a UTF-16 code unit which when /// put together in the sequence they arrived in, forms the text which is the result of pressing /// the key. /// /// Key release messages are a bit different due to the fact that they don't contribute to /// text input. The "sequence" only consists of one WM_KEYUP / WM_SYSKEYUP event. pub struct KeyEventBuilder { event_info: Mutex>, pending: PendingEventQueue, } impl Default for KeyEventBuilder { fn default() -> Self { KeyEventBuilder { event_info: Mutex::new(None), pending: Default::default() } } } impl KeyEventBuilder { /// Call this function for every window message. /// Returns Some() if this window message completes a KeyEvent. /// Returns None otherwise. pub(crate) fn process_message( &self, hwnd: HWND, msg_kind: u32, wparam: WPARAM, lparam: LPARAM, result: &mut ProcResult, ) -> Vec { enum MatchResult { Nothing, TokenToRemove(PendingMessageToken), MessagesToDispatch(Vec), } let mut matcher = || -> MatchResult { match msg_kind { WM_SETFOCUS => { // synthesize keydown events let kbd_state = get_async_kbd_state(); let key_events = Self::synthesize_kbd_state(ElementState::Pressed, &kbd_state); MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) }, WM_KILLFOCUS => { // synthesize keyup events let kbd_state = get_kbd_state(); let key_events = Self::synthesize_kbd_state(ElementState::Released, &kbd_state); MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) }, WM_KEYDOWN | WM_SYSKEYDOWN => { if msg_kind == WM_SYSKEYDOWN && wparam as VIRTUAL_KEY == VK_F4 { // Don't dispatch Alt+F4 to the application. // This is handled in `event_loop.rs` return MatchResult::Nothing; } let pending_token = self.pending.add_pending(); *result = ProcResult::Value(0); let next_msg = next_kbd_msg(hwnd); let mut layouts = LAYOUT_CACHE.lock().unwrap(); let mut finished_event_info = Some(PartialKeyEventInfo::from_message( wparam, lparam, ElementState::Pressed, &mut layouts, )); let mut event_info = self.event_info.lock().unwrap(); *event_info = None; if let Some(next_msg) = next_msg { let next_msg_kind = next_msg.message; let next_belongs_to_this = !matches!( next_msg_kind, WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP ); if next_belongs_to_this { // The next OS event belongs to this Winit event, so let's just // store the partial information, and add to it in the upcoming events *event_info = finished_event_info.take(); } else { let (_, layout) = layouts.get_current_layout(); let is_fake = { let curr_event = finished_event_info.as_ref().unwrap(); is_current_fake(curr_event, next_msg, layout) }; if is_fake { finished_event_info = None; } } } if let Some(event_info) = finished_event_info { let ev = event_info.finalize(); return MatchResult::MessagesToDispatch(self.pending.complete_pending( pending_token, MessageAsKeyEvent { event: ev, is_synthetic: false }, )); } MatchResult::TokenToRemove(pending_token) }, WM_DEADCHAR | WM_SYSDEADCHAR => { let pending_token = self.pending.add_pending(); *result = ProcResult::Value(0); // At this point, we know that there isn't going to be any more events related // to this key press let event_info = self.event_info.lock().unwrap().take().unwrap(); let ev = event_info.finalize(); MatchResult::MessagesToDispatch(self.pending.complete_pending( pending_token, MessageAsKeyEvent { event: ev, is_synthetic: false }, )) }, WM_CHAR | WM_SYSCHAR => { let mut event_info = self.event_info.lock().unwrap(); if event_info.is_none() { trace!( "Received a CHAR message but no `event_info` was available. The \ message is probably IME, returning." ); return MatchResult::Nothing; } let pending_token = self.pending.add_pending(); *result = ProcResult::Value(0); let is_high_surrogate = (0xd800..=0xdbff).contains(&wparam); let is_low_surrogate = (0xdc00..=0xdfff).contains(&wparam); let is_utf16 = is_high_surrogate || is_low_surrogate; if is_utf16 { if let Some(ev_info) = event_info.as_mut() { ev_info.utf16parts.push(wparam as u16); } } else { // In this case, wparam holds a UTF-32 character. // Let's encode it as UTF-16 and append it to the end of `utf16parts` let utf16parts = match event_info.as_mut() { Some(ev_info) => &mut ev_info.utf16parts, None => { warn!("The event_info was None when it was expected to be some"); return MatchResult::TokenToRemove(pending_token); }, }; let start_offset = utf16parts.len(); let new_size = utf16parts.len() + 2; utf16parts.resize(new_size, 0); if let Some(ch) = char::from_u32(wparam as u32) { let encode_len = ch.encode_utf16(&mut utf16parts[start_offset..]).len(); let new_size = start_offset + encode_len; utf16parts.resize(new_size, 0); } } // It's important that we unlock the mutex, and create the pending event token // before calling `next_msg` std::mem::drop(event_info); let next_msg = next_kbd_msg(hwnd); let more_char_coming = next_msg .map(|m| matches!(m.message, WM_CHAR | WM_SYSCHAR)) .unwrap_or(false); if more_char_coming { // No need to produce an event just yet, because there are still more // characters that need to appended to this keyobard // event MatchResult::TokenToRemove(pending_token) } else { let mut event_info = self.event_info.lock().unwrap(); let mut event_info = match event_info.take() { Some(ev_info) => ev_info, None => { warn!("The event_info was None when it was expected to be some"); return MatchResult::TokenToRemove(pending_token); }, }; let mut layouts = LAYOUT_CACHE.lock().unwrap(); // It's okay to call `ToUnicode` here, because at this point the dead key // is already consumed by the character. let kbd_state = get_kbd_state(); let mod_state = WindowsModifiers::active_modifiers(&kbd_state); let (_, layout) = layouts.get_current_layout(); let ctrl_on = if layout.has_alt_graph { let alt_on = mod_state.contains(WindowsModifiers::ALT); !alt_on && mod_state.contains(WindowsModifiers::CONTROL) } else { mod_state.contains(WindowsModifiers::CONTROL) }; // If Ctrl is not pressed, just use the text with all // modifiers because that already consumed the dead key. Otherwise, // we would interpret the character incorrectly, missing the dead key. if !ctrl_on { event_info.text = PartialText::System(event_info.utf16parts.clone()); } else { let mod_no_ctrl = mod_state.remove_only_ctrl(); let num_lock_on = kbd_state[VK_NUMLOCK as usize] & 1 != 0; let vkey = event_info.vkey; let physical_key = &event_info.physical_key; let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, physical_key); event_info.text = PartialText::Text(key.to_text().map(SmolStr::new)); } let ev = event_info.finalize(); MatchResult::MessagesToDispatch(self.pending.complete_pending( pending_token, MessageAsKeyEvent { event: ev, is_synthetic: false }, )) } }, WM_KEYUP | WM_SYSKEYUP => { let pending_token = self.pending.add_pending(); *result = ProcResult::Value(0); let mut layouts = LAYOUT_CACHE.lock().unwrap(); let event_info = PartialKeyEventInfo::from_message( wparam, lparam, ElementState::Released, &mut layouts, ); // We MUST release the layout lock before calling `next_kbd_msg`, otherwise it // may deadlock drop(layouts); // It's important that we create the pending token before reading the next // message. let next_msg = next_kbd_msg(hwnd); let mut valid_event_info = Some(event_info); if let Some(next_msg) = next_msg { let mut layouts = LAYOUT_CACHE.lock().unwrap(); let (_, layout) = layouts.get_current_layout(); let is_fake = { let event_info = valid_event_info.as_ref().unwrap(); is_current_fake(event_info, next_msg, layout) }; if is_fake { valid_event_info = None; } } if let Some(event_info) = valid_event_info { let event = event_info.finalize(); return MatchResult::MessagesToDispatch(self.pending.complete_pending( pending_token, MessageAsKeyEvent { event, is_synthetic: false }, )); } MatchResult::TokenToRemove(pending_token) }, _ => MatchResult::Nothing, } }; let matcher_result = matcher(); match matcher_result { MatchResult::TokenToRemove(t) => self.pending.remove_pending(t), MatchResult::MessagesToDispatch(m) => m, MatchResult::Nothing => Vec::new(), } } // Allowing nominimal_bool lint because the `is_key_pressed` macro triggers this warning // and I don't know of another way to resolve it and also keeping the macro #[allow(clippy::nonminimal_bool)] fn synthesize_kbd_state( key_state: ElementState, kbd_state: &[u8; 256], ) -> Vec { let mut key_events = Vec::new(); let mut layouts = LAYOUT_CACHE.lock().unwrap(); let (locale_id, _) = layouts.get_current_layout(); macro_rules! is_key_pressed { ($vk:expr) => { kbd_state[$vk as usize] & 0x80 != 0 }; } // Is caps-lock active? Note that this is different from caps-lock // being held down. let caps_lock_on = kbd_state[VK_CAPITAL as usize] & 1 != 0; let num_lock_on = kbd_state[VK_NUMLOCK as usize] & 1 != 0; // We are synthesizing the press event for caps-lock first for the following reasons: // 1. If caps-lock is *not* held down but *is* active, then we have to synthesize all // printable keys, respecting the caps-lock state. // 2. If caps-lock is held down, we could choose to synthesize its keypress after every // other key, in which case all other keys *must* be sythesized as if the caps-lock state // was be the opposite of what it currently is. // -- // For the sake of simplicity we are choosing to always synthesize // caps-lock first, and always use the current caps-lock state // to determine the produced text if is_key_pressed!(VK_CAPITAL) { let event = Self::create_synthetic( VK_CAPITAL, key_state, caps_lock_on, num_lock_on, locale_id as HKL, &mut layouts, ); if let Some(event) = event { key_events.push(event); } } let do_non_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { for vk in 0..256 { match vk { VK_CONTROL | VK_LCONTROL | VK_RCONTROL | VK_SHIFT | VK_LSHIFT | VK_RSHIFT | VK_MENU | VK_LMENU | VK_RMENU | VK_CAPITAL => continue, _ => (), } if !is_key_pressed!(vk) { continue; } let event = Self::create_synthetic( vk, key_state, caps_lock_on, num_lock_on, locale_id as HKL, layouts, ); if let Some(event) = event { key_events.push(event); } } }; let do_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { const CLEAR_MODIFIER_VKS: [VIRTUAL_KEY; 6] = [VK_LCONTROL, VK_LSHIFT, VK_LMENU, VK_RCONTROL, VK_RSHIFT, VK_RMENU]; for vk in CLEAR_MODIFIER_VKS.iter() { if is_key_pressed!(*vk) { let event = Self::create_synthetic( *vk, key_state, caps_lock_on, num_lock_on, locale_id as HKL, layouts, ); if let Some(event) = event { key_events.push(event); } } } }; // Be cheeky and sequence modifier and non-modifier // key events such that non-modifier keys are not affected // by modifiers (except for caps-lock) match key_state { ElementState::Pressed => { do_non_modifier(&mut key_events, &mut layouts); do_modifier(&mut key_events, &mut layouts); }, ElementState::Released => { do_modifier(&mut key_events, &mut layouts); do_non_modifier(&mut key_events, &mut layouts); }, } key_events } fn create_synthetic( vk: VIRTUAL_KEY, key_state: ElementState, caps_lock_on: bool, num_lock_on: bool, locale_id: HKL, layouts: &mut MutexGuard<'_, LayoutCache>, ) -> Option { let scancode = unsafe { MapVirtualKeyExW(vk as u32, MAPVK_VK_TO_VSC_EX, locale_id) }; if scancode == 0 { return None; } let scancode = scancode as ExScancode; let physical_key = scancode_to_physicalkey(scancode as u32); let mods = if caps_lock_on { WindowsModifiers::CAPS_LOCK } else { WindowsModifiers::empty() }; let layout = layouts.layouts.get(&(locale_id as u64)).unwrap(); let logical_key = layout.get_key(mods, num_lock_on, vk, &physical_key); let key_without_modifiers = layout.get_key(WindowsModifiers::empty(), false, vk, &physical_key); let text = if key_state == ElementState::Pressed { logical_key.to_text().map(SmolStr::new) } else { None }; let event_info = PartialKeyEventInfo { vkey: vk, logical_key: PartialLogicalKey::This(logical_key.clone()), key_without_modifiers, key_state, is_repeat: false, physical_key, location: get_location(scancode, locale_id), utf16parts: Vec::with_capacity(8), text: PartialText::Text(text.clone()), }; let mut event = event_info.finalize(); event.logical_key = logical_key; event.platform_specific.text_with_all_modifiers = text; Some(MessageAsKeyEvent { event, is_synthetic: true }) } } enum PartialText { // Unicode System(Vec), Text(Option), } enum PartialLogicalKey { /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If /// the text consists of multiple grapheme clusters (user-precieved characters) that means that /// dead key could not be combined with the second input, and in that case we should fall back /// to using what would have without a dead-key input. TextOr(Key), /// Use the value directly provided by this variant This(Key), } struct PartialKeyEventInfo { vkey: VIRTUAL_KEY, key_state: ElementState, is_repeat: bool, physical_key: PhysicalKey, location: KeyLocation, logical_key: PartialLogicalKey, key_without_modifiers: Key, /// The UTF-16 code units of the text that was produced by the keypress event. /// This take all modifiers into account. Including CTRL utf16parts: Vec, text: PartialText, } impl PartialKeyEventInfo { fn from_message( wparam: WPARAM, lparam: LPARAM, state: ElementState, layouts: &mut MutexGuard<'_, LayoutCache>, ) -> Self { const NO_MODS: WindowsModifiers = WindowsModifiers::empty(); let (_, layout) = layouts.get_current_layout(); let lparam_struct = destructure_key_lparam(lparam); let vkey = wparam as VIRTUAL_KEY; let scancode = if lparam_struct.scancode == 0 { // In some cases (often with media keys) the device reports a scancode of 0 but a // valid virtual key. In these cases we obtain the scancode from the virtual key. unsafe { MapVirtualKeyExW(vkey as u32, MAPVK_VK_TO_VSC_EX, layout.hkl as HKL) as u16 } } else { new_ex_scancode(lparam_struct.scancode, lparam_struct.extended) }; let physical_key = scancode_to_physicalkey(scancode as u32); let location = get_location(scancode, layout.hkl as HKL); let kbd_state = get_kbd_state(); let mods = WindowsModifiers::active_modifiers(&kbd_state); let mods_without_ctrl = mods.remove_only_ctrl(); let num_lock_on = kbd_state[VK_NUMLOCK as usize] & 1 != 0; // On Windows Ctrl+NumLock = Pause (and apparently Ctrl+Pause -> NumLock). In these cases // the KeyCode still stores the real key, so in the name of consistency across platforms, we // circumvent this mapping and force the key values to match the keycode. // For more on this, read the article by Raymond Chen, titled: // "Why does Ctrl+ScrollLock cancel dialogs?" // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 let code_as_key = if mods.contains(WindowsModifiers::CONTROL) { match physical_key { PhysicalKey::Code(KeyCode::NumLock) => Some(Key::Named(NamedKey::NumLock)), PhysicalKey::Code(KeyCode::Pause) => Some(Key::Named(NamedKey::Pause)), _ => None, } } else { None }; let preliminary_logical_key = layout.get_key(mods_without_ctrl, num_lock_on, vkey, &physical_key); let key_is_char = matches!(preliminary_logical_key, Key::Character(_)); let is_pressed = state == ElementState::Pressed; let logical_key = if let Some(key) = code_as_key.clone() { PartialLogicalKey::This(key) } else if is_pressed && key_is_char && !mods.contains(WindowsModifiers::CONTROL) { // In some cases we want to use the UNICHAR text for logical_key in order to allow // dead keys to have an effect on the character reported by `logical_key`. PartialLogicalKey::TextOr(preliminary_logical_key) } else { PartialLogicalKey::This(preliminary_logical_key) }; let key_without_modifiers = if let Some(key) = code_as_key { key } else { match layout.get_key(NO_MODS, false, vkey, &physical_key) { // We convert dead keys into their character. // The reason for this is that `key_without_modifiers` is designed for key-bindings, // but the US International layout treats `'` (apostrophe) as a dead key and the // regular US layout treats it a character. In order for a single binding // configuration to work with both layouts, we forward each dead key as a character. Key::Dead(k) => { if let Some(ch) = k { // I'm avoiding the heap allocation. I don't want to talk about it :( let mut utf8 = [0; 4]; let s = ch.encode_utf8(&mut utf8); Key::Character(SmolStr::new(s)) } else { Key::Unidentified(NativeKey::Unidentified) } }, key => key, } }; PartialKeyEventInfo { vkey, key_state: state, logical_key, key_without_modifiers, is_repeat: lparam_struct.is_repeat, physical_key, location, utf16parts: Vec::with_capacity(8), text: PartialText::System(Vec::new()), } } fn finalize(self) -> KeyEvent { let mut char_with_all_modifiers = None; if !self.utf16parts.is_empty() { let os_string = OsString::from_wide(&self.utf16parts); if let Ok(string) = os_string.into_string() { char_with_all_modifiers = Some(SmolStr::new(string)); } } // The text without Ctrl let mut text = None; match self.text { PartialText::System(wide) => { if !wide.is_empty() { let os_string = OsString::from_wide(&wide); if let Ok(string) = os_string.into_string() { text = Some(SmolStr::new(string)); } } }, PartialText::Text(s) => { text = s.map(SmolStr::new); }, } let logical_key = match self.logical_key { PartialLogicalKey::TextOr(fallback) => match text.as_ref() { Some(s) => { if s.grapheme_indices(true).count() > 1 { fallback } else { Key::Character(s.clone()) } }, None => Key::Unidentified(NativeKey::Windows(self.vkey)), }, PartialLogicalKey::This(v) => v, }; KeyEvent { physical_key: self.physical_key, logical_key, text, location: self.location, state: self.key_state, repeat: self.is_repeat, platform_specific: KeyEventExtra { text_with_all_modifiers: char_with_all_modifiers, key_without_modifiers: self.key_without_modifiers, }, } } } #[derive(Debug, Copy, Clone)] struct KeyLParam { pub scancode: u8, pub extended: bool, /// This is `previous_state XOR transition_state`. See the lParam for WM_KEYDOWN and WM_KEYUP /// for further details. pub is_repeat: bool, } fn destructure_key_lparam(lparam: LPARAM) -> KeyLParam { let previous_state = (lparam >> 30) & 0x01; let transition_state = (lparam >> 31) & 0x01; KeyLParam { scancode: ((lparam >> 16) & 0xff) as u8, extended: ((lparam >> 24) & 0x01) != 0, is_repeat: (previous_state ^ transition_state) != 0, } } #[inline] fn new_ex_scancode(scancode: u8, extended: bool) -> ExScancode { (scancode as u16) | (if extended { 0xe000 } else { 0 }) } #[inline] fn ex_scancode_from_lparam(lparam: LPARAM) -> ExScancode { let lparam = destructure_key_lparam(lparam); new_ex_scancode(lparam.scancode, lparam.extended) } /// Gets the keyboard state as reported by messages that have been removed from the event queue. /// See also: get_async_kbd_state fn get_kbd_state() -> [u8; 256] { unsafe { let mut kbd_state: MaybeUninit<[u8; 256]> = MaybeUninit::uninit(); GetKeyboardState(kbd_state.as_mut_ptr() as *mut u8); kbd_state.assume_init() } } /// Gets the current keyboard state regardless of whether the corresponding keyboard events have /// been removed from the event queue. See also: get_kbd_state #[allow(clippy::uninit_assumed_init)] fn get_async_kbd_state() -> [u8; 256] { unsafe { let mut kbd_state: [u8; 256] = [0; 256]; for (vk, state) in kbd_state.iter_mut().enumerate() { let vk = vk as VIRTUAL_KEY; let async_state = GetAsyncKeyState(vk as i32); let is_down = (async_state & (1 << 15)) != 0; *state = if is_down { 0x80 } else { 0 }; if matches!(vk, VK_CAPITAL | VK_NUMLOCK | VK_SCROLL) { // Toggle states aren't reported by `GetAsyncKeyState` let toggle_state = GetKeyState(vk as i32); let is_active = (toggle_state & 1) != 0; *state |= u8::from(is_active); } } kbd_state } } /// On windows, AltGr == Ctrl + Alt /// /// Due to this equivalence, the system generates a fake Ctrl key-press (and key-release) preceding /// every AltGr key-press (and key-release). We check if the current event is a Ctrl event and if /// the next event is a right Alt (AltGr) event. If this is the case, the current event must be the /// fake Ctrl event. fn is_current_fake(curr_info: &PartialKeyEventInfo, next_msg: MSG, layout: &Layout) -> bool { let curr_is_ctrl = matches!(curr_info.logical_key, PartialLogicalKey::This(Key::Named(NamedKey::Control))); if layout.has_alt_graph { let next_code = ex_scancode_from_lparam(next_msg.lParam); let next_is_altgr = next_code == 0xe038; // 0xE038 is right alt if curr_is_ctrl && next_is_altgr { return true; } } false } enum PendingMessage { Incomplete, Complete(T), } struct IdentifiedPendingMessage { token: PendingMessageToken, msg: PendingMessage, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PendingMessageToken(u32); /// While processing keyboard events, we sometimes need /// to call `PeekMessageW` (`next_msg`). But `PeekMessageW` /// can also call the event handler, which means that the new event /// gets processed before finishing to process the one that came before. /// /// This would mean that the application receives events in the wrong order. /// To avoid this, we keep track whether we are in the middle of processing /// an event. Such an event is an "incomplete pending event". A /// "complete pending event" is one that has already finished processing, but /// hasn't been dispatched to the application because there still are incomplete /// pending events that came before it. /// /// When we finish processing an event, we call `complete_pending`, /// which returns an empty array if there are incomplete pending events, but /// if all pending events are complete, then it returns all pending events in /// the order they were encountered. These can then be dispatched to the application pub struct PendingEventQueue { pending: Mutex>>, next_id: AtomicU32, } impl PendingEventQueue { /// Add a new pending event to the "pending queue" pub fn add_pending(&self) -> PendingMessageToken { let token = self.next_token(); let mut pending = self.pending.lock().unwrap(); pending.push(IdentifiedPendingMessage { token, msg: PendingMessage::Incomplete }); token } /// Returns all finished pending events /// /// If the return value is non empty, it's guaranteed to contain `msg` /// /// See also: `add_pending` pub fn complete_pending(&self, token: PendingMessageToken, msg: T) -> Vec { let mut pending = self.pending.lock().unwrap(); let mut target_is_first = false; for (i, pending_msg) in pending.iter_mut().enumerate() { if pending_msg.token == token { pending_msg.msg = PendingMessage::Complete(msg); if i == 0 { target_is_first = true; } break; } } if target_is_first { // If the message that we just finished was the first one in the pending queue, // then we can empty the queue, and dispatch all of the messages. Self::drain_pending(&mut *pending) } else { Vec::new() } } pub fn complete_multi(&self, msgs: Vec) -> Vec { let mut pending = self.pending.lock().unwrap(); if pending.is_empty() { return msgs; } pending.reserve(msgs.len()); for msg in msgs { pending.push(IdentifiedPendingMessage { token: self.next_token(), msg: PendingMessage::Complete(msg), }); } Vec::new() } /// Returns all finished pending events /// /// It's safe to call this even if the element isn't in the list anymore /// /// See also: `add_pending` pub fn remove_pending(&self, token: PendingMessageToken) -> Vec { let mut pending = self.pending.lock().unwrap(); let mut was_first = false; if let Some(m) = pending.first() { if m.token == token { was_first = true; } } pending.retain(|m| m.token != token); if was_first { Self::drain_pending(&mut *pending) } else { Vec::new() } } fn drain_pending(pending: &mut Vec>) -> Vec { pending .drain(..) .map(|m| match m.msg { PendingMessage::Complete(msg) => msg, PendingMessage::Incomplete => { panic!( "Found an incomplete pending message when collecting messages. This \ indicates a bug in winit." ) }, }) .collect() } fn next_token(&self) -> PendingMessageToken { // It's okay for the u32 to overflow here. Yes, that could mean // that two different messages have the same token, // but that would only happen after having about 4 billion // messages sitting in the pending queue. // // In that case, having two identical tokens is the least of your concerns. let id = self.next_id.fetch_add(1, Relaxed); PendingMessageToken(id) } } impl Default for PendingEventQueue { fn default() -> Self { PendingEventQueue { pending: Mutex::new(Vec::new()), next_id: AtomicU32::new(0) } } } /// WARNING: Due to using PeekMessage, the event handler /// function may get called during this function. /// (Re-entrance to the event handler) /// /// This can cause a deadlock if calling this function /// while having a mutex locked. /// /// It can also cause code to get executed in a surprising order. pub fn next_kbd_msg(hwnd: HWND) -> Option { unsafe { let mut next_msg = MaybeUninit::uninit(); let peek_retval = PeekMessageW(next_msg.as_mut_ptr(), hwnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE); (peek_retval != 0).then(|| next_msg.assume_init()) } } fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { const ABNT_C2: VIRTUAL_KEY = VK_ABNT_C2 as VIRTUAL_KEY; let extension = 0xe000; let extended = (scancode & extension) == extension; let vkey = unsafe { MapVirtualKeyExW(scancode as u32, MAPVK_VSC_TO_VK_EX, hkl) as VIRTUAL_KEY }; // Use the native VKEY and the extended flag to cover most cases // This is taken from the `druid` GUI library, specifically // druid-shell/src/platform/windows/keyboard.rs match vkey { VK_LSHIFT | VK_LCONTROL | VK_LMENU | VK_LWIN => KeyLocation::Left, VK_RSHIFT | VK_RCONTROL | VK_RMENU | VK_RWIN => KeyLocation::Right, VK_RETURN if extended => KeyLocation::Numpad, VK_INSERT | VK_DELETE | VK_END | VK_DOWN | VK_NEXT | VK_LEFT | VK_CLEAR | VK_RIGHT | VK_HOME | VK_UP | VK_PRIOR => { if extended { KeyLocation::Standard } else { KeyLocation::Numpad } }, VK_NUMPAD0 | VK_NUMPAD1 | VK_NUMPAD2 | VK_NUMPAD3 | VK_NUMPAD4 | VK_NUMPAD5 | VK_NUMPAD6 | VK_NUMPAD7 | VK_NUMPAD8 | VK_NUMPAD9 | VK_DECIMAL | VK_DIVIDE | VK_MULTIPLY | VK_SUBTRACT | VK_ADD | ABNT_C2 => KeyLocation::Numpad, _ => KeyLocation::Standard, } } pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option { // See `scancode_to_physicalkey` for more info let hkl = unsafe { GetKeyboardLayout(0) }; let primary_lang_id = primarylangid(loword(hkl as u32)); let is_korean = primary_lang_id as u32 == LANG_KOREAN; let code = match physical_key { PhysicalKey::Code(code) => code, PhysicalKey::Unidentified(code) => { return match code { NativeKeyCode::Windows(scancode) => Some(scancode as u32), _ => None, }; }, }; match code { KeyCode::Backquote => Some(0x0029), KeyCode::Backslash => Some(0x002b), KeyCode::Backspace => Some(0x000e), KeyCode::BracketLeft => Some(0x001a), KeyCode::BracketRight => Some(0x001b), KeyCode::Comma => Some(0x0033), KeyCode::Digit0 => Some(0x000b), KeyCode::Digit1 => Some(0x0002), KeyCode::Digit2 => Some(0x0003), KeyCode::Digit3 => Some(0x0004), KeyCode::Digit4 => Some(0x0005), KeyCode::Digit5 => Some(0x0006), KeyCode::Digit6 => Some(0x0007), KeyCode::Digit7 => Some(0x0008), KeyCode::Digit8 => Some(0x0009), KeyCode::Digit9 => Some(0x000a), KeyCode::Equal => Some(0x000d), KeyCode::IntlBackslash => Some(0x0056), KeyCode::IntlRo => Some(0x0073), KeyCode::IntlYen => Some(0x007d), KeyCode::KeyA => Some(0x001e), KeyCode::KeyB => Some(0x0030), KeyCode::KeyC => Some(0x002e), KeyCode::KeyD => Some(0x0020), KeyCode::KeyE => Some(0x0012), KeyCode::KeyF => Some(0x0021), KeyCode::KeyG => Some(0x0022), KeyCode::KeyH => Some(0x0023), KeyCode::KeyI => Some(0x0017), KeyCode::KeyJ => Some(0x0024), KeyCode::KeyK => Some(0x0025), KeyCode::KeyL => Some(0x0026), KeyCode::KeyM => Some(0x0032), KeyCode::KeyN => Some(0x0031), KeyCode::KeyO => Some(0x0018), KeyCode::KeyP => Some(0x0019), KeyCode::KeyQ => Some(0x0010), KeyCode::KeyR => Some(0x0013), KeyCode::KeyS => Some(0x001f), KeyCode::KeyT => Some(0x0014), KeyCode::KeyU => Some(0x0016), KeyCode::KeyV => Some(0x002f), KeyCode::KeyW => Some(0x0011), KeyCode::KeyX => Some(0x002d), KeyCode::KeyY => Some(0x0015), KeyCode::KeyZ => Some(0x002c), KeyCode::Minus => Some(0x000c), KeyCode::Period => Some(0x0034), KeyCode::Quote => Some(0x0028), KeyCode::Semicolon => Some(0x0027), KeyCode::Slash => Some(0x0035), KeyCode::AltLeft => Some(0x0038), KeyCode::AltRight => Some(0xe038), KeyCode::CapsLock => Some(0x003a), KeyCode::ContextMenu => Some(0xe05d), KeyCode::ControlLeft => Some(0x001d), KeyCode::ControlRight => Some(0xe01d), KeyCode::Enter => Some(0x001c), KeyCode::SuperLeft => Some(0xe05b), KeyCode::SuperRight => Some(0xe05c), KeyCode::ShiftLeft => Some(0x002a), KeyCode::ShiftRight => Some(0x0036), KeyCode::Space => Some(0x0039), KeyCode::Tab => Some(0x000f), KeyCode::Convert => Some(0x0079), KeyCode::Lang1 => { if is_korean { Some(0xe0f2) } else { Some(0x0072) } }, KeyCode::Lang2 => { if is_korean { Some(0xe0f1) } else { Some(0x0071) } }, KeyCode::KanaMode => Some(0x0070), KeyCode::NonConvert => Some(0x007b), KeyCode::Delete => Some(0xe053), KeyCode::End => Some(0xe04f), KeyCode::Home => Some(0xe047), KeyCode::Insert => Some(0xe052), KeyCode::PageDown => Some(0xe051), KeyCode::PageUp => Some(0xe049), KeyCode::ArrowDown => Some(0xe050), KeyCode::ArrowLeft => Some(0xe04b), KeyCode::ArrowRight => Some(0xe04d), KeyCode::ArrowUp => Some(0xe048), KeyCode::NumLock => Some(0xe045), KeyCode::Numpad0 => Some(0x0052), KeyCode::Numpad1 => Some(0x004f), KeyCode::Numpad2 => Some(0x0050), KeyCode::Numpad3 => Some(0x0051), KeyCode::Numpad4 => Some(0x004b), KeyCode::Numpad5 => Some(0x004c), KeyCode::Numpad6 => Some(0x004d), KeyCode::Numpad7 => Some(0x0047), KeyCode::Numpad8 => Some(0x0048), KeyCode::Numpad9 => Some(0x0049), KeyCode::NumpadAdd => Some(0x004e), KeyCode::NumpadComma => Some(0x007e), KeyCode::NumpadDecimal => Some(0x0053), KeyCode::NumpadDivide => Some(0xe035), KeyCode::NumpadEnter => Some(0xe01c), KeyCode::NumpadEqual => Some(0x0059), KeyCode::NumpadMultiply => Some(0x0037), KeyCode::NumpadSubtract => Some(0x004a), KeyCode::Escape => Some(0x0001), KeyCode::F1 => Some(0x003b), KeyCode::F2 => Some(0x003c), KeyCode::F3 => Some(0x003d), KeyCode::F4 => Some(0x003e), KeyCode::F5 => Some(0x003f), KeyCode::F6 => Some(0x0040), KeyCode::F7 => Some(0x0041), KeyCode::F8 => Some(0x0042), KeyCode::F9 => Some(0x0043), KeyCode::F10 => Some(0x0044), KeyCode::F11 => Some(0x0057), KeyCode::F12 => Some(0x0058), KeyCode::F13 => Some(0x0064), KeyCode::F14 => Some(0x0065), KeyCode::F15 => Some(0x0066), KeyCode::F16 => Some(0x0067), KeyCode::F17 => Some(0x0068), KeyCode::F18 => Some(0x0069), KeyCode::F19 => Some(0x006a), KeyCode::F20 => Some(0x006b), KeyCode::F21 => Some(0x006c), KeyCode::F22 => Some(0x006d), KeyCode::F23 => Some(0x006e), KeyCode::F24 => Some(0x0076), KeyCode::PrintScreen => Some(0xe037), // KeyCode::PrintScreen => Some(0x0054), // Alt + PrintScreen KeyCode::ScrollLock => Some(0x0046), KeyCode::Pause => Some(0x0045), // KeyCode::Pause => Some(0xE046), // Ctrl + Pause KeyCode::BrowserBack => Some(0xe06a), KeyCode::BrowserFavorites => Some(0xe066), KeyCode::BrowserForward => Some(0xe069), KeyCode::BrowserHome => Some(0xe032), KeyCode::BrowserRefresh => Some(0xe067), KeyCode::BrowserSearch => Some(0xe065), KeyCode::BrowserStop => Some(0xe068), KeyCode::LaunchApp1 => Some(0xe06b), KeyCode::LaunchApp2 => Some(0xe021), KeyCode::LaunchMail => Some(0xe06c), KeyCode::MediaPlayPause => Some(0xe022), KeyCode::MediaSelect => Some(0xe06d), KeyCode::MediaStop => Some(0xe024), KeyCode::MediaTrackNext => Some(0xe019), KeyCode::MediaTrackPrevious => Some(0xe010), KeyCode::Power => Some(0xe05e), KeyCode::AudioVolumeDown => Some(0xe02e), KeyCode::AudioVolumeMute => Some(0xe020), KeyCode::AudioVolumeUp => Some(0xe030), _ => None, } } pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { // See: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html // and: https://www.w3.org/TR/uievents-code/ // and: The widget/NativeKeyToDOMCodeName.h file in the firefox source PhysicalKey::Code(match scancode { 0x0029 => KeyCode::Backquote, 0x002b => KeyCode::Backslash, 0x000e => KeyCode::Backspace, 0x001a => KeyCode::BracketLeft, 0x001b => KeyCode::BracketRight, 0x0033 => KeyCode::Comma, 0x000b => KeyCode::Digit0, 0x0002 => KeyCode::Digit1, 0x0003 => KeyCode::Digit2, 0x0004 => KeyCode::Digit3, 0x0005 => KeyCode::Digit4, 0x0006 => KeyCode::Digit5, 0x0007 => KeyCode::Digit6, 0x0008 => KeyCode::Digit7, 0x0009 => KeyCode::Digit8, 0x000a => KeyCode::Digit9, 0x000d => KeyCode::Equal, 0x0056 => KeyCode::IntlBackslash, 0x0073 => KeyCode::IntlRo, 0x007d => KeyCode::IntlYen, 0x001e => KeyCode::KeyA, 0x0030 => KeyCode::KeyB, 0x002e => KeyCode::KeyC, 0x0020 => KeyCode::KeyD, 0x0012 => KeyCode::KeyE, 0x0021 => KeyCode::KeyF, 0x0022 => KeyCode::KeyG, 0x0023 => KeyCode::KeyH, 0x0017 => KeyCode::KeyI, 0x0024 => KeyCode::KeyJ, 0x0025 => KeyCode::KeyK, 0x0026 => KeyCode::KeyL, 0x0032 => KeyCode::KeyM, 0x0031 => KeyCode::KeyN, 0x0018 => KeyCode::KeyO, 0x0019 => KeyCode::KeyP, 0x0010 => KeyCode::KeyQ, 0x0013 => KeyCode::KeyR, 0x001f => KeyCode::KeyS, 0x0014 => KeyCode::KeyT, 0x0016 => KeyCode::KeyU, 0x002f => KeyCode::KeyV, 0x0011 => KeyCode::KeyW, 0x002d => KeyCode::KeyX, 0x0015 => KeyCode::KeyY, 0x002c => KeyCode::KeyZ, 0x000c => KeyCode::Minus, 0x0034 => KeyCode::Period, 0x0028 => KeyCode::Quote, 0x0027 => KeyCode::Semicolon, 0x0035 => KeyCode::Slash, 0x0038 => KeyCode::AltLeft, 0xe038 => KeyCode::AltRight, 0x003a => KeyCode::CapsLock, 0xe05d => KeyCode::ContextMenu, 0x001d => KeyCode::ControlLeft, 0xe01d => KeyCode::ControlRight, 0x001c => KeyCode::Enter, 0xe05b => KeyCode::SuperLeft, 0xe05c => KeyCode::SuperRight, 0x002a => KeyCode::ShiftLeft, 0x0036 => KeyCode::ShiftRight, 0x0039 => KeyCode::Space, 0x000f => KeyCode::Tab, 0x0079 => KeyCode::Convert, 0x0072 => KeyCode::Lang1, // for non-Korean layout 0xe0f2 => KeyCode::Lang1, // for Korean layout 0x0071 => KeyCode::Lang2, // for non-Korean layout 0xe0f1 => KeyCode::Lang2, // for Korean layout 0x0070 => KeyCode::KanaMode, 0x007b => KeyCode::NonConvert, 0xe053 => KeyCode::Delete, 0xe04f => KeyCode::End, 0xe047 => KeyCode::Home, 0xe052 => KeyCode::Insert, 0xe051 => KeyCode::PageDown, 0xe049 => KeyCode::PageUp, 0xe050 => KeyCode::ArrowDown, 0xe04b => KeyCode::ArrowLeft, 0xe04d => KeyCode::ArrowRight, 0xe048 => KeyCode::ArrowUp, 0xe045 => KeyCode::NumLock, 0x0052 => KeyCode::Numpad0, 0x004f => KeyCode::Numpad1, 0x0050 => KeyCode::Numpad2, 0x0051 => KeyCode::Numpad3, 0x004b => KeyCode::Numpad4, 0x004c => KeyCode::Numpad5, 0x004d => KeyCode::Numpad6, 0x0047 => KeyCode::Numpad7, 0x0048 => KeyCode::Numpad8, 0x0049 => KeyCode::Numpad9, 0x004e => KeyCode::NumpadAdd, 0x007e => KeyCode::NumpadComma, 0x0053 => KeyCode::NumpadDecimal, 0xe035 => KeyCode::NumpadDivide, 0xe01c => KeyCode::NumpadEnter, 0x0059 => KeyCode::NumpadEqual, 0x0037 => KeyCode::NumpadMultiply, 0x004a => KeyCode::NumpadSubtract, 0x0001 => KeyCode::Escape, 0x003b => KeyCode::F1, 0x003c => KeyCode::F2, 0x003d => KeyCode::F3, 0x003e => KeyCode::F4, 0x003f => KeyCode::F5, 0x0040 => KeyCode::F6, 0x0041 => KeyCode::F7, 0x0042 => KeyCode::F8, 0x0043 => KeyCode::F9, 0x0044 => KeyCode::F10, 0x0057 => KeyCode::F11, 0x0058 => KeyCode::F12, 0x0064 => KeyCode::F13, 0x0065 => KeyCode::F14, 0x0066 => KeyCode::F15, 0x0067 => KeyCode::F16, 0x0068 => KeyCode::F17, 0x0069 => KeyCode::F18, 0x006a => KeyCode::F19, 0x006b => KeyCode::F20, 0x006c => KeyCode::F21, 0x006d => KeyCode::F22, 0x006e => KeyCode::F23, 0x0076 => KeyCode::F24, 0xe037 => KeyCode::PrintScreen, 0x0054 => KeyCode::PrintScreen, // Alt + PrintScreen 0x0046 => KeyCode::ScrollLock, 0x0045 => KeyCode::Pause, 0xe046 => KeyCode::Pause, // Ctrl + Pause 0xe06a => KeyCode::BrowserBack, 0xe066 => KeyCode::BrowserFavorites, 0xe069 => KeyCode::BrowserForward, 0xe032 => KeyCode::BrowserHome, 0xe067 => KeyCode::BrowserRefresh, 0xe065 => KeyCode::BrowserSearch, 0xe068 => KeyCode::BrowserStop, 0xe06b => KeyCode::LaunchApp1, 0xe021 => KeyCode::LaunchApp2, 0xe06c => KeyCode::LaunchMail, 0xe022 => KeyCode::MediaPlayPause, 0xe06d => KeyCode::MediaSelect, 0xe024 => KeyCode::MediaStop, 0xe019 => KeyCode::MediaTrackNext, 0xe010 => KeyCode::MediaTrackPrevious, 0xe05e => KeyCode::Power, 0xe02e => KeyCode::AudioVolumeDown, 0xe020 => KeyCode::AudioVolumeMute, 0xe030 => KeyCode::AudioVolumeUp, _ => return PhysicalKey::Unidentified(NativeKeyCode::Windows(scancode as u16)), }) } winit-0.30.9/src/platform_impl/windows/keyboard_layout.rs000064400000000000000000001233601046102023000217450ustar 00000000000000use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::ffi::OsString; use std::os::windows::ffi::OsStringExt; use std::sync::Mutex; use crate::utils::Lazy; use smol_str::SmolStr; use windows_sys::Win32::System::SystemServices::{LANG_JAPANESE, LANG_KOREAN}; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ GetKeyState, GetKeyboardLayout, MapVirtualKeyExW, ToUnicodeEx, MAPVK_VK_TO_VSC_EX, VIRTUAL_KEY, VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, VK_BACK, VK_BROWSER_BACK, VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH, VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_CANCEL, VK_CAPITAL, VK_CLEAR, VK_CONTROL, VK_CONVERT, VK_CRSEL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_EREOF, VK_ESCAPE, VK_EXECUTE, VK_EXSEL, VK_F1, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, VK_F19, VK_F2, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_FINAL, VK_GAMEPAD_A, VK_GAMEPAD_B, VK_GAMEPAD_DPAD_DOWN, VK_GAMEPAD_DPAD_LEFT, VK_GAMEPAD_DPAD_RIGHT, VK_GAMEPAD_DPAD_UP, VK_GAMEPAD_LEFT_SHOULDER, VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON, VK_GAMEPAD_LEFT_THUMBSTICK_DOWN, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT, VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT, VK_GAMEPAD_LEFT_THUMBSTICK_UP, VK_GAMEPAD_LEFT_TRIGGER, VK_GAMEPAD_MENU, VK_GAMEPAD_RIGHT_SHOULDER, VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON, VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN, VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT, VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT, VK_GAMEPAD_RIGHT_THUMBSTICK_UP, VK_GAMEPAD_RIGHT_TRIGGER, VK_GAMEPAD_VIEW, VK_GAMEPAD_X, VK_GAMEPAD_Y, VK_HANGUL, VK_HANJA, VK_HELP, VK_HOME, VK_ICO_00, VK_ICO_CLEAR, VK_ICO_HELP, VK_INSERT, VK_JUNJA, VK_KANA, VK_KANJI, VK_LAUNCH_APP1, VK_LAUNCH_APP2, VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LBUTTON, VK_LCONTROL, VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MBUTTON, VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE, VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_MENU, VK_MODECHANGE, VK_MULTIPLY, VK_NAVIGATION_ACCEPT, VK_NAVIGATION_CANCEL, VK_NAVIGATION_DOWN, VK_NAVIGATION_LEFT, VK_NAVIGATION_MENU, VK_NAVIGATION_RIGHT, VK_NAVIGATION_UP, VK_NAVIGATION_VIEW, VK_NEXT, VK_NONAME, VK_NONCONVERT, VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_OEM_1, VK_OEM_102, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_ATTN, VK_OEM_AUTO, VK_OEM_AX, VK_OEM_BACKTAB, VK_OEM_CLEAR, VK_OEM_COMMA, VK_OEM_COPY, VK_OEM_CUSEL, VK_OEM_ENLW, VK_OEM_FINISH, VK_OEM_FJ_LOYA, VK_OEM_FJ_MASSHOU, VK_OEM_FJ_ROYA, VK_OEM_FJ_TOUROKU, VK_OEM_JUMP, VK_OEM_MINUS, VK_OEM_NEC_EQUAL, VK_OEM_PA1, VK_OEM_PA2, VK_OEM_PA3, VK_OEM_PERIOD, VK_OEM_PLUS, VK_OEM_RESET, VK_OEM_WSCTRL, VK_PA1, VK_PACKET, VK_PAUSE, VK_PLAY, VK_PRINT, VK_PRIOR, VK_PROCESSKEY, VK_RBUTTON, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT, VK_RWIN, VK_SCROLL, VK_SELECT, VK_SEPARATOR, VK_SHIFT, VK_SLEEP, VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_TAB, VK_UP, VK_VOLUME_DOWN, VK_VOLUME_MUTE, VK_VOLUME_UP, VK_XBUTTON1, VK_XBUTTON2, VK_ZOOM, }; use windows_sys::Win32::UI::TextServices::HKL; use crate::keyboard::{Key, KeyCode, ModifiersState, NamedKey, NativeKey, PhysicalKey}; use crate::platform_impl::{loword, primarylangid, scancode_to_physicalkey}; pub(crate) static LAYOUT_CACHE: Lazy> = Lazy::new(|| Mutex::new(LayoutCache::default())); fn key_pressed(vkey: VIRTUAL_KEY) -> bool { unsafe { (GetKeyState(vkey as i32) & (1 << 15)) == (1 << 15) } } const NUMPAD_VKEYS: [VIRTUAL_KEY; 16] = [ VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_MULTIPLY, VK_ADD, VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL, VK_DIVIDE, ]; static NUMPAD_KEYCODES: Lazy> = Lazy::new(|| { let mut keycodes = HashSet::new(); keycodes.insert(KeyCode::Numpad0); keycodes.insert(KeyCode::Numpad1); keycodes.insert(KeyCode::Numpad2); keycodes.insert(KeyCode::Numpad3); keycodes.insert(KeyCode::Numpad4); keycodes.insert(KeyCode::Numpad5); keycodes.insert(KeyCode::Numpad6); keycodes.insert(KeyCode::Numpad7); keycodes.insert(KeyCode::Numpad8); keycodes.insert(KeyCode::Numpad9); keycodes.insert(KeyCode::NumpadMultiply); keycodes.insert(KeyCode::NumpadAdd); keycodes.insert(KeyCode::NumpadComma); keycodes.insert(KeyCode::NumpadSubtract); keycodes.insert(KeyCode::NumpadDecimal); keycodes.insert(KeyCode::NumpadDivide); keycodes }); bitflags::bitflags! { #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct WindowsModifiers : u8 { const SHIFT = 1 << 0; const CONTROL = 1 << 1; const ALT = 1 << 2; const CAPS_LOCK = 1 << 3; const FLAGS_END = 1 << 4; } } impl WindowsModifiers { pub fn active_modifiers(key_state: &[u8; 256]) -> WindowsModifiers { let shift = key_state[VK_SHIFT as usize] & 0x80 != 0; let lshift = key_state[VK_LSHIFT as usize] & 0x80 != 0; let rshift = key_state[VK_RSHIFT as usize] & 0x80 != 0; let control = key_state[VK_CONTROL as usize] & 0x80 != 0; let lcontrol = key_state[VK_LCONTROL as usize] & 0x80 != 0; let rcontrol = key_state[VK_RCONTROL as usize] & 0x80 != 0; let alt = key_state[VK_MENU as usize] & 0x80 != 0; let lalt = key_state[VK_LMENU as usize] & 0x80 != 0; let ralt = key_state[VK_RMENU as usize] & 0x80 != 0; let caps = key_state[VK_CAPITAL as usize] & 0x01 != 0; let mut result = WindowsModifiers::empty(); if shift || lshift || rshift { result.insert(WindowsModifiers::SHIFT); } if control || lcontrol || rcontrol { result.insert(WindowsModifiers::CONTROL); } if alt || lalt || ralt { result.insert(WindowsModifiers::ALT); } if caps { result.insert(WindowsModifiers::CAPS_LOCK); } result } pub fn apply_to_kbd_state(self, key_state: &mut [u8; 256]) { if self.intersects(Self::SHIFT) { key_state[VK_SHIFT as usize] |= 0x80; } else { key_state[VK_SHIFT as usize] &= !0x80; key_state[VK_LSHIFT as usize] &= !0x80; key_state[VK_RSHIFT as usize] &= !0x80; } if self.intersects(Self::CONTROL) { key_state[VK_CONTROL as usize] |= 0x80; } else { key_state[VK_CONTROL as usize] &= !0x80; key_state[VK_LCONTROL as usize] &= !0x80; key_state[VK_RCONTROL as usize] &= !0x80; } if self.intersects(Self::ALT) { key_state[VK_MENU as usize] |= 0x80; } else { key_state[VK_MENU as usize] &= !0x80; key_state[VK_LMENU as usize] &= !0x80; key_state[VK_RMENU as usize] &= !0x80; } if self.intersects(Self::CAPS_LOCK) { key_state[VK_CAPITAL as usize] |= 0x01; } else { key_state[VK_CAPITAL as usize] &= !0x01; } } /// Removes the control modifier if the alt modifier is not present. /// This is useful because on Windows: (Control + Alt) == AltGr /// but we don't want to interfere with the AltGr state. pub fn remove_only_ctrl(mut self) -> WindowsModifiers { if !self.contains(WindowsModifiers::ALT) { self.remove(WindowsModifiers::CONTROL); } self } } pub(crate) struct Layout { pub hkl: u64, /// Maps numpad keys from Windows virtual key to a `Key`. /// /// This is useful because some numpad keys generate different characters based on the locale. /// For example `VK_DECIMAL` is sometimes "." and sometimes ",". Note: numpad-specific virtual /// keys are only produced by Windows when the NumLock is active. /// /// Making this field separate from the `keys` field saves having to add NumLock as a modifier /// to `WindowsModifiers`, which would double the number of items in keys. pub numlock_on_keys: HashMap, /// Like `numlock_on_keys` but this will map to the key that would be produced if numlock was /// off. The keys of this map are identical to the keys of `numlock_on_keys`. pub numlock_off_keys: HashMap, /// Maps a modifier state to group of key strings /// We're not using `ModifiersState` here because that object cannot express caps lock, /// but we need to handle caps lock too. /// /// This map shouldn't need to exist. /// However currently this seems to be the only good way /// of getting the label for the pressed key. Note that calling `ToUnicode` /// just when the key is pressed/released would be enough if `ToUnicode` wouldn't /// change the keyboard state (it clears the dead key). There is a flag to prevent /// changing the state, but that flag requires Windows 10, version 1607 or newer) pub keys: HashMap>, pub has_alt_graph: bool, } impl Layout { pub fn get_key( &self, mods: WindowsModifiers, num_lock_on: bool, vkey: VIRTUAL_KEY, physical_key: &PhysicalKey, ) -> Key { let native_code = NativeKey::Windows(vkey); let unknown_alt = vkey == VK_MENU; if !unknown_alt { // Here we try using the virtual key directly but if the virtual key doesn't distinguish // between left and right alt, we can't report AltGr. Therefore, we only do this if the // key is not the "unknown alt" key. // // The reason for using the virtual key directly is that `MapVirtualKeyExW` (used when // building the keys map) sometimes maps virtual keys to odd scancodes that don't match // the scancode coming from the KEYDOWN message for the same key. For example: `VK_LEFT` // is mapped to `0x004B`, but the scancode for the left arrow is `0xE04B`. let key_from_vkey = vkey_to_non_char_key(vkey, native_code.clone(), self.hkl, self.has_alt_graph); if !matches!(key_from_vkey, Key::Unidentified(_)) { return key_from_vkey; } } if num_lock_on { if let Some(key) = self.numlock_on_keys.get(&vkey) { return key.clone(); } } else if let Some(key) = self.numlock_off_keys.get(&vkey) { return key.clone(); } if let PhysicalKey::Code(code) = physical_key { if let Some(keys) = self.keys.get(&mods) { if let Some(key) = keys.get(code) { return key.clone(); } } } Key::Unidentified(native_code) } } #[derive(Default)] pub(crate) struct LayoutCache { /// Maps locale identifiers (HKL) to layouts pub layouts: HashMap, } impl LayoutCache { /// Checks whether the current layout is already known and /// prepares the layout if it isn't known. /// The current layout is then returned. pub fn get_current_layout(&mut self) -> (u64, &Layout) { let locale_id = unsafe { GetKeyboardLayout(0) } as u64; match self.layouts.entry(locale_id) { Entry::Occupied(entry) => (locale_id, entry.into_mut()), Entry::Vacant(entry) => { let layout = Self::prepare_layout(locale_id); (locale_id, entry.insert(layout)) }, } } pub fn get_agnostic_mods(&mut self) -> ModifiersState { let (_, layout) = self.get_current_layout(); let filter_out_altgr = layout.has_alt_graph && key_pressed(VK_RMENU); let mut mods = ModifiersState::empty(); mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT)); mods.set(ModifiersState::CONTROL, key_pressed(VK_CONTROL) && !filter_out_altgr); mods.set(ModifiersState::ALT, key_pressed(VK_MENU) && !filter_out_altgr); mods.set(ModifiersState::SUPER, key_pressed(VK_LWIN) || key_pressed(VK_RWIN)); mods } fn prepare_layout(locale_id: u64) -> Layout { let mut layout = Layout { hkl: locale_id, numlock_on_keys: Default::default(), numlock_off_keys: Default::default(), keys: Default::default(), has_alt_graph: false, }; // We initialize the keyboard state with all zeros to // simulate a scenario when no modifier is active. let mut key_state = [0u8; 256]; // `MapVirtualKeyExW` maps (non-numpad-specific) virtual keys to scancodes as if numlock // was off. We rely on this behavior to find all virtual keys which are not numpad-specific // but map to the numpad. // // src_vkey: VK ==> scancode: u16 (on the numpad) // // Then we convert the source virtual key into a `Key` and the scancode into a virtual key // to get the reverse mapping. // // src_vkey: VK ==> scancode: u16 (on the numpad) // || || // \/ \/ // map_value: Key <- map_vkey: VK layout.numlock_off_keys.reserve(NUMPAD_KEYCODES.len()); for vk in 0..256 { let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) }; if scancode == 0 { continue; } let keycode = match scancode_to_physicalkey(scancode) { PhysicalKey::Code(code) => code, // TODO: validate that we can skip on unidentified keys (probably never occurs?) _ => continue, }; if !is_numpad_specific(vk as VIRTUAL_KEY) && NUMPAD_KEYCODES.contains(&keycode) { let native_code = NativeKey::Windows(vk as VIRTUAL_KEY); let map_vkey = keycode_to_vkey(keycode, locale_id); if map_vkey == 0 { continue; } let map_value = vkey_to_non_char_key(vk as VIRTUAL_KEY, native_code, locale_id, false); if matches!(map_value, Key::Unidentified(_)) { continue; } layout.numlock_off_keys.insert(map_vkey, map_value); } } layout.numlock_on_keys.reserve(NUMPAD_VKEYS.len()); for vk in NUMPAD_VKEYS.iter() { let vk = (*vk) as u32; let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) }; let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); if let ToUnicodeResult::Str(s) = unicode { layout.numlock_on_keys.insert(vk as VIRTUAL_KEY, Key::Character(SmolStr::new(s))); } } // Iterate through every combination of modifiers let mods_end = WindowsModifiers::FLAGS_END.bits(); for mod_state in 0..mods_end { let mut keys_for_this_mod = HashMap::with_capacity(256); let mod_state = WindowsModifiers::from_bits_retain(mod_state); mod_state.apply_to_kbd_state(&mut key_state); // Virtual key values are in the domain [0, 255]. // This is reinforced by the fact that the keyboard state array has 256 // elements. This array is allowed to be indexed by virtual key values // giving the key state for the virtual key used for indexing. for vk in 0..256 { let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) }; if scancode == 0 { continue; } let native_code = NativeKey::Windows(vk as VIRTUAL_KEY); let key_code = match scancode_to_physicalkey(scancode) { PhysicalKey::Code(code) => code, // TODO: validate that we can skip on unidentified keys (probably never occurs?) _ => continue, }; // Let's try to get the key from just the scancode and vk // We don't necessarily know yet if AltGraph is present on this layout so we'll // assume it isn't. Then we'll do a second pass where we set the "AltRight" keys to // "AltGr" in case we find out that there's an AltGraph. let preliminary_key = vkey_to_non_char_key(vk as VIRTUAL_KEY, native_code, locale_id, false); match preliminary_key { Key::Unidentified(_) => (), _ => { keys_for_this_mod.insert(key_code, preliminary_key); continue; }, } let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); let key = match unicode { ToUnicodeResult::Str(str) => Key::Character(SmolStr::new(str)), ToUnicodeResult::Dead(dead_char) => { // println!("{:?} - {:?} produced dead {:?}", key_code, mod_state, // dead_char); Key::Dead(dead_char) }, ToUnicodeResult::None => { let has_alt = mod_state.contains(WindowsModifiers::ALT); let has_ctrl = mod_state.contains(WindowsModifiers::CONTROL); // HACK: `ToUnicodeEx` seems to fail getting the string for the numpad // divide key, so we handle that explicitly here if !has_alt && !has_ctrl && key_code == KeyCode::NumpadDivide { Key::Character(SmolStr::new("/")) } else { // Just use the unidentified key, we got earlier preliminary_key } }, }; // Check for alt graph. // The logic is that if a key pressed with no modifier produces // a different `Character` from when it's pressed with CTRL+ALT then the layout // has AltGr. let ctrl_alt: WindowsModifiers = WindowsModifiers::CONTROL | WindowsModifiers::ALT; let is_in_ctrl_alt = mod_state == ctrl_alt; if !layout.has_alt_graph && is_in_ctrl_alt { // Unwrapping here because if we are in the ctrl+alt modifier state // then the alt modifier state must have come before. let simple_keys = layout.keys.get(&WindowsModifiers::empty()).unwrap(); if let Some(Key::Character(key_no_altgr)) = simple_keys.get(&key_code) { if let Key::Character(key) = &key { layout.has_alt_graph = key != key_no_altgr; } } } keys_for_this_mod.insert(key_code, key); } layout.keys.insert(mod_state, keys_for_this_mod); } // Second pass: replace right alt keys with AltGr if the layout has alt graph if layout.has_alt_graph { for mod_state in 0..mods_end { let mod_state = WindowsModifiers::from_bits_retain(mod_state); if let Some(keys) = layout.keys.get_mut(&mod_state) { if let Some(key) = keys.get_mut(&KeyCode::AltRight) { *key = Key::Named(NamedKey::AltGraph); } } } } layout } fn to_unicode_string( key_state: &[u8; 256], vkey: u32, scancode: u32, locale_id: u64, ) -> ToUnicodeResult { unsafe { let mut label_wide = [0u16; 8]; let mut wide_len = ToUnicodeEx( vkey, scancode, (&key_state[0]) as *const _, (&mut label_wide[0]) as *mut _, label_wide.len() as i32, 0, locale_id as HKL, ); if wide_len < 0 { // If it's dead, we run `ToUnicode` again to consume the dead-key wide_len = ToUnicodeEx( vkey, scancode, (&key_state[0]) as *const _, (&mut label_wide[0]) as *mut _, label_wide.len() as i32, 0, locale_id as HKL, ); if wide_len > 0 { let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); if let Ok(label_str) = os_string.into_string() { if let Some(ch) = label_str.chars().next() { return ToUnicodeResult::Dead(Some(ch)); } } } return ToUnicodeResult::Dead(None); } if wide_len > 0 { let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); if let Ok(label_str) = os_string.into_string() { return ToUnicodeResult::Str(label_str); } } } ToUnicodeResult::None } } #[derive(Debug, Clone, Eq, PartialEq)] enum ToUnicodeResult { Str(String), Dead(Option), None, } fn is_numpad_specific(vk: VIRTUAL_KEY) -> bool { matches!( vk, VK_NUMPAD0 | VK_NUMPAD1 | VK_NUMPAD2 | VK_NUMPAD3 | VK_NUMPAD4 | VK_NUMPAD5 | VK_NUMPAD6 | VK_NUMPAD7 | VK_NUMPAD8 | VK_NUMPAD9 | VK_ADD | VK_SUBTRACT | VK_DIVIDE | VK_DECIMAL | VK_SEPARATOR ) } fn keycode_to_vkey(keycode: KeyCode, hkl: u64) -> VIRTUAL_KEY { let primary_lang_id = primarylangid(loword(hkl as u32)); let is_korean = primary_lang_id as u32 == LANG_KOREAN; let is_japanese = primary_lang_id as u32 == LANG_JAPANESE; match keycode { KeyCode::Backquote => 0, KeyCode::Backslash => 0, KeyCode::BracketLeft => 0, KeyCode::BracketRight => 0, KeyCode::Comma => 0, KeyCode::Digit0 => 0, KeyCode::Digit1 => 0, KeyCode::Digit2 => 0, KeyCode::Digit3 => 0, KeyCode::Digit4 => 0, KeyCode::Digit5 => 0, KeyCode::Digit6 => 0, KeyCode::Digit7 => 0, KeyCode::Digit8 => 0, KeyCode::Digit9 => 0, KeyCode::Equal => 0, KeyCode::IntlBackslash => 0, KeyCode::IntlRo => 0, KeyCode::IntlYen => 0, KeyCode::KeyA => 0, KeyCode::KeyB => 0, KeyCode::KeyC => 0, KeyCode::KeyD => 0, KeyCode::KeyE => 0, KeyCode::KeyF => 0, KeyCode::KeyG => 0, KeyCode::KeyH => 0, KeyCode::KeyI => 0, KeyCode::KeyJ => 0, KeyCode::KeyK => 0, KeyCode::KeyL => 0, KeyCode::KeyM => 0, KeyCode::KeyN => 0, KeyCode::KeyO => 0, KeyCode::KeyP => 0, KeyCode::KeyQ => 0, KeyCode::KeyR => 0, KeyCode::KeyS => 0, KeyCode::KeyT => 0, KeyCode::KeyU => 0, KeyCode::KeyV => 0, KeyCode::KeyW => 0, KeyCode::KeyX => 0, KeyCode::KeyY => 0, KeyCode::KeyZ => 0, KeyCode::Minus => 0, KeyCode::Period => 0, KeyCode::Quote => 0, KeyCode::Semicolon => 0, KeyCode::Slash => 0, KeyCode::AltLeft => VK_LMENU, KeyCode::AltRight => VK_RMENU, KeyCode::Backspace => VK_BACK, KeyCode::CapsLock => VK_CAPITAL, KeyCode::ContextMenu => VK_APPS, KeyCode::ControlLeft => VK_LCONTROL, KeyCode::ControlRight => VK_RCONTROL, KeyCode::Enter => VK_RETURN, KeyCode::SuperLeft => VK_LWIN, KeyCode::SuperRight => VK_RWIN, KeyCode::ShiftLeft => VK_RSHIFT, KeyCode::ShiftRight => VK_LSHIFT, KeyCode::Space => VK_SPACE, KeyCode::Tab => VK_TAB, KeyCode::Convert => VK_CONVERT, KeyCode::KanaMode => VK_KANA, KeyCode::Lang1 if is_korean => VK_HANGUL, KeyCode::Lang1 if is_japanese => VK_KANA, KeyCode::Lang2 if is_korean => VK_HANJA, KeyCode::Lang2 if is_japanese => 0, KeyCode::Lang3 if is_japanese => VK_OEM_FINISH, KeyCode::Lang4 if is_japanese => 0, KeyCode::Lang5 if is_japanese => 0, KeyCode::NonConvert => VK_NONCONVERT, KeyCode::Delete => VK_DELETE, KeyCode::End => VK_END, KeyCode::Help => VK_HELP, KeyCode::Home => VK_HOME, KeyCode::Insert => VK_INSERT, KeyCode::PageDown => VK_NEXT, KeyCode::PageUp => VK_PRIOR, KeyCode::ArrowDown => VK_DOWN, KeyCode::ArrowLeft => VK_LEFT, KeyCode::ArrowRight => VK_RIGHT, KeyCode::ArrowUp => VK_UP, KeyCode::NumLock => VK_NUMLOCK, KeyCode::Numpad0 => VK_NUMPAD0, KeyCode::Numpad1 => VK_NUMPAD1, KeyCode::Numpad2 => VK_NUMPAD2, KeyCode::Numpad3 => VK_NUMPAD3, KeyCode::Numpad4 => VK_NUMPAD4, KeyCode::Numpad5 => VK_NUMPAD5, KeyCode::Numpad6 => VK_NUMPAD6, KeyCode::Numpad7 => VK_NUMPAD7, KeyCode::Numpad8 => VK_NUMPAD8, KeyCode::Numpad9 => VK_NUMPAD9, KeyCode::NumpadAdd => VK_ADD, KeyCode::NumpadBackspace => VK_BACK, KeyCode::NumpadClear => VK_CLEAR, KeyCode::NumpadClearEntry => 0, KeyCode::NumpadComma => VK_SEPARATOR, KeyCode::NumpadDecimal => VK_DECIMAL, KeyCode::NumpadDivide => VK_DIVIDE, KeyCode::NumpadEnter => VK_RETURN, KeyCode::NumpadEqual => 0, KeyCode::NumpadHash => 0, KeyCode::NumpadMemoryAdd => 0, KeyCode::NumpadMemoryClear => 0, KeyCode::NumpadMemoryRecall => 0, KeyCode::NumpadMemoryStore => 0, KeyCode::NumpadMemorySubtract => 0, KeyCode::NumpadMultiply => VK_MULTIPLY, KeyCode::NumpadParenLeft => 0, KeyCode::NumpadParenRight => 0, KeyCode::NumpadStar => 0, KeyCode::NumpadSubtract => VK_SUBTRACT, KeyCode::Escape => VK_ESCAPE, KeyCode::Fn => 0, KeyCode::FnLock => 0, KeyCode::PrintScreen => VK_SNAPSHOT, KeyCode::ScrollLock => VK_SCROLL, KeyCode::Pause => VK_PAUSE, KeyCode::BrowserBack => VK_BROWSER_BACK, KeyCode::BrowserFavorites => VK_BROWSER_FAVORITES, KeyCode::BrowserForward => VK_BROWSER_FORWARD, KeyCode::BrowserHome => VK_BROWSER_HOME, KeyCode::BrowserRefresh => VK_BROWSER_REFRESH, KeyCode::BrowserSearch => VK_BROWSER_SEARCH, KeyCode::BrowserStop => VK_BROWSER_STOP, KeyCode::Eject => 0, KeyCode::LaunchApp1 => VK_LAUNCH_APP1, KeyCode::LaunchApp2 => VK_LAUNCH_APP2, KeyCode::LaunchMail => VK_LAUNCH_MAIL, KeyCode::MediaPlayPause => VK_MEDIA_PLAY_PAUSE, KeyCode::MediaSelect => VK_LAUNCH_MEDIA_SELECT, KeyCode::MediaStop => VK_MEDIA_STOP, KeyCode::MediaTrackNext => VK_MEDIA_NEXT_TRACK, KeyCode::MediaTrackPrevious => VK_MEDIA_PREV_TRACK, KeyCode::Power => 0, KeyCode::Sleep => 0, KeyCode::AudioVolumeDown => VK_VOLUME_DOWN, KeyCode::AudioVolumeMute => VK_VOLUME_MUTE, KeyCode::AudioVolumeUp => VK_VOLUME_UP, KeyCode::WakeUp => 0, KeyCode::Hyper => 0, KeyCode::Turbo => 0, KeyCode::Abort => 0, KeyCode::Resume => 0, KeyCode::Suspend => 0, KeyCode::Again => 0, KeyCode::Copy => 0, KeyCode::Cut => 0, KeyCode::Find => 0, KeyCode::Open => 0, KeyCode::Paste => 0, KeyCode::Props => 0, KeyCode::Select => VK_SELECT, KeyCode::Undo => 0, KeyCode::Hiragana => 0, KeyCode::Katakana => 0, KeyCode::F1 => VK_F1, KeyCode::F2 => VK_F2, KeyCode::F3 => VK_F3, KeyCode::F4 => VK_F4, KeyCode::F5 => VK_F5, KeyCode::F6 => VK_F6, KeyCode::F7 => VK_F7, KeyCode::F8 => VK_F8, KeyCode::F9 => VK_F9, KeyCode::F10 => VK_F10, KeyCode::F11 => VK_F11, KeyCode::F12 => VK_F12, KeyCode::F13 => VK_F13, KeyCode::F14 => VK_F14, KeyCode::F15 => VK_F15, KeyCode::F16 => VK_F16, KeyCode::F17 => VK_F17, KeyCode::F18 => VK_F18, KeyCode::F19 => VK_F19, KeyCode::F20 => VK_F20, KeyCode::F21 => VK_F21, KeyCode::F22 => VK_F22, KeyCode::F23 => VK_F23, KeyCode::F24 => VK_F24, KeyCode::F25 => 0, KeyCode::F26 => 0, KeyCode::F27 => 0, KeyCode::F28 => 0, KeyCode::F29 => 0, KeyCode::F30 => 0, KeyCode::F31 => 0, KeyCode::F32 => 0, KeyCode::F33 => 0, KeyCode::F34 => 0, KeyCode::F35 => 0, _ => 0, } } /// This converts virtual keys to `Key`s. Only virtual keys which can be unambiguously converted to /// a `Key`, with only the information passed in as arguments, are converted. /// /// In other words: this function does not need to "prepare" the current layout in order to do /// the conversion, but as such it cannot convert certain keys, like language-specific character /// keys. /// /// The result includes all non-character keys defined within `Key` plus characters from numpad /// keys. For example, backspace and tab are included. fn vkey_to_non_char_key( vkey: VIRTUAL_KEY, native_code: NativeKey, hkl: u64, has_alt_graph: bool, ) -> Key { // List of the Web key names and their corresponding platform-native key names: // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values let primary_lang_id = primarylangid(loword(hkl as u32)); let is_korean = primary_lang_id as u32 == LANG_KOREAN; let is_japanese = primary_lang_id as u32 == LANG_JAPANESE; match vkey { VK_LBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse VK_RBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse // I don't think this can be represented with a Key VK_CANCEL => Key::Unidentified(native_code), VK_MBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse VK_XBUTTON1 => Key::Unidentified(NativeKey::Unidentified), // Mouse VK_XBUTTON2 => Key::Unidentified(NativeKey::Unidentified), // Mouse VK_BACK => Key::Named(NamedKey::Backspace), VK_TAB => Key::Named(NamedKey::Tab), VK_CLEAR => Key::Named(NamedKey::Clear), VK_RETURN => Key::Named(NamedKey::Enter), VK_SHIFT => Key::Named(NamedKey::Shift), VK_CONTROL => Key::Named(NamedKey::Control), VK_MENU => Key::Named(NamedKey::Alt), VK_PAUSE => Key::Named(NamedKey::Pause), VK_CAPITAL => Key::Named(NamedKey::CapsLock), // VK_HANGEUL => Key::Named(NamedKey::HangulMode), // Deprecated in favour of VK_HANGUL // VK_HANGUL and VK_KANA are defined as the same constant, therefore // we use appropriate conditions to differentiate between them VK_HANGUL if is_korean => Key::Named(NamedKey::HangulMode), VK_KANA if is_japanese => Key::Named(NamedKey::KanaMode), VK_JUNJA => Key::Named(NamedKey::JunjaMode), VK_FINAL => Key::Named(NamedKey::FinalMode), // VK_HANJA and VK_KANJI are defined as the same constant, therefore // we use appropriate conditions to differentiate between them VK_HANJA if is_korean => Key::Named(NamedKey::HanjaMode), VK_KANJI if is_japanese => Key::Named(NamedKey::KanjiMode), VK_ESCAPE => Key::Named(NamedKey::Escape), VK_CONVERT => Key::Named(NamedKey::Convert), VK_NONCONVERT => Key::Named(NamedKey::NonConvert), VK_ACCEPT => Key::Named(NamedKey::Accept), VK_MODECHANGE => Key::Named(NamedKey::ModeChange), VK_SPACE => Key::Named(NamedKey::Space), VK_PRIOR => Key::Named(NamedKey::PageUp), VK_NEXT => Key::Named(NamedKey::PageDown), VK_END => Key::Named(NamedKey::End), VK_HOME => Key::Named(NamedKey::Home), VK_LEFT => Key::Named(NamedKey::ArrowLeft), VK_UP => Key::Named(NamedKey::ArrowUp), VK_RIGHT => Key::Named(NamedKey::ArrowRight), VK_DOWN => Key::Named(NamedKey::ArrowDown), VK_SELECT => Key::Named(NamedKey::Select), VK_PRINT => Key::Named(NamedKey::Print), VK_EXECUTE => Key::Named(NamedKey::Execute), VK_SNAPSHOT => Key::Named(NamedKey::PrintScreen), VK_INSERT => Key::Named(NamedKey::Insert), VK_DELETE => Key::Named(NamedKey::Delete), VK_HELP => Key::Named(NamedKey::Help), VK_LWIN => Key::Named(NamedKey::Super), VK_RWIN => Key::Named(NamedKey::Super), VK_APPS => Key::Named(NamedKey::ContextMenu), VK_SLEEP => Key::Named(NamedKey::Standby), // Numpad keys produce characters VK_NUMPAD0 => Key::Unidentified(native_code), VK_NUMPAD1 => Key::Unidentified(native_code), VK_NUMPAD2 => Key::Unidentified(native_code), VK_NUMPAD3 => Key::Unidentified(native_code), VK_NUMPAD4 => Key::Unidentified(native_code), VK_NUMPAD5 => Key::Unidentified(native_code), VK_NUMPAD6 => Key::Unidentified(native_code), VK_NUMPAD7 => Key::Unidentified(native_code), VK_NUMPAD8 => Key::Unidentified(native_code), VK_NUMPAD9 => Key::Unidentified(native_code), VK_MULTIPLY => Key::Unidentified(native_code), VK_ADD => Key::Unidentified(native_code), VK_SEPARATOR => Key::Unidentified(native_code), VK_SUBTRACT => Key::Unidentified(native_code), VK_DECIMAL => Key::Unidentified(native_code), VK_DIVIDE => Key::Unidentified(native_code), VK_F1 => Key::Named(NamedKey::F1), VK_F2 => Key::Named(NamedKey::F2), VK_F3 => Key::Named(NamedKey::F3), VK_F4 => Key::Named(NamedKey::F4), VK_F5 => Key::Named(NamedKey::F5), VK_F6 => Key::Named(NamedKey::F6), VK_F7 => Key::Named(NamedKey::F7), VK_F8 => Key::Named(NamedKey::F8), VK_F9 => Key::Named(NamedKey::F9), VK_F10 => Key::Named(NamedKey::F10), VK_F11 => Key::Named(NamedKey::F11), VK_F12 => Key::Named(NamedKey::F12), VK_F13 => Key::Named(NamedKey::F13), VK_F14 => Key::Named(NamedKey::F14), VK_F15 => Key::Named(NamedKey::F15), VK_F16 => Key::Named(NamedKey::F16), VK_F17 => Key::Named(NamedKey::F17), VK_F18 => Key::Named(NamedKey::F18), VK_F19 => Key::Named(NamedKey::F19), VK_F20 => Key::Named(NamedKey::F20), VK_F21 => Key::Named(NamedKey::F21), VK_F22 => Key::Named(NamedKey::F22), VK_F23 => Key::Named(NamedKey::F23), VK_F24 => Key::Named(NamedKey::F24), VK_NAVIGATION_VIEW => Key::Unidentified(native_code), VK_NAVIGATION_MENU => Key::Unidentified(native_code), VK_NAVIGATION_UP => Key::Unidentified(native_code), VK_NAVIGATION_DOWN => Key::Unidentified(native_code), VK_NAVIGATION_LEFT => Key::Unidentified(native_code), VK_NAVIGATION_RIGHT => Key::Unidentified(native_code), VK_NAVIGATION_ACCEPT => Key::Unidentified(native_code), VK_NAVIGATION_CANCEL => Key::Unidentified(native_code), VK_NUMLOCK => Key::Named(NamedKey::NumLock), VK_SCROLL => Key::Named(NamedKey::ScrollLock), VK_OEM_NEC_EQUAL => Key::Unidentified(native_code), // VK_OEM_FJ_JISHO => Key::Unidentified(native_code), // Conflicts with `VK_OEM_NEC_EQUAL` VK_OEM_FJ_MASSHOU => Key::Unidentified(native_code), VK_OEM_FJ_TOUROKU => Key::Unidentified(native_code), VK_OEM_FJ_LOYA => Key::Unidentified(native_code), VK_OEM_FJ_ROYA => Key::Unidentified(native_code), VK_LSHIFT => Key::Named(NamedKey::Shift), VK_RSHIFT => Key::Named(NamedKey::Shift), VK_LCONTROL => Key::Named(NamedKey::Control), VK_RCONTROL => Key::Named(NamedKey::Control), VK_LMENU => Key::Named(NamedKey::Alt), VK_RMENU => { if has_alt_graph { Key::Named(NamedKey::AltGraph) } else { Key::Named(NamedKey::Alt) } }, VK_BROWSER_BACK => Key::Named(NamedKey::BrowserBack), VK_BROWSER_FORWARD => Key::Named(NamedKey::BrowserForward), VK_BROWSER_REFRESH => Key::Named(NamedKey::BrowserRefresh), VK_BROWSER_STOP => Key::Named(NamedKey::BrowserStop), VK_BROWSER_SEARCH => Key::Named(NamedKey::BrowserSearch), VK_BROWSER_FAVORITES => Key::Named(NamedKey::BrowserFavorites), VK_BROWSER_HOME => Key::Named(NamedKey::BrowserHome), VK_VOLUME_MUTE => Key::Named(NamedKey::AudioVolumeMute), VK_VOLUME_DOWN => Key::Named(NamedKey::AudioVolumeDown), VK_VOLUME_UP => Key::Named(NamedKey::AudioVolumeUp), VK_MEDIA_NEXT_TRACK => Key::Named(NamedKey::MediaTrackNext), VK_MEDIA_PREV_TRACK => Key::Named(NamedKey::MediaTrackPrevious), VK_MEDIA_STOP => Key::Named(NamedKey::MediaStop), VK_MEDIA_PLAY_PAUSE => Key::Named(NamedKey::MediaPlayPause), VK_LAUNCH_MAIL => Key::Named(NamedKey::LaunchMail), VK_LAUNCH_MEDIA_SELECT => Key::Named(NamedKey::LaunchMediaPlayer), VK_LAUNCH_APP1 => Key::Named(NamedKey::LaunchApplication1), VK_LAUNCH_APP2 => Key::Named(NamedKey::LaunchApplication2), // This function only converts "non-printable" VK_OEM_1 => Key::Unidentified(native_code), VK_OEM_PLUS => Key::Unidentified(native_code), VK_OEM_COMMA => Key::Unidentified(native_code), VK_OEM_MINUS => Key::Unidentified(native_code), VK_OEM_PERIOD => Key::Unidentified(native_code), VK_OEM_2 => Key::Unidentified(native_code), VK_OEM_3 => Key::Unidentified(native_code), VK_GAMEPAD_A => Key::Unidentified(native_code), VK_GAMEPAD_B => Key::Unidentified(native_code), VK_GAMEPAD_X => Key::Unidentified(native_code), VK_GAMEPAD_Y => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_SHOULDER => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_SHOULDER => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_TRIGGER => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_TRIGGER => Key::Unidentified(native_code), VK_GAMEPAD_DPAD_UP => Key::Unidentified(native_code), VK_GAMEPAD_DPAD_DOWN => Key::Unidentified(native_code), VK_GAMEPAD_DPAD_LEFT => Key::Unidentified(native_code), VK_GAMEPAD_DPAD_RIGHT => Key::Unidentified(native_code), VK_GAMEPAD_MENU => Key::Unidentified(native_code), VK_GAMEPAD_VIEW => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_THUMBSTICK_UP => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_THUMBSTICK_DOWN => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_THUMBSTICK_LEFT => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_THUMBSTICK_UP => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT => Key::Unidentified(native_code), // This function only converts "non-printable" VK_OEM_4 => Key::Unidentified(native_code), VK_OEM_5 => Key::Unidentified(native_code), VK_OEM_6 => Key::Unidentified(native_code), VK_OEM_7 => Key::Unidentified(native_code), VK_OEM_8 => Key::Unidentified(native_code), VK_OEM_AX => Key::Unidentified(native_code), VK_OEM_102 => Key::Unidentified(native_code), VK_ICO_HELP => Key::Unidentified(native_code), VK_ICO_00 => Key::Unidentified(native_code), VK_PROCESSKEY => Key::Named(NamedKey::Process), VK_ICO_CLEAR => Key::Unidentified(native_code), VK_PACKET => Key::Unidentified(native_code), VK_OEM_RESET => Key::Unidentified(native_code), VK_OEM_JUMP => Key::Unidentified(native_code), VK_OEM_PA1 => Key::Unidentified(native_code), VK_OEM_PA2 => Key::Unidentified(native_code), VK_OEM_PA3 => Key::Unidentified(native_code), VK_OEM_WSCTRL => Key::Unidentified(native_code), VK_OEM_CUSEL => Key::Unidentified(native_code), VK_OEM_ATTN => Key::Named(NamedKey::Attn), VK_OEM_FINISH => { if is_japanese { Key::Named(NamedKey::Katakana) } else { // This matches IE and Firefox behaviour according to // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values // At the time of writing, there is no `NamedKey::Finish` variant as // Finish is not mentioned at https://w3c.github.io/uievents-key/ // Also see: https://github.com/pyfisch/keyboard-types/issues/9 Key::Unidentified(native_code) } }, VK_OEM_COPY => Key::Named(NamedKey::Copy), VK_OEM_AUTO => Key::Named(NamedKey::Hankaku), VK_OEM_ENLW => Key::Named(NamedKey::Zenkaku), VK_OEM_BACKTAB => Key::Named(NamedKey::Romaji), VK_ATTN => Key::Named(NamedKey::KanaMode), VK_CRSEL => Key::Named(NamedKey::CrSel), VK_EXSEL => Key::Named(NamedKey::ExSel), VK_EREOF => Key::Named(NamedKey::EraseEof), VK_PLAY => Key::Named(NamedKey::Play), VK_ZOOM => Key::Named(NamedKey::ZoomToggle), VK_NONAME => Key::Unidentified(native_code), VK_PA1 => Key::Unidentified(native_code), VK_OEM_CLEAR => Key::Named(NamedKey::Clear), _ => Key::Unidentified(native_code), } } winit-0.30.9/src/platform_impl/windows/minimal_ime.rs000064400000000000000000000041061046102023000210240ustar 00000000000000use std::sync::{ atomic::{AtomicBool, Ordering::Relaxed}, Mutex, }; use winapi::{ shared::{ minwindef::{LPARAM, WPARAM}, windef::HWND, }, um::winuser, }; use crate::platform_impl::platform::{event_loop::ProcResult, keyboard::next_kbd_msg}; pub struct MinimalIme { // True if we're currently receiving messages belonging to a finished IME session. getting_ime_text: AtomicBool, utf16parts: Mutex>, } impl Default for MinimalIme { fn default() -> Self { MinimalIme { getting_ime_text: AtomicBool::new(false), utf16parts: Mutex::new(Vec::with_capacity(16)), } } } impl MinimalIme { pub(crate) fn process_message( &self, hwnd: HWND, msg_kind: u32, wparam: WPARAM, _lparam: LPARAM, result: &mut ProcResult, ) -> Option { match msg_kind { winuser::WM_IME_ENDCOMPOSITION => { self.getting_ime_text.store(true, Relaxed); } winuser::WM_CHAR | winuser::WM_SYSCHAR => { if self.getting_ime_text.load(Relaxed) { *result = ProcResult::Value(0); self.utf16parts.lock().unwrap().push(wparam as u16); // It's important that we push the new character and release the lock // before getting the next message let next_msg = next_kbd_msg(hwnd); let more_char_coming = next_msg .map(|m| matches!(m.message, winuser::WM_CHAR | winuser::WM_SYSCHAR)) .unwrap_or(false); if !more_char_coming { let mut utf16parts = self.utf16parts.lock().unwrap(); let result = String::from_utf16(&utf16parts).ok(); utf16parts.clear(); self.getting_ime_text.store(false, Relaxed); return result; } } } _ => (), } None } } winit-0.30.9/src/platform_impl/windows/mod.rs000064400000000000000000000121301046102023000173170ustar 00000000000000use smol_str::SmolStr; use windows_sys::Win32::Foundation::{HANDLE, HWND}; use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX}; pub(crate) use self::event_loop::{ ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle, PlatformSpecificEventLoopAttributes, }; pub(crate) use self::icon::{SelectedCursor, WinIcon}; pub(crate) use self::keyboard::{physicalkey_to_scancode, scancode_to_physicalkey}; pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle}; pub(crate) use self::window::Window; pub(crate) use self::icon::WinCursor as PlatformCustomCursor; pub use self::icon::WinIcon as PlatformIcon; pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource; use crate::platform_impl::Fullscreen; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; use crate::keyboard::Key; use crate::platform::windows::{BackdropType, Color, CornerPreference}; #[derive(Clone, Debug)] pub struct PlatformSpecificWindowAttributes { pub owner: Option, pub menu: Option, pub taskbar_icon: Option, pub no_redirection_bitmap: bool, pub drag_and_drop: bool, pub skip_taskbar: bool, pub class_name: String, pub decoration_shadow: bool, pub backdrop_type: BackdropType, pub clip_children: bool, pub border_color: Option, pub title_background_color: Option, pub title_text_color: Option, pub corner_preference: Option, } impl Default for PlatformSpecificWindowAttributes { fn default() -> Self { Self { owner: None, menu: None, taskbar_icon: None, no_redirection_bitmap: false, drag_and_drop: true, skip_taskbar: false, class_name: "Window Class".to_string(), decoration_shadow: false, backdrop_type: BackdropType::default(), clip_children: true, border_color: None, title_background_color: None, title_text_color: None, corner_preference: None, } } } unsafe impl Send for PlatformSpecificWindowAttributes {} unsafe impl Sync for PlatformSpecificWindowAttributes {} #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(u32); impl DeviceId { pub const fn dummy() -> Self { DeviceId(0) } } impl DeviceId { pub fn persistent_identifier(&self) -> Option { if self.0 != 0 { raw_input::get_raw_input_device_name(self.0 as HANDLE) } else { None } } } // Constant device ID, to be removed when this backend is updated to report real device IDs. const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0)); fn wrap_device_id(id: u32) -> RootDeviceId { RootDeviceId(DeviceId(id)) } pub type OsError = std::io::Error; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct KeyEventExtra { pub text_with_all_modifiers: Option, pub key_without_modifiers: Key, } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(HWND); unsafe impl Send for WindowId {} unsafe impl Sync for WindowId {} impl WindowId { pub const fn dummy() -> Self { WindowId(0) } } impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.0 as u64 } } impl From for HWND { fn from(window_id: WindowId) -> Self { window_id.0 } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id as HWND) } } #[inline(always)] const fn get_xbutton_wparam(x: u32) -> u16 { hiword(x) } #[inline(always)] const fn get_x_lparam(x: u32) -> i16 { loword(x) as _ } #[inline(always)] const fn get_y_lparam(x: u32) -> i16 { hiword(x) as _ } #[inline(always)] pub(crate) const fn primarylangid(lgid: u16) -> u16 { lgid & 0x3ff } #[inline(always)] pub(crate) const fn loword(x: u32) -> u16 { (x & 0xffff) as u16 } #[inline(always)] const fn hiword(x: u32) -> u16 { ((x >> 16) & 0xffff) as u16 } #[inline(always)] unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize { #[cfg(target_pointer_width = "64")] return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW(hwnd, nindex) }; #[cfg(target_pointer_width = "32")] return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::GetWindowLongW(hwnd, nindex) as isize }; } #[inline(always)] unsafe fn set_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX, dwnewlong: isize) -> isize { #[cfg(target_pointer_width = "64")] return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW(hwnd, nindex, dwnewlong) }; #[cfg(target_pointer_width = "32")] return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::SetWindowLongW(hwnd, nindex, dwnewlong as i32) as isize }; } #[macro_use] mod util; mod dark_mode; mod definitions; mod dpi; mod drop_handler; mod event_loop; mod icon; mod ime; mod keyboard; mod keyboard_layout; mod monitor; mod raw_input; mod window; mod window_state; winit-0.30.9/src/platform_impl/windows/monitor.rs000064400000000000000000000201221046102023000202270ustar 00000000000000use std::collections::{BTreeSet, VecDeque}; use std::hash::Hash; use std::{io, mem, ptr}; use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, POINT, RECT}; use windows_sys::Win32::Graphics::Gdi::{ EnumDisplayMonitors, EnumDisplaySettingsExW, GetMonitorInfoW, MonitorFromPoint, MonitorFromWindow, DEVMODEW, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH, ENUM_CURRENT_SETTINGS, HDC, HMONITOR, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTOPRIMARY, }; use super::util::decode_wide; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::monitor::VideoModeHandle as RootVideoModeHandle; use crate::platform_impl::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi}; use crate::platform_impl::platform::util::has_flag; use crate::platform_impl::platform::window::Window; #[derive(Clone)] pub struct VideoModeHandle { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, pub(crate) monitor: MonitorHandle, // DEVMODEW is huge so we box it to avoid blowing up the size of winit::window::Fullscreen pub(crate) native_video_mode: Box, } impl PartialEq for VideoModeHandle { fn eq(&self, other: &Self) -> bool { self.size == other.size && self.bit_depth == other.bit_depth && self.refresh_rate_millihertz == other.refresh_rate_millihertz && self.monitor == other.monitor } } impl Eq for VideoModeHandle {} impl std::hash::Hash for VideoModeHandle { fn hash(&self, state: &mut H) { self.size.hash(state); self.bit_depth.hash(state); self.refresh_rate_millihertz.hash(state); self.monitor.hash(state); } } impl std::fmt::Debug for VideoModeHandle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("VideoModeHandle") .field("size", &self.size) .field("bit_depth", &self.bit_depth) .field("refresh_rate_millihertz", &self.refresh_rate_millihertz) .field("monitor", &self.monitor) .finish() } } impl VideoModeHandle { pub fn size(&self) -> PhysicalSize { self.size.into() } pub fn bit_depth(&self) -> u16 { self.bit_depth } pub fn refresh_rate_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } } #[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct MonitorHandle(HMONITOR); // Send is not implemented for HMONITOR, we have to wrap it and implement it manually. // For more info see: // https://github.com/retep998/winapi-rs/issues/360 // https://github.com/retep998/winapi-rs/issues/396 unsafe impl Send for MonitorHandle {} unsafe extern "system" fn monitor_enum_proc( hmonitor: HMONITOR, _hdc: HDC, _place: *mut RECT, data: LPARAM, ) -> BOOL { let monitors = data as *mut VecDeque; unsafe { (*monitors).push_back(MonitorHandle::new(hmonitor)) }; true.into() // continue enumeration } pub fn available_monitors() -> VecDeque { let mut monitors: VecDeque = VecDeque::new(); unsafe { EnumDisplayMonitors( 0, ptr::null(), Some(monitor_enum_proc), &mut monitors as *mut _ as LPARAM, ); } monitors } pub fn primary_monitor() -> MonitorHandle { const ORIGIN: POINT = POINT { x: 0, y: 0 }; let hmonitor = unsafe { MonitorFromPoint(ORIGIN, MONITOR_DEFAULTTOPRIMARY) }; MonitorHandle::new(hmonitor) } pub fn current_monitor(hwnd: HWND) -> MonitorHandle { let hmonitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }; MonitorHandle::new(hmonitor) } impl Window { pub fn available_monitors(&self) -> VecDeque { available_monitors() } pub fn primary_monitor(&self) -> Option { let monitor = primary_monitor(); Some(monitor) } } pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result { let mut monitor_info: MONITORINFOEXW = unsafe { mem::zeroed() }; monitor_info.monitorInfo.cbSize = mem::size_of::() as u32; let status = unsafe { GetMonitorInfoW(hmonitor, &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO) }; if status == false.into() { Err(io::Error::last_os_error()) } else { Ok(monitor_info) } } impl MonitorHandle { pub(crate) fn new(hmonitor: HMONITOR) -> Self { MonitorHandle(hmonitor) } #[inline] pub fn name(&self) -> Option { let monitor_info = get_monitor_info(self.0).unwrap(); Some(decode_wide(&monitor_info.szDevice).to_string_lossy().to_string()) } #[inline] pub fn native_identifier(&self) -> String { self.name().unwrap() } #[inline] pub fn hmonitor(&self) -> HMONITOR { self.0 } #[inline] pub fn size(&self) -> PhysicalSize { let rc_monitor = get_monitor_info(self.0).unwrap().monitorInfo.rcMonitor; PhysicalSize { width: (rc_monitor.right - rc_monitor.left) as u32, height: (rc_monitor.bottom - rc_monitor.top) as u32, } } #[inline] pub fn refresh_rate_millihertz(&self) -> Option { let monitor_info = get_monitor_info(self.0).ok()?; let device_name = monitor_info.szDevice.as_ptr(); unsafe { let mut mode: DEVMODEW = mem::zeroed(); mode.dmSize = mem::size_of_val(&mode) as u16; if EnumDisplaySettingsExW(device_name, ENUM_CURRENT_SETTINGS, &mut mode, 0) == false.into() { None } else { Some(mode.dmDisplayFrequency * 1000) } } } #[inline] pub fn position(&self) -> PhysicalPosition { get_monitor_info(self.0) .map(|info| { let rc_monitor = info.monitorInfo.rcMonitor; PhysicalPosition { x: rc_monitor.left, y: rc_monitor.top } }) .unwrap_or(PhysicalPosition { x: 0, y: 0 }) } #[inline] pub fn scale_factor(&self) -> f64 { dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96)) } #[inline] pub fn video_modes(&self) -> impl Iterator { // EnumDisplaySettingsExW can return duplicate values (or some of the // fields are probably changing, but we aren't looking at those fields // anyway), so we're using a BTreeSet deduplicate let mut modes = BTreeSet::::new(); let mod_map = |mode: RootVideoModeHandle| mode.video_mode; let monitor_info = match get_monitor_info(self.0) { Ok(monitor_info) => monitor_info, Err(error) => { tracing::warn!("Error from get_monitor_info: {error}"); return modes.into_iter().map(mod_map); }, }; let device_name = monitor_info.szDevice.as_ptr(); let mut i = 0; loop { let mut mode: DEVMODEW = unsafe { mem::zeroed() }; mode.dmSize = mem::size_of_val(&mode) as u16; if unsafe { EnumDisplaySettingsExW(device_name, i, &mut mode, 0) } == false.into() { break; } const REQUIRED_FIELDS: u32 = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; assert!(has_flag(mode.dmFields, REQUIRED_FIELDS)); // Use Ord impl of RootVideoModeHandle modes.insert(RootVideoModeHandle { video_mode: VideoModeHandle { size: (mode.dmPelsWidth, mode.dmPelsHeight), bit_depth: mode.dmBitsPerPel as u16, refresh_rate_millihertz: mode.dmDisplayFrequency * 1000, monitor: self.clone(), native_video_mode: Box::new(mode), }, }); i += 1; } modes.into_iter().map(mod_map) } } winit-0.30.9/src/platform_impl/windows/raw_input.rs000064400000000000000000000257321046102023000205640ustar 00000000000000use std::mem::{self, size_of}; use std::ptr; use windows_sys::Win32::Devices::HumanInterfaceDevice::{ HID_USAGE_GENERIC_KEYBOARD, HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC, }; use windows_sys::Win32::Foundation::{HANDLE, HWND}; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ MapVirtualKeyW, MAPVK_VK_TO_VSC_EX, VK_NUMLOCK, VK_SHIFT, }; use windows_sys::Win32::UI::Input::{ GetRawInputData, GetRawInputDeviceInfoW, GetRawInputDeviceList, RegisterRawInputDevices, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST, RAWINPUTHEADER, RAWKEYBOARD, RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE, RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, }; use windows_sys::Win32::UI::WindowsAndMessaging::{ RI_KEY_E0, RI_KEY_E1, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP, RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP, }; use super::scancode_to_physicalkey; use crate::event::ElementState; use crate::event_loop::DeviceEvents; use crate::keyboard::{KeyCode, PhysicalKey}; use crate::platform_impl::platform::util; #[allow(dead_code)] pub fn get_raw_input_device_list() -> Option> { let list_size = size_of::() as u32; let mut num_devices = 0; let status = unsafe { GetRawInputDeviceList(ptr::null_mut(), &mut num_devices, list_size) }; if status == u32::MAX { return None; } let mut buffer = Vec::with_capacity(num_devices as _); let num_stored = unsafe { GetRawInputDeviceList(buffer.as_mut_ptr(), &mut num_devices, list_size) }; if num_stored == u32::MAX { return None; } debug_assert_eq!(num_devices, num_stored); unsafe { buffer.set_len(num_devices as _) }; Some(buffer) } #[allow(dead_code)] pub enum RawDeviceInfo { Mouse(RID_DEVICE_INFO_MOUSE), Keyboard(RID_DEVICE_INFO_KEYBOARD), Hid(RID_DEVICE_INFO_HID), } impl From for RawDeviceInfo { fn from(info: RID_DEVICE_INFO) -> Self { unsafe { match info.dwType { RIM_TYPEMOUSE => RawDeviceInfo::Mouse(info.Anonymous.mouse), RIM_TYPEKEYBOARD => RawDeviceInfo::Keyboard(info.Anonymous.keyboard), RIM_TYPEHID => RawDeviceInfo::Hid(info.Anonymous.hid), _ => unreachable!(), } } } } #[allow(dead_code)] pub fn get_raw_input_device_info(handle: HANDLE) -> Option { let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() }; let info_size = size_of::() as u32; info.cbSize = info_size; let mut minimum_size = 0; let status = unsafe { GetRawInputDeviceInfoW(handle, RIDI_DEVICEINFO, &mut info as *mut _ as _, &mut minimum_size) }; if status == u32::MAX || status == 0 { return None; } debug_assert_eq!(info_size, status); Some(info.into()) } pub fn get_raw_input_device_name(handle: HANDLE) -> Option { let mut minimum_size = 0; let status = unsafe { GetRawInputDeviceInfoW(handle, RIDI_DEVICENAME, ptr::null_mut(), &mut minimum_size) }; if status != 0 { return None; } let mut name: Vec = Vec::with_capacity(minimum_size as _); let status = unsafe { GetRawInputDeviceInfoW(handle, RIDI_DEVICENAME, name.as_ptr() as _, &mut minimum_size) }; if status == u32::MAX || status == 0 { return None; } debug_assert_eq!(minimum_size, status); unsafe { name.set_len(minimum_size as _) }; util::decode_wide(&name).into_string().ok() } pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { let device_size = size_of::() as u32; unsafe { RegisterRawInputDevices(devices.as_ptr(), devices.len() as u32, device_size) == true.into() } } pub fn register_all_mice_and_keyboards_for_raw_input( mut window_handle: HWND, filter: DeviceEvents, ) -> bool { // RIDEV_DEVNOTIFY: receive hotplug events // RIDEV_INPUTSINK: receive events even if we're not in the foreground // RIDEV_REMOVE: don't receive device events (requires NULL hwndTarget) let flags = match filter { DeviceEvents::Never => { window_handle = 0; RIDEV_REMOVE }, DeviceEvents::WhenFocused => RIDEV_DEVNOTIFY, DeviceEvents::Always => RIDEV_DEVNOTIFY | RIDEV_INPUTSINK, }; let devices: [RAWINPUTDEVICE; 2] = [ RAWINPUTDEVICE { usUsagePage: HID_USAGE_PAGE_GENERIC, usUsage: HID_USAGE_GENERIC_MOUSE, dwFlags: flags, hwndTarget: window_handle, }, RAWINPUTDEVICE { usUsagePage: HID_USAGE_PAGE_GENERIC, usUsage: HID_USAGE_GENERIC_KEYBOARD, dwFlags: flags, hwndTarget: window_handle, }, ]; register_raw_input_devices(&devices) } pub fn get_raw_input_data(handle: HRAWINPUT) -> Option { let mut data: RAWINPUT = unsafe { mem::zeroed() }; let mut data_size = size_of::() as u32; let header_size = size_of::() as u32; let status = unsafe { GetRawInputData(handle, RID_INPUT, &mut data as *mut _ as _, &mut data_size, header_size) }; if status == u32::MAX || status == 0 { return None; } Some(data) } fn button_flags_to_element_state( button_flags: u32, down_flag: u32, up_flag: u32, ) -> Option { // We assume the same button won't be simultaneously pressed and released. if util::has_flag(button_flags, down_flag) { Some(ElementState::Pressed) } else if util::has_flag(button_flags, up_flag) { Some(ElementState::Released) } else { None } } pub fn get_raw_mouse_button_state(button_flags: u32) -> [Option; 5] { [ button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP), button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP), button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP), button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP), button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP), ] } pub fn get_keyboard_physical_key(keyboard: RAWKEYBOARD) -> Option { let extension = { if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) { 0xe000 } else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) { 0xe100 } else { 0x0000 } }; let scancode = if keyboard.MakeCode == 0 { // In some cases (often with media keys) the device reports a scancode of 0 but a // valid virtual key. In these cases we obtain the scancode from the virtual key. unsafe { MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 } } else { keyboard.MakeCode | extension }; if scancode == 0xe11d || scancode == 0xe02a { // At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing // Ctrl+NumLock. // This equvalence means that if the user presses Pause, the keyboard will emit two // subsequent keypresses: // 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100) // 2, 0x0045 - Which on its own can be interpreted as Pause // // There's another combination which isn't quite an equivalence: // PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing // PrtSc (print screen) produces the following sequence: // 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000) // 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on // its own it can be interpreted as PrtSc // // For this reason, if we encounter the first keypress, we simply ignore it, trusting // that there's going to be another event coming, from which we can extract the // appropriate key. // For more on this, read the article by Raymond Chen, titled: // "Why does Ctrl+ScrollLock cancel dialogs?" // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 return None; } let physical_key = if keyboard.VKey == VK_NUMLOCK { // Historically, the NumLock and the Pause key were one and the same physical key. // The user could trigger Pause by pressing Ctrl+NumLock. // Now these are often physically separate and the two keys can be differentiated by // checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045. // // However in this event, both keys are reported as 0x0045 even on modern hardware. // Therefore we use the virtual key instead to determine whether it's a NumLock and // set the KeyCode accordingly. // // For more on this, read the article by Raymond Chen, titled: // "Why does Ctrl+ScrollLock cancel dialogs?" // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 PhysicalKey::Code(KeyCode::NumLock) } else { scancode_to_physicalkey(scancode as u32) }; if keyboard.VKey == VK_SHIFT { if let PhysicalKey::Code( KeyCode::NumpadDecimal | KeyCode::Numpad0 | KeyCode::Numpad1 | KeyCode::Numpad2 | KeyCode::Numpad3 | KeyCode::Numpad4 | KeyCode::Numpad5 | KeyCode::Numpad6 | KeyCode::Numpad7 | KeyCode::Numpad8 | KeyCode::Numpad9, ) = physical_key { // On Windows, holding the Shift key makes numpad keys behave as if NumLock // wasn't active. The way this is exposed to applications by the system is that // the application receives a fake key release event for the shift key at the // moment when the numpad key is pressed, just before receiving the numpad key // as well. // // The issue is that in the raw device event (here), the fake shift release // event reports the numpad key as the scancode. Unfortunately, the event // doesn't have any information to tell whether it's the // left shift or the right shift that needs to get the fake // release (or press) event so we don't forward this // event to the application at all. // // For more on this, read the article by Raymond Chen, titled: // "The shift key overrides NumLock" // https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953 return None; } } Some(physical_key) } winit-0.30.9/src/platform_impl/windows/util.rs000064400000000000000000000240301046102023000175170ustar 00000000000000use std::ffi::{c_void, OsStr, OsString}; use std::iter::once; use std::ops::BitAnd; use std::os::windows::prelude::{OsStrExt, OsStringExt}; use std::sync::atomic::{AtomicBool, Ordering}; use std::{io, mem, ptr}; use crate::utils::Lazy; use windows_sys::core::{HRESULT, PCWSTR}; use windows_sys::Win32::Foundation::{BOOL, HANDLE, HMODULE, HWND, RECT}; use windows_sys::Win32::Graphics::Gdi::{ClientToScreen, HMONITOR}; use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; use windows_sys::Win32::System::SystemServices::IMAGE_DOS_HEADER; use windows_sys::Win32::UI::HiDpi::{ DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS, }; use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow; use windows_sys::Win32::UI::Input::Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO}; use windows_sys::Win32::UI::WindowsAndMessaging::{ ClipCursor, GetClientRect, GetClipCursor, GetSystemMetrics, GetWindowPlacement, GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_MAXIMIZE, WINDOWPLACEMENT, }; use crate::window::CursorIcon; pub fn encode_wide(string: impl AsRef) -> Vec { string.as_ref().encode_wide().chain(once(0)).collect() } pub fn decode_wide(mut wide_c_string: &[u16]) -> OsString { if let Some(null_pos) = wide_c_string.iter().position(|c| *c == 0) { wide_c_string = &wide_c_string[..null_pos]; } OsString::from_wide(wide_c_string) } pub fn has_flag(bitset: T, flag: T) -> bool where T: Copy + PartialEq + BitAnd, { bitset & flag == flag } pub(crate) fn win_to_err(result: BOOL) -> Result<(), io::Error> { if result != false.into() { Ok(()) } else { Err(io::Error::last_os_error()) } } pub enum WindowArea { Outer, Inner, } impl WindowArea { pub fn get_rect(self, hwnd: HWND) -> Result { let mut rect = unsafe { mem::zeroed() }; match self { WindowArea::Outer => { win_to_err(unsafe { GetWindowRect(hwnd, &mut rect) })?; }, WindowArea::Inner => unsafe { let mut top_left = mem::zeroed(); win_to_err(ClientToScreen(hwnd, &mut top_left))?; win_to_err(GetClientRect(hwnd, &mut rect))?; rect.left += top_left.x; rect.top += top_left.y; rect.right += top_left.x; rect.bottom += top_left.y; }, } Ok(rect) } } pub fn is_maximized(window: HWND) -> bool { unsafe { let mut placement: WINDOWPLACEMENT = mem::zeroed(); placement.length = mem::size_of::() as u32; GetWindowPlacement(window, &mut placement); placement.showCmd == SW_MAXIMIZE as u32 } } pub fn set_cursor_hidden(hidden: bool) { static HIDDEN: AtomicBool = AtomicBool::new(false); let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden; if changed { unsafe { ShowCursor(BOOL::from(!hidden)) }; } } pub fn get_cursor_clip() -> Result { unsafe { let mut rect: RECT = mem::zeroed(); win_to_err(GetClipCursor(&mut rect)).map(|_| rect) } } /// Sets the cursor's clip rect. /// /// Note that calling this will automatically dispatch a `WM_MOUSEMOVE` event. pub fn set_cursor_clip(rect: Option) -> Result<(), io::Error> { unsafe { let rect_ptr = rect.as_ref().map(|r| r as *const RECT).unwrap_or(ptr::null()); win_to_err(ClipCursor(rect_ptr)) } } pub fn get_desktop_rect() -> RECT { unsafe { let left = GetSystemMetrics(SM_XVIRTUALSCREEN); let top = GetSystemMetrics(SM_YVIRTUALSCREEN); RECT { left, top, right: left + GetSystemMetrics(SM_CXVIRTUALSCREEN), bottom: top + GetSystemMetrics(SM_CYVIRTUALSCREEN), } } } pub fn is_focused(window: HWND) -> bool { window == unsafe { GetActiveWindow() } } pub fn is_minimized(window: HWND) -> bool { unsafe { IsIconic(window) != false.into() } } pub fn get_instance_handle() -> HMODULE { // Gets the instance handle by taking the address of the // pseudo-variable created by the microsoft linker: // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483 // This is preferred over GetModuleHandle(NULL) because it also works in DLLs: // https://stackoverflow.com/questions/21718027/getmodulehandlenull-vs-hinstance extern "C" { static __ImageBase: IMAGE_DOS_HEADER; } unsafe { &__ImageBase as *const _ as _ } } pub(crate) fn to_windows_cursor(cursor: CursorIcon) -> PCWSTR { match cursor { CursorIcon::Default => IDC_ARROW, CursorIcon::Pointer => IDC_HAND, CursorIcon::Crosshair => IDC_CROSS, CursorIcon::Text | CursorIcon::VerticalText => IDC_IBEAM, CursorIcon::NotAllowed | CursorIcon::NoDrop => IDC_NO, CursorIcon::Grab | CursorIcon::Grabbing | CursorIcon::Move | CursorIcon::AllScroll => { IDC_SIZEALL }, CursorIcon::EResize | CursorIcon::WResize | CursorIcon::EwResize | CursorIcon::ColResize => IDC_SIZEWE, CursorIcon::NResize | CursorIcon::SResize | CursorIcon::NsResize | CursorIcon::RowResize => IDC_SIZENS, CursorIcon::NeResize | CursorIcon::SwResize | CursorIcon::NeswResize => IDC_SIZENESW, CursorIcon::NwResize | CursorIcon::SeResize | CursorIcon::NwseResize => IDC_SIZENWSE, CursorIcon::Wait => IDC_WAIT, CursorIcon::Progress => IDC_APPSTARTING, CursorIcon::Help => IDC_HELP, _ => IDC_ARROW, // use arrow for the missing cases. } } // Helper function to dynamically load function pointer as some functions // may not be available on all Windows platforms supported by winit. // // `library` and `function` must be zero-terminated. pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> { assert_eq!(library.chars().last(), Some('\0')); assert_eq!(function.chars().last(), Some('\0')); // Library names we will use are ASCII so we can use the A version to avoid string conversion. let module = unsafe { LoadLibraryA(library.as_ptr()) }; if module == 0 { return None; } unsafe { GetProcAddress(module, function.as_ptr()) }.map(|function_ptr| function_ptr as _) } macro_rules! get_function { ($lib:expr, $func:ident) => { crate::platform_impl::platform::util::get_function_impl( concat!($lib, '\0'), concat!(stringify!($func), '\0'), ) .map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) }) }; } pub type SetProcessDPIAware = unsafe extern "system" fn() -> BOOL; pub type SetProcessDpiAwareness = unsafe extern "system" fn(value: PROCESS_DPI_AWARENESS) -> HRESULT; pub type SetProcessDpiAwarenessContext = unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL; pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32; pub type GetDpiForMonitor = unsafe extern "system" fn( hmonitor: HMONITOR, dpi_type: MONITOR_DPI_TYPE, dpi_x: *mut u32, dpi_y: *mut u32, ) -> HRESULT; pub type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL; pub type AdjustWindowRectExForDpi = unsafe extern "system" fn( rect: *mut RECT, dwStyle: u32, bMenu: BOOL, dwExStyle: u32, dpi: u32, ) -> BOOL; pub type GetPointerFrameInfoHistory = unsafe extern "system" fn( pointerId: u32, entriesCount: *mut u32, pointerCount: *mut u32, pointerInfo: *mut POINTER_INFO, ) -> BOOL; pub type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL; pub type GetPointerDeviceRects = unsafe extern "system" fn( device: HANDLE, pointerDeviceRect: *mut RECT, displayRect: *mut RECT, ) -> BOOL; pub type GetPointerTouchInfo = unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL; pub type GetPointerPenInfo = unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL; pub(crate) static GET_DPI_FOR_WINDOW: Lazy> = Lazy::new(|| get_function!("user32.dll", GetDpiForWindow)); pub(crate) static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy> = Lazy::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi)); pub(crate) static GET_DPI_FOR_MONITOR: Lazy> = Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor)); pub(crate) static ENABLE_NON_CLIENT_DPI_SCALING: Lazy> = Lazy::new(|| get_function!("user32.dll", EnableNonClientDpiScaling)); pub(crate) static SET_PROCESS_DPI_AWARENESS_CONTEXT: Lazy> = Lazy::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext)); pub(crate) static SET_PROCESS_DPI_AWARENESS: Lazy> = Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness)); pub(crate) static SET_PROCESS_DPI_AWARE: Lazy> = Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware)); pub(crate) static GET_POINTER_FRAME_INFO_HISTORY: Lazy> = Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); pub(crate) static SKIP_POINTER_FRAME_MESSAGES: Lazy> = Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); pub(crate) static GET_POINTER_DEVICE_RECTS: Lazy> = Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects)); pub(crate) static GET_POINTER_TOUCH_INFO: Lazy> = Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo)); pub(crate) static GET_POINTER_PEN_INFO: Lazy> = Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo)); winit-0.30.9/src/platform_impl/windows/window.rs000064400000000000000000001623231046102023000200610ustar 00000000000000#![cfg(windows_platform)] use std::cell::Cell; use std::ffi::c_void; use std::mem::{self, MaybeUninit}; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex, MutexGuard}; use std::{io, panic, ptr}; use windows_sys::Win32::Foundation::{ HWND, LPARAM, OLE_E_WRONGCOMPOBJ, POINT, POINTS, RECT, RPC_E_CHANGED_MODE, S_OK, WPARAM, }; use windows_sys::Win32::Graphics::Dwm::{ DwmEnableBlurBehindWindow, DwmSetWindowAttribute, DWMWA_BORDER_COLOR, DWMWA_CAPTION_COLOR, DWMWA_SYSTEMBACKDROP_TYPE, DWMWA_TEXT_COLOR, DWMWA_WINDOW_CORNER_PREFERENCE, DWM_BB_BLURREGION, DWM_BB_ENABLE, DWM_BLURBEHIND, DWM_SYSTEMBACKDROP_TYPE, DWM_WINDOW_CORNER_PREFERENCE, }; use windows_sys::Win32::Graphics::Gdi::{ ChangeDisplaySettingsExW, ClientToScreen, CreateRectRgn, DeleteObject, InvalidateRgn, RedrawWindow, CDS_FULLSCREEN, DISP_CHANGE_BADFLAGS, DISP_CHANGE_BADMODE, DISP_CHANGE_BADPARAM, DISP_CHANGE_FAILED, DISP_CHANGE_SUCCESSFUL, RDW_INTERNALPAINT, }; use windows_sys::Win32::System::Com::{ CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_APARTMENTTHREADED, }; use windows_sys::Win32::System::Ole::{OleInitialize, RegisterDragDrop}; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ EnableWindow, GetActiveWindow, MapVirtualKeyW, ReleaseCapture, SendInput, ToUnicode, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, MAPVK_VK_TO_VSC, VIRTUAL_KEY, VK_LMENU, VK_MENU, VK_SPACE, }; use windows_sys::Win32::UI::Input::Touch::{RegisterTouchWindow, TWF_WANTPALM}; use windows_sys::Win32::UI::WindowsAndMessaging::{ CreateWindowExW, EnableMenuItem, FlashWindowEx, GetClientRect, GetCursorPos, GetForegroundWindow, GetSystemMenu, GetSystemMetrics, GetWindowPlacement, GetWindowTextLengthW, GetWindowTextW, IsWindowVisible, LoadCursorW, PeekMessageW, PostMessageW, RegisterClassExW, SetCursor, SetCursorPos, SetForegroundWindow, SetMenuDefaultItem, SetWindowDisplayAffinity, SetWindowPlacement, SetWindowPos, SetWindowTextW, TrackPopupMenu, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, FLASHWINFO, FLASHW_ALL, FLASHW_STOP, FLASHW_TIMERNOFG, FLASHW_TRAY, GWLP_HINSTANCE, HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT, HTCAPTION, HTLEFT, HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT, MENU_ITEM_STATE, MFS_DISABLED, MFS_ENABLED, MF_BYCOMMAND, NID_READY, PM_NOREMOVE, SC_CLOSE, SC_MAXIMIZE, SC_MINIMIZE, SC_MOVE, SC_RESTORE, SC_SIZE, SM_DIGITIZER, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOSIZE, SWP_NOZORDER, TPM_LEFTALIGN, TPM_RETURNCMD, WDA_EXCLUDEFROMCAPTURE, WDA_NONE, WM_NCLBUTTONDOWN, WM_SYSCOMMAND, WNDCLASSEXW, }; use tracing::warn; use crate::cursor::Cursor; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::icon::Icon; use crate::platform::windows::{BackdropType, Color, CornerPreference}; use crate::platform_impl::platform::dark_mode::try_theme; use crate::platform_impl::platform::definitions::{ CLSID_TaskbarList, IID_ITaskbarList, IID_ITaskbarList2, ITaskbarList, ITaskbarList2, }; use crate::platform_impl::platform::dpi::{ dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_dpi, }; use crate::platform_impl::platform::drop_handler::FileDropHandler; use crate::platform_impl::platform::event_loop::{self, ActiveEventLoop, DESTROY_MSG_ID}; use crate::platform_impl::platform::icon::{self, IconType, WinCursor}; use crate::platform_impl::platform::ime::ImeContext; use crate::platform_impl::platform::keyboard::KeyEventBuilder; use crate::platform_impl::platform::monitor::{self, MonitorHandle}; use crate::platform_impl::platform::window_state::{ CursorFlags, SavedWindow, WindowFlags, WindowState, }; use crate::platform_impl::platform::{util, Fullscreen, SelectedCursor, WindowId}; use crate::window::{ CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }; /// The Win32 implementation of the main `Window` object. pub(crate) struct Window { /// Main handle for the window. window: HWND, /// The current window state. window_state: Arc>, // The events loop proxy. thread_executor: event_loop::EventLoopThreadExecutor, } impl Window { pub(crate) fn new( event_loop: &ActiveEventLoop, w_attr: WindowAttributes, ) -> Result { // We dispatch an `init` function because of code style. // First person to remove the need for cloning here gets a cookie! // // done. you owe me -- ossi unsafe { init(w_attr, event_loop) } } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { // TODO: Use `thread_executor` here f(self) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { // TODO: Use `thread_executor` here f(self) } fn window_state_lock(&self) -> MutexGuard<'_, WindowState> { self.window_state.lock().unwrap() } pub fn set_title(&self, text: &str) { let wide_text = util::encode_wide(text); unsafe { SetWindowTextW(self.hwnd(), wide_text.as_ptr()); } } pub fn set_transparent(&self, transparent: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::TRANSPARENT, transparent) }); }); } pub fn set_blur(&self, _blur: bool) {} #[inline] pub fn set_visible(&self, visible: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::VISIBLE, visible) }); }); } #[inline] pub fn is_visible(&self) -> Option { Some(unsafe { IsWindowVisible(self.window) == 1 }) } #[inline] pub fn request_redraw(&self) { // NOTE: mark that we requested a redraw to handle requests during `WM_PAINT` handling. self.window_state.lock().unwrap().redraw_requested = true; unsafe { RedrawWindow(self.hwnd(), ptr::null(), 0, RDW_INTERNALPAINT); } } #[inline] pub fn pre_present_notify(&self) {} #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { util::WindowArea::Outer .get_rect(self.hwnd()) .map(|rect| Ok(PhysicalPosition::new(rect.left, rect.top))) .expect( "Unexpected GetWindowRect failure; please report this error to \ rust-windowing/winit", ) } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { let mut position: POINT = unsafe { mem::zeroed() }; if unsafe { ClientToScreen(self.hwnd(), &mut position) } == false.into() { panic!( "Unexpected ClientToScreen failure: please report this error to \ rust-windowing/winit" ) } Ok(PhysicalPosition::new(position.x, position.y)) } #[inline] pub fn set_outer_position(&self, position: Position) { let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); let window_state = Arc::clone(&self.window_state); let window = self.window; self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MAXIMIZED, false) }); }); unsafe { SetWindowPos( self.hwnd(), 0, x, y, 0, 0, SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE, ); InvalidateRgn(self.hwnd(), 0, false.into()); } } #[inline] pub fn inner_size(&self) -> PhysicalSize { let mut rect: RECT = unsafe { mem::zeroed() }; if unsafe { GetClientRect(self.hwnd(), &mut rect) } == false.into() { panic!( "Unexpected GetClientRect failure: please report this error to \ rust-windowing/winit" ) } PhysicalSize::new((rect.right - rect.left) as u32, (rect.bottom - rect.top) as u32) } #[inline] pub fn outer_size(&self) -> PhysicalSize { util::WindowArea::Outer .get_rect(self.hwnd()) .map(|rect| { PhysicalSize::new((rect.right - rect.left) as u32, (rect.bottom - rect.top) as u32) }) .unwrap() } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let scale_factor = self.scale_factor(); let physical_size = size.to_physical::(scale_factor); let window_flags = self.window_state_lock().window_flags; window_flags.set_size(self.hwnd(), physical_size); if physical_size != self.inner_size() { let window_state = Arc::clone(&self.window_state); let window = self.window; self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MAXIMIZED, false) }); }); } None } #[inline] pub fn set_min_inner_size(&self, size: Option) { self.window_state_lock().min_size = size; // Make windows re-check the window size bounds. let size = self.inner_size(); self.request_inner_size(size.into()); } #[inline] pub fn set_max_inner_size(&self, size: Option) { self.window_state_lock().max_size = size; // Make windows re-check the window size bounds. let size = self.inner_size(); self.request_inner_size(size.into()); } #[inline] pub fn resize_increments(&self) -> Option> { let w = self.window_state_lock(); let scale_factor = w.scale_factor; w.resize_increments.map(|size| size.to_physical(scale_factor)) } #[inline] pub fn set_resize_increments(&self, increments: Option) { self.window_state_lock().resize_increments = increments; } #[inline] pub fn set_resizable(&self, resizable: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::RESIZABLE, resizable) }); }); } #[inline] pub fn is_resizable(&self) -> bool { let window_state = self.window_state_lock(); window_state.window_flags.contains(WindowFlags::RESIZABLE) } #[inline] pub fn set_enabled_buttons(&self, buttons: WindowButtons) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MINIMIZABLE, buttons.contains(WindowButtons::MINIMIZE)); f.set(WindowFlags::MAXIMIZABLE, buttons.contains(WindowButtons::MAXIMIZE)); f.set(WindowFlags::CLOSABLE, buttons.contains(WindowButtons::CLOSE)) }); }); } pub fn enabled_buttons(&self) -> WindowButtons { let mut buttons = WindowButtons::empty(); let window_state = self.window_state_lock(); if window_state.window_flags.contains(WindowFlags::MINIMIZABLE) { buttons |= WindowButtons::MINIMIZE; } if window_state.window_flags.contains(WindowFlags::MAXIMIZABLE) { buttons |= WindowButtons::MAXIMIZE; } if window_state.window_flags.contains(WindowFlags::CLOSABLE) { buttons |= WindowButtons::CLOSE; } buttons } /// Returns the `hwnd` of this window. #[inline] pub fn hwnd(&self) -> HWND { self.window } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::Win32Handle::empty(); window_handle.hwnd = self.window as *mut _; let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) }; window_handle.hinstance = hinstance as *mut _; rwh_04::RawWindowHandle::Win32(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::Win32WindowHandle::empty(); window_handle.hwnd = self.window as *mut _; let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) }; window_handle.hinstance = hinstance as *mut _; rwh_05::RawWindowHandle::Win32(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Windows(rwh_05::WindowsDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] #[inline] pub unsafe fn rwh_06_no_thread_check( &self, ) -> Result { let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe { // SAFETY: Handle will never be zero. std::num::NonZeroIsize::new_unchecked(self.window) }); let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) }; window_handle.hinstance = std::num::NonZeroIsize::new(hinstance); Ok(rwh_06::RawWindowHandle::Win32(window_handle)) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { // TODO: Write a test once integration framework is ready to ensure that it holds. // If we aren't in the GUI thread, we can't return the window. if !self.thread_executor.in_event_loop_thread() { tracing::error!("tried to access window handle outside of the main thread"); return Err(rwh_06::HandleError::Unavailable); } // SAFETY: We are on the correct thread. unsafe { self.rwh_06_no_thread_check() } } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Windows(rwh_06::WindowsDisplayHandle::new())) } #[inline] pub fn set_cursor(&self, cursor: Cursor) { match cursor { Cursor::Icon(icon) => { self.window_state_lock().mouse.selected_cursor = SelectedCursor::Named(icon); self.thread_executor.execute_in_thread(move || unsafe { let cursor = LoadCursorW(0, util::to_windows_cursor(icon)); SetCursor(cursor); }); }, Cursor::Custom(cursor) => { let new_cursor = match cursor.inner { WinCursor::Cursor(cursor) => cursor, WinCursor::Failed => { warn!("Requested to apply failed cursor"); return; }, }; self.window_state_lock().mouse.selected_cursor = SelectedCursor::Custom(new_cursor.clone()); self.thread_executor.execute_in_thread(move || unsafe { SetCursor(new_cursor.as_raw_handle()); }); }, } } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { let confine = match mode { CursorGrabMode::None => false, CursorGrabMode::Confined => true, CursorGrabMode::Locked => { return Err(ExternalError::NotSupported(NotSupportedError::new())) }, }; let window = self.window; let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel(); self.thread_executor.execute_in_thread(move || { let _ = &window; let result = window_state .lock() .unwrap() .mouse .set_cursor_flags(window, |f| f.set(CursorFlags::GRABBED, confine)) .map_err(|e| ExternalError::Os(os_error!(e))); let _ = tx.send(result); }); rx.recv().unwrap() } #[inline] pub fn set_cursor_visible(&self, visible: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel(); self.thread_executor.execute_in_thread(move || { let _ = &window; let result = window_state .lock() .unwrap() .mouse .set_cursor_flags(window, |f| f.set(CursorFlags::HIDDEN, !visible)) .map_err(|e| e.to_string()); let _ = tx.send(result); }); rx.recv().unwrap().ok(); } #[inline] pub fn scale_factor(&self) -> f64 { self.window_state_lock().scale_factor } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { let scale_factor = self.scale_factor(); let (x, y) = position.to_physical::(scale_factor).into(); let mut point = POINT { x, y }; unsafe { if ClientToScreen(self.hwnd(), &mut point) == false.into() { return Err(ExternalError::Os(os_error!(io::Error::last_os_error()))); } if SetCursorPos(point.x, point.y) == false.into() { return Err(ExternalError::Os(os_error!(io::Error::last_os_error()))); } } Ok(()) } unsafe fn handle_os_dragging(&self, wparam: WPARAM) { let window = self.window; let window_state = self.window_state.clone(); self.thread_executor.execute_in_thread(move || { { let mut guard = window_state.lock().unwrap(); if !guard.dragging { guard.dragging = true; } else { return; } } let points = { let mut pos = unsafe { mem::zeroed() }; unsafe { GetCursorPos(&mut pos) }; pos }; let points = POINTS { x: points.x as i16, y: points.y as i16 }; // ReleaseCapture needs to execute on the main thread unsafe { ReleaseCapture() }; unsafe { PostMessageW(window, WM_NCLBUTTONDOWN, wparam, &points as *const _ as LPARAM) }; }); } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { unsafe { self.handle_os_dragging(HTCAPTION as WPARAM); } Ok(()) } #[inline] pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { unsafe { self.handle_os_dragging(match direction { ResizeDirection::East => HTRIGHT, ResizeDirection::North => HTTOP, ResizeDirection::NorthEast => HTTOPRIGHT, ResizeDirection::NorthWest => HTTOPLEFT, ResizeDirection::South => HTBOTTOM, ResizeDirection::SouthEast => HTBOTTOMRIGHT, ResizeDirection::SouthWest => HTBOTTOMLEFT, ResizeDirection::West => HTLEFT, } as WPARAM); } Ok(()) } unsafe fn handle_showing_window_menu(&self, position: Position) { unsafe { let point = { let mut point = POINT { x: 0, y: 0 }; let scale_factor = self.scale_factor(); let (x, y) = position.to_physical::(scale_factor).into(); point.x = x; point.y = y; if ClientToScreen(self.hwnd(), &mut point) == false.into() { warn!( "Can't convert client-area coordinates to screen coordinates when showing \ window menu." ); return; } point }; // get the current system menu let h_menu = GetSystemMenu(self.hwnd(), 0); if h_menu == 0 { warn!("The corresponding window doesn't have a system menu"); // This situation should not be treated as an error so just return without showing // menu. return; } fn enable(b: bool) -> MENU_ITEM_STATE { if b { MFS_ENABLED } else { MFS_DISABLED } } // Change the menu items according to the current window status. let restore_btn = enable(self.is_maximized() && self.is_resizable()); let size_btn = enable(!self.is_maximized() && self.is_resizable()); let maximize_btn = enable(!self.is_maximized() && self.is_resizable()); EnableMenuItem(h_menu, SC_RESTORE, MF_BYCOMMAND | restore_btn); EnableMenuItem(h_menu, SC_MOVE, MF_BYCOMMAND | enable(!self.is_maximized())); EnableMenuItem(h_menu, SC_SIZE, MF_BYCOMMAND | size_btn); EnableMenuItem(h_menu, SC_MINIMIZE, MF_BYCOMMAND | MFS_ENABLED); EnableMenuItem(h_menu, SC_MAXIMIZE, MF_BYCOMMAND | maximize_btn); EnableMenuItem(h_menu, SC_CLOSE, MF_BYCOMMAND | MFS_ENABLED); // Set the default menu item. SetMenuDefaultItem(h_menu, SC_CLOSE, 0); // Popup the system menu at the position. let result = TrackPopupMenu( h_menu, TPM_RETURNCMD | TPM_LEFTALIGN, /* for now im using LTR, but we have to use user * layout direction */ point.x, point.y, 0, self.hwnd(), std::ptr::null_mut(), ); if result == 0 { // User canceled the menu, no need to continue. return; } // Send the command that the user select to the corresponding window. if PostMessageW(self.hwnd(), WM_SYSCOMMAND, result as _, 0) == 0 { warn!("Can't post the system menu message to the window."); } } } #[inline] pub fn show_window_menu(&self, position: Position) { unsafe { self.handle_showing_window_menu(position); } } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::IGNORE_CURSOR_EVENT, !hittest) }); }); Ok(()) } #[inline] pub fn id(&self) -> WindowId { WindowId(self.hwnd()) } #[inline] pub fn set_minimized(&self, minimized: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); let is_minimized = util::is_minimized(self.hwnd()); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags_in_place(&mut window_state.lock().unwrap(), |f| { f.set(WindowFlags::MINIMIZED, is_minimized) }); WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MINIMIZED, minimized) }); }); } #[inline] pub fn is_minimized(&self) -> Option { Some(util::is_minimized(self.hwnd())) } #[inline] pub fn set_maximized(&self, maximized: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MAXIMIZED, maximized) }); }); } #[inline] pub fn is_maximized(&self) -> bool { let window_state = self.window_state_lock(); window_state.window_flags.contains(WindowFlags::MAXIMIZED) } #[inline] pub fn fullscreen(&self) -> Option { let window_state = self.window_state_lock(); window_state.fullscreen.clone() } #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { let window = self.window; let window_state = Arc::clone(&self.window_state); let mut window_state_lock = window_state.lock().unwrap(); let old_fullscreen = window_state_lock.fullscreen.clone(); match (&old_fullscreen, &fullscreen) { // Return if we already are in the same fullscreen mode _ if old_fullscreen == fullscreen => return, // Return if saved Borderless(monitor) is the same as current monitor when requested // fullscreen is Borderless(None) (Some(Fullscreen::Borderless(Some(monitor))), Some(Fullscreen::Borderless(None))) if *monitor == monitor::current_monitor(window) => { return }, _ => {}, } window_state_lock.fullscreen.clone_from(&fullscreen); drop(window_state_lock); self.thread_executor.execute_in_thread(move || { let _ = &window; // Change video mode if we're transitioning to or from exclusive // fullscreen match (&old_fullscreen, &fullscreen) { (_, Some(Fullscreen::Exclusive(video_mode))) => { let monitor = video_mode.monitor(); let monitor_info = monitor::get_monitor_info(monitor.hmonitor()).unwrap(); let res = unsafe { ChangeDisplaySettingsExW( monitor_info.szDevice.as_ptr(), &*video_mode.native_video_mode, 0, CDS_FULLSCREEN, ptr::null(), ) }; debug_assert!(res != DISP_CHANGE_BADFLAGS); debug_assert!(res != DISP_CHANGE_BADMODE); debug_assert!(res != DISP_CHANGE_BADPARAM); debug_assert!(res != DISP_CHANGE_FAILED); assert_eq!(res, DISP_CHANGE_SUCCESSFUL); }, (Some(Fullscreen::Exclusive(_)), _) => { let res = unsafe { ChangeDisplaySettingsExW( ptr::null(), ptr::null(), 0, CDS_FULLSCREEN, ptr::null(), ) }; debug_assert!(res != DISP_CHANGE_BADFLAGS); debug_assert!(res != DISP_CHANGE_BADMODE); debug_assert!(res != DISP_CHANGE_BADPARAM); debug_assert!(res != DISP_CHANGE_FAILED); assert_eq!(res, DISP_CHANGE_SUCCESSFUL); }, _ => (), } unsafe { // There are some scenarios where calling `ChangeDisplaySettingsExW` takes long // enough to execute that the DWM thinks our program has frozen and takes over // our program's window. When that happens, the `SetWindowPos` call below gets // eaten and the window doesn't get set to the proper fullscreen position. // // Calling `PeekMessageW` here notifies Windows that our process is still running // fine, taking control back from the DWM and ensuring that the `SetWindowPos` call // below goes through. let mut msg = mem::zeroed(); PeekMessageW(&mut msg, 0, 0, 0, PM_NOREMOVE); } // Update window style WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set( WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN, matches!(fullscreen, Some(Fullscreen::Exclusive(_))), ); f.set( WindowFlags::MARKER_BORDERLESS_FULLSCREEN, matches!(fullscreen, Some(Fullscreen::Borderless(_))), ); }); // Mark as fullscreen window wrt to z-order // // this needs to be called before the below fullscreen SetWindowPos as this itself // will generate WM_SIZE messages of the old window size that can race with what we set // below unsafe { taskbar_mark_fullscreen(window, fullscreen.is_some()); } // Update window bounds match &fullscreen { Some(fullscreen) => { // Save window bounds before entering fullscreen let placement = unsafe { let mut placement = mem::zeroed(); GetWindowPlacement(window, &mut placement); placement }; window_state.lock().unwrap().saved_window = Some(SavedWindow { placement }); let monitor = match &fullscreen { Fullscreen::Exclusive(video_mode) => video_mode.monitor(), Fullscreen::Borderless(Some(monitor)) => monitor.clone(), Fullscreen::Borderless(None) => monitor::current_monitor(window), }; let position: (i32, i32) = monitor.position().into(); let size: (u32, u32) = monitor.size().into(); unsafe { SetWindowPos( window, 0, position.0, position.1, size.0 as i32, size.1 as i32, SWP_ASYNCWINDOWPOS | SWP_NOZORDER, ); InvalidateRgn(window, 0, false.into()); } }, None => { let mut window_state_lock = window_state.lock().unwrap(); if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() { drop(window_state_lock); unsafe { SetWindowPlacement(window, &placement); InvalidateRgn(window, 0, false.into()); } } }, } }); } #[inline] pub fn set_decorations(&self, decorations: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MARKER_DECORATIONS, decorations) }); }); } #[inline] pub fn is_decorated(&self) -> bool { let window_state = self.window_state_lock(); window_state.window_flags.contains(WindowFlags::MARKER_DECORATIONS) } #[inline] pub fn set_window_level(&self, level: WindowLevel) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::ALWAYS_ON_TOP, level == WindowLevel::AlwaysOnTop); f.set(WindowFlags::ALWAYS_ON_BOTTOM, level == WindowLevel::AlwaysOnBottom); }); }); } #[inline] pub fn current_monitor(&self) -> Option { Some(monitor::current_monitor(self.hwnd())) } #[inline] pub fn set_window_icon(&self, window_icon: Option) { if let Some(ref window_icon) = window_icon { window_icon.inner.set_for_window(self.hwnd(), IconType::Small); } else { icon::unset_for_window(self.hwnd(), IconType::Small); } self.window_state_lock().window_icon = window_icon; } #[inline] pub fn set_enable(&self, enabled: bool) { unsafe { EnableWindow(self.hwnd(), enabled.into()) }; } #[inline] pub fn set_taskbar_icon(&self, taskbar_icon: Option) { if let Some(ref taskbar_icon) = taskbar_icon { taskbar_icon.inner.set_for_window(self.hwnd(), IconType::Big); } else { icon::unset_for_window(self.hwnd(), IconType::Big); } self.window_state_lock().taskbar_icon = taskbar_icon; } #[inline] pub fn set_ime_cursor_area(&self, spot: Position, size: Size) { let window = self.window; let state = self.window_state.clone(); self.thread_executor.execute_in_thread(move || unsafe { let scale_factor = state.lock().unwrap().scale_factor; ImeContext::current(window).set_ime_cursor_area(spot, size, scale_factor); }); } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { let window = self.window; let state = self.window_state.clone(); self.thread_executor.execute_in_thread(move || unsafe { state.lock().unwrap().ime_allowed = allowed; ImeContext::set_ime_allowed(window, allowed); }) } #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] pub fn request_user_attention(&self, request_type: Option) { let window = self.window; let active_window_handle = unsafe { GetActiveWindow() }; if window == active_window_handle { return; } self.thread_executor.execute_in_thread(move || unsafe { let (flags, count) = request_type .map(|ty| match ty { UserAttentionType::Critical => (FLASHW_ALL | FLASHW_TIMERNOFG, u32::MAX), UserAttentionType::Informational => (FLASHW_TRAY | FLASHW_TIMERNOFG, 0), }) .unwrap_or((FLASHW_STOP, 0)); let flash_info = FLASHWINFO { cbSize: mem::size_of::() as u32, hwnd: window, dwFlags: flags, uCount: count, dwTimeout: 0, }; FlashWindowEx(&flash_info); }); } #[inline] pub fn set_theme(&self, theme: Option) { try_theme(self.window, theme); } #[inline] pub fn theme(&self) -> Option { Some(self.window_state_lock().current_theme) } #[inline] pub fn has_focus(&self) -> bool { let window_state = self.window_state.lock().unwrap(); window_state.has_active_focus() } pub fn title(&self) -> String { let len = unsafe { GetWindowTextLengthW(self.window) } + 1; let mut buf = vec![0; len as usize]; unsafe { GetWindowTextW(self.window, buf.as_mut_ptr(), len) }; util::decode_wide(&buf).to_string_lossy().to_string() } #[inline] pub fn set_skip_taskbar(&self, skip: bool) { self.window_state_lock().skip_taskbar = skip; unsafe { set_skip_taskbar(self.hwnd(), skip) }; } #[inline] pub fn set_undecorated_shadow(&self, shadow: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow) }); }); } #[inline] pub fn set_system_backdrop(&self, backdrop_type: BackdropType) { unsafe { DwmSetWindowAttribute( self.hwnd(), DWMWA_SYSTEMBACKDROP_TYPE as u32, &(backdrop_type as i32) as *const _ as _, mem::size_of::() as _, ); } } #[inline] pub fn focus_window(&self) { let window_flags = self.window_state_lock().window_flags(); let is_visible = window_flags.contains(WindowFlags::VISIBLE); let is_minimized = util::is_minimized(self.hwnd()); let is_foreground = self.window == unsafe { GetForegroundWindow() }; if is_visible && !is_minimized && !is_foreground { unsafe { force_window_active(self.window) }; } } #[inline] pub fn set_content_protected(&self, protected: bool) { unsafe { SetWindowDisplayAffinity( self.hwnd(), if protected { WDA_EXCLUDEFROMCAPTURE } else { WDA_NONE }, ) }; } #[inline] pub fn reset_dead_keys(&self) { // `ToUnicode` consumes the dead-key by default, so we are constructing a fake (but valid) // key input which we can call `ToUnicode` with. unsafe { let vk = VK_SPACE as VIRTUAL_KEY; let scancode = MapVirtualKeyW(vk as u32, MAPVK_VK_TO_VSC); let kbd_state = [0; 256]; let mut char_buff = [MaybeUninit::uninit(); 8]; ToUnicode( vk as u32, scancode, kbd_state.as_ptr(), char_buff[0].as_mut_ptr(), char_buff.len() as i32, 0, ); } } #[inline] pub fn set_border_color(&self, color: Color) { unsafe { DwmSetWindowAttribute( self.hwnd(), DWMWA_BORDER_COLOR as u32, &color as *const _ as _, mem::size_of::() as _, ); } } #[inline] pub fn set_title_background_color(&self, color: Color) { unsafe { DwmSetWindowAttribute( self.hwnd(), DWMWA_CAPTION_COLOR as u32, &color as *const _ as _, mem::size_of::() as _, ); } } #[inline] pub fn set_title_text_color(&self, color: Color) { unsafe { DwmSetWindowAttribute( self.hwnd(), DWMWA_TEXT_COLOR as u32, &color as *const _ as _, mem::size_of::() as _, ); } } #[inline] pub fn set_corner_preference(&self, preference: CornerPreference) { unsafe { DwmSetWindowAttribute( self.hwnd(), DWMWA_WINDOW_CORNER_PREFERENCE as u32, &(preference as DWM_WINDOW_CORNER_PREFERENCE) as *const _ as _, mem::size_of::() as _, ); } } } impl Drop for Window { #[inline] fn drop(&mut self) { unsafe { // The window must be destroyed from the same thread that created it, so we send a // custom message to be handled by our callback to do the actual work. PostMessageW(self.hwnd(), DESTROY_MSG_ID.get(), 0, 0); } } } pub(super) struct InitData<'a> { // inputs pub event_loop: &'a ActiveEventLoop, pub attributes: WindowAttributes, pub window_flags: WindowFlags, // outputs pub window: Option, } impl InitData<'_> { unsafe fn create_window(&self, window: HWND) -> Window { // Register for touch events if applicable { let digitizer = unsafe { GetSystemMetrics(SM_DIGITIZER) as u32 }; if digitizer & NID_READY != 0 { unsafe { RegisterTouchWindow(window, TWF_WANTPALM) }; } } let dpi = unsafe { hwnd_dpi(window) }; let scale_factor = dpi_to_scale_factor(dpi); // If the system theme is dark, we need to set the window theme now // before we update the window flags (and possibly show the // window for the first time). let current_theme = try_theme(window, self.attributes.preferred_theme); let window_state = { let window_state = WindowState::new( &self.attributes, scale_factor, current_theme, self.attributes.preferred_theme, ); let window_state = Arc::new(Mutex::new(window_state)); WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { *f = self.window_flags }); window_state }; enable_non_client_dpi_scaling(window); unsafe { ImeContext::set_ime_allowed(window, false) }; Window { window, window_state, thread_executor: self.event_loop.create_thread_executor() } } unsafe fn create_window_data(&self, win: &Window) -> event_loop::WindowData { let file_drop_handler = if self.attributes.platform_specific.drag_and_drop { let ole_init_result = unsafe { OleInitialize(ptr::null_mut()) }; // It is ok if the initialize result is `S_FALSE` because it might happen that // multiple windows are created on the same thread. if ole_init_result == OLE_E_WRONGCOMPOBJ { panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`"); } else if ole_init_result == RPC_E_CHANGED_MODE { panic!( "OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`. Make sure other \ crates are not using multithreaded COM library on the same thread or disable \ drag and drop support." ); } let file_drop_runner = self.event_loop.runner_shared.clone(); let file_drop_handler = FileDropHandler::new( win.window, Box::new(move |event| { if let Ok(e) = event.map_nonuser_event() { file_drop_runner.send_event(e) } }), ); let handler_interface_ptr = unsafe { &mut (*file_drop_handler.data).interface as *mut _ as *mut c_void }; assert_eq!(unsafe { RegisterDragDrop(win.window, handler_interface_ptr) }, S_OK); Some(file_drop_handler) } else { None }; event_loop::WindowData { window_state: win.window_state.clone(), event_loop_runner: self.event_loop.runner_shared.clone(), key_event_builder: KeyEventBuilder::default(), _file_drop_handler: file_drop_handler, userdata_removed: Cell::new(false), recurse_depth: Cell::new(0), } } // Returns a pointer to window user data on success. // The user data will be registered for the window and can be accessed within the window event // callback. pub unsafe fn on_nccreate(&mut self, window: HWND) -> Option { let runner = self.event_loop.runner_shared.clone(); let result = runner.catch_unwind(|| { let window = unsafe { self.create_window(window) }; let window_data = unsafe { self.create_window_data(&window) }; (window, window_data) }); result.map(|(win, userdata)| { self.window = Some(win); let userdata = Box::into_raw(Box::new(userdata)); userdata as _ }) } pub unsafe fn on_create(&mut self) { let win = self.window.as_mut().expect("failed window creation"); // making the window transparent if self.attributes.transparent && !self.attributes.platform_specific.no_redirection_bitmap { // Empty region for the blur effect, so the window is fully transparent let region = unsafe { CreateRectRgn(0, 0, -1, -1) }; let bb = DWM_BLURBEHIND { dwFlags: DWM_BB_ENABLE | DWM_BB_BLURREGION, fEnable: true.into(), hRgnBlur: region, fTransitionOnMaximized: false.into(), }; let hr = unsafe { DwmEnableBlurBehindWindow(win.hwnd(), &bb) }; if hr < 0 { warn!("Setting transparent window is failed. HRESULT Code: 0x{:X}", hr); } unsafe { DeleteObject(region) }; } win.set_skip_taskbar(self.attributes.platform_specific.skip_taskbar); win.set_window_icon(self.attributes.window_icon.clone()); win.set_taskbar_icon(self.attributes.platform_specific.taskbar_icon.clone()); let attributes = self.attributes.clone(); if attributes.content_protected { win.set_content_protected(true); } win.set_cursor(attributes.cursor); // Set visible before setting the size to ensure the // attribute is correctly applied. win.set_visible(attributes.visible); win.set_enabled_buttons(attributes.enabled_buttons); let size = attributes.inner_size.unwrap_or_else(|| PhysicalSize::new(800, 600).into()); let max_size = attributes .max_inner_size .unwrap_or_else(|| PhysicalSize::new(f64::MAX, f64::MAX).into()); let min_size = attributes.min_inner_size.unwrap_or_else(|| PhysicalSize::new(0, 0).into()); let clamped_size = Size::clamp(size, min_size, max_size, win.scale_factor()); win.request_inner_size(clamped_size); // let margins = MARGINS { // cxLeftWidth: 1, // cxRightWidth: 1, // cyTopHeight: 1, // cyBottomHeight: 1, // }; // dbg!(DwmExtendFrameIntoClientArea(win.hwnd(), &margins as *const _)); if let Some(position) = attributes.position { win.set_outer_position(position); } win.set_system_backdrop(self.attributes.platform_specific.backdrop_type); if let Some(color) = self.attributes.platform_specific.border_color { win.set_border_color(color); } if let Some(color) = self.attributes.platform_specific.title_background_color { win.set_title_background_color(color); } if let Some(color) = self.attributes.platform_specific.title_text_color { win.set_title_text_color(color); } if let Some(corner) = self.attributes.platform_specific.corner_preference { win.set_corner_preference(corner); } } } unsafe fn init( attributes: WindowAttributes, event_loop: &ActiveEventLoop, ) -> Result { let title = util::encode_wide(&attributes.title); let class_name = util::encode_wide(&attributes.platform_specific.class_name); unsafe { register_window_class(&class_name) }; let mut window_flags = WindowFlags::empty(); window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations); window_flags.set( WindowFlags::MARKER_UNDECORATED_SHADOW, attributes.platform_specific.decoration_shadow, ); window_flags .set(WindowFlags::ALWAYS_ON_TOP, attributes.window_level == WindowLevel::AlwaysOnTop); window_flags .set(WindowFlags::ALWAYS_ON_BOTTOM, attributes.window_level == WindowLevel::AlwaysOnBottom); window_flags .set(WindowFlags::NO_BACK_BUFFER, attributes.platform_specific.no_redirection_bitmap); window_flags.set(WindowFlags::MARKER_ACTIVATE, attributes.active); window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent); // WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured. window_flags.set(WindowFlags::RESIZABLE, attributes.resizable); // Will be changed later using `window.set_enabled_buttons` but we need to set a default here // so the diffing later can work. window_flags.set(WindowFlags::CLOSABLE, true); window_flags.set(WindowFlags::CLIP_CHILDREN, attributes.platform_specific.clip_children); let mut fallback_parent = || match attributes.platform_specific.owner { Some(parent) => { window_flags.set(WindowFlags::POPUP, true); Some(parent) }, None => { window_flags.set(WindowFlags::ON_TASKBAR, true); None }, }; #[cfg(feature = "rwh_06")] let parent = match attributes.parent_window.as_ref().map(|handle| handle.0) { Some(rwh_06::RawWindowHandle::Win32(handle)) => { window_flags.set(WindowFlags::CHILD, true); if attributes.platform_specific.menu.is_some() { warn!("Setting a menu on a child window is unsupported"); } Some(handle.hwnd.get() as HWND) }, Some(raw) => unreachable!("Invalid raw window handle {raw:?} on Windows"), None => fallback_parent(), }; #[cfg(not(feature = "rwh_06"))] let parent = fallback_parent(); let menu = attributes.platform_specific.menu; let fullscreen = attributes.fullscreen.clone(); let maximized = attributes.maximized; let mut initdata = InitData { event_loop, attributes, window_flags, window: None }; let (style, ex_style) = window_flags.to_window_styles(); let handle = unsafe { CreateWindowExW( ex_style, class_name.as_ptr(), title.as_ptr(), style, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, parent.unwrap_or(0), menu.unwrap_or(0), util::get_instance_handle(), &mut initdata as *mut _ as *mut _, ) }; // If the window creation in `InitData` panicked, then should resume panicking here if let Err(panic_error) = event_loop.runner_shared.take_panic_error() { panic::resume_unwind(panic_error) } if handle == 0 { return Err(os_error!(io::Error::last_os_error())); } // If the handle is non-null, then window creation must have succeeded, which means // that we *must* have populated the `InitData.window` field. let win = initdata.window.unwrap(); // Need to set FULLSCREEN or MAXIMIZED after CreateWindowEx // This is because if the size is changed in WM_CREATE, the restored size will be stored in that // size. if fullscreen.is_some() { win.set_fullscreen(fullscreen.map(Into::into)); unsafe { force_window_active(win.window) }; } else if maximized { win.set_maximized(true); } Ok(win) } unsafe fn register_window_class(class_name: &[u16]) { let class = WNDCLASSEXW { cbSize: mem::size_of::() as u32, style: CS_HREDRAW | CS_VREDRAW, lpfnWndProc: Some(super::event_loop::public_window_callback), cbClsExtra: 0, cbWndExtra: 0, hInstance: util::get_instance_handle(), hIcon: 0, hCursor: 0, // must be null in order for cursor state to work properly hbrBackground: 0, lpszMenuName: ptr::null(), lpszClassName: class_name.as_ptr(), hIconSm: 0, }; // We ignore errors because registering the same window class twice would trigger // an error, and because errors here are detected during CreateWindowEx anyway. // Also since there is no weird element in the struct, there is no reason for this // call to fail. unsafe { RegisterClassExW(&class) }; } struct ComInitialized(#[allow(dead_code)] *mut ()); impl Drop for ComInitialized { fn drop(&mut self) { unsafe { CoUninitialize() }; } } thread_local! { static COM_INITIALIZED: ComInitialized = { unsafe { CoInitializeEx(ptr::null(), COINIT_APARTMENTTHREADED as u32); ComInitialized(ptr::null_mut()) } }; static TASKBAR_LIST: Cell<*mut ITaskbarList> = const { Cell::new(ptr::null_mut()) }; static TASKBAR_LIST2: Cell<*mut ITaskbarList2> = const { Cell::new(ptr::null_mut()) }; } pub fn com_initialized() { COM_INITIALIZED.with(|_| {}); } // Reference Implementation: // https://github.com/chromium/chromium/blob/f18e79d901f56154f80eea1e2218544285e62623/ui/views/win/fullscreen_handler.cc // // As per MSDN marking the window as fullscreen should ensure that the // taskbar is moved to the bottom of the Z-order when the fullscreen window // is activated. If the window is not fullscreen, the Shell falls back to // heuristics to determine how the window should be treated, which means // that it could still consider the window as fullscreen. :( unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) { com_initialized(); TASKBAR_LIST2.with(|task_bar_list2_ptr| { let mut task_bar_list2 = task_bar_list2_ptr.get(); if task_bar_list2.is_null() { let hr = unsafe { CoCreateInstance( &CLSID_TaskbarList, ptr::null_mut(), CLSCTX_ALL, &IID_ITaskbarList2, &mut task_bar_list2 as *mut _ as *mut _, ) }; if hr != S_OK { // In visual studio retrieving the taskbar list fails return; } let hr_init = unsafe { (*(*task_bar_list2).lpVtbl).parent.HrInit }; if unsafe { hr_init(task_bar_list2.cast()) } != S_OK { // In some old windows, the taskbar object could not be created, we just ignore it return; } task_bar_list2_ptr.set(task_bar_list2) } task_bar_list2 = task_bar_list2_ptr.get(); let mark_fullscreen_window = unsafe { (*(*task_bar_list2).lpVtbl).MarkFullscreenWindow }; unsafe { mark_fullscreen_window(task_bar_list2, handle, fullscreen.into()) }; }) } pub(crate) unsafe fn set_skip_taskbar(hwnd: HWND, skip: bool) { com_initialized(); TASKBAR_LIST.with(|task_bar_list_ptr| { let mut task_bar_list = task_bar_list_ptr.get(); if task_bar_list.is_null() { let hr = unsafe { CoCreateInstance( &CLSID_TaskbarList, ptr::null_mut(), CLSCTX_ALL, &IID_ITaskbarList, &mut task_bar_list as *mut _ as *mut _, ) }; if hr != S_OK { // In visual studio retrieving the taskbar list fails return; } let hr_init = unsafe { (*(*task_bar_list).lpVtbl).HrInit }; if unsafe { hr_init(task_bar_list.cast()) } != S_OK { // In some old windows, the taskbar object could not be created, we just ignore it return; } task_bar_list_ptr.set(task_bar_list) } task_bar_list = task_bar_list_ptr.get(); if skip { let delete_tab = unsafe { (*(*task_bar_list).lpVtbl).DeleteTab }; unsafe { delete_tab(task_bar_list, hwnd) }; } else { let add_tab = unsafe { (*(*task_bar_list).lpVtbl).AddTab }; unsafe { add_tab(task_bar_list, hwnd) }; } }); } unsafe fn force_window_active(handle: HWND) { // In some situation, calling SetForegroundWindow could not bring up the window, // This is a little hack which can "steal" the foreground window permission // We only call this function in the window creation, so it should be fine. // See : https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open let alt_sc = unsafe { MapVirtualKeyW(VK_MENU as u32, MAPVK_VK_TO_VSC) }; let inputs = [ INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { wVk: VK_LMENU, wScan: alt_sc as u16, dwFlags: KEYEVENTF_EXTENDEDKEY, dwExtraInfo: 0, time: 0, }, }, }, INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { wVk: VK_LMENU, wScan: alt_sc as u16, dwFlags: KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, dwExtraInfo: 0, time: 0, }, }, }, ]; // Simulate a key press and release unsafe { SendInput(inputs.len() as u32, inputs.as_ptr(), mem::size_of::() as i32) }; unsafe { SetForegroundWindow(handle) }; } winit-0.30.9/src/platform_impl/windows/window_state.rs000064400000000000000000000440121046102023000212530ustar 00000000000000use crate::dpi::{PhysicalPosition, PhysicalSize, Size}; use crate::icon::Icon; use crate::keyboard::ModifiersState; use crate::platform_impl::platform::{event_loop, util, Fullscreen, SelectedCursor}; use crate::window::{Theme, WindowAttributes}; use bitflags::bitflags; use std::io; use std::sync::MutexGuard; use windows_sys::Win32::Foundation::{HWND, RECT}; use windows_sys::Win32::Graphics::Gdi::InvalidateRgn; use windows_sys::Win32::UI::WindowsAndMessaging::{ AdjustWindowRectEx, EnableMenuItem, GetMenu, GetSystemMenu, GetWindowLongW, SendMessageW, SetWindowLongW, SetWindowPos, ShowWindow, GWL_EXSTYLE, GWL_STYLE, HWND_BOTTOM, HWND_NOTOPMOST, HWND_TOPMOST, MF_BYCOMMAND, MF_DISABLED, MF_ENABLED, SC_CLOSE, SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOREPOSITION, SWP_NOSIZE, SWP_NOZORDER, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SW_SHOW, SW_SHOWNOACTIVATE, WINDOWPLACEMENT, WINDOW_EX_STYLE, WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD, WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE, }; /// Contains information about states and the window that the callback is going to use. pub(crate) struct WindowState { pub mouse: MouseProperties, /// Used by `WM_GETMINMAXINFO`. pub min_size: Option, pub max_size: Option, pub resize_increments: Option, pub window_icon: Option, pub taskbar_icon: Option, pub saved_window: Option, pub scale_factor: f64, pub modifiers_state: ModifiersState, pub fullscreen: Option, pub current_theme: Theme, pub preferred_theme: Option, pub window_flags: WindowFlags, pub ime_state: ImeState, pub ime_allowed: bool, // Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS pub is_active: bool, pub is_focused: bool, // Flag whether redraw was requested. pub redraw_requested: bool, pub dragging: bool, pub skip_taskbar: bool, } #[derive(Clone)] pub struct SavedWindow { pub placement: WINDOWPLACEMENT, } #[derive(Clone)] pub struct MouseProperties { pub(crate) selected_cursor: SelectedCursor, pub capture_count: u32, cursor_flags: CursorFlags, pub last_position: Option>, } bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct CursorFlags: u8 { const GRABBED = 1 << 0; const HIDDEN = 1 << 1; const IN_WINDOW = 1 << 2; } } bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct WindowFlags: u32 { const RESIZABLE = 1 << 0; const MINIMIZABLE = 1 << 1; const MAXIMIZABLE = 1 << 2; const CLOSABLE = 1 << 3; const VISIBLE = 1 << 4; const ON_TASKBAR = 1 << 5; const ALWAYS_ON_TOP = 1 << 6; const ALWAYS_ON_BOTTOM = 1 << 7; const NO_BACK_BUFFER = 1 << 8; const TRANSPARENT = 1 << 9; const CHILD = 1 << 10; const MAXIMIZED = 1 << 11; const POPUP = 1 << 12; /// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is /// included here to make masking easier. const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 13; const MARKER_BORDERLESS_FULLSCREEN = 1 << 14; /// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`. /// In most cases, it's okay to let those parameters change the state. However, when we're /// running the `WindowFlags::apply_diff` function, we *don't* want those parameters to /// effect our stored state, because the purpose of `apply_diff` is to update the actual /// window's state to match our stored state. This controls whether to accept those changes. const MARKER_RETAIN_STATE_ON_SIZE = 1 << 15; const MARKER_IN_SIZE_MOVE = 1 << 16; const MINIMIZED = 1 << 17; const IGNORE_CURSOR_EVENT = 1 << 18; /// Fully decorated window (incl. caption, border and drop shadow). const MARKER_DECORATIONS = 1 << 19; /// Drop shadow for undecorated windows. const MARKER_UNDECORATED_SHADOW = 1 << 20; const MARKER_ACTIVATE = 1 << 21; const CLIP_CHILDREN = 1 << 22; const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits(); } } #[derive(Eq, PartialEq)] pub enum ImeState { Disabled, Enabled, Preedit, } impl WindowState { pub(crate) fn new( attributes: &WindowAttributes, scale_factor: f64, current_theme: Theme, preferred_theme: Option, ) -> WindowState { WindowState { mouse: MouseProperties { selected_cursor: SelectedCursor::default(), capture_count: 0, cursor_flags: CursorFlags::empty(), last_position: None, }, min_size: attributes.min_inner_size, max_size: attributes.max_inner_size, resize_increments: attributes.resize_increments, window_icon: attributes.window_icon.clone(), taskbar_icon: None, saved_window: None, scale_factor, modifiers_state: ModifiersState::default(), fullscreen: None, current_theme, preferred_theme, window_flags: WindowFlags::empty(), ime_state: ImeState::Disabled, ime_allowed: false, is_active: false, is_focused: false, redraw_requested: false, dragging: false, skip_taskbar: false, } } pub fn window_flags(&self) -> WindowFlags { self.window_flags } pub fn set_window_flags(mut this: MutexGuard<'_, Self>, window: HWND, f: F) where F: FnOnce(&mut WindowFlags), { let old_flags = this.window_flags; f(&mut this.window_flags); let new_flags = this.window_flags; drop(this); old_flags.apply_diff(window, new_flags); } pub fn set_window_flags_in_place(&mut self, f: F) where F: FnOnce(&mut WindowFlags), { f(&mut self.window_flags); } pub fn has_active_focus(&self) -> bool { self.is_active && self.is_focused } // Updates is_active and returns whether active-focus state has changed pub fn set_active(&mut self, is_active: bool) -> bool { let old = self.has_active_focus(); self.is_active = is_active; old != self.has_active_focus() } // Updates is_focused and returns whether active-focus state has changed pub fn set_focused(&mut self, is_focused: bool) -> bool { let old = self.has_active_focus(); self.is_focused = is_focused; old != self.has_active_focus() } } impl MouseProperties { pub fn cursor_flags(&self) -> CursorFlags { self.cursor_flags } pub fn set_cursor_flags(&mut self, window: HWND, f: F) -> Result<(), io::Error> where F: FnOnce(&mut CursorFlags), { let old_flags = self.cursor_flags; f(&mut self.cursor_flags); match self.cursor_flags.refresh_os_cursor(window) { Ok(()) => (), Err(e) => { self.cursor_flags = old_flags; return Err(e); }, } Ok(()) } } impl WindowFlags { fn mask(mut self) -> WindowFlags { if self.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) { self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK; } self } pub fn to_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) { // Required styles to properly support common window functionality like aero snap. let mut style = WS_CAPTION | WS_BORDER | WS_CLIPSIBLINGS | WS_SYSMENU; let mut style_ex = WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; if self.contains(WindowFlags::RESIZABLE) { style |= WS_SIZEBOX; } if self.contains(WindowFlags::MAXIMIZABLE) { style |= WS_MAXIMIZEBOX; } if self.contains(WindowFlags::MINIMIZABLE) { style |= WS_MINIMIZEBOX; } if self.contains(WindowFlags::VISIBLE) { style |= WS_VISIBLE; } if self.contains(WindowFlags::ON_TASKBAR) { style_ex |= WS_EX_APPWINDOW; } if self.contains(WindowFlags::ALWAYS_ON_TOP) { style_ex |= WS_EX_TOPMOST; } if self.contains(WindowFlags::NO_BACK_BUFFER) { style_ex |= WS_EX_NOREDIRECTIONBITMAP; } if self.contains(WindowFlags::CHILD) { style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually. // Remove decorations window styles for child if !self.contains(WindowFlags::MARKER_DECORATIONS) { style &= !(WS_CAPTION | WS_BORDER); style_ex &= !WS_EX_WINDOWEDGE; } } if self.contains(WindowFlags::POPUP) { style |= WS_POPUP; } if self.contains(WindowFlags::MINIMIZED) { style |= WS_MINIMIZE; } if self.contains(WindowFlags::MAXIMIZED) { style |= WS_MAXIMIZE; } if self.contains(WindowFlags::IGNORE_CURSOR_EVENT) { style_ex |= WS_EX_TRANSPARENT | WS_EX_LAYERED; } if self.contains(WindowFlags::CLIP_CHILDREN) { style |= WS_CLIPCHILDREN; } if self.intersects( WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN, ) { style &= !WS_OVERLAPPEDWINDOW; } (style, style_ex) } /// Adjust the window client rectangle to the return value, if present. fn apply_diff(mut self, window: HWND, mut new: WindowFlags) { self = self.mask(); new = new.mask(); let mut diff = self ^ new; if diff == WindowFlags::empty() { return; } if new.contains(WindowFlags::VISIBLE) { let flag = if !self.contains(WindowFlags::MARKER_ACTIVATE) { self.set(WindowFlags::MARKER_ACTIVATE, true); SW_SHOWNOACTIVATE } else { SW_SHOW }; unsafe { ShowWindow(window, flag); } } if diff.intersects(WindowFlags::ALWAYS_ON_TOP | WindowFlags::ALWAYS_ON_BOTTOM) { unsafe { SetWindowPos( window, match ( new.contains(WindowFlags::ALWAYS_ON_TOP), new.contains(WindowFlags::ALWAYS_ON_BOTTOM), ) { (true, false) => HWND_TOPMOST, (false, false) => HWND_NOTOPMOST, (false, true) => HWND_BOTTOM, (true, true) => unreachable!(), }, 0, 0, 0, 0, SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, ); InvalidateRgn(window, 0, false.into()); } } if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) { unsafe { ShowWindow(window, match new.contains(WindowFlags::MAXIMIZED) { true => SW_MAXIMIZE, false => SW_RESTORE, }); } } // Minimize operations should execute after maximize for proper window animations if diff.contains(WindowFlags::MINIMIZED) { unsafe { ShowWindow(window, match new.contains(WindowFlags::MINIMIZED) { true => SW_MINIMIZE, false => SW_RESTORE, }); } diff.remove(WindowFlags::MINIMIZED); } if diff.contains(WindowFlags::CLOSABLE) || new.contains(WindowFlags::CLOSABLE) { let flags = MF_BYCOMMAND | if new.contains(WindowFlags::CLOSABLE) { MF_ENABLED } else { MF_DISABLED }; unsafe { EnableMenuItem(GetSystemMenu(window, 0), SC_CLOSE, flags); } } if !new.contains(WindowFlags::VISIBLE) { unsafe { ShowWindow(window, SW_HIDE); } } if diff != WindowFlags::empty() { let (style, style_ex) = new.to_window_styles(); unsafe { SendMessageW(window, event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID.get(), 1, 0); // This condition is necessary to avoid having an unrestorable window if !new.contains(WindowFlags::MINIMIZED) { SetWindowLongW(window, GWL_STYLE, style as i32); SetWindowLongW(window, GWL_EXSTYLE, style_ex as i32); } let mut flags = SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED; // We generally don't want style changes here to affect window // focus, but for fullscreen windows they must be activated // (i.e. focused) so that they appear on top of the taskbar if !new.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) && !new.contains(WindowFlags::MARKER_BORDERLESS_FULLSCREEN) { flags |= SWP_NOACTIVATE; } // Refresh the window frame SetWindowPos(window, 0, 0, 0, 0, 0, flags); SendMessageW(window, event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID.get(), 0, 0); } } } pub fn adjust_rect(self, hwnd: HWND, mut rect: RECT) -> Result { unsafe { let mut style = GetWindowLongW(hwnd, GWL_STYLE) as u32; let style_ex = GetWindowLongW(hwnd, GWL_EXSTYLE) as u32; // Frameless style implemented by manually overriding the non-client area in // `WM_NCCALCSIZE`. if !self.contains(WindowFlags::MARKER_DECORATIONS) { style &= !(WS_CAPTION | WS_SIZEBOX); } util::win_to_err({ let b_menu = GetMenu(hwnd) != 0; if let (Some(get_dpi_for_window), Some(adjust_window_rect_ex_for_dpi)) = (*util::GET_DPI_FOR_WINDOW, *util::ADJUST_WINDOW_RECT_EX_FOR_DPI) { let dpi = get_dpi_for_window(hwnd); adjust_window_rect_ex_for_dpi(&mut rect, style, b_menu.into(), style_ex, dpi) } else { AdjustWindowRectEx(&mut rect, style, b_menu.into(), style_ex) } })?; Ok(rect) } } pub fn adjust_size(self, hwnd: HWND, size: PhysicalSize) -> PhysicalSize { let (width, height): (u32, u32) = size.into(); let rect = RECT { left: 0, right: width as i32, top: 0, bottom: height as i32 }; let rect = self.adjust_rect(hwnd, rect).unwrap_or(rect); let outer_x = (rect.right - rect.left).abs(); let outer_y = (rect.top - rect.bottom).abs(); PhysicalSize::new(outer_x as _, outer_y as _) } pub fn set_size(self, hwnd: HWND, size: PhysicalSize) { unsafe { let (width, height): (u32, u32) = self.adjust_size(hwnd, size).into(); SetWindowPos( hwnd, 0, 0, 0, width as _, height as _, SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOACTIVATE, ); InvalidateRgn(hwnd, 0, false.into()); } } } impl CursorFlags { fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> { let client_rect = util::WindowArea::Inner.get_rect(window)?; if util::is_focused(window) { let cursor_clip = match self.contains(CursorFlags::GRABBED) { true => { if self.contains(CursorFlags::HIDDEN) { // Confine the cursor to the center of the window if the cursor is hidden. // This avoids problems with the cursor activating // the taskbar if the window borders or overlaps that. let cx = (client_rect.left + client_rect.right) / 2; let cy = (client_rect.top + client_rect.bottom) / 2; Some(RECT { left: cx, right: cx + 1, top: cy, bottom: cy + 1 }) } else { Some(client_rect) } }, false => None, }; let rect_to_tuple = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom); let active_cursor_clip = rect_to_tuple(util::get_cursor_clip()?); let desktop_rect = rect_to_tuple(util::get_desktop_rect()); let active_cursor_clip = match desktop_rect == active_cursor_clip { true => None, false => Some(active_cursor_clip), }; // We do this check because calling `set_cursor_clip` incessantly will flood the event // loop with `WM_MOUSEMOVE` events, and `refresh_os_cursor` is called by // `set_cursor_flags` which at times gets called once every iteration of the // eventloop. if active_cursor_clip != cursor_clip.map(rect_to_tuple) { util::set_cursor_clip(cursor_clip)?; } } let cursor_in_client = self.contains(CursorFlags::IN_WINDOW); if cursor_in_client { util::set_cursor_hidden(self.contains(CursorFlags::HIDDEN)); } else { util::set_cursor_hidden(false); } Ok(()) } } winit-0.30.9/src/utils.rs000064400000000000000000000011571046102023000133500ustar 00000000000000// A poly-fill for `lazy_cell` // Replace with std::sync::LazyLock when https://github.com/rust-lang/rust/issues/109736 is stabilized. // This isn't used on every platform, which can come up as dead code warnings. #![allow(dead_code)] use std::ops::Deref; use std::sync::OnceLock; pub(crate) struct Lazy { cell: OnceLock, init: fn() -> T, } impl Lazy { pub const fn new(f: fn() -> T) -> Self { Self { cell: OnceLock::new(), init: f } } } impl Deref for Lazy { type Target = T; #[inline] fn deref(&self) -> &'_ T { self.cell.get_or_init(self.init) } } winit-0.30.9/src/window.rs000064400000000000000000002146271046102023000135270ustar 00000000000000//! The [`Window`] struct and associated types. use std::fmt; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError}; use crate::monitor::{MonitorHandle, VideoModeHandle}; use crate::platform_impl::{self, PlatformSpecificWindowAttributes}; pub use crate::cursor::{BadImage, Cursor, CustomCursor, CustomCursorSource, MAX_CURSOR_SIZE}; pub use crate::icon::{BadIcon, Icon}; #[doc(inline)] pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Represents a window. /// /// The window is closed when dropped. /// /// ## Threading /// /// This is `Send + Sync`, meaning that it can be freely used from other /// threads. /// /// However, some platforms (macOS, Web and iOS) only allow user interface /// interactions on the main thread, so on those platforms, if you use the /// window from a thread other than the main, the code is scheduled to run on /// the main thread, and your thread may be blocked until that completes. /// /// ## Platform-specific /// /// **Web:** The [`Window`], which is represented by a `HTMLElementCanvas`, can /// not be closed by dropping the [`Window`]. pub struct Window { pub(crate) window: platform_impl::Window, } impl fmt::Debug for Window { fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { fmtr.pad("Window { .. }") } } impl Drop for Window { /// This will close the [`Window`]. /// /// See [`Window`] for more details. fn drop(&mut self) { self.window.maybe_wait_on_main(|w| { // If the window is in exclusive fullscreen, we must restore the desktop // video mode (generally this would be done on application exit, but // closing the window doesn't necessarily always mean application exit, // such as when there are multiple windows) if let Some(Fullscreen::Exclusive(_)) = w.fullscreen().map(|f| f.into()) { w.set_fullscreen(None); } }) } } /// Identifier of a window. Unique for each window. /// /// Can be obtained with [`window.id()`][`Window::id`]. /// /// Whenever you receive an event specific to a window, this event contains a `WindowId` which you /// can then compare to the ids of your windows. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(pub(crate) platform_impl::WindowId); impl WindowId { /// Returns a dummy id, useful for unit testing. /// /// # Notes /// /// The only guarantee made about the return value of this function is that /// it will always be equal to itself and to future values returned by this function. /// No other guarantees are made. This may be equal to a real [`WindowId`]. pub const fn dummy() -> Self { WindowId(platform_impl::WindowId::dummy()) } } impl fmt::Debug for WindowId { fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(fmtr) } } impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.0.into() } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id.into()) } } /// Attributes used when creating a window. #[derive(Debug, Clone)] pub struct WindowAttributes { pub inner_size: Option, pub min_inner_size: Option, pub max_inner_size: Option, pub position: Option, pub resizable: bool, pub enabled_buttons: WindowButtons, pub title: String, pub maximized: bool, pub visible: bool, pub transparent: bool, pub blur: bool, pub decorations: bool, pub window_icon: Option, pub preferred_theme: Option, pub resize_increments: Option, pub content_protected: bool, pub window_level: WindowLevel, pub active: bool, pub cursor: Cursor, #[cfg(feature = "rwh_06")] pub(crate) parent_window: Option, pub fullscreen: Option, // Platform-specific configuration. #[allow(dead_code)] pub(crate) platform_specific: PlatformSpecificWindowAttributes, } impl Default for WindowAttributes { #[inline] fn default() -> WindowAttributes { WindowAttributes { inner_size: None, min_inner_size: None, max_inner_size: None, position: None, resizable: true, enabled_buttons: WindowButtons::all(), title: "winit window".to_owned(), maximized: false, fullscreen: None, visible: true, transparent: false, blur: false, decorations: true, window_level: Default::default(), window_icon: None, preferred_theme: None, resize_increments: None, content_protected: false, cursor: Cursor::default(), #[cfg(feature = "rwh_06")] parent_window: None, active: true, platform_specific: Default::default(), } } } /// Wrapper for [`rwh_06::RawWindowHandle`] for [`WindowAttributes::parent_window`]. /// /// # Safety /// /// The user has to account for that when using [`WindowAttributes::with_parent_window()`], /// which is `unsafe`. #[derive(Debug, Clone)] #[cfg(feature = "rwh_06")] pub(crate) struct SendSyncRawWindowHandle(pub(crate) rwh_06::RawWindowHandle); #[cfg(feature = "rwh_06")] unsafe impl Send for SendSyncRawWindowHandle {} #[cfg(feature = "rwh_06")] unsafe impl Sync for SendSyncRawWindowHandle {} impl WindowAttributes { /// Initializes new attributes with default values. #[inline] #[deprecated = "use `Window::default_attributes` instead"] pub fn new() -> Self { Default::default() } } impl WindowAttributes { /// Get the parent window stored on the attributes. #[cfg(feature = "rwh_06")] pub fn parent_window(&self) -> Option<&rwh_06::RawWindowHandle> { self.parent_window.as_ref().map(|handle| &handle.0) } /// Requests the window to be of specific dimensions. /// /// If this is not set, some platform-specific dimensions will be used. /// /// See [`Window::request_inner_size`] for details. #[inline] pub fn with_inner_size>(mut self, size: S) -> Self { self.inner_size = Some(size.into()); self } /// Sets the minimum dimensions a window can have. /// /// If this is not set, the window will have no minimum dimensions (aside /// from reserved). /// /// See [`Window::set_min_inner_size`] for details. #[inline] pub fn with_min_inner_size>(mut self, min_size: S) -> Self { self.min_inner_size = Some(min_size.into()); self } /// Sets the maximum dimensions a window can have. /// /// If this is not set, the window will have no maximum or will be set to /// the primary monitor's dimensions by the platform. /// /// See [`Window::set_max_inner_size`] for details. #[inline] pub fn with_max_inner_size>(mut self, max_size: S) -> Self { self.max_inner_size = Some(max_size.into()); self } /// Sets a desired initial position for the window. /// /// If this is not set, some platform-specific position will be chosen. /// /// See [`Window::set_outer_position`] for details. /// /// ## Platform-specific /// /// - **macOS:** The top left corner position of the window content, the window's "inner" /// position. The window title bar will be placed above it. The window will be positioned such /// that it fits on screen, maintaining set `inner_size` if any. If you need to precisely /// position the top left corner of the whole window you have to use /// [`Window::set_outer_position`] after creating the window. /// - **Windows:** The top left corner position of the window title bar, the window's "outer" /// position. There may be a small gap between this position and the window due to the /// specifics of the Window Manager. /// - **X11:** The top left corner of the window, the window's "outer" position. /// - **Others:** Ignored. #[inline] pub fn with_position>(mut self, position: P) -> Self { self.position = Some(position.into()); self } /// Sets whether the window is resizable or not. /// /// The default is `true`. /// /// See [`Window::set_resizable`] for details. #[inline] pub fn with_resizable(mut self, resizable: bool) -> Self { self.resizable = resizable; self } /// Sets the enabled window buttons. /// /// The default is [`WindowButtons::all`] /// /// See [`Window::set_enabled_buttons`] for details. #[inline] pub fn with_enabled_buttons(mut self, buttons: WindowButtons) -> Self { self.enabled_buttons = buttons; self } /// Sets the initial title of the window in the title bar. /// /// The default is `"winit window"`. /// /// See [`Window::set_title`] for details. #[inline] pub fn with_title>(mut self, title: T) -> Self { self.title = title.into(); self } /// Sets whether the window should be put into fullscreen upon creation. /// /// The default is `None`. /// /// See [`Window::set_fullscreen`] for details. #[inline] pub fn with_fullscreen(mut self, fullscreen: Option) -> Self { self.fullscreen = fullscreen; self } /// Request that the window is maximized upon creation. /// /// The default is `false`. /// /// See [`Window::set_maximized`] for details. #[inline] pub fn with_maximized(mut self, maximized: bool) -> Self { self.maximized = maximized; self } /// Sets whether the window will be initially visible or hidden. /// /// The default is to show the window. /// /// See [`Window::set_visible`] for details. #[inline] pub fn with_visible(mut self, visible: bool) -> Self { self.visible = visible; self } /// Sets whether the background of the window should be transparent. /// /// If this is `true`, writing colors with alpha values different than /// `1.0` will produce a transparent window. On some platforms this /// is more of a hint for the system and you'd still have the alpha /// buffer. To control it see [`Window::set_transparent`]. /// /// The default is `false`. #[inline] pub fn with_transparent(mut self, transparent: bool) -> Self { self.transparent = transparent; self } /// Sets whether the background of the window should be blurred by the system. /// /// The default is `false`. /// /// See [`Window::set_blur`] for details. #[inline] pub fn with_blur(mut self, blur: bool) -> Self { self.blur = blur; self } /// Get whether the window will support transparency. #[inline] pub fn transparent(&self) -> bool { self.transparent } /// Sets whether the window should have a border, a title bar, etc. /// /// The default is `true`. /// /// See [`Window::set_decorations`] for details. #[inline] pub fn with_decorations(mut self, decorations: bool) -> Self { self.decorations = decorations; self } /// Sets the window level. /// /// This is just a hint to the OS, and the system could ignore it. /// /// The default is [`WindowLevel::Normal`]. /// /// See [`WindowLevel`] for details. #[inline] pub fn with_window_level(mut self, level: WindowLevel) -> Self { self.window_level = level; self } /// Sets the window icon. /// /// The default is `None`. /// /// See [`Window::set_window_icon`] for details. #[inline] pub fn with_window_icon(mut self, window_icon: Option) -> Self { self.window_icon = window_icon; self } /// Sets a specific theme for the window. /// /// If `None` is provided, the window will use the system theme. /// /// The default is `None`. /// /// ## Platform-specific /// /// - **Wayland:** This controls only CSD. When using `None` it'll try to use dbus to get the /// system preference. When explicit theme is used, this will avoid dbus all together. /// - **x11:** Build window with `_GTK_THEME_VARIANT` hint set to `dark` or `light`. /// - **iOS / Android / Web / x11 / Orbital:** Ignored. #[inline] pub fn with_theme(mut self, theme: Option) -> Self { self.preferred_theme = theme; self } /// Build window with resize increments hint. /// /// The default is `None`. /// /// See [`Window::set_resize_increments`] for details. #[inline] pub fn with_resize_increments>(mut self, resize_increments: S) -> Self { self.resize_increments = Some(resize_increments.into()); self } /// Prevents the window contents from being captured by other apps. /// /// The default is `false`. /// /// ## Platform-specific /// /// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely prevent all /// apps from reading the window content, for instance, QuickTime. /// - **iOS / Android / Web / x11 / Orbital:** Ignored. /// /// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone #[inline] pub fn with_content_protected(mut self, protected: bool) -> Self { self.content_protected = protected; self } /// Whether the window will be initially focused or not. /// /// The window should be assumed as not focused by default /// following by the [`WindowEvent::Focused`]. /// /// ## Platform-specific: /// /// **Android / iOS / X11 / Wayland / Orbital:** Unsupported. /// /// [`WindowEvent::Focused`]: crate::event::WindowEvent::Focused. #[inline] pub fn with_active(mut self, active: bool) -> Self { self.active = active; self } /// Modifies the cursor icon of the window. /// /// The default is [`CursorIcon::Default`]. /// /// See [`Window::set_cursor()`] for more details. #[inline] pub fn with_cursor(mut self, cursor: impl Into) -> Self { self.cursor = cursor.into(); self } /// Build window with parent window. /// /// The default is `None`. /// /// ## Safety /// /// `parent_window` must be a valid window handle. /// /// ## Platform-specific /// /// - **Windows** : A child window has the WS_CHILD style and is confined /// to the client area of its parent window. For more information, see /// /// - **X11**: A child window is confined to the client area of its parent window. /// - **Android / iOS / Wayland / Web:** Unsupported. #[cfg(feature = "rwh_06")] #[inline] pub unsafe fn with_parent_window( mut self, parent_window: Option, ) -> Self { self.parent_window = parent_window.map(SendSyncRawWindowHandle); self } } /// Base Window functions. impl Window { /// Create a new [`WindowAttributes`] which allows modifying the window's attributes before /// creation. #[inline] pub fn default_attributes() -> WindowAttributes { WindowAttributes::default() } /// Returns an identifier unique to the window. #[inline] pub fn id(&self) -> WindowId { let _span = tracing::debug_span!("winit::Window::id",).entered(); self.window.maybe_wait_on_main(|w| WindowId(w.id())) } /// Returns the scale factor that can be used to map logical pixels to physical pixels, and /// vice versa. /// /// Note that this value can change depending on user action (for example if the window is /// moved to another screen); as such, tracking [`WindowEvent::ScaleFactorChanged`] events is /// the most robust way to track the DPI you need to use to draw. /// /// This value may differ from [`MonitorHandle::scale_factor`]. /// /// See the [`dpi`] crate for more information. /// /// ## Platform-specific /// /// The scale factor is calculated differently on different platforms: /// /// - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from /// the display settings. While users are free to select any option they want, they're only /// given a selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale /// factor is global and changing it requires logging out. See [this article][windows_1] for /// technical details. /// - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific /// displays. When available, the user may pick a per-monitor scaling factor from a set of /// pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default, /// but the specific value varies across devices. /// - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. /// Winit currently uses a three-pronged approach: /// + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present. /// + If not present, use the value set in `Xft.dpi` in Xresources. /// + Otherwise, calculate the scale factor based on the millimeter monitor dimensions /// provided by XRandR. /// /// If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use /// the XRandR scaling method. Generally speaking, you should try to configure the /// standard system variables to do what you want before resorting to /// `WINIT_X11_SCALE_FACTOR`. /// - **Wayland:** The scale factor is suggested by the compositor for each window individually /// by using the wp-fractional-scale protocol if available. Falls back to integer-scale /// factors otherwise. /// /// The monitor scale factor may differ from the window scale factor. /// - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range /// from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more /// information. /// /// This uses the underlying `UIView`'s [`contentScaleFactor`]. /// - **Android:** Scale factors are set by the manufacturer to the value that best suits the /// device, and range from `1.0` to `4.0`. See [this article][android_1] for more information. /// /// This is currently unimplemented, and this function always returns 1.0. /// - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels. /// In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by /// both the screen scaling and the browser zoom level and can go below `1.0`. /// - **Orbital:** This is currently unimplemented, and this function always returns 1.0. /// /// [`WindowEvent::ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged /// [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows /// [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html /// [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ /// [android_1]: https://developer.android.com/training/multiscreen/screendensities /// [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc #[inline] pub fn scale_factor(&self) -> f64 { let _span = tracing::debug_span!("winit::Window::scale_factor",).entered(); self.window.maybe_wait_on_main(|w| w.scale_factor()) } /// Queues a [`WindowEvent::RedrawRequested`] event to be emitted that aligns with the windowing /// system drawing loop. /// /// This is the **strongly encouraged** method of redrawing windows, as it can integrate with /// OS-requested redraws (e.g. when a window gets resized). To improve the event delivery /// consider using [`Window::pre_present_notify`] as described in docs. /// /// Applications should always aim to redraw whenever they receive a `RedrawRequested` event. /// /// There are no strong guarantees about when exactly a `RedrawRequest` event will be emitted /// with respect to other events, since the requirements can vary significantly between /// windowing systems. /// /// However as the event aligns with the windowing system drawing loop, it may not arrive in /// same or even next event loop iteration. /// /// ## Platform-specific /// /// - **Windows** This API uses `RedrawWindow` to request a `WM_PAINT` message and /// `RedrawRequested` is emitted in sync with any `WM_PAINT` messages. /// - **iOS:** Can only be called on the main thread. /// - **Wayland:** The events are aligned with the frame callbacks when /// [`Window::pre_present_notify`] is used. /// - **Web:** [`WindowEvent::RedrawRequested`] will be aligned with the /// `requestAnimationFrame`. /// /// [`WindowEvent::RedrawRequested`]: crate::event::WindowEvent::RedrawRequested #[inline] pub fn request_redraw(&self) { let _span = tracing::debug_span!("winit::Window::request_redraw",).entered(); self.window.maybe_queue_on_main(|w| w.request_redraw()) } /// Notify the windowing system before presenting to the window. /// /// You should call this event after your drawing operations, but before you submit /// the buffer to the display or commit your drawings. Doing so will help winit to properly /// schedule and make assumptions about its internal state. For example, it could properly /// throttle [`WindowEvent::RedrawRequested`]. /// /// ## Example /// /// This example illustrates how it looks with OpenGL, but it applies to other graphics /// APIs and software rendering. /// /// ```no_run /// # use winit::window::Window; /// # fn swap_buffers() {} /// # fn scope(window: &Window) { /// // Do the actual drawing with OpenGL. /// /// // Notify winit that we're about to submit buffer to the windowing system. /// window.pre_present_notify(); /// /// // Submit buffer to the windowing system. /// swap_buffers(); /// # } /// ``` /// /// ## Platform-specific /// /// - **Android / iOS / X11 / Web / Windows / macOS / Orbital:** Unsupported. /// - **Wayland:** Schedules a frame callback to throttle [`WindowEvent::RedrawRequested`]. /// /// [`WindowEvent::RedrawRequested`]: crate::event::WindowEvent::RedrawRequested #[inline] pub fn pre_present_notify(&self) { let _span = tracing::debug_span!("winit::Window::pre_present_notify",).entered(); self.window.maybe_queue_on_main(|w| w.pre_present_notify()); } /// Reset the dead key state of the keyboard. /// /// This is useful when a dead key is bound to trigger an action. Then /// this function can be called to reset the dead key state so that /// follow-up text input won't be affected by the dead key. /// /// ## Platform-specific /// - **Web, macOS:** Does nothing // --------------------------- // Developers' Note: If this cannot be implemented on every desktop platform // at least, then this function should be provided through a platform specific // extension trait pub fn reset_dead_keys(&self) { let _span = tracing::debug_span!("winit::Window::reset_dead_keys",).entered(); self.window.maybe_queue_on_main(|w| w.reset_dead_keys()) } } /// Position and size functions. impl Window { /// Returns the position of the top-left hand corner of the window's client area relative to the /// top-left hand corner of the desktop. /// /// The same conditions that apply to [`Window::outer_position`] apply to this method. /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window's [safe area] in the screen space coordinate system. /// - **Web:** Returns the top-left coordinates relative to the viewport. _Note: this returns /// the same value as [`Window::outer_position`]._ /// - **Android / Wayland:** Always returns [`NotSupportedError`]. /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { let _span = tracing::debug_span!("winit::Window::inner_position",).entered(); self.window.maybe_wait_on_main(|w| w.inner_position()) } /// Returns the position of the top-left hand corner of the window relative to the /// top-left hand corner of the desktop. /// /// Note that the top-left hand corner of the desktop is not necessarily the same as /// the screen. If the user uses a desktop with multiple monitors, the top-left hand corner /// of the desktop is the top-left hand corner of the monitor at the top-left of the desktop. /// /// The coordinates can be negative if the top-left hand corner of the window is outside /// of the visible screen region. /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window in the screen space coordinate system. /// - **Web:** Returns the top-left coordinates relative to the viewport. /// - **Android / Wayland:** Always returns [`NotSupportedError`]. #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { let _span = tracing::debug_span!("winit::Window::outer_position",).entered(); self.window.maybe_wait_on_main(|w| w.outer_position()) } /// Modifies the position of the window. /// /// See [`Window::outer_position`] for more information about the coordinates. /// This automatically un-maximizes the window if it's maximized. /// /// ```no_run /// # use winit::dpi::{LogicalPosition, PhysicalPosition}; /// # use winit::window::Window; /// # fn scope(window: &Window) { /// // Specify the position in logical dimensions like this: /// window.set_outer_position(LogicalPosition::new(400.0, 200.0)); /// /// // Or specify the position in physical dimensions like this: /// window.set_outer_position(PhysicalPosition::new(400, 200)); /// # } /// ``` /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the /// window in the screen space coordinate system. /// - **Web:** Sets the top-left coordinates relative to the viewport. Doesn't account for CSS /// [`transform`]. /// - **Android / Wayland:** Unsupported. /// /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[inline] pub fn set_outer_position>(&self, position: P) { let position = position.into(); let _span = tracing::debug_span!( "winit::Window::set_outer_position", position = ?position ) .entered(); self.window.maybe_queue_on_main(move |w| w.set_outer_position(position)) } /// Returns the physical size of the window's client area. /// /// The client area is the content of the window, excluding the title bar and borders. /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window's /// [safe area] in screen space coordinates. /// - **Web:** Returns the size of the canvas element. Doesn't account for CSS [`transform`]. /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[inline] pub fn inner_size(&self) -> PhysicalSize { let _span = tracing::debug_span!("winit::Window::inner_size",).entered(); self.window.maybe_wait_on_main(|w| w.inner_size()) } /// Request the new size for the window. /// /// On platforms where the size is entirely controlled by the user the /// applied size will be returned immediately, resize event in such case /// may not be generated. /// /// On platforms where resizing is disallowed by the windowing system, the current /// inner size is returned immediately, and the user one is ignored. /// /// When `None` is returned, it means that the request went to the display system, /// and the actual size will be delivered later with the [`WindowEvent::Resized`]. /// /// See [`Window::inner_size`] for more information about the values. /// /// The request could automatically un-maximize the window if it's maximized. /// /// ```no_run /// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::window::Window; /// # fn scope(window: &Window) { /// // Specify the size in logical dimensions like this: /// let _ = window.request_inner_size(LogicalSize::new(400.0, 200.0)); /// /// // Or specify the size in physical dimensions like this: /// let _ = window.request_inner_size(PhysicalSize::new(400, 200)); /// # } /// ``` /// /// ## Platform-specific /// /// - **Web:** Sets the size of the canvas element. Doesn't account for CSS [`transform`]. /// /// [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[inline] #[must_use] pub fn request_inner_size>(&self, size: S) -> Option> { let size = size.into(); let _span = tracing::debug_span!( "winit::Window::request_inner_size", size = ?size ) .entered(); self.window.maybe_wait_on_main(|w| w.request_inner_size(size)) } /// Returns the physical size of the entire window. /// /// These dimensions include the title bar and borders. If you don't want that (and you usually /// don't), use [`Window::inner_size`] instead. /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. Returns the [`PhysicalSize`] of the window /// in screen space coordinates. /// - **Web:** Returns the size of the canvas element. _Note: this returns the same value as /// [`Window::inner_size`]._ #[inline] pub fn outer_size(&self) -> PhysicalSize { let _span = tracing::debug_span!("winit::Window::outer_size",).entered(); self.window.maybe_wait_on_main(|w| w.outer_size()) } /// Sets a minimum dimension size for the window. /// /// ```no_run /// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::window::Window; /// # fn scope(window: &Window) { /// // Specify the size in logical dimensions like this: /// window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0))); /// /// // Or specify the size in physical dimensions like this: /// window.set_min_inner_size(Some(PhysicalSize::new(400, 200))); /// # } /// ``` /// /// ## Platform-specific /// /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_min_inner_size>(&self, min_size: Option) { let min_size = min_size.map(|s| s.into()); let _span = tracing::debug_span!( "winit::Window::set_min_inner_size", min_size = ?min_size ) .entered(); self.window.maybe_queue_on_main(move |w| w.set_min_inner_size(min_size)) } /// Sets a maximum dimension size for the window. /// /// ```no_run /// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::window::Window; /// # fn scope(window: &Window) { /// // Specify the size in logical dimensions like this: /// window.set_max_inner_size(Some(LogicalSize::new(400.0, 200.0))); /// /// // Or specify the size in physical dimensions like this: /// window.set_max_inner_size(Some(PhysicalSize::new(400, 200))); /// # } /// ``` /// /// ## Platform-specific /// /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_max_inner_size>(&self, max_size: Option) { let max_size = max_size.map(|s| s.into()); let _span = tracing::debug_span!( "winit::Window::max_size", max_size = ?max_size ) .entered(); self.window.maybe_queue_on_main(move |w| w.set_max_inner_size(max_size)) } /// Returns window resize increments if any were set. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Wayland / Orbital:** Always returns [`None`]. #[inline] pub fn resize_increments(&self) -> Option> { let _span = tracing::debug_span!("winit::Window::resize_increments",).entered(); self.window.maybe_wait_on_main(|w| w.resize_increments()) } /// Sets window resize increments. /// /// This is a niche constraint hint usually employed by terminal emulators /// and other apps that need "blocky" resizes. /// /// ## Platform-specific /// /// - **macOS:** Increments are converted to logical size and then macOS rounds them to whole /// numbers. /// - **Wayland:** Not implemented. /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_resize_increments>(&self, increments: Option) { let increments = increments.map(Into::into); let _span = tracing::debug_span!( "winit::Window::set_resize_increments", increments = ?increments ) .entered(); self.window.maybe_queue_on_main(move |w| w.set_resize_increments(increments)) } } /// Misc. attribute functions. impl Window { /// Modifies the title of the window. /// /// ## Platform-specific /// /// - **iOS / Android:** Unsupported. #[inline] pub fn set_title(&self, title: &str) { let _span = tracing::debug_span!("winit::Window::set_title", title).entered(); self.window.maybe_wait_on_main(|w| w.set_title(title)) } /// Change the window transparency state. /// /// This is just a hint that may not change anything about /// the window transparency, however doing a mismatch between /// the content of your window and this hint may result in /// visual artifacts. /// /// The default value follows the [`WindowAttributes::with_transparent`]. /// /// ## Platform-specific /// /// - **macOS:** This will reset the window's background color. /// - **Web / iOS / Android:** Unsupported. /// - **X11:** Can only be set while building the window, with /// [`WindowAttributes::with_transparent`]. #[inline] pub fn set_transparent(&self, transparent: bool) { let _span = tracing::debug_span!("winit::Window::set_transparent", transparent).entered(); self.window.maybe_queue_on_main(move |w| w.set_transparent(transparent)) } /// Change the window blur state. /// /// If `true`, this will make the transparent window background blurry. /// /// ## Platform-specific /// /// - **Android / iOS / X11 / Web / Windows:** Unsupported. /// - **Wayland:** Only works with org_kde_kwin_blur_manager protocol. #[inline] pub fn set_blur(&self, blur: bool) { let _span = tracing::debug_span!("winit::Window::set_blur", blur).entered(); self.window.maybe_queue_on_main(move |w| w.set_blur(blur)) } /// Modifies the window's visibility. /// /// If `false`, this will hide the window. If `true`, this will show the window. /// /// ## Platform-specific /// /// - **Android / Wayland / Web:** Unsupported. /// - **iOS:** Can only be called on the main thread. #[inline] pub fn set_visible(&self, visible: bool) { let _span = tracing::debug_span!("winit::Window::set_visible", visible).entered(); self.window.maybe_queue_on_main(move |w| w.set_visible(visible)) } /// Gets the window's current visibility state. /// /// `None` means it couldn't be determined, so it is not recommended to use this to drive your /// rendering backend. /// /// ## Platform-specific /// /// - **X11:** Not implemented. /// - **Wayland / iOS / Android / Web:** Unsupported. #[inline] pub fn is_visible(&self) -> Option { let _span = tracing::debug_span!("winit::Window::is_visible",).entered(); self.window.maybe_wait_on_main(|w| w.is_visible()) } /// Sets whether the window is resizable or not. /// /// Note that making the window unresizable doesn't exempt you from handling /// [`WindowEvent::Resized`], as that event can still be triggered by DPI scaling, entering /// fullscreen mode, etc. Also, the window could still be resized by calling /// [`Window::request_inner_size`]. /// /// ## Platform-specific /// /// This only has an effect on desktop platforms. /// /// - **X11:** Due to a bug in XFCE, this has no effect on Xfwm. /// - **iOS / Android / Web:** Unsupported. /// /// [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized #[inline] pub fn set_resizable(&self, resizable: bool) { let _span = tracing::debug_span!("winit::Window::set_resizable", resizable).entered(); self.window.maybe_queue_on_main(move |w| w.set_resizable(resizable)) } /// Gets the window's current resizable state. /// /// ## Platform-specific /// /// - **X11:** Not implemented. /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn is_resizable(&self) -> bool { let _span = tracing::debug_span!("winit::Window::is_resizable",).entered(); self.window.maybe_wait_on_main(|w| w.is_resizable()) } /// Sets the enabled window buttons. /// /// ## Platform-specific /// /// - **Wayland / X11 / Orbital:** Not implemented. /// - **Web / iOS / Android:** Unsupported. pub fn set_enabled_buttons(&self, buttons: WindowButtons) { let _span = tracing::debug_span!( "winit::Window::set_enabled_buttons", buttons = ?buttons ) .entered(); self.window.maybe_queue_on_main(move |w| w.set_enabled_buttons(buttons)) } /// Gets the enabled window buttons. /// /// ## Platform-specific /// /// - **Wayland / X11 / Orbital:** Not implemented. Always returns [`WindowButtons::all`]. /// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`]. pub fn enabled_buttons(&self) -> WindowButtons { let _span = tracing::debug_span!("winit::Window::enabled_buttons",).entered(); self.window.maybe_wait_on_main(|w| w.enabled_buttons()) } /// Sets the window to minimized or back /// /// ## Platform-specific /// /// - **iOS / Android / Web / Orbital:** Unsupported. /// - **Wayland:** Un-minimize is unsupported. #[inline] pub fn set_minimized(&self, minimized: bool) { let _span = tracing::debug_span!("winit::Window::set_minimized", minimized).entered(); self.window.maybe_queue_on_main(move |w| w.set_minimized(minimized)) } /// Gets the window's current minimized state. /// /// `None` will be returned, if the minimized state couldn't be determined. /// /// ## Note /// /// - You shouldn't stop rendering for minimized windows, however you could lower the fps. /// /// ## Platform-specific /// /// - **Wayland**: always `None`. /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn is_minimized(&self) -> Option { let _span = tracing::debug_span!("winit::Window::is_minimized",).entered(); self.window.maybe_wait_on_main(|w| w.is_minimized()) } /// Sets the window to maximized or back. /// /// ## Platform-specific /// /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn set_maximized(&self, maximized: bool) { let _span = tracing::debug_span!("winit::Window::set_maximized", maximized).entered(); self.window.maybe_queue_on_main(move |w| w.set_maximized(maximized)) } /// Gets the window's current maximized state. /// /// ## Platform-specific /// /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn is_maximized(&self) -> bool { let _span = tracing::debug_span!("winit::Window::is_maximized",).entered(); self.window.maybe_wait_on_main(|w| w.is_maximized()) } /// Sets the window to fullscreen or back. /// /// ## Platform-specific /// /// - **macOS:** [`Fullscreen::Exclusive`] provides true exclusive mode with a video mode /// change. *Caveat!* macOS doesn't provide task switching (or spaces!) while in exclusive /// fullscreen mode. This mode should be used when a video mode change is desired, but for a /// better user experience, borderless fullscreen might be preferred. /// /// [`Fullscreen::Borderless`] provides a borderless fullscreen window on a /// separate space. This is the idiomatic way for fullscreen games to work /// on macOS. See `WindowExtMacOs::set_simple_fullscreen` if /// separate spaces are not preferred. /// /// The dock and the menu bar are disabled in exclusive fullscreen mode. /// - **iOS:** Can only be called on the main thread. /// - **Wayland:** Does not support exclusive fullscreen mode and will no-op a request. /// - **Windows:** Screen saver is disabled in fullscreen mode. /// - **Android / Orbital:** Unsupported. /// - **Web:** Does nothing without a [transient activation]. /// /// [transient activation]: https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { let _span = tracing::debug_span!( "winit::Window::set_fullscreen", fullscreen = ?fullscreen ) .entered(); self.window.maybe_queue_on_main(move |w| w.set_fullscreen(fullscreen.map(|f| f.into()))) } /// Gets the window's current fullscreen state. /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. /// - **Android / Orbital:** Will always return `None`. /// - **Wayland:** Can return `Borderless(None)` when there are no monitors. /// - **Web:** Can only return `None` or `Borderless(None)`. #[inline] pub fn fullscreen(&self) -> Option { let _span = tracing::debug_span!("winit::Window::fullscreen",).entered(); self.window.maybe_wait_on_main(|w| w.fullscreen().map(|f| f.into())) } /// Turn window decorations on or off. /// /// Enable/disable window decorations provided by the server or Winit. /// By default this is enabled. Note that fullscreen windows and windows on /// mobile and web platforms naturally do not have decorations. /// /// ## Platform-specific /// /// - **iOS / Android / Web:** No effect. #[inline] pub fn set_decorations(&self, decorations: bool) { let _span = tracing::debug_span!("winit::Window::set_decorations", decorations).entered(); self.window.maybe_queue_on_main(move |w| w.set_decorations(decorations)) } /// Gets the window's current decorations state. /// /// Returns `true` when windows are decorated (server-side or by Winit). /// Also returns `true` when no decorations are required (mobile, web). /// /// ## Platform-specific /// /// - **iOS / Android / Web:** Always returns `true`. #[inline] pub fn is_decorated(&self) -> bool { let _span = tracing::debug_span!("winit::Window::is_decorated",).entered(); self.window.maybe_wait_on_main(|w| w.is_decorated()) } /// Change the window level. /// /// This is just a hint to the OS, and the system could ignore it. /// /// See [`WindowLevel`] for details. pub fn set_window_level(&self, level: WindowLevel) { let _span = tracing::debug_span!( "winit::Window::set_window_level", level = ?level ) .entered(); self.window.maybe_queue_on_main(move |w| w.set_window_level(level)) } /// Sets the window icon. /// /// On Windows and X11, this is typically the small icon in the top-left /// corner of the titlebar. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Wayland / macOS / Orbital:** Unsupported. /// /// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. /// /// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. /// That said, it's usually in the same ballpark as on Windows. #[inline] pub fn set_window_icon(&self, window_icon: Option) { let _span = tracing::debug_span!("winit::Window::set_window_icon",).entered(); self.window.maybe_queue_on_main(move |w| w.set_window_icon(window_icon)) } /// Set the IME cursor editing area, where the `position` is the top left corner of that area /// and `size` is the size of this area starting from the position. An example of such area /// could be a input field in the UI or line in the editor. /// /// The windowing system could place a candidate box close to that area, but try to not obscure /// the specified area, so the user input to it stays visible. /// /// The candidate box is the window / popup / overlay that allows you to select the desired /// characters. The look of this box may differ between input devices, even on the same /// platform. /// /// (Apple's official term is "candidate window", see their [chinese] and [japanese] guides). /// /// ## Example /// /// ```no_run /// # use winit::dpi::{LogicalPosition, PhysicalPosition, LogicalSize, PhysicalSize}; /// # use winit::window::Window; /// # fn scope(window: &Window) { /// // Specify the position in logical dimensions like this: /// window.set_ime_cursor_area(LogicalPosition::new(400.0, 200.0), LogicalSize::new(100, 100)); /// /// // Or specify the position in physical dimensions like this: /// window.set_ime_cursor_area(PhysicalPosition::new(400, 200), PhysicalSize::new(100, 100)); /// # } /// ``` /// /// ## Platform-specific /// /// - **X11:** - area is not supported, only position. /// - **iOS / Android / Web / Orbital:** Unsupported. /// /// [chinese]: https://support.apple.com/guide/chinese-input-method/use-the-candidate-window-cim12992/104/mac/12.0 /// [japanese]: https://support.apple.com/guide/japanese-input-method/use-the-candidate-window-jpim10262/6.3/mac/12.0 #[inline] pub fn set_ime_cursor_area, S: Into>(&self, position: P, size: S) { let position = position.into(); let size = size.into(); let _span = tracing::debug_span!( "winit::Window::set_ime_cursor_area", position = ?position, size = ?size, ) .entered(); self.window.maybe_queue_on_main(move |w| w.set_ime_cursor_area(position, size)) } /// Sets whether the window should get IME events /// /// When IME is allowed, the window will receive [`Ime`] events, and during the /// preedit phase the window will NOT get [`KeyboardInput`] events. The window /// should allow IME while it is expecting text input. /// /// When IME is not allowed, the window won't receive [`Ime`] events, and will /// receive [`KeyboardInput`] events for every keypress instead. Not allowing /// IME is useful for games for example. /// /// IME is **not** allowed by default. /// /// ## Platform-specific /// /// - **macOS:** IME must be enabled to receive text-input where dead-key sequences are /// combined. /// - **iOS / Android:** This will show / hide the soft keyboard. /// - **Web / Orbital:** Unsupported. /// - **X11**: Enabling IME will disable dead keys reporting during compose. /// /// [`Ime`]: crate::event::WindowEvent::Ime /// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput #[inline] pub fn set_ime_allowed(&self, allowed: bool) { let _span = tracing::debug_span!("winit::Window::set_ime_allowed", allowed).entered(); self.window.maybe_queue_on_main(move |w| w.set_ime_allowed(allowed)) } /// Sets the IME purpose for the window using [`ImePurpose`]. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported. #[inline] pub fn set_ime_purpose(&self, purpose: ImePurpose) { let _span = tracing::debug_span!( "winit::Window::set_ime_purpose", purpose = ?purpose ) .entered(); self.window.maybe_queue_on_main(move |w| w.set_ime_purpose(purpose)) } /// Brings the window to the front and sets input focus. Has no effect if the window is /// already in focus, minimized, or not visible. /// /// This method steals input focus from other applications. Do not use this method unless /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive /// user experience. /// /// ## Platform-specific /// /// - **iOS / Android / Wayland / Orbital:** Unsupported. #[inline] pub fn focus_window(&self) { let _span = tracing::debug_span!("winit::Window::focus_window",).entered(); self.window.maybe_queue_on_main(|w| w.focus_window()) } /// Gets whether the window has keyboard focus. /// /// This queries the same state information as [`WindowEvent::Focused`]. /// /// [`WindowEvent::Focused`]: crate::event::WindowEvent::Focused #[inline] pub fn has_focus(&self) -> bool { let _span = tracing::debug_span!("winit::Window::has_focus",).entered(); self.window.maybe_wait_on_main(|w| w.has_focus()) } /// Requests user attention to the window, this has no effect if the application /// is already focused. How requesting for user attention manifests is platform dependent, /// see [`UserAttentionType`] for details. /// /// Providing `None` will unset the request for user attention. Unsetting the request for /// user attention might not be done automatically by the WM when the window receives input. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Orbital:** Unsupported. /// - **macOS:** `None` has no effect. /// - **X11:** Requests for user attention must be manually cleared. /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. #[inline] pub fn request_user_attention(&self, request_type: Option) { let _span = tracing::debug_span!( "winit::Window::request_user_attention", request_type = ?request_type ) .entered(); self.window.maybe_queue_on_main(move |w| w.request_user_attention(request_type)) } /// Set or override the window theme. /// /// Specify `None` to reset the theme to the system default. /// /// ## Platform-specific /// /// - **Wayland:** Sets the theme for the client side decorations. Using `None` will use dbus to /// get the system preference. /// - **X11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used, it /// will default to [`Theme::Dark`]. /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_theme(&self, theme: Option) { let _span = tracing::debug_span!( "winit::Window::set_theme", theme = ?theme ) .entered(); self.window.maybe_queue_on_main(move |w| w.set_theme(theme)) } /// Returns the current window theme. /// /// Returns `None` if it cannot be determined on the current platform. /// /// ## Platform-specific /// /// - **iOS / Android / x11 / Orbital:** Unsupported. /// - **Wayland:** Only returns theme overrides. #[inline] pub fn theme(&self) -> Option { let _span = tracing::debug_span!("winit::Window::theme",).entered(); self.window.maybe_wait_on_main(|w| w.theme()) } /// Prevents the window contents from being captured by other apps. /// /// ## Platform-specific /// /// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely prevent all /// apps from reading the window content, for instance, QuickTime. /// - **iOS / Android / x11 / Wayland / Web / Orbital:** Unsupported. /// /// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone pub fn set_content_protected(&self, protected: bool) { let _span = tracing::debug_span!("winit::Window::set_content_protected", protected).entered(); self.window.maybe_queue_on_main(move |w| w.set_content_protected(protected)) } /// Gets the current title of the window. /// /// ## Platform-specific /// /// - **iOS / Android / x11 / Wayland / Web:** Unsupported. Always returns an empty string. #[inline] pub fn title(&self) -> String { let _span = tracing::debug_span!("winit::Window::title",).entered(); self.window.maybe_wait_on_main(|w| w.title()) } } /// Cursor functions. impl Window { /// Modifies the cursor icon of the window. /// /// ## Platform-specific /// /// - **iOS / Android / Orbital:** Unsupported. /// - **Web:** Custom cursors have to be loaded and decoded first, until then the previous /// cursor is shown. #[inline] pub fn set_cursor(&self, cursor: impl Into) { let cursor = cursor.into(); let _span = tracing::debug_span!("winit::Window::set_cursor",).entered(); self.window.maybe_queue_on_main(move |w| w.set_cursor(cursor)) } /// Deprecated! Use [`Window::set_cursor()`] instead. #[deprecated = "Renamed to `set_cursor`"] #[inline] pub fn set_cursor_icon(&self, icon: CursorIcon) { self.set_cursor(icon) } /// Changes the position of the cursor in window coordinates. /// /// ```no_run /// # use winit::dpi::{LogicalPosition, PhysicalPosition}; /// # use winit::window::Window; /// # fn scope(window: &Window) { /// // Specify the position in logical dimensions like this: /// window.set_cursor_position(LogicalPosition::new(400.0, 200.0)); /// /// // Or specify the position in physical dimensions like this: /// window.set_cursor_position(PhysicalPosition::new(400, 200)); /// # } /// ``` /// /// ## Platform-specific /// /// - **Wayland**: Cursor must be in [`CursorGrabMode::Locked`]. /// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { let position = position.into(); let _span = tracing::debug_span!( "winit::Window::set_cursor_position", position = ?position ) .entered(); self.window.maybe_wait_on_main(|w| w.set_cursor_position(position)) } /// Set grabbing [mode][CursorGrabMode] on the cursor preventing it from leaving the window. /// /// # Example /// /// First try confining the cursor, and if that fails, try locking it instead. /// /// ```no_run /// # use winit::window::{CursorGrabMode, Window}; /// # fn scope(window: &Window) { /// window /// .set_cursor_grab(CursorGrabMode::Confined) /// .or_else(|_e| window.set_cursor_grab(CursorGrabMode::Locked)) /// .unwrap(); /// # } /// ``` #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { let _span = tracing::debug_span!( "winit::Window::set_cursor_grab", mode = ?mode ) .entered(); self.window.maybe_wait_on_main(|w| w.set_cursor_grab(mode)) } /// Modifies the cursor's visibility. /// /// If `false`, this will hide the cursor. If `true`, this will show the cursor. /// /// ## Platform-specific /// /// - **Windows:** The cursor is only hidden within the confines of the window. /// - **X11:** The cursor is only hidden within the confines of the window. /// - **Wayland:** The cursor is only hidden within the confines of the window. /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor /// is outside of the window. /// - **iOS / Android:** Unsupported. #[inline] pub fn set_cursor_visible(&self, visible: bool) { let _span = tracing::debug_span!("winit::Window::set_cursor_visible", visible).entered(); self.window.maybe_queue_on_main(move |w| w.set_cursor_visible(visible)) } /// Moves the window with the left mouse button until the button is released. /// /// There's no guarantee that this will work unless the left mouse button was pressed /// immediately before this function is called. /// /// ## Platform-specific /// /// - **X11:** Un-grabs the cursor. /// - **Wayland:** Requires the cursor to be inside the window to be dragged. /// - **macOS:** May prevent the button release event to be triggered. /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { let _span = tracing::debug_span!("winit::Window::drag_window",).entered(); self.window.maybe_wait_on_main(|w| w.drag_window()) } /// Resizes the window with the left mouse button until the button is released. /// /// There's no guarantee that this will work unless the left mouse button was pressed /// immediately before this function is called. /// /// ## Platform-specific /// /// - **macOS:** Always returns an [`ExternalError::NotSupported`] /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { let _span = tracing::debug_span!( "winit::Window::drag_resize_window", direction = ?direction ) .entered(); self.window.maybe_wait_on_main(|w| w.drag_resize_window(direction)) } /// Show [window menu] at a specified position . /// /// This is the context menu that is normally shown when interacting with /// the title bar. This is useful when implementing custom decorations. /// /// ## Platform-specific /// **Android / iOS / macOS / Orbital / Wayland / Web / X11:** Unsupported. /// /// [window menu]: https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu pub fn show_window_menu(&self, position: impl Into) { let position = position.into(); let _span = tracing::debug_span!( "winit::Window::show_window_menu", position = ?position ) .entered(); self.window.maybe_queue_on_main(move |w| w.show_window_menu(position)) } /// Modifies whether the window catches cursor events. /// /// If `true`, the window will catch the cursor events. If `false`, events are passed through /// the window such that any other window behind it receives them. By default hittest is /// enabled. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { let _span = tracing::debug_span!("winit::Window::set_cursor_hittest", hittest).entered(); self.window.maybe_wait_on_main(|w| w.set_cursor_hittest(hittest)) } } /// Monitor info functions. impl Window { /// Returns the monitor on which the window currently resides. /// /// Returns `None` if current monitor can't be detected. #[inline] pub fn current_monitor(&self) -> Option { let _span = tracing::debug_span!("winit::Window::current_monitor",).entered(); self.window.maybe_wait_on_main(|w| w.current_monitor().map(|inner| MonitorHandle { inner })) } /// Returns the list of all the monitors available on the system. /// /// This is the same as [`ActiveEventLoop::available_monitors`], and is provided for /// convenience. /// /// [`ActiveEventLoop::available_monitors`]: crate::event_loop::ActiveEventLoop::available_monitors #[inline] pub fn available_monitors(&self) -> impl Iterator { let _span = tracing::debug_span!("winit::Window::available_monitors",).entered(); self.window.maybe_wait_on_main(|w| { w.available_monitors().into_iter().map(|inner| MonitorHandle { inner }) }) } /// Returns the primary monitor of the system. /// /// Returns `None` if it can't identify any monitor as a primary one. /// /// This is the same as [`ActiveEventLoop::primary_monitor`], and is provided for convenience. /// /// ## Platform-specific /// /// **Wayland / Web:** Always returns `None`. /// /// [`ActiveEventLoop::primary_monitor`]: crate::event_loop::ActiveEventLoop::primary_monitor #[inline] pub fn primary_monitor(&self) -> Option { let _span = tracing::debug_span!("winit::Window::primary_monitor",).entered(); self.window.maybe_wait_on_main(|w| w.primary_monitor().map(|inner| MonitorHandle { inner })) } } #[cfg(feature = "rwh_06")] impl rwh_06::HasWindowHandle for Window { fn window_handle(&self) -> Result, rwh_06::HandleError> { let raw = self.window.raw_window_handle_rwh_06()?; // SAFETY: The window handle will never be deallocated while the window is alive, // and the main thread safety requirements are upheld internally by each platform. Ok(unsafe { rwh_06::WindowHandle::borrow_raw(raw) }) } } #[cfg(feature = "rwh_06")] impl rwh_06::HasDisplayHandle for Window { fn display_handle(&self) -> Result, rwh_06::HandleError> { let raw = self.window.raw_display_handle_rwh_06()?; // SAFETY: The window handle will never be deallocated while the window is alive, // and the main thread safety requirements are upheld internally by each platform. Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) }) } } /// Wrapper to make objects `Send`. /// /// # Safety /// /// This is not safe! This is only used for `RawWindowHandle`, which only has unsafe getters. #[cfg(any(feature = "rwh_05", feature = "rwh_04"))] struct UnsafeSendWrapper(T); #[cfg(any(feature = "rwh_05", feature = "rwh_04"))] unsafe impl Send for UnsafeSendWrapper {} #[cfg(feature = "rwh_05")] unsafe impl rwh_05::HasRawWindowHandle for Window { fn raw_window_handle(&self) -> rwh_05::RawWindowHandle { self.window.maybe_wait_on_main(|w| UnsafeSendWrapper(w.raw_window_handle_rwh_05())).0 } } #[cfg(feature = "rwh_05")] unsafe impl rwh_05::HasRawDisplayHandle for Window { /// Returns a [`rwh_05::RawDisplayHandle`] used by the [`EventLoop`] that /// created a window. /// /// [`EventLoop`]: crate::event_loop::EventLoop fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { self.window.maybe_wait_on_main(|w| UnsafeSendWrapper(w.raw_display_handle_rwh_05())).0 } } #[cfg(feature = "rwh_04")] unsafe impl rwh_04::HasRawWindowHandle for Window { fn raw_window_handle(&self) -> rwh_04::RawWindowHandle { self.window.maybe_wait_on_main(|w| UnsafeSendWrapper(w.raw_window_handle_rwh_04())).0 } } /// The behavior of cursor grabbing. /// /// Use this enum with [`Window::set_cursor_grab`] to grab the cursor. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum CursorGrabMode { /// No grabbing of the cursor is performed. None, /// The cursor is confined to the window area. /// /// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you /// want to do so. /// /// ## Platform-specific /// /// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. Confined, /// The cursor is locked inside the window area to the certain position. /// /// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you /// want to do so. /// /// ## Platform-specific /// /// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for /// now. /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. Locked, } /// Defines the orientation that a window resize will be performed. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum ResizeDirection { East, North, NorthEast, NorthWest, South, SouthEast, SouthWest, West, } impl From for CursorIcon { fn from(direction: ResizeDirection) -> Self { use ResizeDirection::*; match direction { East => CursorIcon::EResize, North => CursorIcon::NResize, NorthEast => CursorIcon::NeResize, NorthWest => CursorIcon::NwResize, South => CursorIcon::SResize, SouthEast => CursorIcon::SeResize, SouthWest => CursorIcon::SwResize, West => CursorIcon::WResize, } } } /// Fullscreen modes. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Fullscreen { Exclusive(VideoModeHandle), /// Providing `None` to `Borderless` will fullscreen on the current monitor. Borderless(Option), } /// The theme variant to use. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Theme { /// Use the light variant. Light, /// Use the dark variant. Dark, } /// ## Platform-specific /// /// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and /// [`Informational`]. /// /// [`Critical`]: Self::Critical /// [`Informational`]: Self::Informational #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum UserAttentionType { /// ## Platform-specific /// /// - **macOS:** Bounces the dock icon until the application is in focus. /// - **Windows:** Flashes both the window and the taskbar button until the application is in /// focus. Critical, /// ## Platform-specific /// /// - **macOS:** Bounces the dock icon once. /// - **Windows:** Flashes the taskbar button until the application is in focus. #[default] Informational, } bitflags::bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct WindowButtons: u32 { const CLOSE = 1 << 0; const MINIMIZE = 1 << 1; const MAXIMIZE = 1 << 2; } } /// A window level groups windows with respect to their z-position. /// /// The relative ordering between windows in different window levels is fixed. /// The z-order of a window within the same window level may change dynamically on user interaction. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Wayland:** Unsupported. #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] pub enum WindowLevel { /// The window will always be below normal windows. /// /// This is useful for a widget-based app. AlwaysOnBottom, /// The default. #[default] Normal, /// The window will always be on top of normal windows. AlwaysOnTop, } /// Generic IME purposes for use in [`Window::set_ime_purpose`]. /// /// The purpose may improve UX by optimizing the IME for the specific use case, /// if winit can express the purpose to the platform and the platform reacts accordingly. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[non_exhaustive] pub enum ImePurpose { /// No special hints for the IME (default). Normal, /// The IME is used for password input. Password, /// The IME is used to input into a terminal. /// /// For example, that could alter OSK on Wayland to show extra buttons. Terminal, } impl Default for ImePurpose { fn default() -> Self { Self::Normal } } /// An opaque token used to activate the [`Window`]. /// /// [`Window`]: crate::window::Window #[derive(Debug, PartialEq, Eq, Clone)] pub struct ActivationToken { pub(crate) token: String, } impl ActivationToken { /// Make an [`ActivationToken`] from a string. /// /// This method should be used to wrap tokens passed by side channels to your application, like /// dbus. /// /// The validity of the token is ensured by the windowing system. Using the invalid token will /// only result in the side effect of the operation involving it being ignored (e.g. window /// won't get focused automatically), but won't yield any errors. /// /// To obtain a valid token, use #[cfg_attr(any(x11_platform, wayland_platform, docsrs), doc = " [`request_activation_token`].")] #[cfg_attr( not(any(x11_platform, wayland_platform, docsrs)), doc = " `request_activation_token`." )] /// #[rustfmt::skip] /// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token pub fn from_raw(token: String) -> Self { Self { token } } /// Convert the token to its string representation to later pass via IPC. pub fn into_raw(self) -> String { self.token } } winit-0.30.9/tests/send_objects.rs000064400000000000000000000015611046102023000152240ustar 00000000000000#[allow(dead_code)] fn needs_send() {} #[test] fn event_loop_proxy_send() { #[allow(dead_code)] fn is_send() { // ensures that `winit::EventLoopProxy` implements `Send` needs_send::>(); } } #[test] fn window_send() { // ensures that `winit::Window` implements `Send` needs_send::(); } #[test] fn window_builder_send() { needs_send::(); } #[test] fn ids_send() { // ensures that the various `..Id` types implement `Send` needs_send::(); needs_send::(); needs_send::(); } #[test] fn custom_cursor_send() { needs_send::(); needs_send::(); } winit-0.30.9/tests/serde_objects.rs000064400000000000000000000020761046102023000153770ustar 00000000000000#![cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase}; use winit::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey, PhysicalKey}; use winit::window::CursorIcon; #[allow(dead_code)] fn needs_serde>() {} #[test] fn window_serde() { needs_serde::(); } #[test] fn events_serde() { needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); } #[test] fn dpi_serde() { needs_serde::>(); needs_serde::>(); needs_serde::>(); needs_serde::>(); needs_serde::>(); } winit-0.30.9/tests/sync_object.rs000064400000000000000000000012141046102023000150570ustar 00000000000000#[allow(dead_code)] fn needs_sync() {} #[test] fn event_loop_proxy_send() { #[allow(dead_code)] fn is_send() { // ensures that `winit::EventLoopProxy` implements `Sync` needs_sync::>(); } } #[test] fn window_sync() { // ensures that `winit::Window` implements `Sync` needs_sync::(); } #[test] fn window_builder_sync() { needs_sync::(); } #[test] fn custom_cursor_sync() { needs_sync::(); needs_sync::(); }