diff --git a/apps/maple-research/frontend/bun.lock b/apps/maple-research/frontend/bun.lock index a1bc6b3b1..35f9db86f 100644 --- a/apps/maple-research/frontend/bun.lock +++ b/apps/maple-research/frontend/bun.lock @@ -20,10 +20,10 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.100.1", "@tanstack/react-router": "^1.170.18", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "@tauri-apps/plugin-deep-link": "2.4.8", - "@tauri-apps/plugin-opener": "^2.5.4", - "@tauri-apps/plugin-os": "^2.3.2", + "@tauri-apps/plugin-opener": "^2.7.2", + "@tauri-apps/plugin-os": "^2.4.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "katex": "^0.18.2", @@ -47,7 +47,7 @@ "@eslint/js": "^9.39.4", "@noble/hashes": "^1.8.0", "@tanstack/router-plugin": "^1.168.23", - "@tauri-apps/cli": "2.11.1", + "@tauri-apps/cli": "2.12.3", "@types/bun": "1.3.13", "@types/node": "^22.19.17", "@types/react": "^18.3.28", @@ -74,7 +74,7 @@ "overrides": { "@babel/core": "^7.29.7", "@humanfs/node": "^0.16.8", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "browserslist": "^4.28.8", "flatted": "^3.4.2", "glob": "^10.5.0", @@ -444,37 +444,37 @@ "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="], - "@tauri-apps/api": ["@tauri-apps/api@2.11.0", "", {}, "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA=="], + "@tauri-apps/api": ["@tauri-apps/api@2.12.4", "", {}, "sha512-pVmfis+4zT8H0Qhp3xPZx9Ju7kpzvA6rr/PSJPyuwfvucTQ8sVt61NYU9KMkK703UFlYadFmjKA4HhybOtN0gg=="], - "@tauri-apps/cli": ["@tauri-apps/cli@2.11.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.11.1", "@tauri-apps/cli-darwin-x64": "2.11.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.1", "@tauri-apps/cli-linux-arm64-gnu": "2.11.1", "@tauri-apps/cli-linux-arm64-musl": "2.11.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.11.1", "@tauri-apps/cli-linux-x64-gnu": "2.11.1", "@tauri-apps/cli-linux-x64-musl": "2.11.1", "@tauri-apps/cli-win32-arm64-msvc": "2.11.1", "@tauri-apps/cli-win32-ia32-msvc": "2.11.1", "@tauri-apps/cli-win32-x64-msvc": "2.11.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-rpEbaJ/HzNb6fwsquwoAbq29/Vt4gADhS423A8fdkwL4edJ0wZmoB8ar7O6JPDL834MUKOCm/rrJ7c9oAaEaYQ=="], + "@tauri-apps/cli": ["@tauri-apps/cli@2.12.3", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.12.3", "@tauri-apps/cli-darwin-x64": "2.12.3", "@tauri-apps/cli-linux-arm-gnueabihf": "2.12.3", "@tauri-apps/cli-linux-arm64-gnu": "2.12.3", "@tauri-apps/cli-linux-arm64-musl": "2.12.3", "@tauri-apps/cli-linux-riscv64-gnu": "2.12.3", "@tauri-apps/cli-linux-x64-gnu": "2.12.3", "@tauri-apps/cli-linux-x64-musl": "2.12.3", "@tauri-apps/cli-win32-arm64-msvc": "2.12.3", "@tauri-apps/cli-win32-ia32-msvc": "2.12.3", "@tauri-apps/cli-win32-x64-msvc": "2.12.3" }, "bin": { "tauri": "tauri.js" } }, "sha512-f7/LsY74SVb1ChmOPw1ajOpxii0GSzDAgmWHRci8JUYuuai04rXsSbFobpmB0sP2DdlvMD/B5ZotEn8GOGMRUg=="], - "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6eEKMBXsQPCuM1EmvrjT2+aBuxWQuFdKdW8pzNuNQtpq45nEEpBlD5gr8pUeAyOU1DQKlkFaEc/MPBxb/Pfjtg=="], + "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.12.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xmVNoJOOKzKLj7GLHXmXLimRby9M42aBIJa0sB80N7o24eKV5JiVmToKTRLVH9yhL05GZ/B6rSSOmbzNFJwBAA=="], - "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-LQUO7exfRWjWALNhetph5guWpMeHphRpokOLk0OIbTTExaNwJNFu3I4vb+CCM/4G/QGoZe/5XikZOJdNEFP1ig=="], + "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.12.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-URd+0usfmUQQyb5kNPiKt2OT8gp7oGU7QR+qOppB6e35mpVBFFVNhWc9oWb3p0u71fDJ09DpF4T6m9xYxoIB8g=="], - "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-5i/awiBCRRhOUG8yjn0fMHXIWD5Ez8eEk5LtvOxyQrKuJkRaZDvnbIjZbE183blAwkoA4xN3aO/prJiqscl02Q=="], + "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.12.3", "", { "os": "linux", "cpu": "arm" }, "sha512-0kwUVx2LbmOoTRlwF1oDww7th4/NSluv4ZBQ6KRZsFKg4g0TlTcIiJ0VehqkQYUeC9xIT1Mk6XtF52mI155VSQ=="], - "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-9LrwDw3S9Fygtw/Q6WDhOP+3svJRGAsejeE+GKrc0eO1ThMVhwi2LL6hw4dlKw93IfS7VY1G19sWGxJ/NcU4nA=="], + "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.12.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-gKSGDx6fx+HBLQmG6guChFdisbrCSdx9Yli+j5teOa64gm1CeM+C6NLru5Pj3ZkkEep05BmCFjxvX+cjqAycZQ=="], - "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mNA5dbbqPqDUdTIwdUYYuhO2GvIe9UnB2r0VU2njxBOS3Opbx4gKNC5yP0Iu4rYmEmqdlwry9VzGZQ3wq9dyFg=="], + "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.12.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Yr59qMy/gcvAfBuFdCGlAmGiLsYWMFDbsP4bAI6iNBzcJTGrYWZGeiQhGDo29MQwfF/NUBqyM2qljXu5tZ6wjQ=="], - "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-fZj3Gwq+6fUs305T5WQiD5iSGJw+j/4w/HGmk4sHDAcy+rp9zU5eaxB7nOyz5/I/nkNAuKPqfp6uIbiUBXkBCw=="], + "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.12.3", "", { "os": "linux", "cpu": "none" }, "sha512-jKr0/REBMnLKC0OwPQZkyge0mKDaNZ0b/AbfGwVyCgFfwS730EVLfVDo3nt7MUu6jJaytRq08+xSj5qfy+U1+Q=="], - "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-XFxGxOvHM7jjeD6ozCKdGfhzJ7lERYDGZl1/Kb4fsvchaJsfLJ981TlyTG8Qy/gFq+f5GitH3bfrX9JAkjPEyw=="], + "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.12.3", "", { "os": "linux", "cpu": "x64" }, "sha512-gTkEqJAGCMROGanxHnzaJzQ+/rXl39HWVg0/hDODS1wDWBGqoceqGC6Qm1+mQbyT11xnsB8mKu/gyVID/YpFIw=="], - "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-d5C2/Zm+68v7R9wTuTCjRQEVrWjcdMkJBZ1+rXse+QdMMlTB9+u9PDNDLw9PQflWxYLaYZ7tjxxL9Nb9II6PbA=="], + "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.12.3", "", { "os": "linux", "cpu": "x64" }, "sha512-T2dTxL1svRym71fZtaW4pZYXbthbfFsEJr1NeOUg/p7laWXZbUt/8DMSZf8recu9f7+1F/Lv00HtzsEu6vDO9w=="], - "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YdeVWFAR1pTXzUU6NLstPq4G6OLxuDrXCXEBdmBH+5EZIDXUx0D2kJlz3+YjpazkKvAzYpgziTsyRagls0OfRQ=="], + "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.12.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-eoiQwpEZWbpsVHcM9BAzqqe4TCHszvr3rsM7pUQ4ofLK0lF1i1d/jX0Qo5DYMqcrUtwQuDdDuOCbcT0Izf1VwQ=="], - "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-VBGkuH0eB9K9LLSMv361Gzr5Ou72sCS4+ztpmkWEQ+wd/amhcYOsf3X6qn1RJZDzIhiOYHJEOysZUC3baD01rA=="], + "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.12.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-kE+KUg6Dbbkgvcw0m99wJVIToFQzJqpMBAXah74WqxaLzssNwSTUccrHp6+QVzMhjQP330tvekFmnQF+0NbE6Q=="], - "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-b3ORhIAKgp9ZYY+zBt7b7r0kLU2kjvyGF0+MS2SBym3emsweGPybEqocJcmtMuxyBhkOKHP4CiuEJEDuAlTx6A=="], + "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.12.3", "", { "os": "win32", "cpu": "x64" }, "sha512-L7wndWAgX2mLW7alswpL3d1R0P9FN2WYBgHaQkAYEBxHHGkY4vFX9CjcRQ1LAxDAMIJxrY7/aDziFJeChS84NQ=="], "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.8", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-Cd2Cs960MGuGONeIwxOPx9wqwedetAHOGlwK5boJ/SMTfAtAyfErpfVPEn+EJzgXsJun8EKzsEumHjr+64V4fw=="], - "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.4", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ=="], + "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.7.2", "", { "dependencies": { "@tauri-apps/api": "^2.12.0" } }, "sha512-LyybP1VSzTMQiaFYcsbAse47KpivlyXdLZMQzGLEnkgQacGESnpjAfFoQ6h5sqWgAdZDH9szYjPzHMpv0Sz+TQ=="], - "@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="], + "@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.4.0", "", { "dependencies": { "@tauri-apps/api": "^2.12.0" } }, "sha512-XFQjntNNI3Vlqdy4WUGwiXX8DByzKVvoemAcUVkBm9NJPGmoDtII5i+vfuAzWaJfOE5cUJJLUDqXBnd4reSAIA=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], diff --git a/apps/maple-research/frontend/package.json b/apps/maple-research/frontend/package.json index 96a093d6b..245b2ae8f 100644 --- a/apps/maple-research/frontend/package.json +++ b/apps/maple-research/frontend/package.json @@ -30,7 +30,7 @@ "zod": "4.6.7", "@babel/core": "^7.29.7", "@humanfs/node": "^0.16.8", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "mdast-util-gfm-autolink-literal": "2.0.0", "browserslist": "^4.28.8", "flatted": "^3.4.2", @@ -64,10 +64,10 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.100.1", "@tanstack/react-router": "^1.170.18", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "@tauri-apps/plugin-deep-link": "2.4.8", - "@tauri-apps/plugin-opener": "^2.5.4", - "@tauri-apps/plugin-os": "^2.3.2", + "@tauri-apps/plugin-opener": "^2.7.2", + "@tauri-apps/plugin-os": "^2.4.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "katex": "^0.18.2", @@ -91,7 +91,7 @@ "@eslint/js": "^9.39.4", "@noble/hashes": "^1.8.0", "@tanstack/router-plugin": "^1.168.23", - "@tauri-apps/cli": "2.11.1", + "@tauri-apps/cli": "2.12.3", "@types/bun": "1.3.13", "@types/node": "^22.19.17", "@types/react": "^18.3.28", diff --git a/apps/maple-research/frontend/src-tauri/Cargo.lock b/apps/maple-research/frontend/src-tauri/Cargo.lock index c89ea8d04..59f6d0a16 100644 --- a/apps/maple-research/frontend/src-tauri/Cargo.lock +++ b/apps/maple-research/frontend/src-tauri/Cargo.lock @@ -103,13 +103,28 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +[[package]] +name = "alloc-no-stdlib" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" + [[package]] name = "alloc-stdlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ - "alloc-no-stdlib", + "alloc-no-stdlib 2.0.4", +] + +[[package]] +name = "alloc-stdlib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" +dependencies = [ + "alloc-no-stdlib 3.0.0", ] [[package]] @@ -626,6 +641,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.0" @@ -736,47 +757,45 @@ dependencies = [ ] [[package]] -name = "borsh" -version = "1.5.7" +name = "brotli" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ - "borsh-derive", - "cfg_aliases", + "alloc-no-stdlib 2.0.4", + "alloc-stdlib 0.2.2", + "brotli-decompressor 5.0.0", ] [[package]] -name = "borsh-derive" -version = "1.5.7" +name = "brotli" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b" dependencies = [ - "once_cell", - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.108", + "alloc-no-stdlib 3.0.0", + "alloc-stdlib 0.3.0", + "brotli-decompressor 6.0.1", ] [[package]] -name = "brotli" -version = "8.0.2" +name = "brotli-decompressor" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", + "alloc-no-stdlib 2.0.4", + "alloc-stdlib 0.2.2", ] [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "alloc-no-stdlib 3.0.0", + "alloc-stdlib 0.3.0", ] [[package]] @@ -800,17 +819,6 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "byte-unit" -version = "5.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" -dependencies = [ - "rust_decimal", - "serde", - "utf8-width", -] - [[package]] name = "bytemuck" version = "1.24.0" @@ -911,12 +919,13 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.22.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +checksum = "82f4b26e751e711a5302649417f2da046dce6391b2ea30a4820f37462314f0b9" dependencies = [ + "semver", "serde", - "toml 0.9.8", + "toml 1.1.8+spec-1.1.0", ] [[package]] @@ -948,9 +957,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfb" -version = "0.7.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +checksum = "d8a4f8e55be323b378facfcf1f06aa97f6ec17cf4ac84fb17325093aaf62da41" dependencies = [ "byteorder", "fnv", @@ -959,13 +968,13 @@ dependencies = [ [[package]] name = "cfb" -version = "0.10.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a4f8e55be323b378facfcf1f06aa97f6ec17cf4ac84fb17325093aaf62da41" +checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3" dependencies = [ - "byteorder", "fnv", "uuid", + "web-time", ] [[package]] @@ -1394,9 +1403,9 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98" dependencies = [ "cssparser-macros", "dtoa-short", @@ -1407,9 +1416,9 @@ dependencies = [ [[package]] name = "cssparser-macros" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a" dependencies = [ "quote", "syn 2.0.108", @@ -1417,19 +1426,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" -dependencies = [ - "ctor-proc-macro", - "dtor", -] - -[[package]] -name = "ctor-proc-macro" -version = "0.0.7" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" [[package]] name = "ctr" @@ -1644,9 +1643,9 @@ dependencies = [ [[package]] name = "dirs" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" dependencies = [ "dirs-sys", ] @@ -1660,7 +1659,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1729,9 +1728,9 @@ dependencies = [ [[package]] name = "dom_query" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc" dependencies = [ "bit-set", "cssparser", @@ -1772,21 +1771,6 @@ dependencies = [ "dtoa", ] -[[package]] -name = "dtor" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" - [[package]] name = "dunce" version = "1.0.5" @@ -1973,7 +1957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2717,7 +2701,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.12.0", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -2749,9 +2733,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hayro-ccitt" @@ -2836,9 +2820,9 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" dependencies = [ "log", "markup5ever", @@ -2979,7 +2963,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -3181,23 +3165,23 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.17.1", "serde", "serde_core", ] [[package]] name = "infer" -version = "0.19.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4" dependencies = [ - "cfb 0.7.3", + "cfb 0.14.0", ] [[package]] @@ -3400,21 +3384,21 @@ dependencies = [ [[package]] name = "json-patch" -version = "3.0.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" dependencies = [ "jsonptr", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "jsonptr" -version = "0.6.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" dependencies = [ "serde", "serde_json", @@ -3422,13 +3406,12 @@ dependencies = [ [[package]] name = "keyboard-types" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" dependencies = [ "bitflags 2.10.0", "serde", - "unicode-segmentation", ] [[package]] @@ -3594,9 +3577,6 @@ name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -dependencies = [ - "value-bag", -] [[package]] name = "loop9" @@ -3723,9 +3703,9 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" dependencies = [ "log", "tendril", @@ -3815,9 +3795,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "minisign-verify" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43" +checksum = "871285dc19d8d0ebe0eef3d0e99a205f2a71363b122632cbbfa4a6c370a960ce" [[package]] name = "miniz_oxide" @@ -3852,9 +3832,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a2e3dff89cd322c66647942668faee0a2b1f88ea6cbb4d374b4a8d7e92528c" +checksum = "cca139e57da4383727f189e43e66e84fa372347027fc996f9f1a007ec0a37996" dependencies = [ "crossbeam-channel", "dpi", @@ -3920,6 +3900,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -4000,7 +3986,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4109,7 +4095,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", "syn 2.0.108", @@ -4161,6 +4147,7 @@ dependencies = [ "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", ] [[package]] @@ -4667,7 +4654,7 @@ dependencies = [ "aes 0.9.1", "base64 0.22.1", "bitflags 2.10.0", - "brotli", + "brotli 8.0.2", "byteorder", "bytes", "cbc", @@ -4883,7 +4870,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" dependencies = [ "base64 0.22.1", - "indexmap 2.12.0", + "indexmap 2.14.2", "quick-xml 0.41.0", "serde", "time", @@ -5246,7 +5233,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -5664,23 +5651,6 @@ dependencies = [ "ordered-multimap", ] -[[package]] -name = "rust_decimal" -version = "1.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand 0.8.6", - "rand 0.9.4", - "serde", - "serde_json", - "wasm-bindgen", -] - [[package]] name = "rustc-hash" version = "2.1.2" @@ -5738,7 +5708,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5797,7 +5767,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5971,9 +5941,9 @@ dependencies = [ [[package]] name = "selectors" -version = "0.36.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d" dependencies = [ "bitflags 2.10.0", "cssparser", @@ -5990,9 +5960,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -6097,9 +6067,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" dependencies = [ "serde_core", ] @@ -6127,7 +6097,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.0", + "indexmap 2.14.2", "schemars 0.9.0", "schemars 1.0.5", "serde_core", @@ -6389,6 +6359,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "static_vcruntime" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7589a1859b09232522bb9edf076ca7fcf14ded96685d3fa629db73023ffbb7" + [[package]] name = "strength_reduce" version = "0.2.4" @@ -6462,9 +6438,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "swift-rs" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +checksum = "cefd87076fd385308ee4aff597256c902f1fbb4d031de1558efc12f8ffefadd9" dependencies = [ "base64 0.21.7", "serde", @@ -6569,7 +6545,7 @@ dependencies = [ [[package]] name = "tao" -version = "0.35.2" +version = "0.37.1" dependencies = [ "bitflags 2.10.0", "block2 0.6.2", @@ -6587,12 +6563,12 @@ dependencies = [ "libc", "log", "ndk", + "ndk-context", "ndk-sys", "objc2 0.6.4", "objc2-app-kit", "objc2-foundation 0.3.2", "objc2-ui-kit", - "once_cell", "parking_lot", "percent-encoding", "raw-window-handle", @@ -6600,16 +6576,16 @@ dependencies = [ "unicode-segmentation", "url", "windows", - "windows-core 0.61.2", + "windows-core", "windows-version", "x11-dl", ] [[package]] name = "tao-macros" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" dependencies = [ "proc-macro2", "quote", @@ -6635,9 +6611,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.11.2" +version = "2.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28" +checksum = "f316751027fc2644221d1a448564d519efb07067fda584879d9cda966391d212" dependencies = [ "anyhow", "bytes", @@ -6686,9 +6662,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.6.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7" +checksum = "641dd5fd297c99ab92717b0428cca5b9c6f960b2ac96ca131b3eebc9d70b8ddc" dependencies = [ "anyhow", "cargo_toml", @@ -6700,6 +6676,8 @@ dependencies = [ "semver", "serde", "serde_json", + "static_vcruntime", + "strsim", "tauri-utils", "tauri-winres", "walkdir", @@ -6707,16 +6685,16 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.6.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9" +checksum = "a6585c5b536c00f039d12ce70a9755172d5423bbcfd3969863baa70314c3e02f" dependencies = [ - "base64 0.22.1", - "brotli", + "base64 0.23.1", + "brotli 9.0.0", "ico", "json-patch", "plist", - "png 0.17.16", + "png 0.18.1", "proc-macro2", "quote", "semver", @@ -6734,9 +6712,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.6.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924" +checksum = "e2e83a0cfa1bae376e8fbbdc0b1692707aed50cd6072ce85cae4789af5d6c19f" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -6748,9 +6726,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.6.2" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e126abc9e84e35cdfd01596140a73a1850cdb0df0a23acf0185776c30b469a6e" +checksum = "67fb21a77e8f94e8beb3325e583102488659365af0a3948b7ea244c5b779c291" dependencies = [ "anyhow", "glob", @@ -6785,9 +6763,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.5.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" +checksum = "5d58eb1d4984e535f47a642ffec09a2da6f5c18a12b5902d12126cb7626d628b" dependencies = [ "anyhow", "dunce", @@ -6803,7 +6781,7 @@ dependencies = [ "tauri-plugin", "tauri-utils", "thiserror 2.0.18", - "toml 1.1.0+spec-1.1.0", + "toml 1.1.8+spec-1.1.0", "url", ] @@ -6820,12 +6798,11 @@ dependencies = [ [[package]] name = "tauri-plugin-log" -version = "2.8.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7545bd67f070a4500432c826e2e0682146a1d6712aee22a2786490156b574d93" +checksum = "d83980f6765feb86cf2224959246be28b0488e14a23204a719f7f9deecfc64c9" dependencies = [ "android_logger", - "byte-unit", "fern", "log", "objc2 0.6.4", @@ -6842,9 +6819,9 @@ dependencies = [ [[package]] name = "tauri-plugin-opener" -version = "2.5.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" +checksum = "53810be1b4fed22bb6ac3f4cab5a2b80d9391d1af436bda145607532bc0bb219" dependencies = [ "dunce", "glob", @@ -6864,9 +6841,9 @@ dependencies = [ [[package]] name = "tauri-plugin-os" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997" +checksum = "4bd69d272ab772b30462779de12a40f837616831c794c71531bea91474e1061f" dependencies = [ "gethostname", "log", @@ -6910,9 +6887,9 @@ dependencies = [ [[package]] name = "tauri-plugin-updater" -version = "2.10.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" +checksum = "7259989d4fe11b2bf51f7d671642249b1837c4ae2da1430dc713ae38f98ceeec" dependencies = [ "base64 0.22.1", "dirs", @@ -6920,6 +6897,7 @@ dependencies = [ "futures-util", "http", "infer", + "libc", "log", "minisign-verify", "osakit", @@ -6937,14 +6915,14 @@ dependencies = [ "time", "tokio", "url", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "tauri-runtime" -version = "2.11.2" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef" +checksum = "7c55766d99cea69055904d1735b93c2049302b2e09effd462120deb5581d78e9" dependencies = [ "cookie", "dpi", @@ -6967,9 +6945,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.11.2" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9" +checksum = "8d61892587aea2a85245bcaec98c6fe46268874c0a0deae5535fb71ca7b93eae" dependencies = [ "gtk", "http", @@ -6993,12 +6971,12 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.9.2" +version = "2.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95" +checksum = "362ab21cd4cc713f2a02b1a9b5688198b1541cfb6907c4281e9190e0f0ef4aa3" dependencies = [ "anyhow", - "brotli", + "brotli 9.0.0", "cargo_metadata", "ctor", "dom_query", @@ -7022,7 +7000,7 @@ dependencies = [ "serde_with", "swift-rs", "thiserror 2.0.18", - "toml 1.1.0+spec-1.1.0", + "toml 1.1.8+spec-1.1.0", "url", "urlpattern", "uuid", @@ -7049,7 +7027,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7287,9 +7265,9 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "serde_core", - "serde_spanned 1.1.1", + "serde_spanned 1.1.2", "toml_datetime 0.7.3", "toml_parser", "toml_writer", @@ -7298,14 +7276,14 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.0+spec-1.1.0" +version = "1.1.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", + "serde_spanned 1.1.2", + "toml_datetime 1.1.2+spec-1.1.0", "toml_parser", "toml_writer", "winnow 1.0.3", @@ -7331,9 +7309,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" dependencies = [ "serde_core", ] @@ -7344,7 +7322,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -7355,7 +7333,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -7368,7 +7346,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "toml_datetime 0.7.3", "toml_parser", "winnow 0.7.13", @@ -7376,18 +7354,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ "winnow 1.0.3", ] [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" [[package]] name = "tower" @@ -7510,9 +7488,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.23.1" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773" +checksum = "b2b9c52859a94554803ccd4a24b98f74148ebc73b90676d783f3490b1bff9d72" dependencies = [ "crossbeam-channel", "dirs", @@ -7574,47 +7552,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicode-bidi" version = "0.3.18" @@ -7730,13 +7667,13 @@ dependencies = [ [[package]] name = "urlpattern" -version = "0.3.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b" dependencies = [ + "icu_properties", "regex", "serde", - "unic-ucd-ident", "url", ] @@ -7746,12 +7683,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf8-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -7793,12 +7724,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "value-bag" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" - [[package]] name = "vcpkg" version = "0.2.15" @@ -8048,23 +7973,21 @@ dependencies = [ [[package]] name = "webview2-com" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +checksum = "3f89fca7a704cee10dcb3654c1dbb8941d1783132f1917358af75bec37a7d7e6" dependencies = [ "webview2-com-macros", "webview2-com-sys", "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "windows-core", ] [[package]] name = "webview2-com-macros" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", @@ -8073,13 +7996,13 @@ dependencies = [ [[package]] name = "webview2-com-sys" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +checksum = "b3a07132775117d6065853d9d1178157b8c90e228de47129d6bce2c7edebedfb" dependencies = [ "thiserror 2.0.18", "windows", - "windows-core 0.61.2", + "windows-core", ] [[package]] @@ -8126,7 +8049,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -8137,52 +8060,39 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window-vibrancy" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +checksum = "111e51caca442cafd9bab396628ac4d814880eaea4e63dfd76a12e9f342b5580" dependencies = [ "objc2 0.6.4", "objc2-app-kit", "objc2-core-foundation", "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", "raw-window-handle", - "windows-sys 0.59.0", + "windows-sys 0.61.2", "windows-version", ] [[package]] name = "windows" -version = "0.61.3" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", - "windows-link 0.1.3", "windows-numerics", ] [[package]] name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -8200,12 +8110,12 @@ dependencies = [ [[package]] name = "windows-future" -version = "0.2.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", + "windows-core", + "windows-link 0.2.1", "windows-threading", ] @@ -8245,12 +8155,12 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", + "windows-core", + "windows-link 0.2.1", ] [[package]] @@ -8395,11 +8305,11 @@ dependencies = [ [[package]] name = "windows-threading" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.1.3", + "windows-link 0.2.1", ] [[package]] @@ -8608,7 +8518,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb731d4c4d93eacc69a1ad2f270f905788a98e4a3438267bcafbe08d3431c8d8" dependencies = [ "font-types", - "indexmap 2.12.0", + "indexmap 2.14.2", "kurbo", "log", "read-fonts", @@ -8622,9 +8532,9 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wry" -version = "0.55.1" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +checksum = "a819957a01b3119af85e638a38d242af76dbc87d130dca67bfd0441072e21ff0" dependencies = [ "base64 0.22.1", "block2 0.6.2", @@ -8659,7 +8569,7 @@ dependencies = [ "webkit2gtk-sys", "webview2-com", "windows", - "windows-core 0.61.2", + "windows-core", "windows-version", "x11-dl", ] @@ -8922,7 +8832,7 @@ checksum = "6e04976da629d3b04204ba8105b396a193af8ad2583b2c79bfb3ab8af580a11e" dependencies = [ "crc32fast", "flate2", - "indexmap 2.12.0", + "indexmap 2.14.2", "memchr", "nix 0.31.3", "typed-path", diff --git a/apps/maple-research/frontend/src-tauri/Cargo.toml b/apps/maple-research/frontend/src-tauri/Cargo.toml index e5859be22..7d35090f6 100644 --- a/apps/maple-research/frontend/src-tauri/Cargo.toml +++ b/apps/maple-research/frontend/src-tauri/Cargo.toml @@ -15,7 +15,7 @@ name = "app_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { version = "2.6.2", features = [] } +tauri-build = { version = "2.7.3", features = [] } plist = "1" serde_json = "1.0" @@ -23,23 +23,25 @@ serde_json = "1.0" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } log = "0.4" -tauri = { version = "2.11.2", features = [] } -tauri-plugin-log = "2.8.0" +tauri = { version = "2.12.4", features = [] } +tauri-plugin-log = "2.10.1" # Maple publishes raw Windows installers and Linux AppImages, so it does not # need the updater's legacy ZIP / AppImage.tar.gz handlers or their ZIP dependency. -tauri-plugin-updater = { version = "2.10.1", default-features = false, features = ["rustls-tls"] } -tauri-plugin = "2.6.2" +tauri-plugin-updater = { version = "2.14.0", default-features = false, features = ["rustls-tls"] } +tauri-plugin = "2.7.2" # Keep Android deep links on the last known-working plugin behavior. # tauri-plugin-deep-link 2.4.9 added Android-side config filtering that drops # our existing cloud.opensecret.maple:// OAuth callback before Rust receives it. tauri-plugin-deep-link = "=2.4.7" +# single-instance 2.4.1+ needs deep-link >= 2.4.8, so it stays on the newest release +# that resolves with the deep-link pin above. tauri-plugin-single-instance = { version = "=2.4.0", features = ["deep-link"] } -tauri-plugin-opener = "2.5.4" -tauri-plugin-os = "2.3.2" +tauri-plugin-opener = "2.7.2" +tauri-plugin-os = "2.4.0" tauri-plugin-sign-in-with-apple = "1.0.2" tokio = { version = "1.0", features = ["fs", "io-util", "net", "sync", "rt-multi-thread", "macros", "time"] } once_cell = "1.18.0" -tauri-plugin-fs = "2.5.1" +tauri-plugin-fs = "2.7.1" anyhow = "1.0" pdf_oxide = { version = "=0.3.74", git = "https://github.com/OpenSecretCloud/pdf_oxide.git", rev = "2bc9246782fe874a97e57e348acc7a9eaa27ca4d", features = ["ocr-ort", "rendering"] } # Keep the pre-1.0 Word parser exact-pinned. office_oxide is already in the @@ -96,13 +98,16 @@ keyring = { version = "3", features = ["windows-native"] } # The embedded proxy requires `maple-sdk` from the registry; resolve it to the same # in-tree source as the dependency above (docs/sdk-publishing.md). maple-sdk = { path = "../../../../sdk/rust" } -# Local patch for tao 0.35.2 Android intent crashes: -# https://github.com/tauri-apps/tao/issues/1217 +# Local patch for tao 0.37.1 Android intent crashes. tauri-apps/tao#1217 was +# closed by #1224, which only made getType() null-safe: 0.37.1 still unwraps +# getIntent, getAction, ClipData items and EXTRA_STREAM, so a null launch intent +# from real Android launch/deep-link flows aborts the process before +# tauri-plugin-deep-link sees OAuth/payment redirects. Remove the patch once +# upstream stops unwrapping those calls. # -# Tao's Android intent handler currently unwraps several JNI calls that can -# receive null objects from real Android launch/deep-link flows, aborting the -# process before tauri-plugin-deep-link sees OAuth/payment redirects. Remove -# this once Tauri resolves to a Tao release containing the upstream fix. -# The vendored library uses this application's Cargo.lock; its upstream -# standalone lockfile is intentionally omitted. -tao = { path = "patches/tao-0.35.2" } +# Vendored from crates.io tao 0.37.1 (sha256 +# f37f381f4e048e6cdf038b5705f8cf14ad108279d46eb968140a7b291aba9400, upstream +# 37b7e8bc90a0); Maple's changes follow the vendoring commit. The vendored +# library uses this application's Cargo.lock; its upstream standalone lockfile +# is intentionally omitted. +tao = { path = "patches/tao-0.37.1" } diff --git a/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties index 0d4bd191c..df3acc569 100644 --- a/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties +++ b/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ #Tue May 10 19:22:52 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/header.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/header.rs deleted file mode 100644 index 480eb4c85..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/header.rs +++ /dev/null @@ -1,36 +0,0 @@ -use gtk::{prelude::*, ApplicationWindow, EventBox, HeaderBar}; - -pub struct WlHeader; - -impl WlHeader { - pub fn setup(window: &ApplicationWindow, title: &str) { - let header = HeaderBar::builder() - .show_close_button(true) - .decoration_layout("menu:minimize,maximize,close") - .title(title) - .build(); - - let event_box = EventBox::new(); - event_box.set_above_child(true); - event_box.set_visible(true); - event_box.set_can_focus(false); - event_box.add(&header); - - window.set_titlebar(Some(&event_box)); - Self::connect_resize_window(&header, window); - } - - fn connect_resize_window(header: &HeaderBar, window: &ApplicationWindow) { - let header_weak = header.downgrade(); - window.connect_resizable_notify(move |window| { - if let Some(header) = header_weak.upgrade() { - let is_resizable = window.is_resizable(); - header.set_decoration_layout(if !is_resizable { - Some("menu:minimize,close") - } else { - Some("menu:minimize,maximize,close") - }); - } - }); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/mod.rs deleted file mode 100644 index a200d2603..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -pub mod header; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard.rs deleted file mode 100644 index 3e85d6a2f..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard.rs +++ /dev/null @@ -1,816 +0,0 @@ -use parking_lot::{Mutex, MutexGuard}; -use std::{ - char, - collections::{HashMap, HashSet}, - ffi::OsString, - mem::MaybeUninit, - os::windows::ffi::OsStringExt, -}; - -use windows::Win32::{ - Foundation::{HWND, LPARAM, LRESULT, WPARAM}, - UI::{ - Input::KeyboardAndMouse::{self as win32km, *}, - WindowsAndMessaging::{self as win32wm, *}, - }, -}; - -use once_cell::sync::Lazy; -use unicode_segmentation::UnicodeSegmentation; - -use crate::{ - event::{ElementState, KeyEvent}, - keyboard::{Key, KeyCode, KeyLocation, NativeKeyCode}, - platform_impl::{ - platform::{ - event_loop::ProcResult, - keyboard_layout::{get_or_insert_str, Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, - KeyEventExtra, - }, - WindowId, - }, -}; - -pub fn is_msg_keyboard_related(msg: u32) -> bool { - let is_keyboard_msg = (WM_KEYFIRST..=WM_KEYLAST).contains(&msg); - - is_keyboard_msg || msg == WM_SETFOCUS || msg == WM_KILLFOCUS -} - -pub type ExScancode = u16; - -pub struct MessageAsKeyEvent { - pub event: KeyEvent, - pub is_synthetic: bool, -} - -pub(crate) static KEY_EVENT_BUILDERS: Lazy>> = - Lazy::new(|| Mutex::new(HashMap::new())); - -/// Stores information required to make `KeyEvent`s. -/// -/// A single Tao `KeyEvent` contains information which the Windows API passes to the application -/// in multiple window messages. In other words: a Tao `KeyEvent` cannot be built from a single -/// window message. Therefore, this type keeps track of certain information from previous events so -/// that a `KeyEvent` can be constructed when the last event related to a keypress is received. -/// -/// `PeekMessage` is sometimes used to determine whether the next window message still belongs to the -/// current keypress. If it doesn't and the current state represents a key event waiting to be -/// dispatched, then said event is considered complete and is dispatched. -/// -/// The sequence of window messages for a key press event is the following: -/// - Exactly one WM_KEYDOWN / WM_SYSKEYDOWN -/// - Zero or one WM_DEADCHAR / WM_SYSDEADCHAR -/// - Zero or more WM_CHAR / WM_SYSCHAR. These messages each come with a UTF-16 code unit which when -/// put together in the sequence they arrived in, forms the text which is the result of pressing the -/// key. -/// -/// Key release messages are a bit different due to the fact that they don't contribute to -/// text input. The "sequence" only consists of one WM_KEYUP / WM_SYSKEYUP event. -#[derive(Default)] -pub struct KeyEventBuilder { - event_info: Option, -} -impl KeyEventBuilder { - /// Call this function for every window message. - /// Returns Some() if this window message completes a KeyEvent. - /// Returns None otherwise. - pub(crate) fn process_message( - &mut self, - hwnd: HWND, - msg_kind: u32, - wparam: WPARAM, - lparam: LPARAM, - result: &mut ProcResult, - ) -> Vec { - match msg_kind { - win32wm::WM_SETFOCUS => { - // synthesize keydown events - let kbd_state = get_async_kbd_state(); - let key_events = self.synthesize_kbd_state(ElementState::Pressed, &kbd_state); - if !key_events.is_empty() { - return key_events; - } - } - win32wm::WM_KILLFOCUS => { - // sythesize keyup events - let kbd_state = get_kbd_state(); - let key_events = self.synthesize_kbd_state(ElementState::Released, &kbd_state); - if !key_events.is_empty() { - return key_events; - } - } - win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN => { - if msg_kind == WM_SYSKEYDOWN && wparam.0 == usize::from(VK_F4.0) { - // Don't dispatch Alt+F4 to the application. - // This is handled in `event_loop.rs` - return vec![]; - } - - if msg_kind == win32wm::WM_SYSKEYDOWN { - *result = ProcResult::DefSubclassProc; - } else { - *result = ProcResult::Value(LRESULT(0)); - } - - let mut layouts = LAYOUT_CACHE.lock(); - let event_info = - PartialKeyEventInfo::from_message(wparam, lparam, ElementState::Pressed, &mut layouts); - - let mut next_msg = MaybeUninit::uninit(); - let peek_retval = unsafe { - PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ) - }; - let has_next_key_message = peek_retval.as_bool(); - self.event_info = None; - let mut finished_event_info = Some(event_info); - if has_next_key_message { - let next_msg = unsafe { next_msg.assume_init() }; - let next_msg_kind = next_msg.message; - let next_belongs_to_this = !matches!( - next_msg_kind, - win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN | win32wm::WM_KEYUP | win32wm::WM_SYSKEYUP - ); - if next_belongs_to_this { - self.event_info = finished_event_info.take(); - } else { - let (_, layout) = layouts.get_current_layout(); - let is_fake = { - let curr_event = finished_event_info.as_ref().unwrap(); - is_current_fake(curr_event, next_msg, layout) - }; - if is_fake { - finished_event_info = None; - } - } - } - if let Some(event_info) = finished_event_info { - let ev = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event: ev, - is_synthetic: false, - }]; - } - } - win32wm::WM_DEADCHAR | win32wm::WM_SYSDEADCHAR => { - *result = ProcResult::Value(LRESULT(0)); - // At this point, we know that there isn't going to be any more events related to - // this key press - let event_info = self.event_info.take().unwrap(); - let mut layouts = LAYOUT_CACHE.lock(); - let ev = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event: ev, - is_synthetic: false, - }]; - } - win32wm::WM_CHAR | win32wm::WM_SYSCHAR => { - if self.event_info.is_none() { - trace!("Received a CHAR message but no `event_info` was available. The message is probably IME, returning."); - return vec![]; - } - *result = ProcResult::Value(LRESULT(0)); - let is_high_surrogate = (0xD800..=0xDBFF).contains(&wparam.0); - let is_low_surrogate = (0xDC00..=0xDFFF).contains(&wparam.0); - - let is_utf16 = is_high_surrogate || is_low_surrogate; - - let more_char_coming; - unsafe { - let mut next_msg = MaybeUninit::uninit(); - let has_message = PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ); - let has_message = has_message.as_bool(); - if !has_message { - more_char_coming = false; - } else { - let next_msg = next_msg.assume_init().message; - more_char_coming = next_msg == WM_CHAR || next_msg == WM_SYSCHAR; - } - } - - if is_utf16 { - if let Some(ev_info) = self.event_info.as_mut() { - ev_info.utf16parts.push(wparam.0 as u16); - } - } else { - // In this case, wparam holds a UTF-32 character. - // Let's encode it as UTF-16 and append it to the end of `utf16parts` - let utf16parts = match self.event_info.as_mut() { - Some(ev_info) => &mut ev_info.utf16parts, - None => { - warn!("The event_info was None when it was expected to be some"); - return vec![]; - } - }; - let start_offset = utf16parts.len(); - let new_size = utf16parts.len() + 2; - utf16parts.resize(new_size, 0); - if let Some(ch) = char::from_u32(wparam.0 as u32) { - let encode_len = ch.encode_utf16(&mut utf16parts[start_offset..]).len(); - let new_size = start_offset + encode_len; - utf16parts.resize(new_size, 0); - } - } - if !more_char_coming { - let mut event_info = match self.event_info.take() { - Some(ev_info) => ev_info, - None => { - warn!("The event_info was None when it was expected to be some"); - return vec![]; - } - }; - let mut layouts = LAYOUT_CACHE.lock(); - // It's okay to call `ToUnicode` here, because at this point the dead key - // is already consumed by the character. - let kbd_state = get_kbd_state(); - let mod_state = WindowsModifiers::active_modifiers(&kbd_state); - - let (_, layout) = layouts.get_current_layout(); - let ctrl_on = if layout.has_alt_graph { - let alt_on = mod_state.contains(WindowsModifiers::ALT); - !alt_on && mod_state.contains(WindowsModifiers::CONTROL) - } else { - mod_state.contains(WindowsModifiers::CONTROL) - }; - - // If Ctrl is not pressed, just use the text with all - // modifiers because that already consumed the dead key. Otherwise, - // we would interpret the character incorrectly, missing the dead key. - if !ctrl_on { - event_info.text = PartialText::System(event_info.utf16parts.clone()); - } else { - let mod_no_ctrl = mod_state.remove_only_ctrl(); - let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; - let vkey = event_info.vkey; - let scancode = event_info.scancode; - let keycode = event_info.code; - let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, scancode, keycode); - event_info.text = PartialText::Text(key.to_text()); - } - let ev = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event: ev, - is_synthetic: false, - }]; - } - } - win32wm::WM_KEYUP | win32wm::WM_SYSKEYUP => { - if msg_kind == win32wm::WM_SYSKEYUP { - *result = ProcResult::DefSubclassProc; - } else { - *result = ProcResult::Value(LRESULT(0)); - } - - let mut layouts = LAYOUT_CACHE.lock(); - let event_info = - PartialKeyEventInfo::from_message(wparam, lparam, ElementState::Released, &mut layouts); - let mut next_msg = MaybeUninit::uninit(); - let peek_retval = unsafe { - PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ) - }; - let has_next_key_message = peek_retval.as_bool(); - let mut valid_event_info = Some(event_info); - if has_next_key_message { - let next_msg = unsafe { next_msg.assume_init() }; - let (_, layout) = layouts.get_current_layout(); - let is_fake = { - let event_info = valid_event_info.as_ref().unwrap(); - is_current_fake(event_info, next_msg, layout) - }; - if is_fake { - valid_event_info = None; - } - } - if let Some(event_info) = valid_event_info { - let event = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event, - is_synthetic: false, - }]; - } - } - _ => (), - } - - Vec::new() - } - - fn synthesize_kbd_state( - &mut self, - key_state: ElementState, - kbd_state: &[u8; 256], - ) -> Vec { - let mut key_events = Vec::new(); - - let mut layouts = LAYOUT_CACHE.lock(); - let (locale_id, _) = layouts.get_current_layout(); - - let is_key_pressed = |vk: VIRTUAL_KEY| &kbd_state[usize::from(vk.0)] & 0x80 != 0; - - // Is caps-lock active? Note that this is different from caps-lock - // being held down. - let caps_lock_on = kbd_state[usize::from(VK_CAPITAL.0)] & 1 != 0; - let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; - - // We are synthesizing the press event for caps-lock first for the following reasons: - // 1. If caps-lock is *not* held down but *is* active, then we have to - // synthesize all printable keys, respecting the caps-lock state. - // 2. If caps-lock is held down, we could choose to sythesize its - // keypress after every other key, in which case all other keys *must* - // be sythesized as if the caps-lock state was be the opposite - // of what it currently is. - // -- - // For the sake of simplicity we are choosing to always sythesize - // caps-lock first, and always use the current caps-lock state - // to determine the produced text - if is_key_pressed(VK_CAPITAL) { - let event = self.create_synthetic( - VK_CAPITAL, - key_state, - caps_lock_on, - num_lock_on, - locale_id, - &mut layouts, - ); - if let Some(event) = event { - key_events.push(event); - } - } - let do_non_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { - for vk in 0..256 { - let vk = VIRTUAL_KEY(vk); - match vk { - win32km::VK_CONTROL - | win32km::VK_LCONTROL - | win32km::VK_RCONTROL - | win32km::VK_SHIFT - | win32km::VK_LSHIFT - | win32km::VK_RSHIFT - | win32km::VK_MENU - | win32km::VK_LMENU - | win32km::VK_RMENU - | win32km::VK_CAPITAL => continue, - _ => (), - } - if !is_key_pressed(vk) { - continue; - } - let event = self.create_synthetic( - vk, - key_state, - caps_lock_on, - num_lock_on, - locale_id as HKL, - layouts, - ); - if let Some(event) = event { - key_events.push(event); - } - } - }; - let do_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { - const CLEAR_MODIFIER_VKS: [VIRTUAL_KEY; 6] = [ - VK_LCONTROL, - VK_LSHIFT, - VK_LMENU, - VK_RCONTROL, - VK_RSHIFT, - VK_RMENU, - ]; - for vk in CLEAR_MODIFIER_VKS.iter() { - if is_key_pressed(*vk) { - let event = self.create_synthetic( - *vk, - key_state, - caps_lock_on, - num_lock_on, - locale_id as HKL, - layouts, - ); - if let Some(event) = event { - key_events.push(event); - } - } - } - }; - - // Be cheeky and sequence modifier and non-modifier - // key events such that non-modifier keys are not affected - // by modifiers (except for caps-lock) - match key_state { - ElementState::Pressed => { - do_non_modifier(&mut key_events, &mut layouts); - do_modifier(&mut key_events, &mut layouts); - } - ElementState::Released => { - do_modifier(&mut key_events, &mut layouts); - do_non_modifier(&mut key_events, &mut layouts); - } - } - - key_events - } - - fn create_synthetic( - &self, - vk: VIRTUAL_KEY, - key_state: ElementState, - caps_lock_on: bool, - num_lock_on: bool, - locale_id: HKL, - layouts: &mut MutexGuard<'_, LayoutCache>, - ) -> Option { - let scancode = - unsafe { MapVirtualKeyExW(u32::from(vk.0), MAPVK_VK_TO_VSC_EX, Some(locale_id)) }; - if scancode == 0 { - return None; - } - let scancode = scancode as ExScancode; - let code = KeyCode::from_scancode(scancode as u32); - let mods = if caps_lock_on { - WindowsModifiers::CAPS_LOCK - } else { - WindowsModifiers::empty() - }; - let layout = layouts.layouts.get(&(locale_id.0 as _)).unwrap(); - let logical_key = layout.get_key(mods, num_lock_on, vk, scancode, code); - let key_without_modifiers = - layout.get_key(WindowsModifiers::empty(), false, vk, scancode, code); - let text = if key_state == ElementState::Pressed { - logical_key.to_text() - } else { - None - }; - let event_info = PartialKeyEventInfo { - vkey: vk, - logical_key: PartialLogicalKey::This(logical_key.clone()), - key_without_modifiers, - key_state, - scancode, - is_repeat: false, - code, - location: get_location(scancode, locale_id), - utf16parts: Vec::with_capacity(8), - text: PartialText::Text(text), - }; - - let mut event = event_info.finalize(&mut layouts.strings); - event.logical_key = logical_key; - event.platform_specific.text_with_all_modifiers = text; - Some(MessageAsKeyEvent { - event, - is_synthetic: true, - }) - } -} - -enum PartialText { - // Unicode - System(Vec), - Text(Option<&'static str>), -} - -enum PartialLogicalKey { - /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If - /// the text consists of multiple grapheme clusters (user-precieved characters) that means that - /// dead key could not be combined with the second input, and in that case we should fall back - /// to using what would have without a dead-key input. - TextOr(Key<'static>), - - /// Use the value directly provided by this variant - This(Key<'static>), -} - -struct PartialKeyEventInfo { - vkey: VIRTUAL_KEY, - scancode: ExScancode, - key_state: ElementState, - is_repeat: bool, - code: KeyCode, - location: KeyLocation, - logical_key: PartialLogicalKey, - - key_without_modifiers: Key<'static>, - - /// The UTF-16 code units of the text that was produced by the keypress event. - /// This take all modifiers into account. Including CTRL - utf16parts: Vec, - - text: PartialText, -} - -impl PartialKeyEventInfo { - fn from_message( - wparam: WPARAM, - lparam: LPARAM, - state: ElementState, - layouts: &mut MutexGuard<'_, LayoutCache>, - ) -> Self { - const NO_MODS: WindowsModifiers = WindowsModifiers::empty(); - - let (_, layout) = layouts.get_current_layout(); - let lparam_struct = destructure_key_lparam(lparam); - let vkey = VIRTUAL_KEY(wparam.0 as u16); - let scancode = if lparam_struct.scancode == 0 { - // In some cases (often with media keys) the device reports a scancode of 0 but a - // valid virtual key. In these cases we obtain the scancode from the virtual key. - unsafe { - MapVirtualKeyExW( - u32::from(vkey.0), - MAPVK_VK_TO_VSC_EX, - Some(HKL(layout.hkl as _)), - ) as u16 - } - } else { - new_ex_scancode(lparam_struct.scancode, lparam_struct.extended) - }; - let code = KeyCode::from_scancode(scancode as u32); - let location = get_location(scancode, HKL(layout.hkl as _)); - - let kbd_state = get_kbd_state(); - let mods = WindowsModifiers::active_modifiers(&kbd_state); - let mods_without_ctrl = mods.remove_only_ctrl(); - let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; - - // On Windows Ctrl+NumLock = Pause (and apparently Ctrl+Pause -> NumLock). In these cases - // the KeyCode still stores the real key, so in the name of consistency across platforms, we - // circumvent this mapping and force the key values to match the keycode. - // For more on this, read the article by Raymond Chen, titled: - // "Why does Ctrl+ScrollLock cancel dialogs?" - // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 - let code_as_key = if mods.contains(WindowsModifiers::CONTROL) { - match code { - KeyCode::NumLock => Some(Key::NumLock), - KeyCode::Pause => Some(Key::Pause), - _ => None, - } - } else { - None - }; - - let preliminary_logical_key = - layout.get_key(mods_without_ctrl, num_lock_on, vkey, scancode, code); - let key_is_char = matches!(preliminary_logical_key, Key::Character(_)); - let is_pressed = state == ElementState::Pressed; - - let logical_key = if let Some(key) = code_as_key.clone() { - PartialLogicalKey::This(key) - } else if is_pressed && key_is_char && !mods.contains(WindowsModifiers::CONTROL) { - // In some cases we want to use the UNICHAR text for logical_key in order to allow - // dead keys to have an effect on the character reported by `logical_key`. - PartialLogicalKey::TextOr(preliminary_logical_key) - } else { - PartialLogicalKey::This(preliminary_logical_key) - }; - let key_without_modifiers = if let Some(key) = code_as_key { - key - } else { - match layout.get_key(NO_MODS, false, vkey, scancode, code) { - // We convert dead keys into their character. - // The reason for this is that `key_without_modifiers` is designed for key-bindings, - // but the US International layout treats `'` (apostrophe) as a dead key and the - // reguar US layout treats it a character. In order for a single binding - // configuration to work with both layouts, we forward each dead key as a character. - Key::Dead(k) => { - if let Some(ch) = k { - // I'm avoiding the heap allocation. I don't want to talk about it :( - let mut utf8 = [0; 4]; - let s = ch.encode_utf8(&mut utf8); - let static_str = get_or_insert_str(&mut layouts.strings, s); - Key::Character(static_str) - } else { - Key::Unidentified(NativeKeyCode::Unidentified) - } - } - key => key, - } - }; - - PartialKeyEventInfo { - vkey, - scancode, - key_state: state, - logical_key, - key_without_modifiers, - is_repeat: lparam_struct.is_repeat, - code, - location, - utf16parts: Vec::with_capacity(8), - text: PartialText::System(Vec::new()), - } - } - - fn finalize(self, strings: &mut HashSet<&'static str>) -> KeyEvent { - let mut char_with_all_modifiers = None; - if !self.utf16parts.is_empty() { - let os_string = OsString::from_wide(&self.utf16parts); - if let Ok(string) = os_string.into_string() { - let static_str = get_or_insert_str(strings, string); - char_with_all_modifiers = Some(static_str); - } - } - - // The text without Ctrl - let mut text = None; - match self.text { - PartialText::System(wide) => { - if !wide.is_empty() { - let os_string = OsString::from_wide(&wide); - if let Ok(string) = os_string.into_string() { - let static_str = get_or_insert_str(strings, string); - text = Some(static_str); - } - } - } - PartialText::Text(s) => { - text = s; - } - } - - let logical_key = match self.logical_key { - PartialLogicalKey::TextOr(fallback) => match text { - Some(s) => { - if s.grapheme_indices(true).count() > 1 { - fallback - } else { - Key::Character(s) - } - } - None => Key::Unidentified(NativeKeyCode::Windows(self.scancode)), - }, - PartialLogicalKey::This(v) => v, - }; - - KeyEvent { - physical_key: self.code, - logical_key, - text, - location: self.location, - state: self.key_state, - repeat: self.is_repeat, - platform_specific: KeyEventExtra { - text_with_all_modifiers: char_with_all_modifiers, - key_without_modifiers: self.key_without_modifiers, - }, - } - } -} - -#[derive(Debug, Copy, Clone)] -struct KeyLParam { - pub scancode: u8, - pub extended: bool, - - /// This is `previous_state XOR transition_state`. See the lParam for WM_KEYDOWN and WM_KEYUP for further details. - pub is_repeat: bool, -} - -fn destructure_key_lparam(lparam: LPARAM) -> KeyLParam { - let previous_state = (lparam.0 >> 30) & 0x01; - let transition_state = (lparam.0 >> 31) & 0x01; - KeyLParam { - scancode: ((lparam.0 >> 16) & 0xFF) as u8, - extended: ((lparam.0 >> 24) & 0x01) != 0, - is_repeat: (previous_state ^ transition_state) != 0, - } -} - -#[inline] -fn new_ex_scancode(scancode: u8, extended: bool) -> ExScancode { - (scancode as u16) | (if extended { 0xE000 } else { 0 }) -} - -#[inline] -fn ex_scancode_from_lparam(lparam: LPARAM) -> ExScancode { - let lparam = destructure_key_lparam(lparam); - new_ex_scancode(lparam.scancode, lparam.extended) -} - -/// Gets the keyboard state as reported by messages that have been removed from the event queue. -/// See also: get_async_kbd_state -fn get_kbd_state() -> [u8; 256] { - unsafe { - let mut kbd_state: MaybeUninit<[u8; 256]> = MaybeUninit::uninit(); - let _ = GetKeyboardState(&mut *kbd_state.as_mut_ptr()); - kbd_state.assume_init() - } -} - -/// Gets the current keyboard state regardless of whether the corresponding keyboard events have -/// been removed from the event queue. See also: get_kbd_state -fn get_async_kbd_state() -> [u8; 256] { - unsafe { - let mut kbd_state: [u8; 256] = [0; 256]; - for (vk, state) in kbd_state.iter_mut().enumerate() { - let vk = VIRTUAL_KEY(vk as u16); - let async_state = GetAsyncKeyState(i32::from(vk.0)); - let is_down = (async_state & (1 << 15)) != 0; - if is_down { - *state = 0x80; - } - - if matches!( - vk, - win32km::VK_CAPITAL | win32km::VK_NUMLOCK | win32km::VK_SCROLL - ) { - // Toggle states aren't reported by `GetAsyncKeyState` - let toggle_state = GetKeyState(i32::from(vk.0)); - let is_active = (toggle_state & 1) != 0; - *state |= if is_active { 1 } else { 0 }; - } - } - kbd_state - } -} - -/// On windows, AltGr == Ctrl + Alt -/// -/// Due to this equivalence, the system generates a fake Ctrl key-press (and key-release) preceeding -/// every AltGr key-press (and key-release). We check if the current event is a Ctrl event and if -/// the next event is a right Alt (AltGr) event. If this is the case, the current event must be the -/// fake Ctrl event. -fn is_current_fake(curr_info: &PartialKeyEventInfo, next_msg: MSG, layout: &Layout) -> bool { - let curr_is_ctrl = matches!(curr_info.logical_key, PartialLogicalKey::This(Key::Control)); - if layout.has_alt_graph { - let next_code = ex_scancode_from_lparam(next_msg.lParam); - let next_is_altgr = next_code == 0xE038; // 0xE038 is right alt - if curr_is_ctrl && next_is_altgr { - return true; - } - } - false -} - -fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { - const VK_ABNT_C2: VIRTUAL_KEY = win32km::VK_ABNT_C2; - - let extension = 0xE000; - let extended = (scancode & extension) == extension; - let vkey = - VIRTUAL_KEY(unsafe { MapVirtualKeyExW(scancode as u32, MAPVK_VSC_TO_VK_EX, Some(hkl)) } as u16); - - // Use the native VKEY and the extended flag to cover most cases - // This is taken from the `druid` GUI library, specifically - // druid-shell/src/platform/windows/keyboard.rs - match vkey { - win32km::VK_LSHIFT | win32km::VK_LCONTROL | win32km::VK_LMENU | win32km::VK_LWIN => { - KeyLocation::Left - } - win32km::VK_RSHIFT | win32km::VK_RCONTROL | win32km::VK_RMENU | win32km::VK_RWIN => { - KeyLocation::Right - } - win32km::VK_RETURN if extended => KeyLocation::Numpad, - win32km::VK_INSERT - | win32km::VK_DELETE - | win32km::VK_END - | win32km::VK_DOWN - | win32km::VK_NEXT - | win32km::VK_LEFT - | win32km::VK_CLEAR - | win32km::VK_RIGHT - | win32km::VK_HOME - | win32km::VK_UP - | win32km::VK_PRIOR => { - if extended { - KeyLocation::Standard - } else { - KeyLocation::Numpad - } - } - win32km::VK_NUMPAD0 - | win32km::VK_NUMPAD1 - | win32km::VK_NUMPAD2 - | win32km::VK_NUMPAD3 - | win32km::VK_NUMPAD4 - | win32km::VK_NUMPAD5 - | win32km::VK_NUMPAD6 - | win32km::VK_NUMPAD7 - | win32km::VK_NUMPAD8 - | win32km::VK_NUMPAD9 - | win32km::VK_DECIMAL - | win32km::VK_DIVIDE - | win32km::VK_MULTIPLY - | win32km::VK_SUBTRACT - | win32km::VK_ADD - | VK_ABNT_C2 => KeyLocation::Numpad, - _ => KeyLocation::Standard, - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/minimal_ime.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/minimal_ime.rs deleted file mode 100644 index 0e0f1d218..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/minimal_ime.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::mem::MaybeUninit; - -use windows::Win32::{ - Foundation::{HWND, LPARAM, LRESULT, WPARAM}, - UI::WindowsAndMessaging::{self as win32wm, *}, -}; - -use crate::platform_impl::platform::event_loop::ProcResult; - -pub fn is_msg_ime_related(msg_kind: u32) -> bool { - matches!( - msg_kind, - win32wm::WM_IME_COMPOSITION - | win32wm::WM_IME_COMPOSITIONFULL - | win32wm::WM_IME_STARTCOMPOSITION - | win32wm::WM_IME_ENDCOMPOSITION - | win32wm::WM_IME_CHAR - | win32wm::WM_CHAR - | win32wm::WM_SYSCHAR - ) -} - -pub struct MinimalIme { - // True if we're currently receiving messages belonging to a finished IME session. - getting_ime_text: bool, - - utf16parts: Vec, -} -impl Default for MinimalIme { - fn default() -> Self { - MinimalIme { - getting_ime_text: false, - utf16parts: Vec::with_capacity(16), - } - } -} -impl MinimalIme { - pub(crate) fn process_message( - &mut self, - hwnd: HWND, - msg_kind: u32, - wparam: WPARAM, - _lparam: LPARAM, - result: &mut ProcResult, - ) -> Option { - match msg_kind { - win32wm::WM_IME_ENDCOMPOSITION => { - self.getting_ime_text = true; - } - win32wm::WM_CHAR | win32wm::WM_SYSCHAR => { - *result = ProcResult::Value(LRESULT(0)); - if self.getting_ime_text { - self.utf16parts.push(wparam.0 as u16); - - let more_char_coming; - unsafe { - let mut next_msg = MaybeUninit::uninit(); - let has_message = PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ); - let has_message = has_message.as_bool(); - if !has_message { - more_char_coming = false; - } else { - let next_msg = next_msg.assume_init().message; - more_char_coming = next_msg == WM_CHAR || next_msg == WM_SYSCHAR; - } - } - if !more_char_coming { - let result = String::from_utf16(&self.utf16parts).ok(); - self.utf16parts.clear(); - self.getting_ime_text = false; - return result; - } - } else { - return String::from_utf16(&[wparam.0 as u16]).ok(); - } - } - _ => (), - } - - None - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml similarity index 96% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml index 3f0674d4e..e894d86b8 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml @@ -11,9 +11,9 @@ [package] edition = "2021" -rust-version = "1.74" +rust-version = "1.85" name = "tao" -version = "0.35.2" +version = "0.37.1" authors = [ "Tauri Programme within The Commons Conservancy", "The winit contributors", @@ -79,6 +79,10 @@ x11 = [ name = "tao" path = "src/lib.rs" +[[example]] +name = "background_color" +path = "examples/background_color.rs" + [[example]] name = "control_flow" path = "examples/control_flow.rs" @@ -214,9 +218,6 @@ version = "0.2" [dependencies.log] version = "0.4" -[dependencies.once_cell] -version = "1" - [dependencies.rwh_04] version = "0.4" optional = true @@ -284,6 +285,9 @@ version = "0.21" [target.'cfg(target_os = "android")'.dependencies.ndk] version = "0.9" +[target.'cfg(target_os = "android")'.dependencies.ndk-context] +version = "0.1" + [target.'cfg(target_os = "android")'.dependencies.ndk-sys] version = "0.6" @@ -291,7 +295,7 @@ version = "0.6" version = "2.3" [target.'cfg(target_os = "android")'.dependencies.tao-macros] -version = "0.1.0" +version = "0.1.4" [target.'cfg(target_os = "ios")'.dependencies.objc2-foundation] version = "0.3" @@ -305,6 +309,8 @@ features = [ "UIScene", "UISceneOptions", "UIOpenURLContext", + "UISceneWindowingControlStyle", + "UIWindowScene", ] [target.'cfg(target_os = "macos")'.dependencies.core-foundation] @@ -365,7 +371,7 @@ version = "0.12" version = "1.11" [target.'cfg(target_os = "windows")'.dependencies.windows] -version = "0.61" +version = "0.62" features = [ "Win32_Devices_HumanInterfaceDevice", "Win32_Foundation", @@ -397,7 +403,7 @@ features = [ ] [target.'cfg(target_os = "windows")'.dependencies.windows-core] -version = "0.61" +version = "0.62" [target.'cfg(target_os = "windows")'.dependencies.windows-version] version = "0.1" diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml.orig b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml.orig similarity index 95% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml.orig rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml.orig index 75c8c138b..5aa24dfdb 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml.orig +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml.orig @@ -1,13 +1,13 @@ [package] name = "tao" -version = "0.35.2" +version = "0.37.1" description = "Cross-platform window manager library." authors = [ "Tauri Programme within The Commons Conservancy", "The winit contributors", ] edition = "2021" -rust-version = "1.74" +rust-version = "1.85" keywords = ["windowing"] license = "Apache-2.0" readme = "README.md" @@ -42,7 +42,6 @@ members = ["tao-macros"] [dependencies] libc = "0.2" log = "0.4" -once_cell = "1" serde = { version = "1", optional = true, features = ["serde_derive"] } rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true } rwh_05 = { package = "raw-window-handle", version = "0.5", features = [ @@ -67,10 +66,10 @@ softbuffer = "0.4" parking_lot = "0.12" unicode-segmentation = "1.11" windows-version = "0.1" -windows-core = "0.61" +windows-core = "0.62" [target."cfg(target_os = \"windows\")".dependencies.windows] -version = "0.61" +version = "0.62" features = [ "Win32_Devices_HumanInterfaceDevice", "Win32_Foundation", @@ -105,8 +104,9 @@ features = [ jni = "0.21" ndk = "0.9" ndk-sys = "0.6" +ndk-context = "0.1" percent-encoding = "2.3" -tao-macros = { version = "0.1.0", path = "./tao-macros" } +tao-macros = { version = "0.1.4", path = "./tao-macros" } [target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies] objc2 = "0.6" @@ -157,6 +157,8 @@ objc2-ui-kit = { version = "0.3", features = [ "UIScene", "UISceneOptions", "UIOpenURLContext", + "UISceneWindowingControlStyle", + "UIWindowScene", ] } objc2-foundation = { version = "0.3", default-features = false, features = [ "NSProcessInfo", diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE.spdx b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE.spdx similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE.spdx rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE.spdx diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/README.md b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/README.md similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/README.md rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/README.md diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/background_color.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/background_color.rs new file mode 100644 index 000000000..9dff72f93 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/background_color.rs @@ -0,0 +1,49 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use dpi::LogicalSize; +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::KeyCode, + window::WindowBuilder, +}; + +fn main() { + let event_loop = EventLoop::new(); + + let mut background_color = (100, 100, 100, 255); + + let window = WindowBuilder::new() + .with_title("Hit space to change background color!") + .with_inner_size(LogicalSize::new(400.0, 300.0)) + .with_background_color(background_color) + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key: KeyCode::Space, + state: ElementState::Released, + .. + }, + .. + } => { + background_color.1 = background_color.1.wrapping_add(20); + println!("Setting background color to: {background_color:?}"); + window.set_background_color(Some(background_color)); + } + _ => (), + }, + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/control_flow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/control_flow.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/control_flow.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/control_flow.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor_grab.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor_grab.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor_grab.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor_grab.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/custom_events.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/custom_events.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/custom_events.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/custom_events.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/decorations.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/decorations.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/decorations.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/decorations.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/drag_window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/drag_window.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/drag_window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/drag_window.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/fullscreen.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/fullscreen.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/fullscreen.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/fullscreen.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/handling_close.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/handling_close.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/handling_close.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/handling_close.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/min_max_size.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/min_max_size.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/min_max_size.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/min_max_size.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/minimize.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/minimize.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/minimize.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/minimize.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/monitor_list.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/monitor_list.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/monitor_list.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/monitor_list.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/mouse_wheel.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/mouse_wheel.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/mouse_wheel.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/mouse_wheel.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multithreaded.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multithreaded.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multithreaded.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multithreaded.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multiwindow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multiwindow.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multiwindow.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multiwindow.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/overlay.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/overlay.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/overlay.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/overlay.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/parentwindow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/parentwindow.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/parentwindow.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/parentwindow.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/progress_bar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/progress_bar.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/progress_bar.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/progress_bar.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/reopen_event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/reopen_event.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/reopen_event.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/reopen_event.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw_threaded.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw_threaded.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw_threaded.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw_threaded.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/resizable.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/resizable.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/resizable.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/resizable.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/set_ime_position.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/set_ime_position.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/set_ime_position.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/set_ime_position.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/theme.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/theme.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/theme.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/theme.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/timer.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/timer.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/timer.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/timer.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/transparent.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/transparent.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/transparent.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/transparent.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/video_modes.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/video_modes.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/video_modes.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/video_modes.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_debug.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_debug.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_debug.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_debug.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_icon.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_icon.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_icon.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_run_return.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_run_return.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_run_return.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_run_return.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/error.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/error.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/error.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/error.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event.rs similarity index 97% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event.rs index d72af30ba..14325dc5e 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event.rs @@ -77,24 +77,6 @@ pub enum Event<'a, T: 'static> { /// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event) UserEvent(T), - /// Emitted when the application has been suspended. - /// - /// ## Platform-specific - /// - /// - **Android**: This is triggered by `onPause` method of the Activity. - /// - **iOS**: This is triggered by `applicationWillResignActive` method of the UIApplicationDelegate. - /// - **Linux / macOS / Windows**: Unsupported. - Suspended, - - /// Emitted when the application has been resumed. - /// - /// ## Platform-specific - /// - /// - **Android**: This is triggered by `onResume` method of the Activity. The first onResume() is ignored to match the iOS implementation, since that is called on activity creation. - /// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate. - /// - **Linux / macOS / Windows**: Unsupported. - Resumed, - /// Emitted when all of the event loop's input events have been processed and redraw processing /// is about to begin. /// @@ -141,7 +123,7 @@ pub enum Event<'a, T: 'static> { /// Emitted when the event loop is being shut down. /// - /// This is irreversable - if this event is emitted, it is guaranteed to be the last event that + /// This is irreversible - if this event is emitted, it is guaranteed to be the last event that /// gets emitted. You generally want to treat this as an "do on quit" event. LoopDestroyed, @@ -189,8 +171,6 @@ impl Clone for Event<'static, T> { RedrawRequested(wid) => RedrawRequested(*wid), RedrawEventsCleared => RedrawEventsCleared, LoopDestroyed => LoopDestroyed, - Suspended => Suspended, - Resumed => Resumed, Opened { urls } => Opened { urls: urls.clone() }, Reopen { has_visible_windows, @@ -218,8 +198,6 @@ impl<'a, T> Event<'a, T> { RedrawRequested(wid) => Ok(RedrawRequested(wid)), RedrawEventsCleared => Ok(RedrawEventsCleared), LoopDestroyed => Ok(LoopDestroyed), - Suspended => Ok(Suspended), - Resumed => Ok(Resumed), Opened { urls } => Ok(Opened { urls }), Reopen { has_visible_windows, @@ -246,8 +224,6 @@ impl<'a, T> Event<'a, T> { RedrawRequested(wid) => Some(RedrawRequested(wid)), RedrawEventsCleared => Some(RedrawEventsCleared), LoopDestroyed => Some(LoopDestroyed), - Suspended => Some(Suspended), - Resumed => Some(Resumed), Opened { urls } => Some(Opened { urls }), Reopen { has_visible_windows, @@ -300,7 +276,7 @@ pub enum WindowEvent<'a> { /// /// ## Platform-specific /// - /// - **Linux(Wayland)**: will always be (0, 0) since Wayland doesn't support a global cordinate system. + /// - **Linux(Wayland)**: will always be (0, 0) since Wayland doesn't support a global coordinate system. Moved(PhysicalPosition), /// The window has been requested to close. @@ -314,6 +290,40 @@ pub enum WindowEvent<'a> { /// - **macOS:** Fired if the [`crate::window::Window`] is dropped or the dock `Quit` item is clicked. Destroyed, + /// The window has been started. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onStart` method of the Activity. + /// - **Linux / macOS / iOS / Windows**: Unsupported. + Started, + + /// The window has been suspended. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onPause` method of the Activity. + /// - **iOS**: This is triggered by `applicationWillResignActive` method of the UIApplicationDelegate. + /// - **Linux / macOS / Windows**: Unsupported. + Suspended, + + /// The window has been resumed. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onResume` method of the Activity. The first onResume() is ignored to match the iOS implementation, since that is called on activity creation. + /// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate. + /// - **Linux / macOS / Windows**: Unsupported. + Resumed, + + /// The window has been stopped. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onStop` method of the Activity. + /// - **Linux / macOS / iOS / Windows**: Unsupported. + Stopped, + /// A file has been dropped into the window. /// /// When the user drops multiple files at once, this event will be emitted for each file @@ -467,6 +477,10 @@ impl Clone for WindowEvent<'static> { Moved(pos) => Moved(*pos), CloseRequested => CloseRequested, Destroyed => Destroyed, + Started => Started, + Suspended => Suspended, + Resumed => Resumed, + Stopped => Stopped, DroppedFile(file) => DroppedFile(file.clone()), HoveredFile(file) => HoveredFile(file.clone()), HoveredFileCancelled => HoveredFileCancelled, @@ -559,6 +573,10 @@ impl<'a> WindowEvent<'a> { Moved(position) => Some(Moved(position)), CloseRequested => Some(CloseRequested), Destroyed => Some(Destroyed), + Started => Some(Started), + Suspended => Some(Suspended), + Resumed => Some(Resumed), + Stopped => Some(Stopped), DroppedFile(file) => Some(DroppedFile(file)), HoveredFile(file) => Some(HoveredFile(file)), HoveredFileCancelled => Some(HoveredFileCancelled), diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event_loop.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event_loop.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event_loop.rs index 6e3406d45..eb73dbbba 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event_loop.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event_loop.rs @@ -403,7 +403,7 @@ impl fmt::Display for EventLoopClosed { impl error::Error for EventLoopClosed {} -/// Fiter controlling the propagation of device events. +/// Filter controlling the propagation of device events. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub enum DeviceEventFilter { /// Always filter out device events. diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/icon.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/icon.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/icon.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/keyboard.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/keyboard.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/keyboard.rs index ac50d42c5..8628f0f67 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/keyboard.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/keyboard.rs @@ -202,7 +202,7 @@ pub enum NativeKeyCode { /// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and /// "SuperRight" here. /// - The key that the specification calls "Super" is reported as `Unidentified` here. -/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. +/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`. /// /// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables #[non_exhaustive] @@ -837,8 +837,8 @@ impl fmt::Display for KeyCode { /// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's /// another key which the specification calls `Super`. That does not exist here.) /// - The `Space` variant here, can be identified by the character it generates in the -/// specificaiton. -/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. +/// specification. +/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`. /// - The `Dead` variant here, can specify the character which is inserted when pressing the /// dead-key twice. /// diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/lib.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/lib.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/lib.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/lib.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/monitor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/monitor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/monitor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/android.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/android.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/android.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/android.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/ios.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/ios.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/ios.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/ios.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/linux.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/linux.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/linux.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/linux.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/macos.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/macos.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/macos.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/macos.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/mod.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/mod.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/run_return.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/run_return.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/run_return.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/run_return.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/unix.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/unix.rs similarity index 98% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/unix.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/unix.rs index c96fb9361..c25b89fc8 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/unix.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/unix.rs @@ -123,7 +123,7 @@ pub trait WindowBuilderExtUnix { /// Whether to enable or disable the internal draw for transparent window. /// - /// When tranparent attribute is enabled, we will call `connect_draw` and draw a transparent background. + /// When transparent attribute is enabled, we will call `connect_draw` and draw a transparent background. /// For anyone who wants to draw the background themselves, set this to `false`. /// Default is `true`. fn with_transparent_draw(self, draw: bool) -> WindowBuilder; @@ -138,7 +138,7 @@ pub trait WindowBuilderExtUnix { /// Default is `false` but is always `true` if [`WindowAttributes::transparent`](crate::window::WindowAttributes::transparent) is `true` fn with_rgba_visual(self, rgba_visual: bool) -> WindowBuilder; - /// Wether to set this window as app paintable + /// Whether to set this window as app paintable /// /// /// diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/windows.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/windows.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/windows.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/windows.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/mod.rs similarity index 91% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/mod.rs index b78113853..5acb4d7ff 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/mod.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/mod.rs @@ -15,12 +15,11 @@ use ndk::{ configuration::Configuration, looper::{ForeignLooper, Poll, ThreadLooper}, }; -use once_cell::sync::Lazy; use std::{ - collections::VecDeque, + collections::{HashSet, VecDeque}, error::Error, fmt, - sync::RwLock, + sync::{LazyLock, RwLock}, time::{Duration, Instant}, }; @@ -123,7 +122,8 @@ macro_rules! jni_call_method { pub mod ndk_glue; use ndk_glue::{ActivityId, Event, Rect, WindowEvent}; -static CONFIG: Lazy> = Lazy::new(|| RwLock::new(Configuration::new())); +static CONFIG: LazyLock> = + LazyLock::new(|| RwLock::new(Configuration::new())); #[derive(Debug)] pub enum OsError { @@ -170,22 +170,12 @@ pub struct EventLoop { first_event: Option, start_cause: event::StartCause, looper: ThreadLooper, - running: bool, + running: HashSet, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} -macro_rules! call_event_handler { - ( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{ - if let ControlFlow::ExitWithCode(code) = $cf { - $event_handler($event, $window_target, &mut ControlFlow::ExitWithCode(code)); - } else { - $event_handler($event, $window_target, &mut $cf); - } - }}; -} - impl EventLoop { pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { let (sender, receiver) = crossbeam_channel::unbounded(); @@ -202,7 +192,7 @@ impl EventLoop { first_event: None, start_cause: event::StartCause::Init, looper: ThreadLooper::for_thread().unwrap(), - running: false, + running: HashSet::new(), } } @@ -219,14 +209,14 @@ impl EventLoop { where F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { - let mut control_flow = ControlFlow::default(); + let event_handler = &mut event_handler; + let control_flow = &mut ControlFlow::default(); - 'event_loop: loop { - call_event_handler!( + loop { + self.call_event_handler( event_handler, - self.window_target(), control_flow, - event::Event::NewEvents(self.start_cause) + event::Event::NewEvents(self.start_cause), ); let mut redraw_window_id = None; @@ -234,12 +224,14 @@ impl EventLoop { match self.first_event.take() { Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { - Event::Resume => { - call_event_handler!( + Event::Resume { id: window_id } => { + self.call_event_handler( event_handler, - self.window_target(), control_flow, - event::Event::Resumed + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Resumed, + }, ); } Event::WindowEvent { @@ -248,49 +240,65 @@ impl EventLoop { } => match event { WindowEvent::Resized => resized_window_id = Some(window_id), WindowEvent::RedrawNeeded => redraw_window_id = Some(window_id), + WindowEvent::Started => { + self.running.insert(window_id); + self.call_event_handler( + event_handler, + control_flow, + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Started, + }, + ); + } WindowEvent::Focused(focused) => { - call_event_handler!( + self.call_event_handler( event_handler, - self.window_target(), control_flow, event::Event::WindowEvent { window_id, - event: event::WindowEvent::Focused(focused) - } + event: event::WindowEvent::Focused(focused), + }, + ); + } + WindowEvent::Stopped => { + self.running.remove(&window_id); + self.call_event_handler( + event_handler, + control_flow, + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Stopped, + }, ); } WindowEvent::Destroyed => { - call_event_handler!( + self.running.remove(&window_id); + self.call_event_handler( event_handler, - self.window_target(), control_flow, event::Event::WindowEvent { window_id, event: event::WindowEvent::Destroyed, - } + }, ); } _ => {} }, - Event::Pause => { - call_event_handler!( + Event::Pause { id: window_id } => { + self.call_event_handler( event_handler, - self.window_target(), control_flow, - event::Event::Suspended + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Suspended, + }, ); } - Event::Stop => self.running = false, - Event::Start => self.running = true, Event::Opened => { let urls = ndk_glue::take_intent_urls(); if !urls.is_empty() { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::Opened { urls } - ); + self.call_event_handler(event_handler, control_flow, event::Event::Opened { urls }); } } //Event::ConfigChanged => { @@ -309,7 +317,7 @@ impl EventLoop { // scale_factor, // }, // }; - // call_event_handler!(event_handler, self.window_target(), control_flow, event); + // self.call_event_handler(event_handler, control_flow, event); // } //} _ => {} @@ -365,9 +373,8 @@ impl EventLoop { force: None, }), }; - call_event_handler!( + self.call_event_handler( event_handler, - self.window_target(), control_flow, event ); @@ -403,7 +410,11 @@ impl EventLoop { is_synthetic: false, }, }; - call_event_handler!(event_handler, self.window_target(), control_flow, event); + self.call_event_handler( + event_handler, + control_flow, + event, + ); } _ => {} }; @@ -413,80 +424,58 @@ impl EventLoop { } */ } - Some(EventSource::User) => { - while let Ok(event) = self.receiver.try_recv() { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::UserEvent(event) - ); - } - } - None => {} + _ => {} } - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::MainEventsCleared - ); + // We need to treat every event as if they were from [`EventLoopProxy::send_event`] + // + // > All return values may also imply ALOOPER_POLL_WAKE. If you call this in a loop, + // > you must treat all return values as if they also indicated ALOOPER_POLL_WAKE. + // > https://developer.android.com/ndk/reference/group/looper#alooper_pollonce + while let Ok(event) = self.receiver.try_recv() { + self.call_event_handler(event_handler, control_flow, event::Event::UserEvent(event)); + } + + self.call_event_handler(event_handler, control_flow, event::Event::MainEventsCleared); if let Some(window_id) = resized_window_id { - if self.running { + if self.running.contains(&window_id) { let size = MonitorHandle.size(); let event = event::Event::WindowEvent { window_id, event: event::WindowEvent::Resized(size), }; - call_event_handler!(event_handler, self.window_target(), control_flow, event); + self.call_event_handler(event_handler, control_flow, event); } } if let Some(window_id) = redraw_window_id { - if self.running { + if self.running.contains(&window_id) { let event = event::Event::RedrawRequested(window_id); - call_event_handler!(event_handler, self.window_target(), control_flow, event); + self.call_event_handler(event_handler, control_flow, event); } } - call_event_handler!( + self.call_event_handler( event_handler, - self.window_target(), control_flow, - event::Event::RedrawEventsCleared + event::Event::RedrawEventsCleared, ); - match control_flow { + match *control_flow { ControlFlow::ExitWithCode(code) => { - self.first_event = poll( - self - .looper - .poll_once_timeout(Duration::from_millis(0)) - .unwrap(), - ); + self.first_event = poll(self.looper.poll_once_timeout(Duration::ZERO).unwrap()); self.start_cause = event::StartCause::WaitCancelled { start: Instant::now(), requested_resume: None, }; - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::LoopDestroyed - ); + self.call_event_handler(event_handler, control_flow, event::Event::LoopDestroyed); - break 'event_loop code; + return code; } ControlFlow::Poll => { - self.first_event = poll( - self - .looper - .poll_all_timeout(Duration::from_millis(0)) - .unwrap(), - ); + self.first_event = poll(self.looper.poll_all_timeout(Duration::ZERO).unwrap()); self.start_cause = event::StartCause::Poll; } ControlFlow::Wait => { @@ -520,6 +509,25 @@ impl EventLoop { } } + fn call_event_handler( + &self, + event_handler: &mut F, + control_flow: &mut ControlFlow, + event: event::Event<'_, T>, + ) where + F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + if let ControlFlow::ExitWithCode(code) = *control_flow { + event_handler( + event, + self.window_target(), + &mut ControlFlow::ExitWithCode(code), + ); + } else { + event_handler(event, self.window_target(), control_flow); + } + } + pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { &self.window_target } @@ -539,7 +547,7 @@ pub struct EventLoopProxy { impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { - _ = self.queue.try_send(event); + let _ = self.queue.send(event); self.looper.wake(); Ok(()) } @@ -883,7 +891,7 @@ impl Window { if let Some(w) = ndk_glue::activity_window_manager(self.activity_id).as_ref() { handle.a_native_window = w.as_obj().as_raw() as *mut _; } else { - panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events."); + panic!("Cannot get the native window, it's null and will always be null before WindowEvent::Resumed and after WindowEvent::Suspended. Make sure you only call this function between those events."); }; rwh_04::RawWindowHandle::AndroidNdk(handle) } @@ -895,7 +903,7 @@ impl Window { if let Some(w) = ndk_glue::activity_window_manager(self.activity_id).as_ref() { handle.a_native_window = w.as_obj().as_raw() as *mut _; } else { - panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events."); + panic!("Cannot get the native window, it's null and will always be null before WindowEvent::Resumed and after WindowEvent::Suspended. Make sure you only call this function between those events."); }; rwh_05::RawWindowHandle::AndroidNdk(handle) } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/ndk_glue.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs similarity index 78% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/ndk_glue.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs index 396936a9e..bed08d359 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/ndk_glue.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs @@ -17,7 +17,6 @@ use ndk::{ input_queue::InputQueue, looper::{FdEvent, ForeignLooper, ThreadLooper}, }; -use once_cell::sync::{Lazy, OnceCell}; use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; use std::{ collections::{BTreeMap, HashSet}, @@ -25,17 +24,14 @@ use std::{ fs::File, io::{BufRead, BufReader}, os::unix::prelude::*, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Condvar, Mutex, RwLock, RwLockReadGuard, - }, + sync::{Arc, Condvar, LazyLock, Mutex, OnceLock, RwLock, RwLockReadGuard}, thread, time::Duration, }; /// Android pacakge name that could be used to reference classes /// in the android project. -pub static PACKAGE: OnceCell<&str> = OnceCell::new(); +pub static PACKAGE: OnceLock<&str> = OnceLock::new(); /// Character set for encoding text content in data URLs. /// Encodes all control characters and special characters that might cause issues in URLs. @@ -64,22 +60,23 @@ const DATA_URL_ENCODING_SET: &AsciiSet = &CONTROLS /// 1. android app domain name in reverse snake_case as an ident (for ex: com_example) /// 2. android package anme (for ex: wryapp) /// 3. the android activity that has external linking for the following functions and calls them: -/// - `private external fun onActivityCreate(activity: WryActivity)`` -/// - `private external fun start()` -/// - `private external fun resume()` -/// - `private external fun pause()` -/// - `private external fun stop()` -/// - `private external fun onActivitySaveInstanceState()` -/// - `private external fun onActivityDestroy(activity: WryActivity)` -/// - `private external fun onActivityLowMemory()` +/// - `private external fun onFirstActivityCreate()` +/// - `private external fun onCreate(activity: WryActivity)` +/// - `private external fun onStart(activity: WryActivity)` +/// - `private external fun onResume(activity: WryActivity)` +/// - `private external fun onPause(activity: WryActivity)` +/// - `private external fun onStop(activity: WryActivity)` +/// - `private external fun onDestroy(activity: WryActivity)` /// - `private external fun onWindowFocusChanged(activity: WryActivity, focus: Boolean)` -/// 4. a on_activity_create function that will be ran once after the `onActivityCreate` function above. -/// 5. the main entry point of your android application. +/// - `private external fun onLowMemory()` +/// - `private external fun onNewIntent(intent: Intent)` +/// 4. an on_activity_create function that will be ran once after the `onCreate` function above. +/// 5. the main entry point of your android application that runs in `onFirstActivityCreate` after `onCreate` and `on_activity_create`. #[rustfmt::skip] #[macro_export] macro_rules! android_binding { ($domain:ident, $package:ident, $activity:ident, $on_activity_create:path, $main:ident) => { - ::tao::android_binding!($domain, $package, $activity, $setup, $main, ::tao) + ::tao::android_binding!($domain, $package, $activity, $on_activity_create, $main, ::tao) }; ($domain:ident, $package:ident, $activity:ident, $on_activity_create:path, $main:ident, $tao:path) => {{ // NOTE: be careful when changing how this use statement is written @@ -92,8 +89,8 @@ macro_rules! android_binding { $domain, $package, $activity, - create, - [JObject], + onFirstActivityCreate, + [], __VOID__, [$main], ); @@ -101,20 +98,19 @@ macro_rules! android_binding { $domain, $package, $activity, - onActivityCreate, + onCreate, [JObject], __VOID__, [$on_activity_create], _____tao_store_package_name__, ); - android_fn!($domain, $package, $activity, start, [JObject]); - android_fn!($domain, $package, $activity, stop, [JObject]); - android_fn!($domain, $package, $activity, resume, [JObject]); - android_fn!($domain, $package, $activity, pause, [JObject]); - android_fn!($domain, $package, $activity, onActivitySaveInstanceState, [JObject]); - android_fn!($domain, $package, $activity, onActivityDestroy, [JObject]); - android_fn!($domain, $package, $activity, onActivityLowMemory, [JObject]); - android_fn!($domain, $package, $activity, onWindowFocusChanged, [JObject,i32]); + android_fn!($domain, $package, $activity, onStart, [JObject]); + android_fn!($domain, $package, $activity, onStop, [JObject]); + android_fn!($domain, $package, $activity, onResume, [JObject]); + android_fn!($domain, $package, $activity, onPause, [JObject]); + android_fn!($domain, $package, $activity, onDestroy, [JObject]); + android_fn!($domain, $package, $activity, onWindowFocusChanged, [JObject, i32]); + android_fn!($domain, $package, $activity, onLowMemory, []); android_fn!($domain, $package, $activity, onNewIntent, [JObject]); }}; } @@ -212,16 +208,19 @@ unsafe impl Sync for AndroidContext {} pub type ActivityId = i32; -pub(crate) static CONTEXTS: Lazy>> = - Lazy::new(Default::default); -static WINDOW_MANAGER: Lazy>> = Lazy::new(Default::default); -pub(crate) static ACTIVITY_CREATED_SENDERS: Lazy>>> = - Lazy::new(Default::default); -static INTENT_URLS: Lazy>> = Lazy::new(Default::default); -static INPUT_QUEUE: Lazy>> = Lazy::new(Default::default); -static CONTENT_RECT: Lazy> = Lazy::new(Default::default); -static LOOPER: Lazy>> = Lazy::new(Default::default); -static DID_RESUME: AtomicBool = AtomicBool::new(false); +pub(crate) static CONTEXTS: LazyLock>> = + LazyLock::new(Default::default); +// Keeping a reference so we can store it in ndk-context +static APPLICATION_CONTEXT: OnceLock = OnceLock::new(); +static WINDOW_MANAGER: LazyLock>> = + LazyLock::new(Default::default); +pub(crate) static ACTIVITY_CREATED_SENDERS: LazyLock>>> = + LazyLock::new(Default::default); +static INTENT_URLS: LazyLock>> = LazyLock::new(Default::default); +static INPUT_QUEUE: LazyLock>> = LazyLock::new(Default::default); +static CONTENT_RECT: LazyLock> = LazyLock::new(Default::default); +static LOOPER: LazyLock>> = LazyLock::new(Default::default); +static RESUMED_ACTIVITIES: LazyLock>> = LazyLock::new(Default::default); pub fn main_window_manager() -> Option { WINDOW_MANAGER.lock().unwrap().values().next().cloned() @@ -243,6 +242,14 @@ pub fn content_rect() -> Rect { CONTENT_RECT.read().unwrap().clone() } +fn activity_id(env: &mut JNIEnv<'_>, activity: &JObject<'_>) -> ActivityId { + env + .call_method(activity, "getId", "()I", &[]) + .unwrap() + .i() + .unwrap() +} + pub fn main_android_context() -> Option { CONTEXTS.lock().unwrap().values().next().cloned() } @@ -257,7 +264,7 @@ pub fn next_available_activity() -> Option<(ActivityId, AndroidContext)> { .map(|(id, ctx)| (*id, ctx.clone())) } -pub static PIPE: Lazy<[OwnedFd; 2]> = Lazy::new(|| { +pub static PIPE: LazyLock<[OwnedFd; 2]> = LazyLock::new(|| { let mut pipe: [RawFd; 2] = Default::default(); unsafe { libc::pipe(pipe.as_mut_ptr()) }; pipe.map(|fd| unsafe { OwnedFd::from_raw_fd(fd) }) @@ -266,7 +273,7 @@ pub static PIPE: Lazy<[OwnedFd; 2]> = Lazy::new(|| { pub fn poll_events() -> Option { unsafe { let size = std::mem::size_of::(); - let mut event = Event::Start; + let mut event = Event::LowMemory; if libc::read(PIPE[0].as_raw_fd(), &mut event as *mut _ as *mut _, size) == size as libc::ssize_t { @@ -300,10 +307,8 @@ pub struct Rect { #[derive(Clone, Debug, Eq, PartialEq, Copy)] #[repr(u8)] pub enum Event { - Start, - Resume, - Pause, - Stop, + Resume { id: WindowId }, + Pause { id: WindowId }, LowMemory, WindowEvent { id: WindowId, event: WindowEvent }, ContentRectChanged, @@ -315,12 +320,15 @@ pub enum Event { pub enum WindowEvent { Focused(bool), Created, + Started, Resized, RedrawNeeded, + Stopped, Destroyed, } -pub unsafe fn create(_env: JNIEnv, _: JClass, _: JObject, main: fn()) { +#[allow(non_snake_case)] +pub unsafe fn onFirstActivityCreate(_env: JNIEnv, _: JClass, main: fn()) { let logpipe = { let mut logpipe: [RawFd; 2] = Default::default(); libc::pipe(logpipe.as_mut_ptr()); @@ -379,7 +387,7 @@ pub unsafe fn create(_env: JNIEnv, _: JClass, _: JObject, main: fn()) { } #[allow(non_snake_case)] -pub unsafe fn onActivityCreate( +pub unsafe fn onCreate( mut env: JNIEnv, _jclass: JClass, activity: JObject, @@ -388,6 +396,25 @@ pub unsafe fn onActivityCreate( let intent = jni_call_method!(env, &activity, "getIntent", "()Landroid/content/Intent;", l).ok(); + let vm = env.get_java_vm().unwrap(); + + APPLICATION_CONTEXT.get_or_init(|| { + let application = jni_call_method!( + env, + &activity, + "getApplicationContext", + "()Landroid/content/Context;", + l + ) + .unwrap(); + let application = env.new_global_ref(application).unwrap(); + ndk_context::initialize_android_context( + vm.get_java_vm_pointer() as *mut _, + application.as_obj().as_raw() as *mut _, + ); + application + }); + let activity_id = jni_call_method!(env, &activity, "getId", "()I", i).unwrap(); let activity_name: JString = jni_call_method!( @@ -420,7 +447,6 @@ pub unsafe fn onActivityCreate( .unwrap() .insert(activity_id, window_manager); let activity = env.new_global_ref(activity).unwrap(); - let vm = env.get_java_vm().unwrap(); let thread_env = vm.attach_current_thread_as_daemon().unwrap(); CONTEXTS.lock().unwrap().insert( @@ -448,17 +474,25 @@ pub unsafe fn onActivityCreate( } } -pub unsafe fn resume(_: JNIEnv, _: JClass, _: JObject) { - let did_resume = DID_RESUME.swap(true, Ordering::Relaxed); +#[allow(non_snake_case)] +pub unsafe fn onResume(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + let did_resume = !RESUMED_ACTIVITIES.lock().unwrap().insert(activity_id); // first Activity onResume() is called even after onCreate() // to match the iOS implementation, we ignore the first resume event if did_resume { - wake(Event::Resume); + wake(Event::Resume { + id: WindowId(super::WindowId(activity_id)), + }); } } -pub unsafe fn pause(_: JNIEnv, _: JClass, _: JObject) { - wake(Event::Pause); +#[allow(non_snake_case)] +pub unsafe fn onPause(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + wake(Event::Pause { + id: WindowId(super::WindowId(activity_id)), + }); } #[allow(non_snake_case)] @@ -468,7 +502,11 @@ pub unsafe fn onWindowFocusChanged( activity: JObject, has_focus: libc::c_int, ) { - let activity_id = jni_call_method!(env, &activity, "getId", "()I", i).unwrap(); + let activity_id = env + .call_method(&activity, "getId", "()I", &[]) + .unwrap() + .i() + .unwrap(); let event = Event::WindowEvent { id: WindowId(super::WindowId(activity_id)), event: WindowEvent::Focused(has_focus != 0), @@ -482,12 +520,14 @@ pub unsafe fn onNewIntent(env: JNIEnv, _: JClass, intent: JObject) { } pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { - // Maple local patch for https://github.com/tauri-apps/tao/issues/1217. + // Maple local patch. tauri-apps/tao#1217 was closed by #1224, which only made + // getType() null-safe; upstream tao (still in 0.37.1) unwraps getIntent, + // getAction, ClipData items and EXTRA_STREAM. // // Android can provide null intent/action/type objects during cold launch, - // task restoration, and OEM deep-link delivery. The upstream tao 0.35.2 - // handler unwraps these JNI calls, which aborts the process across the Rust - // JNI boundary. Treat malformed intent pieces as absent and return/continue. + // task restoration, and OEM deep-link delivery. Unwrapping these JNI calls + // aborts the process across the Rust JNI boundary. Treat malformed intent + // pieces as absent and return/continue. if intent.is_null() { return; } @@ -519,13 +559,13 @@ pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { // Get intent type (may be null) let intent_type = jni_call_method!(env, &intent, "getType", "()Ljava/lang/String;", l) .ok() - .filter(|intent_type| !intent_type.is_null()) - .and_then(|jstr| { - let intent_type: JString = jstr.into(); + .filter(|jstr| !jstr.is_null()) + .map(|jstr| jstr.into()) + .and_then(|intent_type: JString| { env .get_string(&intent_type) .ok() - .map(|intent_type| intent_type.to_string_lossy().to_string()) + .map(|s| s.to_string_lossy().to_string()) }); // Handle text/plain intents (EXTRA_TEXT) @@ -700,17 +740,31 @@ pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { } } -pub unsafe fn start(_: JNIEnv, _: JClass, _: JObject) { - wake(Event::Start); +#[allow(non_snake_case)] +pub unsafe fn onStart(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + wake(Event::WindowEvent { + id: WindowId(super::WindowId(activity_id)), + event: WindowEvent::Started, + }); } -pub unsafe fn stop(_: JNIEnv, _: JClass, _: JObject) { - wake(Event::Stop); +#[allow(non_snake_case)] +pub unsafe fn onStop(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + wake(Event::WindowEvent { + id: WindowId(super::WindowId(activity_id)), + event: WindowEvent::Stopped, + }); } #[allow(non_snake_case)] -pub unsafe fn onActivityDestroy(mut env: JNIEnv, _: JClass, activity: JObject) { - let activity_id = jni_call_method!(env, &activity, "getId", "()I", i).unwrap(); +pub unsafe fn onDestroy(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = env + .call_method(&activity, "getId", "()I", &[]) + .unwrap() + .i() + .unwrap(); let is_changing_configurations = jni_call_method!(env, &activity, "isChangingConfigurations", "()Z", z).unwrap(); @@ -724,6 +778,7 @@ pub unsafe fn onActivityDestroy(mut env: JNIEnv, _: JClass, activity: JObject) { }); CONTEXTS.lock().unwrap().remove(&activity_id); WINDOW_MANAGER.lock().unwrap().remove(&activity_id); + RESUMED_ACTIVITIES.lock().unwrap().remove(&activity_id); } } @@ -732,10 +787,7 @@ pub unsafe fn onActivityDestroy(mut env: JNIEnv, _: JClass, activity: JObject) { /////////////////////////////////////////////// #[allow(non_snake_case)] -pub unsafe fn onActivitySaveInstanceState(_: JNIEnv, _: JClass, _: JObject) {} - -#[allow(non_snake_case)] -pub unsafe fn onActivityLowMemory(_: JNIEnv, _: JClass, _: JObject) { +pub unsafe fn onLowMemory(_: JNIEnv, _: JClass) { wake(Event::LowMemory); } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/app_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs similarity index 93% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/app_state.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs index e1c5e2667..d46e076f4 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/app_state.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs @@ -28,7 +28,7 @@ use crate::{ CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger, NSOperatingSystemVersion, NSUInteger, }, - scene::multiple_scenes_enabled, + scene::scene_lifecycle_enabled, }, window::WindowId as RootWindowId, }; @@ -104,7 +104,14 @@ struct AppState { // Window::new might create a window before a scene is ready for it, // requesting a new scene to be activated and deferring the setWindowScene call to the scene delegate windows_for_next_scenes: Vec, + // Stores the UIWindow instances that were created while the app had no connected scene. + // The system can run an app on the scene lifecycle even when its Info.plist does not + // adopt it, in which case the windows are created before the first scene connects + scene_less_windows: Vec, did_first_scene_connect: bool, + // set once the queued windows were flushed and `StartCause::Init` was emitted, + // either from the application delegate or from the first scene connection + is_app_ready: bool, } impl Drop for AppState { @@ -157,7 +164,9 @@ impl AppState { control_flow: ControlFlow::default(), waker, windows_for_next_scenes: Vec::new(), + scene_less_windows: Vec::new(), did_first_scene_connect: false, + is_app_ready: false, }); } init_guard(&mut guard) @@ -485,6 +494,16 @@ pub unsafe fn unitialized_scene() -> Option> { None } +// the first connected scene that is able to display windows +pub unsafe fn first_window_scene() -> Option> { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + application + .connectedScenes() + .iter() + .find_map(|scene| scene.downcast_ref::().map(|s| s.retain())) +} + pub unsafe fn scene_by_id(id: &str) -> Option> { let mtm = MainThreadMarker::new().unwrap(); let application = UIApplication::sharedApplication(mtm); @@ -529,6 +548,18 @@ pub unsafe fn connect_scene(scene: &UIScene, options: &UISceneConnectionOptions) options: options.retain(), })); } + + // windows that were created before any scene existed are only ever displayed once + // they join one, so the first scene to connect adopts all of them + let scene_less_windows = mem::take(&mut AppState::get_mut().scene_less_windows); + for window in scene_less_windows { + // the system might have attached the window to a scene on its own already + let assigned_scene: id = msg_send![window, windowScene]; + if assigned_scene.is_null() { + let () = msg_send![window, setWindowScene: window_scene]; + } + let () = msg_send![window, release]; + } } } @@ -538,6 +569,17 @@ pub unsafe fn register_window_for_scene(window: id) { .push(msg_send![window, retain]); } +// retains window +pub unsafe fn register_scene_less_window(window: id) { + AppState::get_mut() + .scene_less_windows + .push(msg_send![window, retain]); +} + +pub unsafe fn did_first_scene_connect() -> bool { + AppState::get_mut().did_first_scene_connect +} + // requires main thread and window is a UIWindow // retains window pub unsafe fn set_key_window(window: id) { @@ -608,13 +650,20 @@ pub unsafe fn will_launch(queued_event_handler: Box) { // requires main thread pub unsafe fn did_finish_launching() { // when app is run in scenes lifecycle mode, we defer the did_finish_launching call to the first scene setup - if !multiple_scenes_enabled() { + if !scene_lifecycle_enabled() { on_app_ready(); } } unsafe fn on_app_ready() { let mut this = AppState::get_mut(); + // an app whose Info.plist does not declare a scene manifest is set up on + // `didFinishLaunchingWithOptions:` but can still be given a scene by the system, + // in which case the scene connection must not run this a second time + if this.is_app_ready { + return; + } + this.is_app_ready = true; let windows = match this.state_mut() { AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), s => bug!("unexpected state {:?}", s), @@ -875,6 +924,11 @@ pub unsafe fn handle_events_cleared() { AppState::get_mut().events_cleared_transition(); } +// requires main thread +pub unsafe fn is_terminated() -> bool { + matches!(AppState::get_mut().state(), AppStateImpl::Terminated) +} + // requires main thread pub unsafe fn terminated() { let mut this = AppState::get_mut(); @@ -1068,8 +1122,8 @@ impl NSOperatingSystemVersion { } pub fn os_capabilities() -> OSCapabilities { - use once_cell::sync::Lazy; - static OS_CAPABILITIES: Lazy = Lazy::new(|| { + use std::sync::LazyLock; + static OS_CAPABILITIES: LazyLock = LazyLock::new(|| { let version: NSOperatingSystemVersion = unsafe { let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; let atleast_ios_8: bool = msg_send![ diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/badge.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/badge.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/badge.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/badge.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/event_loop.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/event_loop.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/event_loop.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/ffi.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/ffi.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/ffi.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/keycode.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/keycode.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/keycode.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/mod.rs similarity index 89% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/mod.rs index ba183d445..08a994426 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/mod.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/mod.rs @@ -50,8 +50,10 @@ //! //! This is how those event are represented in tao: //! -//! - applicationWillEnterForeground is Resumed -//! - applicationWillResignActive is Suspended +//! - sceneWillEnterForeground is WindowEvent::Resumed for each window in the scene +//! - sceneWillResignActive is WindowEvent::Suspended for each window in the scene +//! - applicationWillEnterForeground is WindowEvent::Resumed for each window in non-scene apps +//! - applicationWillResignActive is WindowEvent::Suspended for each window in non-scene apps //! - applicationWillTerminate is LoopDestroyed //! //! Keep in mind that after LoopDestroyed event is received every attempt to draw with diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/monitor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/monitor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/monitor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/scene.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs similarity index 58% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/scene.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs index 23414f06b..0077367b3 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/scene.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs @@ -3,12 +3,11 @@ use objc2::{define_class, rc::Retained, MainThreadMarker, MainThreadOnly}; use objc2_foundation::{ - NSBundle, NSDictionary, NSError, NSNumber, NSObject, NSObjectProtocol, NSSet, NSString, - NSUserActivity, + NSBundle, NSError, NSObject, NSObjectProtocol, NSSet, NSString, NSUserActivity, }; use objc2_ui_kit::{ UIApplication, UIOpenURLContext, UIScene, UISceneConnectionOptions, UISceneDelegate, - UISceneSession, UIWindowScene, + UISceneSession, UISceneWindowingControlStyle, UIWindowScene, UIWindowSceneDelegate, }; use crate::{ @@ -17,7 +16,8 @@ use crate::{ window::WindowId as RootWindowId, }; -// true when the system allows the app to display multiple scenes and multiple_scenes_enabled() returns true +// true when the app enabled `UIApplicationSupportsMultipleScenes` in its Info.plist +// and the device allows it to display multiple scenes // https://developer.apple.com/documentation/uikit/uiapplication/supportsmultiplescenes?language=objc pub unsafe fn app_supports_multiple_scenes() -> bool { let mtm = MainThreadMarker::new().unwrap(); @@ -25,26 +25,38 @@ pub unsafe fn app_supports_multiple_scenes() -> bool { application.supportsMultipleScenes() } -// check whether the app's Info.plist enabled multiple scenes -pub unsafe fn multiple_scenes_enabled() -> bool { +// check whether the app adopted the scene lifecycle by declaring a +// `UIApplicationSceneManifest` in its Info.plist +// +// such apps get their windows and their lifecycle callbacks from a `UISceneDelegate` +// instead of the application delegate, so a `UIWindow` is only visible once it is +// attached to a scene +// https://developer.apple.com/documentation/bundleresources/information-property-list/uiapplicationscenemanifest +pub unsafe fn scene_lifecycle_enabled() -> bool { let bundle = NSBundle::mainBundle(); let Some(info) = bundle.infoDictionary() else { return false; }; let key = NSString::from_str("UIApplicationSceneManifest"); - let Some(manifest) = (*info).objectForKey(&key) else { - return false; - }; + (*info).objectForKey(&key).is_some() +} - let manifest_dict = Retained::cast_unchecked::>(manifest); - let supports_key = NSString::from_str("UIApplicationSupportsMultipleScenes"); - let Some(value) = (*manifest_dict).objectForKey(&supports_key) else { - return false; - }; +unsafe fn handle_scene_window_events(scene: &UIScene, event: impl Fn() -> WindowEvent<'static>) { + if let Some(window_scene) = scene.downcast_ref::() { + let windows = window_scene.windows(); + + if windows.is_empty() { + log::debug!("scene has no windows; no window events were emitted"); + } - let num = Retained::cast_unchecked::(value); - (*num).as_bool() + for window in windows { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: event(), + })); + } + } } define_class!( @@ -69,39 +81,43 @@ define_class!( } } + // iOS may disconnect a scene because the user closed it, or because + // the system discarded a backgrounded scene to free resources. A + // restored scene goes through `scene:willConnectToSession:` again + // and gets a new `UIWindow` and `WindowId`. #[unsafe(method(sceneDidDisconnect:))] - fn sceneDidDisconnect(&self, _scene: &UIScene) {} + fn sceneDidDisconnect(&self, scene: &UIScene) { + unsafe { + if app_state::is_terminated() { + log::debug!("ignoring `sceneDidDisconnect` after application termination"); + return; + } + + handle_scene_window_events(scene, || WindowEvent::Destroyed); + } + } #[unsafe(method(sceneDidBecomeActive:))] fn sceneDidBecomeActive(&self, scene: &UIScene) { unsafe { - if let Some(window_scene) = scene.downcast_ref::() { - for window in window_scene.windows() { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Focused(true), - })); - } - } + handle_scene_window_events(scene, || WindowEvent::Focused(true)); } } #[unsafe(method(sceneWillResignActive:))] fn sceneWillResignActive(&self, scene: &UIScene) { unsafe { - if let Some(window_scene) = scene.downcast_ref::() { - for window in window_scene.windows() { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Focused(false), - })); - } - } + handle_scene_window_events(scene, || WindowEvent::Focused(false)); + handle_scene_window_events(scene, || WindowEvent::Suspended); } } #[unsafe(method(sceneWillEnterForeground:))] - fn sceneWillEnterForeground(&self, _scene: &UIScene) {} + fn sceneWillEnterForeground(&self, scene: &UIScene) { + unsafe { + handle_scene_window_events(scene, || WindowEvent::Resumed); + } + } #[unsafe(method(sceneDidEnterBackground:))] fn sceneDidEnterBackground(&self, _scene: &UIScene) {} @@ -182,4 +198,24 @@ define_class!( #[unsafe(method(scene:didUpdateUserActivity:))] fn scene_didUpdateUserActivity(&self, _scene: &UIScene, _user_activity: &NSUserActivity) {} } + + #[allow(non_snake_case)] + unsafe impl UIWindowSceneDelegate for TaoSceneDelegate {} + + // Registered from a plain `impl` because objc2 panics in debug builds when a + // protocol-block selector is missing from the runtime protocol, and this one is + // iOS 26+. Both paths register the same method, so iOS 26 still resolves it. + // + // TODO: Move back into the protocol block once objc2 0.7.0 fixes the check, + // ref . + #[allow(non_snake_case)] + impl TaoSceneDelegate { + #[unsafe(method_id(preferredWindowingControlStyleForScene:))] + fn preferredWindowingControlStyleForScene( + &self, + _window_scene: &UIWindowScene, + ) -> Retained { + UISceneWindowingControlStyle::minimalStyle() + } + } ); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/view.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/view.rs similarity index 81% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/view.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/view.rs index 7f37832fc..fb1f83c32 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/view.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/view.rs @@ -12,9 +12,8 @@ use objc2::{ runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel}, ClassType, MainThreadMarker, }; -use objc2_foundation::NSString; use objc2_ui_kit::{ - UIApplication, UISceneActivationRequestOptions, UISceneConfiguration, + UIApplication, UISceneActivationRequestOptions, UISceneConfiguration, UISceneSession, UISceneSessionActivationRequest, }; @@ -29,7 +28,7 @@ use crate::{ id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, UIRectEdge, UITouchPhase, UITouchType, BOOL, NO, YES, }, - scene::{app_supports_multiple_scenes, multiple_scenes_enabled}, + scene::app_supports_multiple_scenes, window::PlatformSpecificWindowBuilderAttributes, DeviceId, }, @@ -136,7 +135,11 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { let () = msg_send![super(object, superclass), layoutSubviews]; let window: id = msg_send![object, window]; - assert!(!window.is_null()); + // UIKit can lay out a view while it is detached during window and view-controller + // transitions. There is no window ID or screen geometry to report until reattachment. + if window.is_null() { + return; + } let window_bounds: CGRect = msg_send![window, bounds]; let screen: id = msg_send![window, screen]; let screen_space: id = msg_send![screen, coordinateSpace]; @@ -537,61 +540,55 @@ pub unsafe fn create_window( "Failed to initialize `UIWindow` instance" ); - if multiple_scenes_enabled() { - // if multiple scenes is enabled in Info.plist, we need to assign it - // regarless of whether the device supports multiple scenes or not - if let Some(scene) = app_state::unitialized_scene() { - let _: () = msg_send![window, setWindowScene: Retained::as_ptr(&scene)]; - } else if !app_supports_multiple_scenes() { - // if there's no unitialized scene and the app does not support multiple scenes - // we need to move this window to the main scene, otherwise it won't be visible - let scene = unsafe { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - application.connectedScenes().iter().next() - }; - let scene = scene - .as_ref() - .map(|s| Retained::as_ptr(s)) - .unwrap_or(std::ptr::null_mut()); + // on the scene lifecycle a window is only displayed once it belongs to a scene, so we + // must always assign one - the app runs on it whenever the system connected a scene, + // which it may do even when the Info.plist does not declare a scene manifest + if let Some(scene) = app_state::unitialized_scene() { + let _: () = msg_send![window, setWindowScene: Retained::as_ptr(&scene)]; + } else if app_supports_multiple_scenes() { + // only request a new scene if the system actually supports it + // otherwise it is silently ignored + unsafe { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + app_state::register_window_for_scene(window); - let _: () = msg_send![window, setWindowScene: scene]; - } else { - // only request a new scene if the system actually supports it - // otherwise it is silently ignored - unsafe { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - app_state::register_window_for_scene(window); - - let options = UISceneActivationRequestOptions::new(mtm); - if let Some(scene) = platform_attributes - .requesting_scene_identifier - .as_ref() - .and_then(|id| app_state::scene_by_id(id)) - { - options.setRequestingScene(Some(&scene)); - } + let options = UISceneActivationRequestOptions::new(mtm); + if let Some(scene) = platform_attributes + .requesting_scene_identifier + .as_ref() + .and_then(|id| app_state::scene_by_id(id)) + { + options.setRequestingScene(Some(&scene)); + } - let error_handler = block2::RcBlock::new(move |error| { - log::error!("error activating scene: {error:?}"); - }); - - if operating_system_version().0 >= 17 { - let request = UISceneSessionActivationRequest::request(); - request.setOptions(Some(&options)); - application.activateSceneSessionForRequest_errorHandler(&request, Some(&error_handler)); - } else { - #[allow(deprecated)] - application.requestSceneSessionActivation_userActivity_options_errorHandler( - None, - None, - Some(&options), - Some(&error_handler), - ); - } + let error_handler = block2::RcBlock::new(move |error| { + log::error!("error activating scene: {error:?}"); + }); + + if operating_system_version().0 >= 17 { + let request = UISceneSessionActivationRequest::request(); + request.setOptions(Some(&options)); + application.activateSceneSessionForRequest_errorHandler(&request, Some(&error_handler)); + } else { + #[allow(deprecated)] + application.requestSceneSessionActivation_userActivity_options_errorHandler( + None, + None, + Some(&options), + Some(&error_handler), + ); } } + } else if let Some(scene) = app_state::first_window_scene() { + // if there's no unitialized scene and the app does not support multiple scenes + // we need to move this window to the main scene, otherwise it won't be visible + let _: () = msg_send![window, setWindowScene: Retained::as_ptr(&scene)]; + } else { + // no scene is connected yet, either because the app runs on the legacy lifecycle + // - where the system attaches the window to a scene on its own - or because it was + // created before the first scene connected, which then adopts it + app_state::register_scene_less_window(window); } let () = msg_send![window, setRootViewController: view_controller]; @@ -630,20 +627,22 @@ pub fn create_delegate_class() { _: &Object, _: Sel, _application: id, - _connecting_scene_session: id, + connecting_scene_session: id, _options: id, ) -> id { unsafe { let mtm = objc2_foundation::MainThreadMarker::new_unchecked(); - let config = UISceneConfiguration::configurationWithName_sessionRole( - Some(&NSString::from_str("TaoScene")), - &NSString::from_str("UIWindowSceneSessionRoleApplication"), - mtm, - ); + // keep the role of the session that is connecting instead of assuming the + // window scene role, so scenes such as external displays still get their own. + // a `None` name picks the app's own configuration for that role when its + // Info.plist declares one, and the default configuration when it doesn't + let session: &UISceneSession = &*connecting_scene_session.cast(); + let config = + UISceneConfiguration::configurationWithName_sessionRole(None, &session.role(), mtm); // Dynamically set the delegate class name config.setDelegateClass(Some(super::scene::TaoSceneDelegate::class())); - Retained::as_ptr(&config) as _ + Retained::autorelease_ptr(config) as _ } } @@ -665,6 +664,27 @@ pub fn create_delegate_class() { } } + unsafe fn handle_tao_window_events(event: impl Fn() -> WindowEvent<'static>) { + let app: id = msg_send![class!(UIApplication), sharedApplication]; + let windows: id = msg_send![app, windows]; + let windows_enum: id = msg_send![windows, objectEnumerator]; + let mut events = Vec::new(); + loop { + let window: id = msg_send![windows_enum, nextObject]; + if window == nil { + break; + } + let is_tao_window: bool = msg_send![window, isKindOfClass: class!(TaoUIWindow)]; + if is_tao_window { + events.push(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: event(), + })); + } + } + app_state::handle_nonuser_events(events); + } + // custom URL schemes // https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app extern "C" fn application_open_url( @@ -701,35 +721,28 @@ pub fn create_delegate_class() { } extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) { - unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) } + unsafe { + if app_state::did_first_scene_connect() { + // the scene delegate already emits this event for the windows of its scene + return; + } + handle_tao_window_events(|| WindowEvent::Suspended) + } } extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) { - unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) } + unsafe { + if app_state::did_first_scene_connect() { + // the scene delegate already emits this event for the windows of its scene + return; + } + handle_tao_window_events(|| WindowEvent::Resumed) + } } - extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) {} - extern "C" fn will_terminate(_: &Object, _: Sel, _: id) { unsafe { - let app: id = msg_send![class!(UIApplication), sharedApplication]; - let windows: id = msg_send![app, windows]; - let windows_enum: id = msg_send![windows, objectEnumerator]; - let mut events = Vec::new(); - loop { - let window: id = msg_send![windows_enum, nextObject]; - if window == nil { - break; - } - let is_tao_window: bool = msg_send![window, isKindOfClass: class!(TaoUIWindow)]; - if is_tao_window { - events.push(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Destroyed, - })); - } - } - app_state::handle_nonuser_events(events); + handle_tao_window_events(|| WindowEvent::Destroyed); app_state::terminated(); } } @@ -742,17 +755,23 @@ pub fn create_delegate_class() { .expect("Failed to declare class `AppDelegate`"); unsafe { + // register the scene delegate class in the runtime upfront, the system looks it up by + // name when it decodes a scene session that was encoded on a previous launch + let _ = super::scene::TaoSceneDelegate::class(); + decl.add_method( sel!(application:didFinishLaunchingWithOptions:), did_finish_launching as extern "C" fn(_, _, _, _) -> _, ); - if multiple_scenes_enabled() { - decl.add_method( - sel!(application:configurationForConnectingSceneSession:options:), - configuration_for_connecting_scene_session as extern "C" fn(_, _, _, _, _) -> _, - ); - } + // always answer the scene configuration request, even when the Info.plist does not + // declare a scene manifest: UIKit only asks for it when the app runs on the scene + // lifecycle, and answering it is the only way to get our scene delegate installed + // there. Apps that never run on scenes simply never receive this callback. + decl.add_method( + sel!(application:configurationForConnectingSceneSession:options:), + configuration_for_connecting_scene_session as extern "C" fn(_, _, _, _, _) -> _, + ); decl.add_method( sel!(application:openURL:options:), @@ -772,10 +791,6 @@ pub fn create_delegate_class() { sel!(applicationWillEnterForeground:), will_enter_foreground as extern "C" fn(_, _, _), ); - decl.add_method( - sel!(applicationDidEnterBackground:), - did_enter_background as extern "C" fn(_, _, _), - ); decl.add_method( sel!(applicationWillTerminate:), diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/window.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/window.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/device.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/device.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/device.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/device.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/event_loop.rs similarity index 94% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/event_loop.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/event_loop.rs index 4e9fe979a..0859023b6 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/event_loop.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/event_loop.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 use std::{ - cell::RefCell, + cell::{Cell, RefCell}, collections::{HashSet, VecDeque}, error::Error, process, @@ -695,8 +695,10 @@ impl EventLoop { }); let tx_clone = event_tx.clone(); + let last_motion_time = Rc::new(Cell::new(0u32)); window.connect_motion_notify_event(move |window, motion| { - if cursor_moved { + let last = last_motion_time.replace(motion.time()); + if cursor_moved && motion.time() != last { if let Some(cursor) = motion.device() { let scale_factor = window.scale_factor(); let (_, x, y) = cursor.window_at_position(); @@ -713,7 +715,7 @@ impl EventLoop { } } } - glib::Propagation::Stop + glib::Propagation::Proceed }); let tx_clone = event_tx.clone(); @@ -730,55 +732,63 @@ impl EventLoop { }); let tx_clone = event_tx.clone(); + let last_press_time = Rc::new(Cell::new(0u32)); window.connect_button_press_event(move |_, event| { - let button = event.button(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - button: match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - _ => MouseButton::Other(button as u16), + let last = last_press_time.replace(event.time()); + if event.time() != last { + let button = event.button(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseInput { + button: match button { + 1 => MouseButton::Left, + 2 => MouseButton::Middle, + 3 => MouseButton::Right, + _ => MouseButton::Other(button as u16), + }, + state: ElementState::Pressed, + device_id: DEVICE_ID, + // this field is depracted so it is fine to pass empty state + modifiers: ModifiersState::empty(), }, - state: ElementState::Pressed, - device_id: DEVICE_ID, - // this field is depracted so it is fine to pass empty state - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!( - "Failed to send mouse input pressed event to event channel: {}", - e - ); + }) { + log::warn!( + "Failed to send mouse input pressed event to event channel: {}", + e + ); + } } - glib::Propagation::Stop + glib::Propagation::Proceed }); let tx_clone = event_tx.clone(); + let last_release_time = Rc::new(Cell::new(0u32)); window.connect_button_release_event(move |_, event| { - let button = event.button(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - button: match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - _ => MouseButton::Other(button as u16), + let last = last_release_time.replace(event.time()); + if event.time() != last { + let button = event.button(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseInput { + button: match button { + 1 => MouseButton::Left, + 2 => MouseButton::Middle, + 3 => MouseButton::Right, + _ => MouseButton::Other(button as u16), + }, + state: ElementState::Released, + device_id: DEVICE_ID, + // this field is depracted so it is fine to pass empty state + modifiers: ModifiersState::empty(), }, - state: ElementState::Released, - device_id: DEVICE_ID, - // this field is depracted so it is fine to pass empty state - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!( - "Failed to send mouse input released event to event channel: {}", - e - ); + }) { + log::warn!( + "Failed to send mouse input released event to event channel: {}", + e + ); + } } - glib::Propagation::Stop + glib::Propagation::Proceed }); let tx_clone = event_tx.clone(); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/icon.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/icon.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/icon.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keyboard.rs similarity index 96% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keyboard.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keyboard.rs index b9d7f781b..aa52b14f2 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keyboard.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keyboard.rs @@ -11,18 +11,18 @@ use gtk::{ gdk::{self, keys::constants::*, EventKey}, glib, }; -use once_cell::sync::Lazy; use std::{ collections::HashSet, ffi::c_void, os::raw::{c_int, c_uint}, ptr, slice, - sync::Mutex, + sync::{LazyLock, Mutex}, }; pub type RawKey = gdk::keys::Key; -static KEY_STRINGS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); +static KEY_STRINGS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); fn insert_or_get_key_str(string: String) -> &'static str { let mut string_set = KEY_STRINGS.lock().unwrap(); @@ -115,6 +115,12 @@ pub(crate) fn raw_key_to_key(gdk_key: RawKey) -> Option> { // KP_Separator? What does it map to? KP_Tab => Some(Key::Tab), KP_Up => Some(Key::ArrowUp), + + // JIS + Zenkaku_Hankaku => Some(Key::ZenkakuHankaku), + Hiragana_Katakana => Some(Key::HiraganaKatakana), + Henkan => Some(Key::Convert), + Muhenkan => Some(Key::NonConvert), // TODO: more mappings (media etc) _ => None, } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keycode.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keycode.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keycode.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/mod.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/mod.rs index 0473f67e5..aa317542e 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/mod.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/mod.rs @@ -15,7 +15,6 @@ mod util; mod window; pub mod taskbar; -pub mod wayland; #[cfg(feature = "x11")] pub mod x11; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/monitor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/monitor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/monitor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/portal.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/portal.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/portal.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/portal.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/taskbar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/taskbar.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/taskbar.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/taskbar.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/util.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/util.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/util.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/util.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/window.rs similarity index 92% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/window.rs index fca4c308c..0308f1bf2 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/window.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/window.rs @@ -6,14 +6,17 @@ use std::{ cell::RefCell, collections::VecDeque, rc::Rc, - sync::atomic::{AtomicBool, AtomicI32, Ordering}, + sync::{ + atomic::{AtomicBool, AtomicI32, Ordering}, + RwLock, + }, }; use gtk::{ gdk::WindowState, glib::{self, translate::ToGlibPtr}, prelude::*, - CssProvider, Settings, + CssProvider, EventBox, Settings, }; use crate::{ @@ -21,7 +24,6 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::MonitorHandle as RootMonitorHandle, - platform_impl::wayland::header::WlHeader, window::{ CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowSizeConstraints, RGBA, @@ -59,11 +61,17 @@ pub struct Window { maximized: Rc, is_always_on_top: Rc, minimized: Rc, - fullscreen: RefCell>, - inner_size_constraints: RefCell, + // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` + // otherwise unsynchronized &RefCell from multiple threads + fullscreen: RwLock>, + // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` + // otherwise unsynchronized &RefCell from multiple threads + inner_size_constraints: RwLock, /// Draw event Sender draw_tx: crossbeam_channel::Sender, - preferred_theme: RefCell>, + // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` + // otherwise unsynchronized &RefCell from multiple threads + preferred_theme: RwLock>, css_provider: CssProvider, } @@ -76,7 +84,6 @@ impl Window { let app = &event_loop_window_target.app; let window_requests_tx = event_loop_window_target.window_requests_tx.clone(); let draw_tx = event_loop_window_target.draw_tx.clone(); - let is_wayland = event_loop_window_target.is_wayland(); let mut window_builder = gtk::ApplicationWindow::builder() .application(app) @@ -87,10 +94,6 @@ impl Window { let window = window_builder.build(); - if is_wayland { - WlHeader::setup(&window, &attributes.title); - } - let window_id = WindowId(window.id()); event_loop_window_target .windows @@ -103,18 +106,25 @@ impl Window { .inner_size .map(|size| size.to_logical::(win_scale_factor as f64).into()) .unwrap_or((800, 600)); - window.set_default_size(1, 1); - window.resize(width, height); - - if attributes.maximized { - let maximize_process = util::WindowMaximizeProcess::new(window.clone(), attributes.resizable); - glib::idle_add_local_full(glib::Priority::HIGH_IDLE, move || { - let mut maximize_process = maximize_process.borrow_mut(); - maximize_process.next_step() - }); - } else { - window.set_resizable(attributes.resizable); - } + + window.set_default_size(width, height); + + // Trying to prevent Wayland Protocol Error about mismatched xdg_surface_buffer sizes + let maximized = attributes.maximized; + let resizable = attributes.resizable; + let configure_handler_id = Rc::new(RefCell::new(None)); + let configure_handler_id_ = configure_handler_id.clone(); + let id = window.connect_configure_event(move |window, _| { + if let Some(id) = configure_handler_id_.take() { + window.disconnect(id); + if maximized { + window.maximize(); + } + window.set_resizable(resizable); + } + false + }); + configure_handler_id.borrow_mut().replace(id); window.set_deletable(attributes.closable); @@ -180,6 +190,11 @@ impl Window { } window.set_visible(attributes.visible); window.set_decorated(attributes.decorations); + if window.display().backend().is_wayland() && !attributes.decorations { + // Enable CSD to prevent compositor from rendering server-side decorations + let event_box = EventBox::new(); + window.set_titlebar(Some(&event_box)); + } if attributes.always_on_bottom { window.set_keep_below(attributes.always_on_bottom); @@ -272,9 +287,9 @@ impl Window { maximized, minimized, is_always_on_top, - fullscreen: RefCell::new(attributes.fullscreen), - inner_size_constraints: RefCell::new(attributes.inner_size_constraints), - preferred_theme: RefCell::new(preferred_theme), + fullscreen: RwLock::new(attributes.fullscreen), + inner_size_constraints: RwLock::new(attributes.inner_size_constraints), + preferred_theme: RwLock::new(preferred_theme), css_provider: CssProvider::new(), }; @@ -415,9 +430,9 @@ impl Window { maximized, minimized, is_always_on_top, - fullscreen: RefCell::new(None), - inner_size_constraints: RefCell::new(WindowSizeConstraints::default()), - preferred_theme: RefCell::new(None), + fullscreen: RwLock::new(None), + inner_size_constraints: RwLock::new(WindowSizeConstraints::default()), + preferred_theme: RwLock::new(None), css_provider: CssProvider::new(), }; @@ -519,7 +534,7 @@ impl Window { pub fn set_min_inner_size(&self, size: Option) { let (width, height) = size.map(crate::extract_width_height).unzip(); - let mut size_constraints = self.inner_size_constraints.borrow_mut(); + let mut size_constraints = self.inner_size_constraints.write().unwrap(); size_constraints.min_width = width; size_constraints.min_height = height; self.set_size_constraints(*size_constraints) @@ -527,14 +542,14 @@ impl Window { pub fn set_max_inner_size(&self, size: Option) { let (width, height) = size.map(crate::extract_width_height).unzip(); - let mut size_constraints = self.inner_size_constraints.borrow_mut(); + let mut size_constraints = self.inner_size_constraints.write().unwrap(); size_constraints.max_width = width; size_constraints.max_height = height; self.set_size_constraints(*size_constraints) } pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { - *self.inner_size_constraints.borrow_mut() = constraints; + *self.inner_size_constraints.write().unwrap() = constraints; self.set_size_constraints(constraints) } @@ -682,7 +697,7 @@ impl Window { } pub fn set_fullscreen(&self, fullscreen: Option) { - self.fullscreen.replace(fullscreen.clone()); + *self.fullscreen.write().unwrap() = fullscreen.clone(); if let Err(e) = self .window_requests_tx .send((self.window_id, WindowRequest::Fullscreen(fullscreen))) @@ -692,7 +707,7 @@ impl Window { } pub fn fullscreen(&self) -> Option { - self.fullscreen.borrow().clone() + self.fullscreen.read().unwrap().clone() } pub fn set_decorations(&self, decorations: bool) { @@ -1009,7 +1024,7 @@ impl Window { } pub fn theme(&self) -> Theme { - if let Some(theme) = *self.preferred_theme.borrow() { + if let Some(theme) = *self.preferred_theme.read().unwrap() { return theme; } @@ -1022,7 +1037,7 @@ impl Window { } pub fn set_theme(&self, theme: Option) { - *self.preferred_theme.borrow_mut() = theme; + *self.preferred_theme.write().unwrap() = theme; if let Err(e) = self .window_requests_tx .send((WindowId::dummy(), WindowRequest::SetTheme(theme))) diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/ffi.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/ffi.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/ffi.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/mod.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/mod.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/xdisplay.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/xdisplay.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/xdisplay.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/xdisplay.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app.rs similarity index 97% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app.rs index a83363eef..ae5fd266b 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app.rs @@ -6,7 +6,7 @@ use std::{collections::VecDeque, ffi::CStr}; use objc2::runtime::{AnyClass as Class, ClassBuilder as ClassDecl, Sel}; use objc2_app_kit::{self as appkit, NSApplication, NSEvent, NSEventType}; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}; use crate::event::{DeviceEvent, ElementState, Event}; @@ -15,7 +15,7 @@ pub struct AppClass(pub *const Class); unsafe impl Send for AppClass {} unsafe impl Sync for AppClass {} -pub static APP_CLASS: Lazy = Lazy::new(|| unsafe { +pub static APP_CLASS: LazyLock = LazyLock::new(|| unsafe { let superclass = class!(NSApplication); let mut decl = ClassDecl::new(CStr::from_bytes_with_nul(b"TaoApp\0").unwrap(), superclass).unwrap(); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_delegate.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_delegate.rs similarity index 98% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_delegate.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_delegate.rs index 5107814c9..41b000477 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_delegate.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_delegate.rs @@ -16,12 +16,11 @@ use objc2::runtime::{ use objc2_foundation::{ NSArray, NSError, NSString, NSUserActivity, NSUserActivityTypeBrowsingWeb, NSURL, }; -use once_cell::sync::Lazy; use std::{ cell::{RefCell, RefMut}, ffi::{CStr, CString}, os::raw::c_void, - sync::Mutex, + sync::{LazyLock, Mutex}, time::Instant, }; @@ -44,7 +43,7 @@ pub struct AppDelegateClass(pub *const Class); unsafe impl Send for AppDelegateClass {} unsafe impl Sync for AppDelegateClass {} -pub static APP_DELEGATE_CLASS: Lazy = Lazy::new(|| unsafe { +pub static APP_DELEGATE_CLASS: LazyLock = LazyLock::new(|| unsafe { let superclass = class!(NSResponder); let mut decl = ClassDecl::new( CStr::from_bytes_with_nul(b"TaoAppDelegateParent\0").unwrap(), diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_state.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_state.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_state.rs index 03dff9132..a305d47f9 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_state.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_state.rs @@ -19,7 +19,7 @@ use std::{ use objc2::{msg_send, rc::Retained, runtime::AnyObject as Object}; use objc2_app_kit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSWindow}; use objc2_foundation::{MainThreadMarker, NSAutoreleasePool, NSSize}; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use crate::{ dpi::LogicalSize, @@ -42,7 +42,7 @@ use crate::{ use super::set_dock_visibility; -static HANDLER: Lazy = Lazy::new(|| Default::default()); +static HANDLER: LazyLock = LazyLock::new(|| Default::default()); impl<'a, Never> Event<'a, Never> { fn userify(self) -> Event<'a, T> { diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/badge.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/badge.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/badge.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/badge.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/dock.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/dock.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/dock.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/dock.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event.rs similarity index 98% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event.rs index 6b1ce97f5..c4bb2a9d2 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event.rs @@ -6,7 +6,7 @@ use std::{collections::HashSet, ffi::c_void, os::raw::c_ushort, sync::Mutex}; use objc2::{msg_send, rc::Retained}; use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSWindow}; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use core_foundation::{base::CFRelease, data::CFDataGetBytePtr}; use objc2_foundation::NSString; @@ -18,7 +18,8 @@ use crate::{ platform_impl::platform::{ffi, util::Never}, }; -static KEY_STRINGS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); +static KEY_STRINGS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); fn insert_or_get_key_str(string: String) -> &'static str { let mut string_set = KEY_STRINGS.lock().unwrap(); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event_loop.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event_loop.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event_loop.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/ffi.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/ffi.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/ffi.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/icon.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/icon.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/icon.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/keycode.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/keycode.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/keycode.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/mod.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/mod.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/monitor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/monitor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/monitor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/observer.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/observer.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/observer.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/observer.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/progress_bar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/progress_bar.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/progress_bar.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/progress_bar.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/async.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/async.rs similarity index 95% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/async.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/async.rs index 28363ffac..23dd8ec48 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/async.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/async.rs @@ -17,6 +17,7 @@ use crate::{ dpi::LogicalSize, platform_impl::platform::{ ffi::{self, id, NO, YES}, + view::reapply_traffic_light_inset, window::SharedState, }, }; @@ -219,11 +220,16 @@ pub unsafe fn make_key_and_order_front_sync(ns_window: &NSWindow) { // `setTitle:` isn't thread-safe. Calling it from another thread invalidates the // window drag regions, which throws an exception when not done in the main // thread -pub unsafe fn set_title_async(ns_window: &NSWindow, title: String) { +pub unsafe fn set_title_async(ns_window: &NSWindow, ns_view: &NSView, title: String) { let ns_window = MainThreadSafe(ns_window.retain()); + let ns_view = MainThreadSafe(ns_view.retain()); DispatchQueue::main().exec_async(move || { let title = NSString::from_str(&title); ns_window.setTitle(&title); + // `setTitle:` moves the traffic light buttons back to their default spot. + // Re-apply the configured inset kept on the view so the custom position + // survives a title change (#13044). + reapply_traffic_light_inset(&ns_window, &ns_view); }); } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/cursor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/cursor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/cursor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/cursor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/mod.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/mod.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/view.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/view.rs similarity index 98% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/view.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/view.rs index dfeba9414..4e4b6a231 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/view.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/view.rs @@ -28,7 +28,7 @@ use objc2_foundation::{ ns_string, MainThreadMarker, NSAttributedString, NSInteger, NSMutableAttributedString, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, }; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use crate::{ dpi::LogicalPosition, @@ -136,7 +136,7 @@ struct ViewClass(&'static Class); unsafe impl Send for ViewClass {} unsafe impl Sync for ViewClass {} -static VIEW_CLASS: Lazy = Lazy::new(|| unsafe { +static VIEW_CLASS: LazyLock = LazyLock::new(|| unsafe { let superclass = class!(NSView); let mut decl = ClassDecl::new(CStr::from_bytes_with_nul(b"TaoView\0").unwrap(), superclass).unwrap(); @@ -1180,3 +1180,18 @@ pub unsafe fn inset_traffic_lights(window: &NSWindow, position: LogicalPosition< button.setFrameOrigin(rect.origin); } } + +// Re-apply the custom traffic light inset stored on the view, if one was set. +// AppKit resets the buttons to their default position on some events (title +// change, leaving fullscreen) without triggering a `drawRect:`, so callers use +// this to restore the configured position afterwards (#13044, #15451). +pub unsafe fn reapply_traffic_light_inset(ns_window: &NSWindow, ns_view: &NSView) { + let state_ptr: *mut c_void = *ns_view.get_ivar("taoState"); + if state_ptr.is_null() { + return; + } + let state = &*(state_ptr as *mut ViewState); + if let Some(position) = state.traffic_light_inset { + inset_traffic_lights(ns_window, position); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window.rs index 8a19b9d2c..f910c957d 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window.rs @@ -60,7 +60,7 @@ use objc2_foundation::{ ns_string, MainThreadMarker, NSArray, NSAutoreleasePool, NSDictionary, NSInteger, NSPoint, NSRect, NSSize, NSString, NSTimeInterval, NSUInteger, }; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use super::{ ffi::{id, nil, NO}, @@ -402,10 +402,8 @@ pub(super) fn set_ns_theme(theme: Option) { } struct WindowClass(&'static Class); -unsafe impl Send for WindowClass {} -unsafe impl Sync for WindowClass {} -static WINDOW_CLASS: Lazy = Lazy::new(|| unsafe { +static WINDOW_CLASS: LazyLock = LazyLock::new(|| unsafe { let window_superclass = class!(NSWindow); let mut decl = ClassDecl::new( CStr::from_bytes_with_nul(b"TaoWindow\0").unwrap(), @@ -657,7 +655,7 @@ impl UnownedWindow { pub fn set_title(&self, title: &str) { unsafe { - util::set_title_async(&self.ns_window, title.to_string()); + util::set_title_async(&self.ns_window, &self.ns_view, title.to_string()); } } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window_delegate.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window_delegate.rs similarity index 98% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window_delegate.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window_delegate.rs index fa6e63085..4bd2f5ec1 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window_delegate.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window_delegate.rs @@ -18,7 +18,7 @@ use objc2_app_kit::{ self as appkit, NSApplicationPresentationOptions, NSPasteboard, NSView, NSWindow, }; use objc2_foundation::{ns_string, NSArray, NSAutoreleasePool, NSString, NSUInteger}; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use crate::{ dpi::{LogicalPosition, LogicalSize}, @@ -29,7 +29,7 @@ use crate::{ event::{EventProxy, EventWrapper}, ffi::{id, nil, BOOL, NO, YES}, util::{self, IdRef}, - view::ViewState, + view::{reapply_traffic_light_inset, ViewState}, window::{get_ns_theme, get_window_id, UnownedWindow}, }, window::{Fullscreen, WindowId}, @@ -159,7 +159,7 @@ struct WindowDelegateClass(*const Class); unsafe impl Send for WindowDelegateClass {} unsafe impl Sync for WindowDelegateClass {} -static WINDOW_DELEGATE_CLASS: Lazy = Lazy::new(|| unsafe { +static WINDOW_DELEGATE_CLASS: LazyLock = LazyLock::new(|| unsafe { let superclass = class!(NSResponder); let mut decl = ClassDecl::new( CStr::from_bytes_with_nul(b"TaoWindowDelegate\0").unwrap(), @@ -605,6 +605,9 @@ extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { if let Some(target_fullscreen) = target_fullscreen { window.set_fullscreen(target_fullscreen); } + // Leaving fullscreen resets the traffic light buttons to their default + // position without a `drawRect:`, so re-apply the custom inset (#15451). + unsafe { reapply_traffic_light_inset(&window.ns_window, &window.ns_view) }; }); state.emit_resize_event(); state.emit_move_event(); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/mod.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/mod.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dark_mode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dark_mode.rs similarity index 81% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dark_mode.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dark_mode.rs index 2a0189aac..26033112a 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dark_mode.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dark_mode.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #![allow(non_snake_case)] -use once_cell::sync::Lazy; +use std::sync::LazyLock; /// This is a simple implementation of support for Windows Dark Mode, /// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode use windows::{ @@ -25,10 +25,10 @@ use crate::window::Theme; use super::util; -static HUXTHEME: Lazy = - Lazy::new(|| unsafe { LoadLibraryA(s!("uxtheme.dll")).unwrap_or_default().0 as _ }); +static HUXTHEME: LazyLock = + LazyLock::new(|| unsafe { LoadLibraryA(s!("uxtheme.dll")).unwrap_or_default().0 as _ }); -static DARK_MODE_SUPPORTED: Lazy = Lazy::new(|| { +static DARK_MODE_SUPPORTED: LazyLock = LazyLock::new(|| { // We won't try to do anything for windows versions < 17763 // (Windows 10 October 2018 update) let v = *util::WIN_VERSION; @@ -39,17 +39,18 @@ pub fn allow_dark_mode_for_app(is_dark_mode: bool) { if *DARK_MODE_SUPPORTED { const UXTHEME_ALLOWDARKMODEFORAPP_ORDINAL: u16 = 135; type AllowDarkModeForApp = unsafe extern "system" fn(bool) -> bool; - static ALLOW_DARK_MODE_FOR_APP: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } + static ALLOW_DARK_MODE_FOR_APP: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORAPP_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORAPP_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); #[repr(C)] enum PreferredAppMode { @@ -61,17 +62,18 @@ pub fn allow_dark_mode_for_app(is_dark_mode: bool) { } const UXTHEME_SETPREFERREDAPPMODE_ORDINAL: u16 = 135; type SetPreferredAppMode = unsafe extern "system" fn(PreferredAppMode) -> PreferredAppMode; - static SET_PREFERRED_APP_MODE: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } + static SET_PREFERRED_APP_MODE: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_SETPREFERREDAPPMODE_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_SETPREFERREDAPPMODE_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); if util::WIN_VERSION.build < 18362 { if let Some(_allow_dark_mode_for_app) = *ALLOW_DARK_MODE_FOR_APP { @@ -93,8 +95,8 @@ pub fn allow_dark_mode_for_app(is_dark_mode: bool) { fn refresh_immersive_color_policy_state() { const UXTHEME_REFRESHIMMERSIVECOLORPOLICYSTATE_ORDINAL: u16 = 104; type RefreshImmersiveColorPolicyState = unsafe extern "system" fn(); - static REFRESH_IMMERSIVE_COLOR_POLICY_STATE: Lazy> = - Lazy::new(|| unsafe { + static REFRESH_IMMERSIVE_COLOR_POLICY_STATE: LazyLock> = + LazyLock::new(|| unsafe { if HMODULE(*HUXTHEME as _).is_invalid() { return None; } @@ -140,17 +142,18 @@ pub fn try_window_theme( pub fn allow_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) { const UXTHEME_ALLOWDARKMODEFORWINDOW_ORDINAL: u16 = 133; type AllowDarkModeForWindow = unsafe extern "system" fn(HWND, bool) -> bool; - static ALLOW_DARK_MODE_FOR_WINDOW: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } + static ALLOW_DARK_MODE_FOR_WINDOW: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORWINDOW_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORWINDOW_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); if *DARK_MODE_SUPPORTED { if let Some(_allow_dark_mode_for_window) = *ALLOW_DARK_MODE_FOR_WINDOW { @@ -210,17 +213,18 @@ fn should_apps_use_dark_mode() -> bool { // See https://github.com/tauri-apps/tao/pull/1165 const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: u16 = 132; type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool; - static SHOULD_APPS_USE_DARK_MODE: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } + static SHOULD_APPS_USE_DARK_MODE: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); SHOULD_APPS_USE_DARK_MODE .map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() }) diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dpi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dpi.rs similarity index 94% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dpi.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dpi.rs index e4eb427c7..25f1850bd 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dpi.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dpi.rs @@ -69,10 +69,6 @@ pub fn dpi_to_scale_factor(dpi: u32) -> f64 { } pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { - let hdc = GetDC(Some(hwnd)); - if hdc.is_invalid() { - panic!("[tao] `GetDC` returned null!"); - } if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { // We are on Windows 10 Anniversary Update (1607) or later. match GetDpiForWindow(hwnd) { @@ -96,9 +92,15 @@ pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { } else { // We are on Vista or later. if IsProcessDPIAware().as_bool() { + let hdc = GetDC(Some(hwnd)); + if hdc.is_invalid() { + panic!("[tao] `GetDC` returned null!"); + } // If the process is DPI aware, then scaling must be handled by the application using // this DPI value. - GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32 + let dpi = GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32; + ReleaseDC(Some(hwnd), hdc); + dpi } else { // If the process is DPI unaware, then scaling is performed by the OS; we thus return // 96 (scale factor 1.0) to prevent the window from being re-scaled by both the diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/drop_handler.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/drop_handler.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/drop_handler.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/drop_handler.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop.rs similarity index 82% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop.rs index d580d0d58..2d8859767 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop.rs @@ -7,7 +7,6 @@ mod runner; use crossbeam_channel::{self as channel, Receiver, Sender}; -use once_cell::sync::Lazy; use parking_lot::Mutex; use std::{ cell::Cell, @@ -16,19 +15,16 @@ use std::{ marker::PhantomData, mem, panic, rc::Rc, - sync::Arc, + sync::{Arc, LazyLock}, thread, time::{Duration, Instant}, }; use windows::{ - core::{s, BOOL, PCWSTR}, + core::{s, w, BOOL, PCWSTR}, Win32::{ - Foundation::{ - HANDLE, HINSTANCE, HWND, LPARAM, LRESULT, POINT, RECT, TRUE, WAIT_TIMEOUT, WPARAM, - }, + Foundation::{HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, TRUE, WAIT_TIMEOUT, WPARAM}, Graphics::Gdi::*, System::{ - LibraryLoader::GetModuleHandleW, Ole::{IDropTarget, RevokeDragDrop}, Threading::{GetCurrentThreadId, INFINITE}, }, @@ -36,8 +32,7 @@ use windows::{ Controls::{self as win32c, HOVER_DEFAULT}, Input::{KeyboardAndMouse::*, Pointer::*, Touch::*, *}, Shell::{ - DefSubclassProc, RemoveWindowSubclass, SHAppBarMessage, SetWindowSubclass, ABE_BOTTOM, - ABE_LEFT, ABE_RIGHT, ABE_TOP, ABM_GETAUTOHIDEBAR, APPBARDATA, + SHAppBarMessage, ABE_BOTTOM, ABE_LEFT, ABE_RIGHT, ABE_TOP, ABM_GETAUTOHIDEBAR, APPBARDATA, }, WindowsAndMessaging::{self as win32wm, *}, }, @@ -53,13 +48,13 @@ use crate::{ monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ dark_mode::try_window_theme, - dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, - keyboard::is_msg_keyboard_related, - keyboard_layout::LAYOUT_CACHE, - minimal_ime::is_msg_ime_related, + dpi::{become_dpi_aware, dpi_to_scale_factor}, + ime::ImeContext, + keyboard::KeyEventBuilder, + keyboard_layout::get_agnostic_mods, monitor::{self, MonitorHandle}, raw_input, util, - window::set_skip_taskbar, + window::{set_skip_taskbar, InitData}, window_state::{CursorFlags, WindowFlags, WindowState}, wrap_device_id, WindowId, DEVICE_ID, }, @@ -97,38 +92,39 @@ type GetPointerTouchInfo = type GetPointerPenInfo = unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL; -static GET_POINTER_FRAME_INFO_HISTORY: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); -static SKIP_POINTER_FRAME_MESSAGES: Lazy> = - Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); -static GET_POINTER_DEVICE_RECTS: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects)); -static GET_POINTER_TOUCH_INFO: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo)); -static GET_POINTER_PEN_INFO: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo)); - -pub(crate) struct SubclassInput { +static GET_POINTER_FRAME_INFO_HISTORY: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); +static SKIP_POINTER_FRAME_MESSAGES: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); +static GET_POINTER_DEVICE_RECTS: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerDeviceRects)); +static GET_POINTER_TOUCH_INFO: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerTouchInfo)); +static GET_POINTER_PEN_INFO: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerPenInfo)); + +pub(crate) struct WindowData { pub window_state: Arc>, pub event_loop_runner: EventLoopRunnerShared, + pub key_event_builder: KeyEventBuilder, pub _file_drop_handler: Option, - pub subclass_removed: Cell, + pub userdata_removed: Cell, pub recurse_depth: Cell, pub event_loop_preferred_theme: Arc>>, } -impl SubclassInput { +impl WindowData { unsafe fn send_event(&self, event: Event<'_, T>) { self.event_loop_runner.send_event(event); } } -struct ThreadMsgTargetSubclassInput { +struct ThreadMsgTargetData { event_loop_runner: EventLoopRunnerShared, user_event_receiver: Receiver, } -impl ThreadMsgTargetSubclassInput { +impl ThreadMsgTargetData { unsafe fn send_event(&self, event: Event<'_, T>) { self.event_loop_runner.send_event(event); } @@ -136,8 +132,7 @@ impl ThreadMsgTargetSubclassInput { /// The result of a subclass procedure (the message handling callback) pub(crate) enum ProcResult { - DefSubclassProc, // <- this should be the default value - DefWindowProc, + DefWindowProc, // <- this should be the default value Value(LRESULT), } @@ -190,7 +185,7 @@ impl EventLoop { become_dpi_aware(); } - let thread_msg_target = create_event_target_window(); + let thread_msg_target = create_event_target_window::(); super::dark_mode::allow_dark_mode_for_app(true); @@ -200,7 +195,8 @@ impl EventLoop { let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); - let thread_msg_sender = subclass_event_target_window(thread_msg_target, runner_shared.clone()); + let thread_msg_sender = + insert_event_target_window_data::(thread_msg_target, runner_shared.clone()); raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target, Default::default()); EventLoop { @@ -533,16 +529,20 @@ impl EventLoopThreadExecutor { let raw = Box::into_raw(boxed2); - let res = PostMessageW( + let result = PostMessageW( Some(self.target_window), *EXEC_MSG_ID, WPARAM(raw as _), LPARAM(0), ); - assert!( - res.is_ok(), - "PostMessage failed ; is the messages queue full?" - ); + // This can happen when we outlive the parent [`EventLoopWindowTarget`] at which point the `target_window` has already been destroyed + if let Err(error) = result { + log::error!( + "PostMessage failed ; is the messages queue full? Error code {} - {}", + error.code(), + error.message() + ); + } } } } @@ -590,63 +590,63 @@ type WaitUntilInstantBox = Box; /// Message sent by the `EventLoopProxy` when we want to wake up the thread. /// WPARAM and LPARAM are unused. -static USER_EVENT_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WakeupMsg")) }); +static USER_EVENT_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WakeupMsg")) }); /// Message sent when we want to execute a closure in the thread. /// WPARAM contains a Box> that must be retrieved with `Box::from_raw`, /// and LPARAM is unused. -static EXEC_MSG_ID: Lazy = Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ExecMsg")) }); -static PROCESS_NEW_EVENTS_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ProcessNewEvents")) }); +static EXEC_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ExecMsg")) }); +static PROCESS_NEW_EVENTS_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ProcessNewEvents")) }); /// lparam is the wait thread's message id. -static SEND_WAIT_THREAD_ID_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SendWaitThreadId")) }); +static SEND_WAIT_THREAD_ID_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SendWaitThreadId")) }); /// lparam points to a `Box` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should /// be sent. -static WAIT_UNTIL_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WaitUntil")) }); -static CANCEL_WAIT_UNTIL_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::CancelWaitUntil")) }); +static WAIT_UNTIL_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WaitUntil")) }); +static CANCEL_WAIT_UNTIL_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::CancelWaitUntil")) }); /// Message sent by a `Window` when it wants to be destroyed by the main thread. /// WPARAM and LPARAM are unused. -pub static DESTROY_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::DestroyMsg")) }); +pub static DESTROY_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::DestroyMsg")) }); /// WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the /// documentation in the `window_state` module for more information. -pub static SET_RETAIN_STATE_ON_SIZE_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SetRetainMaximized")) }); +pub static SET_RETAIN_STATE_ON_SIZE_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SetRetainMaximized")) }); /// Message sent by event loop when event loop's prefered theme changed. /// WPARAM and LPARAM are unused. -pub static CHANGE_THEME_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ChangeTheme")) }); +pub static CHANGE_THEME_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ChangeTheme")) }); /// When the taskbar is created, it registers a message with the "TaskbarCreated" string and then broadcasts this message to all top-level windows /// When the application receives this message, it should assume that any taskbar icons it added have been removed and add them again. -pub static S_U_TASKBAR_RESTART: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("TaskbarCreated")) }); -static THREAD_EVENT_TARGET_WINDOW_CLASS: Lazy> = Lazy::new(|| unsafe { - let class_name = util::encode_wide("Tao Thread Event Target"); - - let class = WNDCLASSEXW { - cbSize: mem::size_of::() as u32, - style: Default::default(), - lpfnWndProc: Some(util::call_default_window_proc), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: HINSTANCE(GetModuleHandleW(PCWSTR::null()).unwrap_or_default().0), - hIcon: HICON::default(), - hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly - hbrBackground: HBRUSH::default(), - lpszMenuName: PCWSTR::null(), - lpszClassName: PCWSTR::from_raw(class_name.as_ptr()), - hIconSm: HICON::default(), - }; +pub static S_U_TASKBAR_RESTART: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("TaskbarCreated")) }); - RegisterClassExW(&class); +fn create_event_target_window() -> HWND { + let thread_event_target_window_class = w!("Tao Thread Event Target"); - class_name -}); + unsafe { + let class = WNDCLASSEXW { + cbSize: mem::size_of::() as u32, + style: Default::default(), + lpfnWndProc: Some(thread_event_target_callback::), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: util::get_instance_handle(), + hIcon: HICON::default(), + hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly + hbrBackground: HBRUSH::default(), + lpszMenuName: PCWSTR::null(), + lpszClassName: thread_event_target_window_class, + hIconSm: HICON::default(), + }; + + RegisterClassExW(&class); + } -fn create_event_target_window() -> HWND { let window = unsafe { CreateWindowExW( WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | WS_EX_LAYERED | @@ -658,7 +658,7 @@ fn create_event_target_window() -> HWND { // `explorer.exe` and then starting the process back up. // It is unclear why the bug is triggered by waiting for several hours. WS_EX_TOOLWINDOW, - PCWSTR::from_raw(THREAD_EVENT_TARGET_WINDOW_CLASS.clone().as_ptr()), + thread_event_target_window_class, PCWSTR::null(), WS_OVERLAPPED, 0, @@ -667,7 +667,7 @@ fn create_event_target_window() -> HWND { 0, None, None, - GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(), + Some(util::get_instance_handle()), None, ) }; @@ -688,39 +688,21 @@ fn create_event_target_window() -> HWND { window } -fn subclass_event_target_window( - window: HWND, +fn insert_event_target_window_data( + thread_msg_target: HWND, event_loop_runner: EventLoopRunnerShared, ) -> Sender { - unsafe { - let (tx, rx) = channel::unbounded(); + let (tx, rx) = channel::unbounded(); - let subclass_input = ThreadMsgTargetSubclassInput { - event_loop_runner, - user_event_receiver: rx, - }; - let input_ptr = Box::into_raw(Box::new(subclass_input)); - let subclass_result = SetWindowSubclass( - window, - Some(thread_event_target_callback::), - THREAD_EVENT_TARGET_SUBCLASS_ID, - input_ptr as usize, - ); - assert!(subclass_result.as_bool()); + let userdata = ThreadMsgTargetData { + event_loop_runner, + user_event_receiver: rx, + }; + let input_ptr = Box::into_raw(Box::new(userdata)); - tx - } -} + util::SetWindowLongPtrW(thread_msg_target, GWL_USERDATA, input_ptr as isize); -fn remove_event_target_window_subclass(window: HWND) { - let removal_result = unsafe { - RemoveWindowSubclass( - window, - Some(thread_event_target_callback::), - THREAD_EVENT_TARGET_SUBCLASS_ID, - ) - }; - assert!(removal_result.as_bool()); + tx } /// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of @@ -741,33 +723,6 @@ unsafe fn release_mouse(mut window_state: parking_lot::MutexGuard<'_, WindowStat } } -const WINDOW_SUBCLASS_ID: usize = 0; -const THREAD_EVENT_TARGET_SUBCLASS_ID: usize = 1; -pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) { - subclass_input.event_loop_runner.register_window(window); - let input_ptr = Box::into_raw(Box::new(subclass_input)); - let subclass_result = unsafe { - SetWindowSubclass( - window, - Some(public_window_callback::), - WINDOW_SUBCLASS_ID, - input_ptr as usize, - ) - }; - assert!(subclass_result.as_bool()); -} - -fn remove_window_subclass(window: HWND) { - let removal_result = unsafe { - RemoveWindowSubclass( - window, - Some(public_window_callback::), - WINDOW_SUBCLASS_ID, - ) - }; - assert!(removal_result.as_bool()); -} - fn normalize_pointer_pressure(pressure: u32) -> Option { match pressure { 1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)), @@ -846,11 +801,11 @@ unsafe fn process_control_flow(runner: &EventLoopRunner) { /// Emit a `ModifiersChanged` event whenever modifiers have changed. /// Returns the current modifier state -fn update_modifiers(window: HWND, subclass_input: &SubclassInput) -> ModifiersState { +fn update_modifiers(window: HWND, useredata: &WindowData) -> ModifiersState { use crate::event::WindowEvent::ModifiersChanged; - let modifiers = LAYOUT_CACHE.lock().get_agnostic_mods(); - let mut window_state = subclass_input.window_state.lock(); + let modifiers = get_agnostic_mods(); + let mut window_state = useredata.window_state.lock(); if window_state.modifiers_state != modifiers { window_state.modifiers_state = modifiers; @@ -858,7 +813,7 @@ fn update_modifiers(window: HWND, subclass_input: &SubclassInput) -> Modif drop(window_state); unsafe { - subclass_input.send_event(Event::WindowEvent { + useredata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: ModifiersChanged(modifiers), }); @@ -867,26 +822,26 @@ fn update_modifiers(window: HWND, subclass_input: &SubclassInput) -> Modif modifiers } -unsafe fn gain_active_focus(window: HWND, subclass_input: &SubclassInput) { +unsafe fn gain_active_focus(window: HWND, userdata: &WindowData) { use crate::event::WindowEvent::Focused; - update_modifiers(window, subclass_input); + update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: Focused(true), }); } -unsafe fn lose_active_focus(window: HWND, subclass_input: &SubclassInput) { +unsafe fn lose_active_focus(window: HWND, userdata: &WindowData) { use crate::event::WindowEvent::{Focused, ModifiersChanged}; - subclass_input.window_state.lock().modifiers_state = ModifiersState::empty(); - subclass_input.send_event(Event::WindowEvent { + userdata.window_state.lock().modifiers_state = ModifiersState::empty(); + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: ModifiersChanged(ModifiersState::empty()), }); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: Focused(false), }); @@ -899,36 +854,58 @@ unsafe fn lose_active_focus(window: HWND, subclass_input: &SubclassInput) // // Returning 0 tells the Win32 API that the message has been processed. // FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary -unsafe extern "system" fn public_window_callback( +pub(super) unsafe extern "system" fn public_window_callback( window: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM, - uidsubclass: usize, - subclass_input_ptr: usize, ) -> LRESULT { - let subclass_input_ptr = subclass_input_ptr as *mut SubclassInput; - let (result, subclass_removed, recurse_depth) = { - let subclass_input = &*subclass_input_ptr; - subclass_input - .recurse_depth - .set(subclass_input.recurse_depth.get() + 1); + let userdata = util::GetWindowLongPtrW(window, GWL_USERDATA); + let userdata_ptr = match (userdata, msg) { + (0, WM_NCCREATE) => { + let createstruct = &mut *(lparam.0 as *mut CREATESTRUCTW); + let initdata = createstruct.lpCreateParams; + let initdata = &mut *(initdata as *mut InitData<'_, T>); + + return match unsafe { initdata.on_nccreate(window) } { + Some(userdata) => unsafe { + util::SetWindowLongPtrW(window, GWL_USERDATA, userdata); + DefWindowProcW(window, msg, wparam, lparam) + }, + None => LRESULT(-1), // failed to create the window + }; + } + // Getting here should quite frankly be impossible, + // but we'll make window creation fail here just in case. + (0, WM_CREATE) => return LRESULT(-1), + (_, WM_CREATE) => unsafe { + let createstruct = &mut *(lparam.0 as *mut CREATESTRUCTW); + let initdata = createstruct.lpCreateParams; + let initdata = &mut *(initdata as *mut InitData<'_, T>); - // Clear userdata - util::SetWindowLongPtrW(window, GWL_USERDATA, 0); + initdata.on_create(); + return DefWindowProcW(window, msg, wparam, lparam); + }, + (0, _) => return DefWindowProcW(window, msg, wparam, lparam), + _ => userdata as *mut WindowData, + }; + + let (result, userdata_removed, recurse_depth) = { + let userdata = &*(userdata_ptr); + + userdata.recurse_depth.set(userdata.recurse_depth.get() + 1); - let result = - public_window_callback_inner(window, msg, wparam, lparam, uidsubclass, subclass_input); + let result = public_window_callback_inner(window, msg, wparam, lparam, userdata); - let subclass_removed = subclass_input.subclass_removed.get(); - let recurse_depth = subclass_input.recurse_depth.get() - 1; - subclass_input.recurse_depth.set(recurse_depth); + let userdata_removed = userdata.userdata_removed.get(); + let recurse_depth = userdata.recurse_depth.get() - 1; + userdata.recurse_depth.set(recurse_depth); - (result, subclass_removed, recurse_depth) + (result, userdata_removed, recurse_depth) }; - if subclass_removed && recurse_depth == 0 { - drop(Box::from_raw(subclass_input_ptr)) + if userdata_removed && recurse_depth == 0 { + drop(Box::from_raw(userdata_ptr)) } result @@ -939,50 +916,38 @@ unsafe fn public_window_callback_inner( msg: u32, wparam: WPARAM, lparam: LPARAM, - _: usize, - subclass_input: &SubclassInput, + userdata: &WindowData, ) -> LRESULT { let _ = RedrawWindow( - Some(subclass_input.event_loop_runner.thread_msg_target()), + Some(userdata.event_loop_runner.thread_msg_target()), None, None, RDW_INTERNALPAINT, ); - let mut result = ProcResult::DefSubclassProc; + let mut result = ProcResult::DefWindowProc; // Send new modifiers before sending key events. let mods_changed_callback = || match msg { win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN | win32wm::WM_KEYUP | win32wm::WM_SYSKEYUP => { - update_modifiers(window, subclass_input); + update_modifiers(window, userdata); result = ProcResult::Value(LRESULT(0)); } _ => (), }; - subclass_input + userdata .event_loop_runner .catch_unwind(mods_changed_callback) .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); let keyboard_callback = || { use crate::event::WindowEvent::KeyboardInput; - let is_keyboard_related = is_msg_keyboard_related(msg); - if !is_keyboard_related { - // We return early to avoid a deadlock from locking the window state - // when not appropriate. - return; - } - let events = { - let mut key_event_builders = - crate::platform_impl::platform::keyboard::KEY_EVENT_BUILDERS.lock(); - if let Some(key_event_builder) = key_event_builders.get_mut(&WindowId(window.0 as _)) { - key_event_builder.process_message(window, msg, wparam, lparam, &mut result) - } else { - Vec::new() - } - }; + let events = + userdata + .key_event_builder + .process_message(window, msg, wparam, lparam, &mut result); for event in events { - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: KeyboardInput { device_id: DEVICE_ID, @@ -992,41 +957,17 @@ unsafe fn public_window_callback_inner( }); } }; - subclass_input + userdata .event_loop_runner .catch_unwind(keyboard_callback) .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); - let ime_callback = || { - use crate::event::WindowEvent::ReceivedImeText; - let is_ime_related = is_msg_ime_related(msg); - if !is_ime_related { - return; - } - let text = { - let mut window_state = subclass_input.window_state.lock(); - window_state - .ime_handler - .process_message(window, msg, wparam, lparam, &mut result) - }; - if let Some(str) = text { - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: ReceivedImeText(str), - }); - } - }; - subclass_input - .event_loop_runner - .catch_unwind(ime_callback) - .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); - // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing // the closure to catch_unwind directly so that the match body indendation wouldn't change and // the git blame and history would be preserved. let callback = || match msg { win32wm::WM_ENTERSIZEMOVE => { - subclass_input + userdata .window_state .lock() .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); @@ -1034,7 +975,7 @@ unsafe fn public_window_callback_inner( } win32wm::WM_EXITSIZEMOVE => { - let mut state = subclass_input.window_state.lock(); + let mut state = userdata.window_state.lock(); if state.dragging { state.dragging = false; let _ = unsafe { PostMessageW(Some(window), WM_LBUTTONUP, WPARAM::default(), lparam) }; @@ -1043,16 +984,13 @@ unsafe fn public_window_callback_inner( result = ProcResult::Value(LRESULT(0)); } - win32wm::WM_NCCREATE => { - enable_non_client_dpi_scaling(window); - } win32wm::WM_NCLBUTTONDOWN => { if wparam.0 == HTCAPTION as _ { let _ = PostMessageW(Some(window), WM_MOUSEMOVE, WPARAM(0), lparam); } use crate::event::WindowEvent::DecorationsClick; - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: DecorationsClick, }); @@ -1060,7 +998,7 @@ unsafe fn public_window_callback_inner( win32wm::WM_CLOSE => { use crate::event::WindowEvent::CloseRequested; - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: CloseRequested, }); @@ -1070,39 +1008,39 @@ unsafe fn public_window_callback_inner( win32wm::WM_DESTROY => { use crate::event::WindowEvent::Destroyed; let _ = RevokeDragDrop(window); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: Destroyed, }); - subclass_input.event_loop_runner.remove_window(window); + userdata.event_loop_runner.remove_window(window); result = ProcResult::Value(LRESULT(0)); } win32wm::WM_NCDESTROY => { - remove_window_subclass::(window); - subclass_input.subclass_removed.set(true); + util::SetWindowLongPtrW(window, GWL_USERDATA, 0); + userdata.userdata_removed.set(true); result = ProcResult::Value(LRESULT(0)); } win32wm::WM_PAINT => { - if subclass_input.event_loop_runner.should_buffer() { + if userdata.event_loop_runner.should_buffer() { // this branch can happen in response to `UpdateWindow`, if win32 decides to // redraw the window outside the normal flow of the event loop. let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); } else { - let managing_redraw = flush_paint_messages(Some(window), &subclass_input.event_loop_runner); - subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId( + let managing_redraw = flush_paint_messages(Some(window), &userdata.event_loop_runner); + userdata.send_event(Event::RedrawRequested(RootWindowId(WindowId( window.0 as _, )))); if managing_redraw { - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); + userdata.event_loop_runner.redraw_events_cleared(); + process_control_flow(&userdata.event_loop_runner); } } } win32wm::WM_ERASEBKGND => { - let w = subclass_input.window_state.lock(); + let w = userdata.window_state.lock(); if let Some(color) = w.background_color { let hdc = HDC(wparam.0 as *mut _); let mut rc = RECT::default(); @@ -1113,15 +1051,15 @@ unsafe fn public_window_callback_inner( result = ProcResult::Value(LRESULT(1)); } else { - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } } else { - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } } win32wm::WM_WINDOWPOSCHANGING => { - let mut window_state = subclass_input.window_state.lock(); + let mut window_state = userdata.window_state.lock(); if let Some(ref mut fullscreen) = window_state.fullscreen { let window_pos = &mut *(lparam.0 as *mut WINDOWPOS); @@ -1214,14 +1152,14 @@ unsafe fn public_window_callback_inner( let windowpos = lparam.0 as *const WINDOWPOS; if (*windowpos).flags & SWP_NOMOVE != SWP_NOMOVE { let physical_position = PhysicalPosition::new((*windowpos).x, (*windowpos).y); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: Moved(physical_position), }); } // This is necessary for us to still get sent WM_SIZE. - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } win32wm::WM_SIZE => { @@ -1236,7 +1174,7 @@ unsafe fn public_window_callback_inner( }; { - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); // See WindowFlags::MARKER_RETAIN_STATE_ON_SIZE docs for info on why this `if` check exists. if !w .window_flags() @@ -1247,24 +1185,36 @@ unsafe fn public_window_callback_inner( } } - subclass_input.send_event(event); + userdata.send_event(event); result = ProcResult::Value(LRESULT(0)); } + // TODO: Catch up with winit on the IME events, + // `WindowEvent::ReceivedImeText` currently maps to `WindowEvent::Ime(Ime::Commit(text))` + win32wm::WM_IME_ENDCOMPOSITION => { + let ime_context = unsafe { ImeContext::current(window) }; + if let Some(text) = unsafe { ime_context.get_composed_text() } { + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: WindowEvent::ReceivedImeText(text), + }); + } + } + // this is necessary for us to maintain minimize/restore state win32wm::WM_SYSCOMMAND => { if wparam.0 == SC_RESTORE as _ { - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, false)); } if wparam.0 == SC_MINIMIZE as _ { - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, true)); } // Send `WindowEvent::Minimized` here if we decide to implement one if wparam.0 == SC_SCREENSAVE as _ { - let window_state = subclass_input.window_state.lock(); + let window_state = userdata.window_state.lock(); if window_state.fullscreen.is_some() { result = ProcResult::Value(LRESULT(0)); return; @@ -1277,7 +1227,7 @@ unsafe fn public_window_callback_inner( win32wm::WM_MOUSEMOVE => { use crate::event::WindowEvent::{CursorEntered, CursorMoved}; let mouse_was_outside_window = { - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); let was_outside_window = !w.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW); w.mouse @@ -1287,7 +1237,7 @@ unsafe fn public_window_callback_inner( }; if mouse_was_outside_window { - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: CursorEntered { device_id: DEVICE_ID, @@ -1311,13 +1261,13 @@ unsafe fn public_window_callback_inner( // handle spurious WM_MOUSEMOVE messages // see https://devblogs.microsoft.com/oldnewthing/20031001-00/?p=42343 // and http://debugandconquer.blogspot.com/2015/08/the-cause-of-spurious-mouse-move.html - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); cursor_moved = w.mouse.last_position != Some(position); w.mouse.last_position = Some(position); } if cursor_moved { - let modifiers = update_modifiers(window, subclass_input); - subclass_input.send_event(Event::WindowEvent { + let modifiers = update_modifiers(window, userdata); + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: CursorMoved { device_id: DEVICE_ID, @@ -1333,13 +1283,13 @@ unsafe fn public_window_callback_inner( win32c::WM_MOUSELEAVE => { use crate::event::WindowEvent::CursorLeft; { - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); w.mouse .set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false)) .ok(); } - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: CursorLeft { device_id: DEVICE_ID, @@ -1355,7 +1305,7 @@ unsafe fn public_window_callback_inner( let value = f32::from(util::GET_WHEEL_DELTA_WPARAM(wparam)); let value = value / WHEEL_DELTA as f32; - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); let mut scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA; @@ -1371,7 +1321,7 @@ unsafe fn public_window_callback_inner( scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA; } - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: WindowEvent::MouseWheel { device_id: DEVICE_ID, @@ -1390,7 +1340,7 @@ unsafe fn public_window_callback_inner( let value = f32::from(util::GET_WHEEL_DELTA_WPARAM(wparam)); let value = value / WHEEL_DELTA as f32; - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); let mut scroll_characters = DEFAULT_SCROLL_CHARACTERS_PER_WHEEL_DELTA; @@ -1401,7 +1351,7 @@ unsafe fn public_window_callback_inner( SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0), ); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: WindowEvent::MouseWheel { device_id: DEVICE_ID, @@ -1416,18 +1366,18 @@ unsafe fn public_window_callback_inner( win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN => { if msg == WM_SYSKEYDOWN && wparam.0 == usize::from(VK_F4.0) { - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } } win32wm::WM_LBUTTONDOWN => { use crate::event::{ElementState::Pressed, MouseButton::Left, WindowEvent::MouseInput}; - capture_mouse(window, &mut subclass_input.window_state.lock()); + capture_mouse(window, &mut userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1442,11 +1392,11 @@ unsafe fn public_window_callback_inner( win32wm::WM_LBUTTONUP => { use crate::event::{ElementState::Released, MouseButton::Left, WindowEvent::MouseInput}; - release_mouse(subclass_input.window_state.lock()); + release_mouse(userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1461,11 +1411,11 @@ unsafe fn public_window_callback_inner( win32wm::WM_RBUTTONDOWN => { use crate::event::{ElementState::Pressed, MouseButton::Right, WindowEvent::MouseInput}; - capture_mouse(window, &mut subclass_input.window_state.lock()); + capture_mouse(window, &mut userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1480,11 +1430,11 @@ unsafe fn public_window_callback_inner( win32wm::WM_RBUTTONUP => { use crate::event::{ElementState::Released, MouseButton::Right, WindowEvent::MouseInput}; - release_mouse(subclass_input.window_state.lock()); + release_mouse(userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1499,11 +1449,11 @@ unsafe fn public_window_callback_inner( win32wm::WM_MBUTTONDOWN => { use crate::event::{ElementState::Pressed, MouseButton::Middle, WindowEvent::MouseInput}; - capture_mouse(window, &mut subclass_input.window_state.lock()); + capture_mouse(window, &mut userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1518,11 +1468,11 @@ unsafe fn public_window_callback_inner( win32wm::WM_MBUTTONUP => { use crate::event::{ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput}; - release_mouse(subclass_input.window_state.lock()); + release_mouse(userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1538,11 +1488,11 @@ unsafe fn public_window_callback_inner( use crate::event::{ElementState::Pressed, MouseButton::Other, WindowEvent::MouseInput}; let xbutton = util::GET_XBUTTON_WPARAM(wparam); - capture_mouse(window, &mut subclass_input.window_state.lock()); + capture_mouse(window, &mut userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1558,11 +1508,11 @@ unsafe fn public_window_callback_inner( use crate::event::{ElementState::Released, MouseButton::Other, WindowEvent::MouseInput}; let xbutton = util::GET_XBUTTON_WPARAM(wparam); - release_mouse(subclass_input.window_state.lock()); + release_mouse(userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1580,7 +1530,7 @@ unsafe fn public_window_callback_inner( // can happen if `SetCapture` is called on our window when it already has the mouse // capture. if lparam.0 != window.0 as _ { - subclass_input.window_state.lock().mouse.capture_count = 0; + userdata.window_state.lock().mouse.capture_count = 0; } result = ProcResult::Value(LRESULT(0)); } @@ -1614,7 +1564,7 @@ unsafe fn public_window_callback_inner( let x = location.x as f64 + (input.x % 100) as f64 / 100f64; let y = location.y as f64 + (input.y % 100) as f64 / 100f64; let location = PhysicalPosition::new(x, y); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: WindowEvent::Touch(Touch { phase: if (input.dwFlags & TOUCHEVENTF_DOWN) != Default::default() { @@ -1750,7 +1700,7 @@ unsafe fn public_window_callback_inner( let x = location.x as f64 + x.fract(); let y = location.y as f64 + y.fract(); let location = PhysicalPosition::new(x, y); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: WindowEvent::Touch(Touch { phase: if (pointer_info.pointerFlags & POINTER_FLAG_DOWN) != Default::default() { @@ -1778,36 +1728,36 @@ unsafe fn public_window_callback_inner( win32wm::WM_NCACTIVATE => { let is_active = wparam != WPARAM(0); - let active_focus_changed = subclass_input.window_state.lock().set_active(is_active); + let active_focus_changed = userdata.window_state.lock().set_active(is_active); if active_focus_changed { if is_active { - gain_active_focus(window, subclass_input); + gain_active_focus(window, userdata); } else { - lose_active_focus(window, subclass_input); + lose_active_focus(window, userdata); } } result = ProcResult::DefWindowProc; } win32wm::WM_SETFOCUS => { - let active_focus_changed = subclass_input.window_state.lock().set_focused(true); + let active_focus_changed = userdata.window_state.lock().set_focused(true); if active_focus_changed { - gain_active_focus(window, subclass_input); + gain_active_focus(window, userdata); } result = ProcResult::Value(LRESULT(0)); } win32wm::WM_KILLFOCUS => { - let active_focus_changed = subclass_input.window_state.lock().set_focused(false); + let active_focus_changed = userdata.window_state.lock().set_focused(false); if active_focus_changed { - lose_active_focus(window, subclass_input); + lose_active_focus(window, userdata); } result = ProcResult::Value(LRESULT(0)); } win32wm::WM_SETCURSOR => { let set_cursor_to = { - let window_state = subclass_input.window_state.lock(); + let window_state = userdata.window_state.lock(); // The return value for the preceding `WM_NCHITTEST` message is conveniently // provided through the low-order word of lParam. We use that here since // `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement. @@ -1833,7 +1783,7 @@ unsafe fn public_window_callback_inner( win32wm::WM_GETMINMAXINFO => { let mmi = lparam.0 as *mut MINMAXINFO; - let window_state = subclass_input.window_state.lock(); + let window_state = userdata.window_state.lock(); let is_decorated = window_state .window_flags() .contains(WindowFlags::MARKER_DECORATIONS); @@ -1896,7 +1846,7 @@ unsafe fn public_window_callback_inner( let old_scale_factor: f64; let (allow_resize, is_decorated) = { - let mut window_state = subclass_input.window_state.lock(); + let mut window_state = userdata.window_state.lock(); old_scale_factor = window_state.scale_factor; window_state.scale_factor = new_scale_factor; @@ -1977,7 +1927,7 @@ unsafe fn public_window_callback_inner( new_physical_inner_size = old_physical_inner_size; } - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: ScaleFactorChanged { scale_factor: new_scale_factor, @@ -1988,7 +1938,7 @@ unsafe fn public_window_callback_inner( let dragging_window: bool; { - let window_state = subclass_input.window_state.lock(); + let window_state = userdata.window_state.lock(); dragging_window = window_state .window_flags() .contains(WindowFlags::MARKER_IN_SIZE_MOVE); @@ -2118,15 +2068,15 @@ unsafe fn public_window_callback_inner( } win32wm::WM_SETTINGCHANGE => { - update_theme(subclass_input, window, true); + update_theme(userdata, window, true); } win32wm::WM_NCCALCSIZE => { - let window_flags = subclass_input.window_state.lock().window_flags(); - let is_fullscreen = subclass_input.window_state.lock().fullscreen.is_some(); + let window_flags = userdata.window_state.lock().window_flags(); + let is_fullscreen = userdata.window_state.lock().fullscreen.is_some(); if wparam == WPARAM(0) || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } else { // adjust the maximized borderless window so it doesn't cover the taskbar if util::is_maximized(window).unwrap_or(false) { @@ -2175,8 +2125,8 @@ unsafe fn public_window_callback_inner( } } - win32wm::WM_NCHITTEST => { - let window_state = subclass_input.window_state.lock(); + WM_NCHITTEST => { + let window_state = userdata.window_state.lock(); let window_flags = window_state.window_flags(); // Allow resizing unmaximized non-fullscreen undecorated window @@ -2222,10 +2172,10 @@ unsafe fn public_window_callback_inner( result = hit_result .map(|r| ProcResult::Value(LRESULT(r as _))) - .unwrap_or(ProcResult::DefSubclassProc); + .unwrap_or(ProcResult::DefWindowProc); } } else { - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } } @@ -2239,42 +2189,39 @@ unsafe fn public_window_callback_inner( let _ = DestroyWindow(window); result = ProcResult::Value(LRESULT(0)); } else if msg == *SET_RETAIN_STATE_ON_SIZE_MSG_ID { - let mut window_state = subclass_input.window_state.lock(); + let mut window_state = userdata.window_state.lock(); window_state.set_window_flags_in_place(|f| { f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam.0 != 0) }); result = ProcResult::Value(LRESULT(0)); } else if msg == *CHANGE_THEME_MSG_ID { - update_theme(subclass_input, window, false); + update_theme(userdata, window, false); result = ProcResult::Value(LRESULT(0)); } else if msg == *S_U_TASKBAR_RESTART { - let window_state = subclass_input.window_state.lock(); - let _ = set_skip_taskbar(window, window_state.skip_taskbar); + let skip_taskbar = userdata.window_state.lock().skip_taskbar; + if skip_taskbar { + let _ = set_skip_taskbar(window, true); + } } } }; - subclass_input + userdata .event_loop_runner .catch_unwind(callback) .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); match result { - ProcResult::DefSubclassProc => DefSubclassProc(window, msg, wparam, lparam), ProcResult::DefWindowProc => DefWindowProcW(window, msg, wparam, lparam), ProcResult::Value(val) => val, } } -fn update_theme( - subclass_input: &SubclassInput, - window: HWND, - from_settings_change_event: bool, -) { - let mut window_state = subclass_input.window_state.lock(); +fn update_theme(userdata: &WindowData, window: HWND, from_settings_change_event: bool) { + let mut window_state = userdata.window_state.lock(); let preferred_theme = window_state .preferred_theme - .or(*subclass_input.event_loop_preferred_theme.lock()); + .or(*userdata.event_loop_preferred_theme.lock()); if from_settings_change_event && preferred_theme.is_some() { return; } @@ -2283,7 +2230,7 @@ fn update_theme( window_state.current_theme = new_theme; mem::drop(window_state); unsafe { - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: WindowEvent::ThemeChanged(new_theme), }) @@ -2309,20 +2256,24 @@ unsafe extern "system" fn thread_event_target_callback( msg: u32, wparam: WPARAM, lparam: LPARAM, - _: usize, - subclass_input_ptr: usize, ) -> LRESULT { - let subclass_input = Box::from_raw(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput); + let userdata_ptr = util::GetWindowLongPtrW(window, GWL_USERDATA) as *mut ThreadMsgTargetData; + if userdata_ptr.is_null() { + // `userdata_ptr` will always be null for the first `WM_GETMINMAXINFO`, as well as + // `WM_NCCREATE` and `WM_CREATE`. + return DefWindowProcW(window, msg, wparam, lparam); + } + let userdata = Box::from_raw(userdata_ptr); - let mut subclass_removed = false; + let mut userdata_removed = false; // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing // the closure to catch_unwind directly so that the match body indendation wouldn't change and // the git blame and history would be preserved. let callback = || match msg { win32wm::WM_NCDESTROY => { - remove_event_target_window_subclass::(window); - subclass_removed = true; + util::SetWindowLongPtrW(window, GWL_USERDATA, 0); + userdata_removed = true; let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); LRESULT(0) } @@ -2333,25 +2284,22 @@ unsafe extern "system" fn thread_event_target_callback( // If the WM_PAINT handler in `public_window_callback` has already flushed the redraw // events, `handling_events` will return false and we won't emit a second // `RedrawEventsCleared` event. - if subclass_input.event_loop_runner.handling_events() { - if subclass_input.event_loop_runner.should_buffer() { + if userdata.event_loop_runner.handling_events() { + if userdata.event_loop_runner.should_buffer() { // This branch can be triggered when a nested win32 event loop is triggered // inside of the `event_handler` callback. let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); } else { // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` // doesn't call WM_PAINT for the thread event target (i.e. this window). - assert!(flush_paint_messages( - None, - &subclass_input.event_loop_runner - )); - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); + assert!(flush_paint_messages(None, &userdata.event_loop_runner)); + userdata.event_loop_runner.redraw_events_cleared(); + process_control_flow(&userdata.event_loop_runner); } } // Default WM_PAINT behaviour. This makes sure modals and popups are shown immediatly when opening them. - DefSubclassProc(window, msg, wparam, lparam) + DefWindowProcW(window, msg, wparam, lparam) } win32wm::WM_INPUT_DEVICE_CHANGE => { @@ -2361,7 +2309,7 @@ unsafe extern "system" fn thread_event_target_callback( _ => unreachable!(), }; - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id: wrap_device_id(lparam.0), event, }); @@ -2372,11 +2320,11 @@ unsafe extern "system" fn thread_event_target_callback( win32wm::WM_INPUT => { if let Some(data) = raw_input::get_raw_input_data(HRAWINPUT(lparam.0 as _)) { - handle_raw_input(&subclass_input, data); + handle_raw_input(&userdata, data); let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); } - DefSubclassProc(window, msg, wparam, lparam) + DefWindowProcW(window, msg, wparam, lparam) } // We don't process `WM_QUERYENDSESSION` yet until we introduce the same mechanism as Tauri's `ExitRequested` event @@ -2385,15 +2333,21 @@ unsafe extern "system" fn thread_event_target_callback( // `wParam` is `FALSE` is for if the shutdown gets canceled, // and we don't need to handle that case since we didn't do anything prior in response to `WM_QUERYENDSESSION` if wparam.0 == TRUE.0 as usize { - subclass_input.event_loop_runner.loop_destroyed(); + userdata.event_loop_runner.loop_destroyed(); + // `WM_ENDSESSION` can be sent by Windows during shutdown or by Restart Manager + // + // - From Windows shutdown: Windows will shut us down after we return `0` here + // - From Restart Manager: Restart Manager only sends the message but will wait for us to shutdown ourselves + // + // So, we call `exit` here directly to align this behavior + std::process::exit(0); } - // Note: after we return 0 here, Windows will shut us down LRESULT(0) } _ if msg == *USER_EVENT_MSG_ID => { - if let Ok(event) = subclass_input.user_event_receiver.recv() { - subclass_input.send_event(Event::UserEvent(event)); + if let Ok(event) = userdata.user_event_receiver.recv() { + userdata.send_event(Event::UserEvent(event)); } let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); LRESULT(0) @@ -2406,7 +2360,7 @@ unsafe extern "system" fn thread_event_target_callback( } _ if msg == *PROCESS_NEW_EVENTS_MSG_ID => { let _ = PostThreadMessageW( - subclass_input.event_loop_runner.wait_thread_id(), + userdata.event_loop_runner.wait_thread_id(), *CANCEL_WAIT_UNTIL_MSG_ID, WPARAM(0), LPARAM(0), @@ -2414,7 +2368,7 @@ unsafe extern "system" fn thread_event_target_callback( // if the control_flow is WaitUntil, make sure the given moment has actually passed // before emitting NewEvents - if let ControlFlow::WaitUntil(wait_until) = subclass_input.event_loop_runner.control_flow() { + if let ControlFlow::WaitUntil(wait_until) = userdata.event_loop_runner.control_flow() { let mut msg = MSG::default(); while Instant::now() < wait_until { if PeekMessageW(&mut msg, None, 0, 0, PM_NOREMOVE).as_bool() { @@ -2435,31 +2389,26 @@ unsafe extern "system" fn thread_event_target_callback( } } } - subclass_input.event_loop_runner.poll(); + userdata.event_loop_runner.poll(); let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); LRESULT(0) } - _ => DefSubclassProc(window, msg, wparam, lparam), + _ => DefWindowProcW(window, msg, wparam, lparam), }; - let result = subclass_input + let result = userdata .event_loop_runner .catch_unwind(callback) .unwrap_or(LRESULT(-1)); - if subclass_removed { - mem::drop(subclass_input); + if userdata_removed { + drop(userdata); } else { - // FIXME: this seems to leak intentionally? - #[allow(unused_must_use)] - Box::into_raw(subclass_input); + Box::leak(userdata); } result } -unsafe fn handle_raw_input( - subclass_input: &ThreadMsgTargetSubclassInput, - data: RAWINPUT, -) { +unsafe fn handle_raw_input(userdata: &ThreadMsgTargetData, data: RAWINPUT) { use crate::event::{ DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, ElementState::{Pressed, Released}, @@ -2476,21 +2425,21 @@ unsafe fn handle_raw_input( let y = mouse.lLastY as f64; if x != 0.0 { - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: Motion { axis: 0, value: x }, }); } if y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: Motion { axis: 1, value: y }, }); } if x != 0.0 || y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: MouseMotion { delta: (x, y) }, }); @@ -2503,7 +2452,7 @@ unsafe fn handle_raw_input( ) { // We must cast to SHORT first, becaues `usButtonData` must be interpreted as signed. let delta = mouse.Anonymous.Anonymous.usButtonData as i16 as f32 / WHEEL_DELTA as f32; - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: MouseWheel { delta: LineDelta(0.0, delta), @@ -2520,7 +2469,7 @@ unsafe fn handle_raw_input( // seem to be anything else reasonable to do for a mouse // button ID. let button = (index + 1) as _; - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: Button { button, state }, }); @@ -2626,7 +2575,7 @@ unsafe fn handle_raw_input( _ => (), } } - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: Key(RawKeyEvent { physical_key: code, diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop/runner.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop/runner.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop/runner.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop/runner.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/icon.rs similarity index 97% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/icon.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/icon.rs index b0d0e1bff..871071020 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/icon.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/icon.rs @@ -8,7 +8,6 @@ use windows::{ core::PCWSTR, Win32::{ Foundation::{HWND, LPARAM, WPARAM}, - System::LibraryLoader::*, UI::WindowsAndMessaging::*, }, }; @@ -97,7 +96,7 @@ impl WinIcon { let (width, height) = size.map(Into::into).unwrap_or((0, 0)); let handle = unsafe { LoadImageW( - GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(), + Some(util::get_instance_handle()), PCWSTR::from_raw(resource_id as usize as *const u16), IMAGE_ICON, width, diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/ime.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/ime.rs new file mode 100644 index 000000000..51156aba9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/ime.rs @@ -0,0 +1,163 @@ +use std::{ + ffi::{c_void, OsString}, + os::windows::prelude::OsStringExt, +}; + +use windows::Win32::{ + Foundation::HWND, + UI::Input::Ime::{ + ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext, GCS_RESULTSTR, HIMC, + IME_COMPOSITION_STRING, + }, +}; + +pub struct ImeContext { + hwnd: HWND, + himc: HIMC, +} + +impl ImeContext { + pub unsafe fn current(hwnd: HWND) -> Self { + let himc = unsafe { ImmGetContext(hwnd) }; + ImeContext { hwnd, himc } + } + + // pub unsafe fn get_composing_text_and_cursor( + // &self, + // ) -> Option<(String, Option, Option)> { + // let text = unsafe { self.get_composition_string(GCS_COMPSTR) }?; + // let attrs = unsafe { self.get_composition_data(GCS_COMPATTR) }.unwrap_or_default(); + + // let mut first = None; + // let mut last = None; + // let mut boundary_before_char = 0; + // let mut attr_idx = 0; + + // for chr in text.chars() { + // let Some(attr) = attrs.get(attr_idx).copied() else { + // break; + // }; + + // let char_is_targeted = + // attr as u32 == ATTR_TARGET_CONVERTED || attr as u32 == ATTR_TARGET_NOTCONVERTED; + + // if first.is_none() && char_is_targeted { + // first = Some(boundary_before_char); + // } else if first.is_some() && last.is_none() && !char_is_targeted { + // last = Some(boundary_before_char); + // } + + // boundary_before_char += chr.len_utf8(); + // attr_idx += chr.len_utf16(); + // } + + // if first.is_some() && last.is_none() { + // last = Some(text.len()); + // } else if first.is_none() { + // // IME haven't split words and select any clause yet, so trying to retrieve normal + // // cursor. + // let cursor = unsafe { self.get_composition_cursor(&text) }; + // first = cursor; + // last = cursor; + // } + + // Some((text, first, last)) + // } + + pub unsafe fn get_composed_text(&self) -> Option { + unsafe { self.get_composition_string(GCS_RESULTSTR) } + } + + // unsafe fn get_composition_cursor(&self, text: &str) -> Option { + // let cursor = unsafe { ImmGetCompositionStringW(self.himc, GCS_CURSORPOS, None, 0) }; + // (cursor >= 0).then(|| text.chars().take(cursor as _).map(|c| c.len_utf8()).sum()) + // } + + unsafe fn get_composition_string(&self, gcs_mode: IME_COMPOSITION_STRING) -> Option { + let data = unsafe { self.get_composition_data(gcs_mode) }?; + let (prefix, shorts, suffix) = unsafe { data.align_to::() }; + if prefix.is_empty() && suffix.is_empty() { + OsString::from_wide(shorts).into_string().ok() + } else { + None + } + } + + unsafe fn get_composition_data(&self, gcs_mode: IME_COMPOSITION_STRING) -> Option> { + let size = match unsafe { ImmGetCompositionStringW(self.himc, gcs_mode, None, 0) } { + 0 => return Some(Vec::new()), + size if size < 0 => return None, + size => size, + }; + + let mut buf = Vec::::with_capacity(size as _); + let size = unsafe { + ImmGetCompositionStringW( + self.himc, + gcs_mode, + Some(buf.as_mut_ptr() as *mut c_void), + size as _, + ) + }; + + if size < 0 { + None + } else { + unsafe { buf.set_len(size as _) }; + Some(buf) + } + } + + // pub unsafe fn set_ime_cursor_area(&self, spot: Position, size: Size, scale_factor: f64) { + // if !unsafe { ImeContext::system_has_ime() } { + // return; + // } + + // let (x, y) = spot.to_physical::(scale_factor).into(); + // let (width, height): (i32, i32) = size.to_physical::(scale_factor).into(); + // let rc_area = RECT { + // left: x, + // top: y, + // right: x + width, + // bottom: y + height, + // }; + // let candidate_form = CANDIDATEFORM { + // dwIndex: 0, + // dwStyle: CFS_EXCLUDE, + // ptCurrentPos: POINT { x, y }, + // rcArea: rc_area, + // }; + // let composition_form = COMPOSITIONFORM { + // dwStyle: CFS_POINT, + // ptCurrentPos: POINT { x, y: y + height }, + // rcArea: rc_area, + // }; + + // unsafe { + // ImmSetCompositionWindow(self.himc, &composition_form); + // ImmSetCandidateWindow(self.himc, &candidate_form); + // } + // } + + // pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) { + // if !unsafe { ImeContext::system_has_ime() } { + // return; + // } + + // if allowed { + // unsafe { ImmAssociateContextEx(hwnd, HIMC::default(), IACE_DEFAULT) }; + // } else { + // unsafe { ImmAssociateContextEx(hwnd, HIMC::default(), IACE_CHILDREN) }; + // } + // } + + // pub unsafe fn system_has_ime() -> bool { + // unsafe { GetSystemMetrics(SM_IMMENABLED) != 0 } + // } +} + +impl Drop for ImeContext { + fn drop(&mut self) { + let _ = unsafe { ImmReleaseContext(self.hwnd, self.himc) }; + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard.rs new file mode 100644 index 000000000..aefa9658a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard.rs @@ -0,0 +1,951 @@ +use std::{ + char, + collections::HashSet, + ffi::OsString, + mem::MaybeUninit, + os::windows::ffi::OsStringExt, + sync::atomic::{AtomicU32, Ordering::Relaxed}, +}; + +use crate::{ + event::{ElementState, KeyEvent}, + platform_impl::KeyEventExtra, +}; +use crate::{ + keyboard::{Key, KeyCode, KeyLocation, NativeKeyCode}, + platform_impl::platform::keyboard_layout::get_or_insert_str, +}; +use parking_lot::{Mutex, MutexGuard}; +use unicode_segmentation::UnicodeSegmentation; +use windows::Win32::{ + Foundation::{HWND, LPARAM, LRESULT, WPARAM}, + UI::{ + Input::KeyboardAndMouse::{ + GetAsyncKeyState, GetKeyState, GetKeyboardState, MapVirtualKeyExW, HKL, MAPVK_VK_TO_VSC_EX, + MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY, VK_ABNT_C2, VK_ADD, VK_CAPITAL, VK_CLEAR, VK_CONTROL, + VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_F4, VK_HOME, VK_INSERT, VK_LCONTROL, + VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MENU, VK_MULTIPLY, VK_NEXT, VK_NUMLOCK, VK_NUMPAD0, + VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, + VK_NUMPAD8, VK_NUMPAD9, VK_PRIOR, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT, + VK_RWIN, VK_SCROLL, VK_SHIFT, VK_SUBTRACT, VK_UP, + }, + WindowsAndMessaging::{ + PeekMessageW, MSG, PM_NOREMOVE, WM_CHAR, WM_DEADCHAR, WM_KEYDOWN, WM_KEYFIRST, WM_KEYLAST, + WM_KEYUP, WM_KILLFOCUS, WM_SETFOCUS, WM_SYSCHAR, WM_SYSDEADCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, + }, + }, +}; + +use crate::platform_impl::platform::{ + event_loop::ProcResult, + keyboard_layout::{Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, +}; + +pub type ExScancode = u16; + +pub struct MessageAsKeyEvent { + pub event: KeyEvent, + pub is_synthetic: bool, +} + +/// Stores information required to make `KeyEvent`s. +/// +/// A single Tao `KeyEvent` contains information which the Windows API passes to the application +/// in multiple window messages. In other words: a Tao `KeyEvent` cannot be built from a single +/// window message. Therefore, this type keeps track of certain information from previous events so +/// that a `KeyEvent` can be constructed when the last event related to a keypress is received. +/// +/// `PeekMessage` is sometimes used to determine whether the next window message still belongs to the +/// current keypress. If it doesn't and the current state represents a key event waiting to be +/// dispatched, then said event is considered complete and is dispatched. +/// +/// The sequence of window messages for a key press event is the following: +/// - Exactly one WM_KEYDOWN / WM_SYSKEYDOWN +/// - Zero or one WM_DEADCHAR / WM_SYSDEADCHAR +/// - Zero or more WM_CHAR / WM_SYSCHAR. These messages each come with a UTF-16 code unit which when +/// put together in the sequence they arrived in, forms the text which is the result of pressing the +/// key. +/// +/// Key release messages are a bit different due to the fact that they don't contribute to +/// text input. The "sequence" only consists of one WM_KEYUP / WM_SYSKEYUP event. +pub struct KeyEventBuilder { + event_info: Mutex>, + pending: PendingEventQueue, +} +impl Default for KeyEventBuilder { + fn default() -> Self { + KeyEventBuilder { + event_info: Mutex::new(None), + pending: Default::default(), + } + } +} +impl KeyEventBuilder { + /// Call this function for every window message. + /// Returns Some() if this window message completes a KeyEvent. + /// Returns None otherwise. + pub(crate) fn process_message( + &self, + hwnd: HWND, + msg_kind: u32, + wparam: WPARAM, + lparam: LPARAM, + result: &mut ProcResult, + ) -> Vec { + enum MatchResult { + Nothing, + TokenToRemove(PendingMessageToken), + MessagesToDispatch(Vec), + } + + let mut matcher = || -> MatchResult { + match msg_kind { + WM_SETFOCUS => { + // synthesize keydown events + let kbd_state = get_async_kbd_state(); + let key_events = Self::synthesize_kbd_state(ElementState::Pressed, &kbd_state); + MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) + } + WM_KILLFOCUS => { + // synthesize keyup events + let kbd_state = get_kbd_state(); + let key_events = Self::synthesize_kbd_state(ElementState::Released, &kbd_state); + MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) + } + WM_KEYDOWN | WM_SYSKEYDOWN => { + if msg_kind == WM_SYSKEYDOWN && wparam.0 == usize::from(VK_F4.0) { + // Don't dispatch Alt+F4 to the application. + // This is handled in `event_loop.rs` + return MatchResult::Nothing; + } + let pending_token = self.pending.add_pending(); + if msg_kind == WM_SYSKEYDOWN { + *result = ProcResult::DefWindowProc; + } else { + *result = ProcResult::Value(LRESULT(0)); + } + + let next_msg = next_kbd_msg(hwnd); + + let mut layouts = LAYOUT_CACHE.lock(); + let mut finished_event_info = Some(PartialKeyEventInfo::from_message( + wparam, + lparam, + ElementState::Pressed, + &mut layouts, + )); + let mut event_info = self.event_info.lock(); + *event_info = None; + if let Some(next_msg) = next_msg { + let next_msg_kind = next_msg.message; + let next_belongs_to_this = !matches!( + next_msg_kind, + WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP + ); + if next_belongs_to_this { + // The next OS event belongs to this Winit event, so let's just + // store the partial information, and add to it in the upcoming events + *event_info = finished_event_info.take(); + } else { + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let curr_event = finished_event_info.as_ref().unwrap(); + is_current_fake(curr_event, next_msg, layout) + }; + if is_fake { + finished_event_info = None; + } + } + } + if let Some(event_info) = finished_event_info { + let ev = event_info.finalize(&mut layouts.strings); + return MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )); + } + MatchResult::TokenToRemove(pending_token) + } + WM_DEADCHAR | WM_SYSDEADCHAR => { + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(LRESULT(0)); + // At this point, we know that there isn't going to be any more events related + // to this key press + let event_info = self.event_info.lock().take().unwrap(); + let mut layouts = LAYOUT_CACHE.lock(); + let ev = event_info.finalize(&mut layouts.strings); + MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )) + } + WM_CHAR | WM_SYSCHAR => { + let mut event_info = self.event_info.lock(); + if event_info.is_none() { + trace!( + "Received a CHAR message but no `event_info` was available. The \ + message is probably IME, returning." + ); + return MatchResult::Nothing; + } + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(LRESULT(0)); + let is_high_surrogate = (0xd800..=0xdbff).contains(&wparam.0); + let is_low_surrogate = (0xdc00..=0xdfff).contains(&wparam.0); + + let is_utf16 = is_high_surrogate || is_low_surrogate; + + if is_utf16 { + if let Some(ev_info) = event_info.as_mut() { + ev_info.utf16parts.push(wparam.0 as u16); + } + } else { + // In this case, wparam holds a UTF-32 character. + // Let's encode it as UTF-16 and append it to the end of `utf16parts` + let utf16parts = match event_info.as_mut() { + Some(ev_info) => &mut ev_info.utf16parts, + None => { + warn!("The event_info was None when it was expected to be some"); + return MatchResult::TokenToRemove(pending_token); + } + }; + let start_offset = utf16parts.len(); + let new_size = utf16parts.len() + 2; + utf16parts.resize(new_size, 0); + if let Some(ch) = char::from_u32(wparam.0 as u32) { + let encode_len = ch.encode_utf16(&mut utf16parts[start_offset..]).len(); + let new_size = start_offset + encode_len; + utf16parts.resize(new_size, 0); + } + } + // It's important that we unlock the mutex, and create the pending event token + // before calling `next_msg` + std::mem::drop(event_info); + let next_msg = next_kbd_msg(hwnd); + let more_char_coming = next_msg + .map(|m| matches!(m.message, WM_CHAR | WM_SYSCHAR)) + .unwrap_or(false); + if more_char_coming { + // No need to produce an event just yet, because there are still more + // characters that need to be appended to this keyboard event + MatchResult::TokenToRemove(pending_token) + } else { + let mut event_info = self.event_info.lock(); + let mut event_info = match event_info.take() { + Some(ev_info) => ev_info, + None => { + warn!("The event_info was None when it was expected to be some"); + return MatchResult::TokenToRemove(pending_token); + } + }; + let mut layouts = LAYOUT_CACHE.lock(); + // It's okay to call `ToUnicode` here, because at this point the dead key + // is already consumed by the character. + let kbd_state = get_kbd_state(); + let mod_state = WindowsModifiers::active_modifiers(&kbd_state); + + let (_, layout) = layouts.get_current_layout(); + let ctrl_on = if layout.has_alt_graph { + let alt_on = mod_state.contains(WindowsModifiers::ALT); + !alt_on && mod_state.contains(WindowsModifiers::CONTROL) + } else { + mod_state.contains(WindowsModifiers::CONTROL) + }; + + // If Ctrl is not pressed, just use the text with all + // modifiers because that already consumed the dead key. Otherwise, + // we would interpret the character incorrectly, missing the dead key. + if !ctrl_on { + event_info.text = PartialText::System(event_info.utf16parts.clone()); + } else { + let mod_no_ctrl = mod_state.remove_only_ctrl(); + let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; + let vkey = event_info.vkey; + let scancode = event_info.scancode; + let keycode = event_info.code; + let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, scancode, keycode); + event_info.text = PartialText::Text(key.to_text()); + } + let ev = event_info.finalize(&mut layouts.strings); + MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )) + } + } + WM_KEYUP | WM_SYSKEYUP => { + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(LRESULT(0)); + + let mut layouts = LAYOUT_CACHE.lock(); + let event_info = + PartialKeyEventInfo::from_message(wparam, lparam, ElementState::Released, &mut layouts); + // We MUST release the layout lock before calling `next_kbd_msg`, otherwise it + // may deadlock + drop(layouts); + // It's important that we create the pending token before reading the next + // message. + let next_msg = next_kbd_msg(hwnd); + let mut valid_event_info = Some(event_info); + if let Some(next_msg) = next_msg { + let mut layouts = LAYOUT_CACHE.lock(); + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let event_info = valid_event_info.as_ref().unwrap(); + is_current_fake(event_info, next_msg, layout) + }; + if is_fake { + valid_event_info = None; + } + } + if let Some(event_info) = valid_event_info { + let mut layouts = LAYOUT_CACHE.lock(); + let event = event_info.finalize(&mut layouts.strings); + return MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event, + is_synthetic: false, + }, + )); + } + MatchResult::TokenToRemove(pending_token) + } + _ => MatchResult::Nothing, + } + }; + let matcher_result = matcher(); + match matcher_result { + MatchResult::TokenToRemove(t) => self.pending.remove_pending(t), + MatchResult::MessagesToDispatch(m) => m, + MatchResult::Nothing => Vec::new(), + } + } + + fn synthesize_kbd_state( + key_state: ElementState, + kbd_state: &[u8; 256], + ) -> Vec { + let mut key_events = Vec::new(); + + let mut layouts = LAYOUT_CACHE.lock(); + let (locale_id, _) = layouts.get_current_layout(); + + let is_key_pressed = |vk: VIRTUAL_KEY| &kbd_state[usize::from(vk.0)] & 0x80 != 0; + + // Is caps-lock active? Note that this is different from caps-lock + // being held down. + let caps_lock_on = kbd_state[usize::from(VK_CAPITAL.0)] & 1 != 0; + let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; + + // We are synthesizing the press event for caps-lock first for the following reasons: + // 1. If caps-lock is *not* held down but *is* active, then we have to synthesize all + // printable keys, respecting the caps-lock state. + // 2. If caps-lock is held down, we could choose to synthesize its keypress after every + // other key, in which case all other keys *must* be synthesized as if the caps-lock + // state was be the opposite of what it currently is. + // -- + // For the sake of simplicity we are choosing to always synthesize + // caps-lock first, and always use the current caps-lock state + // to determine the produced text + if is_key_pressed(VK_CAPITAL) { + let event = Self::create_synthetic( + VK_CAPITAL, + key_state, + caps_lock_on, + num_lock_on, + locale_id, + &mut layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + let do_non_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { + for vk in 0..256 { + let vk = VIRTUAL_KEY(vk); + match vk { + VK_CONTROL | VK_LCONTROL | VK_RCONTROL | VK_SHIFT | VK_LSHIFT | VK_RSHIFT | VK_MENU + | VK_LMENU | VK_RMENU | VK_CAPITAL => continue, + _ => (), + } + if !is_key_pressed(vk) { + continue; + } + let event = + Self::create_synthetic(vk, key_state, caps_lock_on, num_lock_on, locale_id, layouts); + if let Some(event) = event { + key_events.push(event); + } + } + }; + let do_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { + const CLEAR_MODIFIER_VKS: [VIRTUAL_KEY; 6] = [ + VK_LCONTROL, + VK_LSHIFT, + VK_LMENU, + VK_RCONTROL, + VK_RSHIFT, + VK_RMENU, + ]; + for vk in CLEAR_MODIFIER_VKS.iter() { + if is_key_pressed(*vk) { + let event = Self::create_synthetic( + *vk, + key_state, + caps_lock_on, + num_lock_on, + locale_id, + layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + } + }; + + // Be cheeky and sequence modifier and non-modifier + // key events such that non-modifier keys are not affected + // by modifiers (except for caps-lock) + match key_state { + ElementState::Pressed => { + do_non_modifier(&mut key_events, &mut layouts); + do_modifier(&mut key_events, &mut layouts); + } + ElementState::Released => { + do_modifier(&mut key_events, &mut layouts); + do_non_modifier(&mut key_events, &mut layouts); + } + } + + key_events + } + + fn create_synthetic( + vk: VIRTUAL_KEY, + key_state: ElementState, + caps_lock_on: bool, + num_lock_on: bool, + locale_id: HKL, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Option { + let scancode = + unsafe { MapVirtualKeyExW(u32::from(vk.0), MAPVK_VK_TO_VSC_EX, Some(locale_id)) }; + if scancode == 0 { + return None; + } + let scancode = scancode as ExScancode; + let code = KeyCode::from_scancode(scancode as u32); + let mods = if caps_lock_on { + WindowsModifiers::CAPS_LOCK + } else { + WindowsModifiers::empty() + }; + let layout = layouts.layouts.get(&(locale_id.0 as _)).unwrap(); + let logical_key = layout.get_key(mods, num_lock_on, vk, scancode, code); + let key_without_modifiers = + layout.get_key(WindowsModifiers::empty(), false, vk, scancode, code); + let text = if key_state == ElementState::Pressed { + logical_key.to_text() + } else { + None + }; + let event_info = PartialKeyEventInfo { + vkey: vk, + logical_key: PartialLogicalKey::This(logical_key.clone()), + key_without_modifiers, + key_state, + scancode, + is_repeat: false, + code, + location: get_location(scancode, locale_id), + utf16parts: Vec::with_capacity(8), + text: PartialText::Text(text), + }; + + let mut event = event_info.finalize(&mut layouts.strings); + event.logical_key = logical_key; + event.platform_specific.text_with_all_modifiers = text; + Some(MessageAsKeyEvent { + event, + is_synthetic: true, + }) + } +} + +enum PartialText { + // Unicode + System(Vec), + Text(Option<&'static str>), +} + +enum PartialLogicalKey { + /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If + /// the text consists of multiple grapheme clusters (user-perceived characters) that means that + /// dead key could not be combined with the second input, and in that case we should fall back + /// to using what would have without a dead-key input. + TextOr(Key<'static>), + + /// Use the value directly provided by this variant + This(Key<'static>), +} + +struct PartialKeyEventInfo { + vkey: VIRTUAL_KEY, + scancode: ExScancode, + key_state: ElementState, + is_repeat: bool, + code: KeyCode, + location: KeyLocation, + logical_key: PartialLogicalKey, + + key_without_modifiers: Key<'static>, + + /// The UTF-16 code units of the text that was produced by the keypress event. + /// This take all modifiers into account. Including CTRL + utf16parts: Vec, + + text: PartialText, +} + +impl PartialKeyEventInfo { + fn from_message( + wparam: WPARAM, + lparam: LPARAM, + state: ElementState, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Self { + const NO_MODS: WindowsModifiers = WindowsModifiers::empty(); + + let (_, layout) = layouts.get_current_layout(); + let lparam_struct = destructure_key_lparam(lparam); + let vkey = VIRTUAL_KEY(wparam.0 as u16); + let scancode = if lparam_struct.scancode == 0 { + // In some cases (often with media keys) the device reports a scancode of 0 but a + // valid virtual key. In these cases we obtain the scancode from the virtual key. + unsafe { + MapVirtualKeyExW( + u32::from(vkey.0), + MAPVK_VK_TO_VSC_EX, + Some(HKL(layout.hkl as _)), + ) as u16 + } + } else { + new_ex_scancode(lparam_struct.scancode, lparam_struct.extended) + }; + let code = KeyCode::from_scancode(scancode as u32); + let location = get_location(scancode, HKL(layout.hkl as _)); + + let kbd_state = get_kbd_state(); + let mods = WindowsModifiers::active_modifiers(&kbd_state); + let mods_without_ctrl = mods.remove_only_ctrl(); + let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; + + // On Windows Ctrl+NumLock = Pause (and apparently Ctrl+Pause -> NumLock). In these cases + // the KeyCode still stores the real key, so in the name of consistency across platforms, we + // circumvent this mapping and force the key values to match the keycode. + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + let code_as_key = if mods.contains(WindowsModifiers::CONTROL) { + match code { + KeyCode::NumLock => Some(Key::NumLock), + KeyCode::Pause => Some(Key::Pause), + _ => None, + } + } else { + None + }; + + let preliminary_logical_key = + layout.get_key(mods_without_ctrl, num_lock_on, vkey, scancode, code); + let key_is_char = matches!(preliminary_logical_key, Key::Character(_)); + let is_pressed = state == ElementState::Pressed; + + let logical_key = if let Some(key) = code_as_key.clone() { + PartialLogicalKey::This(key) + } else if is_pressed && key_is_char && !mods.contains(WindowsModifiers::CONTROL) { + // In some cases we want to use the UNICHAR text for logical_key in order to allow + // dead keys to have an effect on the character reported by `logical_key`. + PartialLogicalKey::TextOr(preliminary_logical_key) + } else { + PartialLogicalKey::This(preliminary_logical_key) + }; + let key_without_modifiers = if let Some(key) = code_as_key { + key + } else { + match layout.get_key(NO_MODS, false, vkey, scancode, code) { + // We convert dead keys into their character. + // The reason for this is that `key_without_modifiers` is designed for key-bindings, + // but the US International layout treats `'` (apostrophe) as a dead key and the + // regular US layout treats it a character. In order for a single binding + // configuration to work with both layouts, we forward each dead key as a character. + Key::Dead(k) => { + if let Some(ch) = k { + // I'm avoiding the heap allocation. I don't want to talk about it :( + let mut utf8 = [0; 4]; + let s = ch.encode_utf8(&mut utf8); + let static_str = get_or_insert_str(&mut layouts.strings, s); + Key::Character(static_str) + } else { + Key::Unidentified(NativeKeyCode::Unidentified) + } + } + key => key, + } + }; + + PartialKeyEventInfo { + vkey, + scancode, + key_state: state, + logical_key, + key_without_modifiers, + is_repeat: lparam_struct.is_repeat, + code, + location, + utf16parts: Vec::with_capacity(8), + text: PartialText::System(Vec::new()), + } + } + + fn finalize(self, strings: &mut HashSet<&'static str>) -> KeyEvent { + let mut char_with_all_modifiers = None; + if !self.utf16parts.is_empty() { + let os_string = OsString::from_wide(&self.utf16parts); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + char_with_all_modifiers = Some(static_str); + } + } + + // The text without Ctrl + let mut text = None; + match self.text { + PartialText::System(wide) => { + if !wide.is_empty() { + let os_string = OsString::from_wide(&wide); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + text = Some(static_str); + } + } + } + PartialText::Text(s) => { + text = s; + } + } + + let logical_key = match self.logical_key { + PartialLogicalKey::TextOr(fallback) => match text { + Some(s) => { + if s.grapheme_indices(true).count() > 1 { + fallback + } else { + Key::Character(s) + } + } + None => Key::Unidentified(NativeKeyCode::Windows(self.scancode)), + }, + PartialLogicalKey::This(v) => v, + }; + + KeyEvent { + physical_key: self.code, + logical_key, + text, + location: self.location, + state: self.key_state, + repeat: self.is_repeat, + platform_specific: KeyEventExtra { + text_with_all_modifiers: char_with_all_modifiers, + key_without_modifiers: self.key_without_modifiers, + }, + } + } +} + +#[derive(Debug, Copy, Clone)] +struct KeyLParam { + pub scancode: u8, + pub extended: bool, + + /// This is `previous_state XOR transition_state`. See the lParam for WM_KEYDOWN and WM_KEYUP for further details. + pub is_repeat: bool, +} + +fn destructure_key_lparam(lparam: LPARAM) -> KeyLParam { + let previous_state = (lparam.0 >> 30) & 0x01; + let transition_state = (lparam.0 >> 31) & 0x01; + KeyLParam { + scancode: ((lparam.0 >> 16) & 0xFF) as u8, + extended: ((lparam.0 >> 24) & 0x01) != 0, + is_repeat: (previous_state ^ transition_state) != 0, + } +} + +#[inline] +fn new_ex_scancode(scancode: u8, extended: bool) -> ExScancode { + (scancode as u16) | (if extended { 0xE000 } else { 0 }) +} + +#[inline] +fn ex_scancode_from_lparam(lparam: LPARAM) -> ExScancode { + let lparam = destructure_key_lparam(lparam); + new_ex_scancode(lparam.scancode, lparam.extended) +} + +/// Gets the keyboard state as reported by messages that have been removed from the event queue. +/// See also: get_async_kbd_state +fn get_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: MaybeUninit<[u8; 256]> = MaybeUninit::uninit(); + let _ = GetKeyboardState(&mut *kbd_state.as_mut_ptr()); + kbd_state.assume_init() + } +} + +/// Gets the current keyboard state regardless of whether the corresponding keyboard events have +/// been removed from the event queue. See also: get_kbd_state +fn get_async_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: [u8; 256] = [0; 256]; + for (vk, state) in kbd_state.iter_mut().enumerate() { + let vk = VIRTUAL_KEY(vk as u16); + let async_state = GetAsyncKeyState(i32::from(vk.0)); + let is_down = (async_state & (1 << 15)) != 0; + *state = if is_down { 0x80 } else { 0 }; + + if matches!(vk, VK_CAPITAL | VK_NUMLOCK | VK_SCROLL) { + // Toggle states aren't reported by `GetAsyncKeyState` + let toggle_state = GetKeyState(i32::from(vk.0)); + let is_active = (toggle_state & 1) != 0; + *state |= u8::from(is_active); + } + } + kbd_state + } +} + +/// On windows, AltGr == Ctrl + Alt +/// +/// Due to this equivalence, the system generates a fake Ctrl key-press (and key-release) preceding +/// every AltGr key-press (and key-release). We check if the current event is a Ctrl event and if +/// the next event is a right Alt (AltGr) event. If this is the case, the current event must be the +/// fake Ctrl event. +fn is_current_fake(curr_info: &PartialKeyEventInfo, next_msg: MSG, layout: &Layout) -> bool { + let curr_is_ctrl = matches!(curr_info.logical_key, PartialLogicalKey::This(Key::Control)); + if layout.has_alt_graph { + let next_code = ex_scancode_from_lparam(next_msg.lParam); + let next_is_altgr = next_code == 0xE038; // 0xE038 is right alt + if curr_is_ctrl && next_is_altgr { + return true; + } + } + false +} + +enum PendingMessage { + Incomplete, + Complete(T), +} +struct IdentifiedPendingMessage { + token: PendingMessageToken, + msg: PendingMessage, +} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PendingMessageToken(u32); + +/// While processing keyboard events, we sometimes need +/// to call `PeekMessageW` (`next_msg`). But `PeekMessageW` +/// can also call the event handler, which means that the new event +/// gets processed before finishing to process the one that came before. +/// +/// This would mean that the application receives events in the wrong order. +/// To avoid this, we keep track whether we are in the middle of processing +/// an event. Such an event is an "incomplete pending event". A +/// "complete pending event" is one that has already finished processing, but +/// hasn't been dispatched to the application because there still are incomplete +/// pending events that came before it. +/// +/// When we finish processing an event, we call `complete_pending`, +/// which returns an empty array if there are incomplete pending events, but +/// if all pending events are complete, then it returns all pending events in +/// the order they were encountered. These can then be dispatched to the application +pub struct PendingEventQueue { + pending: Mutex>>, + next_id: AtomicU32, +} +impl PendingEventQueue { + /// Add a new pending event to the "pending queue" + pub fn add_pending(&self) -> PendingMessageToken { + let token = self.next_token(); + let mut pending = self.pending.lock(); + pending.push(IdentifiedPendingMessage { + token, + msg: PendingMessage::Incomplete, + }); + token + } + + /// Returns all finished pending events + /// + /// If the return value is non empty, it's guaranteed to contain `msg` + /// + /// See also: `add_pending` + pub fn complete_pending(&self, token: PendingMessageToken, msg: T) -> Vec { + let mut pending = self.pending.lock(); + let mut target_is_first = false; + for (i, pending_msg) in pending.iter_mut().enumerate() { + if pending_msg.token == token { + pending_msg.msg = PendingMessage::Complete(msg); + if i == 0 { + target_is_first = true; + } + break; + } + } + if target_is_first { + // If the message that we just finished was the first one in the pending queue, + // then we can empty the queue, and dispatch all of the messages. + Self::drain_pending(&mut *pending) + } else { + Vec::new() + } + } + + pub fn complete_multi(&self, msgs: Vec) -> Vec { + let mut pending = self.pending.lock(); + if pending.is_empty() { + return msgs; + } + pending.reserve(msgs.len()); + for msg in msgs { + pending.push(IdentifiedPendingMessage { + token: self.next_token(), + msg: PendingMessage::Complete(msg), + }); + } + Vec::new() + } + + /// Returns all finished pending events + /// + /// It's safe to call this even if the element isn't in the list anymore + /// + /// See also: `add_pending` + pub fn remove_pending(&self, token: PendingMessageToken) -> Vec { + let mut pending = self.pending.lock(); + let mut was_first = false; + if let Some(m) = pending.first() { + if m.token == token { + was_first = true; + } + } + pending.retain(|m| m.token != token); + if was_first { + Self::drain_pending(&mut *pending) + } else { + Vec::new() + } + } + + fn drain_pending(pending: &mut Vec>) -> Vec { + pending + .drain(..) + .map(|m| match m.msg { + PendingMessage::Complete(msg) => msg, + PendingMessage::Incomplete => { + panic!( + "Found an incomplete pending message when collecting messages. This \ + indicates a bug in tao." + ) + } + }) + .collect() + } + + fn next_token(&self) -> PendingMessageToken { + // It's okay for the u32 to overflow here. Yes, that could mean + // that two different messages have the same token, + // but that would only happen after having about 4 billion + // messages sitting in the pending queue. + // + // In that case, having two identical tokens is the least of your concerns. + let id = self.next_id.fetch_add(1, Relaxed); + PendingMessageToken(id) + } +} +impl Default for PendingEventQueue { + fn default() -> Self { + PendingEventQueue { + pending: Mutex::new(Vec::new()), + next_id: AtomicU32::new(0), + } + } +} + +/// WARNING: Due to using PeekMessage, the event handler +/// function may get called during this function. +/// (Re-entrance to the event handler) +/// +/// This can cause a deadlock if calling this function +/// while having a mutex locked. +/// +/// It can also cause code to get executed in a surprising order. +pub(crate) fn next_kbd_msg(window: HWND) -> Option { + unsafe { + let mut next_msg = MaybeUninit::uninit(); + if PeekMessageW( + next_msg.as_mut_ptr(), + Some(window), + WM_KEYFIRST, + WM_KEYLAST, + PM_NOREMOVE, + ) + .as_bool() + { + Some(next_msg.assume_init()) + } else { + None + } + } +} + +fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { + let extension = 0xE000; + let extended = (scancode & extension) == extension; + let vkey = + VIRTUAL_KEY(unsafe { MapVirtualKeyExW(scancode as u32, MAPVK_VSC_TO_VK_EX, Some(hkl)) } as u16); + + // Use the native VKEY and the extended flag to cover most cases + // This is taken from the `druid` GUI library, specifically + // druid-shell/src/platform/windows/keyboard.rs + match vkey { + VK_LSHIFT | VK_LCONTROL | VK_LMENU | VK_LWIN => KeyLocation::Left, + VK_RSHIFT | VK_RCONTROL | VK_RMENU | VK_RWIN => KeyLocation::Right, + VK_RETURN if extended => KeyLocation::Numpad, + VK_INSERT | VK_DELETE | VK_END | VK_DOWN | VK_NEXT | VK_LEFT | VK_CLEAR | VK_RIGHT + | VK_HOME | VK_UP | VK_PRIOR => { + if extended { + KeyLocation::Standard + } else { + KeyLocation::Numpad + } + } + VK_NUMPAD0 | VK_NUMPAD1 | VK_NUMPAD2 | VK_NUMPAD3 | VK_NUMPAD4 | VK_NUMPAD5 | VK_NUMPAD6 + | VK_NUMPAD7 | VK_NUMPAD8 | VK_NUMPAD9 | VK_DECIMAL | VK_DIVIDE | VK_MULTIPLY | VK_SUBTRACT + | VK_ADD | VK_ABNT_C2 => KeyLocation::Numpad, + _ => KeyLocation::Standard, + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard_layout.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard_layout.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard_layout.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard_layout.rs index 9862c5532..e4105a3de 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard_layout.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard_layout.rs @@ -1,9 +1,9 @@ -use once_cell::sync::Lazy; use parking_lot::Mutex; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, ffi::OsString, os::windows::ffi::OsStringExt, + sync::LazyLock, }; use windows::Win32::{ @@ -17,8 +17,12 @@ use crate::{ platform_impl::platform::util, }; -pub(crate) static LAYOUT_CACHE: Lazy> = - Lazy::new(|| Mutex::new(LayoutCache::default())); +pub(crate) static LAYOUT_CACHE: LazyLock> = + LazyLock::new(|| Mutex::new(LayoutCache::default())); + +pub(crate) fn get_agnostic_mods() -> ModifiersState { + LAYOUT_CACHE.lock().get_agnostic_mods() +} fn key_pressed(vkey: VIRTUAL_KEY) -> bool { unsafe { (GetKeyState(u32::from(vkey.0) as i32) & (1 << 15)) == (1 << 15) } @@ -43,7 +47,7 @@ const NUMPAD_VKEYS: [VIRTUAL_KEY; 16] = [ VK_DIVIDE, ]; -static NUMPAD_KEYCODES: Lazy> = Lazy::new(|| { +static NUMPAD_KEYCODES: LazyLock> = LazyLock::new(|| { let mut keycodes = HashSet::new(); keycodes.insert(KeyCode::Numpad0); keycodes.insert(KeyCode::Numpad1); @@ -245,7 +249,7 @@ impl LayoutCache { } } - pub fn get_agnostic_mods(&mut self) -> ModifiersState { + fn get_agnostic_mods(&mut self) -> ModifiersState { let (_, layout) = self.get_current_layout(); let filter_out_altgr = layout.has_alt_graph && key_pressed(VK_RMENU); let mut mods = ModifiersState::empty(); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keycode.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keycode.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keycode.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/mod.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/mod.rs index 8cd4d97a1..169d35e03 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/mod.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/mod.rs @@ -148,9 +148,9 @@ mod dpi; mod drop_handler; mod event_loop; mod icon; +mod ime; mod keyboard; mod keyboard_layout; -mod minimal_ime; mod monitor; mod raw_input; mod window; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/monitor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/monitor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/monitor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/raw_input.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/raw_input.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/raw_input.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/raw_input.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/util.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/util.rs similarity index 89% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/util.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/util.rs index b4bc6e066..3535b00ab 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/util.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/util.rs @@ -17,11 +17,11 @@ use crate::{ window::CursorIcon, }; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use windows::{ core::{BOOL, HRESULT, PCSTR, PCWSTR}, Win32::{ - Foundation::{COLORREF, FARPROC, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}, + Foundation::{COLORREF, FARPROC, HWND, LPARAM, POINT, RECT, WPARAM}, Globalization::lstrlenW, Graphics::Gdi::{ClientToScreen, InvalidateRgn, HMONITOR}, System::LibraryLoader::*, @@ -292,22 +292,22 @@ pub type AdjustWindowRectExForDpi = unsafe extern "system" fn( dpi: u32, ) -> BOOL; -pub static GET_DPI_FOR_WINDOW: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetDpiForWindow)); -pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy> = - Lazy::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi)); -pub static GET_DPI_FOR_MONITOR: Lazy> = - Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor)); -pub static GET_SYSTEM_METRICS_FOR_DPI: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetSystemMetricsForDpi)); -pub static ENABLE_NON_CLIENT_DPI_SCALING: Lazy> = - Lazy::new(|| get_function!("user32.dll", EnableNonClientDpiScaling)); -pub static SET_PROCESS_DPI_AWARENESS_CONTEXT: Lazy> = - Lazy::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext)); -pub static SET_PROCESS_DPI_AWARENESS: Lazy> = - Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness)); -pub static SET_PROCESS_DPI_AWARE: Lazy> = - Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware)); +pub static GET_DPI_FOR_WINDOW: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetDpiForWindow)); +pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi)); +pub static GET_DPI_FOR_MONITOR: LazyLock> = + LazyLock::new(|| get_function!("shcore.dll", GetDpiForMonitor)); +pub static GET_SYSTEM_METRICS_FOR_DPI: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetSystemMetricsForDpi)); +pub static ENABLE_NON_CLIENT_DPI_SCALING: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", EnableNonClientDpiScaling)); +pub static SET_PROCESS_DPI_AWARENESS_CONTEXT: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext)); +pub static SET_PROCESS_DPI_AWARENESS: LazyLock> = + LazyLock::new(|| get_function!("shcore.dll", SetProcessDpiAwareness)); +pub static SET_PROCESS_DPI_AWARE: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SetProcessDPIAware)); #[allow(non_snake_case)] #[cfg(target_pointer_width = "32")] @@ -398,16 +398,7 @@ pub fn RGB>(r: T, g: T, b: T) -> COLORREF { COLORREF(r.into() | (g.into() << 8) | (b.into() << 16)) } -pub unsafe extern "system" fn call_default_window_proc( - hwnd: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - DefWindowProcW(hwnd, msg, wparam, lparam) -} - -pub fn get_instance_handle() -> windows::Win32::Foundation::HMODULE { +pub fn get_instance_handle() -> windows::Win32::Foundation::HINSTANCE { // Gets the instance handle by taking the address of the // pseudo-variable created by the microsoft linker: // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483 @@ -419,11 +410,11 @@ pub fn get_instance_handle() -> windows::Win32::Foundation::HMODULE { static __ImageBase: windows::Win32::System::SystemServices::IMAGE_DOS_HEADER; } - windows::Win32::Foundation::HMODULE(unsafe { &__ImageBase as *const _ as _ }) + windows::Win32::Foundation::HINSTANCE(unsafe { &__ImageBase as *const _ as _ }) } -pub static WIN_VERSION: Lazy = - Lazy::new(windows_version::OsVersion::current); +pub static WIN_VERSION: LazyLock = + LazyLock::new(windows_version::OsVersion::current); pub fn get_frame_thickness(dpi: u32) -> i32 { let resize_frame_thickness = unsafe { get_system_metrics_for_dpi(SM_CXSIZEFRAME, dpi) }; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window.rs similarity index 72% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window.rs index 170de3668..c92bcfad0 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window.rs @@ -11,6 +11,7 @@ use std::{ ffi::OsStr, io, mem, os::windows::ffi::OsStrExt, + panic, sync::Arc, }; @@ -18,18 +19,16 @@ use crossbeam_channel as channel; use windows::{ core::PCWSTR, Win32::{ - Foundation::{ - self as win32f, HINSTANCE, HMODULE, HWND, LPARAM, LRESULT, POINT, POINTS, RECT, WPARAM, - }, + Foundation::{self as win32f, HINSTANCE, HWND, LPARAM, POINT, POINTS, RECT, WPARAM}, Graphics::{ Dwm::{DwmEnableBlurBehindWindow, DWM_BB_BLURREGION, DWM_BB_ENABLE, DWM_BLURBEHIND}, Gdi::*, }, - System::{Com::*, LibraryLoader::*, Ole::*}, + System::{Com::*, Ole::*}, UI::{ Input::{Ime::*, KeyboardAndMouse::*, Touch::*}, Shell::{ITaskbarList4 as ITaskbarList, TaskbarList, *}, - WindowsAndMessaging::{self as win32wm, *}, + WindowsAndMessaging::*, }, }, }; @@ -41,7 +40,7 @@ use crate::{ monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ dark_mode::try_window_theme, - dpi::{dpi_to_scale_factor, hwnd_dpi}, + dpi::{dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_dpi}, drop_handler::FileDropHandler, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, icon::{self, IconType}, @@ -57,11 +56,11 @@ use crate::{ }; use super::{ - event_loop::CHANGE_THEME_MSG_ID, - keyboard::{KeyEventBuilder, KEY_EVENT_BUILDERS}, - util::calculate_insets_for_dpi, + event_loop::CHANGE_THEME_MSG_ID, keyboard::KeyEventBuilder, util::calculate_insets_for_dpi, }; +use super::event_loop::WindowData; + /// A simple non-owning wrapper around a window. #[derive(Clone, Copy)] pub struct WindowWrapper(pub HWND); @@ -73,6 +72,20 @@ pub struct WindowWrapper(pub HWND); unsafe impl Sync for WindowWrapper {} unsafe impl Send for WindowWrapper {} +impl WindowWrapper { + /// This is used for moving `WindowWrapper` instead of `window.0` + /// when using closures + /// + /// ```ignore + /// let window = self.window; + /// let not_send_sync = move || { window.0 }; + /// let send_sync = move || { window.hwnd() }; + /// ``` + fn hwnd(&self) -> HWND { + self.0 + } +} + /// The Win32 implementation of the main `Window` object. pub struct Window { /// Main handle for the window. @@ -95,77 +108,28 @@ impl Window { // First person to remove the need for cloning here gets a cookie! // // done. you owe me -- ossi - unsafe { - let drag_and_drop = pl_attr.drag_and_drop; - init(w_attr, pl_attr, event_loop).map(|win| { - let file_drop_handler = if drag_and_drop { - // It is ok if the initialize result is `S_FALSE` because it might happen that - // multiple windows are created on the same thread. - if let Err(error) = OleInitialize(None) { - match error.code() { - win32f::OLE_E_WRONGCOMPOBJ => { - panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`") - } - win32f::RPC_E_CHANGED_MODE => panic!( - "OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`. \ - Make sure other crates are not using multithreaded COM library \ - on the same thread or disable drag and drop support." - ), - _ => (), - }; - } - - let file_drop_runner = event_loop.runner_shared.clone(); - let file_drop_handler: IDropTarget = FileDropHandler::new( - win.window.0, - Box::new(move |event| { - if let Ok(e) = event.map_nonuser_event() { - file_drop_runner.send_event(e) - } - }), - ) - .into(); - - assert!(RegisterDragDrop(win.window.0, &file_drop_handler).is_ok()); - Some(file_drop_handler) - } else { - None - }; - - let subclass_input = event_loop::SubclassInput { - window_state: win.window_state.clone(), - event_loop_runner: event_loop.runner_shared.clone(), - _file_drop_handler: file_drop_handler, - subclass_removed: Cell::new(false), - recurse_depth: Cell::new(0), - event_loop_preferred_theme: event_loop.preferred_theme.clone(), - }; - - event_loop::subclass_window(win.window.0, subclass_input); - win - }) - } + unsafe { init::(w_attr, pl_attr, event_loop) } } pub fn set_title(&self, text: &str) { let text = util::encode_wide(text); unsafe { - let _ = SetWindowTextW(self.window.0, PCWSTR::from_raw(text.as_ptr())); + let _ = SetWindowTextW(self.hwnd(), PCWSTR::from_raw(text.as_ptr())); } } pub fn title(&self) -> String { - let len = unsafe { GetWindowTextLengthW(self.window.0) }; + let len = unsafe { GetWindowTextLengthW(self.hwnd()) }; let mut buf = vec![0; (len + 1) as usize]; - unsafe { GetWindowTextW(self.window.0, &mut buf) }; + unsafe { GetWindowTextW(self.hwnd(), &mut buf) }; String::from_utf16_lossy(&buf[..len as _]) } #[inline] pub fn set_visible(&self, visible: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::VISIBLE, visible) }); }); @@ -187,10 +151,10 @@ impl Window { #[inline] pub fn set_focusable(&self, focusable: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::FOCUSABLE, focusable) }); }); @@ -205,13 +169,13 @@ impl Window { #[inline] pub fn request_redraw(&self) { unsafe { - let _ = RedrawWindow(Some(self.window.0), None, None, RDW_INTERNALPAINT); + let _ = RedrawWindow(Some(self.hwnd()), None, None, RDW_INTERNALPAINT); } } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { - unsafe { util::get_window_rect(self.window.0) } + unsafe { util::get_window_rect(self.hwnd()) } .map(|rect| Ok(PhysicalPosition::new(rect.left, rect.top))) .expect("Unexpected GetWindowRect failure") } @@ -219,7 +183,7 @@ impl Window { #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { let mut position = POINT::default(); - if !unsafe { ClientToScreen(self.window.0, &mut position) }.as_bool() { + if !unsafe { ClientToScreen(self.hwnd(), &mut position) }.as_bool() { panic!("Unexpected ClientToScreen failure") } Ok(PhysicalPosition::new(position.x, position.y)) @@ -230,16 +194,16 @@ impl Window { let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); let window_state = Arc::clone(&self.window_state); - let window = self.window.0 .0 as isize; + let window = self.window; self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MAXIMIZED, false) }); }); unsafe { let _ = SetWindowPos( - self.window.0, + self.hwnd(), None, x, y, @@ -247,7 +211,7 @@ impl Window { 0, SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE, ); - let _ = InvalidateRgn(self.window.0, None, false); + let _ = InvalidateRgn(self.hwnd(), None, false); } } @@ -298,15 +262,15 @@ impl Window { } let window_state = Arc::clone(&self.window_state); - let window = self.window.0 .0 as isize; + let window = self.window; self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MAXIMIZED, false) }); }); util::set_inner_size_physical( - self.window.0, + self.hwnd(), desired_width, desired_height, window_flags.contains(WindowFlags::MARKER_DECORATIONS), @@ -353,11 +317,11 @@ impl Window { #[inline] pub fn set_resizable(&self, resizable: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::RESIZABLE, resizable) }); }); @@ -365,11 +329,11 @@ impl Window { #[inline] pub fn set_minimizable(&self, minimizable: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MINIMIZABLE, minimizable) }); }); @@ -377,11 +341,11 @@ impl Window { #[inline] pub fn set_maximizable(&self, maximizable: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MAXIMIZABLE, maximizable) }); }); @@ -389,10 +353,10 @@ impl Window { #[inline] pub fn set_closable(&self, closable: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::CLOSABLE, closable) }); }); @@ -401,11 +365,11 @@ impl Window { /// Returns the `hwnd` of this window. #[inline] pub fn hwnd(&self) -> HWND { - self.window.0 + self.window.hwnd() } #[inline] - pub fn hinstance(&self) -> HMODULE { + pub fn hinstance(&self) -> HINSTANCE { util::get_instance_handle() } @@ -413,7 +377,7 @@ impl Window { #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::Win32Handle::empty(); - window_handle.hwnd = self.window.0 .0 as *mut _; + window_handle.hwnd = self.hwnd().0; let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); window_handle.hinstance = hinstance as *mut _; rwh_04::RawWindowHandle::Win32(window_handle) @@ -423,7 +387,7 @@ impl Window { #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::Win32WindowHandle::empty(); - window_handle.hwnd = self.window.0 .0 as *mut _; + window_handle.hwnd = self.hwnd().0; let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); window_handle.hinstance = hinstance as *mut _; rwh_05::RawWindowHandle::Win32(window_handle) @@ -440,7 +404,7 @@ impl Window { pub fn raw_window_handle_rwh_06(&self) -> Result { let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe { // SAFETY: Handle will never be zero. - let window = self.window.0 .0; + let window = self.hwnd().0; std::num::NonZeroIsize::new_unchecked(window as _) }); let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); @@ -467,7 +431,7 @@ impl Window { #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel::unbounded(); @@ -475,7 +439,7 @@ impl Window { let result = window_state .lock() .mouse - .set_cursor_flags(HWND(window as _), |f| f.set(CursorFlags::GRABBED, grab)) + .set_cursor_flags(window.hwnd(), |f| f.set(CursorFlags::GRABBED, grab)) .map_err(|e| ExternalError::Os(os_error!(OsError::IoError(e)))); let _ = tx.send(result); }); @@ -484,7 +448,7 @@ impl Window { #[inline] pub fn set_cursor_visible(&self, visible: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel::unbounded(); @@ -492,7 +456,7 @@ impl Window { let result = window_state .lock() .mouse - .set_cursor_flags(HWND(window as _), |f| f.set(CursorFlags::HIDDEN, !visible)) + .set_cursor_flags(window.hwnd(), |f| f.set(CursorFlags::HIDDEN, !visible)) .map_err(|e| e.to_string()); let _ = tx.send(result); }); @@ -516,7 +480,7 @@ impl Window { let mut point = POINT { x, y }; unsafe { - if !ClientToScreen(self.window.0, &mut point).as_bool() { + if !ClientToScreen(self.hwnd(), &mut point).as_bool() { return Err(ExternalError::Os(os_error!(OsError::IoError( io::Error::last_os_error() )))); @@ -564,10 +528,10 @@ impl Window { #[inline] pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::IGNORE_CURSOR_EVENT, ignore) }); }); @@ -577,12 +541,12 @@ impl Window { #[inline] pub fn id(&self) -> WindowId { - WindowId(self.window.0 .0 as _) + WindowId(self.hwnd().0 as _) } #[inline] pub fn set_minimized(&self, minimized: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); let is_minimized = self.is_minimized(); @@ -591,7 +555,7 @@ impl Window { WindowState::set_window_flags_in_place(&mut window_state.lock(), |f| { f.set(WindowFlags::MINIMIZED, is_minimized) }); - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MINIMIZED, minimized) }); }); @@ -599,11 +563,11 @@ impl Window { #[inline] pub fn set_maximized(&self, maximized: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MAXIMIZED, maximized) }); }); @@ -662,7 +626,7 @@ impl Window { #[inline] pub fn is_visible(&self) -> bool { - util::is_visible(self.window.0) + util::is_visible(self.hwnd()) } #[inline] @@ -826,11 +790,11 @@ impl Window { #[inline] pub fn set_decorations(&self, decorations: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MARKER_DECORATIONS, decorations) }); }); @@ -838,11 +802,11 @@ impl Window { #[inline] pub fn set_always_on_bottom(&self, always_on_bottom: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::ALWAYS_ON_BOTTOM, always_on_bottom) }); }); @@ -850,22 +814,22 @@ impl Window { #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top) }); }); } pub fn set_rtl(&self, rtl: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::RIGHT_TO_LEFT_LAYOUT, rtl) }); }); @@ -874,7 +838,7 @@ impl Window { #[inline] pub fn current_monitor(&self) -> Option { Some(RootMonitorHandle { - inner: monitor::current_monitor(self.window.0), + inner: monitor::current_monitor(self.hwnd()), }) } @@ -883,9 +847,9 @@ impl Window { if let Some(ref window_icon) = window_icon { window_icon .inner - .set_for_window(self.window.0, IconType::Small); + .set_for_window(self.hwnd(), IconType::Small); } else { - icon::unset_for_window(self.window.0, IconType::Small); + icon::unset_for_window(self.hwnd(), IconType::Small); } self.window_state.lock().window_icon = window_icon; } @@ -895,9 +859,9 @@ impl Window { if let Some(ref taskbar_icon) = taskbar_icon { taskbar_icon .inner - .set_for_window(self.window.0, IconType::Big); + .set_for_window(self.hwnd(), IconType::Big); } else { - icon::unset_for_window(self.window.0, IconType::Big); + icon::unset_for_window(self.hwnd(), IconType::Big); } self.window_state.lock().taskbar_icon = taskbar_icon; } @@ -910,9 +874,9 @@ impl Window { rcArea: RECT::default(), }; unsafe { - let himc = ImmGetContext(self.window.0); + let himc = ImmGetContext(self.hwnd()); let _ = ImmSetCompositionWindow(himc, &composition_form); - let _ = ImmReleaseContext(self.window.0, himc); + let _ = ImmReleaseContext(self.hwnd(), himc); } } } @@ -1024,7 +988,7 @@ impl Window { pub fn set_progress_bar(&self, progress: ProgressBarState) { unsafe { let taskbar_list: ITaskbarList = CoCreateInstance(&TaskbarList, None, CLSCTX_SERVER).unwrap(); - let handle = self.window.0; + let handle = self.hwnd(); if let Some(state) = progress.state { let taskbar_state = { @@ -1060,7 +1024,7 @@ impl Window { unsafe { taskbar - .SetOverlayIcon(self.window.0, icon, None) + .SetOverlayIcon(self.hwnd(), icon, None) .unwrap_or(()); } } @@ -1071,8 +1035,7 @@ impl Window { let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - let _ = &window; - WindowState::set_window_flags(window_state.lock(), window.0, |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow) }); }); @@ -1104,12 +1067,167 @@ impl Window { impl Drop for Window { #[inline] fn drop(&mut self) { - KEY_EVENT_BUILDERS.lock().remove(&self.id()); unsafe { // The window must be destroyed from the same thread that created it, so we send a // custom message to be handled by our callback to do the actual work. - let _ = PostMessageW(Some(self.window.0), *DESTROY_MSG_ID, WPARAM(0), LPARAM(0)); + let _ = PostMessageW(Some(self.hwnd()), *DESTROY_MSG_ID, WPARAM(0), LPARAM(0)); + } + } +} + +pub(super) struct InitData<'a, T: 'static> { + // inputs + pub event_loop: &'a EventLoopWindowTarget, + pub attributes: WindowAttributes, + pub pl_attribs: PlatformSpecificWindowBuilderAttributes, + pub window_flags: WindowFlags, + // outputs + pub window: Option, +} + +impl InitData<'_, T> { + unsafe fn create_window(&self, window: HWND) -> Window { + // Register for touch events if applicable + { + let digitizer = GetSystemMetrics(SM_DIGITIZER) as u32; + if digitizer & NID_READY != 0 { + let _ = RegisterTouchWindow(window, TWF_WANTPALM); + } + } + + let dpi = hwnd_dpi(window); + let scale_factor = dpi_to_scale_factor(dpi); + + // If the system theme is dark, we need to set the window theme now + // before we update the window flags (and possibly show the + // window for the first time). + let current_theme = try_window_theme( + window, + self + .attributes + .preferred_theme + .or(*self.event_loop.preferred_theme.lock()), + false, + ); + + let window_state = { + let window_state = WindowState::new( + &self.attributes, + None, + scale_factor, + current_theme, + self.attributes.preferred_theme, + self.attributes.background_color, + ); + let window_state = Arc::new(Mutex::new(window_state)); + WindowState::set_window_flags(window_state.lock(), window, |f| *f = self.window_flags); + window_state + }; + + enable_non_client_dpi_scaling(window); + + Window { + window: WindowWrapper(window), + window_state, + thread_executor: self.event_loop.create_thread_executor(), + } + } + + unsafe fn create_window_data(&self, win: &Window) -> event_loop::WindowData { + let file_drop_handler = if self.pl_attribs.drag_and_drop { + // It is ok if the initialize result is `S_FALSE` because it might happen that + // multiple windows are created on the same thread. + if let Err(error) = OleInitialize(None) { + match error.code() { + win32f::OLE_E_WRONGCOMPOBJ => { + panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`") + } + win32f::RPC_E_CHANGED_MODE => panic!( + "OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`. \ + Make sure other crates are not using multithreaded COM library \ + on the same thread or disable drag and drop support." + ), + _ => (), + }; + } + + let file_drop_runner = self.event_loop.runner_shared.clone(); + let file_drop_handler: IDropTarget = FileDropHandler::new( + win.hwnd(), + Box::new(move |event| { + if let Ok(e) = event.map_nonuser_event() { + file_drop_runner.send_event(e) + } + }), + ) + .into(); + + assert!(RegisterDragDrop(win.hwnd(), &file_drop_handler).is_ok()); + Some(file_drop_handler) + } else { + None + }; + + self.event_loop.runner_shared.register_window(win.hwnd()); + + WindowData { + window_state: win.window_state.clone(), + event_loop_runner: self.event_loop.runner_shared.clone(), + key_event_builder: KeyEventBuilder::default(), + _file_drop_handler: file_drop_handler, + userdata_removed: Cell::new(false), + recurse_depth: Cell::new(0), + event_loop_preferred_theme: self.event_loop.preferred_theme.clone(), + } + } + + // Returns a pointer to window user data on success. + // The user data will be registered for the window and can be accessed within the window event + // callback. + pub unsafe fn on_nccreate(&mut self, window: HWND) -> Option { + let runner = self.event_loop.runner_shared.clone(); + let (win, userdata) = runner.catch_unwind(|| { + let window = unsafe { self.create_window(window) }; + let window_data = unsafe { self.create_window_data(&window) }; + (window, window_data) + })?; + + self.window = Some(win); + let userdata = Box::into_raw(Box::new(userdata)); + Some(userdata as _) + } + + pub unsafe fn on_create(&mut self) { + let win = self.window.as_mut().expect("failed window creation"); + + // making the window transparent + if self.attributes.transparent && !self.pl_attribs.no_redirection_bitmap { + // Empty region for the blur effect, so the window is fully transparent + let region = CreateRectRgn(0, 0, -1, -1); + + let bb = DWM_BLURBEHIND { + dwFlags: DWM_BB_ENABLE | DWM_BB_BLURREGION, + fEnable: true.into(), + hRgnBlur: region, + fTransitionOnMaximized: false.into(), + }; + + let _ = DwmEnableBlurBehindWindow(win.hwnd(), &bb); + let _ = DeleteObject(region.into()); + } + + if self.pl_attribs.skip_taskbar { + let _ = win.set_skip_taskbar(true); + } + win.set_window_icon(self.attributes.window_icon.clone()); + win.set_taskbar_icon(self.pl_attribs.taskbar_icon.clone()); + + if self.attributes.content_protection { + win.set_content_protection(true); } + + win.set_visible(self.attributes.visible); + win.set_closable(self.attributes.closable); } } @@ -1119,7 +1237,7 @@ unsafe fn init( event_loop: &EventLoopWindowTarget, ) -> Result { // registering the window class - let class_name = register_window_class(&pl_attribs.window_classname); + let class_name = register_window_class::(&pl_attribs.window_classname); let mut window_flags = WindowFlags::empty(); window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations); @@ -1166,202 +1284,154 @@ unsafe fn init( } }; - // creating the real window this time, by using the functions in `extra_functions` - let real_window = { - let (style, ex_style) = window_flags.to_window_styles(); - let title = util::encode_wide(&attributes.title); - - let (target_monitor, position) = attributes - .position - .and_then(|p| { - monitor::available_monitors() - .into_iter() - .find_map(|monitor| { - let dpi = monitor.dpi(); - let scale_factor = dpi_to_scale_factor(dpi); - let position = p.to_physical::(scale_factor); - let (x, y): (i32, i32) = monitor.position().into(); - let (width, height): (i32, i32) = monitor.size().into(); - - let frame_thickness = if window_flags.contains_shadow() { - util::get_frame_thickness(dpi) - } else { - 0 - }; - - // Only the starting position x needs to be accounted - if x <= position.x + frame_thickness - && position.x <= x + width - && y <= position.y - && position.y <= y + height - { - Some((monitor, position.into())) - } else { - None - } - }) - }) - .unwrap_or_else(|| (monitor::primary_monitor(), (CW_USEDEFAULT, CW_USEDEFAULT))); - - let desired_size = attributes - .inner_size - .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); - let clamped_size = attributes - .inner_size_constraints - .clamp(desired_size, target_monitor.scale_factor()); - - // Best effort: try to create the window with the requested inner size - let adjusted_size = { - let (mut w, mut h): (i32, i32) = clamped_size - .to_physical::(target_monitor.scale_factor()) - .into(); - - if window_flags.contains(WindowFlags::MARKER_DECORATIONS) { - let mut rect = RECT { - left: 0, - top: 0, - right: w, - bottom: h, - }; + let (style, ex_style) = window_flags.to_window_styles(); + let title = util::encode_wide(&attributes.title); + + let (target_monitor, position) = attributes + .position + .and_then(|p| { + monitor::available_monitors() + .into_iter() + .find_map(|monitor| { + let dpi = monitor.dpi(); + let scale_factor = dpi_to_scale_factor(dpi); + let position = p.to_physical::(scale_factor); + let (x, y): (i32, i32) = monitor.position().into(); + let (width, height): (i32, i32) = monitor.size().into(); + + let frame_thickness = if window_flags.contains_shadow() { + util::get_frame_thickness(dpi) + } else { + 0 + }; - unsafe { - AdjustWindowRectEx( - &mut rect, - window_flags.to_adjusted_window_styles().0, - pl_attribs.menu.is_some(), - ex_style, - )?; - } + // Only the starting position x needs to be accounted + if x <= position.x + frame_thickness + && position.x <= x + width + && y <= position.y + && position.y <= y + height + { + Some((monitor, position.into())) + } else { + None + } + }) + }) + .unwrap_or_else(|| (monitor::primary_monitor(), (CW_USEDEFAULT, CW_USEDEFAULT))); + + let desired_size = attributes + .inner_size + .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); + let clamped_size = attributes + .inner_size_constraints + .clamp(desired_size, target_monitor.scale_factor()); + + // Best effort: try to create the window with the requested inner size + let adjusted_size = { + let (mut w, mut h): (i32, i32) = clamped_size + .to_physical::(target_monitor.scale_factor()) + .into(); + + if window_flags.contains(WindowFlags::MARKER_DECORATIONS) { + let mut rect = RECT { + left: 0, + top: 0, + right: w, + bottom: h, + }; - w = rect.right - rect.left; - h = rect.bottom - rect.top; - } else if window_flags.undecorated_with_shadows() { - let dpi = target_monitor.dpi(); - let insets = calculate_insets_for_dpi(dpi); - w += insets.left + insets.right; - h += insets.top + insets.bottom; + unsafe { + AdjustWindowRectEx( + &mut rect, + window_flags.to_adjusted_window_styles().0, + pl_attribs.menu.is_some(), + ex_style, + )?; } - (w, h) - }; - - let handle = CreateWindowExW( - ex_style, - PCWSTR::from_raw(class_name.as_ptr()), - PCWSTR::from_raw(title.as_ptr()), - style, - position.0, - position.1, - adjusted_size.0, - adjusted_size.1, - parent, - pl_attribs.menu, - GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(), - Some(Box::into_raw(Box::new(window_flags)) as _), - )?; - - if !IsWindow(Some(handle)).as_bool() { - return Err(os_error!(OsError::IoError(io::Error::last_os_error()))); + w = rect.right - rect.left; + h = rect.bottom - rect.top; + } else if window_flags.undecorated_with_shadows() { + let dpi = target_monitor.dpi(); + let insets = calculate_insets_for_dpi(dpi); + w += insets.left + insets.right; + h += insets.top + insets.bottom; } - super::dark_mode::allow_dark_mode_for_window(handle, true); - - WindowWrapper(handle) + (w, h) }; - // Register for touch events if applicable - { - let digitizer = GetSystemMetrics(SM_DIGITIZER) as u32; - if digitizer & NID_READY != 0 { - RegisterTouchWindow(real_window.0, TWF_WANTPALM)?; - } - } - - let dpi = hwnd_dpi(real_window.0); - let scale_factor = dpi_to_scale_factor(dpi); - - // making the window transparent - if attributes.transparent && !pl_attribs.no_redirection_bitmap { - // Empty region for the blur effect, so the window is fully transparent - let region = CreateRectRgn(0, 0, -1, -1); - - let bb = DWM_BLURBEHIND { - dwFlags: DWM_BB_ENABLE | DWM_BB_BLURREGION, - fEnable: true.into(), - hRgnBlur: region, - fTransitionOnMaximized: false.into(), - }; - - let _ = DwmEnableBlurBehindWindow(real_window.0, &bb); - let _ = DeleteObject(region.into()); - } + let fullscreen = attributes.fullscreen.clone(); + let maximized = attributes.maximized; + let focused = attributes.focused; + let menu = pl_attribs.menu; + + let mut initdata = InitData { + event_loop, + attributes, + pl_attribs, + window_flags, + window: None, + }; - // If the system theme is dark, we need to set the window theme now - // before we update the window flags (and possibly show the - // window for the first time). - let current_theme = try_window_theme( - real_window.0, - attributes - .preferred_theme - .or(*event_loop.preferred_theme.lock()), - false, + let result = CreateWindowExW( + ex_style, + PCWSTR::from_raw(class_name.as_ptr()), + PCWSTR::from_raw(title.as_ptr()), + style, + position.0, + position.1, + adjusted_size.0, + adjusted_size.1, + parent, + menu, + Some(util::get_instance_handle()), + Some(&mut initdata as *mut _ as *mut _), ); - let window_state = { - let window_state = WindowState::new( - &attributes, - None, - scale_factor, - current_theme, - attributes.preferred_theme, - attributes.background_color, - ); - let window_state = Arc::new(Mutex::new(window_state)); - WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags); - window_state - }; + // If the window creation in `InitData` panicked, then should resume panicking here + if let Err(panic_error) = event_loop.runner_shared.take_panic_error() { + panic::resume_unwind(panic_error) + } - let win = Window { - window: real_window, - window_state, - thread_executor: event_loop.create_thread_executor(), - }; + let handle = result?; - KEY_EVENT_BUILDERS - .lock() - .insert(win.id(), KeyEventBuilder::default()); + super::dark_mode::allow_dark_mode_for_window(handle, true); - let _ = win.set_skip_taskbar(pl_attribs.skip_taskbar); - win.set_window_icon(attributes.window_icon); - win.set_taskbar_icon(pl_attribs.taskbar_icon); + // If the handle is non-null, then window creation must have succeeded, which means + // that we *must* have populated the `InitData.window` field. + let window = initdata.window.unwrap(); - if attributes.fullscreen.is_some() { - win.set_fullscreen(attributes.fullscreen); - force_window_active(win.window.0); - } else if attributes.maximized { - win.set_maximized(true); + // Need to set FULLSCREEN or MAXIMIZED after CreateWindowEx + // This is because if the size is changed in WM_CREATE, the restored size will be stored in that + // size. + if fullscreen.is_some() { + window.set_fullscreen(fullscreen); + force_window_active(window.hwnd()); + } else if maximized { + window.set_maximized(true); } - if attributes.content_protection { - win.set_content_protection(true); + if !focused { + window + .window_state + .lock() + .set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_DONT_FOCUS)); } - win.set_visible(attributes.visible); - win.set_closable(attributes.closable); - - Ok(win) + Ok(window) } -unsafe fn register_window_class(window_classname: &str) -> Vec { +unsafe fn register_window_class(window_classname: &str) -> Vec { let class_name = util::encode_wide(window_classname); let class = WNDCLASSEXW { cbSize: mem::size_of::() as u32, style: CS_HREDRAW | CS_VREDRAW | CS_OWNDC, - lpfnWndProc: Some(window_proc), + lpfnWndProc: Some(super::event_loop::public_window_callback::), cbClsExtra: 0, cbWndExtra: 0, - hInstance: HINSTANCE(GetModuleHandleW(PCWSTR::null()).unwrap_or_default().0), + hInstance: util::get_instance_handle(), hIcon: HICON::default(), hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly hbrBackground: HBRUSH::default(), @@ -1379,62 +1449,6 @@ unsafe fn register_window_class(window_classname: &str) -> Vec { class_name } -unsafe extern "system" fn window_proc( - window: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - // This window procedure is only needed until the subclass procedure is attached. - // we need this because we need to respond to WM_NCCALCSIZE as soon as possible - // in order to make the window borderless if needed. - match msg { - win32wm::WM_NCCALCSIZE => { - let userdata = util::GetWindowLongPtrW(window, GWL_USERDATA); - if userdata != 0 { - let window_flags = WindowFlags::from_bits_truncate(userdata as _); - - if wparam == WPARAM(0) || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { - return DefWindowProcW(window, msg, wparam, lparam); - } - - // adjust the maximized borderless window so it doesn't cover the taskbar - if util::is_maximized(window).unwrap_or(false) { - let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS); - if let Ok(monitor_info) = - monitor::get_monitor_info(MonitorFromRect(¶ms.rgrc[0], MONITOR_DEFAULTTONULL)) - { - params.rgrc[0] = monitor_info.monitorInfo.rcWork; - } - } else if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) { - let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS); - - let insets = util::calculate_window_insets(window); - - params.rgrc[0].left += insets.left; - params.rgrc[0].top += insets.top; - params.rgrc[0].right -= insets.right; - params.rgrc[0].bottom -= insets.bottom; - } - return LRESULT(0); // return 0 here to make the window borderless - } - - DefWindowProcW(window, msg, wparam, lparam) - } - win32wm::WM_NCCREATE => { - let userdata = util::GetWindowLongPtrW(window, GWL_USERDATA); - if userdata == 0 { - let createstruct = &*(lparam.0 as *const CREATESTRUCTW); - let userdata = createstruct.lpCreateParams; - let window_flags = Box::from_raw(userdata as *mut WindowFlags); - util::SetWindowLongPtrW(window, GWL_USERDATA, window_flags.bits() as _); - } - DefWindowProcW(window, msg, wparam, lparam) - } - _ => DefWindowProcW(window, msg, wparam, lparam), - } -} - struct ComInitialized(Option<()>); impl Drop for ComInitialized { fn drop(&mut self) { diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window_state.rs similarity index 94% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window_state.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window_state.rs index 6437c80df..4595b14f9 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window_state.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window_state.rs @@ -6,7 +6,7 @@ use crate::{ dpi::PhysicalPosition, icon::Icon, keyboard::ModifiersState, - platform_impl::platform::{event_loop, minimal_ime::MinimalIme, util}, + platform_impl::platform::{event_loop, util}, window::{CursorIcon, Fullscreen, Theme, WindowAttributes, WindowSizeConstraints, RGBA}, }; use parking_lot::MutexGuard; @@ -39,8 +39,6 @@ pub struct WindowState { pub current_theme: Theme, pub preferred_theme: Option, - pub ime_handler: MinimalIme, - pub window_flags: WindowFlags, // Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS @@ -156,7 +154,6 @@ impl WindowState { fullscreen: None, current_theme, preferred_theme, - ime_handler: MinimalIme::default(), window_flags: WindowFlags::empty(), is_active: false, is_focused: false, @@ -312,30 +309,16 @@ impl WindowFlags { } /// Adjust the window client rectangle to the return value, if present. - fn apply_diff(mut self, window: HWND, mut new: WindowFlags) { - self = self.mask(); - new = new.mask(); + fn apply_diff(self, window: HWND, new: WindowFlags) { + let old = self.mask(); + let new = new.mask(); - let mut diff = self ^ new; + let mut diff = old ^ new; if diff == WindowFlags::empty() { return; } - if new.contains(WindowFlags::VISIBLE) { - unsafe { - let _ = ShowWindow( - window, - if self.contains(WindowFlags::MARKER_DONT_FOCUS) { - self.set(WindowFlags::MARKER_DONT_FOCUS, false); - SW_SHOWNOACTIVATE - } else { - SW_SHOW - }, - ); - } - } - if diff.contains(WindowFlags::ALWAYS_ON_TOP) { unsafe { let _ = SetWindowPos( @@ -374,7 +357,10 @@ impl WindowFlags { } } - if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) { + if (diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED)) + // This is to avoid the window from flashing + && !(new.contains(WindowFlags::MAXIMIZED) && !new.contains(WindowFlags::VISIBLE)) + { unsafe { let _ = ShowWindow( window, @@ -424,7 +410,11 @@ impl WindowFlags { } if diff != WindowFlags::empty() { - let (style, style_ex) = new.to_window_styles(); + let (mut style, style_ex) = new.to_window_styles(); + // Remove `WS_VISIBLE` if we were hidden, this is required for the `ShowWindow` below to work + if !old.contains(WindowFlags::VISIBLE) { + style &= !WS_VISIBLE; + } unsafe { SendMessageW( @@ -461,6 +451,21 @@ impl WindowFlags { ); } } + + // This needs to be after the `SetWindowPos` above or there will be + // a title bar flicker on undecorated windows's creation + if new.contains(WindowFlags::VISIBLE) { + unsafe { + let _ = ShowWindow( + window, + if old.contains(WindowFlags::MARKER_DONT_FOCUS) { + SW_SHOWNOACTIVATE + } else { + SW_SHOW + }, + ); + } + } } pub fn undecorated_with_shadows(&self) -> bool { diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/window.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/window.rs index 9ec5ccc49..e43c72eff 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/window.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/window.rs @@ -718,7 +718,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window in the screen space coordinate system. /// - **Android:** Always returns [`NotSupportedError`]. - /// - **Linux(Wayland)**: Has no effect, since Wayland doesn't support a global cordinate system + /// - **Linux(Wayland)**: Has no effect, since Wayland doesn't support a global coordinate system #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { self.window.outer_position() @@ -828,7 +828,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android:** Unsupported. Returns ane empty string. + /// - **iOS / Android:** Unsupported. Returns an empty string. #[inline] pub fn title(&self) -> String { self.window.title() @@ -1662,6 +1662,7 @@ pub enum ResizeDirection { West, } +#[cfg(any(target_os = "windows", target_os = "linux"))] pub(crate) fn hit_test( (left, top, right, bottom): (i32, i32, i32, i32), cx: i32, diff --git a/scripts/ci/_common.sh b/scripts/ci/_common.sh index d08601b14..1aebbcde0 100755 --- a/scripts/ci/_common.sh +++ b/scripts/ci/_common.sh @@ -1257,6 +1257,47 @@ linuxdeploy_tools_arch() { esac } +# tauri-cli runs target/.tauri/linuxdeploy--.AppImage and downloads +# an unpinned linuxdeploy when that file is missing, so the pinned wrapper is +# installed under that name. The hash is LINUXDEPLOY_COMMIT_HASH in the +# tauri-bundler of @tauri-apps/cli TAURI_LINUXDEPLOY_CLI; bumping the CLI fails +# require_tauri_linuxdeploy_hash until both are updated together. +TAURI_LINUXDEPLOY_HASH="07333c6" +TAURI_LINUXDEPLOY_CLI="2.12.3" + +tauri_linuxdeploy_wrapper() { + local arch + arch="$(linuxdeploy_tools_arch)" || return 1 + printf '%s\n' "${TAURI_DIR}/target/.tauri/linuxdeploy-${TAURI_LINUXDEPLOY_HASH}-${arch}.AppImage" +} + +# Before bundling, so an unpinned linuxdeploy never runs with the signing key in +# its environment: the installed CLI is the one the hash was read from. +require_tauri_linuxdeploy_hash() { + local manifest="${FRONTEND_DIR}/node_modules/@tauri-apps/cli/package.json" installed + installed="$(jq -er .version "${manifest}")" || { + echo "Cannot read the installed @tauri-apps/cli version from ${manifest}." >&2 + return 1 + } + if [ "${installed}" != "${TAURI_LINUXDEPLOY_CLI}" ]; then + echo "@tauri-apps/cli ${installed} may run another linuxdeploy than ${TAURI_LINUXDEPLOY_HASH}, which is pinned for ${TAURI_LINUXDEPLOY_CLI}. Set TAURI_LINUXDEPLOY_HASH in scripts/ci/_common.sh to its tauri-bundler's LINUXDEPLOY_COMMIT_HASH and TAURI_LINUXDEPLOY_CLI to ${installed}." >&2 + return 1 + fi +} + +# After bundling, as a backstop: tauri-cli fetched no linuxdeploy of its own. +verify_tauri_used_pinned_linuxdeploy() { + local arch file wrapper + arch="$(linuxdeploy_tools_arch)" || return 1 + wrapper="$(tauri_linuxdeploy_wrapper)" || return 1 + for file in "${TAURI_DIR}/target/.tauri"/linuxdeploy-*-"${arch}".AppImage; do + if [ -e "${file}" ] && [ "${file}" != "${wrapper}" ]; then + echo "tauri-cli fetched $(basename "${file}") instead of the pinned $(basename "${wrapper}"); update TAURI_LINUXDEPLOY_HASH in scripts/ci/_common.sh." >&2 + return 1 + fi + done +} + write_chmod_patchelf_wrapper() { local wrapper="$1" local real_patchelf="$2" @@ -1434,6 +1475,7 @@ prepare_tauri_linuxdeploy_tools_cache() { echo "MAPLE_NIX_TAURI_LINUXDEPLOY_TOOLS is required for reproducible AppImage bundling." >&2 return 1 fi + require_tauri_linuxdeploy_hash local arch linuxdeploy_arch tools cache internal linuxdeploy_wrapper appimage_wrapper linuxdeploy_appdir_bin plugin_bin runtime_file appimage_real_appimage appimage_appdir gtk_real_plugin gstreamer_real_plugin arch="$(linuxdeploy_tools_arch)" @@ -1441,7 +1483,7 @@ prepare_tauri_linuxdeploy_tools_cache() { tools="${MAPLE_NIX_TAURI_LINUXDEPLOY_TOOLS}" cache="${TAURI_DIR}/target/.tauri" internal="${cache}/maple-linuxdeploy-tools" - linuxdeploy_wrapper="${cache}/linuxdeploy-${linuxdeploy_arch}.AppImage" + linuxdeploy_wrapper="$(tauri_linuxdeploy_wrapper)" appimage_wrapper="${cache}/linuxdeploy-plugin-appimage.AppImage" linuxdeploy_appdir_bin="${cache}/linuxdeploy-${linuxdeploy_arch}.AppDir/usr/bin" plugin_bin="${internal}/plugins" @@ -1470,8 +1512,9 @@ prepare_tauri_linuxdeploy_tools_cache() { wrap_linuxdeploy_appdir_patchelf "${linuxdeploy_appdir_bin}" rm -f "${linuxdeploy_appdir_bin}"/linuxdeploy-plugin-* - write_linuxdeploy_input_plugin_wrapper "${cache}/linuxdeploy-plugin-gtk.sh" "maple-linuxdeploy-tools/linuxdeploy-plugin-gtk.real.sh" - write_linuxdeploy_input_plugin_wrapper "${cache}/linuxdeploy-plugin-gstreamer.sh" "maple-linuxdeploy-tools/linuxdeploy-plugin-gstreamer.real.sh" + # tauri-cli also writes its own embedded linuxdeploy-plugin-*.sh into the + # cache. The wrapper replaces PATH and unsets APPIMAGE, so linuxdeploy only + # finds the pinned plugins below. write_linuxdeploy_input_plugin_wrapper "${plugin_bin}/linuxdeploy-plugin-gtk" "../linuxdeploy-plugin-gtk.real.sh" write_linuxdeploy_input_plugin_wrapper "${plugin_bin}/linuxdeploy-plugin-gstreamer" "../linuxdeploy-plugin-gstreamer.real.sh" @@ -1809,7 +1852,7 @@ EOF print_file_hashes \ "${cache}/AppRun-${arch}" \ "${cache}/linuxdeploy-${linuxdeploy_arch}.real.AppImage" \ - "${cache}/linuxdeploy-${linuxdeploy_arch}.AppImage" \ + "${linuxdeploy_wrapper}" \ "${cache}/linuxdeploy-${linuxdeploy_arch}.AppDir/AppRun" \ "${plugin_bin}/linuxdeploy-plugin-appimage" \ "${plugin_bin}/linuxdeploy-plugin-gtk" \ @@ -1817,9 +1860,7 @@ EOF "${appimage_real_appimage}" \ "${cache}/linuxdeploy-plugin-appimage.AppImage" \ "${appimage_appdir}/AppRun" \ - "${runtime_file}" \ - "${cache}/linuxdeploy-plugin-gtk.sh" \ - "${cache}/linuxdeploy-plugin-gstreamer.sh" + "${runtime_file}" } verify_linuxdeploy_plugin_metadata() { diff --git a/scripts/ci/desktop-pr.sh b/scripts/ci/desktop-pr.sh index 9b10f1da1..300d20a76 100755 --- a/scripts/ci/desktop-pr.sh +++ b/scripts/ci/desktop-pr.sh @@ -23,7 +23,7 @@ case "$(host_os)" in prepend_linux_runtime_library_path prepare_tauri_linuxdeploy_tools_cache verify_linuxdeploy_plugin_metadata - run_with_nix_usr_bin "${TAURI_DIR}/target/.tauri/linuxdeploy-$(linuxdeploy_tools_arch).AppImage" --appimage-extract-and-run --list-plugins + run_with_nix_usr_bin "$(tauri_linuxdeploy_wrapper)" --appimage-extract-and-run --list-plugins run_with_nix_usr_bin pkg-config --modversion glib-2.0 remove_build_tree "${TAURI_DIR}/target/release/bundle/appimage" remove_build_tree "${TAURI_DIR}/target/release/bundle/deb" @@ -31,6 +31,7 @@ case "$(host_os)" in (cd "${TAURI_DIR}" && cargo build --locked --bins --features tauri/custom-protocol --release) sanitize_linux_target_release_executable run_with_nix_usr_bin bun tauri build --verbose --no-sign --config "$(linux_tauri_pr_config)" + verify_tauri_used_pinned_linuxdeploy restore_linux_runtime_library_path normalize_linux_desktop_packages diff --git a/scripts/ci/desktop-release.sh b/scripts/ci/desktop-release.sh index b20a0d97c..4cbf2285c 100755 --- a/scripts/ci/desktop-release.sh +++ b/scripts/ci/desktop-release.sh @@ -41,7 +41,7 @@ case "$(host_os)" in prepend_linux_runtime_library_path prepare_tauri_linuxdeploy_tools_cache verify_linuxdeploy_plugin_metadata - run_with_nix_usr_bin "${TAURI_DIR}/target/.tauri/linuxdeploy-$(linuxdeploy_tools_arch).AppImage" --appimage-extract-and-run --list-plugins + run_with_nix_usr_bin "$(tauri_linuxdeploy_wrapper)" --appimage-extract-and-run --list-plugins remove_build_tree "${TAURI_DIR}/target/release/bundle/appimage" remove_build_tree "${TAURI_DIR}/target/release/bundle/deb" @@ -52,6 +52,7 @@ case "$(host_os)" in (cd "${TAURI_DIR}" && cargo build --locked --bins --features tauri/custom-protocol --release) sanitize_linux_target_release_executable run_with_nix_usr_bin bun tauri build --verbose --config "${release_config}" + verify_tauri_used_pinned_linuxdeploy restore_linux_runtime_library_path normalize_linux_desktop_packages diff --git a/scripts/ci/test_linuxdeploy_pin.py b/scripts/ci/test_linuxdeploy_pin.py new file mode 100644 index 000000000..a846f011c --- /dev/null +++ b/scripts/ci/test_linuxdeploy_pin.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +"""The Linux AppImage build runs only the pinned linuxdeploy that tauri-cli expects.""" + +import json +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[2] +FRONTEND = ROOT / "apps/maple-research/frontend" + + +class LinuxdeployPinTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.tools = self.root / "tauri/target/.tauri" + self.tools.mkdir(parents=True) + self.cli = self.root / "frontend/node_modules/@tauri-apps/cli/package.json" + self.cli.parent.mkdir(parents=True) + + def run_shell(self, commands): + return subprocess.run( + ["bash", "-c", "source scripts/ci/_common.sh\n" + 'TAURI_DIR="$FIXTURE_ROOT/tauri"\n' + 'FRONTEND_DIR="$FIXTURE_ROOT/frontend"\n' + "linuxdeploy_tools_arch() { echo x86_64; }\n" + commands], + cwd=ROOT, env=dict(os.environ, FIXTURE_ROOT=str(self.root)), capture_output=True, text=True) + + def pinned(self, name): + return self.run_shell(f'printf "%s" "${name}"').stdout + + def test_the_pin_names_the_cli_this_research_app_installs(self): + package = json.loads((FRONTEND / "package.json").read_text()) + self.assertEqual(package["devDependencies"]["@tauri-apps/cli"], self.pinned("TAURI_LINUXDEPLOY_CLI")) + self.assertRegex(self.pinned("TAURI_LINUXDEPLOY_HASH"), r"\A[0-9a-f]{7,}\Z") + + def test_bundling_needs_the_cli_the_hash_was_read_from(self): + self.cli.write_text(json.dumps({"version": self.pinned("TAURI_LINUXDEPLOY_CLI")})) + result = self.run_shell("require_tauri_linuxdeploy_hash") + self.assertEqual(result.returncode, 0, result.stderr) + self.cli.write_text(json.dumps({"version": "9.9.9"})) + result = self.run_shell("require_tauri_linuxdeploy_hash") + self.assertNotEqual(result.returncode, 0) + self.assertIn("TAURI_LINUXDEPLOY_HASH", result.stderr) + self.cli.unlink() + self.assertNotEqual(self.run_shell("require_tauri_linuxdeploy_hash").returncode, 0) + + def test_a_linuxdeploy_fetched_by_tauri_fails_the_build(self): + wrapper = f"linuxdeploy-{self.pinned('TAURI_LINUXDEPLOY_HASH')}-x86_64.AppImage" + # Maple's own tools: the wrapper, the real AppImage, the AppImage plugin wrapper + # and the unhashed name earlier tauri-cli versions used. + for name in (wrapper, "linuxdeploy-x86_64.real.AppImage", "linuxdeploy-plugin-appimage.AppImage", + "linuxdeploy-x86_64.AppImage"): + (self.tools / name).write_text("fixture") + result = self.run_shell("verify_tauri_used_pinned_linuxdeploy") + self.assertEqual(result.returncode, 0, result.stderr) + (self.tools / "linuxdeploy-1234567-x86_64.AppImage").write_text("downloaded") + result = self.run_shell("verify_tauri_used_pinned_linuxdeploy") + self.assertNotEqual(result.returncode, 0) + self.assertIn("linuxdeploy-1234567-x86_64.AppImage", result.stderr) + + +if __name__ == "__main__": + unittest.main()