diff --git a/.gitignore b/.gitignore index 6c8cfa9..555aecb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ /target -/assets/ui/.dist - -/scripts/builds -/scripts/appimage/dist -/scripts/appimage/*.AppImage +/assets/locales/TODO.* diff --git a/.gitmodules b/.gitmodules index 177f529..c332b4a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,3 @@ -[submodule "blueprint-compiler"] - path = blueprint-compiler - url = https://gitlab.gnome.org/jwestman/blueprint-compiler -[submodule "anime-game-core"] - path = anime-game-core - url = https://github.com/an-anime-team/anime-game-core -[submodule "components"] - path = components - url = https://github.com/an-anime-team/components +[submodule "anime-launcher-sdk"] + path = anime-launcher-sdk + url = https://github.com/an-anime-team/anime-launcher-sdk diff --git a/Cargo.lock b/Cargo.lock index 9f8685f..62e3eac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,9 +22,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -32,10 +32,11 @@ dependencies = [ [[package]] name = "anime-game-core" version = "1.3.2" +source = "git+https://github.com/an-anime-team/anime-game-core#ea02d1c482da9701f4a5517f05e3e2ad68b71295" dependencies = [ "anyhow", "bzip2", - "cached 0.40.0", + "cached", "curl", "flate2", "fs_extra", @@ -52,131 +53,51 @@ dependencies = [ [[package]] name = "anime-game-launcher" -version = "1.2.4" +version = "2.0.0" dependencies = [ - "anime-game-core", - "anyhow", - "cached 0.41.0", - "dirs", + "anime-launcher-sdk", + "fluent-templates", "glib-build-tools", "gtk4", "lazy_static", "libadwaita", + "relm4", + "tracing", + "tracing-subscriber", + "unic-langid", +] + +[[package]] +name = "anime-launcher-sdk" +version = "0.1.0" +dependencies = [ + "anime-game-core", + "anyhow", + "dirs", + "lazy_static", "md5", - "rfd", "serde", "serde_json", - "wait_not_await", "wincompatlib", ] [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] -name = "ashpd" -version = "0.3.2" +name = "arc-swap" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dcc8ed0b5211687437636d8c95f6a608f4281d142101b3b5d314b38bfadd40f" -dependencies = [ - "enumflags2", - "futures", - "rand", - "serde", - "serde_repr", - "zbus", -] - -[[package]] -name = "async-broadcast" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61" -dependencies = [ - "event-listener", - "futures-core", - "parking_lot", -] - -[[package]] -name = "async-channel" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "once_cell", - "slab", -] - -[[package]] -name = "async-io" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" -dependencies = [ - "autocfg", - "concurrent-queue", - "futures-lite", - "libc", - "log", - "once_cell", - "parking", - "polling", - "slab", - "socket2", - "waker-fn", - "winapi", -] - -[[package]] -name = "async-lock" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-recursion" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-task" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164" [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" dependencies = [ "proc-macro2", "quote", @@ -197,15 +118,15 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" -version = "1.0.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] name = "bitflags" @@ -213,26 +134,29 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] [[package]] -name = "bumpalo" -version = "3.10.0" +name = "bstr" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byteorder" @@ -261,12 +185,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - [[package]] name = "cached" version = "0.40.0" @@ -286,25 +204,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "cached" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec6d20b3d24b6c74e2c5331d2d3d8d1976a9883c7da179aa851afa4c90d62e36" -dependencies = [ - "async-trait", - "async_once", - "cached_proc_macro", - "cached_proc_macro_types", - "futures", - "hashbrown", - "instant", - "lazy_static", - "once_cell", - "thiserror", - "tokio", -] - [[package]] name = "cached_proc_macro" version = "0.15.0" @@ -325,9 +224,9 @@ checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" [[package]] name = "cairo-rs" -version = "0.16.1" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9ee4a4ca9239c9a839453dce04b7ddee2f859ec4cd7acd1f5703b68db549c" +checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d" dependencies = [ "bitflags", "cairo-sys-rs", @@ -339,9 +238,9 @@ dependencies = [ [[package]] name = "cairo-sys-rs" -version = "0.16.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5119ea655ec777b523f0b57279e70f8a4542f61b0e98a48f892b4ef043fd4c5d" +checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421" dependencies = [ "glib-sys", "libc", @@ -350,18 +249,18 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" dependencies = [ "jobserver", ] [[package]] name = "cfg-expr" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" +checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" dependencies = [ "smallvec", ] @@ -381,15 +280,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "concurrent-queue" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" -dependencies = [ - "cache-padded", -] - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -404,9 +294,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -443,49 +333,41 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", - "once_cell", + "memoffset 0.7.1", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] -[[package]] -name = "cty" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" - [[package]] name = "curl" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ "curl-sys", "libc", @@ -498,9 +380,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.55+curl-7.83.1" +version = "0.4.59+curl-7.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" +checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" dependencies = [ "cc", "libc", @@ -547,22 +429,11 @@ dependencies = [ "syn", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "digest" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -590,32 +461,10 @@ dependencies = [ ] [[package]] -name = "dispatch" -version = "0.2.0" +name = "displaydoc" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "either" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" - -[[package]] -name = "enumflags2" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" dependencies = [ "proc-macro2", "quote", @@ -623,19 +472,16 @@ dependencies = [ ] [[package]] -name = "event-listener" -version = "2.5.3" +name = "doc-comment" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] -name = "fastrand" +name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "field-offset" @@ -643,38 +489,140 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" dependencies = [ - "memoffset", + "memoffset 0.6.5", "rustc_version", ] [[package]] name = "filetime" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" dependencies = [ "cfg-if", "libc", "redox_syscall", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", ] +[[package]] +name = "fluent" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f69378194459db76abd2ce3952b790db103ceb003008d3d50d97c41ff847a7" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78" +dependencies = [ + "thiserror", +] + +[[package]] +name = "fluent-template-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec7592cd1f45c1afe9084ce59c62a3a7c266c125c4c2ec97e95b0563c4aa914" +dependencies = [ + "flume", + "ignore", + "once_cell", + "proc-macro2", + "quote", + "syn", + "unic-langid", +] + +[[package]] +name = "fluent-templates" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3ef2c2152757885365abce32ddf682746062f1b6b3c0824a29fbed6ee4d080" +dependencies = [ + "arc-swap", + "fluent", + "fluent-bundle", + "fluent-langneg", + "fluent-syntax", + "fluent-template-macros", + "flume", + "heck", + "ignore", + "intl-memoizer", + "lazy_static", + "log", + "once_cell", + "serde_json", + "snafu", + "unic-langid", +] + +[[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 0.9.4", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "fs_extra" version = "1.2.0" @@ -683,9 +631,9 @@ checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" [[package]] name = "futures" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -698,9 +646,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -708,15 +656,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -725,30 +673,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" - -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -757,21 +690,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -787,9 +720,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.16.0" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fb526c8c3a075eda15f961820edf3e15fe18576ac4fbabbb324e4cc6c421e6" +checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05" dependencies = [ "bitflags", "gdk-pixbuf-sys", @@ -800,9 +733,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.16.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df12d15c10c3c5a84d9fb4ba0e27659f6a2bdee4f27f8b17126da15d5ddd3f2" +checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016" dependencies = [ "gio-sys", "glib-sys", @@ -813,9 +746,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.5.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fe07f362c977c4684d1136a29f097208b3ccb2013ab6f441a3c60a046fd358" +checksum = "272db1bbb9b152ea1fea946f9d464085c86cfe14cafba450d7defa433caff8ec" dependencies = [ "bitflags", "cairo-rs", @@ -829,9 +762,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.5.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddcf9e3ab5f237bb641e7f2fccc4b26d5b86f111f0d62e27d452dc24964541c2" +checksum = "45b571f36b889ab529b2e173248dafe83d75c703f5685b9845e490c7994ae309" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -846,9 +779,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -856,20 +789,22 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] name = "gio" -version = "0.16.2" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1debf8d0315d69be0153aa76249db3c858ef69b7778ad3cc669e6d370c485" +checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092" dependencies = [ "bitflags", "futures-channel", @@ -887,9 +822,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.16.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6da1bba9d3f2ab13a6e9932c40f240dc99ebc9f0bdc35cfb130d1a3df36f374c" +checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" dependencies = [ "glib-sys", "gobject-sys", @@ -900,9 +835,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.16.2" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5abffa711471e015eb93d65d6ea20e7e9f6f7951fc0a1042280439319b2de06" +checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f" dependencies = [ "bitflags", "futures-channel", @@ -922,15 +857,15 @@ dependencies = [ [[package]] name = "glib-build-tools" -version = "0.16.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b70aae3c71f39e3d902e2e842b316e5271f504c3495ad4991aa6e7b7b11dad55" +checksum = "251935cb159350458a627642b0852a7fb8e027e3c5916dc2cebcd70f025de3fc" [[package]] name = "glib-macros" -version = "0.16.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e195c1311fa6b04d7b896ea39385f6bd60ef5d25bf74a7c11c8c3f94f6c1a572" +checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf" dependencies = [ "anyhow", "heck", @@ -943,19 +878,32 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.16.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b33357bb421a77bd849f6a0bfcaf3b4b256a2577802971bb5dd522d530f27021" +checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" dependencies = [ "libc", "system-deps", ] [[package]] -name = "gobject-sys" -version = "0.16.0" +name = "globset" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63ca11a57400f3d4fda594e002844be47900c9fb8b29e2155c6e37a1f24e51b3" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "gobject-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" dependencies = [ "glib-sys", "libc", @@ -964,9 +912,9 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.16.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a8de4506a64776d90fedf9c28fdca5a7127f8cc9c78976e8184ac6f42685d8" +checksum = "95ecb4d347e6d09820df3bdfd89a74a8eec07753a06bb92a3aac3ad31d04447b" dependencies = [ "glib", "graphene-sys", @@ -975,9 +923,9 @@ dependencies = [ [[package]] name = "graphene-sys" -version = "0.16.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c952f764f02f8546fcc5d014bc78aa704c6d453c828c8b429121f704349163" +checksum = "b9aa82337d3972b4eafdea71e607c23f47be6f27f749aab613f1ad8ddbe6dcd6" dependencies = [ "glib-sys", "libc", @@ -987,9 +935,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.5.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc2b86c751a7fe9aad0fdba85937a6aace3a8453e0e2a08d2a31ce4bb8ae55" +checksum = "4053293b79099bdfecd9ab0d811d118a0eafce613dfe0b26075419d955f1f652" dependencies = [ "bitflags", "cairo-rs", @@ -1003,9 +951,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.5.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb53e25cbbe3fa8e3e9db7c06d65085086fadbec4cd0aa567b2e2a4917db83d" +checksum = "08e0642edffdb35028d7d67b830678da98844216b6442e11eee52c91ad2a6dc2" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -1019,9 +967,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.5.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47223ddb27033731b71ea841d1b878bd87a275a865f1df60b41505f9e4933d64" +checksum = "8954da3659ff1cb35aa95110021b33fadcd8e306e8fe41f32146ffa009665a79" dependencies = [ "bitflags", "cairo-rs", @@ -1042,9 +990,9 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.5.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5eb86364b216ee8c497b1121831168fb25130d3378495a135f8e5c1972db7b" +checksum = "58138cd3c595e04f82df050390aa7d2bd093795ce569e5f1d49eb496ef67fe7b" dependencies = [ "anyhow", "proc-macro-crate", @@ -1056,9 +1004,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.5.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f04bd0b63d999a36ae53a916ee4b20ea64a3ef4732ca8a98b1fde4a22c1476c" +checksum = "ef29e09e055b2f2550eb1882caa6961a1ae3c971a70bcb25cb9d5ab6cbd63821" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1087,19 +1035,13 @@ checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hmac" version = "0.12.1" @@ -1115,6 +1057,24 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "instant" version = "0.1.12" @@ -1125,25 +1085,44 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.2" +name = "intl-memoizer" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -1162,9 +1141,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libadwaita" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed92f031cf7f3d501b84f41e4d05aed6ebfd8eed59a8fc0cccbf51359e92c8e3" +checksum = "9dfa0722d4f1724f661cbf668c273c5926296ca411ed3814e206f8fd082b6c48" dependencies = [ "bitflags", "futures-channel", @@ -1181,9 +1160,9 @@ dependencies = [ [[package]] name = "libadwaita-sys" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ec4243e86fb53d06df2461d543529a640c9a0fba2d4cc850b70e11a85f9d952" +checksum = "de902982372b454a0081d7fd9dd567b37b73ae29c8f6da1820374d345fd95d5b" dependencies = [ "gdk4-sys", "gio-sys", @@ -1197,9 +1176,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.126" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libz-sys" @@ -1215,9 +1194,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -1234,24 +1213,15 @@ dependencies = [ [[package]] name = "lzma-sys" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e06754c4acf47d49c727d5665ca9fb828851cda315ed3bd51edd148ef78a8772" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" dependencies = [ "cc", "libc", "pkg-config", ] -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "md5" version = "0.7.0" @@ -1274,25 +1244,30 @@ dependencies = [ ] [[package]] -name = "miniz_oxide" -version = "0.5.3" +name = "memoffset" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] -name = "nix" -version = "0.23.1" +name = "nanorand" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "memoffset", + "getrandom", ] [[package]] @@ -1305,10 +1280,20 @@ dependencies = [ ] [[package]] -name = "num_cpus" -version = "1.13.1" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi", "libc", @@ -1335,49 +1320,11 @@ dependencies = [ "syn", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - [[package]] name = "once_cell" -version = "1.14.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "opaque-debug" @@ -1393,9 +1340,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.74" +version = "0.9.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" dependencies = [ "autocfg", "cc", @@ -1405,20 +1352,16 @@ dependencies = [ ] [[package]] -name = "ordered-stream" -version = "0.0.1" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44630c059eacfd6e08bdaa51b1db2ce33119caa4ddc1235e923109aa5f25ccb1" -dependencies = [ - "futures-core", - "pin-project-lite", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pango" -version = "0.16.0" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7208c60f224cf6e44c551df5ee2ef38f9da0fd29d7c5a0402000b8ab0520e798" +checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" dependencies = [ "bitflags", "gio", @@ -1430,9 +1373,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.16.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "922441c228366ed98d3534b87bc7c987c50564094c3abbc3513717786419252d" +checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" dependencies = [ "glib-sys", "gobject-sys", @@ -1440,40 +1383,11 @@ dependencies = [ "system-deps", ] -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - [[package]] name = "password-hash" -version = "0.3.2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", "rand_core", @@ -1482,9 +1396,9 @@ dependencies = [ [[package]] name = "pbkdf2" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest", "hmac", @@ -1494,13 +1408,34 @@ dependencies = [ [[package]] name = "pest" -version = "2.1.3" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4" dependencies = [ + "thiserror", "ucd-trie", ] +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1515,42 +1450,17 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" - -[[package]] -name = "polling" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" -dependencies = [ - "autocfg", - "cfg-if", - "libc", - "log", - "wepoll-ffi", - "winapi", -] - -[[package]] -name = "pollster" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ + "once_cell", "thiserror", "toml", ] @@ -1580,79 +1490,50 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.40" +name = "proc-macro-hack" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "raw-window-handle" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" -dependencies = [ - "cty", -] +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -1662,9 +1543,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -1693,40 +1574,37 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "relm4" +version = "0.5.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "775146054bd0dd0e719b882efadeb5d7372e7fdeb69779b2e137937fc679d62c" dependencies = [ - "winapi", + "async-trait", + "flume", + "fragile", + "futures", + "gtk4", + "libadwaita", + "once_cell", + "relm4-macros", + "tokio", + "tracing", ] [[package]] -name = "rfd" -version = "0.10.0" +name = "relm4-macros" +version = "0.5.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +checksum = "61467ebc57ae11ec196fc851e7f976d17b5ec29ed293db5cbc8a3781fb2b1f06" dependencies = [ - "ashpd", - "block", - "dispatch", - "js-sys", - "log", - "objc", - "objc-foundation", - "objc_id", - "pollster", - "raw-window-handle", - "urlencoding", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1738,12 +1616,18 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.3.3" @@ -1755,9 +1639,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.6" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" dependencies = [ "log", "ring", @@ -1791,9 +1675,18 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "schannel" @@ -1802,7 +1695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -1821,6 +1714,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "self_cell" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af" + [[package]] name = "semver" version = "0.11.0" @@ -1841,18 +1740,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.137" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8" dependencies = [ "proc-macro2", "quote", @@ -1861,9 +1760,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -1871,70 +1770,78 @@ dependencies = [ ] [[package]] -name = "serde_repr" -version = "0.1.9" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "snafu" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0656e7e3ffb70f6c39b3c2a86332bb74aa3c679da781642590f3c1118c5045" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "475b3bbe5245c26f2d8a6f62d67c1f30eb9fffeccee721c45d162c3ebbdf81b2" +dependencies = [ + "heck", "proc-macro2", "quote", "syn", ] -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - -[[package]] -name = "sha1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - -[[package]] -name = "sha2" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "slab" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" - -[[package]] -name = "smallvec" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2" - [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -1947,10 +1854,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] -name = "static_assertions" -version = "1.1.0" +name = "spin" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +dependencies = [ + "lock_api", +] [[package]] name = "strsim" @@ -1966,9 +1876,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -1992,9 +1902,9 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.0.2" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709" +checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" dependencies = [ "cfg-expr", "heck", @@ -2014,34 +1924,20 @@ dependencies = [ "xattr", ] -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -2049,39 +1945,68 @@ dependencies = [ ] [[package]] -name = "time" -version = "0.3.11" +name = "thread_local" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", - "libc", - "num_threads", + "serde", + "time-core", "time-macros", ] [[package]] -name = "time-macros" -version = "0.2.4" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aeafdfd935e4a7fe16a91ab711fa52d54df84f9c8f7ca5837a9d1d902ef4c2" +dependencies = [ + "displaydoc", +] [[package]] name = "tokio" -version = "1.20.1" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", + "num_cpus", "pin-project-lite", "tokio-macros", + "windows-sys 0.42.0", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -2090,18 +2015,18 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" dependencies = [ "serde", ] [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", @@ -2111,9 +2036,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -2122,40 +2047,108 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "type-map" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46" +dependencies = [ + "rustc-hash", ] [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] -name = "uds_windows" -version = "1.0.2" +name = "unic-langid" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +checksum = "398f9ad7239db44fd0f80fe068d12ff22d78354080332a5077dc6f52f14dcf2f" dependencies = [ - "tempfile", - "winapi", + "unic-langid-impl", + "unic-langid-macros", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35bfd2f2b8796545b55d7d3fd3e89a0613f68a0d1c8bc28cb7ff96b411a35ff" +dependencies = [ + "tinystr", +] + +[[package]] +name = "unic-langid-macros" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "055e618bf694161ffff0466d95cef3e1a5edc59f6ba1888e97801f2b4ebdc4fe" +dependencies = [ + "proc-macro-hack", + "tinystr", + "unic-langid-impl", + "unic-langid-macros-impl", +] + +[[package]] +name = "unic-langid-macros-impl" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f5cdec05b907f4e2f6843f4354f4ce6a5bebe1a56df320a49134944477ce4d8" +dependencies = [ + "proc-macro-hack", + "quote", + "syn", + "unic-langid-impl", ] [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "untrusted" @@ -2164,10 +2157,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] -name = "urlencoding" -version = "2.1.2" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vcpkg" @@ -2177,9 +2170,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version-compare" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[package]] name = "version_check" @@ -2188,16 +2181,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "wait_not_await" -version = "0.2.1" +name = "walkdir" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65aeea435f64e2f00b55f0f3ce1aa44cbe7907da566ceabded8334c4434a9d62" - -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] [[package]] name = "wasi" @@ -2207,9 +2199,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2217,36 +2209,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2254,9 +2234,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -2267,15 +2247,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -2291,15 +2271,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2316,6 +2287,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2331,19 +2311,6 @@ dependencies = [ "regex", ] -[[package]] -name = "windows" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" -dependencies = [ - "windows_aarch64_msvc 0.37.0", - "windows_i686_gnu 0.37.0", - "windows_i686_msvc 0.37.0", - "windows_x86_64_gnu 0.37.0", - "windows_x86_64_msvc 0.37.0", -] - [[package]] name = "windows-sys" version = "0.36.1" @@ -2357,6 +2324,27 @@ dependencies = [ "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -2365,9 +2353,9 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_aarch64_msvc" -version = "0.37.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" @@ -2377,9 +2365,9 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_gnu" -version = "0.37.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" @@ -2389,9 +2377,9 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_i686_msvc" -version = "0.37.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" @@ -2401,9 +2389,15 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_gnu" -version = "0.37.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" @@ -2413,9 +2407,9 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "windows_x86_64_msvc" -version = "0.37.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" [[package]] name = "xattr" @@ -2444,75 +2438,11 @@ dependencies = [ "lzma-sys", ] -[[package]] -name = "zbus" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8f1a037b2c4a67d9654dc7bdfa8ff2e80555bbefdd3c1833c1d1b27c963a6b" -dependencies = [ - "async-broadcast", - "async-channel", - "async-executor", - "async-io", - "async-lock", - "async-recursion", - "async-task", - "async-trait", - "byteorder", - "derivative", - "dirs", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "lazy_static", - "nix", - "once_cell", - "ordered-stream", - "rand", - "serde", - "serde_repr", - "sha1 0.6.1", - "static_assertions", - "tracing", - "uds_windows", - "winapi", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8fb5186d1c87ae88cf234974c240671238b4a679158ad3b94ec465237349a6" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "regex", - "syn", -] - -[[package]] -name = "zbus_names" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a408fd8a352695690f53906dc7fd036be924ec51ea5e05666ff42685ed0af5" -dependencies = [ - "serde", - "static_assertions", - "zvariant", -] - [[package]] name = "zip" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf225bcf73bb52cbb496e70475c7bd7a3f769df699c0020f6c7bd9a96dcf0b8d" +checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080" dependencies = [ "aes", "byteorder", @@ -2523,25 +2453,25 @@ dependencies = [ "flate2", "hmac", "pbkdf2", - "sha1 0.10.1", + "sha1", "time", "zstd", ] [[package]] name = "zstd" -version = "0.10.2+zstd.1.5.2" +version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4a6bd64f22b5e3e94b4e238669ff9f10815c27a5180108b849d24174a83847" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.6+zstd.1.5.2" +version = "5.0.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b61c51bb270702d6167b8ce67340d2754b088d0c091b06e593aa772c3ee9bb" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" dependencies = [ "libc", "zstd-sys", @@ -2549,36 +2479,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.3+zstd.1.5.2" +version = "2.0.4+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0" dependencies = [ "cc", "libc", ] - -[[package]] -name = "zvariant" -version = "3.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd68e4e6432ef19df47d7e90e2e72b5e7e3d778e0ae3baddf12b951265cc758" -dependencies = [ - "byteorder", - "enumflags2", - "libc", - "serde", - "static_assertions", - "zvariant_derive", -] - -[[package]] -name = "zvariant_derive" -version = "3.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08e977eaa3af652f63d479ce50d924254ad76722a6289ec1a1eac3231ca30430" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 3622d3c..13c98fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anime-game-launcher" -version = "1.2.4" +version = "2.0.0" description = "Anime Game launcher" authors = ["Nikita Podvirnyy "] license = "GPL-3.0" @@ -16,19 +16,16 @@ opt-level = 3 glib-build-tools = "0.16" [dependencies] +relm4 = { version = "0.5.0-rc.1", features = ["macros", "libadwaita"] } gtk = { package = "gtk4", version = "0.5", features = ["v4_8"] } adw = { package = "libadwaita", version = "0.2", features = ["v1_2"] } -rfd = { version = "0.10", features = ["xdg-portal"], default-features = false } -anime-game-core = { path = "anime-game-core", features = ["all", "static", "genshin"] } -wincompatlib = { version = "0.1.3", features = ["dxvk"] } +anime-launcher-sdk = { path = "anime-launcher-sdk" } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +tracing = "0.1" +tracing-subscriber = "0.3" + +fluent-templates = "0.8" +unic-langid = "0.9" -dirs = "4.0.0" -wait_not_await = "0.2.1" lazy_static = "1.4.0" -anyhow = "1.0" -md5 = "0.7" -cached = { version = "0.41", features = ["proc_macro"] } diff --git a/anime-game-core b/anime-game-core deleted file mode 160000 index ea02d1c..0000000 --- a/anime-game-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ea02d1c482da9701f4a5517f05e3e2ad68b71295 diff --git a/anime-launcher-sdk b/anime-launcher-sdk new file mode 160000 index 0000000..f85152c --- /dev/null +++ b/anime-launcher-sdk @@ -0,0 +1 @@ +Subproject commit f85152c83a715957b553e5fb1f2c6b2da93f2f89 diff --git a/assets/anime-game-launcher.desktop b/assets/anime-game-launcher.desktop deleted file mode 100644 index 06e4a22..0000000 --- a/assets/anime-game-launcher.desktop +++ /dev/null @@ -1,7 +0,0 @@ -[Desktop Entry] -Name=An Anime Game Launcher GTK -Icon=icon -Exec=AppRun -Type=Application -Categories=Game -Terminal=false \ No newline at end of file diff --git a/assets/locales/en/main.ftl b/assets/locales/en/main.ftl new file mode 100644 index 0000000..0c152ac --- /dev/null +++ b/assets/locales/en/main.ftl @@ -0,0 +1,61 @@ +custom = Custom +none = None + + + +launch = Launch + +preferences = Preferences + +general = General + +launcher-language = Launcher language + +game-voiceovers = Game voiceovers +english = English +japanese = Japanese +korean = Korean +chinese = Chinese + +repair-game = Repair game + +status = Status + +game-version = Game version +patch-version = Patch version + +enhancements = Enhancements + +wine = Wine + +synchronization = Synchronization +wine-sync-description = Technology used to synchronize inner wine events + +language = Language +wine-lang-description = Language used in the wine environment. Can fix keyboard layout issues +system = System + +borderless-window = Borderless window +virtual-desktop = Virtual desktop + +game = Game + +hud = HUD + +fsr = FSR +fsr-description = Upscales game to your monitor size. To use select lower resolution in the game's settings and press Alt+Enter +ultra-quality = Ultra quality +quality = Quality +balanced = Balanced +performance = Performance + +gamemode = Gamemode +gamemode-description = Prioritize the game over the rest of the processes + +fps-unlocker = FPS Unlocker + +enabled = Enabled +fps-unlocker-description = Remove frames rendering limitation by modifying the game's memory. Can be detected by the anti-cheat + +power-saving = Power saving +power-saving-description = Automatically set the FPS limit to 10 and low process priority upon losing focus to the game (e.g. tabbing out) \ No newline at end of file diff --git a/assets/locales/ru/main.ftl b/assets/locales/ru/main.ftl new file mode 100644 index 0000000..d4d4c3c --- /dev/null +++ b/assets/locales/ru/main.ftl @@ -0,0 +1,61 @@ +custom = Свое значение +none = Нет + + + +launch = Запустить + +preferences = Настройки + +general = Основное + +launcher-language = Язык лаунчера + +game-voiceovers = Язык озвучки +english = Английский +japanese = Японский +korean = Корейский +chinese = Китайский + +repair-game = Починить игру + +status = Статус + +game-version = Версия игры +patch-version = Версия патча + +enhancements = Улучшения + +wine = Wine + +synchronization = Синхронизация +wine-sync-description = Технология, используемая для синхронизации внутренних событий Wine + +language = Язык +wine-lang-description = Язык, используемый в окружении Wine. Может исправить проблемы с раскладкой клавиатуры +system = Системный + +borderless-window = Окно без рамок +virtual-desktop = Виртуальный рабочий стол + +game = Игра + +hud = HUD + +fsr = FSR +fsr-description = Для использования установите меньшее разрешение в настройках игры и нажмите Alt+Enter +ultra-quality = Ультра +quality = Хорошо +balanced = Сбалансированно +performance = Производительно + +gamemode = Gamemode +gamemode-description = Выделять игре приоритет перед остальными процессами + +fps-unlocker = FPS Unlocker + +enabled = Включен +fps-unlocker-description = Убрать ограничение количества кадров модифицируя память игры. Может быть обнаружено античитом + +power-saving = Энергосбережение +power-saving-description = Автоматически устанавливать предел количества кадров до 10 и снижать приоритет процесса игры когда она не находится в фокусе \ No newline at end of file diff --git a/assets/resources.xml b/assets/resources.xml index d112228..4be0b90 100644 --- a/assets/resources.xml +++ b/assets/resources.xml @@ -1,26 +1,6 @@ - + images/icon.png - - ui/.dist/main.ui - ui/.dist/first_run.ui - ui/.dist/preferences.ui - - - ui/.dist/first_run/welcome.ui - ui/.dist/first_run/dependencies.ui - ui/.dist/first_run/tos_warning.ui - ui/.dist/first_run/default_paths.ui - ui/.dist/first_run/voice_packages.ui - ui/.dist/first_run/download_components.ui - ui/.dist/first_run/finish.ui - - - ui/.dist/preferences/general.ui - ui/.dist/preferences/enhancements.ui - ui/.dist/preferences/gamescope.ui - ui/.dist/preferences/environment.ui - - + \ No newline at end of file diff --git a/assets/styles.css b/assets/styles.css deleted file mode 100644 index 72a6ae7..0000000 --- a/assets/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -progressbar > text { - margin-bottom: 4px; -} diff --git a/assets/ui/first_run.blp b/assets/ui/first_run.blp deleted file mode 100644 index 0123dfb..0000000 --- a/assets/ui/first_run.blp +++ /dev/null @@ -1,28 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Adw.ApplicationWindow window { - default-width: 780; - default-height: 560; - - content: Adw.ToastOverlay toast_overlay { - Gtk.Box { - orientation: vertical; - - Adw.HeaderBar { - title-widget: Adw.WindowTitle { - title: "An Anime Game Launcher"; - }; - } - - Adw.Carousel carousel { - allow-mouse-drag: false; - } - - Adw.CarouselIndicatorDots { - carousel: carousel; - height-request: 32; - } - } - }; -} diff --git a/assets/ui/first_run/default_paths.blp b/assets/ui/first_run/default_paths.blp deleted file mode 100644 index 8174c21..0000000 --- a/assets/ui/first_run/default_paths.blp +++ /dev/null @@ -1,73 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Gtk.Box page { - orientation: vertical; - hexpand: true; - - Adw.PreferencesPage { - Adw.PreferencesGroup { - Gtk.Label { - label: "Set default paths"; - - styles ["title-1"] - } - } - - Adw.PreferencesGroup { - vexpand: true; - valign: center; - - Adw.ActionRow runners_folder { - title: "Runners saving folder"; - activatable: true; - } - - Adw.ActionRow dxvk_folder { - title: "DXVK saving folder"; - activatable: true; - } - - Adw.ActionRow prefix_folder { - title: "Wine prefix folder"; - activatable: true; - } - - Adw.ActionRow game_folder { - title: "Game installation folder"; - activatable: true; - } - - Adw.ActionRow patch_folder { - title: "Patch storing folder"; - activatable: true; - } - - Adw.ActionRow temp_folder { - title: "Temp data saving folder"; - activatable: true; - } - } - - Adw.PreferencesGroup { - vexpand: true; - valign: center; - - Gtk.Box { - orientation: horizontal; - spacing: 8; - halign: center; - - Gtk.Button continue_button { - label: "Continue"; - - styles ["suggested-action"] - } - - Gtk.Button exit_button { - label: "Exit"; - } - } - } - } -} diff --git a/assets/ui/first_run/dependencies.blp b/assets/ui/first_run/dependencies.blp deleted file mode 100644 index fdcf1c2..0000000 --- a/assets/ui/first_run/dependencies.blp +++ /dev/null @@ -1,102 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Gtk.Box page { - orientation: vertical; - hexpand: true; - - Adw.PreferencesPage { - Adw.PreferencesGroup { - Gtk.Label { - label: "You're missing some dependencies!"; - margin-top: 32; - - styles ["title-1"] - } - - Gtk.Label { - label: "You must install some packages to your system before continue installation process"; - - wrap: true; - justify: center; - margin-top: 32; - } - } - - Adw.PreferencesGroup { - vexpand: true; - valign: center; - - Gtk.Box { - orientation: vertical; - spacing: 16; - - Gtk.Box pkg_pacman { - orientation: vertical; - spacing: 16; - visible: false; - - Gtk.Label { - label: "Arch (pacman)"; - } - - Gtk.Entry { - text: "sudo pacman -Syu git xdelta3"; - editable: false; - } - } - - Gtk.Box pkg_apt { - orientation: vertical; - spacing: 16; - visible: false; - - Gtk.Label { - label: "Debian / Ubuntu (apt)"; - } - - Gtk.Entry { - text: "sudo apt install git xdelta3"; - editable: false; - } - } - - Gtk.Box pkg_dnf { - orientation: vertical; - spacing: 16; - visible: false; - - Gtk.Label { - label: "Fedora (dnf)"; - } - - Gtk.Entry { - text: "sudo dnf install git xdelta"; - editable: false; - } - } - } - } - - Adw.PreferencesGroup { - vexpand: true; - valign: center; - - Gtk.Box { - orientation: horizontal; - spacing: 8; - halign: center; - - Gtk.Button check_button { - label: "Check"; - - styles ["suggested-action"] - } - - Gtk.Button exit_button { - label: "Exit"; - } - } - } - } -} diff --git a/assets/ui/first_run/download_components.blp b/assets/ui/first_run/download_components.blp deleted file mode 100644 index 138e158..0000000 --- a/assets/ui/first_run/download_components.blp +++ /dev/null @@ -1,79 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Gtk.Box page { - orientation: vertical; - hexpand: true; - - Adw.PreferencesPage { - Adw.PreferencesGroup { - Gtk.Label { - label: "Download default components"; - margin-top: 16; - - styles ["title-1"] - } - } - - Adw.PreferencesGroup { - vexpand: true; - valign: center; - - Adw.ComboRow wine_version { - title: "Wine version"; - } - - Adw.ComboRow dxvk_version { - title: "DXVK version"; - } - } - - Adw.PreferencesGroup buttons_group { - vexpand: true; - valign: center; - - Gtk.Box { - orientation: horizontal; - spacing: 8; - halign: center; - - Gtk.Button download_button { - label: "Download"; - - styles ["suggested-action"] - } - - Gtk.Button exit_button { - label: "Exit"; - } - } - } - - Adw.PreferencesGroup progress_bar_group { - vexpand: true; - valign: center; - visible: false; - - Gtk.Box { - halign: center; - margin-top: 64; - spacing: 20; - - Gtk.ProgressBar progress_bar { - text: "Downloading: 37% (3.7 of 10 GB)"; - show-text: true; - - width-request: 360; - fraction: 0.37; - valign: center; - } - - Gtk.Button { - label: "Pause"; - sensitive: false; - tooltip-text: "Work in progress"; - } - } - } - } -} diff --git a/assets/ui/first_run/finish.blp b/assets/ui/first_run/finish.blp deleted file mode 100644 index 615680b..0000000 --- a/assets/ui/first_run/finish.blp +++ /dev/null @@ -1,47 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Gtk.Box page { - orientation: vertical; - hexpand: true; - - Adw.PreferencesPage { - Adw.PreferencesGroup { - Gtk.Label { - label: "Downloading finished!"; - margin-top: 64; - - styles ["title-1"] - } - - Gtk.Label { - label: "All the basic components were downloaded. Now you can restart the launcher and download the game. Welcome to our club!"; - - wrap: true; - justify: center; - margin-top: 32; - } - } - - Adw.PreferencesGroup { - vexpand: true; - valign: center; - - Gtk.Box { - orientation: horizontal; - spacing: 8; - halign: center; - - Gtk.Button restart_button { - label: "Restart"; - - styles ["suggested-action"] - } - - Gtk.Button exit_button { - label: "Exit"; - } - } - } - } -} diff --git a/assets/ui/first_run/tos_warning.blp b/assets/ui/first_run/tos_warning.blp deleted file mode 100644 index 1990030..0000000 --- a/assets/ui/first_run/tos_warning.blp +++ /dev/null @@ -1,94 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Gtk.Box page { - orientation: vertical; - hexpand: true; - - Adw.PreferencesPage { - Adw.PreferencesGroup { - Gtk.Label { - label: "ToS violation warning"; - margin-top: 8; - - styles ["title-1"] - } - - Gtk.Box { - orientation: vertical; - margin-top: 32; - spacing: 12; - - Gtk.Label { - label: "This launcher is an unofficial tool, in no way related to miHoYo nor COGNOSPHERE."; - - wrap: true; - halign: start; - } - - Gtk.Label { - label: "This tool is designed to facilitate playing Genshin Impact on Linux, and was built with the sole purpose of installing and running the game with less hassle."; - - wrap: true; - halign: start; - } - - Gtk.Label { - label: "It does so by using existing components and making the experience simple for the user."; - - wrap: true; - halign: start; - } - - Gtk.Label { - label: "However, some components used here likely break the miHoYo Terms of Service for Genshin Impact."; - - wrap: true; - halign: start; - } - - Gtk.Label { - label: "If you are using this launcher, your player account could become identified as TOS-non-compliant by miHoYo/COGNOSPHERE."; - - wrap: true; - halign: start; - } - - Gtk.Label { - label: "If this happens, as your account would be disobeying TOS, miHoYo/COGNOSPHERE are free to do what they want. Including banning."; - - wrap: true; - halign: start; - } - - Gtk.Label { - label: "If you understand the risk of trying to play the game in an unofficial capacity, press OK and let's go researching the world of Teyvat!"; - - wrap: true; - halign: start; - } - } - } - - Adw.PreferencesGroup { - vexpand: true; - valign: center; - - Gtk.Box { - orientation: horizontal; - spacing: 8; - halign: center; - - Gtk.Button continue_button { - label: "Continue"; - - styles ["suggested-action"] - } - - Gtk.Button exit_button { - label: "Exit"; - } - } - } - } -} diff --git a/assets/ui/first_run/voice_packages.blp b/assets/ui/first_run/voice_packages.blp deleted file mode 100644 index c7bae1e..0000000 --- a/assets/ui/first_run/voice_packages.blp +++ /dev/null @@ -1,44 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Gtk.Box page { - orientation: vertical; - hexpand: true; - - Adw.PreferencesPage { - Adw.PreferencesGroup { - Gtk.Label { - label: "Select voice packages"; - margin-top: 16; - - styles ["title-1"] - } - } - - Adw.PreferencesGroup voice_packages_group { - vexpand: true; - valign: center; - } - - Adw.PreferencesGroup { - vexpand: true; - valign: center; - - Gtk.Box { - orientation: horizontal; - spacing: 8; - halign: center; - - Gtk.Button continue_button { - label: "Continue"; - - styles ["suggested-action"] - } - - Gtk.Button exit_button { - label: "Exit"; - } - } - } - } -} diff --git a/assets/ui/first_run/welcome.blp b/assets/ui/first_run/welcome.blp deleted file mode 100644 index d39453a..0000000 --- a/assets/ui/first_run/welcome.blp +++ /dev/null @@ -1,55 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Gtk.Box page { - orientation: vertical; - hexpand: true; - - Adw.PreferencesPage { - Adw.PreferencesGroup { - Gtk.Image { - resource: "/org/app/assets/images/icon.png"; - - vexpand: true; - margin-top: 16; - } - - Gtk.Label { - label: "An Anime Game Launcher"; - margin-top: 32; - - styles ["title-1"] - } - - Gtk.Label { - label: "Hi there! Welcome to the An Anime Game Launcher. We need to prepare some stuff and download default components before you could run the game"; - - wrap: true; - justify: center; - margin-top: 32; - } - } - - Adw.PreferencesGroup { - vexpand: true; - valign: center; - - Gtk.Box { - orientation: horizontal; - spacing: 8; - halign: center; - - Gtk.Button continue_button { - label: "Continue"; - - styles ["suggested-action"] - } - - Gtk.Button advanced_button { - label: "Advanced"; - tooltip-text: "You can choose default folders paths"; - } - } - } - } -} diff --git a/assets/ui/main.blp b/assets/ui/main.blp deleted file mode 100644 index 48702be..0000000 --- a/assets/ui/main.blp +++ /dev/null @@ -1,158 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Adw.ApplicationWindow window { - default-width: 900; - default-height: 600; - - content: Adw.ToastOverlay toast_overlay { - Adw.Leaflet leaflet { - can-navigate-back: true; - can-unfold: false; - - Gtk.Box { - orientation: vertical; - hexpand: true; - - Adw.HeaderBar { - title-widget: Adw.WindowTitle { - title: "An Anime Game Launcher"; - }; - - [end] - Gtk.MenuButton menu { - menu-model: app_menu; - icon-name: "open-menu-symbolic"; - halign: end; - valign: center; - margin-start: 12; - } - } - - Adw.StatusPage status_page { - icon-name: "image-loading-symbolic"; - title: "Loading data"; - - vexpand: true; - } - - Adw.PreferencesPage launcher_content { - visible: false; - - Adw.PreferencesGroup { - Gtk.Image icon { - resource: "/org/app/assets/images/icon.png"; - - vexpand: true; - margin-top: 48; - } - - Gtk.Label { - label: "An Anime Game Launcher"; - margin-top: 32; - - styles ["title-1"] - } - } - - Adw.PreferencesGroup launch_game_group { - vexpand: true; - valign: center; - - Gtk.Box { - halign: center; - margin-top: 64; - spacing: 8; - - Gtk.Button predownload_game { - icon-name: "document-save-symbolic"; - tooltip-text: "Pre-download 3.1.0 update (15 GB)"; - - hexpand: false; - visible: false; - - styles ["warning"] - } - - Gtk.Button launch_game { - label: "Launch"; - - hexpand: false; - width-request: 200; - - styles ["suggested-action"] - } - - Gtk.Button open_preferences { - icon-name: "emblem-system-symbolic"; - } - } - } - - Adw.PreferencesGroup progress_bar_group { - vexpand: true; - valign: center; - visible: false; - - Gtk.Box { - halign: center; - margin-top: 64; - spacing: 20; - - Gtk.ProgressBar progress_bar { - show-text: true; - width-request: 360; - valign: center; - } - - Gtk.Button { - label: "Pause"; - sensitive: false; - tooltip-text: "Work in progress"; - } - } - } - } - } - } - }; -} - -Adw.AboutWindow about { - application-name: "An Anime Game Launcher"; - application-icon: "moe.launcher.an-anime-game-launcher-gtk"; - - website: "https://github.com/an-anime-team/an-anime-game-launcher-gtk"; - issue-url: "https://github.com/an-anime-team/an-anime-game-launcher-gtk/issues"; - - modal: true; - transient-for: window; - hide-on-close: true; -} - -menu app_menu { - section { - submenu { - label: "Open"; - - item { - label: "Launcher folder"; - action: "open-launcher-folder.open-launcher-folder"; - } - - item { - label: "Game folder"; - action: "open-game-folder.open-game-folder"; - } - - item { - label: "Config file"; - action: "open-config-file.open-config-file"; - } - } - } - - section { - item ("About", "show-about-dialog.show-about-dialog") - } -} diff --git a/assets/ui/preferences.blp b/assets/ui/preferences.blp deleted file mode 100644 index c306acf..0000000 --- a/assets/ui/preferences.blp +++ /dev/null @@ -1,36 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Gtk.Box preferences { - orientation: vertical; - - Adw.HeaderBar { - title-widget: Adw.WindowTitle { - title: "Preferences"; - }; - - Gtk.Button preferences_go_back { - icon-name: "go-previous-symbolic"; - halign: start; - } - } - - Adw.StatusPage status_page { - icon-name: "image-loading-symbolic"; - title: "Loading data"; - - vexpand: true; - } - - Adw.Flap flap { - vexpand: true; - visible: false; - - flap: Gtk.StackSidebar { - width-request: 200; - stack: stack; - }; - - content: Gtk.Stack stack {}; - } -} diff --git a/assets/ui/preferences/enhancements.blp b/assets/ui/preferences/enhancements.blp deleted file mode 100644 index baa59f5..0000000 --- a/assets/ui/preferences/enhancements.blp +++ /dev/null @@ -1,169 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Adw.PreferencesPage page { - Adw.PreferencesGroup { - title: "Wine"; - - Adw.ComboRow sync_combo { - title: "Synchronization"; - subtitle: "Technology used to synchronize inner wine events"; - - model: Gtk.StringList { - strings [ - "None", - "ESync", - "FSync", - "Futex2" - ] - }; - } - - Adw.ComboRow wine_lang { - title: "Language"; - subtitle: "Choose the language to use in wine environment. Can fix keyboard layout detection in-game"; - } - - Adw.ActionRow { - title: "Borderless window"; - - Gtk.Switch borderless { - valign: center; - } - } - - Adw.ComboRow virtual_desktop_row { - title: "Virtual desktop"; - - Gtk.Switch virtual_desktop { - valign: center; - } - } - } - - Adw.PreferencesGroup { - title: "Game"; - - Adw.ComboRow hud_combo { - title: "HUD"; - - model: Gtk.StringList { - strings [ - "None", - "DXVK", - "MangoHUD" - ] - }; - } - - Adw.ComboRow fsr_combo { - title: "FSR"; - subtitle: "Upscales game to your monitor size. To use select lower\nresolution in the game's settings and press Alt+Enter"; - - model: Gtk.StringList { - strings [ - "Ultra Quality", - "Quality", - "Balanced", - "Performance" - ] - }; - - Gtk.Switch fsr_switcher { - valign: center; - } - } - - Adw.ActionRow gamemode_row { - title: "Gamemode"; - subtitle: "This prioritizes the game over the rest of the processes"; - - Gtk.Switch gamemode_switcher { - valign: center; - } - } - - Adw.ActionRow gamescope_row { - title: "Gamescope"; - subtitle: "Gamescope is a tool from Valve that allows for games to run in an isolated Xwayland instance and supports AMD, Intel, and Nvidia GPUs"; - - Gtk.Button gamescope_settings { - icon-name: "emblem-system-symbolic"; - valign: center; - - styles ["flat"] - } - - Gtk.Switch gamescope_switcher { - valign: center; - } - } - } - - Adw.PreferencesGroup { - title: "FPS Unlocker"; - - Adw.ComboRow fps_unlocker_combo { - title: "Enabled"; - subtitle: "Remove frames rendering limitation modifying the game's memory. Can be detected by the anti-cheat"; - - Gtk.Switch fps_unlocker_switcher { - valign: center; - } - } - - Adw.ActionRow { - title: "Power saving"; - subtitle: "Automatically sets the fps limit to 10 and low process priority upon losing focus to the game (e.g. tabbing out of the game)"; - - Gtk.Switch fps_unlocker_power_saving_switcher { - valign: center; - } - } - - Adw.ActionRow { - title: "Monitor"; - subtitle: "Number of monitor you want to run the game on"; - - Gtk.SpinButton fps_unlocker_monitor_num { - valign: center; - - adjustment: Gtk.Adjustment { - value: 1; - lower: 1; - upper: 10; - page-increment: 1; - step-increment: 1; - }; - } - } - - Adw.ComboRow fps_unlocker_window_mode_combo { - title: "Window mode"; - - model: Gtk.StringList { - strings [ - "Default", - "Popup", - "Fullscreen" - ] - }; - } - - Adw.ComboRow fps_unlocker_priority_combo { - title: "Priority"; - subtitle: "Game process priority"; - - model: Gtk.StringList { - strings [ - "Realtime", - "High", - "Above normal", - "Normal", - "Below normal", - "Low" - ] - }; - } - } -} diff --git a/assets/ui/preferences/environment.blp b/assets/ui/preferences/environment.blp deleted file mode 100644 index dcfe644..0000000 --- a/assets/ui/preferences/environment.blp +++ /dev/null @@ -1,41 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Adw.PreferencesPage page { - Adw.PreferencesGroup { - title: "Game command"; - - Gtk.Entry command { - placeholder-text: "%command%"; - } - } - - Adw.PreferencesGroup { - title: "New variable"; - - Gtk.Box { - orientation: horizontal; - spacing: 8; - - Gtk.Entry name { - placeholder-text: "Name"; - } - - Gtk.Entry value { - placeholder-text: "Value"; - hexpand: true; - } - } - - Gtk.Button add { - label: "Add"; - - margin-top: 8; - halign: start; - } - } - - Adw.PreferencesGroup variables { - title: "Variables"; - } -} diff --git a/assets/ui/preferences/gamescope.blp b/assets/ui/preferences/gamescope.blp deleted file mode 100644 index f5e86b2..0000000 --- a/assets/ui/preferences/gamescope.blp +++ /dev/null @@ -1,103 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Adw.PreferencesWindow window { - title: "Gamescope"; - - modal: true; - hide-on-close: true; - - Adw.PreferencesPage { - Adw.PreferencesGroup { - title: "Game resolution"; - - Adw.EntryRow game_width { - title: "Width"; - - input-purpose: digits; - } - - Adw.EntryRow game_height { - title: "Height"; - - input-purpose: digits; - } - } - - Adw.PreferencesGroup { - title: "Gamescope resolution"; - - Adw.EntryRow gamescope_width { - title: "Width"; - - input-purpose: digits; - } - - Adw.EntryRow gamescope_height { - title: "Height"; - - input-purpose: digits; - } - } - - Adw.PreferencesGroup { - title: "Other settings"; - - Adw.EntryRow framerate_limit { - title: "Framerate limit"; - - input-purpose: digits; - } - - Adw.EntryRow framerate_unfocused_limit { - title: "Unfocused framerate limit"; - - input-purpose: digits; - } - - Adw.ActionRow { - title: "Integer scaling"; - - Gtk.Switch integer_scaling { - valign: center; - } - } - - Adw.ActionRow { - title: "FSR"; - - Gtk.Switch fsr { - valign: center; - } - } - - Adw.ActionRow { - title: "Nvidia Image Scaling"; - - Gtk.Switch nis { - valign: center; - } - } - - Adw.ActionRow { - title: "Window type"; - - Gtk.Box { - orientation: horizontal; - - Gtk.ToggleButton borderless { - label: "Borderless"; - valign: center; - } - - Gtk.ToggleButton fullscreen { - label: "Fullscreen"; - valign: center; - } - - styles ["linked"] - } - } - } - } -} diff --git a/assets/ui/preferences/general.blp b/assets/ui/preferences/general.blp deleted file mode 100644 index b1a31be..0000000 --- a/assets/ui/preferences/general.blp +++ /dev/null @@ -1,104 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -Adw.PreferencesPage page { - Adw.PreferencesGroup { - title: "General"; - - Adw.ComboRow { - title: "Launcher language"; - - model: Gtk.StringList { - strings [ - "English", - "German", - "Russian", - "French", - "Chinese" - ] - }; - - sensitive: false; - tooltip-text: "Work in progress"; - } - - Adw.ExpanderRow voiceovers_row { - title: "Game voiceovers"; - subtitle: "Select voice packages used in game"; - } - - Gtk.Box { - orientation: horizontal; - spacing: 8; - margin-top: 16; - - Gtk.Button repair_game { - label: "Repair game"; - } - } - } - - Adw.PreferencesGroup { - title: "Status"; - - Adw.ActionRow { - title: "Game version"; - - Gtk.Label game_version { - label: "2.7.0"; - - styles ["success"] - } - } - - Adw.ActionRow { - title: "Patch version"; - - Gtk.Label patch_version { - label: "2.7.0"; - - styles ["success"] - } - } - } - - Adw.PreferencesGroup { - title: "Wine version"; - - Adw.ComboRow wine_selected { - title: "Selected version"; - } - - Adw.ActionRow { - title: "Recommended only"; - subtitle: "Show only recommended wine versions"; - - Gtk.Switch wine_recommended_only { - valign: center; - state: true; - } - } - } - - Adw.PreferencesGroup wine_groups {} - - Adw.PreferencesGroup { - title: "DXVK version"; - - Adw.ComboRow dxvk_selected { - title: "Selected version"; - } - - Adw.ActionRow { - title: "Recommended only"; - subtitle: "Show only recommended DXVK versions"; - - Gtk.Switch dxvk_recommended_only { - valign: center; - state: true; - } - } - } - - Adw.PreferencesGroup dxvk_groups {} -} diff --git a/blueprint-compiler b/blueprint-compiler deleted file mode 160000 index 039d88a..0000000 --- a/blueprint-compiler +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 039d88ab45001cf799c421e58d4669a0596c4d29 diff --git a/build.rs b/build.rs index 1e940c3..7582368 100644 --- a/build.rs +++ b/build.rs @@ -1,84 +1,7 @@ -use std::process::{Command, Stdio}; -use std::fs::{self, read_dir, create_dir_all, read_to_string}; -use std::path::Path; - -fn compile_blueprint(path: T) -> Result { - // python blueprint-compiler/blueprint-compiler.py compile ui/main.blp - let output = Command::new("python3") - .arg("blueprint-compiler/blueprint-compiler.py") - .arg("compile") - .arg(path.to_string()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output(); - - match output { - Ok(output) => { - if output.status.success() { - Ok(String::from_utf8(output.stdout).unwrap()) - } - - else { - Err(String::from_utf8(output.stdout).unwrap()) - } - }, - Err(err) => Err(err.to_string()) - } -} - -fn blp_process_dir(dir: String) { - let source_dir = format!("assets/ui/{}", &dir).replace("//", "/"); - let dist_dir = format!("assets/ui/.dist/{}", &dir).replace("//", "/"); - - if let Ok(entries) = read_dir(&source_dir) { - if read_dir(&dist_dir).is_err() { - create_dir_all(&dist_dir).expect("UI dist dir couldn't be created"); - } - - // println!("cargo:rerun-if-changed={}/*.blp", &source_dir); - - for entry in entries.flatten() { - if let Ok(metadata) = entry.metadata() { - let entry_path = entry.path().to_str().unwrap().to_string(); - let entry_filename = entry.file_name().to_str().unwrap().to_string(); - - if metadata.is_file() { - let entry_dist_path = format!("{}/{}.ui", &dist_dir, &entry_filename[..entry_filename.len() - 4]); - - match compile_blueprint(&entry_path) { - Ok(xml) => { - let result = fs::write(entry_dist_path, xml); - - if let Err(err) = result { - println!("cargo:warning=Couldn't write compiled XML UI: {}", err); - } - }, - Err(err) => { - if Path::new(&entry_dist_path).exists() { - fs::remove_file(entry_dist_path).expect("Couldn't remove broken file"); - } - - println!("cargo:warning=Couldn't compile {}: {}", entry_path, err); - } - } - } - - else if metadata.is_dir() && &entry_filename[0..1] != "." { - blp_process_dir(format!("{}/{}", &dir, &entry_filename)); - } - } - } - } -} - fn main() { - blp_process_dir(String::new()); - - if read_to_string("assets/resources.xml").is_ok() { - glib_build_tools::compile_resources( - "assets", - "assets/resources.xml", - ".assets.gresource", - ); - } + glib_build_tools::compile_resources( + "assets", + "assets/resources.xml", + "resources.gresource", + ); } diff --git a/components b/components deleted file mode 160000 index 5580f7b..0000000 --- a/components +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5580f7be0fbdfba677ec32b2fd7d11cb762edebf diff --git a/src/i18n.rs b/src/i18n.rs new file mode 100644 index 0000000..53ac62e --- /dev/null +++ b/src/i18n.rs @@ -0,0 +1,19 @@ +use fluent_templates::Loader; +use unic_langid::{langid, LanguageIdentifier}; + +fluent_templates::static_loader! { + static LOCALES = { + locales: "./assets/locales", + fallback_language: "en" + }; +} + +pub static mut LANG: LanguageIdentifier = langid!("en"); + +pub fn tr(id: &str) -> String { + unsafe { + LOCALES + .lookup(&LANG, id) + .expect("Failed to get message with given id") + } +} diff --git a/src/lib/config/game/dxvk.rs b/src/lib/config/game/dxvk.rs deleted file mode 100644 index ea0dbd6..0000000 --- a/src/lib/config/game/dxvk.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::path::PathBuf; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -use crate::lib::consts::launcher_dir; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Dxvk { - pub builds: PathBuf -} - -impl Default for Dxvk { - fn default() -> Self { - let launcher_dir = launcher_dir().expect("Failed to get launcher dir"); - - Self { - builds: launcher_dir.join("dxvks") - } - } -} - -impl From<&JsonValue> for Dxvk { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - builds: match value.get("builds") { - Some(value) => match value.as_str() { - Some(value) => PathBuf::from(value), - None => default.builds - }, - None => default.builds - } - } - } -} diff --git a/src/lib/config/game/enhancements/fps_unlocker/config/fps.rs b/src/lib/config/game/enhancements/fps_unlocker/config/fps.rs deleted file mode 100644 index 04076a9..0000000 --- a/src/lib/config/game/enhancements/fps_unlocker/config/fps.rs +++ /dev/null @@ -1,77 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum Fps { - /// 90 - Ninety, - - /// 120 - HundredTwenty, - - /// 144 - HundredFourtyFour, - - /// 165 - HundredSixtyFive, - - /// 180 - HundredEighty, - - /// 200 - TwoHundred, - - /// 240 - TwoHundredFourty, - - Custom(u64) -} - -impl Fps { - pub fn list() -> Vec { - vec![ - Self::Ninety, - Self::HundredTwenty, - Self::HundredFourtyFour, - Self::HundredSixtyFive, - Self::HundredEighty, - Self::TwoHundred, - Self::TwoHundredFourty - ] - } - - pub fn get_model() -> gtk::StringList { - let model = gtk::StringList::new(&[]); - - model.append("Custom"); - - for res in Self::list() { - model.append(&res.to_num().to_string()); - } - - model - } - - pub fn from_num(fps: u64) -> Self { - match fps { - 90 => Self::Ninety, - 120 => Self::HundredTwenty, - 144 => Self::HundredFourtyFour, - 165 => Self::HundredSixtyFive, - 180 => Self::HundredEighty, - 200 => Self::TwoHundred, - 240 => Self::TwoHundredFourty, - num => Self::Custom(num) - } - } - - pub fn to_num(&self) -> u64 { - match self { - Self::Ninety => 90, - Self::HundredTwenty => 120, - Self::HundredFourtyFour => 144, - Self::HundredSixtyFive => 165, - Self::HundredEighty => 180, - Self::TwoHundred => 200, - Self::TwoHundredFourty => 240, - Self::Custom(num) => *num - } - } -} diff --git a/src/lib/config/game/enhancements/fps_unlocker/config/mod.rs b/src/lib/config/game/enhancements/fps_unlocker/config/mod.rs deleted file mode 100644 index 6cc380d..0000000 --- a/src/lib/config/game/enhancements/fps_unlocker/config/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -pub mod fps; - -pub mod prelude { - pub use super::fps::Fps; -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - pub fps: u64, - pub power_saving: bool, - pub monitor: u64, - pub window_mode: u64, - pub priority: u64 -} - -impl Default for Config { - fn default() -> Self { - Self { - fps: 120, - power_saving: false, - monitor: 1, - window_mode: 0, - priority: 3 - } - } -} - -impl From<&JsonValue> for Config { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - fps: match value.get("fps") { - Some(value) => value.as_u64().unwrap_or(default.fps), - None => default.fps - }, - - power_saving: match value.get("power_saving") { - Some(value) => value.as_bool().unwrap_or(default.power_saving), - None => default.power_saving - }, - - monitor: match value.get("monitor") { - Some(value) => value.as_u64().unwrap_or(default.monitor), - None => default.monitor - }, - - window_mode: match value.get("window_mode") { - Some(value) => value.as_u64().unwrap_or(default.window_mode), - None => default.window_mode - }, - - priority: match value.get("priority") { - Some(value) => value.as_u64().unwrap_or(default.priority), - None => default.priority - } - } - } -} diff --git a/src/lib/config/game/enhancements/fps_unlocker/mod.rs b/src/lib/config/game/enhancements/fps_unlocker/mod.rs deleted file mode 100644 index 040993b..0000000 --- a/src/lib/config/game/enhancements/fps_unlocker/mod.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::path::PathBuf; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -use crate::lib::consts::launcher_dir; - -pub mod config; - -pub mod prelude { - pub use super::config::Config; - - pub use super::config::prelude::*; -} - -use prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FpsUnlocker { - pub path: PathBuf, - pub enabled: bool, - pub config: Config -} - -impl Default for FpsUnlocker { - fn default() -> Self { - let launcher_dir = launcher_dir().expect("Failed to get launcher dir"); - - Self { - path: launcher_dir.join("fps-unlocker"), - enabled: false, - config: Config::default() - } - } -} - -impl From<&JsonValue> for FpsUnlocker { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - path: match value.get("path") { - Some(value) => match value.as_str() { - Some(value) => PathBuf::from(value), - None => default.path - }, - None => default.path - }, - - enabled: match value.get("enabled") { - Some(value) => value.as_bool().unwrap_or(default.enabled), - None => default.enabled - }, - - config: match value.get("config") { - Some(value) => Config::from(value), - None => default.config - } - } - } -} diff --git a/src/lib/config/game/enhancements/fsr.rs b/src/lib/config/game/enhancements/fsr.rs deleted file mode 100644 index 5b6794f..0000000 --- a/src/lib/config/game/enhancements/fsr.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::collections::HashMap; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Fsr { - pub strength: u64, - pub enabled: bool -} - -impl Default for Fsr { - fn default() -> Self { - Self { - strength: 2, - enabled: false - } - } -} - -impl From<&JsonValue> for Fsr { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - strength: match value.get("strength") { - Some(value) => value.as_u64().unwrap_or(default.strength), - None => default.strength - }, - - enabled: match value.get("enabled") { - Some(value) => value.as_bool().unwrap_or(default.enabled), - None => default.enabled - } - } - } -} - -impl Fsr { - /// Get environment variables corresponding to used amd fsr options - pub fn get_env_vars(&self) -> HashMap<&str, String> { - if self.enabled { - HashMap::from([ - ("WINE_FULLSCREEN_FSR", String::from("1")), - ("WINE_FULLSCREEN_FSR_STRENGTH", self.strength.to_string()) - ]) - } - - else { - HashMap::new() - } - } -} diff --git a/src/lib/config/game/enhancements/gamescope/framerate.rs b/src/lib/config/game/enhancements/gamescope/framerate.rs deleted file mode 100644 index 35b7061..0000000 --- a/src/lib/config/game/enhancements/gamescope/framerate.rs +++ /dev/null @@ -1,26 +0,0 @@ -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] -pub struct Framerate { - pub focused: u64, - pub unfocused: u64 -} - -impl From<&JsonValue> for Framerate { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - focused: match value.get("focused") { - Some(value) => value.as_u64().unwrap_or(default.focused), - None => default.focused - }, - - unfocused: match value.get("unfocused") { - Some(value) => value.as_u64().unwrap_or(default.unfocused), - None => default.unfocused - } - } - } -} diff --git a/src/lib/config/game/enhancements/gamescope/mod.rs b/src/lib/config/game/enhancements/gamescope/mod.rs deleted file mode 100644 index 0e5783d..0000000 --- a/src/lib/config/game/enhancements/gamescope/mod.rs +++ /dev/null @@ -1,156 +0,0 @@ -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -pub mod size; -pub mod framerate; -pub mod window_type; - -pub mod prelude { - pub use super::Gamescope; - pub use super::size::Size; - pub use super::framerate::Framerate; - pub use super::window_type::WindowType; -} - -use prelude::*; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Gamescope { - pub enabled: bool, - pub game: Size, - pub gamescope: Size, - pub framerate: Framerate, - pub integer_scaling: bool, - pub fsr: bool, - pub nis: bool, - pub window_type: WindowType -} - -impl Gamescope { - pub fn get_command(&self) -> Option { - // https://github.com/bottlesdevs/Bottles/blob/b908311348ed1184ead23dd76f9d8af41ff24082/src/backend/wine/winecommand.py#L478 - if self.enabled { - let mut gamescope = String::from("gamescope"); - - // Set window type - match self.window_type { - WindowType::Borderless => gamescope += " -b", - WindowType::Fullscreen => gamescope += " -f" - } - - // Set game width - if self.game.width > 0 { - gamescope += &format!(" -w {}", self.game.width); - } - - // Set game height - if self.game.height > 0 { - gamescope += &format!(" -h {}", self.game.height); - } - - // Set gamescope width - if self.gamescope.width > 0 { - gamescope += &format!(" -W {}", self.gamescope.width); - } - - // Set gamescope height - if self.gamescope.height > 0 { - gamescope += &format!(" -H {}", self.gamescope.height); - } - - // Set focused framerate limit - if self.framerate.focused > 0 { - gamescope += &format!(" -r {}", self.framerate.focused); - } - - // Set unfocused framerate limit - if self.framerate.unfocused > 0 { - gamescope += &format!(" -o {}", self.framerate.unfocused); - } - - // Set integer scaling - if self.integer_scaling { - gamescope += " -n"; - } - - // Set FSR support - if self.fsr { - gamescope += " -U"; - } - - // Set NIS (Nvidia Image Scaling) support - if self.nis { - gamescope += " -Y"; - } - - Some(gamescope) - } - - else { - None - } - } -} - -impl Default for Gamescope { - fn default() -> Self { - Self { - enabled: false, - game: Size::default(), - gamescope: Size::default(), - framerate: Framerate::default(), - integer_scaling: true, - fsr: false, - nis: false, - window_type: WindowType::default() - } - } -} - -impl From<&JsonValue> for Gamescope { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - enabled: match value.get("enabled") { - Some(value) => value.as_bool().unwrap_or(default.enabled), - None => default.enabled - }, - - game: match value.get("game") { - Some(value) => Size::from(value), - None => default.game - }, - - gamescope: match value.get("gamescope") { - Some(value) => Size::from(value), - None => default.gamescope - }, - - framerate: match value.get("framerate") { - Some(value) => Framerate::from(value), - None => default.framerate - }, - - integer_scaling: match value.get("integer_scaling") { - Some(value) => value.as_bool().unwrap_or(default.integer_scaling), - None => default.integer_scaling - }, - - fsr: match value.get("fsr") { - Some(value) => value.as_bool().unwrap_or(default.fsr), - None => default.fsr - }, - - nis: match value.get("nis") { - Some(value) => value.as_bool().unwrap_or(default.nis), - None => default.nis - }, - - window_type: match value.get("window_type") { - Some(value) => WindowType::from(value), - None => default.window_type - } - } - } -} diff --git a/src/lib/config/game/enhancements/gamescope/size.rs b/src/lib/config/game/enhancements/gamescope/size.rs deleted file mode 100644 index fc63de0..0000000 --- a/src/lib/config/game/enhancements/gamescope/size.rs +++ /dev/null @@ -1,26 +0,0 @@ -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] -pub struct Size { - pub width: u64, - pub height: u64 -} - -impl From<&JsonValue> for Size { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - width: match value.get("width") { - Some(value) => value.as_u64().unwrap_or(default.width), - None => default.width - }, - - height: match value.get("height") { - Some(value) => value.as_u64().unwrap_or(default.height), - None => default.height - } - } - } -} diff --git a/src/lib/config/game/enhancements/gamescope/window_type.rs b/src/lib/config/game/enhancements/gamescope/window_type.rs deleted file mode 100644 index 3889808..0000000 --- a/src/lib/config/game/enhancements/gamescope/window_type.rs +++ /dev/null @@ -1,20 +0,0 @@ -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum WindowType { - Borderless, - Fullscreen -} - -impl Default for WindowType { - fn default() -> Self { - Self::Borderless - } -} - -impl From<&JsonValue> for WindowType { - fn from(value: &JsonValue) -> Self { - serde_json::from_value(value.clone()).unwrap_or_default() - } -} diff --git a/src/lib/config/game/enhancements/hud.rs b/src/lib/config/game/enhancements/hud.rs deleted file mode 100644 index d95a706..0000000 --- a/src/lib/config/game/enhancements/hud.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::collections::HashMap; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -use crate::lib::config::Config; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum HUD { - None, - DXVK, - MangoHUD -} - -impl Default for HUD { - fn default() -> Self { - Self::None - } -} - -impl From<&JsonValue> for HUD { - fn from(value: &JsonValue) -> Self { - serde_json::from_value(value.clone()).unwrap_or_default() - } -} - -impl TryFrom for HUD { - type Error = String; - - fn try_from(value: u32) -> Result { - match value { - 0 => Ok(Self::None), - 1 => Ok(Self::DXVK), - 2 => Ok(Self::MangoHUD), - _ => Err(String::from("Failed to convert number to HUD enum")) - } - } -} - -#[allow(clippy::from_over_into)] -impl Into for HUD { - fn into(self) -> u32 { - match self { - Self::None => 0, - Self::DXVK => 1, - Self::MangoHUD => 2 - } - } -} - -impl HUD { - /// Get environment variables corresponding to used wine hud - pub fn get_env_vars(&self, config: &Config) -> HashMap<&str, &str> { - match self { - Self::None => HashMap::new(), - Self::DXVK => HashMap::from([ - ("DXVK_HUD", "fps,frametimes,version,gpuload") - ]), - Self::MangoHUD => { - // Don't show mangohud if gamescope is enabled - // otherwise it'll be doubled - if config.game.enhancements.gamescope.enabled { - HashMap::new() - } else { - HashMap::from([ - ("MANGOHUD", "1") - ]) - } - } - } - } -} diff --git a/src/lib/config/game/enhancements/mod.rs b/src/lib/config/game/enhancements/mod.rs deleted file mode 100644 index 1f85c97..0000000 --- a/src/lib/config/game/enhancements/mod.rs +++ /dev/null @@ -1,61 +0,0 @@ -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -pub mod fsr; -pub mod hud; -pub mod fps_unlocker; -pub mod gamescope; - -pub mod prelude { - pub use super::gamescope::prelude::*; - pub use super::fps_unlocker::prelude::*; - - pub use super::Enhancements; - pub use super::fsr::Fsr; - pub use super::hud::HUD; - pub use super::fps_unlocker::FpsUnlocker; -} - -use prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct Enhancements { - pub fsr: Fsr, - pub gamemode: bool, - pub hud: HUD, - pub fps_unlocker: FpsUnlocker, - pub gamescope: Gamescope -} - -impl From<&JsonValue> for Enhancements { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - fsr: match value.get("fsr") { - Some(value) => Fsr::from(value), - None => default.fsr - }, - - gamemode: match value.get("gamemode") { - Some(value) => value.as_bool().unwrap_or(default.gamemode), - None => default.gamemode - }, - - hud: match value.get("hud") { - Some(value) => HUD::from(value), - None => default.hud - }, - - fps_unlocker: match value.get("fps_unlocker") { - Some(value) => FpsUnlocker::from(value), - None => default.fps_unlocker - }, - - gamescope: match value.get("gamescope") { - Some(value) => Gamescope::from(value), - None => default.gamescope - } - } - } -} diff --git a/src/lib/config/game/mod.rs b/src/lib/config/game/mod.rs deleted file mode 100644 index df672e5..0000000 --- a/src/lib/config/game/mod.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::collections::HashMap; -use std::path::PathBuf; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -use crate::lib::consts::launcher_dir; - -pub mod wine; -pub mod dxvk; -pub mod enhancements; - -pub mod prelude { - pub use super::enhancements::prelude::*; - pub use super::wine::prelude::*; - - pub use super::Game; - pub use super::dxvk::Dxvk; -} - -use prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Game { - pub path: PathBuf, - pub voices: Vec, - pub wine: prelude::Wine, - pub dxvk: prelude::Dxvk, - pub enhancements: prelude::Enhancements, - pub environment: HashMap, - pub command: Option -} - -impl Default for Game { - fn default() -> Self { - let launcher_dir = launcher_dir().expect("Failed to get launcher dir"); - - Self { - path: launcher_dir.join("game/drive_c/Program Files/Genshin Impact"), - voices: vec![ - String::from("en-us") - ], - wine: Wine::default(), - dxvk: Dxvk::default(), - enhancements: Enhancements::default(), - environment: HashMap::new(), - command: None - } - } -} - -impl From<&JsonValue> for Game { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - path: match value.get("path") { - Some(value) => match value.as_str() { - Some(value) => PathBuf::from(value), - None => default.path - }, - None => default.path - }, - - voices: match value.get("voices") { - Some(value) => match value.as_array() { - Some(values) => { - let mut voices = Vec::new(); - - for value in values { - if let Some(voice) = value.as_str() { - voices.push(voice.to_string()); - } - } - - voices - }, - None => default.voices - }, - None => default.voices - }, - - wine: match value.get("wine") { - Some(value) => Wine::from(value), - None => default.wine - }, - - dxvk: match value.get("dxvk") { - Some(value) => Dxvk::from(value), - None => default.dxvk - }, - - enhancements: match value.get("enhancements") { - Some(value) => Enhancements::from(value), - None => default.enhancements - }, - - environment: match value.get("environment") { - Some(value) => match value.as_object() { - Some(values) => { - let mut vars = HashMap::new(); - - for (name, value) in values { - if let Some(value) = value.as_str() { - vars.insert(name.clone(), value.to_string()); - } - } - - vars - }, - None => default.environment - }, - None => default.environment - }, - - command: match value.get("command") { - Some(value) => { - if value.is_null() { - None - } else { - match value.as_str() { - Some(value) => Some(value.to_string()), - None => default.command - } - } - }, - None => default.command - } - } - } -} diff --git a/src/lib/config/game/wine/mod.rs b/src/lib/config/game/wine/mod.rs deleted file mode 100644 index d48f627..0000000 --- a/src/lib/config/game/wine/mod.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::path::PathBuf; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -use crate::lib::consts::launcher_dir; - -pub mod wine_sync; -pub mod wine_lang; -pub mod virtual_desktop; - -pub mod prelude { - pub use super::Wine; - pub use super::wine_sync::WineSync; - pub use super::wine_lang::WineLang; - pub use super::virtual_desktop::VirtualDesktop; -} - -use prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Wine { - pub prefix: PathBuf, - pub builds: PathBuf, - pub selected: Option, - pub sync: WineSync, - pub language: WineLang, - pub borderless: bool, - pub virtual_desktop: VirtualDesktop -} - -impl Default for Wine { - fn default() -> Self { - let launcher_dir = launcher_dir().expect("Failed to get launcher dir"); - - Self { - prefix: launcher_dir.join("game"), - builds: launcher_dir.join("runners"), - selected: None, - sync: WineSync::default(), - language: WineLang::default(), - borderless: false, - virtual_desktop: VirtualDesktop::default() - } - } -} - -impl From<&JsonValue> for Wine { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - prefix: match value.get("prefix") { - Some(value) => match value.as_str() { - Some(value) => PathBuf::from(value), - None => default.prefix - }, - None => default.prefix - }, - - builds: match value.get("builds") { - Some(value) => match value.as_str() { - Some(value) => PathBuf::from(value), - None => default.builds - }, - None => default.builds - }, - - selected: match value.get("selected") { - Some(value) => { - if value.is_null() { - None - } else { - match value.as_str() { - Some(value) => Some(value.to_string()), - None => default.selected - } - } - }, - None => default.selected - }, - - sync: match value.get("sync") { - Some(value) => WineSync::from(value), - None => default.sync - }, - - language: match value.get("language") { - Some(value) => WineLang::from(value), - None => default.language - }, - - borderless: match value.get("borderless") { - Some(value) => value.as_bool().unwrap_or(default.borderless), - None => default.borderless - }, - - virtual_desktop: match value.get("virtual_desktop") { - Some(value) => VirtualDesktop::from(value), - None => default.virtual_desktop - } - } - } -} diff --git a/src/lib/config/game/wine/virtual_desktop.rs b/src/lib/config/game/wine/virtual_desktop.rs deleted file mode 100644 index 82d2201..0000000 --- a/src/lib/config/game/wine/virtual_desktop.rs +++ /dev/null @@ -1,60 +0,0 @@ -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -use crate::lib::config::prelude::*; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct VirtualDesktop { - pub enabled: bool, - pub width: u64, - pub height: u64 -} - -impl Default for VirtualDesktop { - fn default() -> Self { - Self { - enabled: false, - width: 1920, - height: 1080 - } - } -} - -impl From<&JsonValue> for VirtualDesktop { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - enabled: match value.get("enabled") { - Some(value) => value.as_bool().unwrap_or(default.enabled), - None => default.enabled - }, - - width: match value.get("width") { - Some(value) => value.as_u64().unwrap_or(default.width), - None => default.width - }, - - height: match value.get("height") { - Some(value) => value.as_u64().unwrap_or(default.height), - None => default.height - } - } - } -} - -impl VirtualDesktop { - pub fn get_resolution(&self) -> Resolution { - Resolution::from_pair(self.width, self.height) - } - - pub fn get_command(&self) -> Option { - if self.enabled { - Some(format!("explorer /desktop=animegame,{}x{}", self.width, self.height)) - } - - else { - None - } - } -} diff --git a/src/lib/config/game/wine/wine_lang.rs b/src/lib/config/game/wine/wine_lang.rs deleted file mode 100644 index ad3d1eb..0000000 --- a/src/lib/config/game/wine/wine_lang.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::collections::HashMap; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum WineLang { - System, - English, - Russian, - German, - Portuguese, - Polish, - French, - Spanish, - Chinese, - Japanese, - Korean -} - -impl Default for WineLang { - fn default() -> Self { - Self::System - } -} - -impl From<&JsonValue> for WineLang { - fn from(value: &JsonValue) -> Self { - serde_json::from_value(value.clone()).unwrap_or_default() - } -} - -#[allow(clippy::from_over_into)] -impl Into for WineLang { - fn into(self) -> u32 { - for (i, lang) in Self::list().into_iter().enumerate() { - if lang == self { - return i as u32; - } - } - - unreachable!() - } -} - -impl WineLang { - pub fn list() -> Vec { - vec![ - Self::System, - Self::English, - Self::Russian, - Self::German, - Self::Portuguese, - Self::Polish, - Self::French, - Self::Spanish, - Self::Chinese, - Self::Japanese, - Self::Korean - ] - } - - pub fn get_model() -> gtk::StringList { - let model = gtk::StringList::new(&[]); - - for lang in Self::list() { - model.append(&lang.to_string()); - } - - model - } - - /// Get environment variables corresponding to used wine language - pub fn get_env_vars(&self) -> HashMap<&str, &str> { - HashMap::from([("LANG", match self { - Self::System => return HashMap::new(), - - Self::English => "en_US.UTF8", - Self::Russian => "ru_RU.UTF8", - Self::German => "de_DE.UTF8", - Self::Portuguese => "pt_PT.UTF8", - Self::Polish => "pl_PL.UTF8", - Self::French => "fr_FR.UTF8", - Self::Spanish => "es_ES.UTF8", - Self::Chinese => "zh_CN.UTF8", - Self::Japanese => "ja_JP.UTF8", - Self::Korean => "ko_KR.UTF8" - })]) - } -} - -impl std::fmt::Display for WineLang { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("{:?}", self)) - } -} diff --git a/src/lib/config/game/wine/wine_sync.rs b/src/lib/config/game/wine/wine_sync.rs deleted file mode 100644 index 183a834..0000000 --- a/src/lib/config/game/wine/wine_sync.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::collections::HashMap; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum WineSync { - None, - ESync, - FSync, - Futex2 -} - -impl Default for WineSync { - fn default() -> Self { - Self::FSync - } -} - -impl From<&JsonValue> for WineSync { - fn from(value: &JsonValue) -> Self { - serde_json::from_value(value.clone()).unwrap_or_default() - } -} - -impl TryFrom for WineSync { - type Error = String; - - fn try_from(value: u32) -> Result { - match value { - 0 => Ok(Self::None), - 1 => Ok(Self::ESync), - 2 => Ok(Self::FSync), - 3 => Ok(Self::Futex2), - - _ => Err(String::from("Failed to convert number to WineSync enum")) - } - } -} - -#[allow(clippy::from_over_into)] -impl Into for WineSync { - fn into(self) -> u32 { - match self { - Self::None => 0, - Self::ESync => 1, - Self::FSync => 2, - Self::Futex2 => 3 - } - } -} - -impl WineSync { - /// Get environment variables corresponding to used wine sync - pub fn get_env_vars(&self) -> HashMap<&str, &str> { - HashMap::from([(match self { - Self::None => return HashMap::new(), - - Self::ESync => "WINEESYNC", - Self::FSync => "WINEFSYNC", - Self::Futex2 => "WINEFSYNC_FUTEX2" - }, "1")]) - } -} diff --git a/src/lib/config/launcher/mod.rs b/src/lib/config/launcher/mod.rs deleted file mode 100644 index f3d5635..0000000 --- a/src/lib/config/launcher/mod.rs +++ /dev/null @@ -1,125 +0,0 @@ -use std::path::PathBuf; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -use anime_game_core::genshin::consts::GameEdition as CoreGameEdition; - -use crate::lib::consts::launcher_dir; - -pub mod repairer; - -pub mod prelude { - pub use super::Launcher; - pub use super::repairer::Repairer; -} - -use prelude::*; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum GameEdition { - Global, - China -} - -impl Default for GameEdition { - fn default() -> Self { - let locale = match std::env::var("LC_ALL") { - Ok(locale) => locale, - Err(_) => match std::env::var("LC_MESSAGES") { - Ok(locale) => locale, - Err(_) => match std::env::var("LANG") { - Ok(locale) => locale, - Err(_) => return Self::Global - } - } - }; - - if locale.len() > 4 && &locale[..5].to_lowercase() == "zh_cn" { - Self::China - } else { - Self::Global - } - } -} - -impl From for CoreGameEdition { - fn from(edition: GameEdition) -> Self { - match edition { - GameEdition::Global => CoreGameEdition::Global, - GameEdition::China => CoreGameEdition::China - } - } -} - -impl From for GameEdition { - fn from(edition: CoreGameEdition) -> Self { - match edition { - CoreGameEdition::Global => Self::Global, - CoreGameEdition::China => Self::China - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Launcher { - pub language: String, - pub temp: Option, - pub speed_limit: u64, - pub repairer: Repairer, - pub edition: GameEdition -} - -impl Default for Launcher { - fn default() -> Self { - Self { - language: String::from("en-us"), - temp: launcher_dir(), - speed_limit: 0, - repairer: Repairer::default(), - edition: GameEdition::default() - } - } -} - -impl From<&JsonValue> for Launcher { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - language: match value.get("language") { - Some(value) => value.as_str().unwrap_or(&default.language).to_string(), - None => default.language - }, - - temp: match value.get("temp") { - Some(value) => { - if value.is_null() { - None - } else { - match value.as_str() { - Some(value) => Some(PathBuf::from(value)), - None => default.temp - } - } - }, - None => default.temp - }, - - speed_limit: match value.get("speed_limit") { - Some(value) => value.as_u64().unwrap_or(default.speed_limit), - None => default.speed_limit - }, - - repairer: match value.get("repairer") { - Some(value) => Repairer::from(value), - None => default.repairer - }, - - edition: match value.get("edition") { - Some(value) => serde_json::from_value(value.clone()).unwrap_or(default.edition), - None => default.edition - } - } - } -} diff --git a/src/lib/config/launcher/repairer.rs b/src/lib/config/launcher/repairer.rs deleted file mode 100644 index 1c6d2fb..0000000 --- a/src/lib/config/launcher/repairer.rs +++ /dev/null @@ -1,35 +0,0 @@ -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Repairer { - pub threads: u64, - pub fast: bool -} - -impl Default for Repairer { - fn default() -> Self { - Self { - threads: 4, - fast: false - } - } -} - -impl From<&JsonValue> for Repairer { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - threads: match value.get("threads") { - Some(value) => value.as_u64().unwrap_or(default.threads), - None => default.threads - }, - - fast: match value.get("fast") { - Some(value) => value.as_bool().unwrap_or(default.fast), - None => default.fast - } - } - } -} diff --git a/src/lib/config/mod.rs b/src/lib/config/mod.rs deleted file mode 100644 index 9a6965d..0000000 --- a/src/lib/config/mod.rs +++ /dev/null @@ -1,210 +0,0 @@ -use std::fs::File; -use std::io::Read; -use std::path::{Path, PathBuf}; -use std::io::Write; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -use wincompatlib::dxvk::Dxvk; - -use crate::lib; -use super::consts::*; -use super::wine::{ - Version as WineVersion, - List as WineList -}; -use super::dxvk::{ - Version as DxvkVersion, - List as DxvkList -}; - -pub mod launcher; -pub mod game; -pub mod patch; -pub mod resolution; - -pub mod prelude { - pub use super::launcher::prelude::*; - pub use super::game::prelude::*; - - pub use super::patch::Patch; - pub use super::resolution::Resolution; -} - -use prelude::*; - -static mut CONFIG: Option = None; - -/// Get config data -/// -/// This method will load config from file once and store it into the memory. -/// If you know that the config file was updated - you should run `get_raw` method -/// that always loads config directly from the file. This will also update in-memory config -pub fn get() -> anyhow::Result { - unsafe { - match &CONFIG { - Some(config) => Ok(config.clone()), - None => get_raw() - } - } -} - -/// Get config data -/// -/// This method will always load data directly from the file and update in-memory config -pub fn get_raw() -> anyhow::Result { - match config_file() { - Some(path) => { - // Try to read config if the file exists - if Path::new(&path).exists() { - let mut file = File::open(path)?; - let mut json = String::new(); - - file.read_to_string(&mut json)?; - - match serde_json::from_str(&json) { - Ok(config) => { - let config = Config::from(&config); - - unsafe { - CONFIG = Some(config.clone()); - } - - Ok(config) - }, - Err(err) => Err(anyhow::anyhow!("Failed to decode data from json format: {}", err.to_string())) - } - } - - // Otherwise create default config file - else { - update_raw(Config::default())?; - - Ok(Config::default()) - } - }, - None => Err(anyhow::anyhow!("Failed to get config file path")) - } -} - -/// Update in-memory config data -/// -/// Use `update_raw` if you want to update config file itself -pub fn update(config: Config) { - unsafe { - CONFIG = Some(config); - } -} - -/// Update config file -/// -/// This method will also update in-memory config data -pub fn update_raw(config: Config) -> anyhow::Result<()> { - update(config.clone()); - - match config_file() { - Some(path) => { - let mut file = File::create(&path)?; - - match serde_json::to_string_pretty(&config) { - Ok(json) => { - file.write_all(json.as_bytes())?; - - Ok(()) - }, - Err(err) => Err(anyhow::anyhow!("Failed to encode data into json format: {}", err.to_string())) - } - }, - None => Err(anyhow::anyhow!("Failed to get config file path")) - } -} - -/// Update config file from the in-memory saved config -pub fn flush() -> anyhow::Result<()> { - unsafe { - match &CONFIG { - Some(config) => update_raw(config.clone()), - None => Err(anyhow::anyhow!("Config wasn't loaded into the memory")) - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct Config { - pub launcher: Launcher, - pub game: Game, - pub patch: Patch -} - -impl Config { - pub fn try_get_selected_wine_info(&self) -> Option { - match &self.game.wine.selected { - Some(selected) => { - WineList::get().iter() - .flat_map(|group| group.versions.clone()) - .find(|version| version.name.eq(selected)) - }, - None => None - } - } - - /// Try to get a path to the wine64 executable based on `game.wine.builds` and `game.wine.selected` - /// - /// Returns `Some("wine64")` if: - /// 1) `game.wine.selected = None` - /// 2) wine64 installed and available in system - pub fn try_get_wine_executable(&self) -> Option { - match self.try_get_selected_wine_info() { - Some(selected) => Some(self.game.wine.builds.join(selected.name).join(selected.files.wine64)), - None => { - if lib::is_available("wine64") { - Some(PathBuf::from("wine64")) - } else { - None - } - } - } - } - - /// Try to get DXVK version applied to wine prefix - /// - /// Returns: - /// 1) `Ok(Some(..))` if version was found - /// 2) `Ok(None)` if version wasn't found, so too old or dxvk is not applied - /// 3) `Err(..)` if failed to get applied dxvk version, likely because wrong prefix path specified - pub fn try_get_selected_dxvk_info(&self) -> std::io::Result> { - Ok(match Dxvk::get_version(&self.game.wine.prefix)? { - Some(version) => { - DxvkList::get() - .iter() - .flat_map(|group| group.versions.clone()) - .find(move |dxvk| dxvk.version == version) - }, - None => None - }) - } -} - -impl From<&JsonValue> for Config { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - launcher: match value.get("launcher") { - Some(value) => Launcher::from(value), - None => default.launcher - }, - - game: match value.get("game") { - Some(value) => Game::from(value), - None => default.game - }, - - patch: match value.get("patch") { - Some(value) => Patch::from(value), - None => default.patch - } - } - } -} diff --git a/src/lib/config/patch.rs b/src/lib/config/patch.rs deleted file mode 100644 index d8044ab..0000000 --- a/src/lib/config/patch.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::path::{Path, PathBuf}; - -use serde::{Serialize, Deserialize}; -use serde_json::Value as JsonValue; - -use crate::lib::consts::launcher_dir; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Patch { - pub path: PathBuf, - pub servers: Vec, - pub root: bool -} - -impl Default for Patch { - fn default() -> Self { - let launcher_dir = launcher_dir().expect("Failed to get launcher dir"); - - Self { - path: launcher_dir.join("patch"), - servers: vec![ - "https://notabug.org/Krock/dawn".to_string(), - "https://codespace.gay/Maroxy/dawnin".to_string() - ], - - // Disable root requirement for patching if we're running launcher in flatpak - root: !Path::new("/.flatpak-info").exists() - } - } -} - -impl From<&JsonValue> for Patch { - fn from(value: &JsonValue) -> Self { - let default = Self::default(); - - Self { - path: match value.get("path") { - Some(value) => match value.as_str() { - Some(value) => PathBuf::from(value), - None => default.path - }, - None => default.path - }, - - servers: match value.get("servers") { - Some(value) => match value.as_array() { - Some(values) => { - let mut servers = Vec::new(); - - for value in values { - if let Some(server) = value.as_str() { - servers.push(server.to_string()); - } - } - - servers - }, - None => default.servers - }, - None => default.servers - }, - - root: match value.get("root") { - Some(value) => value.as_bool().unwrap_or(default.root), - None => default.root - } - } - } -} diff --git a/src/lib/config/resolution.rs b/src/lib/config/resolution.rs deleted file mode 100644 index e71ebdb..0000000 --- a/src/lib/config/resolution.rs +++ /dev/null @@ -1,75 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum Resolution { - // qHD; 960x540 - MiniHD, - - // 1280x720 - HD, - - // 1920x1080 - FullHD, - - // 2560x1440 - QuadHD, - - // 3840x2160 - UltraHD, - - Custom(u64, u64) -} - -impl Resolution { - pub fn list() -> Vec { - vec![ - Self::MiniHD, - Self::HD, - Self::FullHD, - Self::QuadHD, - Self::UltraHD - ] - } - - pub fn get_model() -> gtk::StringList { - let model = gtk::StringList::new(&[]); - - model.append("Custom"); - - for res in Self::list() { - model.append(&res.to_string()); - } - - model - } - - pub fn from_pair(width: u64, height: u64) -> Self { - for res in Self::list() { - let pair = res.get_pair(); - - if pair.0 == width && pair.1 == height { - return res; - } - } - - Self::Custom(width, height) - } - - pub fn get_pair(&self) -> (u64, u64) { - match self { - Self::MiniHD => (960, 540), - Self::HD => (1280, 720), - Self::FullHD => (1920, 1080), - Self::QuadHD => (2560, 1440), - Self::UltraHD => (3840, 2160), - - Self::Custom(w, h) => (*w, *h) - } - } -} - -impl std::fmt::Display for Resolution { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let (w, h) = self.get_pair(); - - f.write_str(&format!("{w}x{h}")) - } -} diff --git a/src/lib/consts.rs b/src/lib/consts.rs deleted file mode 100644 index ad3dce2..0000000 --- a/src/lib/consts.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::path::PathBuf; -use std::time::Duration; - -use cached::proc_macro::cached; - -/// Timeout used by `anime_game_core::telemetry::is_disabled` to check acessibility of telemetry servers -pub const TELEMETRY_CHECK_TIMEOUT: Option = Some(Duration::from_secs(3)); - -/// Timeout used by `anime_game_core::linux_patch::Patch::try_fetch` to fetch patch info -pub const PATCH_FETCHING_TIMEOUT: Option = Some(Duration::from_secs(5)); - -#[cached] -pub fn launcher_dir() -> Option { - dirs::data_dir().map(|dir| dir.join("anime-game-launcher")) -} - -#[cached] -pub fn config_file() -> Option { - launcher_dir().map(|dir| dir.join("config.json")) -} diff --git a/src/lib/dxvk.rs b/src/lib/dxvk.rs deleted file mode 100644 index edd7dea..0000000 --- a/src/lib/dxvk.rs +++ /dev/null @@ -1,114 +0,0 @@ -use serde::{Serialize, Deserialize}; - -use std::process::Output; -use std::path::PathBuf; - -use lazy_static::lazy_static; - -use wincompatlib::prelude::*; - -use crate::lib::config; - -lazy_static! { - static ref GROUPS: Vec = vec![ - Group { - title: String::from("Vanilla"), - subtitle: None, - versions: serde_json::from_str::>(include_str!("../../components/dxvk/vanilla.json")).unwrap().into_iter().take(12).collect() - }, - Group { - title: String::from("Async"), - subtitle: Some(String::from("This version is not recommended for usage as can lead to anti-cheat detection. Automatically uses DXVK_ASYNC=1")), - versions: serde_json::from_str::>(include_str!("../../components/dxvk/async.json")).unwrap().into_iter().take(12).collect() - } - ]; -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct List; - -impl List { - pub fn get() -> Vec { - GROUPS.clone() - } - - /// List only downloaded DXVK versions in some specific folder - pub fn list_downloaded>(folder: T) -> std::io::Result> { - let mut downloaded = Vec::new(); - - let list = Self::get(); - - for entry in std::fs::read_dir(folder.into())? { - let name = entry?.file_name(); - - for group in &list { - for version in &group.versions { - if name == version.name.as_str() { - downloaded.push(version.clone()); - - break; - } - } - } - } - - Ok(downloaded) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Group { - pub title: String, - pub subtitle: Option, - pub versions: Vec -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Version { - pub name: String, - pub version: String, - pub uri: String, - pub recommended: bool -} - -impl Version { - pub fn latest() -> Result { - Ok(List::get()[0].versions[0].clone()) - } - - pub fn is_downloaded_in>(&self, folder: T) -> bool { - folder.into().join(&self.name).exists() - } - - pub fn apply>(&self, dxvks_folder: T, prefix_path: T) -> anyhow::Result { - let apply_path = dxvks_folder.into().join(&self.name).join("setup_dxvk.sh"); - let config = config::get()?; - - let (wine_path, wineserver_path, wineboot_path) = match config.try_get_selected_wine_info() { - Some(wine) => { - let wine_folder = config.game.wine.builds.join(wine.name); - - let wine_path = wine_folder.join(wine.files.wine64); - let wineserver_path = wine_folder.join(wine.files.wineserver); - let wineboot_path = wine_folder.join(wine.files.wineboot); - - (wine_path, wineserver_path, wineboot_path) - }, - None => (PathBuf::from("wine64"), PathBuf::from("wineserver"), PathBuf::from("wineboot")) - }; - - let result = Dxvk::install( - apply_path, - prefix_path.into(), - wine_path.clone(), - wine_path, - wineboot_path, - wineserver_path - ); - - match result { - Ok(output) => Ok(output), - Err(err) => Err(err.into()) - } - } -} diff --git a/src/lib/fps_unlocker/config_schema.rs b/src/lib/fps_unlocker/config_schema.rs deleted file mode 100644 index deee15b..0000000 --- a/src/lib/fps_unlocker/config_schema.rs +++ /dev/null @@ -1,68 +0,0 @@ -use serde::Serialize; - -use super::FpsUnlockerConfig; - -#[derive(Debug, Clone, Serialize)] -#[allow(non_snake_case)] -pub struct ConfigSchema { - pub DllList: Vec, - pub Priority: u64, - pub MonitorNum: u64, - pub CustomResY: u64, - pub CustomResX: u64, - pub FPSTarget: u64, - pub UsePowerSave: bool, - pub StartMinimized: bool, - pub IsExclusiveFullscreen: bool, - pub UseCustomRes: bool, - pub Fullscreen: bool, - pub PopupWindow: bool, - pub AutoClose: bool, - pub AutoDisableVSync: bool, - pub AutoStart: bool, - pub GamePath: Option -} - -impl Default for ConfigSchema { - fn default() -> Self { - Self { - DllList: vec![], - Priority: 3, - MonitorNum: 1, - CustomResY: 1080, - CustomResX: 1920, - FPSTarget: 120, - UsePowerSave: false, - IsExclusiveFullscreen: false, - UseCustomRes: false, - Fullscreen: false, - PopupWindow: false, - AutoDisableVSync: true, - GamePath: None, - - // Launcher-specific settings - AutoStart: true, - AutoClose: true, - StartMinimized: true - } - } -} - -impl ConfigSchema { - pub fn from_config(config: FpsUnlockerConfig) -> Self { - Self { - FPSTarget: config.fps, - UsePowerSave: config.power_saving, - PopupWindow: config.window_mode == 1, - Fullscreen: config.window_mode == 2, - MonitorNum: config.monitor, - Priority: config.priority, - - ..Self::default() - } - } - - pub fn json(&self) -> serde_json::Result { - serde_json::to_string(self) - } -} diff --git a/src/lib/fps_unlocker/mod.rs b/src/lib/fps_unlocker/mod.rs deleted file mode 100644 index c4ffcf4..0000000 --- a/src/lib/fps_unlocker/mod.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::path::PathBuf; - -use anime_game_core::installer::downloader::Downloader; - -use crate::lib::config::game::enhancements::fps_unlocker::config::Config as FpsUnlockerConfig; - -pub mod config_schema; - -const LATEST_INFO: (&str, &str) = ( - "6040a6f0be5dbf4d55d6b129cad47b5b", - "https://github.com/34736384/genshin-fps-unlock/releases/download/v2.0.0/unlockfps_clr.exe" -); - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct FpsUnlocker { - dir: PathBuf -} - -impl FpsUnlocker { - /// Get FpsUnlocker from its containment directory - /// - /// Returns - /// - `Err(..)` if failed to read `unlocker.exe` file - /// - `Ok(None)` if version is not latest - /// - `Ok(..)` if version is latest - pub fn from_dir>(dir: T) -> anyhow::Result> { - let dir = dir.into(); - - let hash = format!("{:x}", md5::compute(std::fs::read(dir.join("unlocker.exe"))?)); - - Ok(if hash == LATEST_INFO.0 { - Some(Self { dir }) - } else { - None - }) - } - - /// Download FPS unlocker to specified directory - pub fn download>(dir: T) -> anyhow::Result { - let mut downloader = Downloader::new(LATEST_INFO.1)?; - - let dir = dir.into(); - - // Create FPS unlocker folder if needed - if !dir.exists() { - std::fs::create_dir_all(&dir)?; - } - - match downloader.download_to(dir.join("unlocker.exe"), |_, _| {}) { - Ok(_) => Ok(Self { - dir - }), - Err(err) => { - let err: std::io::Error = err.into(); - - Err(err.into()) - } - } - } - - pub fn get_binary(&self) -> PathBuf { - Self::get_binary_in(&self.dir) - } - - pub fn get_binary_in>(dir: T) -> PathBuf { - dir.into().join("unlocker.exe") - } - - pub fn dir(&self) -> &PathBuf { - &self.dir - } - - /// Generate and save FPS unlocker config file to the game's directory - pub fn update_config(&self, config: FpsUnlockerConfig) -> anyhow::Result<()> { - let config = config_schema::ConfigSchema::from_config(config); - - Ok(std::fs::write( - self.dir.join("fps_config.json"), - config.json()? - )?) - } -} diff --git a/src/lib/game.rs b/src/lib/game.rs deleted file mode 100644 index 66cc488..0000000 --- a/src/lib/game.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::process::Command; - -use anime_game_core::genshin::telemetry; - -use super::consts; -use super::config; -use super::fps_unlocker::FpsUnlocker; - -/*#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Terminal { - GnomeTerminal, - Konsole, - Xfce4Terminal -} - -impl Terminal { - pub fn get_command(&self) -> &str { - match self { - Terminal::GnomeTerminal => "gnome-terminal", - Terminal::Konsole => "konsole", - Terminal::Xfce4Terminal => "xfce4-terminal" - } - } - - pub fn iter() -> impl Iterator { - [ - Terminal::GnomeTerminal, - Terminal::Konsole, - Terminal::Xfce4Terminal - ].into_iter() - } - - pub fn get_args(&self, bash_command: &str) -> Vec { - match self { - Terminal::GnomeTerminal => vec![ - String::from("--"), - String::from("bash"), - String::from("-c"), - format!("{} && bash", bash_command) - ], - Terminal::Konsole | Terminal::Xfce4Terminal => vec![ - String::from("--hold"), - String::from("-e"), - format!("\"bash -c '{} && bash'\"", bash_command) - ] - } - } -} - -/// Try to get GUI terminal installed in system -pub fn try_get_terminal() -> Option { - for terminal in Terminal::iter() { - if let Ok(output) = Command::new(terminal.get_command()).output() { - if output.status.success() { - return Some(terminal); - } - } - } - - None -}*/ - -/// Try to run the game -/// -/// If `debug = true`, then the game will be run in the new terminal window -pub fn run() -> anyhow::Result<()> { - let config = config::get()?; - - if !config.game.path.exists() { - return Err(anyhow::anyhow!("Game is not installed")); - } - - let wine_executable = match config.try_get_wine_executable() { - Some(path) => path, - None => return Err(anyhow::anyhow!("Couldn't find wine executable")) - }; - - // Check telemetry servers - - if let Some(server) = telemetry::is_disabled(consts::TELEMETRY_CHECK_TIMEOUT) { - return Err(anyhow::anyhow!("Telemetry server is not disabled: {server}")); - } - - // Prepare fps unlocker - // 1) Download if needed - // 2) Generate config file - // 3) Generate fpsunlocker.bat from launcher.bat - - if config.game.enhancements.fps_unlocker.enabled { - let unlocker = match FpsUnlocker::from_dir(&config.game.enhancements.fps_unlocker.path) { - Ok(Some(unlocker)) => unlocker, - - other => { - // Ok(None) means unknown version, so we should delete it before downloading newer one - // because otherwise downloader will try to continue downloading "partially downloaded" file - if let Ok(None) = other { - std::fs::remove_file(FpsUnlocker::get_binary_in(&config.game.enhancements.fps_unlocker.path))?; - } - - match FpsUnlocker::download(&config.game.enhancements.fps_unlocker.path) { - Ok(unlocker) => unlocker, - Err(err) => return Err(anyhow::anyhow!("Failed to download FPS unlocker: {err}")) - } - } - }; - - // Generate FPS unlocker config file - if let Err(err) = unlocker.update_config(config.game.enhancements.fps_unlocker.config.clone()) { - return Err(anyhow::anyhow!("Failed to update FPS unlocker config: {err}")); - } - - let bat_path = config.game.path.join("fpsunlocker.bat"); - let original_bat_path = config.game.path.join("launcher.bat"); - - // Generate fpsunlocker.bat from launcher.bat - std::fs::write(bat_path, std::fs::read_to_string(original_bat_path)? - .replace("start GenshinImpact.exe %*", &format!("start GenshinImpact.exe %*\n\nZ:\ncd \"{}\"\nstart unlocker.exe", unlocker.dir().to_string_lossy())) - .replace("start YuanShen.exe %*", &format!("start YuanShen.exe %*\n\nZ:\ncd \"{}\"\nstart unlocker.exe", unlocker.dir().to_string_lossy())))?; - } - - // Prepare bash -c '' - - let mut bash_chain = String::new(); - - if config.game.enhancements.gamemode { - bash_chain += "gamemoderun "; - } - - bash_chain += &format!("'{}' ", wine_executable.to_string_lossy()); - - if let Some(virtual_desktop) = config.game.wine.virtual_desktop.get_command() { - bash_chain += &format!("{virtual_desktop} "); - } - - bash_chain += if config.game.enhancements.fps_unlocker.enabled { "fpsunlocker.bat " } else { "launcher.bat " }; - - if config.game.wine.borderless { - bash_chain += "-screen-fullscreen 0 -popupwindow "; - } - - // https://notabug.org/Krock/dawn/src/master/TWEAKS.md - if config.game.enhancements.fsr.enabled { - bash_chain += "-window-mode exclusive "; - } - - // gamescope -- - if let Some(gamescope) = config.game.enhancements.gamescope.get_command() { - bash_chain = format!("{gamescope} -- {bash_chain}"); - } - - let bash_chain = match &config.game.command { - Some(command) => command.replace("%command%", &bash_chain), - None => bash_chain - }; - - let mut command = Command::new("bash"); - - command.arg("-c"); - command.arg(&bash_chain); - - // Setup environment - - command.env("WINEARCH", "win64"); - command.env("WINEPREFIX", &config.game.wine.prefix); - - // Add DXVK_ASYNC=1 for dxvk-async builds automatically - if let Ok(Some(dxvk)) = &config.try_get_selected_dxvk_info() { - if dxvk.version.contains("async") { - command.env("DXVK_ASYNC", "1"); - } - } - - command.envs(config.game.wine.sync.get_env_vars()); - command.envs(config.game.enhancements.hud.get_env_vars(&config)); - command.envs(config.game.enhancements.fsr.get_env_vars()); - command.envs(config.game.wine.language.get_env_vars()); - - command.envs(config.game.environment); - - // Run command - - println!("Running command: bash -c \"{}\"", bash_chain); - - command.current_dir(config.game.path).spawn()?; - - Ok(()) -} diff --git a/src/lib/launcher/mod.rs b/src/lib/launcher/mod.rs deleted file mode 100644 index 2039478..0000000 --- a/src/lib/launcher/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod states; diff --git a/src/lib/launcher/states.rs b/src/lib/launcher/states.rs deleted file mode 100644 index eca61c9..0000000 --- a/src/lib/launcher/states.rs +++ /dev/null @@ -1,128 +0,0 @@ -use anime_game_core::prelude::*; -use anime_game_core::genshin::prelude::*; - -use crate::lib::consts; -use crate::lib::config; - -#[derive(Debug, Clone)] -pub enum LauncherState { - Launch, - - /// Always contains `VersionDiff::Predownload` - PredownloadAvailable { - game: VersionDiff, - voices: Vec - }, - - PatchAvailable(Patch), - - WineNotInstalled, - PrefixNotExists, - - // Always contains `VersionDiff::Diff` - VoiceUpdateAvailable(VersionDiff), - - /// Always contains `VersionDiff::Outdated` - VoiceOutdated(VersionDiff), - - /// Always contains `VersionDiff::NotInstalled` - VoiceNotInstalled(VersionDiff), - - // Always contains `VersionDiff::Diff` - GameUpdateAvailable(VersionDiff), - - /// Always contains `VersionDiff::Outdated` - GameOutdated(VersionDiff), - - /// Always contains `VersionDiff::NotInstalled` - GameNotInstalled(VersionDiff) -} - -impl Default for LauncherState { - fn default() -> Self { - Self::Launch - } -} - -impl LauncherState { - pub fn get(status: T) -> anyhow::Result { - let config = config::get()?; - - // Check wine existence - if config.try_get_wine_executable().is_none() { - return Ok(Self::WineNotInstalled); - } - - // Check prefix existence - if !config.game.wine.prefix.join("drive_c").exists() { - return Ok(Self::PrefixNotExists); - } - - // Check game installation status - status("Updating game info..."); - - let game = Game::new(&config.game.path); - let diff = game.try_get_diff()?; - - Ok(match diff { - VersionDiff::Latest(_) | VersionDiff::Predownload { .. } => { - status("Updating voice info..."); - - let mut predownload_voice = Vec::new(); - - for voice_package in &config.game.voices { - let mut voice_package = VoicePackage::with_locale(match VoiceLocale::from_str(voice_package) { - Some(locale) => locale, - None => return Err(anyhow::anyhow!("Incorrect voice locale \"{}\" specified in the config", voice_package)) - })?; - - status(format!("Updating voice info ({})...", voice_package.locale().to_name()).as_str()); - - // Replace voice package struct with the one constructed in the game's folder - // so it'll properly calculate its difference instead of saying "not installed" - if voice_package.is_installed_in(&config.game.path) { - voice_package = match VoicePackage::new(get_voice_package_path(&config.game.path, voice_package.locale())) { - Some(locale) => locale, - None => return Err(anyhow::anyhow!("Failed to load {} voice package", voice_package.locale().to_name())) - }; - } - - let diff = voice_package.try_get_diff()?; - - match diff { - VersionDiff::Latest(_) => (), - VersionDiff::Predownload { .. } => predownload_voice.push(diff), - - VersionDiff::Diff { .. } => return Ok(Self::VoiceUpdateAvailable(diff)), - VersionDiff::Outdated { .. } => return Ok(Self::VoiceOutdated(diff)), - VersionDiff::NotInstalled { .. } => return Ok(Self::VoiceNotInstalled(diff)) - } - } - - status("Updating patch info..."); - - let patch = Patch::try_fetch(config.patch.servers.clone(), consts::PATCH_FETCHING_TIMEOUT)?; - - if patch.is_applied(&config.game.path)? { - if let VersionDiff::Predownload { .. } = diff { - Self::PredownloadAvailable { - game: diff, - voices: predownload_voice - } - } - - else { - Self::Launch - } - } - - else { - Self::PatchAvailable(patch) - } - }, - VersionDiff::Diff { .. } => Self::GameUpdateAvailable(diff), - VersionDiff::Outdated { .. } => Self::GameOutdated(diff), - VersionDiff::NotInstalled { .. } => Self::GameNotInstalled(diff) - }) - } -} diff --git a/src/lib/mod.rs b/src/lib/mod.rs deleted file mode 100644 index 15b66fd..0000000 --- a/src/lib/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -pub mod consts; -pub mod config; -pub mod game; -pub mod dxvk; -pub mod wine; -pub mod launcher; -pub mod prettify_bytes; -pub mod fps_unlocker; - -use std::process::{Command, Stdio}; - -/// Check if specified binary is available -/// -/// ``` -/// use crate::lib; -/// -/// assert!(lib::is_available("bash")); -/// ``` -#[allow(unused_must_use)] -pub fn is_available(binary: &str) -> bool { - match Command::new(binary).stdout(Stdio::null()).stderr(Stdio::null()).spawn() { - Ok(mut child) => { - child.kill(); - - true - }, - Err(_) => false - } -} diff --git a/src/lib/prettify_bytes.rs b/src/lib/prettify_bytes.rs deleted file mode 100644 index 516eb71..0000000 --- a/src/lib/prettify_bytes.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub fn prettify_bytes(bytes: u64) -> String { - if bytes > 1024 * 1024 * 1024 { - format!("{:.2} GB", bytes as f64 / 1024.0 / 1024.0 / 1024.0) - } - - else if bytes > 1024 * 1024 { - format!("{:.2} MB", bytes as f64 / 1024.0 / 1024.0) - } - - else if bytes > 1024 { - format!("{:.2} KB", bytes as f64 / 1024.0) - } - - else { - format!("{:.2} B", bytes) - } -} diff --git a/src/lib/wine.rs b/src/lib/wine.rs deleted file mode 100644 index 0d38a37..0000000 --- a/src/lib/wine.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::path::PathBuf; - -use serde::{Serialize, Deserialize}; - -use wincompatlib::prelude::*; - -lazy_static::lazy_static! { - static ref GROUPS: Vec = vec![ - Group { - title: String::from("Wine-GE-Proton"), - subtitle: None, - versions: serde_json::from_str::>(include_str!("../../components/wine/wine-ge-proton.json")).unwrap().into_iter().take(12).collect() - }, - Group { - title: String::from("GE-Proton"), - subtitle: Some(String::from("This version includes its own DXVK builds and you can use DXVK_ASYNC variable")), - versions: serde_json::from_str::>(include_str!("../../components/wine/ge-proton.json")).unwrap().into_iter().take(12).collect() - }, - Group { - title: String::from("Soda"), - subtitle: Some(String::from("New runner based on Valve's Wine, with patches from Proton, TKG and GE. Developed by Bottles")), - versions: serde_json::from_str::>(include_str!("../../components/wine/soda.json")).unwrap().into_iter().take(12).collect() - }, - Group { - title: String::from("Lutris"), - subtitle: None, - versions: serde_json::from_str::>(include_str!("../../components/wine/lutris.json")).unwrap().into_iter().take(12).collect() - } - ]; -} - -pub struct List; - -impl List { - pub fn get() -> Vec { - GROUPS.clone() - } - - /// List only downloaded wine versions in some specific folder - pub fn list_downloaded>(folder: T) -> std::io::Result> { - let mut downloaded = Vec::new(); - - let list = Self::get(); - - for entry in std::fs::read_dir(folder.into())? { - let name = entry?.file_name(); - - for group in &list { - for version in &group.versions { - if name == version.name.as_str() { - downloaded.push(version.clone()); - - break; - } - } - } - } - - downloaded.sort_by(|a, b| b.name.partial_cmp(&a.name).unwrap()); - - Ok(downloaded) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Group { - pub title: String, - pub subtitle: Option, - pub versions: Vec -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Version { - pub name: String, - pub title: String, - pub uri: String, - pub files: Files, - pub recommended: bool -} - -impl Version { - pub fn latest() -> Result { - Ok(List::get()[0].versions[0].clone()) - } - - pub fn is_downloaded_in>(&self, folder: T) -> bool { - folder.into().join(&self.name).exists() - } - - pub fn to_wine(&self) -> Wine { - Wine::new( - &self.files.wine64, - None, - Some(WineArch::Win64), - Some(&self.files.wineboot), - Some(&self.files.wineserver), - WineLoader::Current - ) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Files { - pub wine: String, - pub wine64: String, - pub wineserver: String, - pub wineboot: String, - pub winecfg: String -} diff --git a/src/main.rs b/src/main.rs index d9e2fff..7357cad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,184 +1,35 @@ -use gtk::prelude::*; +use relm4::prelude::*; -use gtk::{CssProvider, StyleContext, STYLE_PROVIDER_PRIORITY_APPLICATION}; -use gtk::gdk::Display; -use gtk::glib; -use gtk::glib::clone; - -use std::path::Path; -use std::fs; +use anime_launcher_sdk::config; +pub mod i18n; pub mod ui; -pub mod lib; - -use ui::*; - -pub const APP_ID: &str = "moe.launcher.an-anime-game-launcher-gtk"; -pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const APP_DEBUG: bool = cfg!(debug_assertions); fn main() { + tracing_subscriber::fmt() + .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) + .with_max_level(tracing::Level::TRACE) + .init(); + + tracing::info!("Starting application"); + adw::init().expect("Libadwaita initialization failed"); // Register and include resources - gtk::gio::resources_register_include!(".assets.gresource") + gtk::gio::resources_register_include!("resources.gresource") .expect("Failed to register resources"); // Set application's title - glib::set_application_name("An Anime Game Launcher"); - glib::set_program_name(Some("An Anime Game Launcher")); + gtk::glib::set_application_name("An Anime Game Launcher"); + gtk::glib::set_program_name(Some("An Anime Game Launcher")); - // Create app - let application = gtk::Application::new( - Some(APP_ID), - Default::default() - ); + // Set UI language + unsafe { + i18n::LANG = config::get().unwrap().launcher.language.parse().unwrap(); + } - application.add_main_option( - "run-game", - glib::Char::from(0), - glib::OptionFlags::empty(), - glib::OptionArg::None, - "Run the game", - None - ); + // Run the app + let app = RelmApp::new("moe.launcher.an-anime-game-launcher"); - application.add_main_option( - "just-run-game", - glib::Char::from(0), - glib::OptionFlags::empty(), - glib::OptionArg::None, - "Run the game whenever it possible, ignoring updates predownloads", - None - ); - - let run_game = std::rc::Rc::new(std::cell::Cell::new(false)); - let just_run_game = std::rc::Rc::new(std::cell::Cell::new(false)); - - application.connect_handle_local_options(clone!(@strong run_game, @strong just_run_game => move |_, arg| { - if arg.contains("just-run-game") { - just_run_game.set(true); - } - - else if arg.contains("run-game") { - run_game.set(true); - } - - -1 - })); - - // Init app window and show it - application.connect_activate(move |app| { - // Apply CSS styles to the application - let provider = CssProvider::new(); - - provider.load_from_data(include_bytes!("../assets/styles.css")); - - StyleContext::add_provider_for_display( - &Display::default().expect("Could not connect to a display"), - &provider, - STYLE_PROVIDER_PRIORITY_APPLICATION - ); - - // Create default launcher folder if needed - let launcher_dir = lib::consts::launcher_dir().expect("Failed to get launcher dir"); - - if !launcher_dir.exists() || launcher_dir.join(".first-run").exists() { - fs::create_dir_all(&launcher_dir).expect("Failed to create default launcher dir"); - fs::write(launcher_dir.join(".first-run"), "").expect("Failed to create .first-run file"); - - let first_run = FirstRunApp::new(app).expect("Failed to init FirstRunApp"); - - first_run.show(); - } - - else { - let config = lib::config::get().expect("Failed to load config"); - - // Create wine builds folder - if !Path::new(&config.game.wine.builds).exists() { - fs::create_dir_all(config.game.wine.builds) - .expect("Failed to create wine builds directory"); - } - - // Create DXVK builds folder - if !Path::new(&config.game.dxvk.builds).exists() { - fs::create_dir_all(config.game.dxvk.builds) - .expect("Failed to create DXVK builds directory"); - } - - // Set game edition - anime_game_core::genshin::consts::set_game_edition(config.launcher.edition.into()); - - // Load main window - let main = MainApp::new(app).expect("Failed to init MainApp"); - - // Load initial launcher state - let awaiter = main.update_state(); - - if !run_game.get() && !just_run_game.get() { - main.show(); - } - - else { - use lib::launcher::states::LauncherState; - - let just_run_game = just_run_game.get(); - - awaiter.then(move |state| { - let mut state = state.as_ref().expect("Failed to load launcher state"); - - #[allow(clippy::or_fun_call)] - if let LauncherState::PredownloadAvailable { game, voices } = state { - if just_run_game { - state = &LauncherState::Launch; - } - - else if let Ok(config) = lib::config::get() { - let mut predownloaded = true; - - let temp = config.launcher.temp.unwrap_or("/tmp".into()); - - if !temp.join(game.file_name().unwrap_or(String::from("\0"))).exists() { - predownloaded = false; - } - - else { - for voice in voices { - if !temp.join(voice.file_name().unwrap_or(String::from("\0"))).exists() { - predownloaded = false; - - break; - } - } - } - - if predownloaded { - state = &LauncherState::Launch; - } - } - } - - match state { - LauncherState::Launch => { - main.update(ui::main::Actions::PerformButtonEvent).unwrap(); - - std::thread::sleep(std::time::Duration::from_secs(5)); - std::process::exit(0); - } - - _ => main.show() - } - }); - } - } - }); - - // Flush config from the memory to the file before closing the app - application.connect_shutdown(|_| { - lib::config::flush().expect("Failed to save config file"); - }); - - // Run app - application.run(); + app.run::(()); } diff --git a/src/ui/components/dxvk_group.rs b/src/ui/components/dxvk_group.rs deleted file mode 100644 index 5948c80..0000000 --- a/src/ui/components/dxvk_group.rs +++ /dev/null @@ -1,47 +0,0 @@ -use adw::prelude::*; - -use std::path::PathBuf; - -use crate::lib::dxvk::Group; -use super::dxvk_row::DxvkRow; - -#[derive(Debug, Clone)] -pub struct DxvkGroup { - pub group: Group, - pub version_components: Vec, - - pub expander_row: adw::ExpanderRow -} - -impl DxvkGroup { - pub fn new(group: Group) -> Self { - let expander_row = adw::ExpanderRow::new(); - - expander_row.set_title(&group.title); - expander_row.set_subtitle(group.subtitle.as_ref().unwrap_or(&String::new())); - - let mut version_components = Vec::new(); - - for version in &group.versions { - let component = DxvkRow::new(version.clone()); - - expander_row.add_row(&component.row); - - version_components.push(component); - } - - Self { - group, - version_components, - expander_row - } - } - - pub fn update_states>(&self, runners_folder: T) { - let runners_folder = runners_folder.into(); - - for component in &self.version_components { - component.update_state(&runners_folder); - } - } -} diff --git a/src/ui/components/dxvk_row.rs b/src/ui/components/dxvk_row.rs deleted file mode 100644 index 05e17a8..0000000 --- a/src/ui/components/dxvk_row.rs +++ /dev/null @@ -1,104 +0,0 @@ -use gtk::prelude::*; -use adw::prelude::*; - -use std::path::PathBuf; - -use crate::lib::dxvk::Version; -use crate::ui::traits::download_component::*; - -#[derive(Debug, Clone)] -pub struct DxvkRow { - pub version: Version, - - pub row: adw::ActionRow, - pub button: gtk::Button, - pub apply_button: gtk::Button, - pub progress_bar: gtk::ProgressBar -} - -impl DxvkRow { - pub fn new(version: Version) -> Self { - let row = adw::ActionRow::new(); - let button = gtk::Button::new(); - let apply_button = gtk::Button::new(); - - row.set_title(&version.version); - row.set_visible(version.recommended); - - apply_button.set_icon_name("view-refresh-symbolic"); - apply_button.set_valign(gtk::Align::Center); - apply_button.add_css_class("flat"); - apply_button.set_tooltip_text(Some("Apply")); - apply_button.hide(); - - row.add_suffix(&apply_button); - - button.set_icon_name("document-save-symbolic"); - button.set_valign(gtk::Align::Center); - button.add_css_class("flat"); - - row.add_suffix(&button); - - let progress_bar = gtk::ProgressBar::new(); - - progress_bar.set_text(Some("Downloading: 0%")); - progress_bar.set_show_text(true); - - progress_bar.set_width_request(200); - progress_bar.set_valign(gtk::Align::Center); - progress_bar.hide(); - - row.add_suffix(&progress_bar); - - Self { - version, - row, - button, - apply_button, - progress_bar - } - } - - pub fn update_state>(&self, dxvks_folder: T) { - if self.is_downloaded(dxvks_folder) { - self.button.set_icon_name("user-trash-symbolic"); - - self.apply_button.show(); - } - - else { - self.button.set_icon_name("document-save-symbolic"); - - self.apply_button.hide(); - } - } - - pub fn apply>(&self, dxvks_folder: T, prefix_path: T) -> anyhow::Result { - self.button.set_sensitive(false); - self.apply_button.set_sensitive(false); - - let result = self.version.apply(dxvks_folder, prefix_path); - - self.button.set_sensitive(true); - self.apply_button.set_sensitive(true); - - result - } -} - -impl DownloadComponent for DxvkRow { - fn get_component_path>(&self, installation_path: T) -> PathBuf { - installation_path.into().join(&self.version.name) - } - - fn get_downloading_widgets(&self) -> (gtk::ProgressBar, gtk::Button) { - (self.progress_bar.clone(), self.button.clone()) - } - - fn get_download_uri(&self) -> String { - self.version.uri.clone() - } -} - -unsafe impl Send for DxvkRow {} -unsafe impl Sync for DxvkRow {} diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs deleted file mode 100644 index 3286d10..0000000 --- a/src/ui/components/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod wine_group; -pub mod wine_row; -pub mod dxvk_group; -pub mod dxvk_row; -pub mod progress_bar; -pub mod voiceover_row; diff --git a/src/ui/components/progress_bar.rs b/src/ui/components/progress_bar.rs deleted file mode 100644 index f905c0f..0000000 --- a/src/ui/components/progress_bar.rs +++ /dev/null @@ -1,95 +0,0 @@ -use gtk::prelude::*; - -use gtk::glib; - -use anime_game_core::prelude::*; - -use crate::lib::prettify_bytes::prettify_bytes; - -#[derive(Debug)] -pub enum ProgressUpdateResult { - Updated, - Error(String, String), - Finished -} - -#[derive(Clone, glib::Downgrade)] -pub struct ProgressBar { - pub progress_bar: gtk::ProgressBar, - pub default_group: adw::PreferencesGroup, - pub progress_bar_group: adw::PreferencesGroup -} - -impl ProgressBar { - pub fn new(progress_bar: gtk::ProgressBar, default_group: adw::PreferencesGroup, progress_bar_group: adw::PreferencesGroup) -> Self { - Self { - progress_bar, - default_group, - progress_bar_group - } - } - - pub fn show(&self) { - self.progress_bar.set_text(None); - self.progress_bar.set_fraction(0.0); - - self.default_group.hide(); - self.progress_bar_group.show(); - } - - pub fn hide(&self) { - self.default_group.show(); - self.progress_bar_group.hide(); - } - - pub fn update(&self, fraction: f64, text: Option<&str>) { - self.progress_bar.set_fraction(fraction); - self.progress_bar.set_text(text); - } - - pub fn update_from_state(&self, state: InstallerUpdate) -> ProgressUpdateResult { - match state { - InstallerUpdate::CheckingFreeSpace(_) => self.progress_bar.set_text(Some("Checking free space...")), - InstallerUpdate::DownloadingStarted(_) => (), - - InstallerUpdate::DownloadingProgress(curr, total) => { - let progress = curr as f64 / total as f64; - - self.update(progress, Some(&format!( - "Downloading: {:.2}% ({} of {})", - progress * 100.0, - prettify_bytes(curr), - prettify_bytes(total) - ))); - } - - InstallerUpdate::UnpackingProgress(curr, total) => { - let progress = curr as f64 / total as f64; - - self.update(progress, Some(&format!( - "Unpacking: {:.2}% ({} of {})", - progress * 100.0, - prettify_bytes(curr), - prettify_bytes(total) - ))); - } - - InstallerUpdate::DownloadingFinished => (), - InstallerUpdate::UnpackingStarted(_) => (), - - InstallerUpdate::DownloadingError(err) => { - let err: std::io::Error = err.into(); - - return ProgressUpdateResult::Error(String::from("Failed to download"), err.to_string()); - } - - InstallerUpdate::UnpackingError(err) => return ProgressUpdateResult::Error(String::from("Failed to unpack"), err), - InstallerUpdate::UnpackingFinished => return ProgressUpdateResult::Finished - } - - ProgressUpdateResult::Updated - } -} - -unsafe impl Send for ProgressBar {} -unsafe impl Sync for ProgressBar {} diff --git a/src/ui/components/voiceover_row.rs b/src/ui/components/voiceover_row.rs deleted file mode 100644 index e9517d1..0000000 --- a/src/ui/components/voiceover_row.rs +++ /dev/null @@ -1,52 +0,0 @@ -use gtk::prelude::*; -use adw::prelude::*; - -use std::path::PathBuf; - -use anime_game_core::genshin::voice_data::package::VoicePackage; - -#[derive(Debug, Clone)] -pub struct VoiceoverRow { - pub package: VoicePackage, - - pub row: adw::ActionRow, - pub button: gtk::Button -} - -impl VoiceoverRow { - pub fn new(package: VoicePackage) -> Self { - let row = adw::ActionRow::new(); - let button = gtk::Button::new(); - - row.set_title(package.locale().to_name()); - - button.set_icon_name("document-save-symbolic"); - button.set_valign(gtk::Align::Center); - button.add_css_class("flat"); - - row.add_suffix(&button); - - Self { - package, - row, - button - } - } - - pub fn update_state>(&self, game_path: T) { - if self.is_downloaded(game_path) { - self.button.set_icon_name("user-trash-symbolic"); - } - - else { - self.button.set_icon_name("document-save-symbolic"); - } - } - - pub fn is_downloaded>(&self, game_path: T) -> bool { - self.package.is_installed_in(game_path) - } -} - -unsafe impl Send for VoiceoverRow {} -unsafe impl Sync for VoiceoverRow {} diff --git a/src/ui/components/wine_group.rs b/src/ui/components/wine_group.rs deleted file mode 100644 index 00c8017..0000000 --- a/src/ui/components/wine_group.rs +++ /dev/null @@ -1,47 +0,0 @@ -use adw::prelude::*; - -use std::path::PathBuf; - -use crate::lib::wine::Group; -use super::wine_row::WineRow; - -#[derive(Debug, Clone)] -pub struct WineGroup { - pub group: Group, - pub version_components: Vec, - - pub expander_row: adw::ExpanderRow -} - -impl WineGroup { - pub fn new(group: Group) -> Self { - let expander_row = adw::ExpanderRow::new(); - - expander_row.set_title(&group.title); - expander_row.set_subtitle(group.subtitle.as_ref().unwrap_or(&String::new())); - - let mut version_components = Vec::new(); - - for version in &group.versions { - let component = WineRow::new(version.clone()); - - expander_row.add_row(&component.row); - - version_components.push(component); - } - - Self { - group, - version_components, - expander_row - } - } - - pub fn update_states>(&self, runners_folder: T) { - let runners_folder = runners_folder.into(); - - for component in &self.version_components { - component.update_state(&runners_folder); - } - } -} diff --git a/src/ui/components/wine_row.rs b/src/ui/components/wine_row.rs deleted file mode 100644 index 77aa79a..0000000 --- a/src/ui/components/wine_row.rs +++ /dev/null @@ -1,77 +0,0 @@ -use gtk::prelude::*; -use adw::prelude::*; - -use std::path::PathBuf; - -use crate::lib::wine::Version; -use crate::ui::traits::download_component::*; - -#[derive(Debug, Clone)] -pub struct WineRow { - pub version: Version, - - pub row: adw::ActionRow, - pub button: gtk::Button, - pub progress_bar: gtk::ProgressBar -} - -impl WineRow { - pub fn new(version: Version) -> Self { - let row = adw::ActionRow::new(); - let button = gtk::Button::new(); - - row.set_title(&version.title); - row.set_visible(version.recommended); - - button.set_icon_name("document-save-symbolic"); - button.set_valign(gtk::Align::Center); - button.add_css_class("flat"); - - row.add_suffix(&button); - - let progress_bar = gtk::ProgressBar::new(); - - progress_bar.set_text(Some("Downloading: 0%")); - progress_bar.set_show_text(true); - - progress_bar.set_width_request(200); - progress_bar.set_valign(gtk::Align::Center); - progress_bar.set_visible(false); - - row.add_suffix(&progress_bar); - - Self { - version, - row, - button, - progress_bar - } - } - - pub fn update_state>(&self, runners_folder: T) { - if self.is_downloaded(runners_folder) { - self.button.set_icon_name("user-trash-symbolic"); - } - - else { - self.button.set_icon_name("document-save-symbolic"); - } - } -} - -impl DownloadComponent for WineRow { - fn get_component_path>(&self, installation_path: T) -> PathBuf { - installation_path.into().join(&self.version.name) - } - - fn get_downloading_widgets(&self) -> (gtk::ProgressBar, gtk::Button) { - (self.progress_bar.clone(), self.button.clone()) - } - - fn get_download_uri(&self) -> String { - self.version.uri.clone() - } -} - -unsafe impl Send for WineRow {} -unsafe impl Sync for WineRow {} diff --git a/src/ui/first_run/default_paths.rs b/src/ui/first_run/default_paths.rs deleted file mode 100644 index a373cd6..0000000 --- a/src/ui/first_run/default_paths.rs +++ /dev/null @@ -1,121 +0,0 @@ -use adw::prelude::*; - -use gtk::glib; -use gtk::glib::clone; - -use std::path::PathBuf; - -use wait_not_await::Await; - -use crate::lib::config; -use crate::ui::*; - -pub fn choose_dir(current_folder: String) -> Await> { - let dialogue = rfd::FileDialog::new() - .set_directory(current_folder); - - let (sender, receiver) = std::sync::mpsc::channel(); - - std::thread::spawn(move || { - sender.send(dialogue.pick_folder()).unwrap(); - }); - - Await::new(move || { - match receiver.recv() { - Ok(Some(path)) => Some(path.to_string_lossy().to_string()), - Ok(None) => None, - Err(_) => None - } - }) -} - -#[derive(Clone)] -pub struct Page { - pub window: gtk::Window, - pub page: gtk::Box, - - pub runners_folder: adw::ActionRow, - pub dxvk_folder: adw::ActionRow, - pub prefix_folder: adw::ActionRow, - pub game_folder: adw::ActionRow, - pub patch_folder: adw::ActionRow, - pub temp_folder: adw::ActionRow, - - pub continue_button: gtk::Button, - pub exit_button: gtk::Button -} - -impl Page { - pub fn new(window: gtk::Window) -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/first_run/default_paths.ui"); - - let result = Self { - window, - page: get_object(&builder, "page")?, - - runners_folder: get_object(&builder, "runners_folder")?, - dxvk_folder: get_object(&builder, "dxvk_folder")?, - prefix_folder: get_object(&builder, "prefix_folder")?, - game_folder: get_object(&builder, "game_folder")?, - patch_folder: get_object(&builder, "patch_folder")?, - temp_folder: get_object(&builder, "temp_folder")?, - - continue_button: get_object(&builder, "continue_button")?, - exit_button: get_object(&builder, "exit_button")? - }; - - let config = config::get()?; - - // Add paths to subtitles - result.runners_folder.set_subtitle(config.game.wine.builds.to_str().unwrap()); - result.dxvk_folder.set_subtitle(config.game.dxvk.builds.to_str().unwrap()); - result.prefix_folder.set_subtitle(config.game.wine.prefix.to_str().unwrap()); - result.game_folder.set_subtitle(config.game.path.to_str().unwrap()); - result.patch_folder.set_subtitle(config.patch.path.to_str().unwrap()); - result.temp_folder.set_subtitle(&match config.launcher.temp { - Some(temp) => temp.to_string_lossy().to_string(), - None => String::from("/tmp") - }); - - // Connect path selection events - result.connect_activated(&result.runners_folder); - result.connect_activated(&result.dxvk_folder); - result.connect_activated(&result.prefix_folder); - result.connect_activated(&result.game_folder); - result.connect_activated(&result.patch_folder); - result.connect_activated(&result.temp_folder); - - Ok(result) - } - - fn connect_activated(&self, row: &adw::ActionRow) { - row.connect_activated(clone!(@strong self.window as window => move |row| { - let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - - choose_dir(row.subtitle().unwrap().to_string()).then(move |path| { - if let Some(path) = path { - sender.send(path.clone()).unwrap(); - } - }); - - let row = row.clone(); - - receiver.attach(None, move |path| { - row.set_subtitle(&path); - - glib::Continue(false) - }); - })); - } - - pub fn update_config(&self, mut config: config::Config) -> config::Config { - config.game.wine.builds = PathBuf::from(self.runners_folder.subtitle().unwrap().to_string()); - config.game.dxvk.builds = PathBuf::from(self.dxvk_folder.subtitle().unwrap().to_string()); - config.game.wine.prefix = PathBuf::from(self.prefix_folder.subtitle().unwrap().to_string()); - config.game.path = PathBuf::from(self.game_folder.subtitle().unwrap().to_string()); - config.patch.path = PathBuf::from(self.patch_folder.subtitle().unwrap().to_string()); - config.launcher.temp = Some(PathBuf::from(self.temp_folder.subtitle().unwrap().to_string())); - - config - } -} diff --git a/src/ui/first_run/dependencies.rs b/src/ui/first_run/dependencies.rs deleted file mode 100644 index 63c098b..0000000 --- a/src/ui/first_run/dependencies.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::process::{Command, Stdio}; - -use crate::ui::*; - -#[derive(Clone)] -pub struct Page { - pub page: gtk::Box, - - pub pkg_pacman: gtk::Box, - pub pkg_apt: gtk::Box, - pub pkg_dnf: gtk::Box, - - pub check_button: gtk::Button, - pub exit_button: gtk::Button -} - -impl Page { - pub fn new() -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/first_run/dependencies.ui"); - - let result = Self { - page: get_object(&builder, "page")?, - - pkg_pacman: get_object(&builder, "pkg_pacman")?, - pkg_apt: get_object(&builder, "pkg_apt")?, - pkg_dnf: get_object(&builder, "pkg_dnf")?, - - check_button: get_object(&builder, "check_button")?, - exit_button: get_object(&builder, "exit_button")? - }; - - // Decide which packaging format used in system - match Command::new("pacman").stdout(Stdio::null()).spawn() { - Ok(_) => result.pkg_pacman.show(), - - Err(_) => match Command::new("apt").stdout(Stdio::null()).spawn() { - Ok(_) => result.pkg_apt.show(), - - Err(_) => match Command::new("dnf").stdout(Stdio::null()).spawn() { - Ok(_) => result.pkg_dnf.show(), - - Err(_) => { - result.pkg_pacman.show(); - result.pkg_apt.show(); - result.pkg_dnf.show(); - } - } - } - } - - Ok(result) - } -} diff --git a/src/ui/first_run/download_components.rs b/src/ui/first_run/download_components.rs deleted file mode 100644 index bc9ec90..0000000 --- a/src/ui/first_run/download_components.rs +++ /dev/null @@ -1,110 +0,0 @@ -use adw::prelude::*; - -use crate::lib::wine::{Version as WineVersion, List as WineList}; -use crate::lib::dxvk::{Version as DxvkVersion, List as DxvkList}; - -use crate::ui::*; -use crate::ui::components::progress_bar::ProgressBar; - -#[derive(Clone)] -pub struct Page { - pub page: gtk::Box, - - pub wine_version: adw::ComboRow, - pub dxvk_version: adw::ComboRow, - - pub download_button: gtk::Button, - pub exit_button: gtk::Button, - - pub progress_bar: ProgressBar, - - pub wine_versions: Vec, - pub dxvk_versions: Vec, - - system_wine_available: bool -} - -impl Page { - pub fn new() -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/first_run/download_components.ui"); - - let mut result = Self { - page: get_object(&builder, "page")?, - - wine_version: get_object(&builder, "wine_version")?, - dxvk_version: get_object(&builder, "dxvk_version")?, - - download_button: get_object(&builder, "download_button")?, - exit_button: get_object(&builder, "exit_button")?, - - progress_bar: ProgressBar::new( - get_object(&builder, "progress_bar")?, - get_object(&builder, "buttons_group")?, - get_object(&builder, "progress_bar_group")? - ), - - wine_versions: Vec::new(), - dxvk_versions: Vec::new(), - - system_wine_available: crate::lib::is_available("wine64") - }; - - // Add wine versions - let model = gtk::StringList::new(&[]); - - if result.system_wine_available { - model.append("System"); - } - - for version in &WineList::get()[0].versions { - if version.recommended { - model.append(&version.title); - - result.wine_versions.push(version.clone()); - } - } - - result.wine_version.set_model(Some(&model)); - - // We're not recommending user to use system wine - // and suggest to download some wine build better for gaming - if result.system_wine_available { - result.wine_version.set_selected(1); - } - - // Add DXVK versions - let model = gtk::StringList::new(&[]); - - for version in &DxvkList::get()[0].versions { - if version.recommended { - model.append(&version.version); - - result.dxvk_versions.push(version.clone()); - } - } - - result.dxvk_version.set_model(Some(&model)); - - Ok(result) - } - - /// Get selected wine version - /// - /// `None` means `System` - pub fn get_wine_version(&self) -> Option { - if self.system_wine_available { - match self.wine_version.selected() { - 0 => None, - i => Some(self.wine_versions[i as usize - 1].clone()) - } - } - - else { - Some(self.wine_versions[self.wine_version.selected() as usize].clone()) - } - } - - pub fn get_dxvk_version(&self) -> &DxvkVersion { - &self.dxvk_versions[self.dxvk_version.selected() as usize] - } -} diff --git a/src/ui/first_run/finish.rs b/src/ui/first_run/finish.rs deleted file mode 100644 index d60faff..0000000 --- a/src/ui/first_run/finish.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::ui::*; - -#[derive(Clone)] -pub struct Page { - pub page: gtk::Box, - pub restart_button: gtk::Button, - pub exit_button: gtk::Button -} - -impl Page { - pub fn new() -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/first_run/finish.ui"); - - Ok(Self { - page: get_object(&builder, "page")?, - restart_button: get_object(&builder, "restart_button")?, - exit_button: get_object(&builder, "exit_button")? - }) - } -} diff --git a/src/ui/first_run/mod.rs b/src/ui/first_run/mod.rs deleted file mode 100644 index 8b54e06..0000000 --- a/src/ui/first_run/mod.rs +++ /dev/null @@ -1,473 +0,0 @@ -use gtk::prelude::*; - -use gtk::glib; -use gtk::glib::clone; - -use std::rc::Rc; -use std::cell::Cell; -use std::process::Command; - -use anime_game_core::prelude::*; - -use wincompatlib::prelude::*; - -mod welcome; -mod dependencies; -mod tos_warning; -mod default_paths; -mod voice_packages; -mod download_components; -mod finish; - -use crate::ui::*; -use crate::ui::traits::prelude::*; -use crate::ui::components::progress_bar::*; - -use crate::lib; -use crate::lib::config; - -/// This structure is used to describe widgets used in application -/// -/// `AppWidgets::default` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets -/// -/// This function does not implement events -#[derive(Clone)] -pub struct AppWidgets { - pub window: adw::ApplicationWindow, - pub toast_overlay: adw::ToastOverlay, - pub carousel: adw::Carousel, - - pub welcome: welcome::Page, - pub dependencies: dependencies::Page, - pub tos_warning: tos_warning::Page, - pub default_paths: default_paths::Page, - pub voice_packages: voice_packages::Page, - pub download_components: download_components::Page, - pub finish: finish::Page -} - -impl AppWidgets { - pub fn try_get() -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/first_run.ui"); - - let result = Self { - window: get_object(&builder, "window")?, - toast_overlay: get_object(&builder, "toast_overlay")?, - carousel: get_object(&builder, "carousel")?, - - welcome: welcome::Page::new()?, - dependencies: dependencies::Page::new()?, - tos_warning: tos_warning::Page::new()?, - default_paths: default_paths::Page::new(get_object(&builder, "window")?)?, - voice_packages: voice_packages::Page::new()?, - download_components: download_components::Page::new()?, - finish: finish::Page::new()? - }; - - // Add pages to carousel - result.carousel.append(&result.welcome.page); - result.carousel.append(&result.dependencies.page); - result.carousel.append(&result.tos_warning.page); - result.carousel.append(&result.default_paths.page); - result.carousel.append(&result.voice_packages.page); - result.carousel.append(&result.download_components.page); - result.carousel.append(&result.finish.page); - - // Set devel style to ApplicationWindow if it's debug mode - if crate::APP_DEBUG { - result.window.add_css_class("devel"); - } - - Ok(result) - } -} - -/// This enum is used to describe an action inside of this application -/// -/// It may be helpful if you want to add the same event for several widgets, or call an action inside of another action -/// -/// Has to implement glib::Downgrade` trait -#[derive(Debug, glib::Downgrade)] -pub enum Actions { - WelcomeContinue, - WelcomeAdvanced, - DependenciesContinue, - TosWarningContinue, - DefaultPathsContinue, - VoicePackagesContinue, - DownloadComponents, - DownloadComponentsContinue, - Restart, - Exit, - Toast(Rc<(String, String)>) -} - -impl Actions { - pub fn into_fn>(&self, app: &App) -> Box { - Box::new(clone!(@weak self as action, @strong app => move |_| { - app.update(action).unwrap(); - })) - } -} - -/// The main application structure -/// -/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets -/// -/// `Rc>` means this: -/// - `Rc` addeds ability to reference the same value from various clones of the structure. -/// This will guarantee us that inner `Cell` is the same for all the `App::clone()` values -/// - `Cell` addeds inner mutability to its value, so we can mutate it even without mutable reference. -/// -/// So we have a shared reference to some value that can be changed without mutable reference. -/// That's what we need and what we use in `App::update` method -#[derive(Clone)] -pub struct App { - widgets: AppWidgets, - actions: Rc>>>, - advanced: Rc> -} - -impl App { - /// Create new application - pub fn new(app: >k::Application) -> anyhow::Result { - // Get default widgets from ui file and add events to them - let result = Self { - widgets: AppWidgets::try_get()?, - actions: Default::default(), - advanced: Default::default() - }.init_events().init_actions(); - - // Bind app to the window - result.widgets.window.set_application(Some(app)); - - Ok(result) - } - - /// Add default events and values to the widgets - fn init_events(self) -> Self { - self.widgets.welcome.continue_button.connect_clicked(Actions::WelcomeContinue.into_fn(&self)); - self.widgets.tos_warning.continue_button.connect_clicked(Actions::TosWarningContinue.into_fn(&self)); - self.widgets.default_paths.continue_button.connect_clicked(Actions::DefaultPathsContinue.into_fn(&self)); - self.widgets.dependencies.check_button.connect_clicked(Actions::DependenciesContinue.into_fn(&self)); - self.widgets.voice_packages.continue_button.connect_clicked(Actions::VoicePackagesContinue.into_fn(&self)); - - self.widgets.welcome.advanced_button.connect_clicked(Actions::WelcomeAdvanced.into_fn(&self)); - self.widgets.download_components.download_button.connect_clicked(Actions::DownloadComponents.into_fn(&self)); - - self.widgets.dependencies.exit_button.connect_clicked(Actions::Exit.into_fn(&self)); - self.widgets.tos_warning.exit_button.connect_clicked(Actions::Exit.into_fn(&self)); - self.widgets.default_paths.exit_button.connect_clicked(Actions::Exit.into_fn(&self)); - self.widgets.voice_packages.exit_button.connect_clicked(Actions::Exit.into_fn(&self)); - self.widgets.download_components.exit_button.connect_clicked(Actions::Exit.into_fn(&self)); - self.widgets.finish.exit_button.connect_clicked(Actions::Exit.into_fn(&self)); - - self.widgets.finish.restart_button.connect_clicked(Actions::Restart.into_fn(&self)); - - self - } - - /// Add actions processors - /// - /// Changes will happen in the main thread so you can call `update` method from separate thread - pub fn init_actions(self) -> Self { - let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - - let this = self.clone(); - - receiver.attach(None, move |action| { - // Some debug output - println!("[update] action: {:?}", &action); - - match action { - Actions::WelcomeContinue => { - this.widgets.carousel.scroll_to({ - if lib::is_available("git") && lib::is_available("xdelta3") { - &this.widgets.tos_warning.page - } else { - &this.widgets.dependencies.page - } - }, true); - } - - Actions::WelcomeAdvanced => { - this.advanced.set(true); - - this.update(Actions::WelcomeContinue).unwrap(); - } - - Actions::DependenciesContinue => { - let mut installed = true; - - for package in ["git", "xdelta3"] { - if !lib::is_available(package) { - installed = false; - - this.toast(format!("Package {package} is not installed"), ""); - - break; - } - } - - if installed { - this.widgets.carousel.scroll_to(&this.widgets.tos_warning.page, true); - } - } - - Actions::TosWarningContinue => { - this.widgets.carousel.scroll_to({ - if this.advanced.get() { - &this.widgets.default_paths.page - } else { - &this.widgets.voice_packages.page - } - }, true); - } - - Actions::DefaultPathsContinue => { - config::update_raw(this.widgets.default_paths.update_config(config::get().unwrap())).unwrap(); - - this.widgets.carousel.scroll_to(&this.widgets.voice_packages.page, true); - } - - Actions::VoicePackagesContinue => { - config::update_raw(this.widgets.voice_packages.update_config(config::get().unwrap())).unwrap(); - - this.widgets.carousel.scroll_to(&this.widgets.download_components.page, true); - } - - Actions::DownloadComponents => { - this.widgets.download_components.wine_version.set_sensitive(false); - this.widgets.download_components.dxvk_version.set_sensitive(false); - - this.widgets.download_components.progress_bar.show(); - - let (sender_wine, receiver_wine) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - let (sender_dxvk, receiver_dxvk) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - - let progress_bar = this.widgets.download_components.progress_bar.clone(); - - let wine_version = this.widgets.download_components.get_wine_version(); - let dxvk_version = this.widgets.download_components.get_dxvk_version().clone(); - - // Prepare wine downloader - if let Some(wine_version) = &wine_version { - let wine_version_copy = wine_version.clone(); - let this_copy = this.clone(); - - std::thread::spawn(move || { - let config = config::get().unwrap(); - - match Installer::new(&wine_version_copy.uri) { - Ok(mut installer) => { - if let Some(temp_folder) = config.launcher.temp { - installer.temp_folder = temp_folder; - } - - installer.downloader - .set_downloading_speed(config.launcher.speed_limit) - .expect("Failed to set downloading speed limit"); - - // Download wine - #[allow(unused_must_use)] - installer.install(&config.game.wine.builds, move |state| { - sender_wine.send(state); - }); - }, - Err(err) => { - this_copy.update(Actions::Toast(Rc::new(( - String::from("Failed to init wine downloader"), err.to_string() - )))).unwrap(); - } - } - }); - } - - else { - sender_wine.send(InstallerUpdate::UnpackingFinished).unwrap(); - } - - // Display wine downloading progress - let progress_bar_copy = progress_bar.clone(); - let dxvk_version_copy = dxvk_version.clone(); - - let this_copy = this.clone(); - - receiver_wine.attach(None, move |state| { - match progress_bar_copy.update_from_state(state) { - ProgressUpdateResult::Updated => (), - - ProgressUpdateResult::Error(msg, err) => { - this_copy.toast(msg, err); - }, - - ProgressUpdateResult::Finished => { - let mut config = config::get().unwrap(); - - // Update wine config - if let Some(wine_version) = &wine_version { - config.game.wine.selected = Some(wine_version.name.clone()); - - config::update_raw(config.clone()).unwrap(); - } - - // Create wine prefix - let this = this_copy.clone(); - let dxvk_version = dxvk_version_copy.clone(); - let sender_dxvk = sender_dxvk.clone(); - - std::thread::spawn(move || { - let wine = config.try_get_wine_executable() - .expect("None of wine builds are available"); - - let wine = Wine::from_binary(wine) - .with_loader(WineLoader::Current) - .with_arch(WineArch::Win64); - - match wine.update_prefix(&config.game.wine.prefix) { - Ok(output) => { - println!("Wine prefix created:\n{}", String::from_utf8_lossy(&output.stdout)); - - // Prepare DXVK downloader - match Installer::new(&dxvk_version.uri) { - Ok(mut installer) => { - if let Some(temp_folder) = config.launcher.temp { - installer.temp_folder = temp_folder; - } - - installer.downloader - .set_downloading_speed(config.launcher.speed_limit) - .expect("Failed to set downloading speed limit"); - - // Download DXVK - #[allow(unused_must_use)] - installer.install(&config.game.dxvk.builds, move |state| { - sender_dxvk.send(state); - }); - }, - Err(err) => { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to init DXVK downloader"), err.to_string() - )))).unwrap(); - } - } - }, - Err(err) => { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to create wine prefix"), err.to_string() - )))).unwrap(); - } - } - }); - - return glib::Continue(false); - } - } - - glib::Continue(true) - }); - - // Display DXVK downloading progress - let this = this.clone(); - - receiver_dxvk.attach(None, move |state| { - match progress_bar.update_from_state(state) { - ProgressUpdateResult::Updated => (), - - ProgressUpdateResult::Error(msg, err) => { - this.toast(msg, err); - }, - - ProgressUpdateResult::Finished => { - let config = config::get().unwrap(); - - // Apply DXVK - let this = this.clone(); - let dxvk_version = dxvk_version.clone(); - - std::thread::spawn(move || { - match dxvk_version.apply(&config.game.dxvk.builds, &config.game.wine.prefix) { - Ok(output) => { - println!("Applied DXVK:\n\n{}", String::from_utf8_lossy(&output.stdout)); - - // Remove .first-run file - let launcher_dir = crate::lib::consts::launcher_dir().unwrap(); - - std::fs::remove_file(launcher_dir.join(".first-run")).unwrap(); - - // Show next page - this.update(Actions::DownloadComponentsContinue).unwrap(); - }, - Err(err) => { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to apply DXVK"), err.to_string() - )))).unwrap(); - } - } - }); - - return glib::Continue(false); - } - } - - glib::Continue(true) - }); - } - - Actions::DownloadComponentsContinue => { - this.widgets.carousel.scroll_to(&this.widgets.finish.page, true); - } - - Actions::Restart => { - Command::new(std::env::current_exe().unwrap()).spawn().unwrap(); - - this.widgets.window.close(); - } - - Actions::Exit => { - this.widgets.window.close(); - } - - Actions::Toast(toast) => { - let (msg, err) = (toast.0.clone(), toast.1.clone()); - - this.toast(msg, err); - } - } - - glib::Continue(true) - }); - - self.actions.set(Some(sender)); - - self - } - - /// Update widgets state by calling some action - pub fn update(&self, action: Actions) -> Result<(), std::sync::mpsc::SendError> { - let actions = self.actions.take(); - - let result = match &actions { - Some(sender) => Ok(sender.send(action)?), - None => Ok(()) - }; - - self.actions.set(actions); - - result - } - - /// Show application window - pub fn show(&self) { - self.widgets.window.show(); - } -} - -impl Toast for App { - fn get_toast_widgets(&self) -> (adw::ApplicationWindow, adw::ToastOverlay) { - (self.widgets.window.clone(), self.widgets.toast_overlay.clone()) - } -} - -unsafe impl Send for App {} -unsafe impl Sync for App {} diff --git a/src/ui/first_run/tos_warning.rs b/src/ui/first_run/tos_warning.rs deleted file mode 100644 index c947723..0000000 --- a/src/ui/first_run/tos_warning.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::ui::*; - -#[derive(Clone)] -pub struct Page { - pub page: gtk::Box, - pub continue_button: gtk::Button, - pub exit_button: gtk::Button -} - -impl Page { - pub fn new() -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/first_run/tos_warning.ui"); - - Ok(Self { - page: get_object(&builder, "page")?, - continue_button: get_object(&builder, "continue_button")?, - exit_button: get_object(&builder, "exit_button")? - }) - } -} diff --git a/src/ui/first_run/voice_packages.rs b/src/ui/first_run/voice_packages.rs deleted file mode 100644 index f6e3aad..0000000 --- a/src/ui/first_run/voice_packages.rs +++ /dev/null @@ -1,80 +0,0 @@ -use gtk::prelude::*; -use adw::prelude::*; - -use anime_game_core::genshin::voice_data::prelude::*; - -use crate::lib::config; -use crate::ui::*; - -#[derive(Clone)] -pub struct Page { - pub page: gtk::Box, - pub voice_packages_group: adw::PreferencesGroup, - - pub continue_button: gtk::Button, - pub exit_button: gtk::Button, - - pub voice_packages: Vec<(VoiceLocale, adw::ActionRow, gtk::Switch)> -} - -impl Page { - pub fn new() -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/first_run/voice_packages.ui"); - - let mut result = Self { - page: get_object(&builder, "page")?, - voice_packages_group: get_object(&builder, "voice_packages_group")?, - - continue_button: get_object(&builder, "continue_button")?, - exit_button: get_object(&builder, "exit_button")?, - - voice_packages: Vec::new() - }; - - let mut packages = Vec::new(); - - for package in VoicePackage::list_latest().expect("Failed to list voice packages") { - let row = adw::ActionRow::new(); - let switch = gtk::Switch::new(); - - row.set_title(package.locale().to_name()); - switch.set_valign(gtk::Align::Center); - - row.add_suffix(&switch); - - result.voice_packages_group.add(&row); - - packages.push((package.locale(), row, switch)); - } - - if let Ok(config) = config::get() { - for voice in config.game.voices { - if let Some(voice) = VoiceLocale::from_str(voice) { - for (locale, _, switcher) in &packages { - if voice == *locale { - switcher.set_state(true); - } - } - } - } - } - - result.voice_packages = packages; - - Ok(result) - } - - pub fn update_config(&self, mut config: config::Config) -> config::Config { - let mut voices = Vec::new(); - - for (locale, _, switcher) in &self.voice_packages { - if switcher.state() { - voices.push(locale.to_code().to_string()); - } - } - - config.game.voices = voices; - - config - } -} diff --git a/src/ui/first_run/welcome.rs b/src/ui/first_run/welcome.rs deleted file mode 100644 index bef4102..0000000 --- a/src/ui/first_run/welcome.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::ui::*; - -#[derive(Clone)] -pub struct Page { - pub page: gtk::Box, - pub continue_button: gtk::Button, - pub advanced_button: gtk::Button -} - -impl Page { - pub fn new() -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/first_run/welcome.ui"); - - Ok(Self { - page: get_object(&builder, "page")?, - continue_button: get_object(&builder, "continue_button")?, - advanced_button: get_object(&builder, "advanced_button")? - }) - } -} diff --git a/src/ui/main.rs b/src/ui/main.rs index 7b32ffc..5f94f5e 100644 --- a/src/ui/main.rs +++ b/src/ui/main.rs @@ -1,1064 +1,123 @@ +use relm4::prelude::*; + use gtk::prelude::*; +use adw::prelude::*; -use gtk::glib; -use gtk::glib::clone; +use crate::i18n::tr; -use std::rc::Rc; -use std::cell::Cell; -use std::io::Error; -use std::process::{Command, Stdio}; -use std::path::Path; +use super::preferences::main::App as PreferencesApp; -use wait_not_await::Await; +/// Sets to `true` when the `App` component is ready (fully initialized) +pub static mut READY: bool = false; -use anime_game_core::prelude::*; -use anime_game_core::genshin::prelude::*; - -use wincompatlib::prelude::*; - -use crate::ui::*; - -use super::preferences::PreferencesStack; -use super::traits::toast::Toast; -use super::components::progress_bar::*; - -use crate::lib::consts; -use crate::lib::config; -use crate::lib::game; -use crate::lib::launcher::states::LauncherState; -use crate::lib::wine::{ - Version as WineVersion, - List as WineList -}; -use crate::lib::prettify_bytes::prettify_bytes; - -/// This structure is used to describe widgets used in application -/// -/// `AppWidgets::try_get` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets -/// -/// This function does not implement events -#[derive(Clone, glib::Downgrade)] -pub struct AppWidgets { - pub window: adw::ApplicationWindow, - pub toast_overlay: adw::ToastOverlay, - - pub menu: gtk::MenuButton, - pub about: adw::AboutWindow, - - pub leaflet: adw::Leaflet, - pub status_page: adw::StatusPage, - pub launcher_content: adw::PreferencesPage, - - pub icon: gtk::Image, - pub predownload_game: gtk::Button, - pub launch_game: gtk::Button, - pub open_preferences: gtk::Button, - - pub progress_bar: ProgressBar, - - pub preferences_stack: PreferencesStack +// TODO: get rid of using this function in all the components' events +// e.g. by converting preferences pages into Relm4 Components +pub fn is_ready() -> bool { + unsafe { READY } } -impl AppWidgets { - pub fn try_get() -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/main.ui"); - - let window = get_object::(&builder, "window")?; - let toast_overlay = get_object::(&builder, "toast_overlay")?; - - let result = Self { - window: window.clone(), - toast_overlay, - - menu: get_object(&builder, "menu")?, - about: get_object(&builder, "about")?, - - leaflet: get_object(&builder, "leaflet")?, - status_page: get_object(&builder, "status_page")?, - launcher_content: get_object(&builder, "launcher_content")?, - - icon: get_object(&builder, "icon")?, - predownload_game: get_object(&builder, "predownload_game")?, - launch_game: get_object(&builder, "launch_game")?, - open_preferences: get_object(&builder, "open_preferences")?, - - progress_bar: ProgressBar::new( - get_object(&builder, "progress_bar")?, - get_object(&builder, "launch_game_group")?, - get_object(&builder, "progress_bar_group")? - ), - - preferences_stack: PreferencesStack::new(&window)? - }; - - // Set devel style to ApplicationWindow if it's debug mode - if crate::APP_DEBUG { - result.window.add_css_class("devel"); - } - - // Load icon from "icon" file if it exists - if std::path::Path::new("icon").exists() { - result.icon.set_from_file(Some("icon")); - } - - // Set default About Dialog values - if crate::APP_DEBUG { - result.about.set_version(&format!("{}-dev", crate::APP_VERSION)); - } - - else { - result.about.set_version(crate::APP_VERSION); - } - - result.about.set_license_type(gtk::License::Gpl30); - - result.about.set_developers(&[ - "Nikita Podvirnyy https://github.com/krypt0nn" - ]); - - result.about.add_credit_section(Some("Logo"), &[ - "@nightany https://pinterest.com/pin/356206651788051017" - ]); - - result.about.add_credit_section(Some("An Anime Team"), &[ - "@Marie https://github.com/Mar0xy", - "@lane https://github.com/laurinneff" - ]); - - let curl_info = anime_game_core::curl_sys::Version::get(); - - #[allow(clippy::or_fun_call)] - result.about.set_debug_info(&[ - format!("Anime Game core library version: {}", anime_game_core::VERSION), - format!("Curl version: {}", curl_info.version()), - format!("SSL version: {}", curl_info.ssl_version().unwrap_or("?")), - String::new(), - format!("GTK version: {}.{}.{}", gtk::major_version(), gtk::minor_version(), gtk::micro_version()), - format!("Libadwaita version: {}.{}.{}", adw::major_version(), adw::minor_version(), adw::micro_version()), - format!("Pango version: {}", gtk::pango::version_string()), - format!("Cairo version: {}", gtk::cairo::version_string()), - ].join("\n")); - - // Add preferences page to the leaflet - result.leaflet.append(&result.preferences_stack.preferences).set_name(Some("preferences_page")); - - Ok(result) - } -} - -/// This enum is used to describe an action inside of this application -/// -/// It may be helpful if you want to add the same event for several widgets, or call an action inside of another action -#[derive(Debug, Clone, glib::Downgrade)] -pub enum Actions { - OpenPreferencesPage, - PreferencesGoBack, - PerformButtonEvent, - PredownloadUpdate, - RepairGame, - ShowProgressBar, - UpdateProgress { fraction: Rc, title: Rc }, - HideProgressBar, - Toast(Rc<(String, String)>) -} - -impl Actions { - #[allow(clippy::expect_fun_call, clippy::wrong_self_convention)] - pub fn into_fn>(&self, app: &App) -> Box { - Box::new(clone!(@strong self as action, @weak app => move |_| { - app.update(action.clone()).expect(&format!("Failed to execute action {:?}", &action)); - })) - } -} - -/// This enum is used to store some of this application data -/// -/// In this example we store a counter here to know what should we increment or decrement -/// -/// This must implement `Default` trait -#[derive(Debug, Default)] -pub struct Values { - state: LauncherState -} - -/// The main application structure -/// -/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets -/// -/// `Rc>` means this: -/// - `Rc` addeds ability to reference the same value from various clones of the structure. -/// This will guarantee us that inner `Cell` is the same for all the `App::clone()` values -/// - `Cell` addeds inner mutability to its value, so we can mutate it even without mutable reference. -/// -/// So we have a shared reference to some value that can be changed without mutable reference. -/// That's what we need and what we use in `App::update` method -#[derive(Clone, glib::Downgrade)] pub struct App { - widgets: AppWidgets, - values: Rc>, - actions: Rc>>> + preferences_window: Controller } -impl App { - /// Create new application - pub fn new(app: >k::Application) -> anyhow::Result { - let mut result = Self { - widgets: AppWidgets::try_get()?, - values: Default::default(), - actions: Default::default() - }.init_events().init_actions(); +#[derive(Debug)] +pub enum AppMsg { + OpenPreferences, + ClosePreferences +} - // Set app reference - result.widgets.preferences_stack.set_app(result.clone()); +#[relm4::component(pub)] +impl SimpleComponent for App { + type Init = (); + type Input = AppMsg; + type Output = (); - // Bind app to the window - result.widgets.window.set_application(Some(app)); + view! { + main_window = adw::Window { + set_title: Some("An Anime Game Launcher"), + set_default_size: (900, 600), - Ok(result) - } + gtk::Box { + set_orientation: gtk::Orientation::Vertical, - /// Add default events and values to the widgets - fn init_events(self) -> Self { - // Add menu actions - add_action(&self.widgets.menu, "open-launcher-folder", clone!(@weak self as this => move || { - if let Some(launcher_dir) = consts::launcher_dir() { - if let Err(err) = Command::new("xdg-open").arg(launcher_dir).spawn() { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to open launcher folder"), err.to_string() - )))).unwrap(); - } - } - })); + adw::HeaderBar {}, - add_action(&self.widgets.menu, "open-game-folder", clone!(@weak self as this => move || { - if let Ok(config) = config::get() { - if let Err(err) = Command::new("xdg-open").arg(config.game.path).spawn() { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to open game folder"), err.to_string() - )))).unwrap(); - } - } - })); + adw::PreferencesPage { + add = &adw::PreferencesGroup { + gtk::Image { + set_resource: Some("/org/app/images/icon.png"), + set_vexpand: true, + set_margin_top: 48 + }, - add_action(&self.widgets.menu, "open-config-file", clone!(@weak self as this => move || { - if let Some(config_file) = consts::config_file() { - if let Err(err) = Command::new("xdg-open").arg(config_file).spawn() { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to open config file"), err.to_string() - )))).unwrap(); - } - } - })); - - // Other actions - - add_action(&self.widgets.menu, "show-about-dialog", clone!(@strong self.widgets.about as about => move || { - about.show(); - })); - - // Open preferences page - self.widgets.open_preferences.connect_clicked(Actions::OpenPreferencesPage.into_fn(&self)); - - // Go back button for preferences page - self.widgets.preferences_stack.preferences_go_back.connect_clicked(Actions::PreferencesGoBack.into_fn(&self)); - - // Predownload update - self.widgets.predownload_game.connect_clicked(Actions::PredownloadUpdate.into_fn(&self)); - - // Launch game - self.widgets.launch_game.connect_clicked(Actions::PerformButtonEvent.into_fn(&self)); - - self - } - - /// Add actions processors - /// - /// Changes will happen in the main thread so you can call `update` method from separate thread - pub fn init_actions(self) -> Self { - let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - - // I prefer to avoid using clone! here because it breaks my code autocompletion - let this = self.clone(); - - receiver.attach(None, move |action| { - // Some debug output - match &action { - Actions::UpdateProgress { .. } => (), - action => println!("[main] [update] action: {:?}", action) - } - - match action { - Actions::OpenPreferencesPage => { - this.widgets.leaflet.set_visible_child_name("preferences_page"); - - let this = this.clone(); - - std::thread::spawn(move || { - if let Err(err) = this.widgets.preferences_stack.update() { - glib::MainContext::default().invoke(move || { - this.update(Actions::PreferencesGoBack).unwrap(); - - this.toast("Failed to update preferences", err); - }); + gtk::Label { + set_label: "An Anime Game Launcher", + set_margin_top: 32, + add_css_class: "title-1" } - }); - } + }, - Actions::PreferencesGoBack => { - this.widgets.leaflet.navigate(adw::NavigationDirection::Back); + add = &adw::PreferencesGroup { + set_valign: gtk::Align::Center, + set_vexpand: true, - config::flush().expect("Failed to save config file"); - } + gtk::Box { + set_halign: gtk::Align::Center, + set_margin_top: 64, + set_spacing: 8, - Actions::PerformButtonEvent => { - let values = this.values.take(); - let state = values.state.clone(); + gtk::Button { + set_label: &tr("launch"), + set_hexpand: false, + set_width_request: 200, + add_css_class: "suggested-action", - this.values.set(values); - - match config::get() { - Ok(mut config) => { - match state { - LauncherState::PatchAvailable(Patch::NotAvailable) | - LauncherState::PredownloadAvailable { .. } | - LauncherState::Launch => { - let this = this.clone(); - - this.widgets.window.hide(); - - std::thread::spawn(move || { - // Display toast message if the game is failed to run - if let Err(err) = game::run() { - this.widgets.window.show(); - - this.update(Actions::Toast(Rc::new(( - String::from("Failed to run game"), err.to_string() - )))).unwrap(); - } - - else { - std::thread::sleep(std::time::Duration::from_secs(2)); - - loop { - std::thread::sleep(std::time::Duration::from_secs(3)); - - match Command::new("ps").arg("-A").stdout(Stdio::piped()).output() { - Ok(output) => { - let output = String::from_utf8_lossy(&output.stdout); - - if !output.contains("GenshinImpact.e") && !output.contains("unlocker.exe") { - break; - } - }, - Err(_) => break - } - } - - this.widgets.window.show(); - } - }); + connect_clicked => |_| { + anime_launcher_sdk::game::run().expect("Failed to run the game"); } + }, - LauncherState::PatchAvailable(patch) => { - match patch { - Patch::NotAvailable | - Patch::Outdated { .. } | - Patch::Preparation { .. } => unreachable!(), + gtk::Button { + set_icon_name: "emblem-system-symbolic", - Patch::Testing { version, host, .. } | - Patch::Available { version, host, .. } => { - this.widgets.launch_game.set_sensitive(false); - this.widgets.open_preferences.set_sensitive(false); - - let this = this.clone(); - - std::thread::spawn(move || { - let applier = PatchApplier::new(&config.patch.path); - - let mut synced = false; - - match applier.is_sync_with(&host) { - Ok(true) => synced = true, - - Ok(false) => { - match applier.sync(host) { - Ok(true) => synced = true, - - Ok(false) => { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to sync patch folder"), Error::last_os_error().to_string() - )))).unwrap(); - } - - Err(err) => { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to sync patch folder"), err.to_string() - )))).unwrap(); - } - } - } - - Err(err) => this.update(Actions::Toast(Rc::new(( - String::from("Failed to check patch folder state"), err.to_string() - )))).unwrap() - } - - if synced { - match applier.apply(&config.game.path, version, config.patch.root) { - Ok(_) => (), - - Err(err) => { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to patch game"), err.to_string() - )))).unwrap(); - } - } - } - - glib::MainContext::default().invoke(move || { - this.widgets.launch_game.set_sensitive(true); - this.widgets.open_preferences.set_sensitive(true); - - this.update_state(); - }); - }); - } - } - } - - LauncherState::WineNotInstalled => { - match WineList::list_downloaded(&config.game.wine.builds) { - Ok(list) => { - for version in list { - if version.recommended { - config.game.wine.selected = Some(version.name); - - config::update(config.clone()); - - break; - } - } - - if config.game.wine.selected.is_none() { - match WineVersion::latest() { - Ok(wine) => { - match Installer::new(wine.uri) { - Ok(mut installer) => { - if let Some(temp_folder) = config.launcher.temp { - installer.temp_folder = temp_folder; - } - - installer.downloader - .set_downloading_speed(config.launcher.speed_limit) - .expect("Failed to set downloading speed limit"); - - let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - let this = this.clone(); - - this.update(Actions::ShowProgressBar).unwrap(); - - // Download wine version - // We need to update components from the main thread - receiver.attach(None, move |state| { - match this.widgets.progress_bar.update_from_state(state) { - ProgressUpdateResult::Updated => (), - - ProgressUpdateResult::Error(msg, err) => { - this.widgets.progress_bar.hide(); - - this.toast(msg, err); - } - - ProgressUpdateResult::Finished => { - let mut config = config::get().unwrap(); - - config.game.wine.selected = Some(wine.name.clone()); - - config::update(config); - - this.update_state(); - } - } - - glib::Continue(true) - }); - - // Download wine version in separate thread to not to freeze the main one - std::thread::spawn(move || { - installer.install(config.game.wine.builds, move |state| { - sender.send(state).unwrap(); - }); - }); - }, - Err(err) => this.toast("Failed to init wine version installer", err) - } - }, - Err(err) => this.toast("Failed to get latest wine version", err) - } - } - - else { - this.update_state(); - } - }, - Err(err) => this.toast("Failed to list downloaded wine versions", err) - } - } - - LauncherState::PrefixNotExists => { - match config.try_get_wine_executable() { - Some(wine) => { - let this = this.clone(); - - std::thread::spawn(move || { - this.widgets.launch_game.set_sensitive(false); - - let wine = Wine::from_binary(wine) - .with_loader(WineLoader::Current) - .with_arch(WineArch::Win64); - - if let Err(err) = wine.update_prefix(&config.game.wine.prefix) { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to create wine prefix"), err.to_string() - )))).unwrap(); - } - - this.widgets.launch_game.set_sensitive(true); - - this.update_state(); - }); - }, - None => this.toast("Failed to get selected wine version", Error::last_os_error()) - } - } - - LauncherState::VoiceUpdateAvailable(diff) | - LauncherState::VoiceNotInstalled(diff) | - LauncherState::GameUpdateAvailable(diff) | - LauncherState::GameNotInstalled(diff) => { - enum UpdaterState { - State(anime_game_core::installer::installer::Update), - Finished - } - - let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - - let this = this.clone(); - let this_copy = this.clone(); - - this.update(Actions::ShowProgressBar).unwrap(); - - // Download diff - // We need to update components from the main thread - receiver.attach(None, move |state| { - match state { - UpdaterState::State(state) => { - match this.widgets.progress_bar.update_from_state(state) { - ProgressUpdateResult::Updated => (), - - ProgressUpdateResult::Error(msg, err) => { - this.widgets.progress_bar.hide(); - - this.toast(msg, err); - } - - ProgressUpdateResult::Finished => { - this.widgets.progress_bar.update(1.0, Some("Applying patches...")); - } - } - } - - UpdaterState::Finished => { - this.widgets.progress_bar.hide(); - - let this = this.clone(); - - this.update_state().then(move |result| { - if let Ok(state) = result { - match state { - LauncherState::VoiceUpdateAvailable(_) | - LauncherState::VoiceNotInstalled(_) | - LauncherState::GameUpdateAvailable(_) | - LauncherState::GameNotInstalled(_) => { - this.update(Actions::PerformButtonEvent).unwrap(); - }, - _ => () - } - } - }); - - return glib::Continue(false); - } - } - - glib::Continue(true) - }); - - // Download diff in separate thread to not to freeze the main one - std::thread::spawn(move || { - let updater_sender = sender.clone(); - - let result = diff.install_to_by(config.game.path, config.launcher.temp, move |state| { - updater_sender.send(UpdaterState::State(state)).unwrap(); - }); - - if let Err(err) = result { - let err: Error = err.into(); - - this_copy.update(Actions::Toast(Rc::new(( - String::from("Downloading failed"), err.to_string() - )))).unwrap(); - } - - sender.send(UpdaterState::Finished).unwrap(); - }); - }, - - LauncherState::GameOutdated(_) => (), - LauncherState::VoiceOutdated(_) => () + connect_clicked => AppMsg::OpenPreferences } - }, - Err(err) => this.toast("Failed to load config", err) - } - } - - Actions::PredownloadUpdate => { - let values = this.values.take(); - let state = values.state.clone(); - - this.values.set(values); - - match config::get() { - Ok(config) => { - match state { - LauncherState::PredownloadAvailable { game, mut voices } => { - let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - - let mut diffs: Vec = vec![game]; - - diffs.append(&mut voices); - - this.widgets.progress_bar.show(); - - std::thread::spawn(clone!(@strong this => move || { - for mut diff in diffs { - let sender = sender.clone(); - - #[allow(unused_must_use)] - let result = diff.download_in(config.launcher.temp.as_ref().unwrap(), move |curr, total| { - sender.send(InstallerUpdate::DownloadingProgress(curr, total)); - }); - - if let Err(err) = result { - let err: Error = err.into(); - - this.update(Actions::Toast(Rc::new(( - String::from("Downloading failed"), err.to_string() - )))).unwrap(); - - break; - } - } - - // Update button from "predownload available" to "update predownloaded" - this.update_state(); - })); - - receiver.attach(None, clone!(@strong this => move |state| { - this.widgets.progress_bar.update_from_state(state); - - glib::Continue(true) - })); - } - - _ => unreachable!() - } - }, - Err(err) => this.toast("Failed to load config", err) - } - } - - Actions::RepairGame => { - match config::get() { - Ok(config) => { - let this = this.clone(); - - std::thread::spawn(move || { - match repairer::try_get_integrity_files(None) { - Ok(mut files) => { - // Add voiceovers files - let game = Game::new(&config.game.path); - - if let Ok(voiceovers) = game.get_voice_packages() { - for package in voiceovers { - if let Ok(mut voiceover_files) = repairer::try_get_voice_integrity_files(package.locale(), None) { - files.append(&mut voiceover_files); - } - } - } - - this.update(Actions::ShowProgressBar).unwrap(); - - this.update(Actions::UpdateProgress { - fraction: Rc::new(0.0), - title: Rc::new(String::from("Verifying files: 0%")) - }).unwrap(); - - const VERIFIER_THREADS_NUM: u64 = 4; - - let mut total = 0; - - for file in &files { - total += file.size; - } - - let median_size = total / VERIFIER_THREADS_NUM; - let mut i = 0; - - let (sender, receiver) = std::sync::mpsc::channel(); - - for _ in 0..VERIFIER_THREADS_NUM { - let mut thread_files = Vec::new(); - let mut thread_files_size = 0; - - while i < files.len() { - thread_files.push(files[i].clone()); - - thread_files_size += files[i].size; - i += 1; - - if thread_files_size >= median_size { - break; - } - } - - let game_path = config.game.path.clone(); - let thread_sender = sender.clone(); - - std::thread::spawn(move || { - for file in thread_files { - let status = if config.launcher.repairer.fast { - file.fast_verify(&game_path) - } else { - file.verify(&game_path) - }; - - thread_sender.send((file, status)).unwrap(); - } - }); - } - - // We have VERIFIER_THREADS_NUM copies of this sender + the original one - // receiver will return Err when all the senders will be dropped. - // VERIFIER_THREADS_NUM senders will be dropped when threads will finish verifying files - // but this one will live as long as current thread exists so we should drop it manually - drop(sender); - - let mut broken = Vec::new(); - let mut processed = 0; - - while let Ok((file, status)) = receiver.recv() { - processed += file.size; - - if !status { - broken.push(file); - } - - let progress = processed as f64 / total as f64; - - this.update(Actions::UpdateProgress { - fraction: Rc::new(progress), - title: Rc::new(format!("Verifying files: {:.2}%", progress * 100.0)) - }).unwrap(); - } - - if !broken.is_empty() { - this.update(Actions::UpdateProgress { - fraction: Rc::new(0.0), - title: Rc::new(String::from("Repairing files: 0%")) - }).unwrap(); - - println!("Found broken files:"); - - for file in &broken { - println!(" - {:?}", file.path); - } - - let total = broken.len() as f64; - - let is_patch_applied = match Patch::try_fetch(config.patch.servers, consts::PATCH_FETCHING_TIMEOUT) { - Ok(patch) => patch.is_applied(&config.game.path).unwrap_or(true), - Err(_) => true - }; - - println!("Patch status: {}", is_patch_applied); - - fn should_ignore(path: &Path) -> bool { - for part in ["UnityPlayer.dll", "xlua.dll", "crashreport.exe", "upload_crash.exe", "vulkan-1.dll"] { - if path.ends_with(part) { - return true; - } - } - - false - } - - for (i, file) in broken.into_iter().enumerate() { - if !is_patch_applied || !should_ignore(&file.path) { - println!("Repairing: {:?}", &file.path); - - if let Err(err) = file.repair(&config.game.path) { - let err: Error = err.into(); - - this.update(Actions::Toast(Rc::new(( - String::from("Failed to repair game file"), err.to_string() - )))).unwrap(); - } - } - - let progress = i as f64 / total; - - this.update(Actions::UpdateProgress { - fraction: Rc::new(progress), - title: Rc::new(format!("Repairing files: {:.2}%", progress * 100.0)) - }).unwrap(); - } - } - - this.update(Actions::HideProgressBar).unwrap(); - }, - Err(err) => { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to get integrity files"), err.to_string() - )))).unwrap(); - - this.update(Actions::HideProgressBar).unwrap(); - } - } - }); - }, - Err(err) => this.toast("Failed to load config", err) - } - } - - Actions::ShowProgressBar => { - this.widgets.progress_bar.show(); - } - - Actions::UpdateProgress { fraction, title } => { - this.widgets.progress_bar.update(*fraction, Some(title.as_str())); - } - - Actions::HideProgressBar => { - this.widgets.progress_bar.hide(); - } - - Actions::Toast(toast) => { - let (msg, err) = (toast.0.clone(), toast.1.to_string()); - - this.toast(msg, err); - } - } - - glib::Continue(true) - }); - - self.actions.set(Some(sender)); - - self - } - - /// Update widgets state by calling some action - pub fn update(&self, action: Actions) -> Result<(), std::sync::mpsc::SendError> { - let actions = self.actions.take(); - - let result = match &actions { - Some(sender) => Ok(sender.send(action)?), - None => Ok(()) - }; - - self.actions.set(actions); - - result - } - - /// Show application window - pub fn show(&self) { - self.widgets.window.show(); - } - - pub fn set_state(&self, state: LauncherState) { - println!("[main] [set_state] state: {:?}", &state); - - self.widgets.progress_bar.hide(); - - self.widgets.launch_game.set_tooltip_text(None); - self.widgets.launch_game.set_sensitive(true); - - self.widgets.launch_game.add_css_class("suggested-action"); - self.widgets.launch_game.remove_css_class("warning"); - self.widgets.launch_game.remove_css_class("destructive-action"); - - self.widgets.predownload_game.hide(); - - match &state { - LauncherState::Launch => { - self.widgets.launch_game.set_label("Launch"); - } - - LauncherState::PredownloadAvailable { game, voices } => { - self.widgets.launch_game.set_label("Launch"); - - // Calculate size of the update - let size = - game.size().unwrap_or((0, 0)).0 + - voices.iter().fold(0, |acc, voice| acc + voice.size().unwrap_or((0, 0)).0); - - // Update tooltip - self.widgets.predownload_game.set_tooltip_text(Some(&format!("Pre-download {} update ({})", game.latest(), prettify_bytes(size)))); - - // Prepare button's color - self.widgets.predownload_game.remove_css_class("success"); - self.widgets.predownload_game.add_css_class("warning"); - self.widgets.predownload_game.set_sensitive(true); - - if let Ok(config) = config::get() { - if let Some(temp) = config.launcher.temp { - // If all the files were downloaded - let downloaded = - temp.join(game.file_name().unwrap()).exists() && - voices.iter().all(|voice| temp.join(voice.file_name().unwrap()).exists()); - - if downloaded { - self.widgets.predownload_game.remove_css_class("warning"); - self.widgets.predownload_game.add_css_class("success"); - self.widgets.predownload_game.set_sensitive(false); - - self.widgets.predownload_game.set_tooltip_text(Some(&format!("{} update is predownloaded ({})", game.latest(), prettify_bytes(size)))); } } } - - self.widgets.predownload_game.show(); - } - - LauncherState::PatchAvailable(patch) => { - match patch { - Patch::NotAvailable => { - self.widgets.launch_game.set_label("Patch not available"); - - self.widgets.launch_game.set_tooltip_text(Some("Patch servers are unavailable and launcher can't verify the game's patching status. You're allowed to run the game on your own risk")); - - self.widgets.launch_game.remove_css_class("suggested-action"); - self.widgets.launch_game.add_css_class("destructive-action"); - } - - Patch::Outdated { .. } | - Patch::Preparation { .. } => { - self.widgets.launch_game.set_label("Patch not available"); - self.widgets.launch_game.set_sensitive(false); - - self.widgets.launch_game.set_tooltip_text(Some("Patch is outdated or in preparation state, so unavailable for usage. Return back later to see its status")); - - self.widgets.launch_game.remove_css_class("suggested-action"); - self.widgets.launch_game.add_css_class("destructive-action"); - } - - Patch::Testing { .. } => { - self.widgets.launch_game.set_label("Apply test patch"); - - self.widgets.launch_game.remove_css_class("suggested-action"); - self.widgets.launch_game.add_css_class("warning"); - } - - Patch::Available { .. } => { - self.widgets.launch_game.set_label("Apply patch"); - } - } - } - - LauncherState::WineNotInstalled => { - self.widgets.launch_game.set_label("Download wine"); - } - - LauncherState::PrefixNotExists => { - self.widgets.launch_game.set_label("Create prefix"); - } - - LauncherState::GameUpdateAvailable(_) | - LauncherState::VoiceUpdateAvailable(_) => { - self.widgets.launch_game.set_label("Update"); - } - - LauncherState::GameNotInstalled(_) | - LauncherState::VoiceNotInstalled(_) => { - self.widgets.launch_game.set_label("Download"); - } - - LauncherState::VoiceOutdated(_) | - LauncherState::GameOutdated(_) => { - self.widgets.launch_game.set_label("Update"); - self.widgets.launch_game.set_tooltip_text(Some("Version is too outdated and can't be updated")); - self.widgets.launch_game.set_sensitive(false); } } - - let mut values = self.values.take(); - - values.state = state; - - self.values.set(values); } - pub fn update_state(&self) -> Await> { - self.widgets.status_page.show(); - self.widgets.launcher_content.hide(); + fn init( + _counter: Self::Init, + root: &Self::Root, + _sender: ComponentSender, + ) -> ComponentParts { + let widgets = view_output!(); - let (send, recv) = std::sync::mpsc::channel(); + let model = App { + preferences_window: PreferencesApp::builder() + .launch(widgets.main_window.clone().into()) + .detach() + }; - let this = self.clone(); + unsafe { + READY = true; + } - glib::MainContext::default().invoke(move || { - let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); + ComponentParts { model, widgets } + } - receiver.attach(None, clone!(@strong this.widgets.status_page as status_page => move |description| { - status_page.set_description(Some(&description)); + fn update(&mut self, msg: Self::Input, _sender: ComponentSender) { + match msg { + AppMsg::OpenPreferences => { + self.preferences_window.widgets().preferences_window.show(); + } - glib::Continue(true) - })); - - std::thread::spawn(move || { - match LauncherState::get(move |status| sender.send(status.to_string()).unwrap()) { - Ok(state) => { - this.set_state(state.clone()); - - this.widgets.status_page.hide(); - this.widgets.launcher_content.show(); - - send.send(Ok(state)).unwrap(); - }, - Err(err) => { - send.send(Err(err.to_string())).unwrap(); - - glib::MainContext::default().invoke(move || { - this.toast("Failed to get initial launcher state", err); - }); - } - } - }); - }); - - Await::new(move || { - recv.recv().unwrap() - }) + AppMsg::ClosePreferences => { + self.preferences_window.widgets().preferences_window.hide(); + } + } } } - -impl Toast for App { - fn get_toast_widgets(&self) -> (adw::ApplicationWindow, adw::ToastOverlay) { - (self.widgets.window.clone(), self.widgets.toast_overlay.clone()) - } -} - -unsafe impl Send for App {} -unsafe impl Sync for App {} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 6ce43a1..ad8447f 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,49 +1,2 @@ -use gtk::prelude::*; - -pub mod first_run; pub mod main; pub mod preferences; -pub mod traits; - -pub mod components; - -pub use first_run::App as FirstRunApp; -pub use main::App as MainApp; - -/// This function loads object from builder or panics if it doesn't exist -pub fn get_object>(builder: >k::Builder, name: &str) -> anyhow::Result { - match builder.object::(name) { - Some(object) => Ok(object), - None => Err(anyhow::anyhow!("Failed to parse object '{name}'")) - } -} - -/// Add action to widget -/// -/// All the actions needs to be in some group. This function creates new group with the name of the action. -/// This means that to add action to some widget you need to speify `name.name` as its name -/// -/// ## Example: -/// -/// ``` -/// let toast = libadwaita::Toast::new("Example toast"); -/// -/// toast.set_button_label(Some("Example button")); -/// toast.set_action_name(Some("example-button.example-button")); -/// -/// add_action(&toast, "example-button", || { -/// println!("Hello, World!"); -/// }); -/// ``` -pub fn add_action, F: Fn() + 'static>(obj: &T, name: &str, closure: F) { - let action_group = adw::gio::SimpleActionGroup::new(); - let action = adw::gio::SimpleAction::new(name, None); - - obj.insert_action_group(name, Some(&action_group)); - - action.connect_activate(move |_, _| { - closure(); - }); - - action_group.add_action(&action); -} diff --git a/src/ui/preferences/enhancements.rs b/src/ui/preferences/enhancements.rs index c6c9761..efb79d4 100644 --- a/src/ui/preferences/enhancements.rs +++ b/src/ui/preferences/enhancements.rs @@ -1,402 +1,212 @@ -use gtk::prelude::*; +use relm4::prelude::*; + use adw::prelude::*; -use gtk::glib; -use gtk::glib::clone; +use anime_launcher_sdk::config; +use anime_launcher_sdk::config::prelude::*; -use crate::lib; -use crate::lib::config; -use crate::lib::config::prelude::*; +use crate::i18n::tr; +use crate::ui::main::is_ready; -use crate::ui::*; - -use super::gamescope::App as GamescopeApp; - -/// This structure is used to describe widgets used in application -/// -/// `AppWidgets::try_get` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets -/// -/// This function does not implement events -#[derive(Clone, glib::Downgrade)] -pub struct AppWidgets { - pub page: adw::PreferencesPage, - - pub sync_combo: adw::ComboRow, - pub wine_lang: adw::ComboRow, - pub borderless: gtk::Switch, - pub virtual_desktop_row: adw::ComboRow, - pub virtual_desktop: gtk::Switch, - - pub hud_combo: adw::ComboRow, - pub fsr_combo: adw::ComboRow, - pub fsr_switcher: gtk::Switch, - - pub gamemode_row: adw::ActionRow, - pub gamemode_switcher: gtk::Switch, - - pub gamescope_row: adw::ActionRow, - pub gamescope_settings: gtk::Button, - pub gamescope_switcher: gtk::Switch, - - pub gamescope_app: GamescopeApp, - - pub fps_unlocker_combo: adw::ComboRow, - pub fps_unlocker_switcher: gtk::Switch, - pub fps_unlocker_power_saving_switcher: gtk::Switch, - pub fps_unlocker_monitor_num: gtk::SpinButton, - pub fps_unlocker_window_mode_combo: adw::ComboRow, - pub fps_unlocker_priority_combo: adw::ComboRow +lazy_static::lazy_static! { + static ref CONFIG: config::Config = config::get().expect("Failed to load config"); } -impl AppWidgets { - fn try_get(window: &adw::ApplicationWindow) -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/preferences/enhancements.ui"); +#[relm4::widget_template(pub)] +impl WidgetTemplate for Enhancements { + view! { + adw::PreferencesPage { + set_title: &tr("enhancements"), + set_icon_name: Some("applications-graphics-symbolic"), - let result = Self { - page: get_object(&builder, "page")?, + add = &adw::PreferencesGroup { + set_title: &tr("wine"), - sync_combo: get_object(&builder, "sync_combo")?, - wine_lang: get_object(&builder, "wine_lang")?, - borderless: get_object(&builder, "borderless")?, - virtual_desktop_row: get_object(&builder, "virtual_desktop_row")?, - virtual_desktop: get_object(&builder, "virtual_desktop")?, + adw::ComboRow { + set_title: &tr("synchronization"), + set_subtitle: &tr("wine-sync-description"), - hud_combo: get_object(&builder, "hud_combo")?, - fsr_combo: get_object(&builder, "fsr_combo")?, - fsr_switcher: get_object(&builder, "fsr_switcher")?, + #[wrap(Some)] + set_model = >k::StringList::new(&[ + &tr("none"), + "ESync", + "FSync", + "Futex2" + ]), - gamemode_row: get_object(&builder, "gamemode_row")?, - gamemode_switcher: get_object(&builder, "gamemode_switcher")?, + set_selected: CONFIG.game.wine.sync.into(), - gamescope_row: get_object(&builder, "gamescope_row")?, - gamescope_settings: get_object(&builder, "gamescope_settings")?, - gamescope_switcher: get_object(&builder, "gamescope_switcher")?, + connect_selected_notify => move |row| { + if is_ready() { + if let Ok(mut config) = config::get() { + config.game.wine.sync = WineSync::try_from(row.selected()).unwrap(); + + config::update(config); + } + } + } + }, - gamescope_app: GamescopeApp::new(window)?, + adw::ComboRow { + set_title: &tr("language"), + set_subtitle: &tr("wine-lang-description"), - fps_unlocker_combo: get_object(&builder, "fps_unlocker_combo")?, - fps_unlocker_switcher: get_object(&builder, "fps_unlocker_switcher")?, - fps_unlocker_power_saving_switcher: get_object(&builder, "fps_unlocker_power_saving_switcher")?, - fps_unlocker_monitor_num: get_object(&builder, "fps_unlocker_monitor_num")?, - fps_unlocker_window_mode_combo: get_object(&builder, "fps_unlocker_window_mode_combo")?, - fps_unlocker_priority_combo: get_object(&builder, "fps_unlocker_priority_combo")? - }; + #[wrap(Some)] + set_model = >k::StringList::new(&[ + &tr("system"), + "English", + "Русский", + "Deutsch", + "Português", + "Polska", + "Français", + "Español", + "中国", + "日本語", + "한국어" + ]), - // Set availale wine languages - result.wine_lang.set_model(Some(&WineLang::get_model())); + set_selected: CONFIG.game.wine.language.into(), - // Set availale virtual desktop resolutions - result.virtual_desktop_row.set_model(Some(&Resolution::get_model())); + connect_selected_notify => move |row| { + if is_ready() { + if let Ok(mut config) = config::get() { + config.game.wine.language = WineLang::try_from(row.selected()).unwrap(); + + config::update(config); + } + } + } + }, - // Set availale fps unlocker limits - result.fps_unlocker_combo.set_model(Some(&Fps::get_model())); + adw::ActionRow { + set_title: &tr("borderless-window"), - // Disable gamemode row if it's not available - if !lib::is_available("gamemoderun") { - result.gamemode_row.set_sensitive(false); - result.gamemode_row.set_tooltip_text(Some("Gamemode is not installed")); - } + add_suffix = >k::Switch { + set_valign: gtk::Align::Center + } + }, - // Disable gamescope row if it's not available - if !lib::is_available("gamescope") { - result.gamescope_row.set_sensitive(false); - result.gamescope_row.set_tooltip_text(Some("Gamescope is not installed")); - } + adw::ComboRow { + set_title: &tr("virtual-desktop"), - Ok(result) - } -} + #[wrap(Some)] + set_model = >k::StringList::new(&[ + &tr("custom"), + "960x540", + "1280x720", + "1920x1080", + "2560x1440", + "3840x2160" + ]), -/// The main application structure -/// -/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets -/// -/// `Rc>` means this: -/// - `Rc` addeds ability to reference the same value from various clones of the structure. -/// This will guarantee us that inner `Cell` is the same for all the `App::clone()` values -/// - `Cell` addeds inner mutability to its value, so we can mutate it even without mutable reference. -/// -/// So we have a shared reference to some value that can be changed without mutable reference. -/// That's what we need and what we use in `App::update` method -#[derive(Clone, glib::Downgrade)] -pub struct App { - widgets: AppWidgets -} + set_selected: CONFIG.game.wine.virtual_desktop.get_resolution().into(), -impl App { - /// Create new application - pub fn new(window: &adw::ApplicationWindow) -> anyhow::Result { - let result = Self { - widgets: AppWidgets::try_get(window)? - }.init_events(); + connect_selected_notify => move |row| { + if is_ready() { + if let Ok(mut config) = config::get() { + let (width, height) = Resolution::try_from(row.selected()).unwrap().get_pair(); - Ok(result) - } + config.game.wine.virtual_desktop.width = width; + config.game.wine.virtual_desktop.height = height; + + config::update(config); + } + } + }, - /// Add default events and values to the widgets - fn init_events(self) -> Self { - // Wine sync selection - self.widgets.sync_combo.connect_selected_notify(move |row| { - if let Ok(mut config) = config::get() { - config.game.wine.sync = WineSync::try_from(row.selected()).unwrap(); + add_suffix = >k::Switch { + set_valign: gtk::Align::Center, - config::update(config); - } - }); + set_state: CONFIG.game.wine.virtual_desktop.enabled, - // Wine language selection - self.widgets.wine_lang.connect_selected_notify(move |row| { - if let Ok(mut config) = config::get() { - config.game.wine.language = WineLang::list()[row.selected() as usize]; - - config::update(config); - } - }); - - // Borderless switching - self.widgets.borderless.connect_state_notify(move |switch| { - if let Ok(mut config) = config::get() { - config.game.wine.borderless = switch.state(); - - config::update(config); - } - }); - - // Virtual desktop resolution selection - self.widgets.virtual_desktop_row.connect_selected_notify(move |row| { - if let Ok(mut config) = config::get() { - let resolutions = Resolution::list(); - - if row.selected() > 0 { - let (w, h) = resolutions[row.selected() as usize - 1].get_pair(); - - config.game.wine.virtual_desktop.width = w; - config.game.wine.virtual_desktop.height = h; - - config::update(config); - } - } - }); - - // Virtual desktop switching - self.widgets.virtual_desktop.connect_state_notify(move |switch| { - if let Ok(mut config) = config::get() { - config.game.wine.virtual_desktop.enabled = switch.state(); - - config::update(config); - } - }); - - // HUD selection - self.widgets.hud_combo.connect_selected_notify(move |row| { - if let Ok(mut config) = config::get() { - config.game.enhancements.hud = HUD::try_from(row.selected()).unwrap(); - - config::update(config); - } - }); - - // FSR strength selection - // - // Ultra Quality = 5 - // Quality = 4 - // Balanced = 3 - // Performance = 2 - // - // Source: Bottles (https://github.com/bottlesdevs/Bottles/blob/22fa3573a13f4e9b9c429e4cdfe4ca29787a2832/src/ui/details-preferences.ui#L88) - self.widgets.fsr_combo.connect_selected_notify(move |row| { - if let Ok(mut config) = config::get() { - config.game.enhancements.fsr.strength = 5 - row.selected() as u64; - - config::update(config); - } - }); - - // FSR switching - self.widgets.fsr_switcher.connect_state_notify(move |switch| { - if let Ok(mut config) = config::get() { - config.game.enhancements.fsr.enabled = switch.state(); - - config::update(config); - } - }); + connect_state_notify => move |switch| { + if is_ready() { + if let Ok(mut config) = config::get() { + config.game.wine.virtual_desktop.enabled = switch.state(); - // Gamemode switching - self.widgets.gamemode_switcher.connect_state_notify(move |switch| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamemode = switch.state(); - - config::update(config); - } - }); - - // Gamescope settings app - self.widgets.gamescope_settings.connect_clicked(clone!(@weak self as this => move |_| { - this.widgets.gamescope_app.show(); - })); - - // Gamescope swithing - self.widgets.gamescope_switcher.connect_state_notify(move |switch| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.enabled = switch.state(); - - config::update(config); - } - }); - - // FPS unlocker swithing - self.widgets.fps_unlocker_switcher.connect_state_notify(move |switch| { - if let Ok(mut config) = config::get() { - config.game.enhancements.fps_unlocker.enabled = switch.state(); - - config::update(config); - } - }); - - // FPS unlocker -> fps limit combo - self.widgets.fps_unlocker_combo.connect_selected_notify(move |row| { - if let Ok(mut config) = config::get() { - if row.selected() > 0 { - config.game.enhancements.fps_unlocker.config.fps = Fps::list()[row.selected() as usize - 1].to_num(); - - config::update(config); + config::update(config); + } + } + } + } } - } - }); + }, - // FPS unlocker -> power saving swithing - self.widgets.fps_unlocker_power_saving_switcher.connect_state_notify(move |switch| { - if let Ok(mut config) = config::get() { - config.game.enhancements.fps_unlocker.config.power_saving = switch.state(); + add = &adw::PreferencesGroup { + set_title: &tr("game"), - config::update(config); - } - }); + adw::ComboRow { + set_title: &tr("hud"), - // FPS unlocker -> monitor number - self.widgets.fps_unlocker_monitor_num.connect_changed(move |button| { - if let Ok(mut config) = config::get() { - config.game.enhancements.fps_unlocker.config.monitor = button.value() as u64; + #[wrap(Some)] + set_model = >k::StringList::new(&[ + &tr("none"), + "DXVK", + "MangoHud" + ]), + }, - config::update(config); - } - }); + adw::ComboRow { + set_title: &tr("fsr"), + set_subtitle: &tr("fsr-description"), - // FPS unlocker -> window mode combo - self.widgets.fps_unlocker_window_mode_combo.connect_selected_notify(move |row| { - if let Ok(mut config) = config::get() { - config.game.enhancements.fps_unlocker.config.window_mode = row.selected() as u64; + #[wrap(Some)] + set_model = >k::StringList::new(&[ + &tr("ultra-quality"), + &tr("quality"), + &tr("balanced"), + &tr("performance") + ]), - config::update(config); - } - }); + add_suffix = >k::Switch { + set_valign: gtk::Align::Center + } + }, - // FPS unlocker -> priority combo - self.widgets.fps_unlocker_priority_combo.connect_selected_notify(move |row| { - if let Ok(mut config) = config::get() { - config.game.enhancements.fps_unlocker.config.priority = row.selected() as u64; + adw::ActionRow { + set_title: &tr("gamemode"), + set_subtitle: &tr("gamemode-description"), - config::update(config); - } - }); + add_suffix = >k::Switch { + set_valign: gtk::Align::Center + } + } + }, - self - } + add = &adw::PreferencesGroup { + set_title: &tr("fps-unlocker"), - pub fn title() -> String { - String::from("Enhancements") - } + adw::ComboRow { + set_title: &tr("enabled"), + set_subtitle: &tr("fps-unlocker-description"), - pub fn get_page(&self) -> adw::PreferencesPage { - self.widgets.page.clone() - } + #[wrap(Some)] + set_model = >k::StringList::new(&[ + &tr("custom"), + "90", + "120", + "144", + "165", + "180", + "200", + "240" + ]), - /// This method is being called by the `PreferencesStack::update` - pub fn prepare(&self, status_page: &adw::StatusPage) -> anyhow::Result<()> { - let config = config::get()?; + add_suffix = >k::Switch { + set_valign: gtk::Align::Center + } + }, - status_page.set_description(Some("Loading enhancements...")); + adw::ActionRow { + set_title: &tr("power-saving"), + set_subtitle: &tr("power-saving-description"), - // Update Wine sync - self.widgets.sync_combo.set_selected(config.game.wine.sync.into()); - - // Update wine language - self.widgets.wine_lang.set_selected(config.game.wine.language.into()); - - // Update borderless - self.widgets.borderless.set_state(config.game.wine.borderless); - - // Update virtual desktop - self.widgets.virtual_desktop.set_state(config.game.wine.virtual_desktop.enabled); - - let resolution = Resolution::from_pair( - config.game.wine.virtual_desktop.width, - config.game.wine.virtual_desktop.height - ); - - if let Resolution::Custom(_, _) = resolution { - self.widgets.virtual_desktop_row.set_selected(0); - } - - else { - for (i, res) in Resolution::list().into_iter().enumerate() { - if res == resolution { - self.widgets.virtual_desktop_row.set_selected(i as u32 + 1); + add_suffix = >k::Switch { + set_valign: gtk::Align::Center + } } } } - - // Update HUD - self.widgets.hud_combo.set_selected(config.game.enhancements.hud.into()); - - // FSR strength selection - self.widgets.fsr_combo.set_selected(5 - config.game.enhancements.fsr.strength as u32); - - // FSR switching - self.widgets.fsr_switcher.set_state(config.game.enhancements.fsr.enabled); - - // Gamemode switching - self.widgets.gamemode_switcher.set_state(config.game.enhancements.gamemode); - - // Switch gamescope option - self.widgets.gamescope_switcher.set_state(config.game.enhancements.gamescope.enabled); - - // Switch FPS unlocker - self.widgets.fps_unlocker_switcher.set_state(config.game.enhancements.fps_unlocker.enabled); - - // Select FPS limit - let fps = Fps::from_num(config.game.enhancements.fps_unlocker.config.fps); - - if let Fps::Custom(_) = fps { - self.widgets.fps_unlocker_combo.set_selected(0); - } - - else { - for (i, value) in Fps::list().into_iter().enumerate() { - if value == fps { - self.widgets.fps_unlocker_combo.set_selected(i as u32 + 1); - } - } - } - - // Switch FPS unlocker -> power saving - self.widgets.fps_unlocker_power_saving_switcher.set_state(config.game.enhancements.fps_unlocker.config.power_saving); - - // Switch FPS unlocker -> monitor number - self.widgets.fps_unlocker_monitor_num.set_value(config.game.enhancements.fps_unlocker.config.monitor as f64); - - // Switch FPS unlocker -> window mode - self.widgets.fps_unlocker_window_mode_combo.set_selected(config.game.enhancements.fps_unlocker.config.window_mode as u32); - - // Switch FPS unlocker -> priority - self.widgets.fps_unlocker_priority_combo.set_selected(config.game.enhancements.fps_unlocker.config.priority as u32); - - // Prepare gamescope settings app - self.widgets.gamescope_app.prepare(status_page)?; - - Ok(()) } } - -unsafe impl Send for App {} -unsafe impl Sync for App {} diff --git a/src/ui/preferences/environment.rs b/src/ui/preferences/environment.rs deleted file mode 100644 index 8bf42a7..0000000 --- a/src/ui/preferences/environment.rs +++ /dev/null @@ -1,243 +0,0 @@ -use gtk::prelude::*; -use adw::prelude::*; - -use gtk::glib; -use gtk::glib::clone; - -use std::collections::HashMap; -use std::rc::Rc; -use std::cell::Cell; - -use crate::ui::get_object; -use crate::lib::config; - -/// This structure is used to describe widgets used in application -/// -/// `AppWidgets::try_get` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets -/// -/// This function does not implement events -#[derive(Clone, glib::Downgrade)] -pub struct AppWidgets { - pub page: adw::PreferencesPage, - - pub command: gtk::Entry, - - pub variables: adw::PreferencesGroup, - - pub name: gtk::Entry, - pub value: gtk::Entry, - pub add: gtk::Button -} - -impl AppWidgets { - fn try_get() -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/preferences/environment.ui"); - - let result = Self { - page: get_object(&builder, "page")?, - - command: get_object(&builder, "command")?, - - variables: get_object(&builder, "variables")?, - - name: get_object(&builder, "name")?, - value: get_object(&builder, "value")?, - add: get_object(&builder, "add")? - }; - - Ok(result) - } -} - -/// This enum is used to describe an action inside of this application -/// -/// It may be helpful if you want to add the same event for several widgets, or call an action inside of another action -#[derive(Debug, Clone)] -pub enum Actions { - Add(Rc<(String, String)>), - Delete(Rc) -} - -/// This enum is used to store some of this application data -/// -/// In this example we store a counter here to know what should we increment or decrement -/// -/// This must implement `Default` trait -#[derive(Debug, Default)] -pub struct Values { - rows: HashMap -} - -/// The main application structure -/// -/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets -/// -/// `Rc>` means this: -/// - `Rc` addeds ability to reference the same value from various clones of the structure. -/// This will guarantee us that inner `Cell` is the same for all the `App::clone()` values -/// - `Cell` addeds inner mutability to its value, so we can mutate it even without mutable reference. -/// -/// So we have a shared reference to some value that can be changed without mutable reference. -/// That's what we need and what we use in `App::update` method -#[derive(Clone, glib::Downgrade)] -pub struct App { - widgets: AppWidgets, - values: Rc>, - actions: Rc>>> -} - -impl App { - /// Create new application - pub fn new() -> anyhow::Result { - let result = Self { - widgets: AppWidgets::try_get()?, - values: Default::default(), - actions: Default::default() - }.init_events().init_actions(); - - Ok(result) - } - - /// Add default events and values to the widgets - fn init_events(self) -> Self { - let this = self.clone(); - - self.widgets.add.connect_clicked(move |_| { - let name = this.widgets.name.text().to_string(); - let value = this.widgets.value.text().to_string(); - - this.update(Actions::Add(Rc::new((name, value)))).unwrap(); - }); - - self.widgets.command.connect_changed(move |entry| { - if let Ok(mut config) = config::get() { - let command = entry.text().to_string(); - - config.game.command = if command.is_empty() { - None - } else { - Some(command) - }; - - config::update(config); - } - }); - - self - } - - /// Add actions processors - /// - /// Changes will happen in the main thread so you can call `update` method from separate thread - fn init_actions(self) -> Self { - let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - - // I prefer to avoid using clone! here because it breaks my code autocompletion - let this = self.clone(); - - receiver.attach(None, move |action| { - let mut config = config::get().expect("Failed to load config"); - let mut values = this.values.take(); - - // Some debug output - println!("[environment page] [update] action: {:?}", &action); - - match action { - Actions::Add(strs) => { - let (name, value) = &*strs; - - if !name.is_empty() && !value.is_empty() && !values.rows.contains_key(name) { - config.game.environment.insert(name.clone(), value.clone()); - - let row = adw::ActionRow::new(); - - row.set_title(name); - row.set_subtitle(value); - - let button = gtk::Button::new(); - - button.set_icon_name("user-trash-symbolic"); - button.set_valign(gtk::Align::Center); - button.add_css_class("flat"); - - button.connect_clicked(clone!(@weak this, @strong name => move |_| { - this.update(Actions::Delete(Rc::new(name.clone()))).unwrap(); - })); - - row.add_suffix(&button); - - this.widgets.variables.add(&row); - - values.rows.insert(name.clone(), row); - - this.widgets.name.set_text(""); - this.widgets.value.set_text(""); - } - } - - Actions::Delete(name) => { - let name = &*name; - - if let Some(widget) = values.rows.get(name) { - this.widgets.variables.remove(widget); - } - - values.rows.remove(name); - config.game.environment.remove(name); - } - } - - config::update(config); - - this.values.set(values); - - glib::Continue(true) - }); - - self.actions.set(Some(sender)); - - self - } - - /// Update widgets state by calling some action - pub fn update(&self, action: Actions) -> Result<(), std::sync::mpsc::SendError> { - let actions = self.actions.take(); - - let result = match &actions { - Some(sender) => Ok(sender.send(action)?), - None => Ok(()) - }; - - self.actions.set(actions); - - result - } - - pub fn title() -> String { - String::from("Environment") - } - - pub fn get_page(&self) -> adw::PreferencesPage { - self.widgets.page.clone() - } - - /// This method is being called by the `PreferencesStack::update` - pub fn prepare(&self, status_page: &adw::StatusPage) -> anyhow::Result<()> { - let config = config::get()?; - - status_page.set_description(Some("Loading environment...")); - - // Set game command - self.widgets.command.set_text(&config.game.command.unwrap_or_default()); - - // Add environment variables - for (name, value) in config.game.environment { - self.update(Actions::Add(Rc::new((name, value)))).unwrap(); - } - - Ok(()) - } -} - -unsafe impl Send for App {} -unsafe impl Sync for App {} diff --git a/src/ui/preferences/gamescope.rs b/src/ui/preferences/gamescope.rs deleted file mode 100644 index 1baec4f..0000000 --- a/src/ui/preferences/gamescope.rs +++ /dev/null @@ -1,260 +0,0 @@ -use gtk::prelude::*; - -use gtk::glib; - -use crate::lib::config; -use crate::lib::config::prelude::*; - -use crate::ui::*; - -/// This structure is used to describe widgets used in application -/// -/// `AppWidgets::try_get` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets -/// -/// This function does not implement events -#[derive(Clone, glib::Downgrade)] -pub struct AppWidgets { - pub window: adw::PreferencesWindow, - - pub game_width: adw::EntryRow, - pub game_height: adw::EntryRow, - - pub gamescope_width: adw::EntryRow, - pub gamescope_height: adw::EntryRow, - - pub framerate_limit: adw::EntryRow, - pub framerate_unfocused_limit: adw::EntryRow, - pub integer_scaling: gtk::Switch, - pub fsr: gtk::Switch, - pub nis: gtk::Switch, - - pub borderless: gtk::ToggleButton, - pub fullscreen: gtk::ToggleButton -} - -impl AppWidgets { - fn try_get(window: &adw::ApplicationWindow) -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/preferences/gamescope.ui"); - - let result = Self { - window: get_object(&builder, "window")?, - - game_width: get_object(&builder, "game_width")?, - game_height: get_object(&builder, "game_height")?, - - gamescope_width: get_object(&builder, "gamescope_width")?, - gamescope_height: get_object(&builder, "gamescope_height")?, - - framerate_limit: get_object(&builder, "framerate_limit")?, - framerate_unfocused_limit: get_object(&builder, "framerate_unfocused_limit")?, - integer_scaling: get_object(&builder, "integer_scaling")?, - fsr: get_object(&builder, "fsr")?, - nis: get_object(&builder, "nis")?, - - borderless: get_object(&builder, "borderless")?, - fullscreen: get_object(&builder, "fullscreen")? - }; - - result.window.set_transient_for(Some(window)); - - Ok(result) - } -} - -/// The main application structure -/// -/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets -/// -/// `Rc>` means this: -/// - `Rc` addeds ability to reference the same value from various clones of the structure. -/// This will guarantee us that inner `Cell` is the same for all the `App::clone()` values -/// - `Cell` addeds inner mutability to its value, so we can mutate it even without mutable reference. -/// -/// So we have a shared reference to some value that can be changed without mutable reference. -/// That's what we need and what we use in `App::update` method -#[derive(Clone, glib::Downgrade)] -pub struct App { - widgets: AppWidgets -} - -impl App { - /// Create new application - pub fn new(window: &adw::ApplicationWindow) -> anyhow::Result { - let result = Self { - widgets: AppWidgets::try_get(window)? - }.init_events(); - - Ok(result) - } - - /// Add default events and values to the widgets - fn init_events(self) -> Self { - // Game width - self.widgets.game_width.connect_changed(move |entry| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.game.width = entry.text().parse().unwrap_or(0); - - config::update(config); - } - }); - - // Game height - self.widgets.game_height.connect_changed(move |entry| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.game.height = entry.text().parse().unwrap_or(0); - - config::update(config); - } - }); - - // Gamescope width - self.widgets.gamescope_width.connect_changed(move |entry| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.gamescope.width = entry.text().parse().unwrap_or(0); - - config::update(config); - } - }); - - // Gamescope height - self.widgets.gamescope_height.connect_changed(move |entry| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.gamescope.height = entry.text().parse().unwrap_or(0); - - config::update(config); - } - }); - - // Framerate focused - self.widgets.framerate_limit.connect_changed(move |entry| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.framerate.focused = entry.text().parse().unwrap_or(0); - - config::update(config); - } - }); - - // Framerate unfocused - self.widgets.framerate_unfocused_limit.connect_changed(move |entry| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.framerate.unfocused = entry.text().parse().unwrap_or(0); - - config::update(config); - } - }); - - // Use integer scaling - self.widgets.integer_scaling.connect_state_notify(move |switch| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.integer_scaling = switch.state(); - - config::update(config); - } - }); - - // Use FSR - self.widgets.fsr.connect_state_notify(move |switch| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.fsr = switch.state(); - - config::update(config); - } - }); - - // Use NIS (Nvidia Image Scaling) - self.widgets.nis.connect_state_notify(move |switch| { - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.nis = switch.state(); - - config::update(config); - } - }); - - // Window type - - let borderless = self.widgets.borderless.clone(); - let fullscreen = self.widgets.fullscreen.clone(); - - // Window type (Borderless) - self.widgets.borderless.connect_clicked(move |button| { - if !button.is_active() { - button.activate(); - } - - else { - fullscreen.set_active(false); - - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.window_type = if button.is_active() { - WindowType::Borderless - } else { - WindowType::Fullscreen - }; - - config::update(config); - } - } - }); - - // Window type (Fullscreen) - self.widgets.fullscreen.connect_clicked(move |button| { - if !button.is_active() { - button.activate(); - } - - else { - borderless.set_active(false); - - if let Ok(mut config) = config::get() { - config.game.enhancements.gamescope.window_type = if button.is_active() { - WindowType::Fullscreen - } else { - WindowType::Borderless - }; - - config::update(config); - } - } - }); - - self - } - - /// This method is being called by the `EnhancementsPage::prepare` - pub fn prepare(&self, status_page: &adw::StatusPage) -> anyhow::Result<()> { - let config = config::get()?; - - status_page.set_description(Some("Loading gamescope...")); - - fn set_text(widget: &adw::EntryRow, value: u64) { - widget.set_text(&if value == 0 { String::new() } else { value.to_string() }); - } - - set_text(&self.widgets.game_width, config.game.enhancements.gamescope.game.width); - set_text(&self.widgets.game_height, config.game.enhancements.gamescope.game.height); - - set_text(&self.widgets.gamescope_width, config.game.enhancements.gamescope.gamescope.width); - set_text(&self.widgets.gamescope_height, config.game.enhancements.gamescope.gamescope.height); - - set_text(&self.widgets.framerate_limit, config.game.enhancements.gamescope.framerate.focused); - set_text(&self.widgets.framerate_unfocused_limit, config.game.enhancements.gamescope.framerate.unfocused); - - self.widgets.integer_scaling.set_state(config.game.enhancements.gamescope.integer_scaling); - self.widgets.fsr.set_state(config.game.enhancements.gamescope.fsr); - self.widgets.nis.set_state(config.game.enhancements.gamescope.nis); - - match config.game.enhancements.gamescope.window_type { - WindowType::Borderless => self.widgets.borderless.set_active(true), - WindowType::Fullscreen => self.widgets.fullscreen.set_active(true) - }; - - Ok(()) - } - - pub fn show(&self) { - self.widgets.window.show(); - } -} - -unsafe impl Send for App {} -unsafe impl Sync for App {} diff --git a/src/ui/preferences/general.rs b/src/ui/preferences/general.rs index 80657f3..5bd37ed 100644 --- a/src/ui/preferences/general.rs +++ b/src/ui/preferences/general.rs @@ -1,716 +1,134 @@ +use relm4::prelude::*; + use gtk::prelude::*; use adw::prelude::*; -use gtk::glib; -use gtk::glib::clone; +use anime_launcher_sdk::config; -use std::rc::Rc; -use std::cell::Cell; +use crate::i18n::tr; +use crate::ui::main::is_ready; -use anime_game_core::prelude::*; -use anime_game_core::genshin::prelude::*; - -use crate::lib::consts; -use crate::lib::config; -use crate::lib::dxvk; -use crate::lib::wine; -use crate::lib::launcher::states::LauncherState; - -use crate::ui::*; -use crate::ui::traits::prelude::*; -use crate::ui::components::voiceover_row::VoiceoverRow; -use crate::ui::components::wine_group::WineGroup; -use crate::ui::components::dxvk_group::DxvkGroup; - -/// This structure is used to describe widgets used in application -/// -/// `AppWidgets::try_get` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets -/// -/// This function does not implement events -#[derive(Clone, glib::Downgrade)] -pub struct AppWidgets { - pub page: adw::PreferencesPage, - - pub voiceovers_row: adw::ExpanderRow, - pub voieover_components: Rc>, - - pub repair_game: gtk::Button, - - pub game_version: gtk::Label, - pub patch_version: gtk::Label, - - pub wine_selected: adw::ComboRow, - - pub wine_groups: adw::PreferencesGroup, - pub wine_recommended_only: gtk::Switch, - - pub wine_components: Rc>, - - pub dxvk_selected: adw::ComboRow, - - pub dxvk_groups: adw::PreferencesGroup, - pub dxvk_recommended_only: gtk::Switch, - - pub dxvk_components: Rc> +lazy_static::lazy_static! { + static ref CONFIG: config::Config = config::get().expect("Failed to load config"); } -impl AppWidgets { - pub fn try_get() -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/preferences/general.ui"); +#[relm4::widget_template(pub)] +impl WidgetTemplate for General { + view! { + adw::PreferencesPage { + set_title: &tr("general"), + set_icon_name: Some("applications-system-symbolic"), - let mut result = Self { - page: get_object(&builder, "page")?, + add = &adw::PreferencesGroup { + set_title: &tr("general"), - voiceovers_row: get_object(&builder, "voiceovers_row")?, - voieover_components: Default::default(), + adw::ComboRow { + set_title: &tr("launcher-language"), - repair_game: get_object(&builder, "repair_game")?, + // TODO: maybe simplify it by some way? e.g. specify such stuff in i18n mod - game_version: get_object(&builder, "game_version")?, - patch_version: get_object(&builder, "patch_version")?, + #[wrap(Some)] + set_model = >k::StringList::new(&[ + "English", + "Русский" + ]), - wine_selected: get_object(&builder, "wine_selected")?, + set_selected: match CONFIG.launcher.language.as_str() { + "en-us" => 0, + "ru-ru" => 1, + _ => 0 + }, - wine_groups: get_object(&builder, "wine_groups")?, - wine_recommended_only: get_object(&builder, "wine_recommended_only")?, - - wine_components: Default::default(), - - dxvk_selected: get_object(&builder, "dxvk_selected")?, - - dxvk_groups: get_object(&builder, "dxvk_groups")?, - dxvk_recommended_only: get_object(&builder, "dxvk_recommended_only")?, - - dxvk_components: Default::default() - }; - - let config = config::get()?; - - // Update voiceovers list - let voice_packages = VoicePackage::list_latest()?; - - let mut components = Vec::new(); - - for package in voice_packages { - let row = VoiceoverRow::new(package); - - result.voiceovers_row.add_row(&row.row); - - components.push(row); - } - - result.voieover_components = Rc::new(components); - - // Update wine versions lists - let mut components = Vec::new(); - - for group in wine::List::get() { - let group = WineGroup::new(group); - - group.update_states(&config.game.wine.builds); - - result.wine_groups.add(&group.expander_row); - - components.push(group); - } - - result.wine_components = Rc::new(components); - - // Update DXVK list - let mut components = Vec::new(); - - for group in dxvk::List::get() { - let group = DxvkGroup::new(group); - - group.update_states(&config.game.dxvk.builds); - - result.dxvk_groups.add(&group.expander_row); - - components.push(group); - } - - result.dxvk_components = Rc::new(components); - - Ok(result) - } -} - -/// This enum is used to describe an action inside of this application -/// -/// It may be helpful if you want to add the same event for several widgets, or call an action inside of another action -#[derive(Debug, Clone, glib::Downgrade)] -pub enum Actions { - RepairGame, - VoiceoverPerformAction(Rc), - DxvkPerformAction(Rc<(usize, usize)>), - WinePerformAction(Rc<(usize, usize)>), - UpdateDxvkComboRow, - SelectDxvkVersion(Rc), - UpdateWineComboRow, - SelectWineVersion(Rc), - Toast(Rc<(String, String)>) -} - -impl Actions { - #[allow(clippy::expect_fun_call, clippy::wrong_self_convention)] - pub fn into_fn>(&self, app: &App) -> Box { - Box::new(clone!(@strong self as action, @weak app => move |_| { - app.update(action.clone()).expect(&format!("Failed to execute action {:?}", &action)); - })) - } -} - -/// This enum is used to store some of this application data -/// -/// In this example we store a counter here to know what should we increment or decrement -/// -/// This must implement `Default` trait -#[derive(Debug, Default)] -pub struct Values { - downloaded_wine_versions: Option>, - downloaded_dxvk_versions: Option> -} - -/// The main application structure -/// -/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets -/// -/// `Rc>` means this: -/// - `Rc` addeds ability to reference the same value from various clones of the structure. -/// This will guarantee us that inner `Cell` is the same for all the `App::clone()` values -/// - `Cell` addeds inner mutability to its value, so we can mutate it even without mutable reference. -/// -/// So we have a shared reference to some value that can be changed without mutable reference. -/// That's what we need and what we use in `App::update` method -#[derive(Clone, glib::Downgrade)] -pub struct App { - app: Rc>>, - widgets: AppWidgets, - values: Rc>, - actions: Rc>>> -} - -impl App { - /// Create new application - pub fn new() -> anyhow::Result { - let result = Self { - app: Default::default(), - widgets: AppWidgets::try_get()?, - values: Default::default(), - actions: Default::default() - }.init_events().init_actions(); - - Ok(result) - } - - pub fn set_app(&mut self, app: super::MainApp) { - self.app.set(Some(app)); - } - - /// Add default events and values to the widgets - fn init_events(self) -> Self { - self.widgets.repair_game.connect_clicked(Actions::RepairGame.into_fn(&self)); - - // Voiceover download/delete button event - for (i, row) in (*self.widgets.voieover_components).iter().enumerate() { - row.button.connect_clicked(clone!(@weak self as this => move |_| { - this.update(Actions::VoiceoverPerformAction(Rc::new(i))).unwrap(); - })); - } - - // Selecting wine version event - self.widgets.wine_selected.connect_selected_notify(clone!(@weak self as this => move |combo_row| { - if let Some(model) = combo_row.model() { - if model.n_items() > 0 { - this.update(Actions::SelectWineVersion(Rc::new(combo_row.selected() as usize))).unwrap(); - } - } - })); - - // Selecting dxvk version event - self.widgets.dxvk_selected.connect_selected_notify(clone!(@weak self as this => move |combo_row| { - if let Some(model) = combo_row.model() { - if model.n_items() > 0 { - this.update(Actions::SelectDxvkVersion(Rc::new(combo_row.selected() as usize))).unwrap(); - } - } - })); - - // Set wine recommended only switcher event - self.widgets.wine_recommended_only.connect_state_notify(clone!(@weak self as this => move |switcher| { - for group in &*this.widgets.wine_components { - for component in &group.version_components { - component.row.set_visible(if switcher.state() { - component.version.recommended - } else { - true - }); - } - } - })); - - // Wine install/remove buttons - let components = &*self.widgets.wine_components; - - for (i, group) in components.iter().enumerate() { - for (j, component) in group.version_components.iter().enumerate() { - component.button.connect_clicked(Actions::WinePerformAction(Rc::new((i, j))).into_fn(&self)); - } - } - - // Set DXVK recommended only switcher event - self.widgets.dxvk_recommended_only.connect_state_notify(clone!(@weak self as this => move |switcher| { - for group in &*this.widgets.dxvk_components { - for component in &group.version_components { - component.row.set_visible(if switcher.state() { - component.version.recommended - } else { - true - }); - } - } - })); - - // DXVK install/remove/apply buttons - let components = &*self.widgets.dxvk_components; - - for (i, group) in components.iter().enumerate() { - for (j, component) in group.version_components.iter().enumerate() { - component.button.connect_clicked(Actions::DxvkPerformAction(Rc::new((i, j))).into_fn(&self)); - - component.apply_button.connect_clicked(clone!(@strong component, @weak self as this => move |_| { - std::thread::spawn(clone!(@strong component, @strong this => move || { - let config = config::get().expect("Failed to load config"); + connect_selected_notify => move |row| { + if is_ready() { + if let Ok(mut config) = config::get() { + config.launcher.language = String::from(*[ + "en-us", + "ru-ru" + ].get(row.selected() as usize).unwrap_or(&"en-us")); - match component.apply(&config.game.dxvk.builds, &config.game.wine.prefix) { - Ok(output) => println!("{}", String::from_utf8_lossy(&output.stdout)), - Err(err) => { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to apply DXVK"), err.to_string() - )))).unwrap(); - } - } - })); - })); - } - } - - self - } - - /// Add actions processors - /// - /// Changes will happen in the main thread so you can call `update` method from separate thread - fn init_actions(self) -> Self { - let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - - // I prefer to avoid using clone! here because it breaks my code autocompletion - let this = self.clone(); - - receiver.attach(None, move |action| { - let mut config = config::get().expect("Failed to load config"); - - // Some debug output - println!("[general page] [update] action: {:?}", &action); - - match action { - Actions::RepairGame => { - let option = (*this.app).take(); - this.app.set(option.clone()); - - let app = option.unwrap(); - - app.update(super::main::Actions::PreferencesGoBack).unwrap(); - app.update(super::main::Actions::RepairGame).unwrap(); - } - - Actions::VoiceoverPerformAction(i) => { - let component = this.widgets.voieover_components[*i].clone(); - - if component.is_downloaded(&config.game.path) { - component.button.set_sensitive(false); - - let this = this.clone(); - - std::thread::spawn(move || { - if let Err(err) = component.package.delete_in(&config.game.path) { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to delete voiceover"), err.to_string() - )))).unwrap(); - } - - component.button.set_sensitive(true); - - component.update_state(&config.game.path); - }); - } - - else { - let option = (*this.app).take(); - this.app.set(option.clone()); - - let app = option.unwrap(); - - // Add voiceover to config - config.game.voices.push(component.package.locale().to_code().to_string()); - - config::update(config); - - // Return back, update state and press "download" button if needed - app.update(super::main::Actions::PreferencesGoBack).unwrap(); - app.update_state().then(move |state| { - if let Ok(LauncherState::VoiceNotInstalled(_)) = state { - app.update(super::main::Actions::PerformButtonEvent).unwrap(); - } - }); - } - } - - Actions::DxvkPerformAction(version) => { - let component = this.widgets - .dxvk_components[version.0] - .version_components[version.1].clone(); - - if component.is_downloaded(&config.game.dxvk.builds) { - if let Err(err) = component.delete(&config.game.dxvk.builds) { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to delete DXVK"), err.to_string() - )))).unwrap(); - } - - component.update_state(&config.game.dxvk.builds); - - this.update(Actions::UpdateDxvkComboRow).unwrap(); - } - - else if let Ok(awaiter) = component.download(&config.game.dxvk.builds) { - awaiter.then(clone!(@strong this => move |_| { - match component.apply(&config.game.dxvk.builds, &config.game.wine.prefix) { - Ok(output) => println!("{}", String::from_utf8_lossy(&output.stdout)), - Err(err) => { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to apply DXVK"), err.to_string() - )))).unwrap(); - } - } - - component.update_state(&config.game.dxvk.builds); - - this.update(Actions::UpdateDxvkComboRow).unwrap(); - })); - } - } - - Actions::WinePerformAction(version) => { - let component = this.widgets - .wine_components[version.0] - .version_components[version.1].clone(); - - if component.is_downloaded(&config.game.wine.builds) { - if let Err(err) = component.delete(&config.game.wine.builds) { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to delete wine"), err.to_string() - )))).unwrap(); - } - - component.update_state(&config.game.wine.builds); - - this.update(Actions::UpdateWineComboRow).unwrap(); - } - - else if let Ok(awaiter) = component.download(&config.game.wine.builds) { - awaiter.then(clone!(@strong this => move |_| { - component.update_state(&config.game.wine.builds); - - this.update(Actions::UpdateWineComboRow).unwrap(); - })); - } - } - - Actions::UpdateDxvkComboRow => { - let model = gtk::StringList::new(&[]); - - let list = dxvk::List::list_downloaded(&config.game.dxvk.builds) - .expect("Failed to list downloaded DXVK versions"); - - let mut raw_list = Vec::new(); - let mut selected = 0; - - let curr = match config.try_get_selected_dxvk_info() { - Ok(Some(curr)) => Some(curr.name), - _ => None - }; - - for version in list { - model.append(&version.name); - - if let Some(curr) = &curr { - if &version.name == curr { - selected = raw_list.len() as u32; - } - } - - raw_list.push(version); - } - - let mut values = this.values.take(); - - values.downloaded_dxvk_versions = Some(raw_list); - - this.values.set(values); - - // This will prevent SelectDxvkVersion action to be invoked - let guard = this.widgets.dxvk_selected.freeze_notify(); - - // We need to return app values before we call these methods - // because they'll invoke SelectWineVersion action so access - // downloaded_wine_versions value - this.widgets.dxvk_selected.set_model(Some(&model)); - this.widgets.dxvk_selected.set_selected(selected); - - drop(guard); - } - - Actions::SelectDxvkVersion(i) => { - let values = this.values.take(); - - if let Some(dxvk_versions) = &values.downloaded_dxvk_versions { - let version = dxvk_versions[*i].clone(); - let mut apply = true; - - if let Ok(Some(curr)) = config.try_get_selected_dxvk_info() { - if version == curr { - apply = false; - } - } - - if apply { - this.widgets.dxvk_selected.set_sensitive(false); - - std::thread::spawn(clone!(@strong config, @strong this => move || { - match version.apply(&config.game.dxvk.builds, &config.game.wine.prefix) { - Ok(output) => println!("{}", String::from_utf8_lossy(&output.stdout)), - Err(err) => { - this.update(Actions::Toast(Rc::new(( - String::from("Failed to apply DXVK"), err.to_string() - )))).unwrap(); - } - } - - this.widgets.dxvk_selected.set_sensitive(true); - })); - } - } - - this.values.set(values); - - config::update(config); - } - - Actions::UpdateWineComboRow => { - let model = gtk::StringList::new(&["System"]); - - let list = wine::List::list_downloaded(config.game.wine.builds) - .expect("Failed to list downloaded wine versions"); - - let mut selected = 0; - - for (i, version) in list.iter().enumerate() { - model.append(version.title.as_str()); - - if let Some(curr) = &config.game.wine.selected { - if &version.name == curr { - selected = i as u32 + 1; + config::update(config); } } } + }, - let mut values = this.values.take(); + adw::ExpanderRow { + set_title: &tr("game-voiceovers"), - values.downloaded_wine_versions = Some(list); + add_row = &adw::ActionRow { + set_title: &tr("english"), - this.values.set(values); + add_suffix = >k::Button { + add_css_class: "flat", + set_icon_name: "user-trash-symbolic", + set_valign: gtk::Align::Center + } + }, - // This will prevent SelectWineVersion action to be invoked - let guard = this.widgets.wine_selected.freeze_notify(); + add_row = &adw::ActionRow { + set_title: &tr("japanese"), - // We need to return app values before we call these methods - // because they'll invoke SelectWineVersion action so access - // downloaded_wine_versions value - this.widgets.wine_selected.set_model(Some(&model)); - this.widgets.wine_selected.set_selected(selected); + add_suffix = >k::Button { + add_css_class: "flat", + set_icon_name: "user-trash-symbolic", + set_valign: gtk::Align::Center + } + }, - drop(guard); - } + add_row = &adw::ActionRow { + set_title: &tr("korean"), - Actions::SelectWineVersion(i) => { - let values = this.values.take(); + add_suffix = >k::Button { + add_css_class: "flat", + set_icon_name: "user-trash-symbolic", + set_valign: gtk::Align::Center + } + }, - if let Some(wine_versions) = &values.downloaded_wine_versions { - match *i { - 0 => config.game.wine.selected = None, - i => config.game.wine.selected = Some(wine_versions[i - 1].name.clone()) + add_row = &adw::ActionRow { + set_title: &tr("chinese"), + + add_suffix = >k::Button { + add_css_class: "flat", + set_icon_name: "user-trash-symbolic", + set_valign: gtk::Align::Center } } + }, - this.values.set(values); + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 8, + set_margin_top: 16, - config::update(config); + gtk::Button { + set_label: &tr("repair-game") + } } + }, - Actions::Toast(toast) => { - let (msg, err) = (toast.0.clone(), toast.1.to_string()); + add = &adw::PreferencesGroup { + set_title: &tr("status"), - this.toast(msg, err); - } - } + adw::ActionRow { + set_title: &tr("game-version"), - glib::Continue(true) - }); + add_suffix = >k::Label { + set_text: "3.3.0", + add_css_class: "success" + } + }, - self.actions.set(Some(sender)); + adw::ActionRow { + set_title: &tr("patch-version"), - self - } - - /// Update widgets state by calling some action - pub fn update(&self, action: Actions) -> Result<(), std::sync::mpsc::SendError> { - let actions = self.actions.take(); - - let result = match &actions { - Some(sender) => Ok(sender.send(action)?), - None => Ok(()) - }; - - self.actions.set(actions); - - result - } - - pub fn title() -> String { - String::from("General") - } - - pub fn get_page(&self) -> adw::PreferencesPage { - self.widgets.page.clone() - } - - /// This method is being called by the `PreferencesStack::update` - pub fn prepare(&self, status_page: &adw::StatusPage) -> anyhow::Result<()> { - let config = config::get()?; - let game = Game::new(&config.game.path); - - // Update voiceovers states - status_page.set_description(Some("Updating voiceovers info...")); - - for package in &*self.widgets.voieover_components { - package.update_state(&config.game.path); - } - - // Update game version - status_page.set_description(Some("Updating game info...")); - - self.widgets.game_version.set_tooltip_text(None); - self.widgets.patch_version.set_tooltip_text(None); - - match game.try_get_diff()? { - VersionDiff::Latest(version) => { - self.widgets.game_version.set_label(&version.to_string()); - } - - VersionDiff::Predownload { current, latest, .. } => { - self.widgets.game_version.set_label(¤t.to_string()); - self.widgets.game_version.set_css_classes(&["accent"]); - - self.widgets.game_version.set_tooltip_text(Some(&format!("Game update pre-downloading available: {} -> {}", current, latest))); - } - - VersionDiff::Diff { current, latest, .. } => { - self.widgets.game_version.set_label(¤t.to_string()); - self.widgets.game_version.set_css_classes(&["warning"]); - - self.widgets.game_version.set_tooltip_text(Some(&format!("Game update available: {} -> {}", current, latest))); - } - - VersionDiff::Outdated { current, latest } => { - self.widgets.game_version.set_label(¤t.to_string()); - self.widgets.game_version.set_css_classes(&["error"]); - - self.widgets.game_version.set_tooltip_text(Some(&format!("Game is too outdated and can't be updated. Latest version: {latest}"))); - } - - VersionDiff::NotInstalled { .. } => { - self.widgets.game_version.set_label("not installed"); - self.widgets.game_version.set_css_classes(&[]); - } - } - - // Update patch version - status_page.set_description(Some("Updating patch info...")); - - let patch = Patch::try_fetch(config.patch.servers, consts::PATCH_FETCHING_TIMEOUT)?; - - match patch { - Patch::NotAvailable => { - self.widgets.patch_version.set_label("not available"); - self.widgets.patch_version.set_css_classes(&["error"]); - - self.widgets.patch_version.set_tooltip_text(Some("Patch is not available")); - } - - Patch::Outdated { current, latest, .. } => { - self.widgets.patch_version.set_label(&format!("outdated ({})", current)); - self.widgets.patch_version.set_css_classes(&["warning"]); - - self.widgets.patch_version.set_tooltip_text(Some(&format!("Patch is outdated ({current} -> {latest})"))); - } - - Patch::Preparation { .. } => { - self.widgets.patch_version.set_label("preparation"); - self.widgets.patch_version.set_css_classes(&["warning"]); - - self.widgets.patch_version.set_tooltip_text(Some("Patch is in preparation state and will be available later")); - } - - Patch::Testing { version, .. } => { - self.widgets.patch_version.set_label(&version.to_string()); - self.widgets.patch_version.set_css_classes(&["warning"]); - - self.widgets.patch_version.set_tooltip_text(Some("Patch is in testing phase")); - } - - Patch::Available { version, .. } => { - self.widgets.patch_version.set_label(&version.to_string()); - - if let Ok(true) = patch.is_applied(&config.game.path) { - self.widgets.patch_version.set_css_classes(&["success"]); - } - - else { - self.widgets.patch_version.set_css_classes(&["warning"]); - self.widgets.patch_version.set_tooltip_text(Some("Patch is not applied")); + add_suffix = >k::Label { + set_text: "3.3.0", + add_css_class: "success" + } } } } - - // Update downloaded wine versions - self.update(Actions::UpdateWineComboRow).unwrap(); - - // Update downloaded DXVK versions - self.update(Actions::UpdateDxvkComboRow).unwrap(); - - Ok(()) } } - -impl Toast for App { - fn get_toast_widgets(&self) -> (adw::ApplicationWindow, adw::ToastOverlay) { - let app = (*self.app).take(); - self.app.set(app.clone()); - - app.unwrap().get_toast_widgets() - } -} - -unsafe impl Send for App {} -unsafe impl Sync for App {} diff --git a/src/ui/preferences/main.rs b/src/ui/preferences/main.rs new file mode 100644 index 0000000..7f35973 --- /dev/null +++ b/src/ui/preferences/main.rs @@ -0,0 +1,62 @@ +use relm4::prelude::*; + +use gtk::prelude::*; +use adw::prelude::*; + +use crate::i18n::tr; + +pub struct App; + +#[derive(Debug)] +pub enum AppMsg { + Test +} + +#[relm4::component(pub)] +impl SimpleComponent for App { + type Init = gtk::Window; + type Input = AppMsg; + type Output = (); + + view! { + preferences_window = adw::PreferencesWindow { + set_title: Some(&tr("preferences")), + set_default_size: (700, 560), + set_hide_on_close: true, + set_modal: true, + + #[template] + add = &super::general::General, + + #[template] + add = &super::enhancements::Enhancements, + + connect_close_request => |_| { + anime_launcher_sdk::config::flush().unwrap(); // FIXME + + gtk::Inhibit::default() + } + } + } + + fn init( + parent: Self::Init, + root: &Self::Root, + _sender: ComponentSender, + ) -> ComponentParts { + let model = App; + let widgets = view_output!(); + + widgets.preferences_window.set_transient_for(Some(&parent)); + + ComponentParts { model, widgets } + } + + fn update(&mut self, msg: Self::Input, _sender: ComponentSender) { + match msg { + AppMsg::Test => { + println!("sus"); + } + } + } +} diff --git a/src/ui/preferences/mod.rs b/src/ui/preferences/mod.rs index 51b2481..1fa6732 100644 --- a/src/ui/preferences/mod.rs +++ b/src/ui/preferences/mod.rs @@ -1,102 +1,4 @@ -use gtk::prelude::*; - -use gtk::glib; - -use std::rc::Rc; -use std::cell::Cell; - -use crate::ui::*; -use crate::ui::traits::prelude::*; +pub mod main; mod general; mod enhancements; -mod environment; - -pub mod gamescope; - -pub mod pages { - pub use super::general::App as GeneralPage; - pub use super::enhancements::App as EnhancementsPage; - pub use super::environment::App as EnvironmentPage; -} - -#[derive(Clone, glib::Downgrade)] -pub struct PreferencesStack { - pub app: Rc>>, - - pub preferences: gtk::Box, - pub preferences_go_back: gtk::Button, - - pub status_page: adw::StatusPage, - pub flap: adw::Flap, - - pub stack: gtk::Stack, - - pub general_page: pages::GeneralPage, - pub enhancements_page: pages::EnhancementsPage, - pub environment_page: pages::EnvironmentPage -} - -impl PreferencesStack { - pub fn new(window: &adw::ApplicationWindow) -> anyhow::Result { - let builder = gtk::Builder::from_resource("/org/app/ui/preferences.ui"); - - let result = Self { - app: Default::default(), - - preferences: get_object(&builder, "preferences")?, - preferences_go_back: get_object(&builder, "preferences_go_back")?, - - status_page: get_object(&builder, "status_page")?, - flap: get_object(&builder, "flap")?, - - stack: get_object(&builder, "stack")?, - - general_page: pages::GeneralPage::new()?, - enhancements_page: pages::EnhancementsPage::new(window)?, - environment_page: pages::EnvironmentPage::new()? - }; - - result.stack.add_titled(&result.general_page.get_page(), None, &pages::GeneralPage::title()); - result.stack.add_titled(&result.enhancements_page.get_page(), None, &pages::EnhancementsPage::title()); - result.stack.add_titled(&result.environment_page.get_page(), None, &pages::EnvironmentPage::title()); - - Ok(result) - } - - pub fn set_app(&mut self, app: super::MainApp) { - self.app.set(Some(app.clone())); - - self.general_page.set_app(app); - } - - /// Update page info before opening it - /// - /// Being called from the `MainApp` struct - pub fn update(&self) -> anyhow::Result<()> { - self.status_page.show(); - self.status_page.set_description(None); - self.flap.hide(); - - self.general_page.prepare(&self.status_page)?; - self.enhancements_page.prepare(&self.status_page)?; - self.environment_page.prepare(&self.status_page)?; - - self.status_page.hide(); - self.flap.show(); - - Ok(()) - } -} - -impl Toast for PreferencesStack { - fn get_toast_widgets(&self) -> (adw::ApplicationWindow, adw::ToastOverlay) { - let app = (*self.app).take(); - self.app.set(app.clone()); - - app.unwrap().get_toast_widgets() - } -} - -unsafe impl Send for PreferencesStack {} -unsafe impl Sync for PreferencesStack {} diff --git a/src/ui/traits/download_component.rs b/src/ui/traits/download_component.rs deleted file mode 100644 index 92fd557..0000000 --- a/src/ui/traits/download_component.rs +++ /dev/null @@ -1,113 +0,0 @@ -use gtk::prelude::*; - -use gtk::glib; - -use std::path::{Path, PathBuf}; - -use anime_game_core::prelude::*; -use wait_not_await::Await; - -use crate::lib::config; - -#[derive(Debug)] -pub enum DownloadingResult { - DownloadingError(DownloadingError), - UnpackingError(String), - Done -} - -pub trait DownloadComponent { - fn get_component_path>(&self, installation_path: T) -> PathBuf; - fn get_downloading_widgets(&self) -> (gtk::ProgressBar, gtk::Button); - fn get_download_uri(&self) -> String; - - fn is_downloaded>(&self, installation_path: T) -> bool { - Path::new(&self.get_component_path(installation_path)).exists() - } - - fn download>(&self, installation_path: T) -> anyhow::Result> { - let (sender, receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); - let (progress_bar, button) = self.get_downloading_widgets(); - - progress_bar.set_visible(true); - button.set_visible(false); - - let (downl_send, downl_recv) = std::sync::mpsc::channel(); - - receiver.attach(None, move |state| { - match state { - InstallerUpdate::DownloadingStarted(_) => (), - InstallerUpdate::DownloadingFinished => (), - InstallerUpdate::UnpackingStarted(_) => (), - - InstallerUpdate::CheckingFreeSpace(_) => { - progress_bar.set_text(Some("Checking free space...")); - } - - InstallerUpdate::DownloadingProgress(curr, total) => { - let progress = curr as f64 / total as f64; - - progress_bar.set_fraction(progress); - progress_bar.set_text(Some(&format!("Downloading: {}%", (progress * 100.0) as u64))); - } - - InstallerUpdate::UnpackingProgress(curr, total) => { - let progress = curr as f64 / total as f64; - - progress_bar.set_fraction(progress); - progress_bar.set_text(Some(&format!("Unpacking: {}%", (progress * 100.0) as u64))); - } - - InstallerUpdate::UnpackingFinished => { - progress_bar.set_visible(false); - button.set_visible(true); - - downl_send.send(DownloadingResult::Done).unwrap(); - } - - InstallerUpdate::DownloadingError(err) => { - downl_send.send(DownloadingResult::DownloadingError(err)).unwrap(); - } - - InstallerUpdate::UnpackingError(err) => { - downl_send.send(DownloadingResult::UnpackingError(err)).unwrap(); - } - } - - glib::Continue(true) - }); - - let (send, recv) = std::sync::mpsc::channel(); - let config = config::get()?; - - let mut installer = Installer::new(self.get_download_uri())?; - - if let Some(temp_folder) = config.launcher.temp { - installer.temp_folder = temp_folder; - } - - installer.downloader - .set_downloading_speed(config.launcher.speed_limit) - .expect("Failed to set downloading speed limit"); - - send.send(installer).unwrap(); - - let installation_path = installation_path.into(); - - std::thread::spawn(move || { - let mut installer = recv.recv().unwrap(); - - installer.install(installation_path, move |state| { - sender.send(state).unwrap(); - }); - }); - - Ok(Await::new(move || { - downl_recv.recv().unwrap() - })) - } - - fn delete>(&self, installation_path: T) -> std::io::Result<()> { - std::fs::remove_dir_all(self.get_component_path(installation_path)) - } -} diff --git a/src/ui/traits/mod.rs b/src/ui/traits/mod.rs deleted file mode 100644 index e79dc7a..0000000 --- a/src/ui/traits/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod toast; -pub mod download_component; - -pub mod prelude { - pub use super::toast::*; - pub use super::download_component::*; -} diff --git a/src/ui/traits/toast.rs b/src/ui/traits/toast.rs deleted file mode 100644 index a1aaebc..0000000 --- a/src/ui/traits/toast.rs +++ /dev/null @@ -1,43 +0,0 @@ -use gtk::prelude::*; - -use crate::ui::add_action; - -pub trait Toast { - fn get_toast_widgets(&self) -> (adw::ApplicationWindow, adw::ToastOverlay); - - /// Show toast with `toast` title and `See message` button - /// - /// This button will show message dialog with some message - fn toast(&self, toast: T, message: F) { - let toast = adw::Toast::new(toast.to_string().as_str()); - let (window, toast_overlay) = self.get_toast_widgets(); - - toast.set_timeout(0); - - let message = format!("{}", message); - - if !message.is_empty() { - toast.set_button_label(Some("See message")); - toast.set_action_name(Some("see-message.see-message")); - - // Show message in a dialog window - add_action(&toast_overlay, "see-message", move || { - let dialog = gtk::MessageDialog::new( - Some(&window), - gtk::DialogFlags::all(), - gtk::MessageType::Info, - gtk::ButtonsType::Close, - &message - ); - - dialog.connect_response(move |dialog, _| { - dialog.close(); - }); - - dialog.show(); - }); - } - - toast_overlay.add_toast(&toast); - } -}