diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e04719d..8aec590 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -23,6 +23,6 @@ jobs: - name: Run rustfmt run: cargo fmt --all -- --check - name: Run Clippy - run: cargo clippy --all-targets --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W clippy::dbg_macro -A clippy::missing_errors_doc + run: cargo clippy --all-targets --all-features -- -D warnings - name: Run tests run: cargo test --all --all-features diff --git a/.zed/tasks.json b/.zed/tasks.json index b568184..69b667d 100644 --- a/.zed/tasks.json +++ b/.zed/tasks.json @@ -7,13 +7,13 @@ "command": "cargo", "args": ["run"], "env": { - "RUST_BACKTRACE": "1", - "RUST_LOG": "TRACE" - } + // "RUST_BACKTRACE": "1", + "RUST_LOG": "TRACE", + }, }, { "label": "run c", "command": "cargo", - "args": ["check"] - } + "args": ["check"], + }, ] diff --git a/Cargo.lock b/Cargo.lock index cfd796d..bb90b88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,35 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "ab_glyph" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.4", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "android-activity" version = "0.6.0" @@ -75,9 +46,12 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] [[package]] name = "arrayref" @@ -92,10 +66,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" +name = "ash" +version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] [[package]] name = "atomic-waker" @@ -109,6 +86,30 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -136,14 +137,14 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2", + "objc2 0.5.2", ] [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytemuck" @@ -167,9 +168,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "calloop" @@ -185,23 +186,11 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - [[package]] name = "cc" -version = "1.2.45" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -227,20 +216,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "channel-protocol" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cea07cc94d18a7c1a6ad0b27b57e005ffa768d0b45f96e94964dfdfe84edb68" -dependencies = [ - "convert_case", - "itertools", - "oneshot", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "chrono" version = "0.4.42" @@ -252,6 +227,26 @@ dependencies = [ "windows-link", ] +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "clipboard_macos" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" +dependencies = [ + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation 0.2.2", +] + [[package]] name = "cocoa" version = "0.22.0" @@ -267,6 +262,17 @@ dependencies = [ "objc", ] +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "combine" version = "4.6.7" @@ -286,15 +292,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "convert_case" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.7.0" @@ -374,19 +371,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.10.1", - "core-graphics-types 0.2.0", - "foreign-types 0.5.0", - "libc", -] - [[package]] name = "core-graphics-types" version = "0.1.3" @@ -409,6 +393,38 @@ dependencies = [ "libc", ] +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "cosmic-text" +version = "0.15.0" +source = "git+https://github.com/pop-os/cosmic-text.git?rev=a07a6190548c8e40a55f6b7761387047ff1bf6ff#a07a6190548c8e40a55f6b7761387047ff1bf6ff" +dependencies = [ + "bitflags 2.10.0", + "fontdb", + "harfrust", + "linebender_resource_handle", + "log", + "rangemap", + "rustc-hash 1.1.0", + "self_cell", + "skrifa", + "smol_str", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -416,10 +432,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "ctor-lite" -version = "0.1.0" +name = "crunchy" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "cryoglyph" +version = "0.1.0" +source = "git+https://github.com/iced-rs/cryoglyph.git?rev=89883bcf38b5bed0d7bade788ef738d9facc857c#89883bcf38b5bed0d7bade788ef738d9facc857c" +dependencies = [ + "cosmic-text", + "etagere", + "lru", + "rustc-hash 2.1.1", + "wgpu", +] [[package]] name = "cursor-icon" @@ -429,21 +457,22 @@ checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "proc-macro2", "quote", + "rustc_version", "syn", "unicode-xid", ] @@ -482,65 +511,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] -name = "dlib" -version = "0.5.2" +name = "dispatch2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "libloading", + "bitflags 2.10.0", + "objc2 0.6.3", ] [[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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" - -[[package]] -name = "drm" -version = "0.12.0" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "bitflags 2.10.0", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "rustix 0.38.44", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "drm-ffi" -version = "0.8.0" +name = "dlib" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "drm-sys", - "rustix 0.38.44", + "libloading", ] [[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" - -[[package]] -name = "drm-sys" -version = "0.7.0" +name = "document-features" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ - "libc", - "linux-raw-sys 0.6.5", + "litrs", ] +[[package]] +name = "dpi" +version = "0.1.1" +source = "git+https://github.com/iced-rs/winit.git?rev=05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed#05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed" + [[package]] name = "easy-ext" version = "1.0.2" @@ -579,6 +592,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + [[package]] name = "evdev-rs" version = "0.4.0" @@ -603,16 +641,16 @@ dependencies = [ ] [[package]] -name = "fastrand" -version = "2.3.0" +name = "find-msvc-tools" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] -name = "find-msvc-tools" -version = "0.1.4" +name = "float_next_after" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" [[package]] name = "fnv" @@ -620,6 +658,50 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -663,99 +745,634 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] -name = "gethostname" -version = "1.1.0" +name = "form_urlencoded" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ - "rustix 1.1.2", - "windows-link", + "percent-encoding", ] [[package]] -name = "getrandom" -version = "0.2.16" +name = "futures" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ - "cfg-if", - "libc", - "wasi", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "getrandom" -version = "0.3.4" +name = "futures-channel" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", + "futures-core", + "futures-sink", ] [[package]] -name = "hashbrown" -version = "0.16.0" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "hermit-abi" -version = "0.5.2" +name = "futures-executor" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] -name = "humantime" -version = "2.3.0" +name = "futures-io" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] -name = "iana-time-zone" -version = "0.1.64" +name = "futures-macro" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "android_system_properties", - "core-foundation-sys 0.8.7", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "futures-sink" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] -name = "indexmap" -version = "2.12.0" +name = "futures-task" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" -dependencies = [ - "equivalent", - "hashbrown", -] +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] -name = "inotify" -version = "0.8.3" +name = "futures-util" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46dd0a94b393c730779ccfd2a872b67b1eb67be3fc33082e733bdb38b5fde4d4" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "bitflags 1.3.2", - "inotify-sys", - "libc", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.10.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows 0.58.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.10.0", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "harfrust" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c020db12c71d8a12a3fe7607873cade3a01a6287e29d540c8723276221b9d8" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "core_maths", + "read-fonts", + "smallvec", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys 0.8.7", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "iced" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "iced_core", + "iced_debug", + "iced_devtools", + "iced_futures", + "iced_renderer", + "iced_runtime", + "iced_widget", + "iced_winit", + "thiserror 2.0.17", +] + +[[package]] +name = "iced_beacon" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "bincode", + "futures", + "iced_core", + "log", + "semver", + "serde", + "thiserror 2.0.17", + "tokio", +] + +[[package]] +name = "iced_core" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "glam", + "lilt", + "log", + "num-traits", + "rustc-hash 2.1.1", + "serde", + "smol_str", + "thiserror 2.0.17", + "web-time", +] + +[[package]] +name = "iced_debug" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "iced_beacon", + "iced_core", + "iced_futures", + "log", +] + +[[package]] +name = "iced_devtools" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "iced_debug", + "iced_program", + "iced_widget", + "log", +] + +[[package]] +name = "iced_futures" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "futures", + "iced_core", + "log", + "rustc-hash 2.1.1", + "tokio", + "wasm-bindgen-futures", + "wasmtimer", +] + +[[package]] +name = "iced_graphics" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "cosmic-text", + "half", + "iced_core", + "iced_futures", + "log", + "lyon_path", + "raw-window-handle", + "rustc-hash 2.1.1", + "thiserror 2.0.17", + "unicode-segmentation", +] + +[[package]] +name = "iced_program" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "iced_graphics", + "iced_runtime", +] + +[[package]] +name = "iced_renderer" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "iced_graphics", + "iced_tiny_skia", + "iced_wgpu", + "log", + "thiserror 2.0.17", +] + +[[package]] +name = "iced_runtime" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "bytes", + "iced_core", + "iced_futures", + "raw-window-handle", + "thiserror 2.0.17", +] + +[[package]] +name = "iced_tiny_skia" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "bytemuck", + "cosmic-text", + "iced_debug", + "iced_graphics", + "kurbo", + "log", + "rustc-hash 2.1.1", + "softbuffer", + "tiny-skia", +] + +[[package]] +name = "iced_wgpu" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "cryoglyph", + "futures", + "glam", + "guillotiere", + "iced_debug", + "iced_graphics", + "log", + "lyon", + "rustc-hash 2.1.1", + "thiserror 2.0.17", + "wgpu", +] + +[[package]] +name = "iced_widget" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "iced_renderer", + "log", + "num-traits", + "rustc-hash 2.1.1", + "thiserror 2.0.17", + "unicode-segmentation", +] + +[[package]] +name = "iced_winit" +version = "0.15.0-dev" +source = "git+https://github.com/sub07/iced.git?branch=custom-winri#2e7e5c59013e643ab4a257a41d615c97174047f7" +dependencies = [ + "iced_debug", + "iced_program", + "log", + "rustc-hash 2.1.1", + "thiserror 2.0.17", + "tracing", + "wasm-bindgen-futures", + "web-sys", + "window_clipboard", + "winit", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "inotify" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46dd0a94b393c730779ccfd2a872b67b1eb67be3fc33082e733bdb38b5fde4d4" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", ] [[package]] @@ -778,9 +1395,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jni" @@ -831,9 +1448,9 @@ source = "git+https://github.com/sub07/rust-utils#0511d29b84705811e51d249a332749 [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -849,6 +1466,33 @@ dependencies = [ "serde", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -857,9 +1501,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libloading" @@ -871,28 +1515,43 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.5.18", + "redox_syscall 0.6.0", ] [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "lilt" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "f67562e5eff6b20553fa9be1c503356768420994e28f67e3eafe6f41910e57ad" +dependencies = [ + "web-time", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" [[package]] name = "linux-raw-sys" -version = "0.6.5" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" @@ -900,6 +1559,18 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -911,11 +1582,11 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -953,6 +1624,64 @@ dependencies = [ "winapi", ] +[[package]] +name = "lru" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" + +[[package]] +name = "lyon" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcb7d54d54c8937364c9d41902d066656817dce1e03a44e5533afebd1ef4352" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c0829e28c4f336396f250d850c3987e16ce6db057ffe047ce0dd54aab6b647" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e260b6de923e6e47adfedf6243013a7a874684165a6a277594ee3906021b2343" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aeca86bcfd632a15984ba029b539ffb811e0a70bf55e814ef8b0f54f506fdeb" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f586142e1280335b1bc89539f7c97dd80f08fc43e9ab1b74ef0a42b04aa353" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -977,12 +1706,64 @@ dependencies = [ "libc", ] +[[package]] +name = "metal" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" +dependencies = [ + "bitflags 2.10.0", + "block", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + [[package]] name = "mock_instant" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" +[[package]] +name = "naga" +version = "27.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.17", + "unicode-ident", +] + [[package]] name = "ndk" version = "0.9.0" @@ -1020,6 +1801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1069,6 +1851,15 @@ dependencies = [ "objc2-encode", ] +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + [[package]] name = "objc2-app-kit" version = "0.2.2" @@ -1078,11 +1869,11 @@ dependencies = [ "bitflags 2.10.0", "block2", "libc", - "objc2", + "objc2 0.5.2", "objc2-core-data", "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", ] [[package]] @@ -1093,9 +1884,9 @@ checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.10.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -1105,8 +1896,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -1117,8 +1908,32 @@ checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.10.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2 0.6.3", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] @@ -1128,8 +1943,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -1140,9 +1955,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ "block2", - "objc2", + "objc2 0.5.2", "objc2-contacts", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -1161,7 +1976,29 @@ dependencies = [ "block2", "dispatch", "libc", - "objc2", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", ] [[package]] @@ -1171,9 +2008,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2", - "objc2", + "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -1184,8 +2021,8 @@ checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.10.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -1196,19 +2033,31 @@ checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.10.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-symbols" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -1219,14 +2068,14 @@ checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.10.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-cloud-kit", "objc2-core-data", "objc2-core-image", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-link-presentation", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", "objc2-symbols", "objc2-uniform-type-identifiers", "objc2-user-notifications", @@ -1239,8 +2088,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -1251,9 +2100,9 @@ checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.10.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -1262,12 +2111,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "oneshot" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea" - [[package]] name = "option-ext" version = "0.2.0" @@ -1293,12 +2136,12 @@ dependencies = [ ] [[package]] -name = "owned_ttf_parser" -version = "0.25.1" +name = "ordered-float" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" dependencies = [ - "ttf-parser", + "num-traits", ] [[package]] @@ -1324,6 +2167,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1356,6 +2205,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.32" @@ -1372,10 +2227,34 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] +[[package]] +name = "portable-atomic" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1385,6 +2264,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -1404,13 +2289,10 @@ dependencies = [ ] [[package]] -name = "quick-xml" -version = "0.37.5" +name = "profiling" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" -dependencies = [ - "memchr", -] +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" [[package]] name = "quote" @@ -1456,6 +2338,18 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1481,6 +2375,17 @@ dependencies = [ "x11", ] +[[package]] +name = "read-fonts" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +dependencies = [ + "bytemuck", + "core_maths", + "font-types", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1499,6 +2404,15 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_syscall" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -1510,6 +2424,39 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -1525,9 +2472,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -1544,9 +2491,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "same-file" @@ -1557,12 +2504,6 @@ 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 = "scopeguard" version = "1.2.0" @@ -1570,16 +2511,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sctk-adwaita" -version = "0.10.1" +name = "self_cell" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ - "ab_glyph", - "log", - "memmap2", - "smithay-client-toolkit", - "tiny-skia", + "serde", + "serde_core", ] [[package]] @@ -1598,7 +2542,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ - "ordered-float", + "ordered-float 2.10.1", "serde", ] @@ -1624,9 +2568,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" dependencies = [ "itoa", "memchr", @@ -1654,12 +2598,31 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "skrifa" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" +dependencies = [ + "bytemuck", + "read-fonts", +] + [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -1667,88 +2630,130 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "smithay-client-toolkit" -version = "0.19.2" +name = "smol_str" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" dependencies = [ - "bitflags 2.10.0", - "calloop", - "calloop-wayland-source", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", + "serde", ] [[package]] -name = "smol_str" -version = "0.2.2" +name = "socket2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ - "serde", + "libc", + "windows-sys 0.60.2", ] [[package]] name = "softbuffer" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" dependencies = [ - "as-raw-xcb-connection", "bytemuck", - "cfg_aliases", - "core-graphics 0.24.0", - "drm", - "fastrand", - "foreign-types 0.5.0", "js-sys", - "log", - "memmap2", - "objc2", - "objc2-foundation", - "objc2-quartz-core", + "ndk", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", "raw-window-handle", "redox_syscall 0.5.18", - "rustix 0.38.44", - "tiny-xlib", + "tracing", "wasm-bindgen", - "wayland-backend", - "wayland-client", - "wayland-sys", "web-sys", - "windows-sys 0.59.0", - "x11rb", + "windows-sys 0.61.2", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.10.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "swash" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47846491253e976bdd07d0f9cc24b7daf24720d11309302ccbbc6e6b6e53550a" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + [[package]] name = "syn" -version = "2.0.110" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1825,32 +2830,70 @@ dependencies = [ ] [[package]] -name = "tiny-xlib" -version = "0.2.4" +name = "tinystr" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ - "as-raw-xcb-connection", - "ctor-lite", - "libloading", - "pkg-config", - "tracing", + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", @@ -1860,34 +2903,52 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] [[package]] name = "ttf-parser" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] [[package]] name = "typemap-ors" @@ -1898,18 +2959,42 @@ dependencies = [ "unsafe-any-ors", ] +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -1931,6 +3016,24 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "version_check" version = "0.9.5" @@ -1964,9 +3067,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -1977,9 +3080,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -1990,9 +3093,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2000,9 +3103,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", @@ -2013,140 +3116,212 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] -name = "wayland-backend" -version = "0.3.11" +name = "wasmtimer" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" dependencies = [ - "cc", - "downcast-rs", - "rustix 1.1.2", - "scoped-tls", - "smallvec", - "wayland-sys", + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", ] [[package]] -name = "wayland-client" -version = "0.31.11" +name = "web-sys" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ - "bitflags 2.10.0", - "rustix 1.1.2", - "wayland-backend", - "wayland-scanner", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "wayland-csd-frame" -version = "0.3.0" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "bitflags 2.10.0", - "cursor-icon", - "wayland-backend", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "wayland-cursor" -version = "0.31.11" +name = "webbrowser" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" +checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" dependencies = [ - "rustix 1.1.2", - "wayland-client", - "xcursor", + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "url", + "web-sys", ] [[package]] -name = "wayland-protocols" -version = "0.32.9" +name = "wgpu" +version = "27.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" dependencies = [ + "arrayvec", "bitflags 2.10.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", ] [[package]] -name = "wayland-protocols-plasma" -version = "0.3.9" +name = "wgpu-core" +version = "27.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" +checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", "bitflags 2.10.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.17", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-types", ] [[package]] -name = "wayland-protocols-wlr" -version = "0.3.9" +name = "wgpu-core-deps-apple" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" +checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" dependencies = [ - "bitflags 2.10.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", + "wgpu-hal", ] [[package]] -name = "wayland-scanner" -version = "0.31.7" +name = "wgpu-core-deps-emscripten" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5" dependencies = [ - "proc-macro2", - "quick-xml", - "quote", + "wgpu-hal", ] [[package]] -name = "wayland-sys" -version = "0.31.7" +name = "wgpu-core-deps-windows-linux-android" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", + "wgpu-hal", ] [[package]] -name = "web-sys" -version = "0.3.82" +name = "wgpu-hal" +version = "27.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.10.0", + "block", + "bytemuck", + "cfg-if", + "cfg_aliases", + "core-graphics-types 0.2.0", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "ordered-float 5.1.0", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.17", "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows 0.58.0", + "windows-core 0.58.0", ] [[package]] -name = "web-time" -version = "1.1.0" +name = "wgpu-types" +version = "27.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" dependencies = [ + "bitflags 2.10.0", + "bytemuck", "js-sys", - "wasm-bindgen", + "log", + "thiserror 2.0.17", + "web-sys", ] [[package]] @@ -2180,6 +3355,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "window_clipboard" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5654226305eaf2dde8853fb482861d28e5dcecbbd40cb88e8393d94bb80d733" +dependencies = [ + "clipboard-win", + "clipboard_macos", + "raw-window-handle", + "thiserror 2.0.17", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.62.2" @@ -2187,7 +3384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core", + "windows-core 0.62.2", "windows-future", "windows-numerics", ] @@ -2198,7 +3395,20 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core", + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", ] [[package]] @@ -2207,11 +3417,11 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.60.2", + "windows-interface 0.59.3", "windows-link", - "windows-result", - "windows-strings", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -2220,11 +3430,22 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core", + "windows-core 0.62.2", "windows-link", "windows-threading", ] +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -2236,6 +3457,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-interface" version = "0.59.3" @@ -2259,10 +3491,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core", + "windows-core 0.62.2", "windows-link", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -2272,6 +3513,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.5.1" @@ -2308,6 +3559,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -2341,13 +3601,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "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-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.2.1" @@ -2369,6 +3646,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -2381,6 +3664,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -2393,12 +3682,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -2411,6 +3712,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -2423,6 +3730,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -2435,6 +3748,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -2448,17 +3767,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winit" -version = "0.30.12" +name = "windows_x86_64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winit" +version = "0.30.8" +source = "git+https://github.com/iced-rs/winit.git?rev=05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed#05b8ff17a06562f0a10bb46e6eaacbe2a95cb5ed" dependencies = [ - "ahash", "android-activity", "atomic-waker", "bitflags 2.10.0", "block2", - "bytemuck", "calloop", "cfg_aliases", "concurrent-queue", @@ -2468,68 +3790,54 @@ dependencies = [ "dpi", "js-sys", "libc", - "memmap2", "ndk", - "objc2", + "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", - "percent-encoding", "pin-project", "raw-window-handle", "redox_syscall 0.4.1", "rustix 0.38.44", - "sctk-adwaita", - "smithay-client-toolkit", "smol_str", "tracing", "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.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] [[package]] name = "winri" -version = "0.2.1" +version = "0.3.0" dependencies = [ "anyhow", - "channel-protocol", "dirs", "easy-ext", + "iced", "itertools", "joy-error", "joy-vector", "keyboard-types", "log", "log4rs", - "oneshot", - "raw-window-handle", "rdev", - "softbuffer", - "tiny-skia", - "windows", - "windows-strings", - "winit", + "webbrowser", + "windows 0.62.2", + "windows-strings 0.5.1", ] [[package]] @@ -2538,6 +3846,12 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + [[package]] name = "x11" version = "2.21.0" @@ -2549,76 +3863,133 @@ dependencies = [ ] [[package]] -name = "x11-dl" -version = "2.21.0" +name = "xkbcommon-dl" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "libc", + "bitflags 2.10.0", + "dlib", + "log", "once_cell", - "pkg-config", + "xkeysym", ] [[package]] -name = "x11rb" -version = "0.13.2" +name = "xkeysym" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" -dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "libloading", - "once_cell", - "rustix 1.1.2", - "x11rb-protocol", -] +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] -name = "x11rb-protocol" -version = "0.13.2" +name = "xml-rs" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" [[package]] -name = "xcursor" -version = "0.3.10" +name = "yazi" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" [[package]] -name = "xkbcommon-dl" -version = "0.4.2" +name = "yoke" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "bitflags 2.10.0", - "dlib", - "log", - "once_cell", - "xkeysym", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "xkeysym" -version = "0.2.1" +name = "yoke-derive" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 7c1673f..56db8a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "winri" description = "A window scrolling tiler for Windows 11" -version = "0.2.1" +version = "0.3.0" edition = "2024" license = "MIT" @@ -9,20 +9,15 @@ license = "MIT" anyhow = "1" windows-strings = "0" log = "0" -log4rs = "1.4.0" +log4rs = "1.4" rdev = { version = "0", features = ["unstable_grab"] } easy-ext = "1" -winit = { version = "0" } -raw-window-handle = "0" -oneshot = { version = "0", default-features = false, features = ["std"] } -softbuffer = "0" -channel-protocol = "0.3" joy-vector = { git = "https://github.com/sub07/rust-utils", version = "0.2.1" } joy-error = { git = "https://github.com/sub07/rust-utils", version = "0.6.0", features = ["log-crate", "anyhow-crate"] } -itertools = "0.14.0" -keyboard-types = { version = "0.8.3", features = ["std", "serde"] } +itertools = "0" +keyboard-types = { version = "0.8", features = ["std", "serde"] } dirs = "6" -tiny-skia = { version = "0.11.4", features = ["simd", "std"], default-features = false } +webbrowser = "1" [dependencies.windows] version = "0.62" @@ -37,7 +32,16 @@ features = [ "Win32_Graphics_Gdi" ] -[package.metadata.cargo-machete] -ignored = [ - "oneshot" # Generated by channel-protocol proc macro so machete can't detect it +[dependencies.iced] +git = "https://github.com/sub07/iced.git" +branch = "custom-winri" +features = [ + "debug", + "time-travel", + "web-colors", + "wgpu", + "canvas", + "tokio", + "crisp" ] +default-features = false diff --git a/README.md b/README.md index 95692a4..d2f142f 100644 --- a/README.md +++ b/README.md @@ -78,15 +78,15 @@ In Tiler mode: | Win + F | Resize focused window to fullscreen width | | Win + C | Resize focused window to half of screen width | | Win + Shift + Left / Right | Resize by increment (20 px by default) | +| Win + R | Force tiler refresh | | Win + Up | Enter Overview mode | | Win + Escape | Exit winri and restore windows | In Overview mode: -| Input | Action | -|-------------------|-----------------------------------------| -| Click thumbnail | Focus original window & return to Tiler | -| Win + Down | Close overview | +| Input | Action | +|--------------|----------------| +| Win + Down | Close overview | ## Configuration @@ -94,7 +94,7 @@ Configuration is not implemented yet. It will include at least the following opt - Keybindings - Padding between windows -- Border color and thickness for thumbnails and focused window (border on focused window not yet implemented) +- Border color and thickness for thumbnails and focused window - Per-process modifiers (e.g. exclude certain apps from tiling) You can check the [config issue](https://github.com/sub07/winri/issues/3) for more fields to come. @@ -112,7 +112,7 @@ Before tackling an issue or submitting a PR, please check the issue tracker for For your PR to be accepted, please ensure the following: - Format code with `cargo fmt --all -- --check` -- Run this clippy command `cargo clippy --all-targets --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W clippy::dbg_macro -A clippy::missing_errors_doc` +- Run this clippy command `cargo clippy -- -D warnings` - Run tests with `cargo test --all-features` - Run cargo-machete to ensure dependency hygiene: `cargo machete --with-metadata` (install with `cargo install cargo-machete`) @@ -128,7 +128,7 @@ Winri aims to be built with stable Rust. - Dependencies: - `windows` crate for Win32 API - `rdev` for global input capture - - `winit` + `softbuffer` (For custom windows) + - `iced` for overlay and other custom windows Releases are automated via GitHub Actions on pushes to `main`. @@ -136,7 +136,7 @@ Tagging is derived from GitHub releases automatically; manual tagging is not req ## Security -Winri must run with administrative privileges to manipulate windows of elevated processes (like task manager). One can choose to run winri without admin rights, but then windows of elevated processes will be ignored. +For Winri to manipulate windows of elevated processes (like task manager), it must run with administrative privileges. One can choose to run winri without admin rights, but then windows of elevated processes will be ignored. Winri will never collect or transmit any user data. Bugs will be reported by users voluntarily. diff --git a/src/adapter/iced.rs b/src/adapter/iced.rs new file mode 100644 index 0000000..eaa88b1 --- /dev/null +++ b/src/adapter/iced.rs @@ -0,0 +1,31 @@ +use crate::utils; + +impl From for iced::Size { + fn from(value: utils::math::Size) -> Self { + Self { + width: value.width(), + height: value.height(), + } + } +} + +impl From for utils::math::Size { + fn from(value: iced::Size) -> Self { + Self([value.width, value.height]) + } +} + +impl From for iced::Point { + fn from(value: utils::math::Position) -> Self { + Self { + x: value.x(), + y: value.y(), + } + } +} + +impl From for utils::math::Position { + fn from(value: iced::Point) -> Self { + Self([value.x, value.y]) + } +} diff --git a/src/adapter/mod.rs b/src/adapter/mod.rs new file mode 100644 index 0000000..ebda348 --- /dev/null +++ b/src/adapter/mod.rs @@ -0,0 +1 @@ +pub mod iced; diff --git a/src/action.rs b/src/app/action.rs similarity index 80% rename from src/action.rs rename to src/app/action.rs index e30ef60..ff458bd 100644 --- a/src/action.rs +++ b/src/app/action.rs @@ -1,11 +1,11 @@ -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Action { Tiler(TilerAction), Overview(OverviewAction), Exit, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum TilerAction { CloseCurrent, MoveFocusNext, @@ -17,9 +17,10 @@ pub enum TilerAction { IncrementWidth, DecrementWidth, OpenOverview, + ForceRefresh, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum OverviewAction { CloseOverview, } diff --git a/src/app/mod.rs b/src/app/mod.rs new file mode 100644 index 0000000..f8c55cc --- /dev/null +++ b/src/app/mod.rs @@ -0,0 +1,203 @@ +mod action; +pub mod model; +mod service; +mod subscription; +mod view; + +use anyhow::Context; +use iced::{ + Color, Task, + theme::Palette, + window::{Settings, settings::PlatformSpecific}, +}; + +use crate::{ + app::{ + service::{ + overview::{self}, + tiler::{self}, + }, + subscription::global::GlobalMessage, + }, + assert_log_fail, + scroll_tiler::ScrollTiler, + system, + utils::math::Size, + window::{self, Window}, +}; + +pub struct State { + pub tiler: ScrollTiler, + pub mode: Mode, + pub configuration: model::Configuration, + overlay_window_id: iced::window::Id, +} + +pub enum Mode { + Tiler(tiler::State), + Overview(overview::State), + Exit, +} + +impl Default for Mode { + fn default() -> Self { + Self::Tiler(tiler::State::default()) + } +} + +#[derive(Debug, Clone)] +pub enum Message { + Action(action::Action), + + Overview(overview::Message), + + Global(subscription::global::GlobalMessage), + + CleanupAndExit, +} + +fn create_overlay_window(screen_size: Size) -> (iced::window::Id, Task) { + let (id, task) = iced::window::open(Settings { + decorations: false, + transparent: true, + resizable: false, + closeable: false, + level: iced::window::Level::AlwaysOnTop, + position: iced::window::Position::Specific(iced::Point::ORIGIN), + size: screen_size.into(), + platform_specific: PlatformSpecific { + skip_taskbar: true, + ..Default::default() + }, + ..Default::default() + }); + + ( + id, + task.then(iced::window::enable_mouse_passthrough) + .then(|_: Message| { + // HACK: By default the overlay window steals focus when created, but should not be able to be focused. + // It causes weird behavior like keystroke not recorded until another window is focused. + // So we refocus the desktop window after creation. + if let Err(err) = system::get_desktop_window().and_then(Window::focus) { + log::error!("Could not focus desktop window after overlay creation: {err}"); + } + Task::none() + }), + ) +} + +impl State { + pub fn new() -> (Self, Task) { + let screen_size = system::screen_size().expect("Screen size retrieval"); + let tiler = ScrollTiler::new(10.0, 20.0, screen_size); + let (overlay_window_id, overlay_window_creation_task) = create_overlay_window(screen_size); + ( + Self { + tiler, + mode: Mode::default(), + configuration: model::Configuration { + tiler_border_style: model::BorderStyle { + color: system::highlight_color().unwrap(), + thickness: 4.0, + radius: 8.0, + }, + }, + overlay_window_id, + }, + overlay_window_creation_task, + ) + } + + pub fn title(_: &Self, _window_id: iced::window::Id) -> String { + window::filter::WINRI_IGNORED_WINDOW_TITLE_SUBSTRING.into() + } + + pub fn handle_app_message(&mut self, message: Message) -> Task { + let mut task = Task::none(); + match message { + Message::Global(global_message) => { + task = task.chain(self.handle_global_message(global_message)); + } + Message::CleanupAndExit => { + system::restore_windows(); + return iced::exit(); + } + Message::Overview(message) => self + .handle_overview_message(message) + .handle_faillible_process() + .discard(), + Message::Action(action) => { + if let Ok(action_task) = self + .handle_action(action) + .context("action handling") + .handle_faillible_process() + { + task = task.chain(action_task); + } + } + } + if matches!(self.mode, Mode::Exit) { + task = task.chain(Task::done(Message::CleanupAndExit)); + } + task + } + + pub fn handle_global_message(&mut self, message: GlobalMessage) -> Task { + match message { + GlobalMessage::Key(modifiers, key) => { + if let Some(action) = self.resolve_action(modifiers, key) { + return Task::done(Message::Action(action)); + } + } + GlobalMessage::Window => { + self.update_tiler() + .context("global window event") + .handle_faillible_process() + .discard(); + } + } + Task::none() + } + + pub fn view(&self, window_id: iced::window::Id) -> iced::Element<'_, Message> { + if window_id == self.overlay_window_id { + view::overlay::view(self) + } else { + view::empty() + } + } + + pub fn theme(&self, window_id: iced::window::Id) -> iced::Theme { + if window_id == self.overlay_window_id { + iced::Theme::custom( + "Overlay transparent theme", + Palette { + background: Color::from_rgba(0.0, 0.0, 0.0, 0.0), + ..Palette::DARK + }, + ) + } else { + iced::Theme::Dark // TODO: Adapt to system theme + } + } + + pub fn subscription(_: &Self) -> iced::Subscription { + iced::Subscription::run(subscription::global::subscription) + } +} + +#[easy_ext::ext(HandleFaillibleProcessResultExt)] +impl Result { + fn handle_faillible_process(self) -> Self { + match &self { + Ok(_) => {} + Err(e) => { + assert_log_fail!("{:?}", e); + } + } + self + } + + fn discard(self) {} +} diff --git a/src/app/model/mod.rs b/src/app/model/mod.rs new file mode 100644 index 0000000..2353a10 --- /dev/null +++ b/src/app/model/mod.rs @@ -0,0 +1,10 @@ +#[derive(Clone, Copy)] +pub struct BorderStyle { + pub color: iced::Color, + pub thickness: f32, + pub radius: f32, +} + +pub struct Configuration { + pub tiler_border_style: BorderStyle, +} diff --git a/src/app/service/mod.rs b/src/app/service/mod.rs new file mode 100644 index 0000000..309cbf4 --- /dev/null +++ b/src/app/service/mod.rs @@ -0,0 +1,118 @@ +use iced::Task; +use keyboard_types::Modifiers; +use rdev::Key; + +use crate::app::{ + self, Mode, + action::{Action, OverviewAction, TilerAction}, +}; + +pub mod overview; +pub mod tiler; + +impl app::State { + pub fn resolve_action(&self, modifiers: Modifiers, key: Key) -> Option { + match (&self.mode, modifiers, key) { + (Mode::Tiler { .. }, Modifiers::META, Key::LeftArrow) => { + Some(Action::Tiler(TilerAction::MoveFocusPrevious)) + } + (Mode::Tiler { .. }, Modifiers::META, Key::RightArrow) => { + Some(Action::Tiler(TilerAction::MoveFocusNext)) + } + (Mode::Tiler { .. }, _, Key::LeftArrow) + if modifiers == Modifiers::META.union(Modifiers::CONTROL) => + { + Some(Action::Tiler(TilerAction::SwapWithPrevious)) + } + (Mode::Tiler { .. }, _, Key::RightArrow) + if modifiers == Modifiers::META.union(Modifiers::CONTROL) => + { + Some(Action::Tiler(TilerAction::SwapWithNext)) + } + (Mode::Tiler { .. }, Modifiers::META, Key::KeyQ) => { + Some(Action::Tiler(TilerAction::CloseCurrent)) + } + (Mode::Tiler { .. }, Modifiers::META, Key::KeyF) => { + Some(Action::Tiler(TilerAction::ResizeToFullscreen)) + } + (Mode::Tiler { .. }, Modifiers::META, Key::KeyC) => { + Some(Action::Tiler(TilerAction::ResizeToHalfScreen)) + } + (Mode::Tiler { .. }, Modifiers::META, Key::KeyR) => { + Some(Action::Tiler(TilerAction::ForceRefresh)) + } + (Mode::Tiler { .. }, _, Key::LeftArrow) + if modifiers == Modifiers::META.union(Modifiers::SHIFT) => + { + Some(Action::Tiler(TilerAction::DecrementWidth)) + } + (Mode::Tiler { .. }, _, Key::RightArrow) + if modifiers == Modifiers::META.union(Modifiers::SHIFT) => + { + Some(Action::Tiler(TilerAction::IncrementWidth)) + } + (Mode::Tiler { .. }, Modifiers::META, Key::UpArrow) => { + Some(Action::Tiler(TilerAction::OpenOverview)) + } + (Mode::Overview { .. }, Modifiers::META, Key::DownArrow) + | (Mode::Overview { .. }, Modifiers::META, Key::Escape) => { + Some(Action::Overview(OverviewAction::CloseOverview)) + } + (_, Modifiers::META, Key::Escape) => Some(Action::Exit), + _ => None, + } + } + + pub fn handle_action(&mut self, action: Action) -> anyhow::Result> { + log::info!("Executing action: {action:?}"); + match action { + Action::Tiler(tiler_action) => match tiler_action { + TilerAction::CloseCurrent => { + if let Some(window) = self.tiler.focused_window() { + window.close()?; + self.update_tiler()?; + } + } + TilerAction::MoveFocusNext => { + self.tiler.focus_right(); + } + TilerAction::MoveFocusPrevious => { + self.tiler.focus_left(); + } + TilerAction::SwapWithNext => { + self.tiler.swap_current_right(); + self.update_tiler()?; + } + TilerAction::SwapWithPrevious => { + self.tiler.swap_current_left(); + self.update_tiler()?; + } + TilerAction::ResizeToFullscreen => { + self.tiler.set_current_window_fullscreen(); + self.update_tiler()?; + self.update_tiler()?; + } + TilerAction::ResizeToHalfScreen => { + self.tiler.set_current_window_halfscreen(); + self.update_tiler()?; + } + TilerAction::OpenOverview => return Ok(self.prepare_open_overview()), + TilerAction::IncrementWidth => { + self.tiler.increment_current_window_width(); + self.update_tiler()?; + } + TilerAction::DecrementWidth => { + self.tiler.decrement_current_window_width(); + self.update_tiler()?; + } + TilerAction::ForceRefresh => self.update_tiler()?, + }, + Action::Overview(overview_action) => match overview_action { + OverviewAction::CloseOverview => return self.close_overview(), + }, + Action::Exit => self.mode = Mode::Exit, + } + + Ok(Task::none()) + } +} diff --git a/src/app/service/overview/mod.rs b/src/app/service/overview/mod.rs new file mode 100644 index 0000000..be9b22b --- /dev/null +++ b/src/app/service/overview/mod.rs @@ -0,0 +1,148 @@ +mod thumbnail; + +use anyhow::Context; +use iced::Task; +use itertools::Itertools; + +use crate::{ + app::{ + self, Mode, + service::overview::{self, thumbnail::ThumbnailId}, + }, + window::Window, +}; + +pub struct State { + opened_thumbnails: Vec<(ThumbnailId, iced::window::Id)>, +} + +#[derive(Debug, Clone)] +pub struct ThumbnailWindowCreated { + pub src: Window, + pub dest_id: iced::window::Id, + pub dest_raw_handle: u64, + pub size: crate::utils::math::Size, +} + +#[derive(Debug, Clone)] +pub enum Message { + ThumbnailWindowCreated(ThumbnailWindowCreated), +} + +impl app::State { + pub fn prepare_open_overview(&self) -> Task { + if matches!(self.mode, Mode::Overview(_)) { + log::warn!( + "Overview operation requested in {} while already in Overview mode", + crate::function!() + ); + return Task::none(); + } + + let windows = self.tiler.windows(); + + let windows_data = windows + .map(|item| thumbnail::WindowData { + inner: item.inner, + width: item.width, + }) + .collect_vec(); + + let thumbnails_data = thumbnail::compute_thumbnails_bounds_from_tiler_windows( + &windows_data, + self.tiler.screen_size(), + 10.0, + ); + + Task::batch(thumbnails_data.into_iter().zip(windows_data).map( + |(thumbnail_data, window)| { + thumbnail::thumbnail_window_creation_task( + window.inner, + thumbnail_data.pos, + thumbnail_data.size, + ) + .then(|overview_message| Task::done(app::Message::Overview(overview_message))) + }, + )) + } + + pub fn handle_overview_message(&mut self, message: overview::Message) -> anyhow::Result<()> { + match message { + overview::Message::ThumbnailWindowCreated(thumbnail_window_created) => { + self.finalize_open_overview(thumbnail_window_created)?; + } + } + + Ok(()) + } + + fn finalize_open_overview( + &mut self, + ThumbnailWindowCreated { + src, + dest_id, + dest_raw_handle, + size, + }: ThumbnailWindowCreated, + ) -> anyhow::Result<()> { + // Switch to overview mode if not already in it + if !matches!(self.mode, Mode::Overview(_)) { + log::info!("switching to Overview mode"); + self.mode = Mode::Overview(State { + opened_thumbnails: Vec::new(), + }); + } + + let Mode::Overview(State { + opened_thumbnails: thumbnails, + }) = &mut self.mode + else { + unreachable!("checked above that we are in Overview mode") + }; + + for tiled_window in self.tiler.windows() { + tiled_window.inner.move_offscreen()?; + } + + let dest_window = Window::from_safe_hwnd(dest_raw_handle) + .context(dest_raw_handle) + .context("invalid hwnd from thumbnail window")?; + + dest_window.set_no_activate()?; + + let thumbnail_id = + thumbnail::bind_thumbnail(src, dest_window, size).context("thumbnail binding")?; + + dest_window.show()?; + dest_window.set_max_zindex()?; + + thumbnails.push((thumbnail_id, dest_id)); + + Ok(()) + } + + pub fn close_overview(&mut self) -> anyhow::Result> { + let Mode::Overview(State { + opened_thumbnails: thumbnails, + }) = &self.mode + else { + log::warn!( + "Close overview requested in {} while not in Overview mode", + crate::function!() + ); + return Ok(Task::none()); + }; + + let tasks = thumbnails + .iter() + .map(|(thumbnail_id, window_id)| { + thumbnail::unbind_thumbnail(*thumbnail_id).map(|()| window_id) + }) + .map(|window_id| window_id.map(|id| iced::window::close(*id))) + .collect::>>()?; + + self.switch_to_tiler_mode()?; + + Ok(Task::batch(tasks)) + } +} diff --git a/src/app/service/overview/thumbnail.rs b/src/app/service/overview/thumbnail.rs new file mode 100644 index 0000000..fca808a --- /dev/null +++ b/src/app/service/overview/thumbnail.rs @@ -0,0 +1,149 @@ +pub type ThumbnailId = isize; + +use iced::{ + Task, + window::{ + Settings, + settings::{PlatformSpecific, platform::CornerPreference}, + }, +}; +use log::debug; +use windows::Win32::{ + Foundation::RECT, + Graphics::Dwm::{ + DWM_THUMBNAIL_PROPERTIES, DWM_TNP_RECTDESTINATION, DWM_TNP_VISIBLE, DwmRegisterThumbnail, + DwmUnregisterThumbnail, DwmUpdateThumbnailProperties, + }, +}; + +use crate::{ + app::service::overview::{self, ThumbnailWindowCreated}, + utils::math::{Position, Size}, + wincall_result, + window::Window, +}; + +pub struct ThumbnailData { + pub pos: Position, + pub size: Size, +} + +pub struct WindowData { + pub inner: Window, + pub width: f32, +} + +pub fn compute_thumbnails_bounds_from_tiler_windows( + windows: &[WindowData], + screen_size: Size, + padding: f32, +) -> Vec { + // Width of packed windows + let total_tiler_width = windows.iter().map(|w| w.width + padding).sum::() - padding; + + let reduction_ratio = screen_size.width() / total_tiler_width; + + debug!("total tiler width including padding: {total_tiler_width}"); + debug!("Thumbnail reduction ratio for packing windows: {reduction_ratio}"); + + let reduction_ratio = if reduction_ratio > 1.0 { + reduction_ratio * 0.6 + } else if reduction_ratio < 1.0 { + reduction_ratio * 0.9 + } else { + reduction_ratio + } + .clamp(0.0, 0.6); + + debug!("Thumbnail reduction ratio after size adaptation: {reduction_ratio}"); + + let mut current_x = 0.0; + let mut thumbnails = Vec::new(); + + let thumbnail_height = reduction_ratio * screen_size.height(); + let thumbnail_y = (screen_size.height() - thumbnail_height).abs() / 2.0; + let thumbnail_x_center_offset = + (screen_size.width() - reduction_ratio * total_tiler_width).abs() / 2.0; + + for window in windows { + let width = reduction_ratio * window.width; + thumbnails.push(ThumbnailData { + pos: [current_x + thumbnail_x_center_offset, thumbnail_y].into(), + size: [width, thumbnail_height].into(), + }); + current_x += width + padding; + } + + thumbnails +} + +pub fn thumbnail_window_creation_task( + source_window: Window, + at: Position, + size: Size, +) -> Task { + let (_, window_creation) = iced::window::open(Settings { + decorations: false, + size: size.into(), + position: iced::window::Position::Specific(at.into()), + resizable: false, + visible: false, + platform_specific: PlatformSpecific { + skip_taskbar: true, + corner_preference: CornerPreference::Round, + ..Default::default() + }, + ..Default::default() + }); + + window_creation + .then(|id| { + iced::window::raw_id::(id) + .then(move |raw_handle| Task::done((id, raw_handle))) + }) + .then(move |(id, raw_handle)| { + iced::window::enable_mouse_passthrough(id).chain(Task::done( + overview::Message::ThumbnailWindowCreated(ThumbnailWindowCreated { + src: source_window, + dest_id: id, + dest_raw_handle: raw_handle, + size, + }), + )) + }) +} + +pub fn bind_thumbnail(src: Window, dest: Window, size: Size) -> anyhow::Result { + let thumbnail_id = wincall_result!(DwmRegisterThumbnail(dest.handle(), src.handle()))?; + + let thumbnail_props = DWM_THUMBNAIL_PROPERTIES { + dwFlags: DWM_TNP_RECTDESTINATION | DWM_TNP_VISIBLE, + rcDestination: RECT { + left: 0, + top: 0, + right: size.width() as i32, + bottom: size.height() as i32, + }, + rcSource: RECT { + left: 0, + top: 0, + right: 0, + bottom: 0, + }, + opacity: 0, + fVisible: true.into(), + fSourceClientAreaOnly: true.into(), + }; + + wincall_result!(DwmUpdateThumbnailProperties( + thumbnail_id, + &raw const thumbnail_props + ))?; + + Ok(thumbnail_id) +} + +pub fn unbind_thumbnail(thumbnail_id: ThumbnailId) -> anyhow::Result<()> { + wincall_result!(DwmUnregisterThumbnail(thumbnail_id))?; + Ok(()) +} diff --git a/src/app/service/tiler.rs b/src/app/service/tiler.rs new file mode 100644 index 0000000..34a9b6a --- /dev/null +++ b/src/app/service/tiler.rs @@ -0,0 +1,105 @@ +use std::collections::HashSet; + +use anyhow::Context; + +use crate::{ + app::{self, Mode}, + utils::math::Bounds, + window::{Window, filter::opened_windows}, +}; + +#[derive(Default)] +pub struct State { + pub current_border_bounds: Option, +} + +macro_rules! bind_tiler_mode_result { + ($mode:expr => TilerState { $($bindings:tt),+ }) => { + let Mode::Tiler(State { $($bindings),+ ,.. }) = &mut $mode else { + log::warn!( + "Tiler operation requested in {} while not in Tiler mode", + crate::function!() + ); + return Ok(()); + }; + }; +} + +macro_rules! ensure_tiler_mode_result { + ($mode:expr) => { + match &$mode { + Mode::Tiler(_) => {} + _ => { + log::warn!( + "Tiler operation requested in {} while not in Tiler mode", + crate::function!() + ); + return Ok(()); + } + } + }; +} + +fn get_process_names(windows: &HashSet) -> Vec { + windows + .iter() + .map(|w| { + let is_focused = w.is_focused().unwrap_or(false); + format!( + "{}{}[class: {}][hwnd: {:?}][title: {}]", + if is_focused { "[FOCUSED] " } else { "" }, + w.process_name() + .ok() + .unwrap_or_else(|| "[ERROR] Could not get process name".to_string()), + w.class() + .unwrap_or_else(|_| "[ERROR] Could not get class name".to_string()), + w.handle(), + w.title() + .unwrap_or_else(|_| Some("[ERROR] Could not get window title".to_string())) + .unwrap_or_else(|| "[UNNAMED]".to_string()), + ) + }) + .collect::>() +} + +impl app::State { + pub fn update_tiler(&mut self) -> anyhow::Result<()> { + ensure_tiler_mode_result!(self.mode); + + let windows_snapshot = opened_windows().context("Window enumeration for tiler update")?; + + log::info!("Opened windows: {:?}", get_process_names(&windows_snapshot)); + + self.tiler.handle_window_snapshot(&windows_snapshot); + + self.update_tiler_border()?; + + Ok(()) + } + + pub fn update_tiler_border(&mut self) -> anyhow::Result<()> { + bind_tiler_mode_result!(self.mode => TilerState { current_border_bounds }); + if let Some(focused_window) = self.tiler.focused_window() { + let bounds = focused_window + .desktop_manager_bounds() + .context("Desktop manager bounds querying for tiler border update")?; + if current_border_bounds != &Some(bounds) { + *current_border_bounds = Some(bounds); + } + } else { + *current_border_bounds = None; + } + + Ok(()) + } + + pub fn switch_to_tiler_mode(&mut self) -> anyhow::Result<()> { + if !matches!(self.mode, Mode::Tiler(_)) { + log::info!("switching to Tiler mode"); + self.mode = Mode::Tiler(State::default()); + self.update_tiler() + .context("initial tiler update on mode switch")?; + } + Ok(()) + } +} diff --git a/src/app/subscription/global/input.rs b/src/app/subscription/global/input.rs new file mode 100644 index 0000000..8506b2a --- /dev/null +++ b/src/app/subscription/global/input.rs @@ -0,0 +1,86 @@ +use std::thread; + +use iced::futures::channel::mpsc::Sender; +use keyboard_types::Modifiers; + +use crate::app::subscription::global::GlobalMessage; + +fn grab_event_processing( + event: rdev::Event, + modifiers: &mut Modifiers, + mut tx: Sender, +) -> Option { + use rdev::{EventType, Key}; + + if matches!( + event.event_type, + EventType::MouseMove { .. } + | EventType::Wheel { .. } + | EventType::ButtonPress(_) + | EventType::ButtonRelease(_) + ) { + return Some(event); + } + + match event.event_type { + rdev::EventType::KeyPress(key) => { + match key { + Key::ShiftLeft | Key::ShiftRight => { + modifiers.set(Modifiers::SHIFT, true); + return Some(event); + } + Key::ControlLeft | Key::ControlRight => { + modifiers.set(Modifiers::CONTROL, true); + return Some(event); + } + Key::Alt => { + modifiers.set(Modifiers::ALT, true); + return Some(event); + } + // Got an unknown key code for the right Meta key for some reason + Key::MetaLeft | Key::MetaRight | Key::Unknown(92) => { + modifiers.set(Modifiers::META, true); + // Win key presses are swallowed to avoid opening the Start Menu, and triggering system shortcuts + // Winri is supposed to be a sort of "command center" for the system, so the native system shortcuts should not be needed + // I know this might be controversial, but it's the intended behavior for now, and I'm open to feedback on this matter + return None; + } + _ => { + tx.try_send(GlobalMessage::Key(*modifiers, key)).unwrap(); + return (!modifiers.contains(Modifiers::META)).then_some(event); + } + } + } + rdev::EventType::KeyRelease(key) => { + match key { + Key::ShiftLeft | Key::ShiftRight => { + modifiers.set(Modifiers::SHIFT, false); + } + Key::ControlLeft | Key::ControlRight => { + modifiers.set(Modifiers::CONTROL, false); + } + Key::Alt => { + modifiers.set(Modifiers::ALT, false); + } + // Got an unknown key code for the right Meta key for some reason + Key::MetaLeft | Key::MetaRight | Key::Unknown(92) => { + modifiers.set(Modifiers::META, false); + return None; + } + _ => {} + } + } + _ => {} + } + Some(event) +} + +pub fn launch(tx: Sender) { + let _ = thread::Builder::new() + .name("global-key-hook".into()) + .spawn(move || { + let mut modifiers = Modifiers::default(); // TODO: Check initial state of modifiers + rdev::_grab(move |event| grab_event_processing(event, &mut modifiers, tx.clone())) + .unwrap(); + }); +} diff --git a/src/app/subscription/global/mod.rs b/src/app/subscription/global/mod.rs new file mode 100644 index 0000000..7f63d0f --- /dev/null +++ b/src/app/subscription/global/mod.rs @@ -0,0 +1,32 @@ +mod input; +mod window; + +use iced::{ + futures::{SinkExt, Stream, StreamExt, channel::mpsc::channel}, + stream, +}; +use keyboard_types::Modifiers; + +use crate::app::{Message, subscription::STREAM_CHANNEL_BUFFER_SIZE}; + +#[derive(Debug, Clone)] +pub enum GlobalMessage { + Key(Modifiers, rdev::Key), + Window, +} + +pub fn subscription() -> impl Stream { + stream::channel(STREAM_CHANNEL_BUFFER_SIZE, async |mut output| { + let (intermediate_message_tx, mut intermediate_message_rx) = channel(100); + + let global_input_tx = intermediate_message_tx.clone(); + let window_event_tx = intermediate_message_tx; + + input::launch(global_input_tx); + window::launch(window_event_tx); + + while let Some(event) = intermediate_message_rx.next().await { + output.send(Message::Global(event)).await.unwrap(); + } + }) +} diff --git a/src/hook/window.rs b/src/app/subscription/global/window.rs similarity index 52% rename from src/hook/window.rs rename to src/app/subscription/global/window.rs index 35ecc0f..251e992 100644 --- a/src/hook/window.rs +++ b/src/app/subscription/global/window.rs @@ -1,36 +1,32 @@ use std::{ ptr::null_mut, - sync::{ - Mutex, - mpsc::{Receiver, Sender}, - }, + sync::Mutex, thread, time::{Duration, Instant}, }; -use anyhow::ensure; -use windows::Win32::{ - Foundation::HWND, - UI::{ - Accessibility::{HWINEVENTHOOK, SetWinEventHook, UnhookWinEvent}, - WindowsAndMessaging::{ - EVENT_OBJECT_CREATE, EVENT_OBJECT_FOCUS, GetMessageA, WINEVENT_OUTOFCONTEXT, - WINEVENT_SKIPOWNPROCESS, - }, +use iced::futures::channel::mpsc::Sender; +use windows::Win32::UI::{ + Accessibility::{SetWinEventHook, UnhookWinEvent}, + WindowsAndMessaging::{ + EVENT_OBJECT_CREATE, EVENT_OBJECT_FOCUS, GetMessageW, WINEVENT_OUTOFCONTEXT, + WINEVENT_SKIPOWNPROCESS, }, }; +use crate::app::subscription::global::GlobalMessage; + const WINDOW_HOOK_COOLDOWN: Duration = Duration::from_millis(200); -struct WindowHookContext { - notifier: Sender<()>, +struct WindowHookManager { + tx: Sender, last_time_notified: Instant, } -impl WindowHookContext { - fn new(notifier: Sender<()>) -> Self { +impl WindowHookManager { + fn new(notifier: Sender) -> Self { Self { - notifier, + tx: notifier, last_time_notified: Instant::now(), } } @@ -38,10 +34,11 @@ impl WindowHookContext { fn tick(&mut self) { let elapsed = self.last_time_notified.elapsed(); if elapsed > WINDOW_HOOK_COOLDOWN { - self.notifier.send(()).unwrap(); + self.tx.try_send(GlobalMessage::Window).unwrap(); self.last_time_notified = Instant::now(); } else { let original_last_time_notified = self.last_time_notified; + // TODO: Needs serious rework to avoid spawning threads like this. Use async timers instead. thread::Builder::new() .name("window-hook-cooldown-timer".to_string()) .spawn(move || { @@ -50,7 +47,7 @@ impl WindowHookContext { reason = "Underflow is prevented by condition above" )] thread::sleep(WINDOW_HOOK_COOLDOWN - elapsed); - if let Some(context) = WINDOW_HOOK_CHANNEL.lock().unwrap().as_mut() + if let Some(context) = WINDOW_HOOK_MANAGER.lock().unwrap().as_mut() && context.last_time_notified == original_last_time_notified { context.tick(); @@ -61,31 +58,32 @@ impl WindowHookContext { } } -static WINDOW_HOOK_CHANNEL: Mutex> = Mutex::new(None); +static WINDOW_HOOK_MANAGER: Mutex> = Mutex::new(None); unsafe extern "system" fn hook_callback( - _hwineventhook: HWINEVENTHOOK, + _hwineventhook: windows::Win32::UI::Accessibility::HWINEVENTHOOK, _event: u32, - _hwnd: HWND, + _hwnd: windows::Win32::Foundation::HWND, _idobject: i32, _idchild: i32, _ideventthread: u32, _dwmseventtime: u32, ) { - if let Some(context) = WINDOW_HOOK_CHANNEL.lock().unwrap().as_mut() { - context.tick(); + // TODO: try async here with a block on + if let Some(manager) = WINDOW_HOOK_MANAGER.lock().unwrap().as_mut() { + manager.tick(); } } -pub fn launch_hook() -> anyhow::Result> { - let mut window_hook_context = WINDOW_HOOK_CHANNEL.lock().unwrap(); - ensure!(window_hook_context.is_none(), "Hook already launched"); - let (sender, receiver) = std::sync::mpsc::channel(); - *window_hook_context = Some(WindowHookContext::new(sender)); - drop(window_hook_context); - thread::Builder::new() +pub fn launch(tx: Sender) { + { + let mut window_hook_context = WINDOW_HOOK_MANAGER.lock().unwrap(); + debug_assert!(window_hook_context.is_none(), "Hook already launched"); + *window_hook_context = Some(WindowHookManager::new(tx)); + } + if let Err(e) = thread::Builder::new() .name("win-event-hook-loop".to_string()) - .spawn(|| unsafe { + .spawn(move || unsafe { let hook = SetWinEventHook( EVENT_OBJECT_CREATE, EVENT_OBJECT_FOCUS, @@ -95,11 +93,12 @@ pub fn launch_hook() -> anyhow::Result> { 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS, ); - if !GetMessageA(null_mut(), None, 0, 0).as_bool() { + if !GetMessageW(null_mut(), None, 0, 0).as_bool() { let _ = UnhookWinEvent(hook); - WINDOW_HOOK_CHANNEL.lock().unwrap().take(); + WINDOW_HOOK_MANAGER.lock().unwrap().take(); } }) - .unwrap(); - Ok(receiver) + { + log::error!("Failed to launch window hook thread: {e:?}"); + } } diff --git a/src/app/subscription/mod.rs b/src/app/subscription/mod.rs new file mode 100644 index 0000000..7503066 --- /dev/null +++ b/src/app/subscription/mod.rs @@ -0,0 +1,3 @@ +pub mod global; + +pub const STREAM_CHANNEL_BUFFER_SIZE: usize = 100; diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs new file mode 100644 index 0000000..270fb5f --- /dev/null +++ b/src/app/view/mod.rs @@ -0,0 +1,7 @@ +use crate::app; + +pub mod overlay; + +pub fn empty<'a>() -> iced::Element<'a, app::Message> { + iced::widget::Row::new().into() +} diff --git a/src/app/view/overlay.rs b/src/app/view/overlay.rs new file mode 100644 index 0000000..65dcee3 --- /dev/null +++ b/src/app/view/overlay.rs @@ -0,0 +1,68 @@ +use iced::{ + Renderer, Theme, mouse, + widget::{ + self, + canvas::{self, Path, Stroke}, + }, +}; + +use crate::{ + app::{self, model::BorderStyle, service::tiler::State, view}, + utils::math::Bounds, +}; + +pub fn view(app: &app::State) -> iced::Element<'_, app::Message> { + match &app.mode { + app::Mode::Tiler(tiler_state) => tiler_view(app, tiler_state), + _ => view::empty(), + } +} + +fn tiler_view<'a>(app: &'a app::State, tiler_state: &'a State) -> iced::Element<'a, app::Message> { + if let Some(border_bounds) = tiler_state.current_border_bounds { + widget::canvas(TilerBorder { + border_bounds, + border_style: app.configuration.tiler_border_style, + }) + .width(iced::Length::Fill) + .height(iced::Length::Fill) + .into() + } else { + view::empty() + } +} + +struct TilerBorder { + border_bounds: Bounds, + border_style: BorderStyle, +} + +impl canvas::Program for TilerBorder { + type State = (); + + fn draw( + &self, + _state: &Self::State, + renderer: &Renderer, + _theme: &Theme, + bounds: iced::Rectangle, + _cursor: mouse::Cursor, + ) -> Vec> { + let mut frame = canvas::Frame::new(renderer, bounds.size()); + + let path = Path::rounded_rectangle( + self.border_bounds.position().into(), + self.border_bounds.size().into(), + self.border_style.radius.into(), + ); + + frame.stroke( + &path, + Stroke::default() + .with_color(self.border_style.color) + .with_width(self.border_style.thickness), + ); + + vec![frame.into_geometry()] + } +} diff --git a/src/hook/key.rs b/src/hook/key.rs deleted file mode 100644 index 50dc4be..0000000 --- a/src/hook/key.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::sync::mpsc::Receiver; - -use keyboard_types::Modifiers; -use rdev::{EventType, Key}; - -#[derive(Debug)] -pub struct Event(pub Modifiers, pub Key); - -pub fn launch_hook() -> Receiver { - let (sender, receiver) = std::sync::mpsc::channel(); - std::thread::Builder::new() - .name("global-key-hook".into()) - .spawn(move || { - let mut modifiers = Modifiers::default(); - rdev::_grab(move |event| { - if matches!( - event.event_type, - EventType::MouseMove { .. } - | EventType::Wheel { .. } - | EventType::ButtonPress(_) - | EventType::ButtonRelease(_) - ) { - return Some(event); - } - - match event.event_type { - rdev::EventType::KeyPress(key) => { - match key { - Key::ShiftLeft | Key::ShiftRight => { - modifiers.set(Modifiers::SHIFT, true); - return Some(event); - } - Key::ControlLeft | Key::ControlRight => { - modifiers.set(Modifiers::CONTROL, true); - return Some(event); - } - Key::Alt => { - modifiers.set(Modifiers::ALT, true); - return Some(event); - } - // Got an unknown key code for the right Meta key for some reason - Key::MetaLeft | Key::MetaRight | Key::Unknown(92) => { - modifiers.set(Modifiers::META, true); - // Win key presses are swallowed to avoid opening the Start Menu, and triggering system shortcuts - // Winri is supposed to be a sort of "command center" for the system, so the native system shortcuts should not be needed - // I know this might be controversial, but it's the intended behavior for now, and I'm open to feedback on this matter - return None; - } - _ => { - sender.send(Event(modifiers, key)).unwrap(); - return (!modifiers.contains(Modifiers::META)).then_some(event); - } - } - } - rdev::EventType::KeyRelease(key) => { - match key { - Key::ShiftLeft | Key::ShiftRight => { - modifiers.set(Modifiers::SHIFT, false); - } - Key::ControlLeft | Key::ControlRight => { - modifiers.set(Modifiers::CONTROL, false); - } - Key::Alt => { - modifiers.set(Modifiers::ALT, false); - } - // Got an unknown key code for the right Meta key for some reason - Key::MetaLeft | Key::MetaRight | Key::Unknown(92) => { - modifiers.set(Modifiers::META, false); - return None; - } - _ => {} - } - } - _ => {} - } - Some(event) - }) - .unwrap(); - }) - .unwrap(); - receiver -} diff --git a/src/hook/mod.rs b/src/hook/mod.rs deleted file mode 100644 index 8c18a45..0000000 --- a/src/hook/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::sync::mpsc::Sender; - -use crate::Event; - -pub mod key; -// pub mod thumbnail; -pub mod window; - -pub fn launch_hooks(event_tx: Sender) -> anyhow::Result<()> { - let window_event_receiver = window::launch_hook()?; - let key_event_receiver = key::launch_hook(); - - let window_event_tx = event_tx.clone(); - let key_event_tx = event_tx; - - std::thread::Builder::new() - .name("window-event-forwarder".into()) - .spawn(move || { - for () in window_event_receiver { - window_event_tx.send(Event::Window).unwrap(); - } - }) - .unwrap(); - - std::thread::Builder::new() - .name("key-event-forwarder".into()) - .spawn(move || { - for key_event in key_event_receiver { - key_event_tx.send(Event::Key(key_event)).unwrap(); - } - }) - .unwrap(); - - Ok(()) -} diff --git a/src/logger.rs b/src/logger.rs index 7bfbde8..fdf519d 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -12,18 +12,28 @@ use log4rs::{ }, }, }, - config::{Appender, Root}, + config::{Appender, Logger, Root}, encode::pattern::PatternEncoder, }; -use crate::{root_dir, utils::IS_DEBUG}; +const DISABLED_MODULES: &[&str] = &[ + "wgpu_core", + "wgpu_hal", + "naga", + "cosmic_text", + "iced_wgpu", + "iced_winit", + "iced_beacon", +]; -fn log_dir() -> anyhow::Result { +use crate::{DEBUG_MODE, root_dir}; + +pub fn log_dir() -> anyhow::Result { Ok(root_dir()?.join("logs")) } pub fn setup() -> anyhow::Result<()> { - const LEVEL_FILTER: log::LevelFilter = if IS_DEBUG { + const LEVEL_FILTER: log::LevelFilter = if DEBUG_MODE { log::LevelFilter::Debug } else { log::LevelFilter::Info @@ -59,9 +69,14 @@ pub fn setup() -> anyhow::Result<()> { ), ); + let disabled_loggers = DISABLED_MODULES + .iter() + .map(|&module| Logger::builder().build(module, log::LevelFilter::Off)); + log4rs::init_config( Config::builder() .appenders([console_appender, file_appender]) + .loggers(disabled_loggers) .build( Root::builder() .appenders(["console", "file"]) diff --git a/src/main.rs b/src/main.rs index 6c5cc10..a6f76e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,432 +1,68 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -mod action; -mod hook; +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release builds +#![warn(clippy::pedantic, clippy::nursery, clippy::dbg_macro)] +#![allow( + clippy::missing_errors_doc, + clippy::cast_possible_truncation, + clippy::missing_const_for_fn, + clippy::needless_pass_by_value, + clippy::option_if_let_else, + clippy::default_trait_access +)] + +use std::{panic, path::PathBuf}; + +use anyhow::{Context, anyhow}; + +mod adapter; +mod app; mod logger; +mod message_box; +mod scroll_tiler; mod system; -mod thumbnail; -mod tiler; mod utils; +mod winapi; mod window; -use std::{ - collections::{HashMap, HashSet}, - panic, - path::PathBuf, - sync::mpsc::Sender, -}; - -use anyhow::{anyhow, bail}; -use itertools::Itertools; -use keyboard_types::Modifiers; -use log::{error, info, warn}; -use rdev::Key; - -use crate::{ - action::{Action, TilerAction}, - hook::{ - key::{self}, - launch_hooks, - }, - system::{restore_windows, screen_size}, - tiler::ScrollTiler, - utils::{Bounds, IS_DEBUG}, - window::{ - Window, - filter::opened_windows, - manager::{BorderStyle, HandleOutputProtocol, ThumbnailId}, - }, -}; +pub const DEBUG_MODE: bool = cfg!(debug_assertions); +pub const WINRI_VERSION: &str = env!("CARGO_PKG_VERSION"); pub fn root_dir() -> anyhow::Result { - const PROJECT_DIR_NAME: &str = if IS_DEBUG { "winri-dev" } else { "winri" }; + const PROJECT_DIR_NAME: &str = if DEBUG_MODE { "winri-dev" } else { "winri" }; Ok(dirs::config_dir() .ok_or_else(|| anyhow!("Could not determine config directory"))? .join(PROJECT_DIR_NAME)) } -pub enum Event { - Key(key::Event), - WindowManager(window::manager::OutputProtocolMessage), - Window, -} - -enum Mode { - Tiler { - current_border_target: Option<(Window, Bounds)>, - }, - Overview { - thumbnails: HashMap, - }, - ExitingSuccessfully, - ExitingWithError(anyhow::Error), -} - -pub struct Winri { - mode: Mode, - window_manager_client: window::manager::InputProtocolClient, - tiler: ScrollTiler, - event_rx: std::sync::mpsc::Receiver, - event_tx: Sender, -} - -fn get_process_names(windows: &HashSet) -> Vec { - windows - .iter() - .map(|w| { - let is_focused = w.is_focused().unwrap_or(false); - format!( - "{}{}(class: {})", - if is_focused { "[FOCUSED] " } else { "" }, - w.process_name() - .ok() - .unwrap_or_else(|| "[ERROR] Could not get process name".to_string()), - w.class() - .unwrap_or_else(|_| "[ERROR] Could not get class name".to_string()) - ) - }) - .collect::>() -} - -impl window::manager::HandleOutputProtocol for Winri { - fn cursor_entered_thumbnail(&mut self, id: ThumbnailId) { - if let Err(e) = self.window_manager_client.border_thumbnail(id) { - self.mode = Mode::ExitingWithError(e); - } - } - - fn cursor_exited_thumbnail(&mut self, _id: ThumbnailId) { - if let Err(e) = self.window_manager_client.unborder_all_thumbnails() { - self.mode = Mode::ExitingWithError(e); - } - } - - fn thumbnail_clicked(&mut self, id: ThumbnailId) { - let Mode::Overview { thumbnails } = std::mem::replace( - &mut self.mode, - Mode::Tiler { - current_border_target: None, - }, - ) else { - return; - }; - let Some(window) = thumbnails.get(&id) else { - return; - }; - self.window_manager_client.close_all_thumbnails().unwrap(); - window.focus().unwrap(); - self.update_tiler().unwrap(); - } - - fn unrecoverable_error(&mut self, err: anyhow::Error) { - self.mode = Mode::ExitingWithError(err); - } -} - -impl Winri { - fn update_tiler(&mut self) -> anyhow::Result<()> { - let windows_snapshot = opened_windows()?; - if windows_snapshot.is_empty() { - self.reset_tiler_border()?; - } - - let Mode::Tiler { - current_border_target, - } = &mut self.mode - else { - warn!("Tiler update requested while not in Tiler mode; ignoring."); - return Ok(()); - }; - - info!( - "Opened windows: {:#?}", - get_process_names(&windows_snapshot) - ); - - self.tiler.handle_window_snapshot(&windows_snapshot); - - if let Some(focused_window) = self.tiler.current_window() - && windows_snapshot.contains(&focused_window) - { - let bounds = focused_window.desktop_manager_bounds()?; - let window_cache_info = (focused_window, bounds); - if current_border_target != &Some(window_cache_info) { - info!("Bordering focused window"); - self.window_manager_client - .border_tiler_window(focused_window)?; - *current_border_target = Some(window_cache_info); - } - } else { - self.reset_tiler_border()?; - } - Ok(()) - } - - fn reset_tiler_border(&mut self) -> anyhow::Result<()> { - let Mode::Tiler { - current_border_target, - } = &mut self.mode - else { - warn!("Tiler border reset requested while not in Tiler mode; ignoring."); - return Ok(()); - }; - - if current_border_target.is_some() { - info!("Unbordering tiler window"); - self.window_manager_client.unborder_tiler_window()?; - *current_border_target = None; - } - - Ok(()) - } - - fn open_overview(&mut self) -> anyhow::Result<()> { - if matches!(self.mode, Mode::Overview { .. }) { - return Ok(()); - } - - let windows = self.tiler.windows(); - - let windows_data = windows - .map(|item| thumbnail::WindowData { - inner: item.inner, - width: item.width, - }) - .collect_vec(); - - let thumbnails = thumbnail::create_thumbnails_from_tiler_windows( - &windows_data, - self.tiler.screen_size(), - 10, - ); - - for window in &windows_data { - window.inner.move_offscreen()?; - } - - let thumbnails = thumbnails - .into_iter() - .zip(windows_data) - .map(|(thumbnail, window)| { - self.window_manager_client - .create_thumbnail(window.inner, thumbnail.pos, thumbnail.size) - .map(|id| (id, window.inner)) - }) - .collect::>>()?; - - self.mode = Mode::Overview { thumbnails }; - - Ok(()) - } - - fn handle_event(&mut self, event: Event) -> anyhow::Result<()> { - match event { - Event::Key(key_event) => { - if let Some(action) = self.resolve_action(key_event) { - info!("Executing action: {action:?}"); - match action { - Action::Tiler(tiler_action) => match tiler_action { - TilerAction::CloseCurrent => { - if let Some(window) = self.tiler.current_window() { - window.close()?; - self.update_tiler()?; - } - } - TilerAction::MoveFocusNext => { - self.tiler.focus_right(); - } - TilerAction::MoveFocusPrevious => { - self.tiler.focus_left(); - } - TilerAction::SwapWithNext => { - self.tiler.swap_current_right(); - self.update_tiler()?; - } - TilerAction::SwapWithPrevious => { - self.tiler.swap_current_left(); - self.update_tiler()?; - } - TilerAction::ResizeToFullscreen => { - self.tiler.set_current_window_fullscreen(); - self.update_tiler()?; - self.update_tiler()?; - } - TilerAction::ResizeToHalfScreen => { - self.tiler.set_current_window_halfscreen(); - self.update_tiler()?; - } - TilerAction::OpenOverview => { - self.reset_tiler_border()?; - self.open_overview()?; - } - TilerAction::IncrementWidth => { - self.tiler.increment_current_window_width(); - self.update_tiler()?; - } - TilerAction::DecrementWidth => { - self.tiler.decrement_current_window_width(); - self.update_tiler()?; - } - }, - Action::Overview(overview_action) => match overview_action { - action::OverviewAction::CloseOverview => { - if matches!(self.mode, Mode::Tiler { .. }) { - return Ok(()); - } - self.window_manager_client.close_all_thumbnails()?; - self.mode = Mode::Tiler { - current_border_target: None, - }; - self.event_tx.send(Event::Window)?; - } - }, - Action::Exit => self.mode = Mode::ExitingSuccessfully, - } - } - } - Event::Window => { - if matches!(self.mode, Mode::Tiler { .. }) { - self.update_tiler()?; - } - } - Event::WindowManager(msg) => self.dispatch(msg), - } - Ok(()) - } - - fn resolve_action(&self, key::Event(modifiers, key): key::Event) -> Option { - match (&self.mode, modifiers, key) { - (Mode::Tiler { .. }, Modifiers::META, Key::LeftArrow) => { - Some(Action::Tiler(TilerAction::MoveFocusPrevious)) - } - (Mode::Tiler { .. }, Modifiers::META, Key::RightArrow) => { - Some(Action::Tiler(TilerAction::MoveFocusNext)) - } - (Mode::Tiler { .. }, _, Key::LeftArrow) - if modifiers == Modifiers::META.union(Modifiers::CONTROL) => - { - Some(Action::Tiler(TilerAction::SwapWithPrevious)) - } - (Mode::Tiler { .. }, _, Key::RightArrow) - if modifiers == Modifiers::META.union(Modifiers::CONTROL) => - { - Some(Action::Tiler(TilerAction::SwapWithNext)) - } - (Mode::Tiler { .. }, Modifiers::META, Key::KeyQ) => { - Some(Action::Tiler(TilerAction::CloseCurrent)) - } - (Mode::Tiler { .. }, Modifiers::META, Key::KeyF) => { - Some(Action::Tiler(TilerAction::ResizeToFullscreen)) - } - (Mode::Tiler { .. }, Modifiers::META, Key::KeyC) => { - Some(Action::Tiler(TilerAction::ResizeToHalfScreen)) - } - (Mode::Tiler { .. }, _, Key::LeftArrow) - if modifiers == Modifiers::META.union(Modifiers::SHIFT) => - { - Some(Action::Tiler(TilerAction::DecrementWidth)) - } - (Mode::Tiler { .. }, _, Key::RightArrow) - if modifiers == Modifiers::META.union(Modifiers::SHIFT) => - { - Some(Action::Tiler(TilerAction::IncrementWidth)) - } - (Mode::Tiler { .. }, Modifiers::META, Key::UpArrow) => { - Some(Action::Tiler(TilerAction::OpenOverview)) - } - (Mode::Overview { .. }, Modifiers::META, Key::DownArrow) - | (Mode::Overview { .. }, Modifiers::META, Key::Escape) => { - Some(Action::Overview(action::OverviewAction::CloseOverview)) - } - (_, Modifiers::META, Key::Escape) => Some(Action::Exit), - _ => None, - } - } - - fn run(mut self) -> anyhow::Result<()> { - self.update_tiler()?; - - while let Ok(event) = self.event_rx.recv() { - self.handle_event(event)?; - - match self.mode { - Mode::ExitingSuccessfully => { - info!("Exiting successfully."); - break; - } - Mode::ExitingWithError(ref err) => { - bail!("Exiting due to unrecoverable error: {err:#}"); - } - _ => {} - } - } - - Ok(()) - } -} - -pub fn launch_winri() -> anyhow::Result<()> { - logger::setup()?; - +fn main() { let default_hook = panic::take_hook(); panic::set_hook(Box::new(move |info| { - error!("Winri panicked: {info}"); + log::error!("Winri panicked: {info}"); + message_box::message_box_fatal_bug_report(info); system::restore_windows(); - if let Some(error_cause) = info.payload_as_str() { - utils::winapi::message_box( - "Fatal error", - &format!("{error_cause}.\nThe application will now exit."), - ); - } default_hook(info); })); - let screen_size = screen_size()?; - let (event_tx, event_rx) = std::sync::mpsc::channel(); - - launch_hooks(event_tx.clone())?; - - let system_highlight_color = system::highlight_color()?; - - let window_manager_client = window::manager::launch( - event_tx.clone(), - BorderStyle { - color: system_highlight_color, - thickness: 4, - radius: 6, - }, - BorderStyle { - color: system_highlight_color, - thickness: 4, - radius: 12, - }, - )?; - - let app = Winri { - mode: Mode::Tiler { - current_border_target: None, - }, - window_manager_client, - tiler: ScrollTiler::new(10, 20, screen_size), - event_rx, - event_tx, - }; - - if let Err(e) = app.run() { - error!("Fatal error: {e:?}"); - restore_windows(); - utils::winapi::message_box( - "Fatal error", - &format!("{e:#}.\nThe application will now exit."), - ); + if let Err(e) = logger::setup() + .context("Could not initialize log system, no log will be written for this session") + { + message_box::message_box_info_bug_report(e); } - restore_windows(); - Ok(()) -} - -fn main() -> anyhow::Result<()> { - launch_winri()?; + log::info!("Winri starting up"); + + if let Err(e) = iced::daemon( + app::State::new, + app::State::handle_app_message, + app::State::view, + ) + .subscription(app::State::subscription) + .title(app::State::title) + .theme(app::State::theme) + .run() + { + log::error!("Winri exited with error: {e}"); + message_box::message_box_fatal_bug_report(anyhow!(e)); + } - Ok(()) + log::info!("Winri exited successfully"); } diff --git a/src/message_box.rs b/src/message_box.rs new file mode 100644 index 0000000..0bc9ce8 --- /dev/null +++ b/src/message_box.rs @@ -0,0 +1,169 @@ +use std::panic::PanicHookInfo; + +use windows::Win32::UI::WindowsAndMessaging::{IDYES, MB_OK, MB_YESNO}; + +use crate::{DEBUG_MODE, WINRI_VERSION, logger, winapi}; + +#[allow(dead_code, reason = "Could be useful at some point")] +pub fn message_box_info(title: &str, message: &str) { + winapi::message_box(title, message, MB_OK); +} + +pub fn message_box_query(title: &str, message: &str) -> bool { + winapi::message_box(title, message, MB_YESNO) == IDYES +} + +pub fn log_file_path() -> String { + logger::log_dir().map_or_else( + |_| "".into(), + |mut p| { + p.push("winri.log"); + p.display().to_string() + }, + ) +} + +pub trait IntoBugReportInfo { + fn title(&self) -> String; + fn short(&self) -> String; + fn long(&self) -> String; +} + +impl IntoBugReportInfo for anyhow::Error { + fn title(&self) -> String { + "Bug report".into() + } + + fn short(&self) -> String { + format!("{self}") + } + + fn long(&self) -> String { + format!("{self:#?}") + } +} + +impl IntoBugReportInfo for &PanicHookInfo<'_> { + fn title(&self) -> String { + "Panic report".into() + } + + fn short(&self) -> String { + if let Some(str) = self.payload_as_str() { + str.to_string() + } else { + "".into() + } + .trim() + .to_string() + } + + fn long(&self) -> String { + self.to_string() + } +} + +struct FatalBugReport(B); +struct NonFatalBugReport(B); + +impl IntoBugReportInfo for FatalBugReport { + fn title(&self) -> String { + format!("Fatal {}", self.0.title()) + } + + fn short(&self) -> String { + self.0.short() + } + + fn long(&self) -> String { + format!( + r" +An unrecoverable error occurred, Application will now exit: + +``` +{} +``` + +Before it closes, would you like to submit a pre-filled github issue about this bug ? + + +>Please note: +> 1. Your browser will open +> 2. You can edit the issue before submitting it +> 3. Error reports are automatically included and may contain personal information such as window titles, browser tab names, etc. +> 4. To help debugging, you can include your session logs ({}). But note that it may contains personal information such as window titles, browser tab names, etc. + ", + if DEBUG_MODE { + self.0.long() + } else { + self.0.short() + }, + log_file_path() + ) + } +} + +impl IntoBugReportInfo for NonFatalBugReport { + fn title(&self) -> String { + self.0.title() + } + + fn short(&self) -> String { + self.0.short() + } + + fn long(&self) -> String { + format!( + r" +An error occurred, but winri will continue its execution. Some systems might not be working properly: + +``` +{} +``` + +Before resuming winri, +would you like to submit a pre-filled github issue about this bug ? + + +>Please note: +> 1. Your browser will open +> 2. You can edit the issue before submitting it +> 3. Error reports are automatically included and may contain personal information such as window titles, browser tab names, etc. +> 4. To help debugging, you can include your session logs ({}). But note that it may contains personal information such as window titles, browser tab names, etc. + ", + if DEBUG_MODE { + self.0.long() + } else { + self.0.short() + }, + log_file_path() + ) + } +} + +pub fn message_box_bug_report(report: impl IntoBugReportInfo) { + let create_bug_report = message_box_query(report.title().as_str(), report.long().as_str()); + + if create_bug_report { + let create_issue_url = format!( + "https://github.com/sub07/winri/issues/new?labels=crash logs&title=[v{}] {}: {}&body={}", + WINRI_VERSION, + report.title(), + report.short(), + report.long().replace('\n', "%0A") + ); + if let Err(err) = webbrowser::open(&create_issue_url) { + log::error!("Could not open web browser to create bug report: {err:?}"); + } else { + log::info!("Opened web browser to create bug report: {create_issue_url}"); + } + } +} + +pub fn message_box_fatal_bug_report(report: impl IntoBugReportInfo) { + message_box_bug_report(FatalBugReport(report)); +} + +pub fn message_box_info_bug_report(report: impl IntoBugReportInfo) { + message_box_bug_report(NonFatalBugReport(report)); +} diff --git a/src/tiler.rs b/src/scroll_tiler.rs similarity index 68% rename from src/tiler.rs rename to src/scroll_tiler.rs index ab98e84..5cecc6d 100644 --- a/src/tiler.rs +++ b/src/scroll_tiler.rs @@ -4,20 +4,16 @@ use anyhow::Context; use joy_error::log::ResultLogExt; use log::{debug, info, warn}; -use crate::{ - cast, - utils::{Size, cast::FaillibleCastUtils}, - window::Window, -}; +use crate::{cast, utils::math::Size, window::Window}; -#[derive(PartialEq, Eq)] +#[derive(PartialEq)] pub struct WindowItem { pub inner: Window, - pub width: u32, + pub width: f32, } impl WindowItem { - pub const fn new(inner: Window, width: u32) -> Self { + pub const fn new(inner: Window, width: f32) -> Self { Self { inner, width } } } @@ -25,15 +21,15 @@ impl WindowItem { #[derive(Default)] pub struct ScrollTiler { windows: Vec, - padding: u32, - resize_increment: u32, - scroll_offset: i32, + padding: f32, + resize_increment: f32, + scroll_offset: f32, screen_size: Size, previously_focused_window_index: Option, } impl ScrollTiler { - pub fn new(padding: u32, resize_increment: u32, screen_size: Size) -> Self { + pub fn new(padding: f32, resize_increment: f32, screen_size: Size) -> Self { Self { padding, resize_increment, @@ -89,16 +85,21 @@ impl ScrollTiler { self.swap_current(1); } + #[allow( + clippy::cast_sign_loss, + reason = "return value is guaranteed to be positive by the clamp call" + )] + fn compute_index_for_direction(&self, focus_index: usize, direction: i32) -> usize { + cast! { + focus_index => i32, + self.windows.len() => i32 as windows_len, + } + (focus_index + direction).clamp(0, windows_len - 1) as usize + } + fn swap_current(&mut self, direction: i32) { if let Some(focus_index) = self.logged_focus_index() { - #[allow( - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::cast_possible_wrap, - reason = "to add a potential negative number to a usize" - )] - let other_swap_index = - (focus_index as i32 + direction).clamp(0, self.windows.len() as i32 - 1) as usize; + let other_swap_index = self.compute_index_for_direction(focus_index, direction); self.windows.swap(focus_index, other_swap_index); } } @@ -113,14 +114,7 @@ impl ScrollTiler { fn focus(&self, direction: i32) { if let Some(focus_index) = self.focus_index_with_fallback_and_log() { - #[allow( - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::cast_possible_wrap, - reason = "to add a potential negative number to a usize" - )] - let new_focus_index = - (focus_index as i32 + direction).clamp(0, self.windows.len() as i32 - 1) as usize; + let new_focus_index = self.compute_index_for_direction(focus_index, direction); let window = self.windows[new_focus_index].inner; let _ = window @@ -139,14 +133,14 @@ impl ScrollTiler { // TODO: find a better solution for this, the problem is that the scroll system // doesn't handle windows that are equals or bigger than the screen size well. // Fix for now: prevent windows width from being equal or bigger than screen size. - self.windows[focus_index].width = screen_width - self.padding * 2 - 1; + self.windows[focus_index].width = self.padding.mul_add(-2.0, screen_width) - 1.0; } } pub fn set_current_window_halfscreen(&mut self) { if let Some(focus_index) = self.focus_index() { let screen_width = self.screen_size.width(); - self.windows[focus_index].width = (screen_width / 2) - self.padding * 2; + self.windows[focus_index].width = self.padding.mul_add(-2.0, screen_width / 2.0); } } @@ -162,21 +156,22 @@ impl ScrollTiler { /// Direction should be 1 for increasing width and -1 for decreasing width. fn resize_current_window_width_by_resize_increment(&mut self, direction: i32) { if let Some(focus_index) = self.focus_index() { + // TODO: check explanation in `set_current_window_fullscreen` about -1 cast! { - self.windows[focus_index].width => i32 as current_width, - self.resize_increment => i32 as resize_increment, - self.screen_size.width() => i32 as screen_width, - self.padding => i32 as padding, + direction.signum() => f32 as direction, } - // TODO: check explanation in `set_current_window_fullscreen` about -1 - let new_width = (current_width + resize_increment * direction.signum()) - .max(0) - .min(screen_width - padding * 2 - 1); - self.windows[focus_index].width = new_width.cast(); + let new_width = self + .resize_increment + .mul_add(direction, self.windows[focus_index].width) + .clamp( + 0.0, + self.padding.mul_add(-2.0, self.screen_size().width()) - 1.0, + ); + self.windows[focus_index].width = new_width; } } - pub fn current_window(&self) -> Option { + pub fn focused_window(&self) -> Option { self.focus_index().map(|index| self.windows[index].inner) } @@ -195,7 +190,7 @@ impl ScrollTiler { let previous_scroll_offset = self.scroll_offset; self.ajust_scroll(&windows_positions); - if previous_scroll_offset != self.scroll_offset { + if (previous_scroll_offset - self.scroll_offset).abs() > 1.0 { debug!( "Adjusted scroll offset from {} to {}", previous_scroll_offset, self.scroll_offset @@ -216,8 +211,9 @@ impl ScrollTiler { /// If the focused window is tiled, new windows are appended after it. /// Otherwise, they are appended at the end. fn append_new_windows(&mut self, windows_snapshot: &HashSet) { - if let Some(focus_index) = self.focus_index().or(self.previously_focused_window_index) - && !self.windows.is_empty() + if !self.windows.is_empty() + && let Some(focus_index) = self.focus_index().or(self.previously_focused_window_index) + && focus_index < self.windows.len() { for window in windows_snapshot { if !self @@ -226,7 +222,10 @@ impl ScrollTiler { .any(|window_item| window_item.inner == *window) { log::info!("Adding after focused {focus_index}"); - self.insert_window_after(*window, focus_index); + self.windows.insert( + focus_index + 1, + WindowItem::new(*window, self.default_size()), + ); } } } else { @@ -236,31 +235,21 @@ impl ScrollTiler { .iter() .any(|window_item| window_item.inner == *window) { - log::info!("Appending at end"); - self.append_window(*window); + self.windows + .push(WindowItem::new(*window, self.default_size())); } } } } - fn default_size(&self) -> u32 { - self.screen_size.width() / 2 - self.padding * 2 - } - - fn append_window(&mut self, window: Window) { - self.windows - .push(WindowItem::new(window, self.default_size())); - } - - fn insert_window_after(&mut self, window: Window, index: usize) { - self.windows - .insert(index + 1, WindowItem::new(window, self.default_size())); + fn default_size(&self) -> f32 { + self.padding.mul_add(-2.0, self.screen_size.width() / 2.0) } - fn layout_windows(&mut self, windows_positions: &[i32]) { + fn layout_windows(&mut self, windows_positions: &[f32]) { for (window, x) in self.windows.iter_mut().zip(windows_positions) { - let y = self.padding.cast(); - let height = self.screen_size.height() - self.padding * 2; + let y = self.padding; + let height = self.padding.mul_add(-2.0, self.screen_size.height()); if let Err(e) = window.inner.move_to( [x - self.scroll_offset, y].into(), [window.width, height].into(), @@ -286,39 +275,32 @@ impl ScrollTiler { let actual_size = window_rect.right - window_rect.left; - cast! { - actual_size => u32, - } - let expected_size = window.width; if expected_size < actual_size { - window.width = actual_size + self.padding * 2; + window.width = self.padding.mul_add(2.0, actual_size); } } } - fn ajust_scroll(&mut self, windows_positions: &[i32]) { + fn ajust_scroll(&mut self, windows_positions: &[f32]) { if let Some((index, focused_window)) = self .windows .iter() .enumerate() .find(|(_, window_item)| window_item.inner.is_focused().unwrap_or(false)) { - cast! { - self.padding => i32 as padding, - focused_window.width => i32 as focused_window_width, - self.screen_size.width() => i32 as screen_width, - } - - let focused_window_left = windows_positions[index] - padding - self.scroll_offset; - let focused_window_right = focused_window_left + focused_window_width + padding * 2; + let focused_window_left = windows_positions[index] - self.padding - self.scroll_offset; + let focused_window_right = self + .padding + .mul_add(2.0, focused_window_left + focused_window.width); - if focused_window_left >= 0 && focused_window_right <= screen_width { + if focused_window_left >= 0.0 && focused_window_right <= self.screen_size.width() { return; } let window_left_to_screen_left = focused_window_left.abs(); - let window_right_to_screen_right = focused_window_right.sub(screen_width).abs(); + let window_right_to_screen_right = + focused_window_right.sub(self.screen_size.width()).abs(); if window_left_to_screen_left < window_right_to_screen_right { self.scroll_offset -= window_left_to_screen_left; @@ -328,22 +310,14 @@ impl ScrollTiler { } } - pub fn windows_positions(&self) -> Vec { + pub fn windows_positions(&self) -> Vec { let mut positions = Vec::new(); - let mut current_position = 0; - - cast! { - self.padding => i32 as padding, - } + let mut current_position = 0.0; for window in &self.windows { - cast! { - window.width => i32 as window_width, - } - - current_position += padding; + current_position += self.padding; positions.push(current_position); - current_position += window_width + padding; + current_position += window.width + self.padding; } positions diff --git a/src/system.rs b/src/system.rs index a5357f5..16696f4 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,27 +1,34 @@ use log::warn; use windows::Win32::{ Graphics::Gdi::{COLOR_HIGHLIGHT, GetSysColor}, - UI::WindowsAndMessaging::{GetSystemMetrics, SM_CXSCREEN, SM_CYSCREEN}, + UI::WindowsAndMessaging::{GetDesktopWindow, GetSystemMetrics, SM_CXSCREEN, SM_CYSCREEN}, }; use crate::{ - utils::{Position, Size, cast::FaillibleCastUtils, color::Color}, + utils::math::{Position, Size}, wincall_into_result, - window::{Window, filter::is_managed_window}, + window::{self, Window}, }; pub fn screen_size() -> anyhow::Result { - Ok([ - wincall_into_result!(GetSystemMetrics(SM_CXSCREEN).try_cast()?)?, - wincall_into_result!(GetSystemMetrics(SM_CYSCREEN).try_cast()?)?, - ] - .into()) + #[allow( + clippy::cast_precision_loss, + reason = "The values will stay within screen size orders of magnitude" + )] + Ok(Size([ + wincall_into_result!(GetSystemMetrics(SM_CXSCREEN))? as f32, + wincall_into_result!(GetSystemMetrics(SM_CYSCREEN))? as f32, + ])) } -pub fn highlight_color() -> anyhow::Result { - wincall_into_result!(GetSysColor(COLOR_HIGHLIGHT)) - .map(Color::from_abgr_packed) - .map(Color::without_alpha) +pub fn highlight_color() -> anyhow::Result { + // argb + let packed = wincall_into_result!(GetSysColor(COLOR_HIGHLIGHT))?; + + let r = (packed & 0x0000_00FF) as u8; + let g = ((packed & 0x0000_FF00) >> 8) as u8; + let b = ((packed & 0x00FF_0000) >> 16) as u8; + Ok(iced::Color::from_rgb8(r, g, b)) } pub fn restore_windows() { @@ -30,13 +37,17 @@ pub fn restore_windows() { vec![] }); - windows.retain(|w| is_managed_window(*w).unwrap_or(false)); + windows.retain(|w| window::filter::should_be_tiled(*w).unwrap_or(false)); - let mut pos = Position([100, 100]); + let mut pos = Position([100.0, 100.0]); for window in windows { - if let Err(err) = window.move_to(pos, [800, 600].into()) { + if let Err(err) = window.move_to(pos, [800.0, 600.0].into()) { warn!("Failed to move window {window:?}: {err}"); } - pos += 100; + pos += 100.0; } } + +pub fn get_desktop_window() -> anyhow::Result { + Window::from_hwnd(wincall_into_result!(GetDesktopWindow())?) +} diff --git a/src/thumbnail.rs b/src/thumbnail.rs deleted file mode 100644 index bab127f..0000000 --- a/src/thumbnail.rs +++ /dev/null @@ -1,69 +0,0 @@ -use log::debug; - -use crate::{ - cast, f, - utils::{ - Position, Size, - frac::{self, f}, - }, - window::Window, -}; - -pub struct ThumbnailData { - pub pos: Position, - pub size: Size, -} - -pub struct WindowData { - pub inner: Window, - pub width: u32, -} - -pub fn create_thumbnails_from_tiler_windows( - windows: &[WindowData], - screen_size: Size, - padding: u32, -) -> Vec { - // Width of packed windows - let total_tiler_width = windows.iter().map(|w| w.width + padding).sum::() - padding; - - let reduction_ratio = f(screen_size.width(), total_tiler_width); - - debug!("Thumbnail reduction ratio: {reduction_ratio}"); - - let reduction_ratio = match reduction_ratio.category() { - frac::Category::Upside => reduction_ratio * f!(6 / 10), - frac::Category::Downside => reduction_ratio * f!(9 / 10), - frac::Category::Equal => reduction_ratio, - }; - - let mut current_x = 0; - let mut thumbnails = Vec::new(); - - let thumbnail_height = reduction_ratio * screen_size.height(); - let thumbnail_y = screen_size.height().abs_diff(thumbnail_height) / 2; - let thumbnail_x_center_offset = screen_size - .width() - .abs_diff(reduction_ratio * total_tiler_width) - / 2; - - cast! { - thumbnail_y => i32, - thumbnail_x_center_offset => i32, - padding => i32, - } - - for window in windows { - let width = reduction_ratio * window.width; - thumbnails.push(ThumbnailData { - pos: [current_x + thumbnail_x_center_offset, thumbnail_y].into(), - size: [width, thumbnail_height].into(), - }); - cast! { - width => i32, - } - current_x += width + padding; - } - - thumbnails -} diff --git a/src/utils/cast.rs b/src/utils/cast.rs index 60fc3c2..74559bb 100644 --- a/src/utils/cast.rs +++ b/src/utils/cast.rs @@ -1,70 +1,16 @@ -use std::fmt::Display; - -#[easy_ext::ext(FaillibleCastUtils)] -pub impl T -where - T: TryInto + Display + Clone + Copy, -{ - fn cast(self) -> R { - self.try_cast().expect("Cast failed") - } - - fn try_cast(self) -> anyhow::Result { - self.try_into().map_err(|_| { - anyhow::anyhow!( - "Cast from {} with value {self} to {} failed", - std::any::type_name::(), - std::any::type_name::() - ) - }) - } -} - -#[easy_ext::ext(InfaillibleCastUtils)] -pub impl T -where - T: Into + Display + Clone + Copy, -{ - fn infaillible_cast(self) -> R { - self.into() - } -} - #[macro_export] macro_rules! cast { ($src:expr => $t:ty as $dest:ident, $($rem:tt)*) => { - let $dest: $t = $crate::utils::cast::FaillibleCastUtils::cast($src); + #[allow(clippy::cast_possible_wrap)] + #[allow(clippy::cast_precision_loss)] + let $dest: $t = $src as $t; cast!($($rem)*); }; ($i:ident => $t:ty, $($rem:tt)*) => { - let $i: $t = $crate::utils::cast::FaillibleCastUtils::cast($i); + #[allow(clippy::cast_possible_wrap)] + #[allow(clippy::cast_precision_loss)] + let $i: $t = $i as $t; cast!($($rem)*); }; () => {}; } - -#[macro_export] -macro_rules! try_cast { - ($src:expr => $t:ty as $dest:ident, $($rem:tt)*) => { - let $dest: $t = $crate::utils::cast::FaillibleCastUtils::try_cast($src)?; - try_cast!($($rem)*); - }; - ($i:ident => $t:ty, $($rem:tt)*) => { - let $i: $t = $crate::utils::cast::FaillibleCastUtils::try_cast($i)?; - try_cast!($($rem)*); - }; - () => {}; -} - -#[macro_export] -macro_rules! infaillible_cast { - ($src:expr => $t:ty as $dest:ident, $($rem:tt)*) => { - let $dest: $t = $crate::utils::cast::InfaillibleCastUtils::infaillible_cast($src); - infaillible_cast!($($rem)*); - }; - ($i:ident => $t:ty, $($rem:tt)*) => { - let $i: $t = $crate::utils::cast::InfaillibleCastUtils::infaillible_cast($i); - infaillible_cast!($($rem)*); - }; - () => {}; -} diff --git a/src/utils/color.rs b/src/utils/color.rs deleted file mode 100644 index f7208a2..0000000 --- a/src/utils/color.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Color { - pub r: u8, - pub g: u8, - pub b: u8, - pub a: u8, -} - -impl Color { - pub const fn from_abgr_packed(abgr: u32) -> Self { - Self { - a: ((abgr >> 24) & 0xFF) as u8, - b: ((abgr >> 16) & 0xFF) as u8, - g: ((abgr >> 8) & 0xFF) as u8, - r: (abgr & 0xFF) as u8, - } - } - - pub const fn without_alpha(self) -> Self { - Self { - r: self.r, - g: self.g, - b: self.b, - a: 255, - } - } -} diff --git a/src/utils/frac.rs b/src/utils/frac.rs deleted file mode 100644 index a49fde6..0000000 --- a/src/utils/frac.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::fmt::Display; - -use crate::infaillible_cast; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Frac { - pub numerator: u32, - pub denominator: u32, -} - -impl Display for Frac { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.as_f32()) - } -} - -pub enum Category { - /// val > 1 - Upside, - /// val < 1 - Downside, - /// val == 1 - Equal, -} - -pub const fn f(numerator: u32, denominator: u32) -> Frac { - Frac::new(numerator, denominator) -} - -impl Frac { - pub const fn new(numerator: u32, denominator: u32) -> Self { - Self { - numerator, - denominator, - } - } - - #[allow(clippy::cast_precision_loss)] - pub fn as_f32(self) -> f32 { - self.numerator as f32 / self.denominator as f32 - } - - pub fn category(self) -> Category { - match self.numerator.cmp(&self.denominator) { - std::cmp::Ordering::Less => Category::Downside, - std::cmp::Ordering::Equal => Category::Equal, - std::cmp::Ordering::Greater => Category::Upside, - } - } - - pub const fn is_unit(self) -> bool { - self.numerator == self.denominator - } -} - -impl std::ops::Mul for Frac { - type Output = u32; - - fn mul(self, rhs: u32) -> Self::Output { - if self.is_unit() { - return rhs; - } - infaillible_cast! { - rhs => u64, - (self.numerator) => u64 as numerator, - (self.denominator) => u64 as denominator, - } - (rhs * numerator / denominator) - .try_into() - .expect("Multiplication overflowed u32") - } -} - -impl std::ops::Mul for u32 { - type Output = Self; - - fn mul(self, rhs: Frac) -> Self::Output { - rhs * self - } -} - -impl std::ops::Mul for Frac { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - if self.is_unit() { - return rhs; - } - if rhs.is_unit() { - return self; - } - Self::new( - self.numerator * rhs.numerator, - self.denominator * rhs.denominator, - ) - } -} - -#[macro_export] -macro_rules! f { - ($n:literal / $d:literal) => { - $crate::utils::frac::Frac::new($n, $d) - }; -} - -#[cfg(test)] -mod test { - #[test] - fn test_frac() { - let f = f!(1920 / 1080); - assert_eq!(f.numerator, 1920); - assert_eq!(f.denominator, 1080); - assert_eq!(f * 1080, 1920); - - let f = f!(1280 / 720); - assert_eq!(f.numerator, 1280); - assert_eq!(f.denominator, 720); - - let f = f!(1024 / 768); - assert_eq!(f.numerator, 1024); - assert_eq!(f.denominator, 768); - - let f = f!(500 / 1000); - assert_eq!(f.numerator, 500); - assert_eq!(f.denominator, 1000); - assert_eq!(20, f * 41); - } -} diff --git a/src/utils/math.rs b/src/utils/math.rs new file mode 100644 index 0000000..070915e --- /dev/null +++ b/src/utils/math.rs @@ -0,0 +1,22 @@ +use joy_vector::gen_vector; + +gen_vector!(Position with two_dim); +gen_vector!(Size with two_dim); + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Bounds { + pub left: f32, + pub top: f32, + pub right: f32, + pub bottom: f32, +} + +impl Bounds { + pub fn position(&self) -> Position { + Position([self.left, self.top]) + } + + pub fn size(&self) -> Size { + Size([self.right - self.left, self.bottom - self.top]) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 7f43a70..60aebbd 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,13 +1,5 @@ -use joy_vector::gen_vector; - -use crate::utils::cast::FaillibleCastUtils; - pub mod cast; -pub mod color; -pub mod frac; -pub mod winapi; - -pub const IS_DEBUG: bool = cfg!(debug_assertions); +pub mod math; #[macro_export] macro_rules! function { @@ -21,27 +13,50 @@ macro_rules! function { }}; } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Bounds { - pub left: i32, - pub top: i32, - pub right: i32, - pub bottom: i32, -} +// Code from near_o11y crate: https://github.com/near/nearcore and then adapted for my needs +pub mod invariants { + /// + /// If assert fails, panic on debug, and log error on release + /// + #[macro_export] + macro_rules! assert_log { + ($cond:expr) => { + $crate::assert_log!($cond, "assertion failed: {}", stringify!($cond)) + }; + + ($cond:expr, $fmt:literal $($arg:tt)*) => { + if cfg!(debug_assertions) { + assert!($cond, $fmt $($arg)*); + } else { + if !$cond { + log::error!($fmt $($arg)*); + } + } + }; + } -impl Bounds { - pub fn position(&self) -> Position { - [self.left, self.top].into() + #[macro_export] + macro_rules! assert_log_bail { + ($cond:expr) => { + $crate::assert_log_bail!($cond, "assertion failed: {}", stringify!($cond)) + }; + + ($cond:expr, $fmt:literal $($arg:tt)*) => { + if cfg!(debug_assertions) { + assert!($cond, $fmt $($arg)*); + } else { + if !$cond { + log::error!($fmt $($arg)*); + return; + } + } + }; } - pub fn size(&self) -> Size { - [ - (self.right - self.left).cast(), - (self.bottom - self.top).cast(), - ] - .into() + #[macro_export] + macro_rules! assert_log_fail { + ($fmt:literal $($arg:tt)*) => { + $crate::assert_log!(false, $fmt $($arg)*) + }; } } - -gen_vector!(Position with two_dim); -gen_vector!(Size with two_dim); diff --git a/src/utils/winapi.rs b/src/winapi.rs similarity index 75% rename from src/utils/winapi.rs rename to src/winapi.rs index 6a6a092..8172351 100644 --- a/src/utils/winapi.rs +++ b/src/winapi.rs @@ -1,8 +1,8 @@ use windows::Win32::Foundation::{GetLastError, SetLastError, WIN32_ERROR}; -use windows::Win32::UI::WindowsAndMessaging::{MB_OK, MessageBoxW}; +use windows::Win32::UI::WindowsAndMessaging::{MESSAGEBOX_RESULT, MESSAGEBOX_STYLE, MessageBoxW}; use windows_strings::PCWSTR; -pub fn message_box(title: &str, message: &str) { +pub fn message_box(title: &str, message: &str, kind: MESSAGEBOX_STYLE) -> MESSAGEBOX_RESULT { use std::os::windows::ffi::OsStrExt; let title_os = std::ffi::OsStr::new(&title); @@ -13,14 +13,7 @@ pub fn message_box(title: &str, message: &str) { let message = message_os.encode_wide().chain(std::iter::once(0)); let message = message.collect::>(); - unsafe { - MessageBoxW( - None, - PCWSTR(message.as_ptr()), - PCWSTR(title.as_ptr()), - MB_OK, - ); - } + unsafe { MessageBoxW(None, PCWSTR(message.as_ptr()), PCWSTR(title.as_ptr()), kind) } } pub fn clear_last_error() { @@ -42,7 +35,7 @@ macro_rules! wincall { reason = "This macro should always call a winapi function and thus is always unsafe. The caller should know that a unsafe block is automatically applied" )] unsafe { - $crate::utils::winapi::clear_last_error(); + $crate::winapi::clear_last_error(); $fn } } @@ -54,7 +47,7 @@ macro_rules! wincall_result { ($fn:expr) => { anyhow::Context::context( anyhow::Context::context($crate::wincall!($fn), $crate::function!()), - $crate::utils::winapi::last_error().unwrap_or(anyhow::anyhow!("Unknown error")), + $crate::winapi::last_error().unwrap_or(anyhow::anyhow!("Unknown error")), ) }; } @@ -63,7 +56,7 @@ macro_rules! wincall_result { macro_rules! wincall_into_result { ($fn:expr) => {{ let res = $crate::wincall!($fn); - $crate::utils::winapi::last_error().map_or_else( + $crate::winapi::last_error().map_or_else( || Ok(res), |err| anyhow::Context::context(Err(err), $crate::function!()), ) diff --git a/src/window/filter.rs b/src/window/filter.rs index 199d4d4..a9de396 100644 --- a/src/window/filter.rs +++ b/src/window/filter.rs @@ -1,6 +1,9 @@ use std::collections::HashSet; -use crate::window::{Window, manager::utils::WINRI_WINDOW_MANAGER_CLASS_NAME}; +use crate::window::Window; + +pub const WINRI_IGNORED_CLASS_NAME: &str = "Winri_IgnoreWindowClass"; +pub const WINRI_IGNORED_WINDOW_TITLE_SUBSTRING: &str = "[Winri Ignore Window]"; const IGNORED_CLASSES: &[&str] = &[ "Progman", @@ -9,7 +12,7 @@ const IGNORED_CLASSES: &[&str] = &[ "Xaml_WindowedPopupClass", "Shell_TrayWnd", "FindMyMouse", - WINRI_WINDOW_MANAGER_CLASS_NAME, + WINRI_IGNORED_CLASS_NAME, ]; const IGNORED_PROCESS_NAMES: &[&str] = &[ @@ -29,12 +32,14 @@ macro_rules! filter_out_if { }; } -pub fn is_managed_window(window: Window) -> anyhow::Result { +pub fn should_be_tiled(window: Window) -> anyhow::Result { filter_out_if!(!window.is_visible()?); filter_out_if!(window.is_cloaked()?); filter_out_if!(!window.is_ancestor()?); filter_out_if!(window.is_dialog()?); - filter_out_if!(window.title()?.is_none()); + let title = window.title()?; + filter_out_if!(title.is_none()); + filter_out_if!(title.is_some_and(|title| title.contains(WINRI_IGNORED_WINDOW_TITLE_SUBSTRING))); filter_out_if!(IGNORED_CLASSES.contains(&window.class()?.as_str())); filter_out_if!(IGNORED_PROCESS_NAMES.contains(&window.process_name()?.as_str())); filter_out_if!(!window.is_valid()?); @@ -45,7 +50,7 @@ pub fn is_managed_window(window: Window) -> anyhow::Result { pub fn opened_windows() -> anyhow::Result> { let windows = Window::enumerate()? .into_iter() - .filter(|window| is_managed_window(*window).unwrap_or(false)) + .filter(|window| should_be_tiled(*window).unwrap_or(false)) .collect::>(); Ok(windows) diff --git a/src/window/manager.rs b/src/window/manager.rs deleted file mode 100644 index 3076f62..0000000 --- a/src/window/manager.rs +++ /dev/null @@ -1,632 +0,0 @@ -use std::{collections::HashMap, num::NonZero, sync::mpsc::Sender, thread}; - -use anyhow::{Context, anyhow}; -use channel_protocol::channel_protocol; -use log::{debug, info}; -use windows::Win32::{ - Foundation::RECT, - Graphics::Dwm::{ - DWM_THUMBNAIL_PROPERTIES, DWM_TNP_RECTDESTINATION, DWM_TNP_VISIBLE, DwmRegisterThumbnail, - DwmUnregisterThumbnail, DwmUpdateThumbnailProperties, - }, - UI::WindowsAndMessaging::{ - GWL_EXSTYLE, SW_HIDE, SWP_SHOWWINDOW, SetWindowLongPtrW, SetWindowPos, ShowWindow, - WS_EX_NOACTIVATE, - }, -}; -use winit::{ - application::ApplicationHandler, - dpi::{PhysicalPosition, PhysicalSize}, - event::WindowEvent, - event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}, - platform::windows::EventLoopBuilderExtWindows, - window::WindowId, -}; - -use crate::{ - Event, try_cast, - utils::{Position, Size, cast::FaillibleCastUtils, color::Color}, - wincall_into_result, wincall_result, - window::{ - Window, - manager::utils::{WindowUtils, create_new_border_window}, - }, -}; - -#[derive(Debug)] -pub struct BorderStyle { - pub color: Color, - pub thickness: u8, - pub radius: u8, -} - -pub type ThumbnailId = isize; - -#[channel_protocol] -pub trait InputProtocol { - fn create_thumbnail(src: Window, at: Position, size: Size) -> anyhow::Result; - fn close_all_thumbnails() -> anyhow::Result<()>; - fn border_thumbnail(id: ThumbnailId) -> anyhow::Result<()>; - fn unborder_all_thumbnails() -> anyhow::Result<()>; - fn border_tiler_window(window: Window) -> anyhow::Result<()>; - fn unborder_tiler_window() -> anyhow::Result<()>; -} - -#[channel_protocol] -pub trait OutputProtocol { - fn cursor_entered_thumbnail(id: ThumbnailId); - fn cursor_exited_thumbnail(id: ThumbnailId); - fn thumbnail_clicked(id: ThumbnailId); - - fn unrecoverable_error(err: anyhow::Error); -} - -struct Thumbnail { - id: isize, - window: winit::window::Window, -} - -struct Border(winit::window::Window); - -impl Border { - pub const fn window(&self) -> &winit::window::Window { - &self.0 - } -} - -pub struct App { - thumbnails: HashMap, - thumbnail_border_style: BorderStyle, - thumbnail_border: Option, - - tiler_border_style: BorderStyle, - tiler_border: Option, - - context: softbuffer::Context, - output_client: OutputProtocolClient, -} - -impl HandleInputProtocolWithState<&ActiveEventLoop> for App { - fn create_thumbnail( - &mut self, - src: Window, - at: Position, - size: Size, - event_loop: &ActiveEventLoop, - ) -> anyhow::Result { - let window = utils::create_window( - event_loop, - winit::window::WindowAttributes::default() - .with_title("thumbnail") - .with_active(false) - .with_position(PhysicalPosition::new(at.x(), at.y())) - .with_inner_size(PhysicalSize::new(size.width(), size.height())) - .with_visible(false) - .with_decorations(false), - )?; - - let thumbnail_hwnd = window.hwnd()?; - - wincall_into_result!(SetWindowLongPtrW( - thumbnail_hwnd, - GWL_EXSTYLE, - WS_EX_NOACTIVATE.0.try_cast()? - ))?; - - let thumbnail_id = wincall_result!(DwmRegisterThumbnail(thumbnail_hwnd, src.handle()))?; - - let thumbnail_props = DWM_THUMBNAIL_PROPERTIES { - dwFlags: DWM_TNP_RECTDESTINATION | DWM_TNP_VISIBLE, - rcDestination: RECT { - left: 0, - top: 0, - right: size.width().try_cast()?, - bottom: size.height().try_cast()?, - }, - rcSource: RECT { - left: 0, - top: 0, - right: 0, - bottom: 0, - }, - opacity: 0, - fVisible: true.into(), - fSourceClientAreaOnly: true.into(), - }; - - wincall_result!(DwmUpdateThumbnailProperties( - thumbnail_id, - &raw const thumbnail_props - ))?; - - { - let win = window.to_crate_window()?; - win.show()?; - win.set_max_zindex()?; - } - window.set_visible(true); - - self.thumbnails.insert( - thumbnail_id, - Thumbnail { - window, - id: thumbnail_id, - }, - ); - - Ok(thumbnail_id) - } - - fn close_all_thumbnails(&mut self, event_loop: &ActiveEventLoop) -> anyhow::Result<()> { - self.unborder_all_thumbnails(event_loop)?; - - for Thumbnail { id, .. } in self.thumbnails.values() { - wincall_result!(DwmUnregisterThumbnail(*id))?; - } - - self.thumbnails.clear(); - Ok(()) - } - - fn border_thumbnail(&mut self, id: ThumbnailId, _: &ActiveEventLoop) -> anyhow::Result<()> { - let thumbnail = self.thumbnails.get(&id).context(id)?; - let border = self.get_initialized_thumbnail_border()?; - - self.border_window( - thumbnail.window.to_crate_window()?, - border, - &self.thumbnail_border_style, - )?; - - Ok(()) - } - - fn unborder_all_thumbnails(&mut self, _: &ActiveEventLoop) -> anyhow::Result<()> { - let window = self.get_initialized_thumbnail_border()?.window(); - - window.set_visible(false); // winit set_visible doesn't work. So we use ShowWindow directly. But to maintain state consistency inside winit we also call set_visible. - let _ = wincall_into_result!(ShowWindow(window.hwnd()?, SW_HIDE))?; - - Ok(()) - } - - fn border_tiler_window( - &mut self, - window: Window, - _state: &ActiveEventLoop, - ) -> anyhow::Result<()> { - let border_window = self.get_initialized_tiler_border()?; - - self.border_window(window, border_window, &self.tiler_border_style)?; - - Ok(()) - } - - fn unborder_tiler_window(&mut self, _state: &ActiveEventLoop) -> anyhow::Result<()> { - let border_window = self.get_initialized_tiler_border()?.window(); - border_window.set_visible(false); - let _ = wincall_into_result!(ShowWindow(border_window.hwnd()?, SW_HIDE))?; - Ok(()) - } -} - -impl App { - fn find_thumbnail_by_window_id(&self, window_id: WindowId) -> Option<&Thumbnail> { - self.thumbnails - .iter() - .find(|(_, thumbnail)| thumbnail.window.id() == window_id) - .map(|(_, thumbnail)| thumbnail) - } - - fn get_initialized_thumbnail_border(&self) -> anyhow::Result<&Border> { - let border_window = self - .thumbnail_border - .as_ref() - .context("Uninitialized thumbnail border")?; - Ok(border_window) - } - - fn get_initialized_tiler_border(&self) -> anyhow::Result<&Border> { - let border_window = self - .tiler_border - .as_ref() - .context("Uninitialized tiler border")?; - Ok(border_window) - } - - fn handle_window_event(&self, event: &WindowEvent, window_id: WindowId) { - let Some(thumbnail) = self.find_thumbnail_by_window_id(window_id) else { - return; - }; - - match event { - WindowEvent::CursorEntered { .. } => { - self.output_client.cursor_entered_thumbnail(thumbnail.id); - } - WindowEvent::CursorLeft { .. } => { - self.output_client.cursor_exited_thumbnail(thumbnail.id); - } - WindowEvent::MouseInput { state, button, .. } => { - if *state == winit::event::ElementState::Pressed - && *button == winit::event::MouseButton::Left - { - self.output_client.thumbnail_clicked(thumbnail.id); - } - } - _ => {} - } - } - - fn border_window( - &self, - dest: Window, - border: &Border, - border_style: &BorderStyle, - ) -> anyhow::Result<()> { - let dest_bounds = dest.desktop_manager_bounds()?; - let Position([dest_x, dest_y]) = dest_bounds.position(); - let Size([dest_width, dest_height]) = dest_bounds.size(); - - try_cast! { - border_style.thickness => i32 as thickness, - dest_width => i32, - dest_height => i32, - } - - let border_window_x = dest_x - thickness; - let border_window_y = dest_y - thickness; - let border_window_width = dest_width + thickness * 2; - let border_window_height = dest_height + thickness * 2; - - let border_window = border.window().to_crate_window()?; - - border_window.move_to( - [border_window_x, border_window_y].into(), - [ - border_window_width.try_cast()?, - border_window_height.try_cast()?, - ] - .into(), - )?; - - wincall_result!(SetWindowPos( - border_window.handle(), - Some(dest.handle()), - border_window_x, - border_window_y, - border_window_width, - border_window_height, - SWP_SHOWWINDOW, - ))?; - - border.window().set_visible(true); - - border_window.set_max_zindex()?; - dest.set_max_zindex()?; - - self.prepare_border_surface(border, border_style)?; - - Ok(()) - } - - fn prepare_border_surface( - &self, - border: &Border, - border_style: &BorderStyle, - ) -> anyhow::Result<()> { - let border_window = border.window(); - - let mut surface = softbuffer::Surface::new(&self.context, &border_window) - .map_err(|e| anyhow!("{e:?}")) - .context("Softbuffer surface creation for border window")?; - - surface - .resize( - NonZero::new(border_window.inner_size().width).context("border window width")?, - NonZero::new(border_window.inner_size().height).context("border window height")?, - ) - .map_err(|e| anyhow!("{e:?}")) - .context("Softbuffer surface resize to match border window")?; - - let mut buffer = surface - .buffer_mut() - .map_err(|e| anyhow!("{e:?}")) - .context("border window surface mutable buffer extraction")?; - - buffer.fill(0); - - let border_pixmap = utils::draw_border( - border - .window() - .to_crate_window()? - .desktop_manager_bounds()? - .size(), - border_style, - ); - - for (out, [r, g, b, a]) in buffer - .iter_mut() - .zip(border_pixmap.data().as_chunks().0.iter()) - { - let color = u32::from_be_bytes([*a, *r, *g, *b]); - *out = color; - } - - buffer - .present() - .map_err(|e| anyhow!("{e:?}")) - .context("Border window buffer presentation")?; - - Ok(()) - } - - fn initialize_thumbnail_border_window( - &mut self, - event_loop: &ActiveEventLoop, - ) -> anyhow::Result<()> { - let border_window = create_new_border_window(event_loop)?; - self.thumbnail_border = Some(Border(border_window)); - Ok(()) - } - - fn create_tiler_border_window(&mut self, event_loop: &ActiveEventLoop) -> anyhow::Result<()> { - let border_window = create_new_border_window(event_loop)?; - self.tiler_border = Some(Border(border_window)); - Ok(()) - } -} - -impl ApplicationHandler for App { - fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - info!("Window manager started"); - if let Err(e) = self - .initialize_thumbnail_border_window(event_loop) - .and_then(|()| self.create_tiler_border_window(event_loop)) - { - self.output_client.unrecoverable_error(e); - } - } - - fn user_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - message: InputProtocolMessage, - ) { - debug!("Window manager recieved event {message:#?}"); - self.dispatch_with_state(message, event_loop); - } - - fn window_event( - &mut self, - _event_loop: &winit::event_loop::ActiveEventLoop, - window_id: WindowId, - event: winit::event::WindowEvent, - ) { - self.handle_window_event(&event, window_id); - } -} - -pub fn launch( - event_tx: Sender, - thumbnail_border_style: BorderStyle, - tiler_border_style: BorderStyle, -) -> anyhow::Result { - let (input_client, input_rx) = InputProtocolClient::new(); - let (output_client, output_rx) = OutputProtocolClient::new(); - - thread::Builder::new() - .name("Window manager".into()) - .spawn(move || { - let event_loop = EventLoop::with_user_event() - .with_any_thread(true) - .build() - .expect("Window manager event loop"); - - let event_sender = event_loop.create_proxy(); - - thread::spawn(move || { - for input_event in input_rx { - event_sender.send_event(input_event).unwrap(); - } - }); - - let context = softbuffer::Context::new(event_loop.owned_display_handle()) - .map_err(|e| anyhow!("{e:?}")) - .context("Softbuffer context creation for window manager"); - - let context = match context { - Ok(c) => c, - Err(err) => { - output_client.unrecoverable_error(err); - return; - } - }; - - event_loop - .run_app(&mut App { - thumbnails: HashMap::default(), - thumbnail_border_style, - thumbnail_border: None, - tiler_border_style, - tiler_border: None, - context, - output_client, - }) - .expect("Window manager execution"); - })?; - - thread::Builder::new() - .name("window manager output event mapper".into()) - .spawn(move || { - for output_event in output_rx { - event_tx.send(Event::WindowManager(output_event)).unwrap(); - } - })?; - - Ok(input_client) -} - -pub mod utils { - use anyhow::bail; - use raw_window_handle::HasWindowHandle; - use tiny_skia::{Pixmap, Stroke}; - use windows::Win32::{ - Foundation::HWND, - UI::WindowsAndMessaging::{ - GWL_EXSTYLE, GWL_STYLE, SetWindowLongPtrW, WS_EX_NOACTIVATE, WS_POPUP, - }, - }; - use winit::{ - event_loop::ActiveEventLoop, platform::windows::WindowAttributesExtWindows, - window::WindowAttributes, - }; - - use crate::{ - utils::{Size, cast::FaillibleCastUtils}, - wincall_into_result, - window::manager::BorderStyle, - }; - - pub const WINRI_WINDOW_MANAGER_CLASS_NAME: &str = "WinriWindowManagerWindow"; - - #[easy_ext::ext(WindowUtils)] - impl winit::window::Window { - pub fn hwnd(&self) -> anyhow::Result { - let handle = self.window_handle()?; - let handle = match handle.as_raw() { - raw_window_handle::RawWindowHandle::Win32(win32_window_handle) => { - win32_window_handle.hwnd - } - _ => bail!("Unsupported platform"), - }; - let handle = handle.get() as *mut std::ffi::c_void; - Ok(HWND(handle)) - } - - pub fn to_crate_window(&self) -> anyhow::Result { - crate::window::Window::from_hwnd(self.hwnd()?) - } - } - - pub fn create_window( - event_loop: &ActiveEventLoop, - attrib: WindowAttributes, - ) -> anyhow::Result { - let attrib = attrib.with_class_name(WINRI_WINDOW_MANAGER_CLASS_NAME); - let create_window = event_loop.create_window(attrib)?; - Ok(create_window) - } - - pub fn create_new_border_window( - event_loop: &ActiveEventLoop, - ) -> anyhow::Result { - let border_window = create_window( - event_loop, - WindowAttributes::default() - .with_visible(false) - .with_decorations(false) - .with_transparent(true), - )?; - - let hwnd = border_window.hwnd()?; - - wincall_into_result!(SetWindowLongPtrW(hwnd, GWL_STYLE, WS_POPUP.0.try_cast()?))?; - - wincall_into_result!(SetWindowLongPtrW( - hwnd, - GWL_EXSTYLE, - WS_EX_NOACTIVATE.0.try_cast()? - ))?; - - Ok(border_window) - } - - pub fn draw_border(border_window_size: Size, border_style: &BorderStyle) -> Pixmap { - let mut pixmap = Pixmap::new(border_window_size.width(), border_window_size.height()) - .expect("Failed to create border pixmap"); - - let border_color = tiny_skia::Color::from_rgba8( - border_style.color.r, - border_style.color.g, - border_style.color.b, - border_style.color.a, - ); - - let mut paint = tiny_skia::Paint::default(); - paint.set_color(border_color); - paint.anti_alias = true; - - let stroke = Stroke { - width: (border_style.thickness + 1).cast(), - ..Default::default() - }; - - #[allow(clippy::cast_precision_loss)] - let w = pixmap.width() as f32; - #[allow(clippy::cast_precision_loss)] - let h = pixmap.height() as f32; - - let border_radius = border_style.radius.cast(); - - let mut path = tiny_skia::PathBuilder::new(); - - let half_stroke_width = stroke.width / 2.0; - - // top edge - path.move_to(border_radius, half_stroke_width); - path.line_to(w - border_radius, half_stroke_width); - path.cubic_to( - w - half_stroke_width, - half_stroke_width, - w - half_stroke_width, - border_radius, - w - half_stroke_width, - border_radius, - ); - - // right edge - path.line_to(w - half_stroke_width, h - border_radius); - - path.cubic_to( - w - half_stroke_width, - h - half_stroke_width, - w - border_radius, - h - half_stroke_width, - w - border_radius, - h - half_stroke_width, - ); - - // bottom edge - path.line_to(border_radius, h - half_stroke_width); - path.cubic_to( - half_stroke_width, - h - half_stroke_width, - half_stroke_width, - h - border_radius, - half_stroke_width, - h - border_radius, - ); - - // left edge - path.line_to(half_stroke_width, border_radius); - path.cubic_to( - half_stroke_width, - half_stroke_width, - border_radius, - half_stroke_width, - border_radius, - half_stroke_width, - ); - let path = path.finish().expect("Failed to create border path"); - - pixmap.stroke_path( - &path, - &paint, - &stroke, - tiny_skia::Transform::identity(), - None, - ); - - pixmap - } -} diff --git a/src/window/mod.rs b/src/window/mod.rs index 0eb51fd..5618e88 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -1,5 +1,4 @@ pub mod filter; -pub mod manager; use std::{ffi::c_void, hash::Hash, thread, time::Duration}; @@ -15,24 +14,23 @@ use windows::{ Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ}, }, UI::WindowsAndMessaging::{ - EnumWindows, GA_ROOT, GWL_STYLE, GetAncestor, GetClassNameW, GetClientRect, - GetWindowLongW, GetWindowRect, GetWindowTextLengthW, GetWindowTextW, + EnumWindows, GA_ROOT, GWL_EXSTYLE, GWL_STYLE, GetAncestor, GetClassNameW, + GetClientRect, GetWindowLongW, GetWindowRect, GetWindowTextLengthW, GetWindowTextW, GetWindowThreadProcessId, HWND_TOP, IsIconic, IsWindow, IsWindowVisible, MoveWindow, PostMessageW, SW_RESTORE, SW_SHOW, SWP_NOMOVE, SWP_NOSIZE, SetForegroundWindow, - SetWindowPos, ShowWindow, WINDOW_LONG_PTR_INDEX, WINDOW_STYLE, WM_CLOSE, WS_DLGFRAME, - WS_POPUP, + SetWindowLongPtrW, SetWindowPos, ShowWindow, WINDOW_LONG_PTR_INDEX, WINDOW_STYLE, + WM_CLOSE, WS_DLGFRAME, WS_EX_NOACTIVATE, WS_POPUP, }, }, core::BOOL, }; use crate::{ - try_cast, - utils::{Bounds, Position, Size, cast::FaillibleCastUtils}, + utils::math::{Bounds, Position, Size}, wincall_into_result, wincall_result, }; -pub type SafeHWND = isize; +pub type SafeHWND = u64; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Window { @@ -47,11 +45,15 @@ impl Hash for Window { impl From for Bounds { fn from(rect: RECT) -> Self { + #[allow( + clippy::cast_precision_loss, + reason = "The values will stay within screen size orders of magnitude" + )] Self { - left: rect.left, - top: rect.top, - right: rect.right, - bottom: rect.bottom, + left: rect.left as f32, + top: rect.top as f32, + right: rect.right as f32, + bottom: rect.bottom as f32, } } } @@ -71,10 +73,15 @@ impl Window { pub fn from_hwnd(hwnd: HWND) -> anyhow::Result { ensure!(!hwnd.is_invalid(), "Invalid window handle"); Ok(Self { - hwnd: hwnd.0 as isize, + hwnd: hwnd.0 as SafeHWND, }) } + pub fn from_safe_hwnd(safe_hwnd: SafeHWND) -> anyhow::Result { + let hwnd = HWND(safe_hwnd as *mut c_void); + Self::from_hwnd(hwnd) + } + pub fn focused() -> anyhow::Result { let hwnd = wincall_into_result!(windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow())?; @@ -243,23 +250,34 @@ impl Window { ensure_valid!(self); let [left, top, right, bottom] = self.padding()?; - try_cast! { - left => i32 as left_i32, - top => i32 as top_i32, - } - - let x = pos.x() - left_i32; - let y = pos.y() - top_i32; + let x = pos.x() - left; + let y = pos.y() - top; let w = size.width() + right + left; let h = size.height() + bottom + top; - try_cast! { - w => i32, - h => i32, - } - let _ = wincall_into_result!(ShowWindow(self.handle(), SW_RESTORE))?; - wincall_result!(MoveWindow(self.handle(), x, y, w, h, true))?; + wincall_result!(MoveWindow( + self.handle(), + x as i32, + y as i32, + w as i32, + h as i32, + true + ))?; + Ok(()) + } + + pub fn set_no_activate(self) -> anyhow::Result<()> { + ensure_valid!(self); + #[allow( + clippy::cast_possible_wrap, + reason = "Will never run on 32-bit systems" + )] + wincall_into_result!(SetWindowLongPtrW( + self.handle(), + GWL_EXSTYLE, + WS_EX_NOACTIVATE.0 as isize, + ))?; Ok(()) } @@ -275,17 +293,17 @@ impl Window { } pub fn move_offscreen(self) -> anyhow::Result<()> { + const ADDITIONAL_OFFSCREEN_OFFSET: f32 = 100.0; + ensure_valid!(self); let width = self.desktop_manager_bounds()?.size().width(); - try_cast! { - width => i32, - } + let offscreen_offset = width + ADDITIONAL_OFFSCREEN_OFFSET; wincall_result!(SetWindowPos( self.handle(), None, - -width - 100, + -offscreen_offset as i32, 0, 0, 0, @@ -335,15 +353,15 @@ impl Window { Ok(rect.into()) } - pub fn padding(self) -> anyhow::Result<[u32; 4]> { + pub fn padding(self) -> anyhow::Result<[f32; 4]> { ensure_valid!(self); let dm_rect = self.desktop_manager_bounds()?; let rect = self.outer_bounds()?; Ok([ - (rect.left - dm_rect.left).abs().try_cast()?, - (rect.top - dm_rect.top).abs().try_cast()?, - (rect.right - dm_rect.right).abs().try_cast()?, - (rect.bottom - dm_rect.bottom).abs().try_cast()?, + (rect.left - dm_rect.left).abs(), + (rect.top - dm_rect.top).abs(), + (rect.right - dm_rect.right).abs(), + (rect.bottom - dm_rect.bottom).abs(), ]) } @@ -364,7 +382,8 @@ impl Window { thread::sleep(Duration::from_millis(500)); } - // Simulate an alt key release to bypass focus stealing restrictions : https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open + // HACK: Simulate an alt key release to bypass focus stealing restrictions: + // https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open rdev::simulate(&rdev::EventType::KeyRelease(rdev::Key::Alt))?; let _ = wincall_into_result!(SetForegroundWindow(self.handle()))?; Ok(())