From 5e41e4ac4ecb90d6a36129e0774ee5bc3e615c7e Mon Sep 17 00:00:00 2001 From: Andy Goetz Date: Sun, 16 Jul 2023 21:28:12 -0700 Subject: [PATCH] Add support for icons (#20) Icon rendering must use platform-specific APIs which means it must be duplicated for every supported OS. Icon lookup is delegated to a separate thread in order to prevent the system functions from blocking the runtime. This is because if a Jolly entry location is on a network drive, the icon lookup APIs can hang for a long period of time. Buildscript checks that necessary packages are installed and gives a warning if they are not --- .github/workflows/ci.yml | 4 + .github/workflows/release.yml | 5 + Cargo.lock | 1505 ++++++++++++++++++++++++--------- Cargo.toml | 37 +- README.md | 21 +- build.rs | 52 +- docs/config.md | 36 +- src/entry.rs | 251 ++++-- src/icon/linux_and_friends.rs | 336 ++++++++ src/icon/macos.rs | 182 ++++ src/icon/mod.rs | 616 ++++++++++++++ src/icon/windows.rs | 726 ++++++++++++++++ src/lib.rs | 135 ++- src/platform.rs | 8 +- src/search_results.rs | 26 +- src/store.rs | 26 +- src/ui.rs | 4 +- 17 files changed, 3437 insertions(+), 533 deletions(-) create mode 100644 src/icon/linux_and_friends.rs create mode 100644 src/icon/macos.rs create mode 100644 src/icon/mod.rs create mode 100644 src/icon/windows.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 182eebd..9e6ac61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,10 @@ jobs: with: toolchain: ${{ env.MSRV }} + - name: Install dependencies + run: sudo apt-get install -y --no-install-recommends shared-mime-info xdg-utils gnome-icon-theme + if: startsWith(matrix.os, 'ubuntu-') + - name: Build run: cargo build --verbose - name: Run tests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fc6ee76..803d0c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Install dependencies + run: sudo apt-get install -y --no-install-recommends shared-mime-info xdg-utils gnome-icon-theme + if: startsWith(matrix.os, 'ubuntu-') + + - name: Build release binary run: cargo build --verbose --release diff --git a/Cargo.lock b/Cargo.lock index 9c927af..17b44ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe21446ad43aa56417a767f3e2f3d7c4ca522904de1dd640529a76e9c5c3b33c" +checksum = "5110f1c78cf582855d895ecd0746b653db010cec6d9f5575293f27934d980a39" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -46,9 +46,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -73,9 +73,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" @@ -85,17 +85,17 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "8868f09ff8cea88b079da74ae569d9b8c62a23c68c746240b704ee6f7525c89c" [[package]] name = "ash" -version = "0.37.2+1.3.238" +version = "0.37.3+1.3.251" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28bf19c1f0a470be5fbf7522a308a05df06610252c5bcf5143e1b23f629a9a03" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" dependencies = [ - "libloading", + "libloading 0.7.4", ] [[package]] @@ -110,9 +110,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ "async-lock", "async-task", @@ -124,60 +124,59 @@ dependencies = [ [[package]] name = "async-io" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg", + "cfg-if", "concurrent-queue", "futures-lite", - "libc", "log", "parking", "polling", + "rustix", "slab", "socket2", "waker-fn", - "windows-sys 0.42.0", ] [[package]] name = "async-lock" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" dependencies = [ "event-listener", - "futures-lite", ] [[package]] name = "async-recursion" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "async-task" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -196,16 +195,16 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.6.2", "object", "rustc-demangle", ] [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bit-set" @@ -222,6 +221,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -236,18 +241,18 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bstr" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" +checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" dependencies = [ "memchr", "once_cell", @@ -257,28 +262,28 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytemuck" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" +checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -289,10 +294,11 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "calloop" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" dependencies = [ + "bitflags", "log", "nix 0.25.1", "slotmap", @@ -355,9 +361,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.49" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ "cc", ] @@ -380,9 +386,9 @@ dependencies = [ [[package]] name = "cocoa-foundation" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" dependencies = [ "bitflags", "block", @@ -417,18 +423,18 @@ checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" [[package]] name = "concurrent-queue" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] [[package]] name = "const_panic" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58baae561b85ca19b3122a9ddd35c8ec40c3bcd14fe89921824eae73f7baffbf" +checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" [[package]] name = "core-foundation" @@ -442,9 +448,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core-graphics" @@ -485,9 +491,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -503,9 +509,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -513,9 +519,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -524,22 +530,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.7.1", + "memoffset 0.9.0", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -567,6 +573,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -600,7 +612,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" dependencies = [ "bitflags", - "libloading", + "libloading 0.7.4", "winapi", ] @@ -612,7 +624,7 @@ checksum = "a62007a65515b3cd88c733dd3464431f05d2ad066999a824259d8edc3cf6f645" dependencies = [ "dconf_rs", "detect-desktop-environment", - "dirs", + "dirs 4.0.0", "objc", "rust-ini", "web-sys", @@ -642,7 +654,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.109", ] [[package]] @@ -653,7 +665,7 @@ checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -676,7 +688,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -687,9 +699,9 @@ checksum = "21d8ad60dd5b13a4ee6bd8fa2d5d88965c597c67bce32b5fc49c94f55cb50810" [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -701,7 +713,26 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", ] [[package]] @@ -715,6 +746,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -723,11 +777,11 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dlib" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading", + "libloading 0.8.0", ] [[package]] @@ -791,14 +845,14 @@ checksum = "0f2f4de457d974f548d2c2a16f709ebd81013579e543bd1a9b19ced88132c2cf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "enumflags2" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" dependencies = [ "enumflags2_derive", "serde", @@ -806,13 +860,34 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", ] [[package]] @@ -827,9 +902,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.7" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b52c2ef4a78da0ba68fbe1fd920627411096d2ac478f7f4c9f3a54ba6705bade" +checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" dependencies = [ "num-traits", ] @@ -850,6 +925,22 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "exr" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279d3efcc55e19917fff7ab3ddd6c14afb6a90881a0078465196fe2f99d08c56" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide 0.7.1", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -859,6 +950,15 @@ dependencies = [ "instant", ] +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + [[package]] name = "find-crate" version = "0.6.3" @@ -870,12 +970,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.7.1", ] [[package]] @@ -884,6 +984,19 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -907,8 +1020,22 @@ checksum = "ff20bef7942a72af07104346154a70a70b089c572e454b41bef6eb6cb10e9c06" dependencies = [ "fontconfig-parser", "log", - "memmap2", - "ttf-parser", + "memmap2 0.5.10", + "ttf-parser 0.18.1", +] + +[[package]] +name = "fontdb" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8d8cbea8f21307d7e84bca254772981296f058a1d36b461bf4d83a7499fc9e" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2 0.6.2", + "slotmap", + "tinyvec", + "ttf-parser 0.19.0", ] [[package]] @@ -932,13 +1059,13 @@ dependencies = [ [[package]] name = "foreign-types-macros" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8469d0d40519bc608ec6863f1cc88f3f1deee15913f2f3b3e573d81ed38cccc" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -953,6 +1080,28 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freedesktop-icons" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e61ac115df4632b592d36b71fda3c259f4c8061c70b7fa429bac145890e880" +dependencies = [ + "dirs 4.0.0", + "once_cell", + "rust-ini", + "thiserror", + "xdg", +] + [[package]] name = "freetype-rs" version = "0.26.0" @@ -977,9 +1126,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -992,9 +1141,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -1002,15 +1151,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -1020,15 +1169,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -1041,32 +1190,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -1091,9 +1240,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1120,13 +1269,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1151,11 +1302,29 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "glow" -version = "0.12.1" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glow" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" +checksum = "807edf58b70c0b5b2181dd39fe1839dbdb3ba02645630dc5f753e23da307f762" dependencies = [ "js-sys", "slotmap", @@ -1163,11 +1332,23 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glow_glyph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4e62c64947b9a24fe20e2bba9ad819ecb506ef5c8df7ffc4737464c6df9510" +dependencies = [ + "bytemuck", + "glow 0.11.2", + "glyph_brush", + "log", +] + [[package]] name = "glyph_brush" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b773a724cb29f6119043a46bd2fc6bfedce59ab9898a3c07df66082acbea0ca" +checksum = "4edefd123f28a0b1d41ec4a489c2b43020b369180800977801611084f342978d" dependencies = [ "glyph_brush_draw_cache", "glyph_brush_layout", @@ -1203,9 +1384,9 @@ dependencies = [ [[package]] name = "gpu-alloc" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" +checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" dependencies = [ "bitflags", "gpu-alloc-types", @@ -1230,7 +1411,7 @@ dependencies = [ "log", "thiserror", "winapi", - "windows", + "windows 0.44.0", ] [[package]] @@ -1263,6 +1444,15 @@ dependencies = [ "svg_fmt", ] +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1281,7 +1471,7 @@ dependencies = [ "bitflags", "com-rs", "libc", - "libloading", + "libloading 0.7.4", "thiserror", "widestring", "winapi", @@ -1296,6 +1486,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -1308,6 +1504,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "iced" version = "0.9.0" @@ -1316,10 +1521,12 @@ checksum = "efbddf356d01e9d41cd394a9d04d62bfd89650a30f12fda5839cabb8c4591c88" dependencies = [ "iced_core", "iced_futures", + "iced_glow", "iced_graphics", "iced_native", "iced_wgpu", "iced_winit", + "image", "thiserror", ] @@ -1346,6 +1553,22 @@ dependencies = [ "wasm-timer", ] +[[package]] +name = "iced_glow" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc5b081015f5c75777c96ad75e2288916e7d444c97396d6d136517877ef9129" +dependencies = [ + "bytemuck", + "euclid", + "glow 0.11.2", + "glow_glyph", + "glyph_brush", + "iced_graphics", + "iced_native", + "log", +] + [[package]] name = "iced_graphics" version = "0.8.0" @@ -1357,8 +1580,10 @@ dependencies = [ "glam", "iced_native", "iced_style", + "image", + "kamadak-exif", "log", - "raw-window-handle 0.5.0", + "raw-window-handle 0.5.2", "thiserror", ] @@ -1404,7 +1629,7 @@ dependencies = [ "iced_graphics", "iced_native", "log", - "raw-window-handle 0.5.0", + "raw-window-handle 0.5.2", "wgpu", "wgpu_glyph", ] @@ -1442,17 +1667,52 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "qoi", + "tiff", +] + [[package]] name = "imagesize" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf" +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -1470,6 +1730,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "jni-sys" version = "0.3.0" @@ -1480,24 +1751,32 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" name = "jolly" version = "0.2.0" dependencies = [ + "core-foundation", + "core-graphics", "csscolorparser", "dark-light", - "dirs", + "dirs 5.0.1", + "freedesktop-icons", "iced", "iced_native", "ico", "lazy_static", + "objc", + "once_cell", "opener", "pulldown-cmark", - "resvg", + "resvg 0.29.0", + "resvg 0.34.1", "serde", "tempfile", - "tiny-skia 0.8.3", - "toml 0.7.2", + "tiny-skia 0.8.4", + "toml 0.7.4", + "url", "urlencoding", - "usvg", - "windows", + "usvg 0.29.0", + "windows 0.48.0", "winres", + "xdg-mime", ] [[package]] @@ -1505,16 +1784,28 @@ name = "jpeg-decoder" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kamadak-exif" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" +dependencies = [ + "mutate_once", +] + [[package]] name = "khronos-egl" version = "4.1.0" @@ -1522,7 +1813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" dependencies = [ "libc", - "libloading", + "libloading 0.7.4", "pkg-config", ] @@ -1532,16 +1823,16 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.3", ] [[package]] name = "kurbo" -version = "0.9.0" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e119590a03caff1f7a582e8ee8c2164ddcc975791701188132fd1d1b518d3871" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.3", ] [[package]] @@ -1550,11 +1841,30 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec 0.5.2", + "bitflags", + "cfg-if", + "ryu", + "static_assertions", +] + [[package]] name = "libc" -version = "0.2.139" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "libloading" @@ -1567,16 +1877,32 @@ dependencies = [ ] [[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "libloading" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -1584,12 +1910,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "malloc_buf" @@ -1608,9 +1931,18 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.9" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af2c65375e552a67fe3829ca63e8a7c27a378a62824594f43b2851d682b5ec2" +checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" dependencies = [ "libc", ] @@ -1626,9 +1958,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -1647,6 +1979,12 @@ dependencies = [ "objc", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1662,18 +2000,34 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] +[[package]] +name = "mutate_once" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" + [[package]] name = "naga" version = "0.11.1" @@ -1694,6 +2048,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "ndk" version = "0.7.0" @@ -1704,7 +2067,7 @@ dependencies = [ "jni-sys", "ndk-sys", "num_enum", - "raw-window-handle 0.5.0", + "raw-window-handle 0.5.2", "thiserror", ] @@ -1740,7 +2103,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1791,6 +2154,17 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "nom" +version = "5.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "nom" version = "7.1.3" @@ -1802,12 +2176,24 @@ dependencies = [ ] [[package]] -name = "nom8" -version = "0.2.0" +name = "num-integer" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ - "memchr", + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", ] [[package]] @@ -1825,29 +2211,29 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "num_enum" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0072973714303aa6e3631c7e8e777970cf4bdd25dc4932e41031027b8bcc4e" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0629cbd6b897944899b1f10496d9c4a7ac5878d45fd61bc22e9e79bfbbc29597" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1891,18 +2277,18 @@ dependencies = [ [[package]] name = "object" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opener" @@ -1914,11 +2300,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" -version = "3.4.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf" +checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213" dependencies = [ "num-traits", ] @@ -1945,11 +2337,11 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25e9fb15717794fae58ab55c26e044103aad13186fbb625893f9a3bbcc24228" +checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" dependencies = [ - "ttf-parser", + "ttf-parser 0.19.0", ] [[package]] @@ -1973,14 +2365,14 @@ dependencies = [ "find-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -2000,7 +2392,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -2012,29 +2404,29 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.0", ] [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "phf" @@ -2066,7 +2458,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2084,6 +2476,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -2098,34 +2510,37 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "png" -version = "0.17.7" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" dependencies = [ "bitflags", "crc32fast", + "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.7.1", ] [[package]] name = "polling" -version = "2.5.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", + "bitflags", "cfg-if", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "windows-sys 0.42.0", + "pin-project-lite", + "windows-sys 0.48.0", ] [[package]] @@ -2136,28 +2551,28 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit 0.18.1", + "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74605f360ce573babfe43964cbe520294dcb081afbf8c108fc6e23036b4da2df" +checksum = "332cd62e95873ea4f41f3dfd6bbbfc5b52aec892d7e8d534197c4720a0bbbab2" [[package]] name = "pulldown-cmark" @@ -2171,11 +2586,20 @@ dependencies = [ "unicase", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quote" -version = "1.0.23" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -2237,18 +2661,15 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" -dependencies = [ - "cty", -] +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "rayon" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", @@ -2256,9 +2677,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.2" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -2281,6 +2702,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -2288,15 +2718,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", @@ -2311,18 +2741,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "renderdoc-sys" @@ -2344,9 +2765,26 @@ dependencies = [ "rgb", "svgfilters", "svgtypes 0.10.0", - "tiny-skia 0.8.3", - "usvg", - "usvg-text-layout", + "tiny-skia 0.8.4", + "usvg 0.29.0", + "usvg-text-layout 0.29.0", +] + +[[package]] +name = "resvg" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0e3d65cea36eefb28a020edb6e66341764e00cd4b426e0c1f0599b1adaa78f5" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "png", + "rgb", + "svgtypes 0.11.0", + "tiny-skia 0.10.0", + "usvg 0.34.1", ] [[package]] @@ -2402,6 +2840,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustix" +version = "0.37.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "rustybuzz" version = "0.7.0" @@ -2411,13 +2863,19 @@ dependencies = [ "bitflags", "bytemuck", "smallvec", - "ttf-parser", + "ttf-parser 0.18.1", "unicode-bidi-mirroring", "unicode-ccc", "unicode-general-category", "unicode-script", ] +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + [[package]] name = "safe_arch" version = "0.5.2" @@ -2453,40 +2911,40 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.152" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "serde_repr" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "serde_spanned" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" dependencies = [ "serde", ] @@ -2523,6 +2981,12 @@ dependencies = [ "digest", ] +[[package]] +name = "simd-adler32" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" + [[package]] name = "simplecss" version = "0.2.1" @@ -2573,7 +3037,7 @@ dependencies = [ "dlib", "lazy_static", "log", - "memmap2", + "memmap2 0.5.10", "nix 0.24.3", "pkg-config", "wayland-client", @@ -2593,14 +3057,23 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spirv" version = "0.2.0+1.5.4" @@ -2625,9 +3098,9 @@ checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" [[package]] name = "strict-num" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df65f20698aeed245efdde3628a6b559ea1239bbb871af1b6e3b58c413b2bd1" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" dependencies = [ "float-cmp", ] @@ -2670,15 +3143,36 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ffacedcdcf1da6579c907279b4f3c5492fbce99fbbf227f5ed270a589c2765" dependencies = [ - "kurbo 0.9.0", + "kurbo 0.9.5", + "siphasher", +] + +[[package]] +name = "svgtypes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4b0611e7f3277f68c0fa18e385d9e2d26923691379690039548f867cef02a7" +dependencies = [ + "kurbo 0.9.5", "siphasher", ] [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -2687,16 +3181,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -2710,22 +3204,33 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", +] + +[[package]] +name = "tiff" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", ] [[package]] @@ -2745,16 +3250,31 @@ dependencies = [ [[package]] name = "tiny-skia" -version = "0.8.3" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +dependencies = [ + "arrayref", + "arrayvec 0.7.3", + "bytemuck", + "cfg-if", + "png", + "tiny-skia-path 0.8.4", +] + +[[package]] +name = "tiny-skia" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfef3412c6975196fdfac41ef232f910be2bb37b9dd3313a49a1a6bc815a5bdb" +checksum = "7db11798945fa5c3e5490c794ccca7c6de86d3afdd54b4eb324109939c6f37bc" dependencies = [ "arrayref", - "arrayvec 0.7.2", + "arrayvec 0.7.3", "bytemuck", "cfg-if", + "log", "png", - "tiny-skia-path 0.8.3", + "tiny-skia-path 0.10.0", ] [[package]] @@ -2769,9 +3289,9 @@ dependencies = [ [[package]] name = "tiny-skia-path" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b5edac058fc98f51c935daea4d805b695b38e2f151241cad125ade2a2ac20d" +checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" dependencies = [ "arrayref", "bytemuck", @@ -2779,63 +3299,72 @@ dependencies = [ ] [[package]] -name = "toml" -version = "0.5.11" +name = "tiny-skia-path" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "2f60aa35c89ac2687ace1a2556eaaea68e8c0d47408a2e3e7f5c98a489e7281c" dependencies = [ - "serde", + "arrayref", + "bytemuck", + "strict-num", ] [[package]] -name = "toml" -version = "0.7.2" +name = "tinyvec" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime 0.6.1", - "toml_edit 0.19.4", + "tinyvec_macros", ] [[package]] -name = "toml_datetime" -version = "0.5.1" +name = "tinyvec_macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "toml_datetime" -version = "0.6.1" +name = "toml" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] -name = "toml_edit" -version = "0.18.1" +name = "toml" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" dependencies = [ "indexmap", - "nom8", - "toml_datetime 0.5.1", + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", ] [[package]] name = "toml_edit" -version = "0.19.4" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ "indexmap", "serde", "serde_spanned", - "toml_datetime 0.6.1", + "toml_datetime", "winnow", ] @@ -2853,20 +3382,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] @@ -2877,6 +3406,12 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" +[[package]] +name = "ttf-parser" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dcf002ae3b32cd25400d6df128c5babec3927cd1eb7ce813cfff20eb6c3746" + [[package]] name = "twox-hash" version = "1.6.3" @@ -2915,9 +3450,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-bidi-mirroring" @@ -2939,9 +3474,18 @@ checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] [[package]] name = "unicode-script" @@ -2973,6 +3517,17 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "urlencoding" version = "2.1.2" @@ -2988,28 +3543,89 @@ dependencies = [ "base64", "data-url", "flate2", - "imagesize", - "kurbo 0.9.0", + "imagesize 0.11.0", + "kurbo 0.9.5", "log", "rctree", "rosvgtree", "strict-num", ] +[[package]] +name = "usvg" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2304b933107198a910c1f3219acb65246f2b148f862703cffd51c6e62156abe" +dependencies = [ + "base64", + "log", + "pico-args", + "usvg-parser", + "usvg-text-layout 0.34.0", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b940fea80394e3b14cb21c83fa1b8f8a41023c25929bba68bb84a76193ebed" +dependencies = [ + "data-url", + "flate2", + "imagesize 0.12.0", + "kurbo 0.9.5", + "log", + "roxmltree", + "simplecss", + "siphasher", + "svgtypes 0.11.0", + "usvg-tree", +] + [[package]] name = "usvg-text-layout" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "195386e01bc35f860db024de275a76e7a31afdf975d18beb6d0e44764118b4db" dependencies = [ - "fontdb", - "kurbo 0.9.0", + "fontdb 0.12.0", + "kurbo 0.9.5", + "log", + "rustybuzz", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "usvg 0.29.0", +] + +[[package]] +name = "usvg-text-layout" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69dfd6119f431aa7e969b4a69f9cc8b9ae37b8ae85bb26780ccfa3beaf8b71eb" +dependencies = [ + "fontdb 0.14.1", + "kurbo 0.9.5", "log", "rustybuzz", "unicode-bidi", "unicode-script", "unicode-vo", - "usvg", + "usvg-tree", +] + +[[package]] +name = "usvg-tree" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3185eb13b6e3d3cf1817d29612251cc308d5a7e5e6235362e67efe832435c6d9" +dependencies = [ + "rctree", + "strict-num", + "svgtypes 0.11.0", + "tiny-skia-path 0.10.0", ] [[package]] @@ -3038,9 +3654,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3048,24 +3664,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -3075,9 +3691,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3085,22 +3701,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-timer" @@ -3192,9 +3808,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -3206,29 +3822,20 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "wgpu" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d745a1b6d91d85c33defbb29f0eee0450e1d2614d987e14bf6baf26009d132d7" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.3", "cfg-if", "js-sys", "log", "naga", "parking_lot 0.12.1", "profiling", - "raw-window-handle 0.5.0", + "raw-window-handle 0.5.2", "smallvec", "static_assertions", "wasm-bindgen", @@ -3245,7 +3852,7 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7131408d940e335792645a98f03639573b0480e9e2e7cddbbab74f7c6d9f3fff" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.3", "bit-vec", "bitflags", "codespan-reporting", @@ -3254,7 +3861,7 @@ dependencies = [ "naga", "parking_lot 0.12.1", "profiling", - "raw-window-handle 0.5.0", + "raw-window-handle 0.5.2", "smallvec", "thiserror", "web-sys", @@ -3269,7 +3876,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdcf61a283adc744bb5453dd88ea91f3f86d5ca6b027661c6c73c7734ae0288b" dependencies = [ "android_system_properties", - "arrayvec 0.7.2", + "arrayvec 0.7.3", "ash", "bit-set", "bitflags", @@ -3278,7 +3885,7 @@ dependencies = [ "d3d12", "foreign-types 0.3.2", "fxhash", - "glow", + "glow 0.12.2", "gpu-alloc", "gpu-allocator", "gpu-descriptor", @@ -3286,7 +3893,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading", + "libloading 0.7.4", "log", "metal", "naga", @@ -3294,7 +3901,7 @@ dependencies = [ "parking_lot 0.12.1", "profiling", "range-alloc", - "raw-window-handle 0.5.0", + "raw-window-handle 0.5.2", "renderdoc-sys", "smallvec", "thiserror", @@ -3393,7 +4000,16 @@ version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -3411,48 +4027,54 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.1", + "windows-targets 0.48.0", ] [[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows-targets", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.1", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" @@ -3462,9 +4084,15 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" @@ -3474,9 +4102,15 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" @@ -3486,9 +4120,15 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" @@ -3498,15 +4138,27 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" @@ -3516,9 +4168,15 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winit" @@ -3542,7 +4200,7 @@ dependencies = [ "parking_lot 0.12.1", "percent-encoding", "raw-window-handle 0.4.3", - "raw-window-handle 0.5.0", + "raw-window-handle 0.5.2", "sctk-adwaita", "smithay-client-toolkit", "wasm-bindgen", @@ -3555,9 +4213,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.3.0" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdd927d1a3d5d98abcfc4cf8627371862ee6abfe52a988050621c50c66b4493" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" dependencies = [ "memchr", ] @@ -3618,7 +4276,29 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" dependencies = [ - "nom", + "nom 7.1.3", +] + +[[package]] +name = "xdg" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688597db5a750e9cad4511cb94729a078e274308099a0382b5b8203bbc767fee" +dependencies = [ + "home", +] + +[[package]] +name = "xdg-mime" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87bf7b69bb50588d70a36e467be29d3df3e8c32580276d62eded9738c1a797aa" +dependencies = [ + "dirs-next", + "glob", + "mime", + "nom 5.1.3", + "unicase", ] [[package]] @@ -3629,9 +4309,9 @@ checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" [[package]] name = "xml-rs" -version = "0.8.4" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +checksum = "52839dc911083a8ef63efa4d039d1f58b5e409f923e44c80828f206f66e5541c" [[package]] name = "xmlparser" @@ -3639,6 +4319,12 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "zbus" version = "3.10.0" @@ -3654,7 +4340,7 @@ dependencies = [ "async-trait", "byteorder", "derivative", - "dirs", + "dirs 4.0.0", "enumflags2", "event-listener", "futures-core", @@ -3687,25 +4373,34 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn", + "syn 1.0.109", ] [[package]] name = "zbus_names" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3" +checksum = "82441e6033be0a741157a72951a3e4957d519698f3a824439cc131c5ba77ac2a" dependencies = [ "serde", "static_assertions", "zvariant", ] +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zvariant" -version = "3.11.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903169c05b9ab948ee93fefc9127d08930df4ce031d46c980784274439803e51" +checksum = "622cc473f10cef1b0d73b7b34a266be30ebdcfaea40ec297dd8cbda088f9f93c" dependencies = [ "byteorder", "enumflags2", @@ -3717,12 +4412,24 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.11.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce76636e8fab7911be67211cf378c252b115ee7f2bae14b18b84821b39260b5" +checksum = "5d9c1b57352c25b778257c661f3c4744b7cefb7fc09dd46909a153cce7773da2" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] diff --git a/Cargo.toml b/Cargo.toml index 0e7e309..a2912d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,24 +10,43 @@ repository = "https://github.com/apgoetz/jolly" readme = "README.md" keywords = ["launcher","bookmarks", "iced"] exclude = ["docs/"] -rust-version = "1.65" +rust-version = "1.70" [dependencies] -iced = "0.9.0" +iced = { version = "0.9.0", features = ["image"] } iced_native = "0.10.0" toml = { version = "0.7.1", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } -dirs = "4" +dirs = "5" opener = "0.5.0" urlencoding = "2.1.0" lazy_static = "1" csscolorparser = { version = "0.6.2", features = ["serde"] } dark-light = "1.0.0" pulldown-cmark = "0.9" +url = "2" +once_cell = "1.18.0" +resvg = "0.34.0" + +[target.'cfg(target_os = "macos")'.dependencies] +objc = "0.2" +core-graphics = "0.22" +core-foundation = "0.9" + +[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] +freedesktop-icons = "0.2" +xdg-mime = "0.3.3" + + +[target.'cfg(all(unix, not(target_os = "macos")))'.build-dependencies] +freedesktop-icons = "0.2" +dirs = "5" + [dev-dependencies] tempfile = "3" + [target.'cfg(windows)'.build-dependencies] resvg = "0.29.0" usvg = "0.29.0" @@ -36,7 +55,15 @@ ico = "0.3.0" winres = "0.1.12" [target.'cfg(windows)'.dependencies.windows] -version = "0.44.0" +version = "0.48.0" features = [ - 'UI_ViewManagement' + 'UI_ViewManagement', + 'Win32_UI_Shell', + 'Win32_UI_Shell_Common', + 'Win32_UI_WindowsAndMessaging', + 'Win32_Foundation', + 'Win32_Graphics_Gdi', + 'Win32_System_Com', + 'Win32_UI_Controls', + 'Win32_System_LibraryLoader', ] diff --git a/README.md b/README.md index fbb4c3b..ddb606e 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,25 @@ Alternatively, for rust users, Jolly can be installed via cargo: cargo install jolly ``` +## Freedesktop based systems + +If you want to use Jolly on Linux and BSD based platforms, then icon +support is based on freedesktop.org standards. This means that you +will need the following packages installed: + ++ xdg-utils ++ shared-mime-info + +In addition, at least one icon theme needs to be installed. The +default icon theme can be customized at build time using the +environment variable `JOLLY_DEFAULT_THEME`, or it can be configured at +runtime in the config file. See [icon +documentation](docs/config#icon) for more details. + ## NetBSD -On NetBSD, a pre-compiled binary is available from the official repositories. -To install Jolly, simply run: +On NetBSD, a pre-compiled binary is available from the official +repositories. To install Jolly, simply run: ```bash pkgin install jolly @@ -37,7 +52,7 @@ make install uses [iced](https://github.com/iced-rs/iced) for its GUI implementation, which is a fast moving project that generally only targets the latest stable rustc. Therefore Jolly will also usually target the same MSRV as -`iced`. (Currently 1.65.0) +`iced`. (Currently 1.70.0) # Quick Introduction diff --git a/build.rs b/build.rs index b501164..959cf7e 100644 --- a/build.rs +++ b/build.rs @@ -1,9 +1,59 @@ // build script to add icon to Jolly executable. // this script is only used on windows platforms -#[cfg(unix)] +// no build requirements for macos FOR NOW +#[cfg(target_os = "macos")] fn main() {} +// check to make sure dependencies are installed +#[cfg(all(unix, not(target_os = "macos")))] +fn main() { + use std::env; + + let theme = env::var("JOLLY_DEFAULT_THEME").unwrap_or("gnome".into()); + println!("cargo:rustc-env=JOLLY_DEFAULT_THEME={}", theme); + + // check default theme is installed + let themes = freedesktop_icons::list_themes(); + if themes + .iter() + .filter(|t| t.to_uppercase() == theme.to_uppercase()) + .next() + .is_none() + { + println!("cargo:warning=Jolly default icon theme '{}' does not seem to be installed. You can override the default theme via environment variable JOLLY_DEFAULT_THEME", theme); + } + + // check xdg-utils is installed + let path = env::var("PATH").unwrap_or("".into()); + if path + .split(":") + .map(std::path::PathBuf::from) + .find(|p| p.join("xdg-settings").exists()) + .is_none() + { + println!("cargo:warning=package `xdg-utils` does not seem to be installed. Icon support may be broken"); + } + + // check shared-mime-info installed + let mut xdg_data_dirs = env::var("XDG_DATA_DIRS").unwrap_or("".into()); + + if xdg_data_dirs.is_empty() { + xdg_data_dirs = "/usr/local/share/:/usr/share/".into(); + } + + let data_home = dirs::data_dir().unwrap_or("/nonexistant/path".into()); + + if std::iter::once(data_home) + .chain(xdg_data_dirs.split(":").map(std::path::PathBuf::from)) + .find(|p| p.join("mime/mime.cache").exists()) + .is_none() + { + println!("cargo:warning=package `shared-mime-info` does not seem to be installed. Icon support may be broken"); + } +} + +// set a nice icon #[cfg(windows)] fn main() { // determine path to save icon to diff --git a/docs/config.md b/docs/config.md index 23194fb..4731aff 100644 --- a/docs/config.md +++ b/docs/config.md @@ -51,6 +51,7 @@ Below is more detail about the available settings: | `entry` | *table* | customize result entries | | `text_size` | *integer* | font size for UI. | | `max_results` | *integer* | max number of results to show. | +| `icon` | *table* | customize the display of icons | @@ -59,7 +60,6 @@ Below is more detail about the available settings: Determines the width of the Jolly window. Defined in virtual units as used by `iced` - ## `search` — *table* This table contains additional settings. See below for details. @@ -235,3 +235,37 @@ key `config.ui.text_size` is already set, this key will override the size only for the entry results. Default text size is 20. + +# [config.ui.icon] + +*Only valid for Linux and BSD platforms* + +This table contains settings for customizing how icons are displayed in Jolly. + +Currently there is only one field available: + +| field name | data type | description | +|------------|-----------|--------------------------------------| +| `theme` | *string* | icon theme to use (Freedesktop only) | + +## `theme` — *string* + +The value of this option should be the name of a Freedesktop icon +theme to use on Linux and BSD platforms. There is no standard way to +specify which icon theme the user is using, so they should specify it +using this option. If this option is not set, then Jolly will use a +compile-time default, currently the `"gnome"` theme. If this theme is +not installed, then a fallback blank grey icon will be used for all +icons. + +If you would like to change the compile time default theme, you can +use the environment variable `JOLLY_DEFAULT_THEME`. + +For example, to build jolly with a default theme of "Adwaita": + +``` +JOLLY_DEFAULT_THEME=Adwaita cargo build +``` + +As a general rule, the jolly build script will warn if the +`JOLLY_DEFAULT_THEME` doesn't seem to be installed at compile time. diff --git a/src/entry.rs b/src/entry.rs index d48e61f..3acebfd 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -5,10 +5,12 @@ use std::fmt; use std::ops::Deref; use serde::Deserialize; +use url::Url; -use crate::platform; +use crate::icon::Icon; use crate::theme; use crate::ui; +use crate::{icon, platform}; use crate::config::LOGFILE_NAME; @@ -21,6 +23,8 @@ const PARTIAL_TAG_W: u32 = 2; const STARTSWITH_TAG_W: u32 = 4; const FULL_TAG_W: u32 = 6; +pub type EntryId = usize; + #[derive(Debug)] pub enum Error { IOError(std::io::Error), @@ -89,6 +93,7 @@ struct RawStoreEntry { #[serde(alias = "desc")] description: Option, tags: Option>, + icon: Option, } #[derive(Debug, Eq, PartialEq, Clone, Hash)] @@ -98,13 +103,15 @@ enum Keyword { EscapedKeyword(String), } -#[derive(Debug, Eq, PartialEq, Clone, Hash)] +#[derive(Debug, Clone, Hash)] pub struct StoreEntry { name: String, description: Option, entry: EntryType, tags: Vec, keyword: Keyword, + icon_type: icon::IconType, + icon: Option, } #[derive(Debug, Eq, PartialEq, Clone, Hash)] @@ -159,12 +166,31 @@ impl StoreEntry { None => Vec::new(), }; + let icon_type = if let Some(p) = raw_entry.icon { + icon::IconType::custom(p) + } else { + match &entry { + EntryType::SystemEntry(loc) => icon::IconType::file(loc), + EntryType::FileEntry(loc) => { + let parsed_loc = format_param(loc, ""); + + if let Ok(url) = Url::parse(&parsed_loc) { + icon::IconType::url(url) + } else { + icon::IconType::file(parsed_loc) + } + } + } + }; + Ok(StoreEntry { name: name.to_string(), description: raw_entry.description, entry: entry, tags: tags, keyword: keyword, + icon_type, + icon: None, }) } @@ -243,26 +269,18 @@ impl StoreEntry { // format example: // - fn format>(&self, fmt_str: &str, searchtext: S) -> String { + pub fn format_name(&self, searchtext: &str) -> String { if self.keyword == Keyword::None { - return fmt_str.to_string(); + return self.name.clone(); } - fmt_str - .split("%%") - .map(|s| s.replace("%s", searchtext.as_ref())) - .collect::>() - .join("%") - } - - pub fn format_name(&self, searchtext: &str) -> String { let param = if let Some((_, back)) = searchtext.split_once(char::is_whitespace) { back } else { "%s" }; - self.format(&self.name, param) + format_param(&self.name, param) } pub fn format_selection(&self, searchtext: &str) -> String { @@ -272,16 +290,18 @@ impl StoreEntry { "%s" }; + let s = match &self.entry { + EntryType::FileEntry(s) => s, + EntryType::SystemEntry(s) => s, + }; + let escaped_param = match self.keyword { Keyword::EscapedKeyword(_) => urlencoding::encode(param).into_owned(), + Keyword::None => return s.clone(), _ => param.to_string(), }; - let s = match &self.entry { - EntryType::FileEntry(s) => s, - EntryType::SystemEntry(s) => s, - }; - self.format(s, escaped_param) + format_param(s, escaped_param) } pub fn handle_selection(&self, searchtext: &str) -> Result<(), Error> { @@ -299,12 +319,14 @@ impl StoreEntry { searchtext: &str, settings: &ui::UISettings, selected: bool, + my_id: EntryId, ) -> iced_native::Element<'a, Message, Renderer> where - F: 'static + Copy + Fn(StoreEntry) -> Message, + F: 'static + Copy + Fn(EntryId) -> Message, Message: 'static + Clone, Renderer: iced_native::renderer::Renderer + 'a, Renderer: iced_native::text::Renderer, + Renderer: iced_native::image::Renderer, { let text_color = if selected { settings.theme.selected_text_color.clone() @@ -346,9 +368,28 @@ impl StoreEntry { None => iced::widget::Column::new(), }; + let icon = iced::widget::image::Image::new( + self.icon + .clone() + .unwrap_or_else(|| icon::default_icon(&settings.icon)), + ); + + let icon = icon + .height(settings.entry.common.text_size()) + .width(settings.entry.common.text_size()); + + let icon_row = iced::widget::Row::new() + .height(iced::Length::Fixed( + (settings.entry.common.text_size() + 4) as f32, + )) + .spacing(2) + .align_items(iced_native::Alignment::Center) + .push(icon) + .push(title_text); + let column = iced::widget::Column::new() .width(iced_native::Length::Fill) - .push(title_text) + .push(icon_row) .push(description); // need an empty container to create padding around title. @@ -356,13 +397,35 @@ impl StoreEntry { // iced::widget::container::Container::new(title_text).padding::(0u16.into()); let button = iced::widget::button::Button::new(column) - .on_press(message_func(self.clone())) + .on_press(message_func(my_id)) .style(button_style) .width(iced_native::Length::Fill); let element: iced_native::Element<'_, _, _> = button.into(); element } + + // pull out the icon type of this entry in preparation for + // determing it. current icontype is replaced with pending value + pub fn icontype(&self) -> &icon::IconType { + &self.icon_type + } + + pub fn icon(&mut self, icon: Icon) { + self.icon = Some(icon); + } + + pub fn icon_loaded(&self) -> bool { + self.icon.is_some() + } +} + +fn format_param>(fmt_str: &str, searchtext: S) -> String { + fmt_str + .split("%%") + .map(|s| s.replace("%s", searchtext.as_ref())) + .collect::>() + .join("%") } fn desc_to_paragraphs(desc: &str) -> Option> { @@ -406,9 +469,25 @@ fn desc_to_paragraphs(desc: &str) -> Option> { #[cfg(test)] mod tests { + use crate::icon::IconType; + use super::*; use tempfile; + // lets cheat and use the hash of an entry for partial equivalence + // good enough for testing + impl std::cmp::PartialEq for StoreEntry { + fn eq(&self, other: &Self) -> bool { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut sh = DefaultHasher::new(); + let mut oh = DefaultHasher::new(); + self.hash(&mut sh); + other.hash(&mut oh); + sh.finish() == oh.finish() + } + } + fn parse_entry(text: &str) -> StoreEntry { let value: toml::Value = toml::from_str(text).unwrap(); @@ -422,13 +501,11 @@ mod tests { #[test] fn case_sensitive() { - let entry = StoreEntry { - name: "fOO.txt".to_string(), - description: None, - keyword: Keyword::None, - entry: EntryType::FileEntry("test/location/asdf.txt".to_string()), - tags: ["FOO"].into_iter().map(str::to_string).collect(), - }; + let entry = parse_entry( + r#"['fOO.txt'] + location = "test/location/asdf.txt" + tags = ['FOO']"#, + ); // if we give a lowercase query, then default case insensitive match assert_eq!(entry.score("fo"), STARTSWITH_TAG_W); @@ -440,16 +517,11 @@ mod tests { #[test] fn non_keword_score() { - let entry = StoreEntry { - name: "foo.txt".to_string(), - description: None, - keyword: Keyword::None, - entry: EntryType::FileEntry("test/location/foo.txt".to_string()), - tags: ["foo", "bar", "baz"] - .into_iter() - .map(str::to_string) - .collect(), - }; + let entry = parse_entry( + r#"['foo.txt'] + location = "test/location/foo.txt" + tags = ["foo", "bar", "baz"]"#, + ); assert_eq!(entry.score("tx"), PARTIAL_NAME_W); assert_eq!(entry.score("foo"), FULL_TAG_W); @@ -466,16 +538,12 @@ mod tests { #[test] fn keword_score() { - let entry = StoreEntry { - name: "foo.txt".to_string(), - description: None, - keyword: Keyword::RawKeyword("y".to_string()), - entry: EntryType::FileEntry("test/location/foo.txt".to_string()), - tags: ["foo", "bar", "baz"] - .into_iter() - .map(str::to_string) - .collect(), - }; + let entry = parse_entry( + r#"['foo.txt'] + location = "test/location/foo.txt" + keyword = "y" + tags = ["foo", "bar", "baz"]"#, + ); // if you dont use a keyword, score normally assert_eq!(entry.score("fo"), STARTSWITH_TAG_W); @@ -501,6 +569,8 @@ mod tests { .into_iter() .map(str::to_string) .collect(), + icon: None, + icon_type: IconType::file("test/location"), }, ), ( @@ -512,6 +582,21 @@ mod tests { keyword: Keyword::None, entry: EntryType::FileEntry("test/location".to_string()), tags: [].into_iter().map(str::to_string).collect(), + icon: None, + icon_type: IconType::file("test/location"), + }, + ), + ( + r#"['foo.txt'] + location = "tel:12345""#, + StoreEntry { + name: "foo.txt".to_string(), + description: None, + keyword: Keyword::None, + entry: EntryType::FileEntry("tel:12345".to_string()), + tags: [].into_iter().map(str::to_string).collect(), + icon: None, + icon_type: IconType::url(url::Url::parse("tel:12345").unwrap()), }, ), ( @@ -526,6 +611,8 @@ mod tests { .into_iter() .map(str::to_string) .collect(), + icon: None, + icon_type: IconType::file("test/location/foo.txt"), }, ), ( @@ -537,6 +624,8 @@ mod tests { keyword: Keyword::None, entry: EntryType::FileEntry("foo.txt".to_string()), tags: [].into_iter().map(str::to_string).collect(), + icon: None, + icon_type: IconType::file("foo.txt"), }, ), ( @@ -548,6 +637,21 @@ mod tests { keyword: Keyword::None, entry: EntryType::FileEntry("foo.txt".to_string()), tags: [].into_iter().map(str::to_string).collect(), + icon: None, + icon_type: IconType::file("foo.txt"), + }, + ), + ( + r#"['foo.txt'] + icon = "asdf.png""#, + StoreEntry { + name: "foo.txt".to_string(), + description: None, + keyword: Keyword::None, + entry: EntryType::FileEntry("foo.txt".to_string()), + tags: [].into_iter().map(str::to_string).collect(), + icon: None, + icon_type: IconType::custom("asdf.png"), }, ), ]; @@ -578,6 +682,8 @@ mod tests { .into_iter() .map(str::to_string) .collect(), + icon: None, + icon_type: IconType::file("foo bar"), }; let entry = parse_entry(&toml); @@ -602,6 +708,8 @@ mod tests { .into_iter() .map(str::to_string) .collect(), + icon: None, + icon_type: IconType::file(dirname.to_string()), }; let entry = parse_entry(&toml); @@ -611,16 +719,14 @@ mod tests { #[test] fn keyword_search_results() { - let mut entry = StoreEntry { - name: "name:%s".to_string(), - description: None, - keyword: Keyword::RawKeyword(Default::default()), - entry: EntryType::FileEntry("file/%s".to_string()), - tags: Default::default(), - }; + let mut entry = parse_entry( + r#"['name:%s'] + location = "file/%s""#, + ); let raw: Keyword = Keyword::RawKeyword(Default::default()); let escaped: Keyword = Keyword::EscapedKeyword(Default::default()); + let none: Keyword = Keyword::None; let tests = [ (&raw, "a b", "name:b", "file/b"), @@ -628,6 +734,7 @@ mod tests { (&raw, "a b c", "name:b c", "file/b c"), (&escaped, "a b", "name:b", "file/b"), (&escaped, "a b c", "name:b c", "file/b%20c"), + (&none, "a b", "name:%s", "file/%s"), ]; for (entry_type, searchtext, formatted_name, formatted_selection) in tests { @@ -653,14 +760,6 @@ mod tests { #[test] fn test_format() { - let entry = StoreEntry { - name: Default::default(), - description: None, - keyword: Keyword::RawKeyword(Default::default()), - entry: EntryType::FileEntry(Default::default()), - tags: Default::default(), - }; - let tests = [ ("%s", "a", "a"), ("test %s", "a", "test a"), @@ -672,7 +771,7 @@ mod tests { for test in tests { assert_eq!( test.2, - entry.format(test.0, test.1), + format_param(test.0, test.1), r#"format("{}", "{}") -> "{}" failed: "#, test.0, test.1, @@ -703,4 +802,28 @@ mod tests { ); } } + + #[test] + fn test_keyword_icontypes_are_parsed() { + let entry = parse_entry( + r#"['a'] + location = 'http://example.com/%s' + keyword = 'a' + "#, + ); + + assert_eq!( + entry.icon_type, + IconType::url(url::Url::parse("http://example.com/").unwrap()) + ); + + let entry = parse_entry( + r#"['a'] + location = '%s.txt' + keyword = 'a' + "#, + ); + + assert_eq!(entry.icon_type, IconType::file(".txt")) + } } diff --git a/src/icon/linux_and_friends.rs b/src/icon/linux_and_friends.rs new file mode 100644 index 0000000..d7cb69d --- /dev/null +++ b/src/icon/linux_and_friends.rs @@ -0,0 +1,336 @@ +#![cfg(all(unix, not(target_os = "macos")))] +// for now, this covers linux and the bsds +use super::{icon_from_svg, Context, Icon, IconError, DEFAULT_ICON_SIZE}; + +use std::io::Read; + +use serde; +use xdg_mime::SharedMimeInfo; + +// set in build script +pub const DEFAULT_THEME: &str = env!("JOLLY_DEFAULT_THEME"); + +// TODO make mime sniff size a config parameter? +const SNIFFSIZE: usize = 8 * 1024; + +#[derive(serde::Deserialize, Debug, Clone, PartialEq)] +#[serde(default)] +pub struct Os { + pub theme: String, + xdg_folder: Option, +} + +impl Default for Os { + fn default() -> Self { + Self { + theme: DEFAULT_THEME.into(), + xdg_folder: None, + } + } +} + +impl super::IconInterface for Os { + fn get_default_icon(&self) -> Result { + self.get_icon_for_iname("text-x-generic") + } + + fn get_icon_for_file>(&self, path: P) -> Result { + let path = path.as_ref(); + let inames = self.get_iname_for_file(path)?; + + for iname in &inames { + let icon = self.get_icon_for_iname(iname); + if icon.is_ok() { + return icon; + } + } + Err(format!("No valid icon. inames were {:?}", inames).into()) + } + + fn get_icon_for_url(&self, url: &str) -> Result { + let iname = self.get_iname_for_url(url)?; + self.get_icon_for_iname(&iname) + } + + // for linux apps we need to make sure there are some default mime + // types specified since CI is run headless +} + +impl Os { + fn get_iname_for_url(&self, p: &str) -> Result { + use url::Url; + + let url = Url::parse(p).context("Url is not valid: {p}")?; + + use std::process::Command; + let mut cmd = Command::new("xdg-settings"); + + cmd.arg("get") + .arg("default-url-scheme-handler") + .arg(url.scheme()); + + // if xdg folder is specified, we use this to override the + // location we look for url settings + if let Some(f) = &self.xdg_folder { + cmd.env("XDG_DATA_HOME", f); + cmd.env("XDG_DATA_DIRS", f); + cmd.env("HOME", f); + cmd.env("DE", "generic"); + } + + let output = cmd.output().context("xdg-settings unsuccessful")?; + + // assume that we got back utf8 for the application name + let mut handler = + String::from_utf8(output.stdout).context("invalid utf8 from xdg-settings")?; + if handler.ends_with("\n") { + handler.pop(); + } + + if handler.is_empty() { + Err("no scheme handler found".into()) + } else { + Ok(handler) + } + } + + fn get_iname_for_file>( + &self, + path: P, + ) -> Result, IconError> { + let filename = path + .as_ref() + .as_os_str() + .to_str() + .context("filename not valid unicode")?; + + use once_cell::sync::OnceCell; + + static MIMEINFO: OnceCell = OnceCell::new(); + + // if xdg folder is specified, we use this to override the + // location we look for mimetype settings + let mimeinfo = match &self.xdg_folder { + Some(f) => { + let m = Box::new(SharedMimeInfo::new_for_directory(f)); + Box::leak(m) // ok because only used in testing + } + None => MIMEINFO.get_or_init(SharedMimeInfo::new), + }; + + let data: Option>; + + // TODO, handle files we can see but not read + if let Ok(mut file) = std::fs::File::open(filename) { + let mut buf = vec![0u8; SNIFFSIZE]; + if let Ok(numread) = file.read(buf.as_mut_slice()) { + buf.truncate(numread); + data = Some(buf); + } else { + data = None; + } + } else { + data = None; + } + + // this next part is a little gross, but xdg_mime currently + // hardcodes a mimetype of application/x-zerosize if a file is + // empty. So we need to run the mime sniffing 2 different + // ways, once with data and once without, and then lump them + // all together to find whichever one creates an icon + + let guess = match data { + Some(buf) => mimeinfo.guess_mime_type().path(filename).data(&buf).guess(), + None => mimeinfo.guess_mime_type().path(filename).guess(), + }; + + let fn_guess = mimeinfo.get_mime_types_from_file_name(filename); + + let allmimes = std::iter::once(guess.mime_type().clone()).chain(fn_guess.into_iter()); + + let allparents = allmimes + .clone() + .flat_map(|m| mimeinfo.get_parents(&m).unwrap_or_default().into_iter()); + + Ok(allmimes + .chain(allparents) + .flat_map(|m| mimeinfo.lookup_icon_names(&m).into_iter()) + .collect()) + } + + fn get_icon_for_iname(&self, icon_name: &str) -> Result { + use freedesktop_icons::lookup; + + let icon_name = icon_name.strip_suffix(".desktop").unwrap_or(icon_name); + + let icon_path = lookup(icon_name) + .with_size(DEFAULT_ICON_SIZE) + .with_theme(&self.theme) + .find() + .ok_or("Could not lookup icon")?; + + // TODO handle other supported icon types + if icon_path + .extension() + .is_some_and(|e| e.eq_ignore_ascii_case("png")) + { + Ok(iced::widget::image::Handle::from_path(icon_path)) + } else if icon_path + .extension() + .is_some_and(|e| e.eq_ignore_ascii_case("svg")) + { + icon_from_svg(&icon_path) + } else { + Err(format!( + "unsupported icon file type for icon {}", + icon_path.to_string_lossy() + ) + .into()) + } + } +} + +#[cfg(test)] +mod tests { + + use std::fs::{create_dir, write}; + use std::process::Command; + use tempfile; + + use super::*; + + // helper struct to allow building mock xdg data for testing + struct MockXdg(tempfile::TempDir); + + impl MockXdg { + fn new() -> Self { + let dir = tempfile::tempdir().unwrap(); + + let p = dir.path(); + create_dir(p.join("applications")).unwrap(); + create_dir(p.join("mime")).unwrap(); + Self(dir) + } + + fn add_app(&self, appname: &str) { + let p = self.0.path().join(format!("applications/{}", appname)); + write(p, b"[Desktop Entry]\nExec=/bin/sh").unwrap(); + } + + fn register_url(&self, url: &str, appname: &str) { + let out = Command::new("xdg-settings") + .args(["set", "default-url-scheme-handler", url, appname]) + .env("XDG_DATA_HOME", self.0.path()) + .env("XDG_DATA_DIRS", self.0.path()) + .env("HOME", self.0.path()) + .env("DE", "generic") + .env("XDG_UTILS_DEBUG_LEVEL", "2") + .output() + .unwrap(); + println!( + "registering_url: {} -> {} status: {} {}", + url, + appname, + out.status, + String::from_utf8(out.stderr).unwrap() + ); + } + + fn register_mime(&self, mimetype: &str, extension: &str) { + let filename = mimetype.split("/").last().unwrap(); + let filename = self.0.path().join(format!("{}.xml", filename)); + let text = format!( + r#" + + + + + +"#, + mimetype, extension + ); + + write(&filename, text.as_bytes()).unwrap(); + let out = Command::new("xdg-mime") + .args([ + "install", + "--novendor", + "--mode", + "user", + filename.to_str().unwrap(), + ]) + .env("XDG_DATA_HOME", self.0.path()) + .env("XDG_DATA_DIRS", self.0.path()) + .env("HOME", self.0.path()) + .env("DE", "generic") + .env("XDG_UTILS_DEBUG_LEVEL", "2") + .output() + .unwrap(); + println!( + "registering_mime: {} status: {} {}", + mimetype, + out.status, + String::from_utf8(out.stderr).unwrap() + ); + } + + fn os(&self, theme: &str) -> Os { + Os { + theme: theme.into(), + xdg_folder: Some(self.0.path().to_str().unwrap().into()), + } + } + } + + #[test] + fn test_load_icon() { + // build a mock xdg with the ability to handle telephone and nothing else + let xdg = MockXdg::new(); + xdg.add_app("test.desktop"); + xdg.register_url("tel", "test.desktop"); + xdg.register_mime("text/x-rust", "rs"); + let os = xdg.os(DEFAULT_THEME); + + assert!(os.get_iname_for_url("http://google.com").is_err()); + assert!(os.get_iname_for_url("tel:12345").is_ok()); + } + + #[test] + fn test_load_file() { + let dir = tempfile::tempdir().unwrap(); + // build a mock xdg with the ability to handle rust source and nothing else + let xdg = MockXdg::new(); + xdg.register_mime("text/x-rust", "rs"); + let os = xdg.os(DEFAULT_THEME); + let file = dir.path().join("test.rs"); + std::fs::File::create(&file).unwrap(); + let mimetypes = os.get_iname_for_file(file).unwrap(); + assert!( + mimetypes.contains(&"text-x-rust".into()), + "actual {:?}", + mimetypes + ); + } + + #[test] + fn can_load_svg_icons() { + // freedesktop_icons falls back to using the icon name as a + // file path if it cant find it otherwise. so we can use this + // to force loading the jolly svg icon + let svg_icon = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("icon/jolly"); + + let icon = Os::default() + .get_icon_for_iname(svg_icon.as_os_str().to_str().unwrap()) + .unwrap(); + // expect pixel data from the icon + assert!(matches!( + icon.data(), + iced_native::image::Data::Rgba { + width: _, + height: _, + pixels: _ + } + )); + } +} diff --git a/src/icon/macos.rs b/src/icon/macos.rs new file mode 100644 index 0000000..1af1fc7 --- /dev/null +++ b/src/icon/macos.rs @@ -0,0 +1,182 @@ +#![cfg(target_os = "macos")] + +use super::{Context, Icon, IconError, IconInterface, DEFAULT_ICON_SIZE}; +use core_graphics::geometry::{CGPoint, CGRect, CGSize}; +use core_graphics::image::CGImageRef; +use objc::rc::StrongPtr; +use objc::runtime::Object; +use objc::{class, msg_send, sel, sel_impl}; +use serde; +use url::Url; + +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Default)] +pub struct Os; + +impl IconInterface for Os { + fn get_default_icon(&self) -> Result { + let ident: NSString = "public.item".into(); + + unsafe { + let typ: *mut Object = msg_send![class!(UTType), typeWithIdentifier: ident]; + let workspace = get_workspace()?; + + let icon: *mut Object = msg_send![workspace, iconForContentType: typ]; + let icon = icon.as_mut().context("iconForContentType was null")?; + image2icon(icon).context("Could not get default icon") + } + } + + fn get_icon_for_file>(&self, path: P) -> Result { + // now we have an icon! At this point, we can start + // using the nicer wrappers from core_graphics-rs + unsafe { icon_for_file(path.as_ref().as_os_str().into()) } + } + + fn get_icon_for_url(&self, url: &str) -> Result { + Url::parse(url).context("url is not valid")?; // TODO, hoist this out of all 3 implementations + + unsafe { + let webstr: NSString = url.into(); + + let nsurl = class!(NSURL); + + // for full url + let url: *mut Object = msg_send![nsurl, URLWithString: webstr]; + + if url.is_null() { + return Err("Could not make NSURL".into()); + } + + let workspace = get_workspace()?; + + // get app url + let appurl: *mut Object = msg_send![workspace, URLForApplicationToOpenURL: url]; + + if appurl.is_null() { + return Err("Could not get url of app for opening url".into()); + } + + let path: *mut Object = msg_send![appurl, path]; + + if path.is_null() { + return Err("Could not get path of app url".into()); + } + + icon_for_file(path.try_into()?) + } + // convert to URL. Determine application url, get path to application, get icon for file + + // if we cannot convert to URL, assume it is a file. + + // if the file exists, use iconForFile + + // if the file does not exist, take its extension, and use typeWithFilenameExtention, and then iconForContentType + } +} + +unsafe fn get_workspace() -> Result<&'static mut Object, IconError> { + let workspace: *mut Object = msg_send![class!(NSWorkspace), sharedWorkspace]; + workspace + .as_mut() + .context("Could not get sharedWorkspace: Null") +} + +unsafe fn image2icon(image: &mut Object) -> Result { + let rect = CGRect { + origin: CGPoint::new(0.0, 0.0), + size: CGSize::new(DEFAULT_ICON_SIZE as f64, DEFAULT_ICON_SIZE as f64), + }; + + let cgicon: *mut CGImageRef = msg_send![image, CGImageForProposedRect:&rect context:0 hints:0]; + let cgicon = cgicon.as_ref().ok_or("Cannot get CGImage")?; + + // we dont know for sure we got RGBA data but we assume it for the rest of this function + let bpc = cgicon.bits_per_component(); + let bpp = cgicon.bits_per_pixel(); + if bpc != 8 || bpp != 32 { + return Err(format!("CGImage does not have 32bit depth: bpc: {bpc} bpp: {bpp}").into()); + } + + let h = cgicon.height() as u32; + let w = cgicon.width() as u32; + + // copies + let pixels = Vec::from(cgicon.data().bytes()); + + Ok(Icon::from_pixels(h, w, pixels.leak())) +} + +unsafe fn icon_for_file(path: NSString) -> Result { + let workspace = get_workspace()?; + + let icon: *mut Object = msg_send![workspace, iconForFile: path]; + + image2icon(icon.as_mut().context("Could not get iconForFile: null")?) +} + +struct NSString(StrongPtr); + +impl NSString { + unsafe fn from_raw(b: *const u8, len: usize) -> Self { + let nsstring = class!(NSString); + let obj = StrongPtr::new(msg_send![nsstring, alloc]); + if obj.is_null() { + panic!("failed to alloc NSString") + } + let outstr = StrongPtr::new(msg_send![*obj, initWithBytes:b length:len encoding:4]); + + outstr.as_ref().expect("Could not init NSString"); + + Self(outstr) + } +} + +use std::ffi::OsStr; +impl From<&OsStr> for NSString { + fn from(s: &OsStr) -> NSString { + use std::os::unix::ffi::OsStrExt; + unsafe { + let b = s.as_bytes(); + NSString::from_raw(b.as_ptr(), b.len()) + } + } +} + +impl std::ops::Deref for NSString { + type Target = *mut Object; + fn deref(&self) -> &*mut Object { + self.0.deref() + } +} + +use std::fmt::{Formatter, Pointer}; +impl Pointer for NSString { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl From<&str> for NSString { + fn from(s: &str) -> NSString { + let b = s.as_bytes(); + unsafe { NSString::from_raw(b.as_ptr(), b.len()) } + } +} + +impl TryFrom<*mut Object> for NSString { + type Error = IconError; + fn try_from(s: *mut Object) -> Result { + use objc::runtime::{BOOL, NO}; + unsafe { + let p = s.as_ref().context("Null Ptr While converting NSString")?; + + let result: BOOL = msg_send![p, isKindOfClass: class!(NSString)]; + + if result == NO { + return Err("Conversion error: Object is not isKindOfClass NSString".into()); + } + + Ok(NSString(StrongPtr::new(s))) + } + } +} diff --git a/src/icon/mod.rs b/src/icon/mod.rs new file mode 100644 index 0000000..fb6b4ec --- /dev/null +++ b/src/icon/mod.rs @@ -0,0 +1,616 @@ +// contains logic for loading icons for entries +// +// different implementations for macOs, windows, and linux. (linux and +// BSDs assumed to use freedesktop compatible icon standards) + +// general overview and requirements for icon usage: +// +// Icons are mandatory for usage with Jolly. +// Icons are generated based on the entry type. +// +// location entries get searched as a file. If the file does not +// exist, a default icon may be returned +// +// url-like entries get searched as a protocol handler +// +// system entries are currently treated as file entries, which means +// they usually fail to load since the system command is not usually a +// file on disk. +// +// keyword entries are parsed by filling in their customizable value with a blank string "" +// +// If an icon cannot be loaded, we fall back to a platform specific default icon. +// +// If that default icon cannot be loaded, there is an extremely boring +// (grey square) icon that is used instead. +// +// The default icon is always cached staticly (one use per crate) +// +// All of the other icon lookups can be optionally cached using the IconCache struct + +use std::collections::HashMap; +use std::hash::Hash; +use url::Url; + +use std::error; + +mod linux_and_friends; +mod macos; +mod windows; + +use lazy_static::lazy_static; +lazy_static! { + static ref FALLBACK_ICON: Icon = Icon::from_pixels(1, 1, &[127, 127, 127, 255]); +} + +// TODO +// +// This is a list of supported icon formats by iced_graphics. +// This is based on iced_graphics using image-rs to load images, and +// looking at what features are enabled on that package. In the future +// we may not compile support for all formats but (for now) we have a +// comprehensive list here +const SUPPORTED_ICON_EXTS: &[&str] = &[ + "png", "jpg", "jpeg", "gif", "webp", "pbm", "pam", "ppm", "pgm", "tiff", "tif", "tga", "dds", + "bmp", "ico", "hdr", "exr", "ff", "qoi", +]; + +const DEFAULT_ICON_SIZE: u16 = 48; // TODO, support other icon sizes + +#[cfg(target_os = "macos")] +pub use macos::Os as IconSettings; + +#[cfg(all(unix, not(target_os = "macos")))] +pub use linux_and_friends::Os as IconSettings; + +#[cfg(target_os = "windows")] +pub use self::windows::Os as IconSettings; + +#[derive(Debug)] +struct IconError(String, Option>); + +use std::fmt; +impl fmt::Display for IconError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +impl error::Error for IconError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + self.1.as_deref() + } +} + +impl + fmt::Display> From for IconError { + fn from(value: S) -> Self { + Self(value.to_string(), None) + } +} + +trait Context { + fn context + fmt::Display>(self, msg: S) -> Result; +} + +impl Context for Result { + fn context + fmt::Display>(self, msg: S) -> Result { + self.map_err(|e| IconError(msg.to_string(), Some(Box::new(e)))) + } +} + +impl Context for Option { + fn context + fmt::Display>(self, msg: S) -> Result { + self.ok_or(IconError(msg.to_string(), None)) + } +} + +trait IconImpl {} + +// defines functions that must be implemented for every operating +// system in order implement icons in jolly +trait IconInterface { + // default icon to use if icon cannot be loaded. + // must be infallible + // the output is cached by icon module, so it should not be used be other logic + fn get_default_icon(&self) -> Result; + + // icon that would be used for a path that must exist + // path is guaranteed to be already canonicalized + fn get_icon_for_file>(&self, path: P) -> Result; + + // icon to use for a specific url or protocol handler. + fn get_icon_for_url(&self, url: &str) -> Result; + + // provided method: version of get_default_icon that caches its + // value. One value for lifetime of application + fn cached_default(&self) -> Icon { + use once_cell::sync::OnceCell; + static DEFAULT_ICON: OnceCell = OnceCell::new(); + + DEFAULT_ICON + .get_or_init(|| self.get_default_icon().unwrap_or(FALLBACK_ICON.clone())) + .clone() + } + + // provided method: uses icon interfaces to turn icontype into icon + fn load_icon(&self, itype: IconType) -> Icon { + let icon = self.try_load_icon(itype); + icon.unwrap_or(self.cached_default()) + } + + // convert an icontype into an icon + fn try_load_icon(&self, itype: IconType) -> Result { + match itype.0 { + IconVariant::Url(u) => self.get_icon_for_url(u.as_str()), + IconVariant::File(p) => { + if p.exists() { + if let Ok(p) = p.canonicalize() { + self.get_icon_for_file(p) + } else { + Err("File Icon does not exist".into()) + } + } else { + Err("Cannot load icon for nonexistant file".into()) // TODO handle file type lookup by extension + } + } + IconVariant::CustomIcon(p) => { + let ext = p.extension().context("No extension on custom icon file")?; + if SUPPORTED_ICON_EXTS + .iter() + .find(|s| ext.eq_ignore_ascii_case(s)) + .is_some() + { + Ok(Icon::from_path(p)) + } else if ext.eq_ignore_ascii_case("svg") { + icon_from_svg(&p) + } else { + Err("is unsupported icon type".into()) + } + } + } + } +} + +// sufficient for now, until we implement SVG support +pub type Icon = iced::widget::image::Handle; + +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct IconType(IconVariant); + +impl IconType { + pub fn custom>(path: P) -> Self { + Self(IconVariant::CustomIcon(path.as_ref().into())) + } + pub fn url(url: Url) -> Self { + // hack to make paths that start with disk drives not show up as URLs + #[cfg(target_os = "windows")] + if url.scheme().len() == 1 + && "abcdefghijklmnopqrstuvwxyz".contains(url.scheme().chars().next().unwrap()) + { + return Self(IconVariant::File(url.as_ref().into())); + } + + Self(IconVariant::Url(url)) + } + pub fn file>(path: P) -> Self { + Self(IconVariant::File(path.as_ref().into())) + } +} + +// represents the necessary information in an entry to look up an icon +// type. Importantly, url based entries are assumed to have the same +// icon if they have the same protocol (for example, all web links) +#[derive(Debug, Clone)] +enum IconVariant { + // render using icon for protocol of url + Url(url::Url), + // render using icon for path + File(std::path::PathBuf), + // override "normal" icon and use icon from this path + CustomIcon(std::path::PathBuf), +} + +impl Hash for IconVariant { + fn hash(&self, state: &mut H) { + match self { + IconVariant::Url(u) => u.scheme().hash(state), + IconVariant::File(p) => p.hash(state), + IconVariant::CustomIcon(p) => p.hash(state), + } + } +} + +impl Eq for IconVariant {} +impl PartialEq for IconVariant { + fn eq(&self, other: &Self) -> bool { + match self { + IconVariant::Url(s) => { + if let IconVariant::Url(o) = other { + s.scheme() == o.scheme() + } else { + false + } + } + IconVariant::File(s) => { + if let IconVariant::File(o) = other { + s == o + } else { + false + } + } + IconVariant::CustomIcon(s) => { + if let IconVariant::CustomIcon(o) = other { + s == o + } else { + false + } + } + } + } +} + +pub fn default_icon(is: &IconSettings) -> Icon { + is.cached_default() +} + +use crate::Message; +use iced_native::futures::channel::mpsc; + +// represents an icon cache that can look up icons in a deferred worker thread +#[derive(Default)] +pub struct IconCache { + cmd: Option>, + cache: HashMap>, +} + +impl IconCache { + pub fn new() -> Self { + Self { + cmd: None, + cache: HashMap::new(), + } + } + + pub fn get(&mut self, it: &IconType) -> Option { + // if the key is the cache, either we have the icon or it has + // already been scheduled. either way, send it. + if let Some(icon) = self.cache.get(it) { + return icon.clone(); + } + + // if we have a reference the iconwork command channel, then + // we kick off a request to lookup the new icontype + if let Some(cmd) = &self.cmd { + cmd.send(IconCommand::LoadIcon(it.clone())) + .expect("Could not send new icon lookup command"); + self.cache.insert(it.clone(), None); + } + + // at this point, we know we had a cache miss + None + } + + pub fn add_icon(&mut self, it: IconType, i: Icon) { + self.cache.insert(it, Some(i)); + } + + pub fn set_cmd(&mut self, cmd: std::sync::mpsc::Sender) { + self.cmd = Some(cmd); + } +} + +#[derive(Debug)] +pub enum IconCommand { + LoadSettings(IconSettings), + LoadIcon(IconType), +} + +//create stream that satisfies the needs of iced_native::subscription::run +// TODO: add tests +pub fn icon_worker() -> mpsc::Receiver { + // todo: fix magic for channel size + let (mut output, sub_stream) = mpsc::channel(100); + + std::thread::spawn(move || { + let (input, command_stream) = std::sync::mpsc::channel(); + + // send the application a channel to provide us icon work + // TODO: implement error checking if we cant send + output + .try_send(Message::StartedIconWorker(input)) + .expect("Could not send iconworker back to application"); + + let command = match command_stream.recv() { + Ok(i) => i, + _ => return, + }; + let settings = if let IconCommand::LoadSettings(settings) = command { + settings + } else { + return; + }; + + loop { + let command = match command_stream.recv() { + Ok(i) => i, + _ => break, + }; + + match command { + IconCommand::LoadIcon(icontype) => { + // todo: handle error + output + .try_send(Message::IconReceived( + icontype.clone(), + settings.load_icon(icontype), + )) + .expect("Could not send icon back application"); + } + _ => break, + } + } + }); + sub_stream +} + +// convert an svg file into a pixmap +fn icon_from_svg(path: &std::path::Path) -> Result { + use resvg::usvg::TreeParsing; + let svg_data = std::fs::read(path).context("could not open file")?; + let utree = resvg::usvg::Tree::from_data(&svg_data, &Default::default()) + .context("could not parse svg")?; + + let icon_size = DEFAULT_ICON_SIZE as u32; + + let mut pixmap = + resvg::tiny_skia::Pixmap::new(icon_size, icon_size).context("could not create pixmap")?; + + let rtree = resvg::Tree::from_usvg(&utree); + + // we have non-square svg + if rtree.size.width() != rtree.size.height() { + return Err("SVG icons must be square".into()); + } + + let scalefactor = icon_size as f32 / rtree.size.width(); + let transform = resvg::tiny_skia::Transform::from_scale(scalefactor, scalefactor); + + rtree.render(transform, &mut pixmap.as_mut()); + + Ok(Icon::from_pixels( + icon_size, + icon_size, + pixmap.take().leak(), + )) +} + +#[cfg(test)] +mod tests { + use super::{Icon, IconError, IconInterface, IconSettings}; + + pub(crate) fn hash_eq_icon(icon: &Icon, ficon: &Icon) -> bool { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut ihash = DefaultHasher::new(); + let mut fhash = DefaultHasher::new(); + icon.hash(&mut ihash); + ficon.hash(&mut fhash); + ihash.finish() == fhash.finish() + } + + fn iconlike(icon: Icon, err_msg: &str) { + match icon.data() { + iced_native::image::Data::Path(p) => { + assert!(p.exists()) + } + iced_native::image::Data::Bytes(bytes) => { + assert!(bytes.len() > 0) + } + iced_native::image::Data::Rgba { + width, + height, + pixels, + } => { + let num_pixels = width * height; + + assert!(num_pixels > 0, "zero pixels: {}", err_msg); + assert_eq!( + (num_pixels * 4) as usize, + pixels.len(), + "incorrect buffer size: {}", + err_msg + ) + } + }; + + assert!( + !hash_eq_icon(&icon, &super::FALLBACK_ICON), + "icon hash matches fallback icon, should not occur during happycase" + ); + } + + #[test] + fn default_icon_is_iconlike() { + iconlike( + IconSettings::default().get_default_icon().unwrap(), + "for default icon", + ); + } + + // ignore on linux since exes are all detected as libraries which + // dont have a default icon + #[test] + #[cfg(any(target_os = "windows", target_os = "macos"))] + fn executable_is_iconlike() { + let cur_exe = std::env::current_exe().unwrap(); + + iconlike( + IconSettings::default().get_icon_for_file(&cur_exe).unwrap(), + "for current executable", + ); + } + + #[test] + fn paths_are_canonicalized() { + struct MockIcon; + + impl IconInterface for MockIcon { + fn get_default_icon(&self) -> Result { + Ok(super::Icon::from_pixels(1, 1, &[1, 1, 1, 1])) + } + + fn get_icon_for_file>( + &self, + path: P, + ) -> Result { + let path = path.as_ref(); + assert!(path.as_os_str() == path.canonicalize().unwrap().as_os_str()); + self.get_default_icon() + } + + fn get_icon_for_url(&self, _url: &str) -> Result { + panic!("expected file, not url") + } + } + + use tempfile; + let curdir = std::env::current_dir().unwrap(); + let dir = tempfile::tempdir_in(&curdir).unwrap(); + let dirname = dir.path().strip_prefix(curdir).unwrap(); + + let filename = dirname.join("test.txt"); + + let _file = std::fs::File::create(&filename).unwrap(); + + let icon_type = super::IconType(super::IconVariant::File(filename)); + let mock = MockIcon; + mock.load_icon(icon_type); + } + + #[test] + fn common_urls_are_iconlike() { + use crate::icon::Context; + // test urls that default macos has support for + #[cfg(any(target_os = "macos", target_os = "windows"))] + let happycase_urls = vec!["http://example.com", "https://example.com"]; + + #[cfg(all(unix, not(target_os = "macos")))] + let happycase_urls: Vec<&str> = Vec::new(); + + #[cfg(windows)] + let happycase_urls: Vec<_> = happycase_urls + .into_iter() + .chain( + [ + "accountpicturefile:", + "AudioCD:", + "batfile:", + "fonfile:", + "hlpfile:", + "regedit:", + ] + .into_iter(), + ) + .collect(); + + let sadcase_urls = vec![ + "totallynonexistantprotocol:", + "http:", // malformed url + ]; + + #[cfg(windows)] + let sadcase_urls: Vec<_> = sadcase_urls + .into_iter() + .chain( + [ + "anifile:", // uses %1 as the icon + "tel:", // defined but empty on windows + ] + .into_iter(), + ) + .collect(); + + let os = IconSettings::default(); + + let failed_results: Vec<_> = happycase_urls + .into_iter() + .filter_map(|u| { + os.get_icon_for_url(&u) + .context(format!("failed to load '{u}'")) + .err() + .map(|e| e.to_string()) + }) + .chain(sadcase_urls.into_iter().filter_map(|u| { + os.get_icon_for_url(&u) + .ok() + .map(|_| format!("successfully loaded '{u}'")) + })) + .collect(); + + assert!( + failed_results.is_empty(), + "some default urls were loaded incorrectly: {:?}", + failed_results + ); + } + + #[test] + fn common_files_are_iconlike() { + let dir = tempfile::tempdir().unwrap(); + let files = ["foo.txt", "bar.html", "baz.png", "bat.pdf"]; + + let os = IconSettings::default(); + + os.get_icon_for_file(dir.path()) + .expect("No Icon for folder".into()); + + for f in files { + let path = dir.path().join(f); + let file = std::fs::File::create(&path).unwrap(); + file.sync_all().unwrap(); + + assert!(path.exists()); + os.get_icon_for_file(&path) + .expect(&format!("No Icon for file: {f}")); + } + } + + #[test] + fn load_custom_icons() { + use super::*; + // example test pbm image + let pbm_bytes = "P1\n2 2\n1 0 1 0".as_bytes(); + // example test svg + let test_svg = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("icon/jolly.svg"); + + let dir = tempfile::tempdir().unwrap(); + + let pbm_fn = dir.path().join("test.pbm"); + + std::fs::write(&pbm_fn, pbm_bytes).unwrap(); + + let os = IconSettings::default(); + + let pbm_icon = os.try_load_icon(IconType::custom(pbm_fn)).unwrap(); + assert!(matches!(pbm_icon.data(), iced_native::image::Data::Path(_))); + + os.try_load_icon(IconType::custom("file_with_no_extension")) + .unwrap_err(); + + os.try_load_icon(IconType::custom("unsupported_icon_type.pdf")) + .unwrap_err(); + + let svg_icon = os.try_load_icon(IconType::custom(test_svg)).unwrap(); + assert!(matches!( + svg_icon.data(), + iced_native::image::Data::Rgba { + width: w, + height: h, + pixels: _ + } + if *w == DEFAULT_ICON_SIZE as u32 && *h == DEFAULT_ICON_SIZE as u32 + )); + } +} diff --git a/src/icon/windows.rs b/src/icon/windows.rs new file mode 100644 index 0000000..fa5045b --- /dev/null +++ b/src/icon/windows.rs @@ -0,0 +1,726 @@ +#![cfg(target_os = "windows")] + +use super::{Context, Icon, IconError, DEFAULT_ICON_SIZE, SUPPORTED_ICON_EXTS}; + +use serde; + +use std::path::Path; + +use std::mem::{size_of, MaybeUninit}; + +use std::os::windows::ffi::OsStrExt; + +use url::Url; + +use windows::core::{PCWSTR, PWSTR}; +use windows::Win32::Foundation::{BOOL, HMODULE, HWND, MAX_PATH, SIZE}; +use windows::Win32::Graphics::Gdi::{ + DeleteObject, GetDC, GetDIBits, GetObjectW, ReleaseDC, BITMAP, BITMAPINFOHEADER, BI_RGB, + DIB_RGB_COLORS, HBITMAP, +}; +use windows::Win32::UI::Controls::IImageList; +use windows::Win32::UI::Shell::{ + AssocQueryStringW, IShellItemImageFactory, SHCreateItemFromParsingName, SHDefExtractIconW, + SHGetImageList, SHGetStockIconInfo, SHLoadIndirectString, ASSOCF, ASSOCSTR, SHGSI_FLAGS, + SHSTOCKICONID, SHSTOCKICONINFO, SIIGBF, +}; +use windows::Win32::UI::WindowsAndMessaging::{DestroyIcon, GetIconInfo, HICON, ICONINFO}; + +impl Context<()> for BOOL { + fn context + std::fmt::Display>(self, msg: S) -> Result<(), IconError> { + self.ok().context(msg) + } +} + +#[derive(serde::Deserialize, Debug, Clone, PartialEq)] +pub struct Os; + +impl Default for Os { + fn default() -> Self { + #[cfg(test)] + unsafe { + use windows::Win32::System::Com::CoIncrementMTAUsage; + let _ = CoIncrementMTAUsage(); // hack to force COM to be initialized for testing + } + Self + } +} + +// represents a +#[derive(Debug)] +struct WideString(Vec); + +impl> From for WideString { + fn from(val: T) -> Self { + Self( + val.as_ref() + .as_os_str() + .encode_wide() + .chain(std::iter::once(0)) + .collect(), + ) + } +} + +impl TryFrom for String { + type Error = IconError; + fn try_from(val: WideString) -> Result { + Ok(Self::from_utf16(&val.0) + .context("Invalid utf16")? + .trim_end_matches(0 as char) + .to_string()) + } +} + +impl WideString { + fn pcwstr(&self) -> PCWSTR { + PCWSTR(self.0.as_ptr()) + } + + // goal here is to make sure extended paths are properly converted + // widestrings. This can fail if extended path is to long to be + // represented by a PCWSTR + // + // https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry + fn from_path>(val: P) -> Result { + use std::ffi::OsString; + const EXT_PATH: &str = r#"\\?\"#; + const UNC_PATH: &str = r#"\\?\UNC\"#; + let words = val.as_ref().as_os_str().encode_wide(); + let path = val.as_ref().as_os_str().to_string_lossy(); // can be lossy since we are only checking first few chars in the string + let words: Vec<_> = if path.starts_with(UNC_PATH) { + OsString::from(r#"\\"#) + .as_os_str() + .encode_wide() + .chain(words.skip(UNC_PATH.len())) + .chain(std::iter::once(0)) + .collect() + } else if path.starts_with(EXT_PATH) { + words + .skip(EXT_PATH.len()) + .chain(std::iter::once(0)) + .collect() + } else { + words.chain(std::iter::once(0)).collect() + }; + if words.len() > MAX_PATH as usize { + return Err(format!("Path {path} is longer than MAX_PATH").into()); + } else { + Ok(Self(words)) + } + } +} + +impl super::IconInterface for Os { + fn get_default_icon(&self) -> Result { + let siid = SHSTOCKICONID(0); // SIID_DOCNOASSOC + let uflags = SHGSI_FLAGS(0x000004000); // get system icon index + + let mut psii = SHSTOCKICONINFO { + cbSize: size_of::() as u32, + hIcon: Default::default(), + iSysImageIndex: Default::default(), + iIcon: Default::default(), + szPath: [0; MAX_PATH as usize], + }; + + unsafe { + // TODO support other imagelists if the default icon size changes + let list: IImageList = SHGetImageList(0x2).context("could not get imagelist")?; + + // if we get an error, the lookup failed, fall back to builtin default + SHGetStockIconInfo(siid, uflags, std::ptr::addr_of_mut!(psii)) + .context("Cannot SHGetStockIconInfo default icon")?; + + let hicon = list + .GetIcon(psii.iSysImageIndex, 0) + .context("Could not GetIcon default icon")?; + + let icon = get_icon_from_handle(hicon); + + if hicon.is_invalid() { + return Err("HICON is null for fallback image".into()); + } + + DestroyIcon(hicon).context("Could not DestroyIcon fallback icon handle")?; + + icon + } + } + + fn get_icon_for_file>(&self, path: P) -> Result { + let wide_path = WideString::from_path(path.as_ref())?; + + if wide_path.0.len() > MAX_PATH as usize { + return Err(format!( + "full path of icon source is too long to access with shell api: {}", + path.as_ref().to_string_lossy() + ) + .into()); + } + + unsafe { + let ifactory: IShellItemImageFactory = + SHCreateItemFromParsingName(wide_path.pcwstr(), None).context(format!( + "could not get shell entry for path: {:?}", + String::try_from(wide_path) + ))?; + + //IShellItemImageFactory::GetImage + + let sigbf = SIIGBF( + 0x1 // SIIGBF_BIGGERSIZEOK + | 0x20, //SIIGBF_CROPTOSQUARE + ); + let size = SIZE { + cx: DEFAULT_ICON_SIZE as i32, + cy: DEFAULT_ICON_SIZE as i32, + }; + + let hbitmap = ifactory + .GetImage(size, sigbf) + .context("could not get bitmap")?; + + let icon = get_icon_from_hbm(hbitmap); + + if !DeleteObject(hbitmap).as_bool() { + return Err("could not delete bitmap".into()); + } + + icon.context(format!("could not convert hbitmap to icon")) + } + } + + fn get_icon_for_url(&self, url: &str) -> Result { + // https://devblogs.microsoft.com/oldnewthing/20150914-00/?p=91601 + let flags = ASSOCF(0x80 | 0x1000); // ASSOCF_REMAPRUNDLL | ASSOCF_ISPROTOCOL + + let assocstr = ASSOCSTR(15); // ASSOCSTR_DEFAULTICON + let scheme = WideString::from(Url::parse(url).context("url is not valid")?.scheme()); + let mut outsize = 0u32; + unsafe { + // query first to get the size of the result array + let _ = AssocQueryStringW( + flags, + assocstr, + scheme.pcwstr(), + WideString::from("open").pcwstr(), + PWSTR::null(), + std::ptr::addr_of_mut!(outsize), + ); + + if outsize == 0 { + return Err(format!( + "no icon defined for url with scheme {:?}", + String::try_from(scheme) + ) + .into()); + } + + let mut outbuf = Vec::::with_capacity(outsize as usize); + + AssocQueryStringW( + flags, + assocstr, + scheme.pcwstr(), + WideString::from("open").pcwstr(), + PWSTR(outbuf.as_mut_ptr()), + std::ptr::addr_of_mut!(outsize), + ) + .ok() + .context("could not AssocQueryStringW")?; + + if outsize == 0 { + return Err("AssocQueryStringW output length was 0".into()); + } + + outbuf.set_len(outsize as usize); + + // check if the icon is an "indirect string" + let path: String = if outbuf.starts_with(&['@' as u16]) { + let mut newpath = vec![0u16; MAX_PATH as usize]; + + SHLoadIndirectString(PCWSTR(outbuf.as_ptr()), &mut newpath, None) + .context("Error with SHLoadIndirectString")?; + + // need to trim + String::from_utf16(&newpath) + .context(format!("invalid utf16 in defaulticon for {}", url))? + .trim_end_matches(0 as char) + .to_string() + } else { + String::from_utf16(outbuf.split_last().unwrap().1) + .context(format!("invalid utf16 in defaulticon for {}", url))? + // minus 1 to remove null terminator + }; + + if SUPPORTED_ICON_EXTS + .iter() + .find(|f| { + Path::new(&path) + .extension() + .is_some_and(|p| p.eq_ignore_ascii_case(f)) + }) + .is_some() + { + return Ok(Icon::from_path(path)); + } + + // if we have gotten to this point, we assume that the + // icon is of the form "file.exe,-1" where file.exe is the + // path to the file that has the icon, and the number is + // the index of the icon + get_icon_from_file_and_index(path) + .context("Could not load icon for {url}, stored as {path}") + } + } +} + +// takes a string of the form "file.exe,-1" and looks up the HICON that is associated with that index +// +// if the index is not specified, uses first icon in file +// if the file name is wrapped in quotes, removes them +// if the index is specified but doesnt exist, falls back to using the first entry in the file. +fn get_icon_from_file_and_index(path: String) -> Result { + let (mut file, index) = path.rsplit_once(",").unwrap_or((&path, "0")); + + // if the file name is wrapped in double quotes, remove it + let mut chars = file.chars(); + if chars.next().is_some_and(|c| c == '"') && chars.next_back().is_some_and(|c| c == '"') { + file = file + .get(1..file.len() - 1) + .context("could not remove quotes from file name")?; + } + + let index = index + .parse::() + .context(format!("cannot parse index as i32: {}", index))?; + + let mut hicon = HICON(0); + + let pcwstr = WideString::from(file).pcwstr(); + unsafe { + let result = SHDefExtractIconW( + pcwstr, + index, + 0, + Some(std::ptr::addr_of_mut!(hicon)), + None, + DEFAULT_ICON_SIZE as u32, + ); + + // if the first request for the icon doesnt work, we can + // fallback to using the first icon defined in the + // resource file + let hicon = if result.is_err() || hicon.is_invalid() { + let lib = Library::open(file)?; + let icon_groups = lib.get_first_icon_group()?; + + let first_icon = icon_groups.ok_or(IconError::from( + "No icon groups in DefaultIcon resource file", + ))?; + + let hicon = lib.get_hicon_by_group_id(first_icon, DEFAULT_ICON_SIZE as i32)?; + + lib.close()?; + + hicon + } else { + hicon + }; + + let result = get_icon_from_handle(hicon); + + // soft error. Keep going + DestroyIcon(hicon.clone()) + .ok() + .context(format!("Could not destroy HICON({})", hicon.0))?; + + result.context("could not convert hicon to image") + } +} + +unsafe fn get_icon_from_handle(handle: HICON) -> Result { + if handle.is_invalid() { + return Err("invalid handle".into()); + } + + //if we are here, then we successfully got a handle to the icon. Now we need bitmaps + let mut iconinfo = MaybeUninit::::uninit(); + + let retval = GetIconInfo(handle, iconinfo.as_mut_ptr()).as_bool(); + + if !retval { + return Err("Cannot get IconInfo".into()); + } + + let iconinfo = iconinfo.assume_init(); + + if let Err(e1) = DeleteObject(iconinfo.hbmMask).context("Could not delete hbmMask") { + if let Err(e2) = DeleteObject(iconinfo.hbmColor).context("Could not delete hbmColor") { + return Err(e1).context(format!( + "While processing this error, another error occured: {}", + e2 + )); + } else { + return Err(e1); + } + } + + let icon = get_icon_from_hbm(iconinfo.hbmColor); + + DeleteObject(iconinfo.hbmColor).context("Cannot delete hbmColor")?; + + icon +} + +unsafe fn get_icon_from_hbm(hbm: HBITMAP) -> Result { + let mut cbitmap = MaybeUninit::::uninit(); //color bitmap + const BITMAP_SIZE: i32 = size_of::>() as i32; + + if GetObjectW(hbm, BITMAP_SIZE, Some(cbitmap.as_mut_ptr().cast())) == 0 { + return Err("Cannot get hbmColor bitmap object".into()); + } + + let cbitmap = cbitmap.assume_init_ref(); + + let mut header = BITMAPINFOHEADER { + biSize: size_of::() as u32, + biWidth: cbitmap.bmWidth, + biHeight: -cbitmap.bmHeight, + biPlanes: 1, + biBitCount: 32, + biCompression: BI_RGB.0 as u32, + biSizeImage: 0, + biXPelsPerMeter: 0, + biYPelsPerMeter: 0, + biClrUsed: 0, + biClrImportant: 0, + }; + + let mut pixels = + Vec::::with_capacity((cbitmap.bmWidth * cbitmap.bmHeight * 4).try_into().unwrap()); + + let dc = GetDC(HWND(0)); + assert!(!dc.is_invalid()); + + let lines_read = GetDIBits( + dc, + hbm, + 0, + cbitmap.bmHeight as u32, + Some(pixels.as_mut_ptr().cast()), + std::ptr::addr_of_mut!(header).cast(), + DIB_RGB_COLORS, + ); + + if ReleaseDC(HWND(0), dc) != 1 { + return Err("could not ReleaseDC".into()); + } + + if lines_read != cbitmap.bmHeight { + return Err(format!("only wrote {} lines of DIBits", lines_read).into()); + } + + // we have the pixels, extend vec to contain them + pixels.set_len(pixels.capacity()); + + for chunk in pixels.chunks_exact_mut(4) { + let [b, _, r, _] = chunk else {unreachable!()}; + std::mem::swap(b, r); + } + + Ok(Icon::from_pixels( + cbitmap.bmWidth.try_into().unwrap(), + cbitmap.bmHeight.try_into().unwrap(), + pixels.leak(), // TODO fix leak + )) +} + +#[allow(non_snake_case)] +fn MAKEINTRESOURCEW(id: i32) -> PCWSTR { + unsafe { std::mem::transmute::<_, PCWSTR>(id as usize) } +} + +#[allow(non_snake_case)] +fn IS_INTRESOURCE(lptype: PCWSTR) -> Option { + unsafe { + let id: usize = std::mem::transmute(lptype); + if id >> 16 == 0 { + Some(id as i32) + } else { + None + } + } +} + +const RT_ICON: i32 = 3; +const RT_GROUP_ICON: i32 = RT_ICON + 11; + +struct Library(HMODULE, String); + +impl Library { + fn open>(path: P) -> Result { + use windows::Win32::System::LibraryLoader::{LoadLibraryExW, LOAD_LIBRARY_FLAGS}; + + if !path.as_ref().is_absolute() { + return Err(format!("non-absolute path: {}", path.as_ref().to_string_lossy()).into()); + } + + let pathstr = path.as_ref().as_os_str().to_string_lossy(); + + let lflags = LOAD_LIBRARY_FLAGS(0x20 | 0x02 | 0x08); //LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE | LOAD_WITH_ALTERED_SEARCH_PATH + + unsafe { + let hmodule = LoadLibraryExW(WideString::from(path.as_ref()).pcwstr(), None, lflags) + .context(format!("could not LoadLibraryEXW for file {pathstr}"))?; + Ok(Self(hmodule, pathstr.to_string())) + } + } + + fn inner_close(&mut self) -> Result<(), IconError> { + use windows::Win32::System::LibraryLoader::FreeLibrary; + let hmodule = self.0; + + self.0 = HMODULE(0); + + if hmodule == HMODULE(0) { + return Ok(()); + } + + unsafe { + FreeLibrary(hmodule) + .ok() + .context(format!("Could not FreeLibrary {}", self.1))?; + } + Ok(()) + } + + fn close(mut self) -> Result<(), IconError> { + self.inner_close() + } + + fn get_first_icon_group(&self) -> Result, IconError> { + use windows::Win32::System::LibraryLoader::EnumResourceNamesW; + unsafe extern "system" fn get_first_icon_group( + _hmodule: HMODULE, + lptype: PCWSTR, + lpname: PCWSTR, + lparam: isize, + ) -> BOOL { + let icon_groups: *mut Option = std::mem::transmute(lparam); + let icon_groups = icon_groups.as_mut().expect("icon group is NULL"); + + match IS_INTRESOURCE(lpname) { + Some(id) if IS_INTRESOURCE(lptype).is_some_and(|t| t == RT_GROUP_ICON) => { + // only remember the first icon group + if icon_groups.is_none() { + *icon_groups = Some(id); + } + BOOL(1) + } + _ => BOOL(0), // abort if we didnt get an icon_group + } + } + + let mut first_icon_group: Option = None; + + let rt_group_icon = MAKEINTRESOURCEW(RT_GROUP_ICON); + + unsafe { + EnumResourceNamesW( + self.0, + rt_group_icon, + Some(get_first_icon_group), + std::mem::transmute(std::ptr::addr_of_mut!(first_icon_group)), + ) + .context(format!("Could not EnumResourceNames for {}", self.1))?; + } + + Ok(first_icon_group) + } + + fn get_hicon_by_group_id(&self, icon_group: i32, size: i32) -> Result { + use windows::Win32::Foundation::GetLastError; + use windows::Win32::System::LibraryLoader::{ + FindResourceW, LoadResource, LockResource, SizeofResource, + }; + use windows::Win32::UI::WindowsAndMessaging::{ + CreateIconFromResourceEx, LookupIconIdFromDirectoryEx, IMAGE_FLAGS, + }; + + unsafe { + let hrsrc = FindResourceW( + self.0, + MAKEINTRESOURCEW(icon_group), + MAKEINTRESOURCEW(RT_GROUP_ICON), + ); + if hrsrc.is_invalid() { + return Err(GetLastError() + .ok() + .context(format!("Could not FindResource {icon_group}")) + .unwrap_err()); + } + + let hglobal = LoadResource(self.0, hrsrc) + .context(format!("Could not LoadResource {icon_group}"))?; + + let ptr: *const u8 = LockResource(hglobal).cast(); + + if ptr.is_null() { + return Err(IconError::from(format!( + "Could not LockResource {icon_group}" + ))); + } + + let icon_id = LookupIconIdFromDirectoryEx(ptr, true, size, size, IMAGE_FLAGS(0)); + + if icon_id == 0 { + return Err(GetLastError() + .ok() + .context(format!( + "Could not LookupIconIdFromDirectoryEx {icon_group}" + )) + .unwrap_err()); + } + + let hrsrc = FindResourceW(self.0, MAKEINTRESOURCEW(icon_id), MAKEINTRESOURCEW(RT_ICON)); + if hrsrc.is_invalid() { + return Err(GetLastError() + .ok() + .context(format!("Could not FindResource icon_id {icon_id}")) + .unwrap_err()); + } + + let rsrc_size = SizeofResource(self.0, hrsrc); + + if rsrc_size == 0 { + return Err(GetLastError() + .ok() + .context(format!("Could not SizeofResource icon_id {icon_id}")) + .unwrap_err()); + } + + // now we have a resource id for best fitting icon + let hglobal = LoadResource(self.0, hrsrc) + .context(format!("Could not LoadResource for icon_id {icon_id}"))?; + + let ptr: *const u8 = LockResource(hglobal).cast(); + + if ptr.is_null() { + return Err(IconError::from(format!( + "Could not LockResource for icon_id {icon_id}" + ))); + } + + let bytes = std::slice::from_raw_parts(ptr, rsrc_size as usize); + + let hicon = CreateIconFromResourceEx(bytes, true, 0x00030000, 0, 0, IMAGE_FLAGS(0)) + .context("Could not CreateIconFromResourceEx for icon_id {icon_id}")?; + + if hicon.is_invalid() { + return Err("CreateIconFromResourceEx returned NULL HICON".into()); + } else { + Ok(hicon) + } + } + } +} + +impl Drop for Library { + fn drop(&mut self) { + let _ = self.inner_close(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_wide_string() { + let max_path = MAX_PATH as usize; + + assert_eq!( + String::try_from(WideString::from_path(r#"\\?\asdf"#).unwrap()).unwrap(), + "asdf" + ); + assert_eq!( + String::try_from(WideString::from_path(r#"\\?\UNC\asdf"#).unwrap()).unwrap(), + r#"\\asdf"# + ); + assert_eq!( + String::try_from(WideString::from_path("asdf").unwrap()).unwrap(), + "asdf" + ); + + WideString::from_path("a".repeat(max_path + 1)).unwrap_err(); + WideString::from_path("a".repeat(max_path)).unwrap_err(); + WideString::from_path("a".repeat(max_path - 1)).unwrap(); + } + + #[test] + fn test_get_icon_for_file_max_path() { + use crate::icon::IconInterface; + + let max_path = MAX_PATH as usize; + let os = Os::default(); + + let dir = tempfile::tempdir().unwrap(); + + let path = dir.path(); + let remsize = max_path - path.as_os_str().encode_wide().count() - 1; + + let over = path.join("a".repeat(remsize + 1)); + let equal = path.join("a".repeat(remsize)); + let under = path.join("a".repeat(remsize - 1)); + + std::fs::File::create(&over).unwrap(); + std::fs::File::create(&equal).unwrap(); + std::fs::File::create(&under).unwrap(); + + os.get_icon_for_file(over).unwrap_err(); + os.get_icon_for_file(equal).unwrap_err(); + os.get_icon_for_file(under).unwrap(); + } + + #[test] + fn test_indexed_resources() { + use crate::icon::tests::hash_eq_icon; + let filename = r#"C:\Windows\System32\shell32.dll"#; + + get_icon_from_file_and_index("nonexistant.dll".to_string()).unwrap_err(); + + // previously this has been flaky, so we repeat the request + // multiple times to try and confirm it is really fixed + const N: usize = 16; + + let icons: Result, IconError> = std::iter::repeat_with(|| { + Ok(( + get_icon_from_file_and_index(filename.to_string()).context("no index: ")?, + get_icon_from_file_and_index(format!("{filename},0")).context("index0")?, + get_icon_from_file_and_index(format!("{filename},999")).context("index999")?, + get_icon_from_file_and_index(format!("{filename},-999")).context("negindex999")?, + get_icon_from_file_and_index(format!(r#""{filename}",0"#)).context("quoted")?, + )) + }) + .take(N) + .collect(); + + let (noindex, index0, index999, negindex999, quoted) = + icons.unwrap().into_iter().next().unwrap(); + + assert!(hash_eq_icon(&noindex, &index0)); + assert!(hash_eq_icon(&index999, &index0)); + assert!(hash_eq_icon(&negindex999, &index0)); + assert!(hash_eq_icon("ed, &index0)); + } + + //TODO to test + + // have logic to test the following + // geticonforfile handles extended paths correctly (compare to windows) + + // undocumented ability to change icon size from 48 to something else + // +} diff --git a/src/lib.rs b/src/lib.rs index acf5e2d..ebc8d9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,10 +12,12 @@ use iced::{executor, Application, Command, Element, Renderer}; use iced_native::widget::text_input; use iced_native::{clipboard, command, event, keyboard, subscription, widget, window}; use lazy_static; +use std::sync::mpsc; pub mod config; mod entry; pub mod error; +mod icon; mod measured_container; mod platform; mod search_results; @@ -31,8 +33,10 @@ lazy_static::lazy_static! { pub enum Message { SearchTextChanged(String), ExternalEvent(event::Event), - EntrySelected(entry::StoreEntry), + EntrySelected(entry::EntryId), HeightChanged(u32), + StartedIconWorker(mpsc::Sender), + IconReceived(icon::IconType, icon::Icon), } enum StoreLoadedState { @@ -47,15 +51,6 @@ impl Default for StoreLoadedState { } } -impl StoreLoadedState { - fn store(&self) -> Option<&store::Store> { - match self { - StoreLoadedState::LoadSucceeded(s, _) => Some(s), - _ => None, - } - } -} - #[derive(Default)] pub struct Jolly { searchtext: String, @@ -63,6 +58,7 @@ pub struct Jolly { search_results: search_results::SearchResults, modifiers: keyboard::Modifiers, settings: settings::Settings, + icache: icon::IconCache, } impl Jolly { @@ -83,10 +79,15 @@ impl Jolly { })) } - fn handle_selection( - &mut self, - entry: entry::StoreEntry, - ) -> Command<::Message> { + fn handle_selection(&mut self, id: entry::EntryId) -> Command<::Message> { + // we can only continue if the store is loaded + let store = match &self.store_state { + StoreLoadedState::LoadSucceeded(s, _) => s, + _ => return Command::none(), + }; + + let entry = store.get(id); + // if the user is pressing the command key, we want to copy to // clipboard instead of opening the link if self.modifiers.command() { @@ -149,6 +150,32 @@ impl Application for Jolly { } fn update(&mut self, message: Self::Message) -> Command { + // first, match the messages that would cause us to quit regardless of application state + match message { + Message::ExternalEvent(event::Event::Keyboard(e)) => { + if let keyboard::Event::KeyReleased { + key_code: key, + modifiers: _, + } = e + { + if key == keyboard::KeyCode::Escape { + return iced::window::close(); + } + } + } + Message::ExternalEvent(event::Event::Window(w)) if w == window::Event::Unfocused => { + return iced::window::close(); + } + _ => (), // dont care at this point about other messages + }; + + // then, check if we are loaded. ifwe have failed to laod, we stop processing messages + let store = match &mut self.store_state { + StoreLoadedState::LoadSucceeded(s, _) => s, + _ => return Command::none(), + }; + + // if we are here, we are loaded and we dont want to quit match message { Message::HeightChanged(height) => { Command::single(command::Action::Window(window::Action::Resize { @@ -156,31 +183,29 @@ impl Application for Jolly { height: height, })) } - Message::SearchTextChanged(txt) - if matches!(self.store_state, StoreLoadedState::LoadSucceeded(_, _)) => - { + Message::SearchTextChanged(txt) => { self.searchtext = txt; - if let Some(store) = self.store_state.store() { - let matches = store.find_matches(&self.searchtext).into_iter(); - let new_results = - search_results::SearchResults::new(matches, &self.settings.ui); - - if new_results != self.search_results { - self.search_results = new_results; - // since the search text changed we need to - // recalculate window height for the new results. So - // resize the window to hide the results until the new - // height is available - return self.min_height_command(); - } else if self.searchtext.is_empty() { - return self.min_height_command(); - } + + let matches = store.find_matches(&self.searchtext).into_iter(); + + // todo: determine which entries need icons + let new_results = search_results::SearchResults::new(matches, &self.settings.ui); + + // load icons of whatever matches are being displayed + store.load_icons(new_results.entries(), &mut self.icache); + + if new_results != self.search_results { + self.search_results = new_results; + // since the search text changed we need to + // recalculate window height for the new results. So + // resize the window to hide the results until the new + // height is available + return self.min_height_command(); + } else if self.searchtext.is_empty() { + return self.min_height_command(); } Command::none() } - Message::ExternalEvent(event::Event::Window(w)) if w == window::Event::Unfocused => { - iced::window::close() - } Message::ExternalEvent(event::Event::Window(window::Event::FileDropped(path))) => { println!("{:?}", path); Command::none() @@ -196,12 +221,12 @@ impl Application for Jolly { } else if key == keyboard::KeyCode::NumpadEnter || key == keyboard::KeyCode::Enter { - return self.handle_selection(self.search_results.selected().clone()); + return self.handle_selection(self.search_results.selected()); } } if keyboard::Event::CharacterReceived('\r') == e { - return self.handle_selection(self.search_results.selected().clone()); + return self.handle_selection(self.search_results.selected()); } if let keyboard::Event::ModifiersChanged(m) = e { @@ -212,12 +237,31 @@ impl Application for Jolly { Command::none() } Message::EntrySelected(entry) => self.handle_selection(entry), + Message::StartedIconWorker(worker) => { + worker + .send(icon::IconCommand::LoadSettings( + self.settings.ui.icon.clone(), + )) + .expect("Could not send message to iconworker"); + self.icache.set_cmd(worker); + + Command::none() + } + Message::IconReceived(it, icon) => { + self.icache.add_icon(it, icon); + + store.load_icons(self.search_results.entries(), &mut self.icache); + + Command::none() + } _ => Command::none(), } } fn subscription(&self) -> iced::Subscription { - subscription::events().map(Message::ExternalEvent) + let channel = subscription::run(icon::icon_worker); + let external = subscription::events().map(Message::ExternalEvent); + subscription::Subscription::batch([channel, external].into_iter()) } fn view(&self) -> Element<'_, Message, Renderer> { @@ -237,10 +281,17 @@ impl Application for Jolly { .padding(self.settings.ui.search.padding), ); - column = column.push( - self.search_results - .view(&self.searchtext, Message::EntrySelected), - ); + // we can only continue if the store is loaded + let store = match &self.store_state { + StoreLoadedState::LoadSucceeded(s, _) => s, + _ => return column.into(), + }; + + column = column.push(self.search_results.view( + &self.searchtext, + store, + Message::EntrySelected, + )); measured_container::MeasuredContainer::new(column, Message::HeightChanged).into() } diff --git a/src/platform.rs b/src/platform.rs index bb4c0ca..81ed2e7 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -38,12 +38,12 @@ const DEFAULT_ACCENT_COLOR: ui::Color = ui::Color(csscolorparser::Color { // based on subprocess crate #[cfg(unix)] -mod os { +pub(crate) mod os { use crate::ui; use std::ffi::OsStr; use std::process::Command; - const SHELL: [&str; 2] = ["sh", "-c"]; + pub const SHELL: [&str; 2] = ["sh", "-c"]; pub const ACCENT_COLOR: &'static ui::Color = &super::DEFAULT_ACCENT_COLOR; // run a subshell and interpret results @@ -53,14 +53,14 @@ mod os { } #[cfg(windows)] -mod os { +pub(crate) mod os { use crate::ui; use std::ffi::OsStr; use std::os::windows::process::CommandExt; use std::process::Command; use windows::UI::ViewManagement::{UIColorType, UISettings}; - const SHELL: [&str; 2] = ["cmd.exe", "/c"]; + pub const SHELL: [&str; 2] = ["cmd.exe", "/c"]; // try and get the windows accent color. This wont work for // windows < 10 diff --git a/src/search_results.rs b/src/search_results.rs index c57eee1..869ac67 100644 --- a/src/search_results.rs +++ b/src/search_results.rs @@ -1,6 +1,7 @@ use iced_native::{keyboard, widget}; use crate::entry; +use crate::store; use crate::theme; use crate::ui; @@ -8,7 +9,7 @@ const PADDING: u16 = 2; #[derive(Default)] pub struct SearchResults { - entries: Vec, + entries: Vec, selected: usize, settings: ui::UISettings, } @@ -37,19 +38,16 @@ impl std::cmp::PartialEq for SearchResults { } impl SearchResults { - pub fn new<'a>( - results: impl Iterator, - settings: &ui::UISettings, - ) -> Self { + pub fn new(results: impl Iterator, settings: &ui::UISettings) -> Self { SearchResults { - entries: results.cloned().take(settings.max_results).collect(), + entries: results.take(settings.max_results).collect(), selected: 0, settings: settings.clone(), } } - pub fn selected(&self) -> &entry::StoreEntry { - &self.entries[self.selected] + pub fn selected(&self) -> entry::EntryId { + self.entries[self.selected] } pub fn handle_kb(&mut self, event: keyboard::Event) { @@ -77,13 +75,15 @@ impl SearchResults { pub fn view<'a, F, Message, Renderer>( &'a self, searchtext: &str, + store: &'a store::Store, f: F, ) -> iced_native::Element<'a, Message, Renderer> where - F: 'static + Copy + Fn(entry::StoreEntry) -> Message, + F: 'static + Copy + Fn(entry::EntryId) -> Message, Message: 'static + Clone, Renderer: iced_native::renderer::Renderer + 'a, Renderer: iced_native::text::Renderer, + Renderer: iced_native::image::Renderer, { // if we dont have any entries, return an empty search results // (if we dont do this, the empty column will still show its @@ -94,12 +94,18 @@ impl SearchResults { let mut column = widget::column::Column::new().padding(PADDING); for (i, e) in self.entries.iter().enumerate() { + let entry = store.get(*e); // unwrap will never panic since UI_MAX_RESULTS is const - let entry_widget = e.build_entry(f, searchtext, &self.settings, i == self.selected); + let entry_widget = + entry.build_entry(f, searchtext, &self.settings, i == self.selected, *e); column = column.push(entry_widget); } let element: iced_native::Element<'_, _, _> = column.into(); element } + + pub fn entries(&self) -> &[entry::EntryId] { + &self.entries + } } diff --git a/src/store.rs b/src/store.rs index 53f236a..37673e9 100644 --- a/src/store.rs +++ b/src/store.rs @@ -11,7 +11,7 @@ use toml; -use crate::entry; +use crate::{entry, icon}; #[derive(Debug, Default, Clone)] pub struct Store { @@ -29,7 +29,15 @@ impl Store { }) } - pub fn find_matches(&self, query: &str) -> Vec<&entry::StoreEntry> { + pub fn get(&self, id: entry::EntryId) -> &entry::StoreEntry { + &self.entries[id] + } + + pub fn get_mut(&mut self, id: entry::EntryId) -> &mut entry::StoreEntry { + &mut self.entries[id] + } + + pub fn find_matches(&self, query: &str) -> Vec { // get indicies of all entries with scores greater than zero let mut matches: Vec<_> = self .entries @@ -44,7 +52,18 @@ impl Store { matches.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); // get references to entries in sorted order - matches.iter().map(|s| &self.entries[s.0]).collect() + matches.iter().map(|s| s.0).collect() + } + + pub fn load_icons(&mut self, entries: &[entry::EntryId], icache: &mut icon::IconCache) { + for e in entries { + let entry = &mut self.entries[*e]; + if !entry.icon_loaded() { + if let Some(icon) = icache.get(&entry.icontype()) { + entry.icon(icon); + } + } + } } pub fn len(&self) -> usize { @@ -114,6 +133,7 @@ pub mod tests { }); for (l, r) in matches.into_iter().zip(r_entries) { + let l = store.get(l); assert_eq!( l, r, diff --git a/src/ui.rs b/src/ui.rs index f1c7db5..d0098a5 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,7 +1,7 @@ // eventually the jolly main window logic will move here out of main // but for now it will just hold settings. -use crate::{entry, theme}; +use crate::{entry, icon, theme}; use csscolorparser; use iced; use serde; @@ -21,6 +21,7 @@ pub struct UISettings { pub search: SearchSettings, pub entry: entry::EntrySettings, pub max_results: usize, + pub icon: icon::IconSettings, } #[derive(serde::Deserialize, Debug, Clone, PartialEq, Default)] @@ -61,6 +62,7 @@ impl Default for UISettings { search: SearchSettings::default(), entry: Default::default(), max_results: 5, + icon: Default::default(), } } }