diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..40d9aca
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+/.idea
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..304188d
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,2299 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
+[[package]]
+name = "async-channel"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "backtrace"
+version = "0.3.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "bstr"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
+dependencies = [
+ "byteorder",
+ "iovec",
+]
+
+[[package]]
+name = "bytes"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+
+[[package]]
+name = "cairo-rs"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "797fd5a634dcb0ad0d7d583df794deb0a236d88e759cd34b7da20198c6c9d145"
+dependencies = [
+ "bitflags 2.6.0",
+ "cairo-sys-rs",
+ "glib 0.20.0",
+ "libc",
+ "thiserror",
+]
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "428290f914b9b86089f60f5d8a9f6e440508e1bcff23b25afd51502b0a2da88f"
+dependencies = [
+ "glib-sys 0.20.0",
+ "libc",
+ "system-deps 7.0.1",
+]
+
+[[package]]
+name = "cc"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8"
+
+[[package]]
+name = "cfg-expr"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
+dependencies = [
+ "smallvec 1.13.2",
+ "target-lexicon",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils 0.8.20",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils 0.8.20",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils 0.8.20",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
+dependencies = [
+ "autocfg",
+ "cfg-if 0.1.10",
+ "lazy_static",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+
+[[package]]
+name = "cxx"
+version = "1.0.124"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "273dcfd3acd4e1e276af13ed2a43eea7001318823e7a726a6b3ed39b4acc0b82"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.124"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b2766fbd92be34e9ed143898fce6c572dc009de39506ed6903e5a05b68914e"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.124"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "839fcd5e43464614ffaa989eaf1c139ef1f0c51672a1ed08023307fa1b909ccd"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.124"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "field-offset"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
+dependencies = [
+ "memoffset",
+ "rustc_version 0.4.0",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+dependencies = [
+ "bitflags 1.3.2",
+ "fuchsia-zircon-sys",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+
+[[package]]
+name = "futures"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "gdk-pixbuf"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28bb53ecb56857c683c9ec859908e076dd3969c7d67598bd8b1ce095d211304a"
+dependencies = [
+ "gdk-pixbuf-sys",
+ "gio 0.20.0",
+ "glib 0.20.0",
+ "libc",
+]
+
+[[package]]
+name = "gdk-pixbuf-sys"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f6681a0c1330d1d3968bec1529f7172d62819ef0bdbb0d18022320654158b03"
+dependencies = [
+ "gio-sys 0.20.0",
+ "glib-sys 0.20.0",
+ "gobject-sys 0.20.0",
+ "libc",
+ "system-deps 7.0.1",
+]
+
+[[package]]
+name = "gdk4"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b7d7237c1487ed4b300aac7744efcbf1319e12d60d7afcd6f505414bd5b5dea"
+dependencies = [
+ "cairo-rs",
+ "gdk-pixbuf",
+ "gdk4-sys",
+ "gio 0.20.0",
+ "glib 0.20.0",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gdk4-sys"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a67576c8ec012156d7f680e201a807b4432a77babb3157e0555e990ab6bcd878"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gio-sys 0.20.0",
+ "glib-sys 0.20.0",
+ "gobject-sys 0.20.0",
+ "libc",
+ "pango-sys",
+ "pkg-config",
+ "system-deps 7.0.1",
+]
+
+[[package]]
+name = "getopts"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "gimli"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
+
+[[package]]
+name = "gio"
+version = "0.19.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c49f117d373ffcc98a35d114db5478bc223341cff53e39a5d6feced9e2ddffe"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "gio-sys 0.19.8",
+ "glib 0.19.9",
+ "libc",
+ "pin-project-lite",
+ "smallvec 1.13.2",
+ "thiserror",
+]
+
+[[package]]
+name = "gio"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "398e3da68749fdc32783cbf7521ec3f65c9cf946db8c7774f8460af49e52c6e2"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "gio-sys 0.20.0",
+ "glib 0.20.0",
+ "libc",
+ "pin-project-lite",
+ "smallvec 1.13.2",
+ "thiserror",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.19.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cd743ba4714d671ad6b6234e8ab2a13b42304d0e13ab7eba1dcdd78a7d6d4ef"
+dependencies = [
+ "glib-sys 0.19.8",
+ "gobject-sys 0.19.8",
+ "libc",
+ "system-deps 6.2.2",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4feb96b31c32730ea3e1e89aecd2e4e37ecb1c473ad8f685e3430a159419f63"
+dependencies = [
+ "glib-sys 0.20.0",
+ "gobject-sys 0.20.0",
+ "libc",
+ "system-deps 7.0.1",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "glib"
+version = "0.19.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39650279f135469465018daae0ba53357942a5212137515777d5fdca74984a44"
+dependencies = [
+ "bitflags 2.6.0",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys 0.19.8",
+ "glib-macros 0.19.9",
+ "glib-sys 0.19.8",
+ "gobject-sys 0.19.8",
+ "libc",
+ "memchr",
+ "smallvec 1.13.2",
+ "thiserror",
+]
+
+[[package]]
+name = "glib"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fee90a615ce05be7a32932cfb8adf2c4bbb4700e80d37713c981fb24c0c56238"
+dependencies = [
+ "bitflags 2.6.0",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys 0.20.0",
+ "glib-macros 0.20.0",
+ "glib-sys 0.20.0",
+ "gobject-sys 0.20.0",
+ "libc",
+ "memchr",
+ "smallvec 1.13.2",
+ "thiserror",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.19.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4429b0277a14ae9751350ad9b658b1be0abb5b54faa5bcdf6e74a3372582fad7"
+dependencies = [
+ "heck",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4da558d8177c0c8c54368818b508a4244e1286fce2858cef4e547023f0cfa5ef"
+dependencies = [
+ "heck",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.19.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c2dc18d3a82b0006d470b13304fbbb3e0a9bd4884cf985a60a7ed733ac2c4a5"
+dependencies = [
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4958c26e5a01c9af00dea669a97369eccbec29a8e6d125c24ea2d85ee7467b60"
+dependencies = [
+ "libc",
+ "system-deps 7.0.1",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "globset"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
+dependencies = [
+ "bitflags 1.3.2",
+ "ignore",
+ "walkdir",
+]
+
+[[package]]
+name = "gobject-sys"
+version = "0.19.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e697e252d6e0416fd1d9e169bda51c0f1c926026c39ca21fbe8b1bb5c3b8b9e"
+dependencies = [
+ "glib-sys 0.19.8",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "gobject-sys"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6908864f5ffff15b56df7e90346863904f49b949337ed0456b9287af61903b8"
+dependencies = [
+ "glib-sys 0.20.0",
+ "libc",
+ "system-deps 7.0.1",
+]
+
+[[package]]
+name = "graphene-rs"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "630e940ad5824f90221d6579043a9cd1f8bec86b4a17faaf7827d58eb16e8c1f"
+dependencies = [
+ "glib 0.20.0",
+ "graphene-sys",
+ "libc",
+]
+
+[[package]]
+name = "graphene-sys"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb8fade7b754982f47ebbed241fd2680816fdd4598321784da10b9e1168836a"
+dependencies = [
+ "glib-sys 0.20.0",
+ "libc",
+ "pkg-config",
+ "system-deps 7.0.1",
+]
+
+[[package]]
+name = "gsk4"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3cf2091e1af185b347b3450817d93dea6fe435df7abd4c2cd7fb5bcb4cfda8"
+dependencies = [
+ "cairo-rs",
+ "gdk4",
+ "glib 0.20.0",
+ "graphene-rs",
+ "gsk4-sys",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gsk4-sys"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6aa69614a26d8760c186c3690f1b0fbb917572ca23ef83137445770ceddf8cde"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk4-sys",
+ "glib-sys 0.20.0",
+ "gobject-sys 0.20.0",
+ "graphene-sys",
+ "libc",
+ "pango-sys",
+ "system-deps 7.0.1",
+]
+
+[[package]]
+name = "gtk4"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaffc6c743c9160514cc9b67eace364e5dc5798369fa809cdb04e035c21c5c5d"
+dependencies = [
+ "cairo-rs",
+ "field-offset",
+ "futures-channel",
+ "gdk-pixbuf",
+ "gdk4",
+ "gio 0.20.0",
+ "glib 0.20.0",
+ "graphene-rs",
+ "gsk4",
+ "gtk4-macros",
+ "gtk4-sys",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gtk4-macros"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "188211f546ce5801f6d0245c37b6249143a2cb4fa040e54829ca1e76796e9f09"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "gtk4-sys"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1114a207af8ada02cf4658a76692f4190f06f093380d5be07e3ca8b43aa7c666"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk4-sys",
+ "gio-sys 0.20.0",
+ "glib-sys 0.20.0",
+ "gobject-sys 0.20.0",
+ "graphene-sys",
+ "gsk4-sys",
+ "libc",
+ "pango-sys",
+ "system-deps 7.0.1",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.5",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libadwaita"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ff9c222b5c783729de45185f07b2fec2d43a7f9c63961e777d3667e20443878"
+dependencies = [
+ "gdk4",
+ "gio 0.20.0",
+ "glib 0.20.0",
+ "gtk4",
+ "libadwaita-sys",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "libadwaita-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c44d8bdbad31d6639e1f20cc9c1424f1a8e02d751fc28d44659bf743fb9eca6"
+dependencies = [
+ "gdk4-sys",
+ "gio-sys 0.20.0",
+ "glib-sys 0.20.0",
+ "gobject-sys 0.20.0",
+ "gtk4-sys",
+ "libc",
+ "pango-sys",
+ "system-deps 7.0.1",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "libflatpak"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a0466e5bc49c8a9d03c1daf924833474640bf3b334b01efde4234e77efe9780"
+dependencies = [
+ "gio 0.19.8",
+ "glib 0.19.9",
+ "libc",
+ "libflatpak-sys",
+]
+
+[[package]]
+name = "libflatpak-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0022c1b20fa3959a52299cd531c6afc9433182f612b1210f18776a7857a58f2b"
+dependencies = [
+ "gio-sys 0.19.8",
+ "glib-sys 0.19.8",
+ "gobject-sys 0.19.8",
+ "libc",
+ "pkg-config",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
+[[package]]
+name = "lock_api"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "maybe-uninit"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "fuchsia-zircon",
+ "fuchsia-zircon-sys",
+ "iovec",
+ "kernel32-sys",
+ "libc",
+ "log",
+ "miow",
+ "net2",
+ "slab",
+ "winapi 0.2.8",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "mio-uds"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
+dependencies = [
+ "iovec",
+ "libc",
+ "mio 0.6.23",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
+dependencies = [
+ "kernel32-sys",
+ "net2",
+ "winapi 0.2.8",
+ "ws2_32-sys",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "normpath"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5831952a9476f2fed74b77d74182fa5ddc4d21c72ec45a333b250e3ed0272804"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi 0.3.9",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "pango"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54768854025df6903061d0084fd9702a253ddfd60db7d9b751d43b76689a7f0a"
+dependencies = [
+ "gio 0.20.0",
+ "glib 0.20.0",
+ "libc",
+ "pango-sys",
+]
+
+[[package]]
+name = "pango-sys"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b07cc57d10cee4ec661f718a6902cee18c2f4cfae08e87e5a390525946913390"
+dependencies = [
+ "glib-sys 0.20.0",
+ "gobject-sys 0.20.0",
+ "libc",
+ "system-deps 7.0.1",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
+
+[[package]]
+name = "parking_lot"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
+dependencies = [
+ "lock_api 0.3.4",
+ "parking_lot_core 0.6.3",
+ "rustc_version 0.2.3",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api 0.4.12",
+ "parking_lot_core 0.9.10",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66b810a62be75176a80873726630147a5ca780cd33921e0b5709033e66b0a"
+dependencies = [
+ "cfg-if 0.1.10",
+ "cloudabi",
+ "libc",
+ "redox_syscall 0.1.57",
+ "rustc_version 0.2.3",
+ "smallvec 0.6.14",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall 0.5.2",
+ "smallvec 1.13.2",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pikman-update-manager"
+version = "0.1.0"
+dependencies = [
+ "async-channel",
+ "async-trait",
+ "chrono",
+ "crossbeam-utils 0.8.20",
+ "futures 0.3.30",
+ "gtk4",
+ "libadwaita",
+ "libflatpak",
+ "lock_api 0.4.12",
+ "pretty-bytes",
+ "rust-apt",
+ "rust-i18n",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tokio-uds",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "pretty-bytes"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "009d6edd2c1dbf2e1c0cd48a2f7766e03498d49ada7109a01c6911815c685316"
+dependencies = [
+ "atty",
+ "getopts",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
+dependencies = [
+ "toml_edit 0.21.1",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
+dependencies = [
+ "bitflags 2.6.0",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
+name = "rust-apt"
+version = "0.7.0"
+source = "git+https://gitlab.com/volian/rust-apt#89f7f127b3af5595bec61ab520a813d8bf9fc502"
+dependencies = [
+ "cxx",
+ "cxx-build",
+ "paste",
+ "terminal_size",
+]
+
+[[package]]
+name = "rust-i18n"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dcd94370631e5658a0a23635f7f47e43d06a00ad948e0bb5de79b00d85b880c"
+dependencies = [
+ "globwalk",
+ "once_cell",
+ "regex",
+ "rust-i18n-macro",
+ "rust-i18n-support",
+ "smallvec 1.13.2",
+]
+
+[[package]]
+name = "rust-i18n-macro"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "355763801dcf287e777e42def7c578410783477b804b1107852119e0b2518396"
+dependencies = [
+ "glob",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "rust-i18n-support",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "syn",
+]
+
+[[package]]
+name = "rust-i18n-support"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "399801f4d955abf1c3ce3ce2215dc76bd40beb4ae39e3a84936b21a79ce2caa5"
+dependencies = [
+ "arc-swap",
+ "globwalk",
+ "lazy_static",
+ "normpath",
+ "once_cell",
+ "proc-macro2",
+ "regex",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "toml 0.7.8",
+ "triomphe",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver 0.9.0",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver 1.0.23",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags 2.6.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[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 = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "scratch"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "serde"
+version = "1.0.204"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.204"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.120"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
+dependencies = [
+ "indexmap 1.9.3",
+ "ryu",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "0.6.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
+dependencies = [
+ "maybe-uninit",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "socket2"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "syn"
+version = "2.0.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "system-deps"
+version = "6.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
+dependencies = [
+ "cfg-expr",
+ "heck",
+ "pkg-config",
+ "toml 0.8.14",
+ "version-compare",
+]
+
+[[package]]
+name = "system-deps"
+version = "7.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c81f13d9a334a6c242465140bd262fae382b752ff2011c4f7419919a9c97922"
+dependencies = [
+ "cfg-expr",
+ "heck",
+ "pkg-config",
+ "toml 0.8.14",
+ "version-compare",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2"
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
+dependencies = [
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio"
+version = "1.38.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
+dependencies = [
+ "backtrace",
+ "bytes 1.6.0",
+ "libc",
+ "mio 0.8.11",
+ "num_cpus",
+ "parking_lot 0.12.3",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-codec"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.31",
+ "tokio-io",
+]
+
+[[package]]
+name = "tokio-executor"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671"
+dependencies = [
+ "crossbeam-utils 0.7.2",
+ "futures 0.1.31",
+]
+
+[[package]]
+name = "tokio-io"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.31",
+ "log",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-reactor"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351"
+dependencies = [
+ "crossbeam-utils 0.7.2",
+ "futures 0.1.31",
+ "lazy_static",
+ "log",
+ "mio 0.6.23",
+ "num_cpus",
+ "parking_lot 0.9.0",
+ "slab",
+ "tokio-executor",
+ "tokio-io",
+ "tokio-sync",
+]
+
+[[package]]
+name = "tokio-sync"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee"
+dependencies = [
+ "fnv",
+ "futures 0.1.31",
+]
+
+[[package]]
+name = "tokio-uds"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0"
+dependencies = [
+ "bytes 0.4.12",
+ "futures 0.1.31",
+ "iovec",
+ "libc",
+ "log",
+ "mio 0.6.23",
+ "mio-uds",
+ "tokio-codec",
+ "tokio-io",
+ "tokio-reactor",
+]
+
+[[package]]
+name = "toml"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit 0.19.15",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit 0.22.15",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap 2.2.6",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow 0.5.40",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
+dependencies = [
+ "indexmap 2.2.6",
+ "toml_datetime",
+ "winnow 0.5.40",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1"
+dependencies = [
+ "indexmap 2.2.6",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow 0.6.13",
+]
+
+[[package]]
+name = "triomphe"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369"
+dependencies = [
+ "arc-swap",
+ "serde",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
+
+[[package]]
+name = "version-compare"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "winnow"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..8ef248e
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,40 @@
+[package]
+name = "pikman-update-manager"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lib]
+name = "pika_unixsocket_tools"
+path = "src/lib/lib.rs"
+
+[[bin]]
+name = "pikman-update-manager"
+path = "src/bin/gui/main.rs"
+
+[[bin]]
+name = "apt_update"
+path = "src/bin/apt/apt_update/main.rs"
+
+[[bin]]
+name = "apt_full_upgrade"
+path = "src/bin/apt/apt_full_upgrade/main.rs"
+
+[dependencies]
+adw = { version = "0.7.0", package = "libadwaita", features = ["v1_4"] }
+gtk = { version = "0.9.0", package = "gtk4", features = ["v4_12"] }
+async-channel = "2.1.1"
+rust-i18n = "3.0.1"
+rust-apt = { git = "https://gitlab.com/volian/rust-apt" }
+tokio = { version = "1", features = ["full"] }
+tokio-uds = "0.2"
+serde = { version = "1.0.203", features = ["derive"] }
+serde_json = "1.0.118"
+async-trait = "0.1.80"
+futures = "0.3.30"
+pretty-bytes = "0.2.2"
+crossbeam-utils = "0.8.20"
+chrono = "0.4.38"
+lock_api = "0.4.2"
+libflatpak = { version = "0.5.0", package = "libflatpak", features = ["v1_11_1"] }
+
diff --git a/data/com.github.pikaos-linux.pikmanupdatemanager.gschema.xml b/data/com.github.pikaos-linux.pikmanupdatemanager.gschema.xml
new file mode 100644
index 0000000..033a9b9
--- /dev/null
+++ b/data/com.github.pikaos-linux.pikmanupdatemanager.gschema.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ 1400
+ Default window width
+
+
+ 700
+ Default window height
+
+
+ false
+ Default window maximized behaviour
+
+
+ true
+ Show PikaOS Welcome on startup.
+
+ Show PikaOS Welcome on startup.
+
+
+
+
\ No newline at end of file
diff --git a/just_in_case/__main__.py b/just_in_case/__main__.py
new file mode 100755
index 0000000..5d6f26f
--- /dev/null
+++ b/just_in_case/__main__.py
@@ -0,0 +1,91 @@
+#! /bin/python3
+
+import socket
+import os
+
+import sys
+import time
+
+import apt_pkg
+
+import apt
+import apt.progress.base
+
+def get_change(current, total):
+ if current == total:
+ return 100.0
+ try:
+ return float("{:.1f}".format(((current * 100) / total)))
+ except ZeroDivisionError:
+ return 0.0
+
+class UpdateProgressSocket(apt.progress.base.AcquireProgress):
+ # Init
+ def __init__(self):
+ pass
+
+ # Start
+ def start(self):
+ self.current_bytes = 0
+ self.total_bytes = 0
+ print("Starting APT Cache Update.")
+ return super().start()
+
+ # Stop
+ def stop(self):
+ print("\nAPT Cache Update Complete!")
+ return super().stop()
+
+ # Progrss pulse
+ def pulse(self, owner):
+ # Calculate current progress percentage
+ progress_percent = get_change(self.current_bytes, self.total_bytes)
+
+ # apt_update_progress ipc sock
+ socket_path = "/tmp/pika_apt_update.sock"
+
+ # Create a Unix domain socket
+ with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
+ client.connect(socket_path)
+ # Send percentage to socket as UTF-8
+ client.sendall(str(progress_percent).encode('utf-8'))
+
+ #response = client.recv(1024)
+ #print(f"Received: {response.decode('utf-8')}")
+
+ return True
+
+
+ def fail(self, item):
+ print("Failure at: %s %s" % (item.uri, item.shortdesc))
+
+ def fetch(self, item):
+ print("Fetch: %s %s" % (item.uri, item.shortdesc))
+
+ def ims_hit(self, item):
+ print("Download: %s %s" % (item.uri, item.shortdesc))
+
+ def media_change(self, medium, drive):
+ print(f"Please insert medium {medium} in drive {drive}")
+ sys.stdin.readline()
+ # return False
+
+def update_cache():
+ # First of all, open the cache
+ cache = apt.Cache()
+ # Now, lets update the package list
+ cache.update(UpdateProgressSocket())
+ # We need to re-open the cache because it needs to read the package list
+ cache.open(None)
+ # We need to re-open the cache because it needs to read the package list
+ for pkg in cache:
+ if pkg.is_upgradable:
+ print(f"{pkg.name} ({pkg.installed.version} -> {pkg.candidate.version})")
+
+def process(data):
+ # Echo the input data
+ return data
+
+if __name__ == "__main__":
+
+ update_cache()
diff --git a/locales/en_US.json b/locales/en_US.json
new file mode 100644
index 0000000..8eda58c
--- /dev/null
+++ b/locales/en_US.json
@@ -0,0 +1,84 @@
+{
+ "application_name": "Pikman Update Manager",
+ "developer_name": "Cosmo",
+ "installed_version_badge_text": "Installed",
+ "candidate_version_badge_text": "Upgradable",
+ "arch_label_label": "Arch",
+ "mark_for_update": "Mark This Package for Upgrade",
+ "description_button_label": "Description",
+ "extra_info_page_button_label": "Additional Info",
+ "uris_page_button_label": "Download URIs",
+ "changelog_page_button_label": "Changelog (todo)",
+ "extra_info_maintainer": "Maintainer",
+ "extra_info_download_size": "Download Size",
+ "extra_info_installed_size": "Size on Disk",
+ "update_status_error_perms": "Unknown Error! (Likely Permission Denied)",
+ "apt_update_dialog_heading": "APT Cache Update",
+ "apt_update_dialog_retry_label": "Retry",
+ "select_button_deselect_all": "De-Select All",
+ "select_button_select_all": "Select All",
+ "update_button_label": "Commit Upgrade",
+ "installed_version_to_be_installed": "Not Installed Yet",
+ "apt_pkg_property_unknown": "Unknown",
+ "apt_update_dialog_status_failed": "APT Cache Update: Failed!",
+ "excluded_updates_alert_dialog_heading": "Alert: Partial Upgrade",
+ "excluded_updates_alert_dialog_body": "It seems you have deselected some upgrades.\nThis is generally not recommended, and APT partial update security systems might not respect your input.",
+ "excluded_updates_alert_dialog_cancel_label": "Cancel",
+ "excluded_updates_alert_continue_label": "Continue",
+ "gui_changes_emu_msg_0": "The following opreations will be sent to the root process:",
+ "gui_changes_emu_msg_upgrading": "Upgrading",
+ "gui_changes_emu_msg_installing": "Installing",
+ "gui_changes_emu_msg_downgrading": "Downgrading",
+ "gui_changes_emu_msg_removing": "Uninstalling",
+ "package_count_upgrade_badge_label": "Packages to Upgrade",
+ "package_count_install_badge_label": "New Packages to Install",
+ "package_count_downgrade_badge_label": "Packages to Downgrade",
+ "package_count_remove_badge_label": "Packages to Uninstall",
+ "total_download_size_badge_label": "Total Download Size",
+ "total_installed_size_badge_label": "Total Size on Disk",
+ "apt_confirm_dialog_heading": "APT Upgrade Transaction: Please Review",
+ "apt_confirm_dialog_body": "The following changes are going to be made:",
+ "apt_confirm_dialog_cancel_label": "Decline",
+ "apt_confirm_dialog_confirm_label": "Confirm & Accept",
+ "apt_remove_confirm_dialog_heading": "APT Removal Confirmation: Please Review",
+ "apt_remove_confirm_dialog_body": "The Upgrade wants to remove the following packages: (This Might be extremely Dangerous!)",
+ "apt_remove_confirm_dialog_cancel_label": "Cancel",
+ "apt_remove_confirm_dialog_confirm_label": "Allow & Confirm",
+ "upgrade_status_error_perms": "Unknown Error! (Likely Permission Denied)",
+ "apt_upgrade_dialog_heading": "APT Upgrade Transaction: Hang Tight!",
+ "apt_upgrade_dialog_ok_label": "OK",
+ "apt_upgrade_dialog_open_log_file_label": "Open Apt Log File",
+ "apt_upgrade_dialog_status_successful": "APT Upgrade Transaction Successful!",
+ "apt_upgrade_dialog_status_failed": "APT Upgrade Transaction Failed!",
+ "banner_text_no_internet": "Warning: No Internet Connection!",
+ "refresh_button_tooltip_text": "Refresh Opened Page",
+ "apt_update_page_title": "Native Updates (APT)",
+ "flatpak_update_page_title" : "Flatpak Updates",
+ "apt_packages_no_viewport_page_title": "All Native APT Packages are Up to date!",
+ "summary_button_label": "Summary",
+ "flatpak_extra_info_ref_name": "Ref Codename",
+ "flatpak_extra_info_download_size": "Download Size",
+ "flatpak_extra_info_installed_size": "Size on Disk",
+ "remote_label_label": "Remote",
+ "flatpak_update_dialog_heading": "Flatpak Appstream Sync",
+ "flatpak_update_dialog_retry_label": "Retry",
+ "flatpak_update_dialog_status_failed": "Flatpak Appstream Sync: Failed",
+ "flatpak_packages_no_viewport_page_title": "All Flatpak Packages are Up to date!",
+ "flatpak_type_user": "User",
+ "flatpak_type_system": "System",
+ "flatpak_ref": "Flatpak Ref",
+ "flatpak_status": "Status",
+ "flatpak_transaction_bytes_transferred": "Downloaded",
+ "flatpak_transaction_installed_size": "Saved to Disk",
+ "flatpak_confirm_dialog_body": "The following changes are going to be made:",
+ "system_flatref_count_badge_label": "System Flatpaks to Update",
+ "user_flatref_count_badge_label": "User Flatpaks to Update",
+ "flatpak_confirm_dialog_heading": "Flatpak Update Transaction: Please Review",
+ "flatpak_confirm_dialog_cancel_label": "Decline",
+ "flatpak_confirm_dialog_confirm_label": "Confirm & Accept",
+ "flatpak_transaction_dialog_heading": "Flatpak Update Transaction: Hang Tight!",
+ "flatpak_transaction_dialog_ok_label": "OK",
+ "flatpak_transaction_dialog_open_log_file_label": "Open Flatpak Log File",
+ "flatpak_transaction_dialog_status_successful": "Flatpak Update Transaction: Successful!",
+ "flatpak_transaction_dialog_status_failed": "Flatpak Update Transaction: Failed!"
+}
\ No newline at end of file
diff --git a/src/bin/apt/apt_full_upgrade/main.rs b/src/bin/apt/apt_full_upgrade/main.rs
new file mode 100644
index 0000000..49d0654
--- /dev/null
+++ b/src/bin/apt/apt_full_upgrade/main.rs
@@ -0,0 +1,89 @@
+use pika_unixsocket_tools::apt_install_progress_socket::AptInstallProgressSocket;
+use pika_unixsocket_tools::apt_update_progress_socket::AptUpdateProgressSocket;
+use pika_unixsocket_tools::pika_unixsocket_tools::*;
+use rust_apt::cache::Upgrade;
+use rust_apt::new_cache;
+use rust_apt::progress::{AcquireProgress, InstallProgress};
+use tokio::runtime::Runtime;
+
+fn main() {
+ let percent_socket_path = "/tmp/pika_apt_upgrade_percent.sock";
+ let status_socket_path = "/tmp/pika_apt_upgrade_status.sock";
+ let json_file_path = "/tmp/pika-apt-exclusions.json";
+ let mut excluded_updates_vec: Vec = Vec::new();
+
+ if std::path::Path::new(json_file_path).exists() {
+ let data = std::fs::read_to_string(json_file_path).expect("Unable to read file");
+ let json: serde_json::Value =
+ serde_json::from_str(&data).expect("JSON was not well-formatted");
+
+ if let serde_json::Value::Array(exclusions) = &json["exclusions"] {
+ for exclusion in exclusions {
+ match exclusion["package"].as_str() {
+ Some(v) => {
+ excluded_updates_vec.push(v.to_owned());
+ }
+ None => {}
+ }
+ }
+ }
+ }
+
+ let apt_cache = new_cache!().unwrap();
+
+ apt_cache.upgrade(Upgrade::FullUpgrade).unwrap();
+
+ let apt_upgrade_cache = if excluded_updates_vec.is_empty() {
+ apt_cache
+ } else {
+ let apt_upgrade_cache = new_cache!().unwrap();
+ for change in apt_cache.get_changes(false) {
+ if !excluded_updates_vec
+ .iter()
+ .any(|e| change.name().contains(e))
+ {
+ let pkg = apt_upgrade_cache.get(change.name()).unwrap();
+ if change.marked_upgrade() || change.marked_install() || change.marked_downgrade() {
+ pkg.mark_install(true, false);
+ } else if change.marked_delete() {
+ pkg.mark_delete(false);
+ }
+ pkg.protect();
+ }
+ }
+ apt_upgrade_cache
+ };
+
+ apt_upgrade_cache.resolve(true).unwrap();
+
+ let mut acquire_progress = AcquireProgress::new(AptUpdateProgressSocket::new(
+ percent_socket_path,
+ status_socket_path,
+ ));
+ let mut install_progress = InstallProgress::new(AptInstallProgressSocket::new(
+ percent_socket_path,
+ status_socket_path,
+ ));
+
+ apt_upgrade_cache.resolve(true).unwrap();
+
+ match apt_upgrade_cache.get_archives(&mut acquire_progress) {
+ Ok(_) => {}
+ Err(e) => {
+ panic!("{}", e.to_string())
+ }
+ };
+
+ match apt_upgrade_cache.do_install(&mut install_progress) {
+ Ok(_) => {}
+ Err(e) => {
+ Runtime::new()
+ .unwrap()
+ .block_on(send_failed_to_socket(percent_socket_path));
+ Runtime::new()
+ .unwrap()
+ .block_on(send_failed_to_socket(status_socket_path));
+ panic!("{}", e.to_string())
+ }
+ };
+}
diff --git a/src/bin/apt/apt_update/main.rs b/src/bin/apt/apt_update/main.rs
new file mode 100644
index 0000000..ff6d760
--- /dev/null
+++ b/src/bin/apt/apt_update/main.rs
@@ -0,0 +1,26 @@
+use pika_unixsocket_tools::apt_update_progress_socket::AptUpdateProgressSocket;
+use pika_unixsocket_tools::pika_unixsocket_tools::*;
+use rust_apt::new_cache;
+use rust_apt::progress::AcquireProgress;
+use tokio::runtime::Runtime;
+
+fn main() {
+ let update_cache = new_cache!().unwrap();
+ let percent_socket_path = "/tmp/pika_apt_update_percent.sock";
+ let status_socket_path = "/tmp/pika_apt_update_status.sock";
+ match update_cache.update(&mut AcquireProgress::new(AptUpdateProgressSocket::new(
+ percent_socket_path,
+ status_socket_path,
+ ))) {
+ Ok(_) => {}
+ Err(e) => {
+ Runtime::new()
+ .unwrap()
+ .block_on(send_failed_to_socket(percent_socket_path));
+ Runtime::new()
+ .unwrap()
+ .block_on(send_failed_to_socket(status_socket_path));
+ panic!("{}", e.to_string())
+ }
+ };
+}
diff --git a/src/bin/gui/apt_package_row/imp.rs b/src/bin/gui/apt_package_row/imp.rs
new file mode 100644
index 0000000..330341d
--- /dev/null
+++ b/src/bin/gui/apt_package_row/imp.rs
@@ -0,0 +1,597 @@
+use std::{cell::RefCell, sync::OnceLock};
+
+use adw::*;
+use adw::{prelude::*, subclass::prelude::*};
+use glib::{clone, subclass::Signal, Properties};
+use gtk::*;
+use pretty_bytes::converter::convert;
+use std::env;
+
+// ANCHOR: custom_button
+// Object holding the state
+#[derive(Properties, Default)]
+#[properties(wrapper_type = super::AptPackageRow)]
+pub struct AptPackageRow {
+ #[property(get, set)]
+ package_name: RefCell,
+ #[property(get, set)]
+ package_arch: RefCell,
+ #[property(get, set)]
+ package_installed_version: RefCell,
+ #[property(get, set)]
+ package_candidate_version: RefCell,
+ #[property(get, set)]
+ package_description: RefCell,
+ #[property(get, set)]
+ package_source_uri: RefCell,
+ #[property(get, set)]
+ package_maintainer: RefCell,
+ #[property(get, set)]
+ package_size: RefCell,
+ #[property(get, set)]
+ package_installed_size: RefCell,
+ #[property(get, set)]
+ package_marked: RefCell,
+}
+// ANCHOR_END: custom_button
+
+// The central trait for subclassing a GObject
+#[glib::object_subclass]
+impl ObjectSubclass for AptPackageRow {
+ const NAME: &'static str = "AptPackageRow";
+ type Type = super::AptPackageRow;
+ type ParentType = ExpanderRow;
+}
+
+// ANCHOR: object_impl
+// Trait shared by all GObjects
+#[glib::derived_properties]
+impl ObjectImpl for AptPackageRow {
+ fn signals() -> &'static [Signal] {
+ static SIGNALS: OnceLock> = OnceLock::new();
+ SIGNALS.get_or_init(|| {
+ vec![
+ Signal::builder("checkbutton-toggled").build(),
+ Signal::builder("checkbutton-untoggled").build(),
+ ]
+ })
+ }
+ fn constructed(&self) {
+ let current_locale = match env::var_os("LANG") {
+ Some(v) => v
+ .into_string()
+ .unwrap()
+ .chars()
+ .take_while(|&ch| ch != '.')
+ .collect::(),
+ None => panic!("$LANG is not set"),
+ };
+ rust_i18n::set_locale(¤t_locale);
+
+ self.parent_constructed();
+
+ // Bind label to number
+ // `SYNC_CREATE` ensures that the label will be immediately set
+ let obj = self.obj();
+
+ let prefix_box = Box::new(Orientation::Vertical, 0);
+
+ let expandable_box = Box::new(Orientation::Vertical, 0);
+
+ obj.connect_package_name_notify(clone!(
+ #[weak]
+ prefix_box,
+ #[weak]
+ expandable_box,
+ #[strong]
+ obj,
+ move |_| {
+ remove_all_children_from_box(&prefix_box);
+ remove_all_children_from_box(&expandable_box);
+ //
+ let package_name = obj.package_name();
+ let package_arch = obj.package_arch();
+ let package_installed_version = obj.package_installed_version();
+ let package_candidate_version = obj.package_candidate_version();
+ let package_description = obj.package_description();
+ let package_source_uri = obj.package_source_uri();
+ let package_maintainer = obj.package_maintainer();
+ let package_size = obj.package_size();
+ let package_installed_size = obj.package_installed_size();
+ //
+ create_prefix_content(
+ &prefix_box,
+ &package_name,
+ &package_arch,
+ &package_installed_version,
+ &package_candidate_version,
+ );
+ //
+ create_expandable_content(
+ &obj,
+ &expandable_box,
+ package_description,
+ package_source_uri,
+ package_maintainer,
+ package_size,
+ package_installed_size,
+ );
+ }
+ ));
+
+ obj.add_prefix(&prefix_box);
+ obj.add_row(&expandable_box);
+
+ let suffix_toggle = CheckButton::builder()
+ .tooltip_text(t!("mark_for_update"))
+ .halign(Align::Center)
+ .valign(Align::Center)
+ .hexpand(false)
+ .vexpand(false)
+ .build();
+
+ suffix_toggle.connect_toggled(clone!(
+ #[weak]
+ obj,
+ #[weak]
+ suffix_toggle,
+ move |_| {
+ if suffix_toggle.is_active() {
+ obj.emit_by_name::<()>("checkbutton-toggled", &[]);
+ } else {
+ obj.emit_by_name::<()>("checkbutton-untoggled", &[]);
+ }
+ }
+ ));
+
+ obj.add_suffix(&suffix_toggle);
+
+ let obj = self.obj();
+ obj.bind_property("package-marked", &suffix_toggle, "active")
+ .sync_create()
+ .bidirectional()
+ .build();
+
+ // turn on by default
+ obj.set_property("package-marked", true)
+ }
+}
+// Trait shared by all widgets
+impl WidgetImpl for AptPackageRow {}
+
+// Trait shared by all buttons
+// Trait shared by all buttons
+
+impl ListBoxRowImpl for AptPackageRow {}
+impl PreferencesRowImpl for AptPackageRow {}
+impl ExpanderRowImpl for AptPackageRow {}
+
+fn create_version_badge(installed_version: &str, candidate_version: &str) -> ListBox {
+ let (base_version, installed_diff, candidate_diff) =
+ get_diff_by_prefix(installed_version, candidate_version);
+
+ let badge_box = Box::builder()
+ .halign(Align::Start)
+ .hexpand(false)
+ .orientation(Orientation::Horizontal)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(5)
+ .margin_top(5)
+ .build();
+
+ let installed_version_box = Box::builder()
+ .halign(Align::Start)
+ .hexpand(false)
+ .orientation(Orientation::Horizontal)
+ .tooltip_text(t!("installed_version_badge_text"))
+ .build();
+
+ let installed_version_base_version_label = Label::builder()
+ .label(format!(
+ "{}: {}",
+ t!("installed_version_badge_text"),
+ &base_version
+ ))
+ .valign(Align::Center)
+ .halign(Align::Start)
+ .hexpand(false)
+ .vexpand(true)
+ .build();
+
+ let installed_diff_label = Label::builder()
+ .label(installed_diff)
+ .valign(Align::Center)
+ .halign(Align::Start)
+ .hexpand(false)
+ .vexpand(true)
+ .build();
+ installed_diff_label.add_css_class("destructive-color-text");
+
+ installed_version_box.append(&installed_version_base_version_label.clone());
+ installed_version_box.append(&installed_diff_label);
+
+ let label_separator = Separator::builder().margin_start(5).margin_end(5).build();
+
+ let candidate_version_box = Box::builder()
+ .halign(Align::Start)
+ .hexpand(false)
+ .orientation(Orientation::Horizontal)
+ .tooltip_text(t!("candidate_version_badge_text"))
+ .build();
+
+ let candidate_version_base_version_label = Label::builder()
+ .label(format!(
+ "{}: {}",
+ t!("candidate_version_badge_text"),
+ &base_version
+ ))
+ .valign(Align::Center)
+ .halign(Align::Start)
+ .hexpand(false)
+ .vexpand(true)
+ .build();
+
+ let candidate_diff_label = Label::builder()
+ .label(candidate_diff)
+ .valign(Align::Center)
+ .halign(Align::Start)
+ .hexpand(false)
+ .vexpand(true)
+ .build();
+ candidate_diff_label.add_css_class("success-color-text");
+
+ candidate_version_box.append(&candidate_version_base_version_label);
+ candidate_version_box.append(&candidate_diff_label);
+
+ badge_box.append(&installed_version_box);
+ badge_box.append(&label_separator);
+ badge_box.append(&candidate_version_box);
+
+ let boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .halign(Align::Start)
+ .valign(Align::End)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(10)
+ .build();
+
+ boxedlist.add_css_class("boxed-list");
+ boxedlist.append(&badge_box);
+ boxedlist
+}
+
+fn create_arch_badge(arch: &str) -> ListBox {
+ let arch_label = Label::builder()
+ .halign(Align::Start)
+ .hexpand(false)
+ .label(format!("{}: {}", t!("arch_label_label"), arch))
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(5)
+ .margin_top(5)
+ .build();
+
+ let boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .halign(Align::Start)
+ .valign(Align::End)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(10)
+ .build();
+
+ boxedlist.add_css_class("boxed-list");
+ boxedlist.append(&arch_label);
+ boxedlist
+}
+
+fn remove_all_children_from_box(parent: >k::Box) {
+ while let Some(child) = parent.last_child() {
+ parent.remove(&child);
+ }
+}
+
+fn create_prefix_content(
+ prefix_box: >k::Box,
+ package_name: &str,
+ package_arch: &str,
+ package_installed_version: &str,
+ package_candidate_version: &str,
+) {
+ let package_label = Label::builder()
+ .halign(Align::Start)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(5)
+ .margin_top(5)
+ .label(package_name)
+ .build();
+ package_label.add_css_class("size-20-bold-text");
+ let version_box = Box::new(Orientation::Horizontal, 0);
+ version_box.append(&create_version_badge(
+ package_installed_version,
+ package_candidate_version,
+ ));
+ version_box.append(&create_arch_badge(package_arch));
+ prefix_box.append(&package_label);
+ prefix_box.append(&version_box);
+}
+
+fn create_expandable_content(
+ apt_package_row: &impl IsA,
+ expandable_box: >k::Box,
+ package_description: String,
+ package_source_uri: String,
+ package_maintainer: String,
+ package_size: u64,
+ package_installed_size: u64,
+) {
+ let expandable_page_selection_box = Box::builder()
+ .orientation(Orientation::Horizontal)
+ .hexpand(false)
+ .vexpand(false)
+ .halign(Align::Start)
+ .valign(Align::Start)
+ .margin_bottom(10)
+ .margin_top(10)
+ .margin_start(10)
+ .margin_end(10)
+ .build();
+ expandable_page_selection_box.add_css_class("linked");
+ //
+ let description_page_button = ToggleButton::builder()
+ .label(t!("description_button_label"))
+ .active(true)
+ .build();
+ let extra_info_page_button = ToggleButton::builder()
+ .label(t!("extra_info_page_button_label"))
+ .group(&description_page_button)
+ .build();
+ let uris_page_button = ToggleButton::builder()
+ .label(t!("uris_page_button_label"))
+ .group(&description_page_button)
+ .build();
+ let changelog_page_button = ToggleButton::builder()
+ .label(t!("changelog_page_button_label"))
+ // till we find a way to implement
+ .sensitive(false)
+ .group(&description_page_button)
+ .build();
+ expandable_page_selection_box.append(&description_page_button);
+ expandable_page_selection_box.append(&extra_info_page_button);
+ expandable_page_selection_box.append(&uris_page_button);
+ expandable_page_selection_box.append(&changelog_page_button);
+ //
+ expandable_box.append(&expandable_page_selection_box);
+ //
+ let expandable_bin = Bin::builder().hexpand(true).vexpand(true).build();
+ //
+ description_page_button.connect_clicked(clone!(
+ #[strong]
+ expandable_bin,
+ #[strong]
+ description_page_button,
+ move |_| {
+ if description_page_button.is_active() {
+ expandable_bin.set_child(Some(&description_stack_page(&package_description)));
+ }
+ }
+ ));
+
+ extra_info_page_button.connect_clicked(clone!(
+ #[strong]
+ expandable_bin,
+ #[strong]
+ extra_info_page_button,
+ move |_| {
+ if extra_info_page_button.is_active() {
+ expandable_bin.set_child(Some(&extra_info_stack_page(
+ &package_maintainer,
+ package_size,
+ package_installed_size,
+ )));
+ }
+ }
+ ));
+
+ uris_page_button.connect_clicked(clone!(
+ #[strong]
+ expandable_bin,
+ #[strong]
+ uris_page_button,
+ move |_| {
+ if uris_page_button.is_active() {
+ expandable_bin.set_child(Some(&uris_stack_page(&package_source_uri)));
+ }
+ }
+ ));
+
+ apt_package_row.connect_expanded_notify(clone!(
+ #[strong]
+ expandable_bin,
+ #[strong]
+ expandable_box,
+ #[strong]
+ apt_package_row,
+ #[strong]
+ description_page_button,
+ move |_| {
+ if apt_package_row.property("expanded") {
+ description_page_button.set_active(true);
+ description_page_button.emit_by_name::<()>("clicked", &[]);
+ expandable_box.append(&expandable_bin)
+ } else {
+ expandable_box.remove(&expandable_bin)
+ }
+ }
+ ));
+ //expandable_bin.add_named(&extra_info_stack_page(package_maintainer, package_size, package_installed_size), Some("extra_info_page"));
+ //
+}
+
+fn uris_stack_page(package_source_uri: &str) -> gtk::Box {
+ let uris_content_box = Box::builder()
+ .hexpand(true)
+ .vexpand(true)
+ .orientation(Orientation::Vertical)
+ .build();
+ let uris_text_buffer = TextBuffer::builder()
+ .text(package_source_uri.to_owned() + "\n")
+ .build();
+ let uris_text_view = TextView::builder()
+ .buffer(&uris_text_buffer)
+ .hexpand(true)
+ .vexpand(true)
+ .margin_top(15)
+ .margin_bottom(15)
+ .margin_start(15)
+ .margin_end(15)
+ .editable(false)
+ .buffer(&uris_text_buffer)
+ .build();
+ uris_content_box.append(&uris_text_view);
+ uris_content_box
+}
+
+fn description_stack_page(package_description: &str) -> gtk::Box {
+ let description_content_box = Box::builder()
+ .hexpand(true)
+ .vexpand(true)
+ .orientation(Orientation::Vertical)
+ .build();
+ let description_text_buffer = TextBuffer::builder()
+ .text(package_description.to_owned() + "\n")
+ .build();
+ let description_text_view = TextView::builder()
+ .buffer(&description_text_buffer)
+ .hexpand(true)
+ .vexpand(true)
+ .margin_top(0)
+ .margin_bottom(10)
+ .margin_start(15)
+ .margin_end(15)
+ .editable(false)
+ .build();
+ description_content_box.append(&description_text_view);
+ description_content_box
+}
+
+fn extra_info_stack_page(
+ package_maintainer: &str,
+ package_size: u64,
+ package_installed_size: u64,
+) -> gtk::Box {
+ let extra_info_badges_content_box = Box::builder()
+ .hexpand(true)
+ .vexpand(true)
+ .orientation(Orientation::Vertical)
+ .build();
+ let extra_info_badges_size_group = SizeGroup::new(SizeGroupMode::Both);
+ let extra_info_badges_size_group0 = SizeGroup::new(SizeGroupMode::Both);
+ let extra_info_badges_size_group1 = SizeGroup::new(SizeGroupMode::Both);
+ let package_size = package_size as f64;
+ let package_installed_size = package_installed_size as f64;
+ extra_info_badges_content_box.append(&create_color_badge(
+ &t!("extra_info_maintainer").to_string(),
+ package_maintainer,
+ "background-accent-bg",
+ &extra_info_badges_size_group,
+ &extra_info_badges_size_group0,
+ &extra_info_badges_size_group1,
+ ));
+ extra_info_badges_content_box.append(&create_color_badge(
+ &t!("extra_info_download_size").to_string(),
+ &convert(package_size),
+ "background-accent-bg",
+ &extra_info_badges_size_group,
+ &extra_info_badges_size_group0,
+ &extra_info_badges_size_group1,
+ ));
+ extra_info_badges_content_box.append(&create_color_badge(
+ &t!("extra_info_installed_size").to_string(),
+ &convert(package_installed_size),
+ "background-accent-bg",
+ &extra_info_badges_size_group,
+ &extra_info_badges_size_group0,
+ &extra_info_badges_size_group1,
+ ));
+ extra_info_badges_content_box
+}
+fn create_color_badge(
+ label0_text: &str,
+ label1_text: &str,
+ css_style: &str,
+ group_size: &SizeGroup,
+ group_size0: &SizeGroup,
+ group_size1: &SizeGroup,
+) -> ListBox {
+ let badge_box = Box::builder().build();
+
+ let label0 = Label::builder()
+ .label(label0_text)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(1)
+ .margin_top(1)
+ .valign(Align::Center)
+ .halign(Align::Center)
+ .hexpand(true)
+ .vexpand(true)
+ .build();
+ group_size0.add_widget(&label0);
+
+ let label_separator = Separator::builder().build();
+
+ let label1 = Label::builder()
+ .label(label1_text)
+ .margin_start(3)
+ .margin_end(0)
+ .margin_bottom(1)
+ .margin_top(1)
+ .valign(Align::Center)
+ .halign(Align::Center)
+ .hexpand(true)
+ .vexpand(true)
+ .build();
+ group_size1.add_widget(&label1);
+
+ label1.add_css_class(css_style);
+
+ badge_box.append(&label0);
+ badge_box.append(&label_separator);
+ badge_box.append(&label1);
+
+ let boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .halign(Align::Start)
+ .valign(Align::Start)
+ .margin_start(10)
+ .margin_end(10)
+ .margin_bottom(10)
+ .margin_top(10)
+ .build();
+
+ boxedlist.add_css_class("boxed-list");
+ boxedlist.append(&badge_box);
+ group_size.add_widget(&boxedlist);
+ boxedlist
+}
+
+pub fn get_diff_by_prefix(xs: &str, ys: &str) -> (String, String, String) {
+ let mut count = String::new();
+ for (x, y) in xs.chars().zip(ys.chars()) {
+ if x == y {
+ count.push(x)
+ } else {
+ break;
+ }
+ }
+ let count_clone0 = count.clone();
+ return (
+ count_clone0,
+ xs.trim_start_matches(&count.as_str()).to_string(),
+ ys.trim_start_matches(&count.as_str()).to_string(),
+ );
+}
diff --git a/src/bin/gui/apt_package_row/mod.rs b/src/bin/gui/apt_package_row/mod.rs
new file mode 100644
index 0000000..e04e3f0
--- /dev/null
+++ b/src/bin/gui/apt_package_row/mod.rs
@@ -0,0 +1,45 @@
+mod imp;
+
+use crate::apt_update_page::AptPackageSocket;
+use glib::Object;
+use gtk::glib;
+
+glib::wrapper! {
+ pub struct AptPackageRow(ObjectSubclass)
+ @extends adw::ExpanderRow, gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow,
+ @implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
+}
+
+impl AptPackageRow {
+ pub fn new(package: AptPackageSocket) -> Self {
+ Object::builder()
+ .property("package-name", package.name)
+ .property("package-arch", package.arch)
+ .property("package-installed-version", package.installed_version)
+ .property("package-candidate-version", package.candidate_version)
+ .property("package-description", package.description)
+ .property("package-source-uri", package.source_uri)
+ .property("package-maintainer", package.maintainer)
+ .property("package-size", package.size)
+ .property("package-installed-size", package.installed_size)
+ .build()
+ }
+}
+// ANCHOR_END: mod
+
+impl Default for AptPackageRow {
+ fn default() -> Self {
+ Self::new(AptPackageSocket {
+ name: "name".to_string(),
+ arch: "arch".to_string(),
+ installed_version: "0.0".to_string(),
+ candidate_version: "0.0".to_string(),
+ description: "??".to_string(),
+ source_uri: "??".to_string(),
+ maintainer: "??".to_string(),
+ size: 0,
+ installed_size: 0,
+ is_last: false,
+ })
+ }
+}
diff --git a/src/bin/gui/apt_update_page/mod.rs b/src/bin/gui/apt_update_page/mod.rs
new file mode 100644
index 0000000..0429666
--- /dev/null
+++ b/src/bin/gui/apt_update_page/mod.rs
@@ -0,0 +1,492 @@
+mod process;
+
+use crate::apt_package_row::AptPackageRow;
+use adw::gio::SimpleAction;
+use adw::prelude::*;
+use gtk::glib::*;
+use gtk::*;
+use pika_unixsocket_tools::pika_unixsocket_tools::*;
+use rust_apt::cache::*;
+use rust_apt::new_cache;
+use rust_apt::records::RecordField;
+use std::cell::RefCell;
+use std::process::Command;
+use std::rc::Rc;
+use std::thread;
+use tokio::runtime::Runtime;
+
+#[derive(Clone)]
+pub struct AptPackageSocket {
+ pub name: String,
+ pub arch: String,
+ pub installed_version: String,
+ pub candidate_version: String,
+ pub description: String,
+ pub source_uri: String,
+ pub maintainer: String,
+ pub size: u64,
+ pub installed_size: u64,
+ pub is_last: bool,
+}
+pub fn apt_update_page(
+ window: adw::ApplicationWindow,
+ retry_signal_action: &SimpleAction,
+ flatpak_retry_signal_action: &SimpleAction,
+ flatpak_ran_once: Rc>
+) -> gtk::Box {
+ let (update_percent_sender, update_percent_receiver) = async_channel::unbounded::();
+ let update_percent_sender = update_percent_sender.clone();
+ let (update_status_sender, update_status_receiver) = async_channel::unbounded::();
+ let update_status_sender = update_status_sender.clone();
+ let update_status_sender_clone0 = update_status_sender.clone();
+ let (get_upgradable_sender, get_upgradable_receiver) = async_channel::unbounded();
+ let get_upgradable_sender = get_upgradable_sender.clone();
+
+ let excluded_updates_vec: Rc>> = Rc::new(RefCell::new(Vec::new()));
+
+ thread::spawn(move || {
+ Runtime::new().unwrap().block_on(start_socket_server_no_log(
+ update_percent_sender,
+ "/tmp/pika_apt_update_percent.sock",
+ ));
+ });
+
+ thread::spawn(move || {
+ Runtime::new().unwrap().block_on(start_socket_server(
+ update_status_sender,
+ "/tmp/pika_apt_update_status.sock",
+ "/tmp/pika-apt-update.log",
+ ));
+ });
+
+ thread::spawn(move || {
+ let apt_update_command = Command::new("pkexec")
+ .args([
+ "/home/ward/RustroverProjects/pkg-pikman-update-manager/target/debug/apt_update",
+ ])
+ .status()
+ .unwrap();
+ match apt_update_command.code().unwrap() {
+ 0 => update_status_sender_clone0
+ .send_blocking("FN_OVERRIDE_SUCCESSFUL".to_owned())
+ .unwrap(),
+ 53 => {}
+ _ => {
+ update_status_sender_clone0
+ .send_blocking(t!("update_status_error_perms").to_string())
+ .unwrap();
+ update_status_sender_clone0
+ .send_blocking("FN_OVERRIDE_FAILED".to_owned())
+ .unwrap()
+ }
+ }
+ });
+
+ let main_box = Box::builder()
+ .hexpand(true)
+ .vexpand(true)
+ .orientation(Orientation::Vertical)
+ .build();
+
+ let searchbar = SearchEntry::builder()
+ .search_delay(500)
+ .margin_top(15)
+ .margin_bottom(15)
+ .margin_end(30)
+ .margin_start(30)
+ .build();
+ searchbar.add_css_class("rounded-all-25");
+
+ let packages_boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .sensitive(false)
+ .build();
+ packages_boxedlist.add_css_class("boxed-list");
+ packages_boxedlist.add_css_class("round-all-scroll");
+
+ let packages_viewport = ScrolledWindow::builder()
+ .vexpand(true)
+ .hexpand(true)
+ .has_frame(true)
+ .margin_bottom(15)
+ .margin_top(15)
+ .margin_end(15)
+ .margin_start(15)
+ .height_request(390)
+ .child(&packages_boxedlist)
+ .build();
+ packages_viewport.add_css_class("round-all-scroll");
+
+ let packages_no_viewport_page = adw::StatusPage::builder()
+ .icon_name("emblem-default-symbolic")
+ .title(t!("apt_packages_no_viewport_page_title"))
+ .hexpand(true)
+ .vexpand(true)
+ .build();
+
+ let viewport_bin = adw::Bin::builder()
+ .child(&packages_no_viewport_page)
+ .build();
+
+ let apt_update_dialog_child_box = Box::builder().orientation(Orientation::Vertical).build();
+
+ let apt_update_dialog_progress_bar =
+ ProgressBar::builder().show_text(true).hexpand(true).build();
+
+ let apt_update_dialog_spinner = Spinner::builder()
+ .hexpand(true)
+ .valign(Align::Start)
+ .halign(Align::Center)
+ .spinning(true)
+ .height_request(128)
+ .width_request(128)
+ .build();
+
+ apt_update_dialog_child_box.append(&apt_update_dialog_spinner);
+ apt_update_dialog_child_box.append(&apt_update_dialog_progress_bar);
+
+ let apt_update_dialog = adw::MessageDialog::builder()
+ .transient_for(&window)
+ .extra_child(&apt_update_dialog_child_box)
+ .heading(t!("apt_update_dialog_heading"))
+ .width_request(500)
+ .build();
+
+ apt_update_dialog.add_response(
+ "apt_update_dialog_retry",
+ &t!("apt_update_dialog_retry_label").to_string(),
+ );
+
+ apt_update_dialog.set_response_appearance(
+ "apt_update_dialog_retry",
+ adw::ResponseAppearance::Suggested,
+ );
+
+ apt_update_dialog.set_response_enabled("apt_update_dialog_retry", false);
+
+ let retry_signal_action0 = retry_signal_action.clone();
+
+ apt_update_dialog
+ .clone()
+ .choose(None::<&gio::Cancellable>, move |choice| {
+ if choice == "apt_update_dialog_retry" {
+ retry_signal_action0.activate(None);
+ }
+ });
+
+ let bottom_bar = Box::builder().valign(Align::End).build();
+
+ let select_button = Button::builder()
+ .halign(Align::End)
+ .valign(Align::Center)
+ .hexpand(true)
+ .margin_start(10)
+ .margin_end(10)
+ .margin_bottom(15)
+ .label(t!("select_button_deselect_all"))
+ .build();
+
+ select_button.connect_clicked(clone!(
+ #[weak]
+ select_button,
+ #[weak]
+ packages_boxedlist,
+ move |_| {
+ let select_button_label = select_button.label().unwrap();
+ let value_to_mark = if select_button_label == t!("select_button_select_all").to_string()
+ {
+ true
+ } else if select_button_label == t!("select_button_deselect_all").to_string() {
+ false
+ } else {
+ panic!("Unexpected label on selection button")
+ };
+ set_all_apt_row_marks_to(&packages_boxedlist, value_to_mark)
+ }
+ ));
+
+ let update_button = Button::builder()
+ .halign(Align::End)
+ .valign(Align::Center)
+ .hexpand(false)
+ .margin_start(10)
+ .margin_end(30)
+ .margin_bottom(15)
+ .label(t!("update_button_label"))
+ .build();
+ update_button.add_css_class("destructive-action");
+
+ update_button.connect_clicked(clone!(
+ #[weak]
+ window,
+ #[weak]
+ retry_signal_action,
+ #[strong]
+ excluded_updates_vec,
+ move |_| {
+ process::apt_process_update(
+ &excluded_updates_vec.borrow(),
+ window,
+ &retry_signal_action,
+ );
+ }
+ ));
+
+ bottom_bar.append(&select_button);
+ bottom_bar.append(&update_button);
+
+ let update_percent_server_context = MainContext::default();
+ // The main loop executes the asynchronous block
+ update_percent_server_context.spawn_local(clone!(
+ #[weak]
+ apt_update_dialog_progress_bar,
+ async move {
+ while let Ok(state) = update_percent_receiver.recv().await {
+ match state.parse::() {
+ Ok(p) => apt_update_dialog_progress_bar.set_fraction(p / 100.0),
+ Err(_) => {}
+ }
+ }
+ }
+ ));
+
+ let update_status_server_context = MainContext::default();
+ // The main loop executes the asynchronous block
+ update_status_server_context.spawn_local(clone!(
+ #[weak]
+ apt_update_dialog,
+ #[weak]
+ apt_update_dialog_child_box,
+ #[weak]
+ flatpak_retry_signal_action,
+ async move {
+ while let Ok(state) = update_status_receiver.recv().await {
+ match state.as_ref() {
+ "FN_OVERRIDE_SUCCESSFUL" => {
+ let get_upgradable_sender = get_upgradable_sender.clone();
+ thread::spawn(move || {
+ // Create upgradable list cache
+ let upgradable_cache = new_cache!().unwrap();
+ //
+ upgradable_cache.upgrade(Upgrade::FullUpgrade).unwrap();
+
+ upgradable_cache.resolve(true).unwrap();
+
+ let mut upgradeable_iter =
+ upgradable_cache.get_changes(false).peekable();
+ while let Some(pkg) = upgradeable_iter.next() {
+ if !pkg.marked_delete() {
+ let candidate_version_pkg = pkg.candidate().unwrap();
+ let package_struct = AptPackageSocket {
+ name: pkg.name().to_string(),
+ arch: pkg.arch().to_string(),
+ installed_version: match pkg.installed() {
+ Some(t) => t.version().to_string(),
+ _ => {
+ t!("installed_version_to_be_installed").to_string()
+ }
+ },
+ candidate_version: candidate_version_pkg
+ .version()
+ .to_string(),
+ description: match candidate_version_pkg.description() {
+ Some(s) => s,
+ _ => t!("apt_pkg_property_unknown").to_string(),
+ },
+ source_uri: candidate_version_pkg
+ .uris()
+ .collect::>()
+ .join("\n"),
+ maintainer: match candidate_version_pkg
+ .get_record(RecordField::Maintainer)
+ {
+ Some(s) => s,
+ _ => t!("apt_pkg_property_unknown").to_string(),
+ },
+ size: candidate_version_pkg.size(),
+ installed_size: candidate_version_pkg.installed_size(),
+ is_last: upgradeable_iter.peek().is_none(),
+ };
+ get_upgradable_sender.send_blocking(package_struct).unwrap()
+ }
+ }
+ });
+ apt_update_dialog.close();
+ let mut flatpak_ran_once_borrow = flatpak_ran_once.borrow_mut();
+ if *flatpak_ran_once_borrow != true {
+ flatpak_retry_signal_action.activate(None);
+ *flatpak_ran_once_borrow = true;
+ }
+ }
+ "FN_OVERRIDE_FAILED" => {
+ apt_update_dialog_child_box.set_visible(false);
+ apt_update_dialog.set_extra_child(Some(
+ &Image::builder()
+ .pixel_size(128)
+ .icon_name("dialog-error-symbolic")
+ .halign(Align::Center)
+ .build(),
+ ));
+ apt_update_dialog
+ .set_title(Some(&t!("apt_update_dialog_status_failed").to_string()));
+ apt_update_dialog.set_response_enabled("apt_update_dialog_retry", true);
+ }
+ _ => apt_update_dialog.set_body(&state),
+ }
+ }
+ }
+ ));
+
+ let get_upgradable_server_context = MainContext::default();
+ // The main loop executes the asynchronous block
+ get_upgradable_server_context.spawn_local(clone!(
+ #[strong]
+ select_button,
+ #[strong]
+ update_button,
+ #[strong]
+ packages_boxedlist,
+ #[strong]
+ packages_viewport,
+ #[strong]
+ viewport_bin,
+ #[strong]
+ excluded_updates_vec,
+ async move {
+ while let Ok(state) = get_upgradable_receiver.recv().await {
+ viewport_bin.set_child(Some(&packages_viewport));
+ let apt_row = AptPackageRow::new(state.clone());
+ apt_row.connect_closure(
+ "checkbutton-toggled",
+ false,
+ closure_local!(
+ #[strong]
+ select_button,
+ #[strong]
+ update_button,
+ #[strong]
+ packages_boxedlist,
+ #[strong]
+ excluded_updates_vec,
+ move |apt_row: AptPackageRow| {
+ if is_widget_select_all_ready(&packages_boxedlist) {
+ select_button
+ .set_label(&t!("select_button_select_all").to_string());
+ } else {
+ select_button
+ .set_label(&t!("select_button_deselect_all").to_string());
+ }
+ update_button
+ .set_sensitive(!is_all_children_unmarked(&packages_boxedlist));
+ excluded_updates_vec
+ .borrow_mut()
+ .retain(|x| x != &apt_row.package_name());
+ }
+ ),
+ );
+ apt_row.connect_closure(
+ "checkbutton-untoggled",
+ false,
+ closure_local!(
+ #[strong]
+ select_button,
+ #[strong]
+ update_button,
+ #[strong]
+ packages_boxedlist,
+ #[strong]
+ excluded_updates_vec,
+ move |apt_row: AptPackageRow| {
+ select_button.set_label(&t!("select_button_select_all").to_string());
+ update_button
+ .set_sensitive(!is_all_children_unmarked(&packages_boxedlist));
+ excluded_updates_vec
+ .borrow_mut()
+ .push(apt_row.package_name())
+ }
+ ),
+ );
+ packages_boxedlist.append(&apt_row);
+ if state.is_last {
+ packages_boxedlist.set_sensitive(true);
+ }
+ }
+ }
+ ));
+
+ searchbar.connect_search_changed(clone!(
+ #[weak]
+ searchbar,
+ #[weak]
+ packages_boxedlist,
+ move |_| {
+ let mut counter = packages_boxedlist.first_child();
+ while let Some(row) = counter {
+ if row.widget_name() == "AptPackageRow" {
+ if !searchbar.text().is_empty() {
+ if row
+ .property::("package-name")
+ .to_lowercase()
+ .contains(&searchbar.text().to_string().to_lowercase())
+ {
+ row.set_property("visible", true);
+ searchbar.grab_focus();
+ } else {
+ row.set_property("visible", false);
+ }
+ } else {
+ row.set_property("visible", true);
+ }
+ }
+ counter = row.next_sibling();
+ }
+ }
+ ));
+
+ main_box.append(&searchbar);
+ main_box.append(&viewport_bin);
+ main_box.append(&bottom_bar);
+
+ apt_update_dialog.present();
+ main_box
+}
+
+fn is_widget_select_all_ready(parent_listbox: &impl IsA) -> bool {
+ let mut is_ready = false;
+ let mut child_counter = parent_listbox.borrow().first_child();
+ while let Some(child) = child_counter {
+ let next_child = child.next_sibling();
+ let downcast = child.downcast::().unwrap();
+ if !downcast.package_marked() {
+ is_ready = true;
+ break;
+ }
+ child_counter = next_child
+ }
+ is_ready
+}
+
+fn is_all_children_unmarked(parent_listbox: &impl IsA) -> bool {
+ let mut is_all_unmarked = true;
+ let mut child_counter = parent_listbox.borrow().first_child();
+ while let Some(child) = child_counter {
+ let next_child = child.next_sibling();
+ let downcast = child.downcast::().unwrap();
+ if downcast.package_marked() {
+ is_all_unmarked = false;
+ break;
+ }
+ child_counter = next_child
+ }
+ is_all_unmarked
+}
+
+fn set_all_apt_row_marks_to(parent_listbox: &impl IsA, value: bool) {
+ let mut child_counter = parent_listbox.borrow().first_child();
+ while let Some(child) = child_counter {
+ let next_child = child.next_sibling();
+ let downcast = child.downcast::().unwrap();
+ downcast.set_package_marked(value);
+ child_counter = next_child
+ }
+}
diff --git a/src/bin/gui/apt_update_page/process.rs b/src/bin/gui/apt_update_page/process.rs
new file mode 100644
index 0000000..def792a
--- /dev/null
+++ b/src/bin/gui/apt_update_page/process.rs
@@ -0,0 +1,622 @@
+use adw::gio::SimpleAction;
+use adw::prelude::*;
+use gtk::glib::*;
+use gtk::*;
+use pika_unixsocket_tools::pika_unixsocket_tools::{
+ start_socket_server, start_socket_server_no_log,
+};
+use pretty_bytes::converter::convert;
+use rust_apt::cache::Upgrade;
+use rust_apt::new_cache;
+use serde::Serialize;
+use serde_json::Value;
+use std::cell::RefCell;
+use std::path::Path;
+use std::process::Command;
+use std::rc::Rc;
+use std::thread;
+use tokio::runtime::Runtime;
+
+struct AptChangesInfo {
+ package_count_upgrade: u64,
+ package_count_install: u64,
+ package_count_downgrade: u64,
+ package_count_remove: u64,
+ total_download_size: u64,
+ total_installed_size: i64,
+}
+#[derive(Serialize)]
+struct Exclusions {
+ exclusions: Vec,
+}
+
+impl AptChangesInfo {
+ fn add_upgrade(&mut self) {
+ self.package_count_upgrade += 1;
+ }
+ fn add_install(&mut self) {
+ self.package_count_install += 1;
+ }
+ fn add_downgrade(&mut self) {
+ self.package_count_downgrade += 1;
+ }
+ fn add_remove(&mut self) {
+ self.package_count_remove += 1;
+ }
+
+ fn increase_total_download_size_by(&mut self, value: u64) {
+ self.total_download_size += value;
+ }
+
+ fn increase_total_installed_size_by(&mut self, value: u64) {
+ self.total_installed_size += value as i64;
+ }
+
+ fn decrease_total_installed_size_by(&mut self, value: u64) {
+ self.total_installed_size -= value as i64;
+ }
+}
+
+pub fn apt_process_update(
+ excluded_updates_vec: &Vec,
+ window: adw::ApplicationWindow,
+ retry_signal_action: &SimpleAction,
+) {
+ let excluded_updates_alert_dialog = adw::MessageDialog::builder()
+ .transient_for(&window)
+ .heading(t!("excluded_updates_alert_dialog_heading"))
+ .body(t!("excluded_updates_alert_dialog_body"))
+ .build();
+
+ excluded_updates_alert_dialog.add_response(
+ "excluded_updates_alert_dialog_cancel",
+ &t!("excluded_updates_alert_dialog_cancel_label").to_string(),
+ );
+
+ excluded_updates_alert_dialog.add_response(
+ "excluded_updates_alert_continue",
+ &t!("excluded_updates_alert_continue_label").to_string(),
+ );
+
+ excluded_updates_alert_dialog.set_response_appearance(
+ "excluded_updates_alert_continue",
+ adw::ResponseAppearance::Destructive,
+ );
+
+ excluded_updates_alert_dialog.set_default_response(Some("excluded_updates_alert_continue"));
+
+ let excluded_updates_alert_dialog_action =
+ SimpleAction::new("excluded_updates_alert_dialog_action", None);
+
+ excluded_updates_alert_dialog_action.connect_activate(clone!(
+ #[weak]
+ window,
+ #[weak]
+ retry_signal_action,
+ #[strong]
+ excluded_updates_vec,
+ move |_, _| apt_confirm_window(&excluded_updates_vec, window, &retry_signal_action)
+ ));
+
+ if excluded_updates_vec.is_empty() {
+ excluded_updates_alert_dialog_action.activate(None);
+ } else {
+ excluded_updates_alert_dialog.choose(None::<&gio::Cancellable>, move |choice| {
+ if choice == "excluded_updates_alert_continue" {
+ excluded_updates_alert_dialog_action.activate(None);
+ }
+ });
+ }
+}
+
+fn apt_confirm_window(
+ excluded_updates_vec: &Vec,
+ window: adw::ApplicationWindow,
+ retry_signal_action: &SimpleAction,
+) {
+ let to_be_removed_packages_vec: Rc>> = Rc::new(RefCell::new(Vec::new()));
+ // Emulate Apt Full Upgrade to get transaction info
+ let mut apt_changes_struct = AptChangesInfo {
+ package_count_upgrade: 0,
+ package_count_install: 0,
+ package_count_downgrade: 0,
+ package_count_remove: 0,
+ total_download_size: 0,
+ total_installed_size: 0,
+ };
+
+ let apt_cache = new_cache!().unwrap();
+ let apt_upgrade_cache = new_cache!().unwrap();
+
+ apt_cache.upgrade(Upgrade::FullUpgrade).unwrap();
+
+ for change in apt_cache.get_changes(false) {
+ if !excluded_updates_vec
+ .iter()
+ .any(|e| change.name().contains(e))
+ {
+ let pkg = apt_upgrade_cache.get(change.name()).unwrap();
+ if change.marked_upgrade() || change.marked_install() || change.marked_downgrade() {
+ pkg.mark_install(true, false);
+ } else if change.marked_delete() {
+ pkg.mark_delete(false);
+ to_be_removed_packages_vec
+ .borrow_mut()
+ .push(pkg.name().to_owned());
+ }
+ pkg.protect();
+ }
+ }
+
+ apt_upgrade_cache.resolve(true).unwrap();
+
+ println!("{}", t!("gui_changes_emu_msg_0"));
+ for change in apt_upgrade_cache.get_changes(false) {
+ if change.is_installed() {
+ apt_changes_struct
+ .decrease_total_installed_size_by(change.installed().unwrap().installed_size());
+ }
+ if change.marked_upgrade() && change.is_installed() {
+ println!("{}: {}", t!("gui_changes_emu_msg_upgrading"), change.name());
+ apt_changes_struct.add_upgrade();
+ apt_changes_struct.increase_total_download_size_by(change.candidate().unwrap().size());
+ apt_changes_struct
+ .increase_total_installed_size_by(change.candidate().unwrap().installed_size());
+ } else if change.marked_install() || change.marked_upgrade() && !change.is_installed() {
+ println!(
+ "{}: {}",
+ t!("gui_changes_emu_msg_installing"),
+ change.name()
+ );
+ apt_changes_struct.add_install();
+ apt_changes_struct.increase_total_download_size_by(change.candidate().unwrap().size());
+ apt_changes_struct
+ .increase_total_installed_size_by(change.candidate().unwrap().installed_size());
+ } else if change.marked_downgrade() {
+ println!(
+ "{}: {}",
+ t!("gui_changes_emu_msg_downgrading"),
+ change.name()
+ );
+ apt_changes_struct.add_downgrade();
+ apt_changes_struct.increase_total_download_size_by(change.candidate().unwrap().size());
+ apt_changes_struct
+ .increase_total_installed_size_by(change.candidate().unwrap().installed_size());
+ } else if change.marked_delete() {
+ println!("{}: {}", t!("gui_changes_emu_msg_removing"), change.name());
+ apt_changes_struct.add_remove();
+ }
+ }
+
+ let apt_confirm_dialog_child_box = Box::builder().orientation(Orientation::Vertical).build();
+
+ let apt_update_dialog_badges_size_group = SizeGroup::new(SizeGroupMode::Both);
+ let apt_update_dialog_badges_size_group0 = SizeGroup::new(SizeGroupMode::Both);
+ let apt_update_dialog_badges_size_group1 = SizeGroup::new(SizeGroupMode::Both);
+
+ apt_confirm_dialog_child_box.append(&create_color_badge(
+ &t!("package_count_upgrade_badge_label"),
+ &apt_changes_struct.package_count_upgrade.to_string(),
+ "background-accent-bg",
+ &apt_update_dialog_badges_size_group,
+ &apt_update_dialog_badges_size_group0,
+ &apt_update_dialog_badges_size_group1,
+ ));
+
+ apt_confirm_dialog_child_box.append(&create_color_badge(
+ &t!("package_count_install_badge_label"),
+ &apt_changes_struct.package_count_install.to_string(),
+ "background-accent-bg",
+ &apt_update_dialog_badges_size_group,
+ &apt_update_dialog_badges_size_group0,
+ &apt_update_dialog_badges_size_group1,
+ ));
+
+ apt_confirm_dialog_child_box.append(&create_color_badge(
+ &t!("package_count_downgrade_badge_label"),
+ &apt_changes_struct.package_count_downgrade.to_string(),
+ "background-accent-bg",
+ &apt_update_dialog_badges_size_group,
+ &apt_update_dialog_badges_size_group0,
+ &apt_update_dialog_badges_size_group1,
+ ));
+
+ apt_confirm_dialog_child_box.append(&create_color_badge(
+ &t!("package_count_remove_badge_label"),
+ &apt_changes_struct.package_count_remove.to_string(),
+ "background-accent-bg",
+ &apt_update_dialog_badges_size_group,
+ &apt_update_dialog_badges_size_group0,
+ &apt_update_dialog_badges_size_group1,
+ ));
+
+ apt_confirm_dialog_child_box.append(&create_color_badge(
+ &t!("total_download_size_badge_label"),
+ &convert(apt_changes_struct.total_download_size as f64),
+ "background-accent-bg",
+ &apt_update_dialog_badges_size_group,
+ &apt_update_dialog_badges_size_group0,
+ &apt_update_dialog_badges_size_group1,
+ ));
+
+ apt_confirm_dialog_child_box.append(&create_color_badge(
+ &t!("total_installed_size_badge_label"),
+ &convert(apt_changes_struct.total_installed_size as f64),
+ "background-accent-bg",
+ &apt_update_dialog_badges_size_group,
+ &apt_update_dialog_badges_size_group0,
+ &apt_update_dialog_badges_size_group1,
+ ));
+
+ let apt_confirm_dialog = adw::MessageDialog::builder()
+ .transient_for(&window)
+ .heading(t!("apt_confirm_dialog_heading"))
+ .body(t!("apt_confirm_dialog_body"))
+ .extra_child(&apt_confirm_dialog_child_box)
+ .build();
+
+ apt_confirm_dialog.add_response(
+ "apt_confirm_dialog_cancel",
+ &t!("apt_confirm_dialog_cancel_label").to_string(),
+ );
+
+ apt_confirm_dialog.add_response(
+ "apt_confirm_dialog_confirm",
+ &t!("apt_confirm_dialog_confirm_label").to_string(),
+ );
+
+ apt_confirm_dialog.set_response_appearance(
+ "apt_confirm_dialog_confirm",
+ adw::ResponseAppearance::Destructive,
+ );
+
+ apt_confirm_dialog.set_default_response(Some("apt_confirm_dialog_confirm"));
+ apt_confirm_dialog.set_close_response("apt_confirm_dialog_cancel");
+
+ let json_file_path = "/tmp/pika-apt-exclusions.json";
+
+ if Path::new(json_file_path).exists() {
+ std::fs::remove_file(json_file_path).expect("Failed to remove old json file");
+ }
+
+ if !excluded_updates_vec.is_empty() {
+ let exclusions_array = Exclusions {
+ exclusions: excluded_updates_vec
+ .into_iter()
+ .map(|i| serde_json::from_str(format!("{{\"package\":\"{}\"}}", i).as_str()))
+ .collect::, _>>()
+ .unwrap(),
+ };
+
+ std::fs::write(
+ json_file_path,
+ serde_json::to_string_pretty(&exclusions_array).unwrap(),
+ )
+ .expect("Failed to write to json file");
+ }
+
+ let apt_confirm_start_signal_action = SimpleAction::new("apt_confirm_start", None);
+
+ apt_confirm_start_signal_action.connect_activate(clone!(
+ #[weak]
+ window,
+ #[strong]
+ retry_signal_action,
+ #[strong]
+ apt_confirm_dialog,
+ move |_, _| {
+ let retry_signal_action0 = retry_signal_action.clone();
+ apt_confirm_dialog
+ .clone()
+ .choose(None::<&gio::Cancellable>, move |choice| {
+ if choice == "apt_confirm_dialog_confirm" {
+ apt_full_upgrade_from_socket(window, &retry_signal_action0);
+ }
+ });
+ }
+ ));
+
+ let to_be_removed_packages_borrow = to_be_removed_packages_vec.borrow();
+ if to_be_removed_packages_borrow.is_empty() {
+ apt_confirm_start_signal_action.activate(None);
+ } else {
+ let apt_remove_confirm_text_buffer = TextBuffer::builder()
+ .text(
+ to_be_removed_packages_borrow
+ .iter()
+ .map(|x| x.to_string() + "\n")
+ .collect::()
+ + "\n",
+ )
+ .build();
+
+ let apt_remove_confirm_text_view = TextView::builder()
+ .buffer(&apt_remove_confirm_text_buffer)
+ .hexpand(true)
+ .vexpand(true)
+ .editable(false)
+ .build();
+
+ let apt_remove_confirm_text_viewport = gtk::ScrolledWindow::builder()
+ .vexpand(true)
+ .hexpand(true)
+ .has_frame(true)
+ .hscrollbar_policy(PolicyType::Never)
+ .child(&apt_remove_confirm_text_view)
+ .build();
+ apt_remove_confirm_text_viewport.add_css_class("round-all-scroll");
+
+ let apt_remove_confirm_dialog = adw::MessageDialog::builder()
+ .transient_for(&window)
+ .heading(t!("apt_remove_confirm_dialog_heading"))
+ .body(t!("apt_remove_confirm_dialog_body"))
+ .extra_child(&apt_remove_confirm_text_viewport)
+ .build();
+
+ apt_remove_confirm_dialog.add_response(
+ "apt_remove_confirm_dialog_cancel",
+ &t!("apt_remove_confirm_dialog_cancel_label").to_string(),
+ );
+
+ apt_remove_confirm_dialog.add_response(
+ "apt_remove_confirm_dialog_confirm",
+ &t!("apt_remove_confirm_dialog_confirm_label").to_string(),
+ );
+
+ apt_remove_confirm_dialog.set_response_appearance(
+ "apt_remove_confirm_dialog_confirm",
+ adw::ResponseAppearance::Destructive,
+ );
+
+ apt_remove_confirm_dialog.set_default_response(Some("apt_remove_confirm_dialog_confirm"));
+ apt_remove_confirm_dialog.set_close_response("apt_remove_confirm_dialog_cancel");
+
+ apt_remove_confirm_dialog.choose(None::<&gio::Cancellable>, move |choice| {
+ if choice == "apt_remove_confirm_dialog_confirm" {
+ apt_confirm_start_signal_action.activate(None);
+ }
+ });
+ }
+}
+
+fn apt_full_upgrade_from_socket(
+ window: adw::ApplicationWindow,
+ retry_signal_action: &SimpleAction,
+) {
+ let (upgrade_percent_sender, upgrade_percent_receiver) = async_channel::unbounded::();
+ let upgrade_percent_sender = upgrade_percent_sender.clone();
+ let (upgrade_status_sender, upgrade_status_receiver) = async_channel::unbounded::();
+ let upgrade_status_sender = upgrade_status_sender.clone();
+ let upgrade_status_sender_clone0 = upgrade_status_sender.clone();
+
+ let log_file_path = format!(
+ "/tmp/pika-apt-upgrade_{}.log",
+ chrono::offset::Local::now().format("%Y-%m-%d_%H:%M")
+ );
+ let log_file_path_clone0 = log_file_path.clone();
+
+ thread::spawn(move || {
+ Runtime::new().unwrap().block_on(start_socket_server_no_log(
+ upgrade_percent_sender,
+ "/tmp/pika_apt_upgrade_percent.sock",
+ ));
+ });
+
+ thread::spawn(move || {
+ Runtime::new().unwrap().block_on(start_socket_server(
+ upgrade_status_sender,
+ "/tmp/pika_apt_upgrade_status.sock",
+ &log_file_path,
+ ));
+ });
+
+ thread::spawn(move || {
+ let apt_upgrade_command = Command::new("pkexec")
+ .args([
+ "/home/ward/RustroverProjects/pkg-pikman-update-manager/target/debug/apt_full_upgrade",
+ ])
+ .status()
+ .unwrap();
+ match apt_upgrade_command.code().unwrap() {
+ 0 => upgrade_status_sender_clone0
+ .send_blocking("FN_OVERRIDE_SUCCESSFUL".to_owned())
+ .unwrap(),
+ 53 => {}
+ _ => {
+ upgrade_status_sender_clone0
+ .send_blocking(t!("upgrade_status_error_perms").to_string())
+ .unwrap();
+ upgrade_status_sender_clone0
+ .send_blocking("FN_OVERRIDE_FAILED".to_owned())
+ .unwrap()
+ }
+ }
+ });
+
+ let apt_upgrade_dialog_child_box = Box::builder().orientation(Orientation::Vertical).build();
+
+ let apt_upgrade_dialog_progress_bar =
+ ProgressBar::builder().show_text(true).hexpand(true).build();
+
+ let apt_upgrade_dialog_spinner = Spinner::builder()
+ .hexpand(true)
+ .valign(Align::Start)
+ .halign(Align::Center)
+ .spinning(true)
+ .height_request(128)
+ .width_request(128)
+ .build();
+
+ apt_upgrade_dialog_child_box.append(&apt_upgrade_dialog_spinner);
+ apt_upgrade_dialog_child_box.append(&apt_upgrade_dialog_progress_bar);
+
+ let apt_upgrade_dialog = adw::MessageDialog::builder()
+ .transient_for(&window)
+ .extra_child(&apt_upgrade_dialog_child_box)
+ .heading(t!("apt_upgrade_dialog_heading"))
+ .width_request(500)
+ .build();
+
+ apt_upgrade_dialog.add_response(
+ "apt_upgrade_dialog_ok",
+ &t!("apt_upgrade_dialog_ok_label").to_string(),
+ );
+
+ let apt_upgrade_dialog_child_box_done =
+ Box::builder().orientation(Orientation::Vertical).build();
+
+ let apt_upgrade_log_image = Image::builder()
+ .pixel_size(128)
+ .halign(Align::Center)
+ .build();
+
+ let apt_upgrade_log_button = Button::builder()
+ .label(t!("apt_upgrade_dialog_open_log_file_label"))
+ .halign(Align::Center)
+ .margin_start(15)
+ .margin_end(15)
+ .margin_top(15)
+ .margin_bottom(15)
+ .build();
+
+ apt_upgrade_dialog_child_box_done.append(&apt_upgrade_log_image);
+ apt_upgrade_dialog_child_box_done.append(&apt_upgrade_log_button);
+
+ apt_upgrade_dialog.set_response_enabled("apt_upgrade_dialog_ok", false);
+ apt_upgrade_dialog.set_close_response("apt_upgrade_dialog_ok");
+
+ let upgrade_percent_server_context = MainContext::default();
+ // The main loop executes the asynchronous block
+ upgrade_percent_server_context.spawn_local(clone!(
+ #[weak]
+ apt_upgrade_dialog_progress_bar,
+ async move {
+ while let Ok(state) = upgrade_percent_receiver.recv().await {
+ match state.as_ref() {
+ "FN_OVERRIDE_SUCCESSFUL" => {}
+ _ => match state.parse::() {
+ Ok(p) => apt_upgrade_dialog_progress_bar.set_fraction(p / 100.0),
+ Err(_) => {}
+ },
+ }
+ }
+ }
+ ));
+
+ let upgrade_status_server_context = MainContext::default();
+ // The main loop executes the asynchronous block
+ upgrade_status_server_context.spawn_local(clone!(
+ #[weak]
+ apt_upgrade_dialog,
+ #[weak]
+ apt_upgrade_dialog_child_box,
+ #[strong]
+ apt_upgrade_dialog_child_box_done,
+ #[strong]
+ apt_upgrade_log_image,
+ async move {
+ while let Ok(state) = upgrade_status_receiver.recv().await {
+ match state.as_ref() {
+ "FN_OVERRIDE_SUCCESSFUL" => {
+ apt_upgrade_dialog_child_box.set_visible(false);
+ apt_upgrade_log_image.set_icon_name(Some("face-cool-symbolic"));
+ apt_upgrade_dialog
+ .set_extra_child(Some(&apt_upgrade_dialog_child_box_done));
+ apt_upgrade_dialog.set_title(Some(
+ &t!("apt_upgrade_dialog_status_successful").to_string(),
+ ));
+ apt_upgrade_dialog.set_response_enabled("apt_upgrade_dialog_ok", true);
+ }
+ "FN_OVERRIDE_FAILED" => {
+ apt_upgrade_dialog_child_box.set_visible(false);
+ apt_upgrade_log_image.set_icon_name(Some("dialog-error-symbolic"));
+ apt_upgrade_dialog
+ .set_extra_child(Some(&apt_upgrade_dialog_child_box_done));
+ apt_upgrade_dialog
+ .set_title(Some(&t!("apt_upgrade_dialog_status_failed").to_string()));
+ apt_upgrade_dialog.set_response_enabled("apt_upgrade_dialog_ok", true);
+ apt_upgrade_dialog
+ .set_response_enabled("apt_upgrade_dialog_open_log_file", true);
+ }
+ _ => apt_upgrade_dialog.set_body(&state),
+ }
+ }
+ }
+ ));
+
+ let retry_signal_action0 = retry_signal_action.clone();
+
+ apt_upgrade_log_button.connect_clicked(move |_| {
+ let _ = Command::new("xdg-open")
+ .arg(log_file_path_clone0.to_owned())
+ .spawn();
+ });
+
+ apt_upgrade_dialog.choose(None::<&gio::Cancellable>, move |choice| {
+ match choice.as_str() {
+ "apt_upgrade_dialog_ok" => {
+ retry_signal_action0.activate(None);
+ }
+ _ => {}
+ }
+ });
+}
+
+fn create_color_badge(
+ label0_text: &str,
+ label1_text: &str,
+ css_style: &str,
+ group_size: &SizeGroup,
+ group_size0: &SizeGroup,
+ group_size1: &SizeGroup,
+) -> ListBox {
+ let badge_box = Box::builder().build();
+
+ let label0 = Label::builder()
+ .label(label0_text)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(1)
+ .margin_top(1)
+ .valign(Align::Center)
+ .halign(Align::Center)
+ .hexpand(true)
+ .vexpand(true)
+ .build();
+ group_size0.add_widget(&label0);
+
+ let label_separator = Separator::builder().build();
+
+ let label1 = Label::builder()
+ .label(label1_text)
+ .margin_start(3)
+ .margin_end(0)
+ .margin_bottom(1)
+ .margin_top(1)
+ .valign(Align::Center)
+ .halign(Align::Center)
+ .hexpand(true)
+ .vexpand(true)
+ .build();
+ group_size1.add_widget(&label1);
+
+ label1.add_css_class(css_style);
+
+ badge_box.append(&label0);
+ badge_box.append(&label_separator);
+ badge_box.append(&label1);
+
+ let boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .halign(Align::Center)
+ .margin_start(10)
+ .margin_end(10)
+ .margin_bottom(10)
+ .margin_top(10)
+ .build();
+
+ boxedlist.add_css_class("boxed-list");
+ boxedlist.append(&badge_box);
+ group_size.add_widget(&boxedlist);
+ boxedlist
+}
diff --git a/src/bin/gui/build_ui/mod.rs b/src/bin/gui/build_ui/mod.rs
new file mode 100644
index 0000000..777d8f0
--- /dev/null
+++ b/src/bin/gui/build_ui/mod.rs
@@ -0,0 +1,295 @@
+use crate::apt_update_page;
+use crate::config::{APP_GITHUB, APP_ICON, APP_ID, VERSION};
+use crate::flatpak_update_page;
+use adw::prelude::*;
+use adw::*;
+use gtk::glib::{clone, MainContext};
+use gtk::License;
+use std::cell::RefCell;
+use std::process::Command;
+use std::rc::Rc;
+use std::thread;
+
+pub fn build_ui(app: &Application) {
+ // setup glib
+ glib::set_prgname(Some(t!("application_name").to_string()));
+ glib::set_application_name(&t!("application_name").to_string());
+ let glib_settings = gio::Settings::new(APP_ID);
+
+ let internet_connected = Rc::new(RefCell::new(false));
+ let (internet_loop_sender, internet_loop_receiver) = async_channel::unbounded();
+ let internet_loop_sender = internet_loop_sender.clone();
+
+ thread::spawn(move || loop {
+ match Command::new("ping").arg("google.com").arg("-c 1").output() {
+ Ok(t) if t.status.success() => internet_loop_sender
+ .send_blocking(true)
+ .expect("The channel needs to be open"),
+ _ => internet_loop_sender
+ .send_blocking(false)
+ .expect("The channel needs to be open"),
+ };
+ thread::sleep(std::time::Duration::from_secs(5));
+ });
+
+ let window_banner = Banner::builder().revealed(false).build();
+
+ let internet_connected_status = internet_connected.clone();
+
+ let internet_loop_context = MainContext::default();
+ // The main loop executes the asynchronous block
+ internet_loop_context.spawn_local(clone!(
+ #[weak]
+ window_banner,
+ async move {
+ while let Ok(state) = internet_loop_receiver.recv().await {
+ let banner_text = t!("banner_text_no_internet").to_string();
+ if state == true {
+ *internet_connected_status.borrow_mut() = true;
+ if window_banner.title() == banner_text {
+ window_banner.set_revealed(false)
+ }
+ } else {
+ *internet_connected_status.borrow_mut() = false;
+ window_banner.set_title(&banner_text);
+ window_banner.set_revealed(true)
+ }
+ }
+ }
+ ));
+
+ let window_headerbar = HeaderBar::builder()
+ .title_widget(&WindowTitle::builder().title(t!("application_name")).build())
+ .show_title(false)
+ .build();
+
+ let window_breakpoint = adw::Breakpoint::new(BreakpointCondition::new_length(
+ BreakpointConditionLengthType::MaxWidth,
+ 800.0,
+ LengthUnit::Px,
+ ));
+
+ let window_adw_stack = gtk::Stack::builder()
+ .hhomogeneous(true)
+ .vhomogeneous(true)
+ .transition_type(gtk::StackTransitionType::SlideUpDown)
+ .build();
+
+ let window_toolbar = ToolbarView::builder()
+ .content(&window_adw_stack)
+ .top_bar_style(ToolbarStyle::Flat)
+ .bottom_bar_style(ToolbarStyle::Flat)
+ .build();
+
+ let window_adw_view_switcher_sidebar = gtk::StackSidebar::builder()
+ .vexpand(true)
+ .hexpand(true)
+ .margin_start(5)
+ .margin_end(5)
+ .stack(&window_adw_stack)
+ .build();
+
+ let window_adw_view_switcher_sidebar_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
+ window_adw_view_switcher_sidebar_box.append(&WindowTitle::builder().title(t!("application_name")).margin_top(20).margin_bottom(20).margin_start(5).margin_end(5).build());
+ window_adw_view_switcher_sidebar_box.append(&window_adw_view_switcher_sidebar);
+
+ let window_adw_view_sidebar_navigation_page = adw::NavigationPage::new(&window_adw_view_switcher_sidebar_box, "sidebar_view");
+
+ let sidebar_toggle_button = gtk::ToggleButton::builder()
+ .icon_name("view-right-pane-symbolic")
+ .visible(false)
+ .build();
+
+ let window_content_page_split_view = adw::OverlaySplitView::builder()
+ .vexpand(true)
+ .hexpand(true)
+ .content(&window_toolbar)
+ .sidebar(&window_adw_view_sidebar_navigation_page)
+ .max_sidebar_width(300.0)
+ .min_sidebar_width(300.0)
+ .enable_hide_gesture(true)
+ .enable_show_gesture(true)
+ .build();
+
+ let _sidebar_toggle_button_binding = window_content_page_split_view
+ .bind_property("show_sidebar", &sidebar_toggle_button, "active")
+ .sync_create()
+ .bidirectional()
+ .build();
+
+ window_breakpoint.add_setter(
+ &window_content_page_split_view,
+ "collapsed",
+ Some(&true.to_value()),
+ );
+ window_breakpoint.add_setter(
+ &sidebar_toggle_button,
+ "visible",
+ Some(&true.to_value()),
+ );
+ window_breakpoint.add_setter(
+ &window_headerbar,
+ "show_title",
+ Some(&true.to_value()),
+ );
+
+ window_headerbar.pack_end(&sidebar_toggle_button);
+
+ window_toolbar.add_top_bar(&window_headerbar);
+ window_toolbar.add_top_bar(&window_banner);
+
+ // create the main Application window
+ let window = ApplicationWindow::builder()
+ // The text on the titlebar
+ .title(t!("application_name"))
+ // link it to the application "app"
+ .application(app)
+ // Add the box called "window_box" to it
+ // Application icon
+ .icon_name(APP_ICON)
+ // Minimum Size/Default
+ .default_width(glib_settings.int("window-width"))
+ .default_height(glib_settings.int("window-height"))
+ //
+ .width_request(700)
+ .height_request(500)
+ .content(&window_content_page_split_view)
+ // Startup
+ .startup_id(APP_ID)
+ // build the window
+ .build();
+
+ window.add_breakpoint(window_breakpoint);
+
+ if glib_settings.boolean("is-maximized") == true {
+ window.maximize()
+ }
+
+ window.connect_close_request(move |window| {
+ if let Some(application) = window.application() {
+ let size = window.default_size();
+ let _ = glib_settings.set_int("window-width", size.0);
+ let _ = glib_settings.set_int("window-height", size.1);
+ let _ = glib_settings.set_boolean("is-maximized", window.is_maximized());
+ application.remove_window(window);
+ }
+ glib::Propagation::Proceed
+ });
+
+ let credits_button = gtk::Button::builder()
+ .icon_name("dialog-information-symbolic")
+ .build();
+
+ let refresh_button = gtk::Button::builder()
+ .icon_name("view-refresh-symbolic")
+ .tooltip_text(t!("refresh_button_tooltip_text"))
+ .build();
+
+ let credits_window = AboutWindow::builder()
+ .application_icon(APP_ICON)
+ .application_name(t!("application_name"))
+ .transient_for(&window)
+ .version(VERSION)
+ .hide_on_close(true)
+ .developer_name(t!("developer_name"))
+ .license_type(License::Mpl20)
+ .issue_url(APP_GITHUB.to_owned() + "/issues")
+ .build();
+
+ window_headerbar.pack_end(&refresh_button);
+ window_headerbar.pack_end(&credits_button);
+ credits_button.connect_clicked(move |_| credits_window.present());
+
+ // show the window
+
+ window.present();
+
+ // Flatpak Update Page
+
+ let flatpak_retry_signal_action = gio::SimpleAction::new("retry", None);
+
+ let flatpak_update_view_stack_bin = Bin::builder()
+ .build();
+
+ flatpak_retry_signal_action.connect_activate(clone!(
+ #[weak]
+ window,
+ #[strong]
+ flatpak_retry_signal_action,
+ #[strong]
+ flatpak_update_view_stack_bin,
+ move |_, _| {
+ flatpak_update_view_stack_bin.set_child(Some(&flatpak_update_page::flatpak_update_page(
+ window,
+ &flatpak_retry_signal_action,
+ )));
+ }
+ ));
+
+ // Apt Update Page
+ let apt_retry_signal_action = gio::SimpleAction::new("retry", None);
+
+ let flatpak_ran_once = Rc::new(RefCell::new(false));
+
+ let apt_update_view_stack_bin = Bin::builder().build();
+
+ apt_retry_signal_action.connect_activate(clone!(
+ #[weak]
+ window,
+ #[strong]
+ apt_retry_signal_action,
+ #[strong]
+ flatpak_retry_signal_action,
+ #[strong]
+ apt_update_view_stack_bin,
+ #[weak]
+ flatpak_ran_once,
+ move |_, _| {
+ apt_update_view_stack_bin.set_child(Some(&apt_update_page::apt_update_page(
+ window,
+ &apt_retry_signal_action,
+ &flatpak_retry_signal_action,
+ flatpak_ran_once,
+ )));
+ }
+ ));
+
+ apt_update_view_stack_bin.set_child(Some(&apt_update_page::apt_update_page(
+ window.clone(),
+ &apt_retry_signal_action,
+ &flatpak_retry_signal_action,
+ flatpak_ran_once,
+ )));
+
+ // Add to stack switcher
+
+ window_adw_stack.add_titled(
+ &apt_update_view_stack_bin,
+ Some("apt_update_page"),
+ &t!("apt_update_page_title"),
+ );
+
+ window_adw_stack.add_titled(
+ &flatpak_update_view_stack_bin,
+ Some("flatpak_update_page"),
+ &t!("flatpak_update_page_title"),
+ );
+
+ // Refresh button
+
+ refresh_button.connect_clicked(clone!(
+ #[weak]
+ apt_retry_signal_action,
+ #[weak]
+ flatpak_retry_signal_action,
+ #[weak]
+ window_adw_stack,
+ move |_| {
+ match window_adw_stack.visible_child_name().unwrap().as_str() {
+ "apt_update_page" => apt_retry_signal_action.activate(None),
+ "flatpak_update_page" => flatpak_retry_signal_action.activate(None),
+ _ => {}
+ }
+ }
+ ));
+}
diff --git a/src/bin/gui/config.rs b/src/bin/gui/config.rs
new file mode 100644
index 0000000..63ce5f3
--- /dev/null
+++ b/src/bin/gui/config.rs
@@ -0,0 +1,4 @@
+pub const APP_ID: &str = "com.github.pikaos-linux.pikmanupdatemanager";
+pub const VERSION: &str = env!("CARGO_PKG_VERSION");
+pub const APP_ICON: &str = "com.github.pikaos-linux.pikmanupdatemanager";
+pub const APP_GITHUB: &str = "https://github.com/PikaOS-Linux/pkg-pikman-update-manager";
diff --git a/src/bin/gui/flatpak_ref_row/imp.rs b/src/bin/gui/flatpak_ref_row/imp.rs
new file mode 100644
index 0000000..3f11b0d
--- /dev/null
+++ b/src/bin/gui/flatpak_ref_row/imp.rs
@@ -0,0 +1,486 @@
+use std::{cell::RefCell, sync::OnceLock};
+
+use adw::*;
+use adw::{prelude::*, subclass::prelude::*};
+use glib::{clone, subclass::Signal, Properties};
+use gtk::*;
+use pretty_bytes::converter::convert;
+use std::env;
+
+// ANCHOR: custom_button
+// Object holding the state
+#[derive(Properties, Default)]
+#[properties(wrapper_type = super::FlatpakRefRow)]
+pub struct FlatpakRefRow {
+ #[property(get, set)]
+ flatref_name: RefCell,
+ #[property(get, set)]
+ flatref_arch: RefCell,
+ #[property(get, set)]
+ flatref_ref_name: RefCell,
+ #[property(get, set)]
+ flatref_summary: RefCell,
+ #[property(get, set)]
+ flatref_remote_name: RefCell,
+ #[property(get, set)]
+ flatref_installed_size_installed: RefCell,
+ #[property(get, set)]
+ flatref_installed_size_remote: RefCell,
+ #[property(get, set)]
+ flatref_download_size: RefCell,
+ #[property(get, set)]
+ flatref_ref_format: RefCell,
+ #[property(get, set)]
+ flatref_is_system: RefCell,
+ #[property(get, set)]
+ flatref_marked: RefCell,
+}
+// ANCHOR_END: custom_button
+
+// The central trait for subclassing a GObject
+#[glib::object_subclass]
+impl ObjectSubclass for FlatpakRefRow {
+ const NAME: &'static str = "FlatpakRefRow";
+ type Type = super::FlatpakRefRow;
+ type ParentType = ExpanderRow;
+}
+
+// ANCHOR: object_impl
+// Trait shared by all GObjects
+#[glib::derived_properties]
+impl ObjectImpl for FlatpakRefRow {
+ fn signals() -> &'static [Signal] {
+ static SIGNALS: OnceLock> = OnceLock::new();
+ SIGNALS.get_or_init(|| {
+ vec![
+ Signal::builder("checkbutton-toggled").build(),
+ Signal::builder("checkbutton-untoggled").build(),
+ ]
+ })
+ }
+ fn constructed(&self) {
+ let current_locale = match env::var_os("LANG") {
+ Some(v) => v
+ .into_string()
+ .unwrap()
+ .chars()
+ .take_while(|&ch| ch != '.')
+ .collect::(),
+ None => panic!("$LANG is not set"),
+ };
+ rust_i18n::set_locale(¤t_locale);
+
+ self.parent_constructed();
+
+ // Bind label to number
+ // `SYNC_CREATE` ensures that the label will be immediately set
+ let obj = self.obj();
+
+ let prefix_box = Box::new(Orientation::Vertical, 0);
+
+ let expandable_box = Box::new(Orientation::Vertical, 0);
+
+ obj.connect_flatref_name_notify(clone!(
+ #[weak]
+ prefix_box,
+ #[weak]
+ expandable_box,
+ #[strong]
+ obj,
+ move |_| {
+ remove_all_children_from_box(&prefix_box);
+ remove_all_children_from_box(&expandable_box);
+ //
+ let flatref_name = obj.flatref_name();
+ let flatref_arch = obj.flatref_arch();
+ let flatref_ref_name = obj.flatref_ref_name();
+ let flatref_summary = obj.flatref_summary();
+ let flatref_remote_name = obj.flatref_remote_name();
+ let flatref_installed_size_installed = obj.flatref_installed_size_installed();
+ let flatref_installed_size_remote = obj.flatref_installed_size_remote();
+ let flatref_download_size = obj.flatref_download_size();
+ let flatref_ref_format = obj.flatref_download_size();
+ let flatref_is_system = obj.flatref_is_system();
+ let flatref_marked = obj.flatref_marked();
+ //
+ create_prefix_content(
+ &prefix_box,
+ &flatref_name,
+ &flatref_arch,
+ flatref_is_system,
+ &flatref_remote_name,
+ );
+ //
+ create_expandable_content(
+ &obj,
+ &expandable_box,
+ flatref_ref_name,
+ flatref_summary,
+ flatref_download_size,
+ flatref_installed_size_remote,
+ );
+ }
+ ));
+
+ obj.add_prefix(&prefix_box);
+ obj.add_row(&expandable_box);
+
+ let suffix_toggle = CheckButton::builder()
+ .tooltip_text(t!("mark_for_update"))
+ .halign(Align::Center)
+ .valign(Align::Center)
+ .hexpand(false)
+ .vexpand(false)
+ .build();
+
+ suffix_toggle.connect_toggled(clone!(
+ #[weak]
+ obj,
+ #[weak]
+ suffix_toggle,
+ move |_| {
+ if suffix_toggle.is_active() {
+ obj.emit_by_name::<()>("checkbutton-toggled", &[]);
+ } else {
+ obj.emit_by_name::<()>("checkbutton-untoggled", &[]);
+ }
+ }
+ ));
+
+ obj.add_suffix(&suffix_toggle);
+
+ let obj = self.obj();
+ obj.bind_property("flatref_marked", &suffix_toggle, "active")
+ .sync_create()
+ .bidirectional()
+ .build();
+
+ // turn on by default
+ obj.set_property("flatref_marked", true)
+ }
+}
+// Trait shared by all widgets
+impl WidgetImpl for FlatpakRefRow {}
+
+// Trait shared by all buttons
+// Trait shared by all buttons
+
+impl ListBoxRowImpl for FlatpakRefRow {}
+impl PreferencesRowImpl for FlatpakRefRow {}
+impl ExpanderRowImpl for FlatpakRefRow {}
+
+fn create_remote_badge(remote_name: &str) -> ListBox {
+ let remote_label = Label::builder()
+ .halign(Align::Start)
+ .hexpand(false)
+ .label(format!("{}: {}", t!("remote_label_label"), remote_name))
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(5)
+ .margin_top(5)
+ .build();
+
+ let boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .halign(Align::Start)
+ .valign(Align::End)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(10)
+ .build();
+
+ boxedlist.add_css_class("boxed-list");
+ boxedlist.append(&remote_label);
+ boxedlist
+}
+fn create_arch_badge(arch: &str) -> ListBox {
+ let arch_label = Label::builder()
+ .halign(Align::Start)
+ .hexpand(false)
+ .label(format!("{}: {}", t!("arch_label_label"), arch))
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(5)
+ .margin_top(5)
+ .build();
+
+ let boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .halign(Align::Start)
+ .valign(Align::End)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(10)
+ .build();
+
+ boxedlist.add_css_class("boxed-list");
+ boxedlist.append(&arch_label);
+ boxedlist
+}
+
+fn create_system_badge(is_system: bool) -> ListBox {
+ let system_label = Label::builder()
+ .halign(Align::Start)
+ .hexpand(false)
+ .label(match is_system {
+ true => "System",
+ false => "User",
+ })
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(5)
+ .margin_top(5)
+ .build();
+
+ let boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .halign(Align::Start)
+ .valign(Align::End)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(10)
+ .build();
+
+ boxedlist.add_css_class("boxed-list");
+ boxedlist.append(&system_label);
+ boxedlist
+}
+
+fn remove_all_children_from_box(parent: >k::Box) {
+ while let Some(child) = parent.last_child() {
+ parent.remove(&child);
+ }
+}
+
+fn create_prefix_content(
+ prefix_box: >k::Box,
+ flatref_name: &str,
+ flatref_arch: &str,
+ flatref_is_system: bool,
+ flatref_remote_name: &str,
+) {
+ let package_label = Label::builder()
+ .halign(Align::Start)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(5)
+ .margin_top(5)
+ .label(flatref_name)
+ .build();
+ package_label.add_css_class("size-20-bold-text");
+ let prefix_badge_box = Box::new(Orientation::Horizontal, 0);
+ prefix_badge_box.append(&create_remote_badge(flatref_remote_name));
+ prefix_badge_box.append(&create_arch_badge(flatref_arch));
+ prefix_badge_box.append(&create_system_badge(flatref_is_system));
+ prefix_box.append(&package_label);
+ prefix_box.append(&prefix_badge_box);
+}
+
+fn create_expandable_content(
+ flatpak_package_row: &impl IsA,
+ expandable_box: >k::Box,
+ flatref_ref_name: String,
+ flatref_summary: String,
+ flatref_download_size: u64,
+ flatref_installed_size_remote: u64,
+) {
+ let expandable_page_selection_box = Box::builder()
+ .orientation(Orientation::Horizontal)
+ .hexpand(false)
+ .vexpand(false)
+ .halign(Align::Start)
+ .valign(Align::Start)
+ .margin_bottom(10)
+ .margin_top(10)
+ .margin_start(10)
+ .margin_end(10)
+ .build();
+ expandable_page_selection_box.add_css_class("linked");
+ //
+ let summary_page_button = ToggleButton::builder()
+ .label(t!("summary_button_label"))
+ .active(true)
+ .build();
+ let extra_info_page_button = ToggleButton::builder()
+ .label(t!("extra_info_page_button_label"))
+ .group(&summary_page_button)
+ .build();
+ expandable_page_selection_box.append(&summary_page_button);
+ expandable_page_selection_box.append(&extra_info_page_button);
+ //
+ expandable_box.append(&expandable_page_selection_box);
+ //
+ let expandable_bin = Bin::builder().hexpand(true).vexpand(true).build();
+ //
+ summary_page_button.connect_clicked(clone!(
+ #[strong]
+ expandable_bin,
+ #[strong]
+ summary_page_button,
+ move |_| {
+ if summary_page_button.is_active() {
+ expandable_bin.set_child(Some(&summary_stack_page(&flatref_summary)));
+ }
+ }
+ ));
+
+ extra_info_page_button.connect_clicked(clone!(
+ #[strong]
+ expandable_bin,
+ #[strong]
+ extra_info_page_button,
+ move |_| {
+ if extra_info_page_button.is_active() {
+ expandable_bin.set_child(Some(&extra_info_stack_page(
+ &flatref_ref_name,
+ flatref_download_size,
+ flatref_installed_size_remote,
+ )));
+ }
+ }
+ ));
+
+ flatpak_package_row.connect_expanded_notify(clone!(
+ #[strong]
+ expandable_bin,
+ #[strong]
+ expandable_box,
+ #[strong]
+ flatpak_package_row,
+ #[strong]
+ summary_page_button,
+ move |_| {
+ if flatpak_package_row.property("expanded") {
+ summary_page_button.set_active(true);
+ summary_page_button.emit_by_name::<()>("clicked", &[]);
+ expandable_box.append(&expandable_bin)
+ } else {
+ expandable_box.remove(&expandable_bin)
+ }
+ }
+ ));
+}
+
+fn summary_stack_page(flatref_summary: &str) -> gtk::Box {
+ let summary_content_box = Box::builder()
+ .hexpand(true)
+ .vexpand(true)
+ .orientation(Orientation::Vertical)
+ .build();
+ let summary_text_buffer = TextBuffer::builder()
+ .text(flatref_summary.to_owned() + "\n")
+ .build();
+ let summary_text_view = TextView::builder()
+ .buffer(&summary_text_buffer)
+ .hexpand(true)
+ .vexpand(true)
+ .margin_top(0)
+ .margin_bottom(10)
+ .margin_start(15)
+ .margin_end(15)
+ .editable(false)
+ .build();
+ summary_content_box.append(&summary_text_view);
+ summary_content_box
+}
+
+fn extra_info_stack_page(
+ flatref_ref_name: &str,
+ flatref_download_size: u64,
+ flatref_installed_size_remote: u64,
+) -> gtk::Box {
+ let extra_info_badges_content_box = Box::builder()
+ .hexpand(true)
+ .vexpand(true)
+ .orientation(Orientation::Vertical)
+ .build();
+ let extra_info_badges_size_group = SizeGroup::new(SizeGroupMode::Both);
+ let extra_info_badges_size_group0 = SizeGroup::new(SizeGroupMode::Both);
+ let extra_info_badges_size_group1 = SizeGroup::new(SizeGroupMode::Both);
+ let package_size = flatref_download_size as f64;
+ let package_installed_size = flatref_installed_size_remote as f64;
+ extra_info_badges_content_box.append(&create_color_badge(
+ &t!("flatpak_extra_info_ref_name").to_string(),
+ flatref_ref_name,
+ "background-accent-bg",
+ &extra_info_badges_size_group,
+ &extra_info_badges_size_group0,
+ &extra_info_badges_size_group1,
+ ));
+ extra_info_badges_content_box.append(&create_color_badge(
+ &t!("flatpak_extra_info_download_size").to_string(),
+ &convert(package_size),
+ "background-accent-bg",
+ &extra_info_badges_size_group,
+ &extra_info_badges_size_group0,
+ &extra_info_badges_size_group1,
+ ));
+ extra_info_badges_content_box.append(&create_color_badge(
+ &t!("flatpak_extra_info_installed_size").to_string(),
+ &convert(package_installed_size),
+ "background-accent-bg",
+ &extra_info_badges_size_group,
+ &extra_info_badges_size_group0,
+ &extra_info_badges_size_group1,
+ ));
+ extra_info_badges_content_box
+}
+fn create_color_badge(
+ label0_text: &str,
+ label1_text: &str,
+ css_style: &str,
+ group_size: &SizeGroup,
+ group_size0: &SizeGroup,
+ group_size1: &SizeGroup,
+) -> ListBox {
+ let badge_box = Box::builder().build();
+
+ let label0 = Label::builder()
+ .label(label0_text)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(1)
+ .margin_top(1)
+ .valign(Align::Center)
+ .halign(Align::Center)
+ .hexpand(true)
+ .vexpand(true)
+ .build();
+ group_size0.add_widget(&label0);
+
+ let label_separator = Separator::builder().build();
+
+ let label1 = Label::builder()
+ .label(label1_text)
+ .margin_start(3)
+ .margin_end(0)
+ .margin_bottom(1)
+ .margin_top(1)
+ .valign(Align::Center)
+ .halign(Align::Center)
+ .hexpand(true)
+ .vexpand(true)
+ .build();
+ group_size1.add_widget(&label1);
+
+ label1.add_css_class(css_style);
+
+ badge_box.append(&label0);
+ badge_box.append(&label_separator);
+ badge_box.append(&label1);
+
+ let boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .halign(Align::Start)
+ .valign(Align::Start)
+ .margin_start(10)
+ .margin_end(10)
+ .margin_bottom(10)
+ .margin_top(10)
+ .build();
+
+ boxedlist.add_css_class("boxed-list");
+ boxedlist.append(&badge_box);
+ group_size.add_widget(&boxedlist);
+ boxedlist
+}
diff --git a/src/bin/gui/flatpak_ref_row/mod.rs b/src/bin/gui/flatpak_ref_row/mod.rs
new file mode 100644
index 0000000..7f03258
--- /dev/null
+++ b/src/bin/gui/flatpak_ref_row/mod.rs
@@ -0,0 +1,54 @@
+mod imp;
+
+use crate::flatpak_update_page::FlatpakRefStruct;
+use glib::Object;
+use gtk::glib;
+
+glib::wrapper! {
+ pub struct FlatpakRefRow(ObjectSubclass)
+ @extends adw::ExpanderRow, gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow,
+ @implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
+}
+
+impl FlatpakRefRow {
+ pub fn new(flatref: &FlatpakRefStruct) -> Self {
+ let flatref = flatref.clone();
+ Object::builder()
+ .property("flatref-name", flatref.name)
+ .property("flatref-arch", flatref.arch)
+ .property("flatref-ref-name", flatref.ref_name)
+ .property("flatref-summary", flatref.summary)
+ .property("flatref-remote-name", flatref.remote_name)
+ .property(
+ "flatref-installed-size-installed",
+ flatref.installed_size_installed,
+ )
+ .property(
+ "flatref-installed-size-remote",
+ flatref.installed_size_remote,
+ )
+ .property("flatref-download-size", flatref.download_size)
+ .property("flatref-ref-format", flatref.ref_format)
+ .property("flatref-is-system", flatref.is_system)
+ .build()
+ }
+}
+// ANCHOR_END: mod
+
+impl Default for FlatpakRefRow {
+ fn default() -> Self {
+ Self::new(&FlatpakRefStruct {
+ ref_name: "??".to_owned(),
+ name: "??".to_owned(),
+ arch: "??".to_owned(),
+ summary: "??".to_owned(),
+ remote_name: "??".to_owned(),
+ installed_size_installed: 0,
+ installed_size_remote: 0,
+ download_size: 0,
+ ref_format: "??".to_owned(),
+ is_system: false,
+ is_last: false,
+ })
+ }
+}
diff --git a/src/bin/gui/flatpak_update_page/mod.rs b/src/bin/gui/flatpak_update_page/mod.rs
new file mode 100644
index 0000000..38d5535
--- /dev/null
+++ b/src/bin/gui/flatpak_update_page/mod.rs
@@ -0,0 +1,784 @@
+mod process;
+
+use crate::apt_package_row::AptPackageRow;
+use crate::flatpak_ref_row::FlatpakRefRow;
+use adw::gio::SimpleAction;
+use adw::prelude::*;
+use gtk::glib::*;
+use gtk::*;
+use libflatpak::prelude::*;
+use libflatpak::InstalledRef;
+use std::cell::RefCell;
+use std::process::Command;
+use std::rc::Rc;
+use std::thread;
+
+#[derive(Clone)]
+pub struct FlatpakRefStruct {
+ pub ref_name: String,
+ pub name: String,
+ pub arch: String,
+ pub summary: String,
+ pub remote_name: String,
+ pub installed_size_installed: u64,
+ pub installed_size_remote: u64,
+ pub download_size: u64,
+ pub ref_format: String,
+ pub is_system: bool,
+ pub is_last: bool,
+}
+pub fn flatpak_update_page(
+ window: adw::ApplicationWindow,
+ retry_signal_action: &SimpleAction,
+) -> gtk::Box {
+ let (appstream_sync_percent_sender, appstream_sync_percent_receiver) =
+ async_channel::unbounded::();
+ let appstream_sync_percent_sender = appstream_sync_percent_sender.clone();
+ let (appstream_sync_status_sender, appstream_sync_status_receiver) =
+ async_channel::unbounded::();
+ let appstream_sync_status_sender = appstream_sync_status_sender.clone();
+ let appstream_sync_status_sender_clone0 = appstream_sync_status_sender.clone();
+
+ let system_refs_for_upgrade_vec: Rc>> =
+ Rc::new(RefCell::new(Vec::new()));
+
+ let user_refs_for_upgrade_vec: Rc>> =
+ Rc::new(RefCell::new(Vec::new()));
+
+ let system_refs_for_upgrade_vec_all: Rc>> =
+ Rc::new(RefCell::new(Vec::new()));
+
+ let user_refs_for_upgrade_vec_all: Rc>> =
+ Rc::new(RefCell::new(Vec::new()));
+
+ let cancellable_no = libflatpak::gio::Cancellable::NONE;
+
+ thread::spawn(move || {
+ let cancellable_no = libflatpak::gio::Cancellable::NONE;
+ let flatpak_system_installation =
+ libflatpak::Installation::new_user(cancellable_no).unwrap();
+ if let Ok(remotes) =
+ libflatpak::Installation::list_remotes(&flatpak_system_installation, cancellable_no)
+ {
+ for remote in remotes {
+ if remote.is_disabled() {
+ continue;
+ };
+ let mut remote_clousre = |status: &str, progress: u32, _: bool| {
+ appstream_sync_percent_sender
+ .send_blocking(progress)
+ .expect("appstream_sync_percent_receiver closed");
+ appstream_sync_status_sender
+ .send_blocking(format!(
+ "{} - {}: {}",
+ t!("flatpak_type_system"),
+ remote.name().unwrap_or("Unknown remote".into()),
+ status
+ ))
+ .expect("appstream_sync_status_receiver closed");
+ };
+ match libflatpak::Installation::update_appstream_full_sync(
+ &flatpak_system_installation,
+ &remote.name().unwrap(),
+ None,
+ Some(&mut remote_clousre),
+ cancellable_no,
+ ) {
+ Ok(_) => {}
+ Err(e) => {
+ appstream_sync_status_sender
+ .send_blocking(e.to_string())
+ .expect("appstream_sync_status_receiver closed");
+ appstream_sync_status_sender
+ .send_blocking("FN_OVERRIDE_FAILED".to_owned())
+ .expect("appstream_sync_status_receiver closed");
+ break;
+ }
+ }
+ }
+ }
+ let flatpak_user_installation = libflatpak::Installation::new_user(cancellable_no).unwrap();
+ if let Ok(remotes) =
+ libflatpak::Installation::list_remotes(&flatpak_user_installation, cancellable_no)
+ {
+ for remote in remotes {
+ if remote.is_disabled() {
+ continue;
+ };
+ let mut remote_clousre = |status: &str, progress: u32, _: bool| {
+ appstream_sync_percent_sender
+ .send_blocking(progress)
+ .expect("appstream_sync_percent_receiver closed");
+ appstream_sync_status_sender
+ .send_blocking(format!(
+ "{} - {}: {}",
+ t!("flatpak_type_user"),
+ remote.name().unwrap_or("Unknown remote".into()),
+ status
+ ))
+ .expect("appstream_sync_status_receiver closed");
+ };
+ match libflatpak::Installation::update_appstream_full_sync(
+ &flatpak_user_installation,
+ &remote.name().unwrap(),
+ None,
+ Some(&mut remote_clousre),
+ cancellable_no,
+ ) {
+ Ok(_) => {
+ appstream_sync_status_sender
+ .send_blocking("FN_OVERRIDE_SUCCESSFUL".to_owned())
+ .expect("appstream_sync_status_receiver closed");
+ }
+ Err(e) => {
+ appstream_sync_status_sender
+ .send_blocking(e.to_string())
+ .expect("appstream_sync_status_receiver closed");
+ appstream_sync_status_sender
+ .send_blocking("FN_OVERRIDE_FAILED".to_owned())
+ .expect("appstream_sync_status_receiver closed");
+ break;
+ }
+ }
+ }
+ }
+ });
+
+ let main_box = Box::builder()
+ .hexpand(true)
+ .vexpand(true)
+ .orientation(Orientation::Vertical)
+ .build();
+
+ let searchbar = SearchEntry::builder()
+ .search_delay(500)
+ .margin_top(15)
+ .margin_bottom(15)
+ .margin_end(30)
+ .margin_start(30)
+ .build();
+ searchbar.add_css_class("rounded-all-25");
+
+ let packages_boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .sensitive(false)
+ .build();
+ packages_boxedlist.add_css_class("boxed-list");
+ packages_boxedlist.add_css_class("round-all-scroll");
+
+ let packages_viewport = ScrolledWindow::builder()
+ .vexpand(true)
+ .hexpand(true)
+ .has_frame(true)
+ .margin_bottom(15)
+ .margin_top(15)
+ .margin_end(15)
+ .margin_start(15)
+ .height_request(390)
+ .child(&packages_boxedlist)
+ .build();
+ packages_viewport.add_css_class("round-all-scroll");
+
+ let packages_no_viewport_page = adw::StatusPage::builder()
+ .icon_name("emblem-default-symbolic")
+ .title(t!("flatpak_packages_no_viewport_page_title"))
+ .hexpand(true)
+ .vexpand(true)
+ .build();
+
+ let viewport_bin = adw::Bin::builder()
+ .child(&packages_no_viewport_page)
+ .build();
+
+ let flatpak_update_dialog_child_box = Box::builder().orientation(Orientation::Vertical).build();
+
+ let flatpak_update_dialog_progress_bar =
+ ProgressBar::builder().show_text(true).hexpand(true).build();
+
+ let flatpak_update_dialog_spinner = Spinner::builder()
+ .hexpand(true)
+ .valign(Align::Start)
+ .halign(Align::Center)
+ .spinning(true)
+ .height_request(128)
+ .width_request(128)
+ .build();
+
+ flatpak_update_dialog_child_box.append(&flatpak_update_dialog_spinner);
+ flatpak_update_dialog_child_box.append(&flatpak_update_dialog_progress_bar);
+
+ let flatpak_update_dialog = adw::MessageDialog::builder()
+ .transient_for(&window)
+ .extra_child(&flatpak_update_dialog_child_box)
+ .heading(t!("flatpak_update_dialog_heading"))
+ .width_request(500)
+ .build();
+
+ flatpak_update_dialog.add_response(
+ "flatpak_update_dialog_retry",
+ &t!("flatpak_update_dialog_retry_label").to_string(),
+ );
+
+ flatpak_update_dialog.set_response_appearance(
+ "flatpak_update_dialog_retry",
+ adw::ResponseAppearance::Suggested,
+ );
+
+ flatpak_update_dialog.set_response_enabled("flatpak_update_dialog_retry", false);
+
+ let retry_signal_action0 = retry_signal_action.clone();
+
+ flatpak_update_dialog
+ .clone()
+ .choose(None::<&gio::Cancellable>, move |choice| {
+ if choice == "flatpak_update_dialog_retry" {
+ retry_signal_action0.activate(None);
+ }
+ });
+
+ let bottom_bar = Box::builder().valign(Align::End).build();
+
+ let select_button = Button::builder()
+ .halign(Align::End)
+ .valign(Align::Center)
+ .hexpand(true)
+ .margin_start(10)
+ .margin_end(10)
+ .margin_bottom(15)
+ .label(t!("select_button_deselect_all"))
+ .build();
+
+ select_button.connect_clicked(clone!(
+ #[weak]
+ select_button,
+ #[weak]
+ packages_boxedlist,
+ move |_| {
+ let select_button_label = select_button.label().unwrap();
+ let value_to_mark = if select_button_label == t!("select_button_select_all").to_string()
+ {
+ true
+ } else if select_button_label == t!("select_button_deselect_all").to_string() {
+ false
+ } else {
+ panic!("Unexpected label on selection button")
+ };
+ set_all_flatpak_row_marks_to(&packages_boxedlist, value_to_mark)
+ }
+ ));
+
+ let update_button = Button::builder()
+ .halign(Align::End)
+ .valign(Align::Center)
+ .hexpand(false)
+ .margin_start(10)
+ .margin_end(30)
+ .margin_bottom(15)
+ .label(t!("update_button_label"))
+ .build();
+ update_button.add_css_class("destructive-action");
+
+ let system_refs_for_upgrade_vec_all_clone0 = &system_refs_for_upgrade_vec_all.clone();
+ let user_refs_for_upgrade_vec_all_clone0 = user_refs_for_upgrade_vec_all.clone();
+
+ let system_refs_for_upgrade_vec_clone0 = system_refs_for_upgrade_vec.clone();
+ let user_refs_for_upgrade_vec_clone0 = user_refs_for_upgrade_vec.clone();
+
+ update_button.connect_clicked(clone!(
+ #[weak]
+ window,
+ #[weak]
+ retry_signal_action,
+ #[strong]
+ system_refs_for_upgrade_vec_all_clone0,
+ #[strong]
+ user_refs_for_upgrade_vec_all_clone0,
+ move |_| {
+ process::flatpak_process_update(
+ Some(&system_refs_for_upgrade_vec_clone0.borrow()),
+ Some(&user_refs_for_upgrade_vec_clone0.borrow()),
+ &system_refs_for_upgrade_vec_all_clone0.borrow(),
+ &user_refs_for_upgrade_vec_all_clone0.borrow(),
+ window,
+ &retry_signal_action,
+ )
+ }
+ ));
+
+ bottom_bar.append(&select_button);
+ bottom_bar.append(&update_button);
+
+ let appstream_sync_percent_server_context = MainContext::default();
+ // The main loop executes the asynchronous block
+ appstream_sync_percent_server_context.spawn_local(clone!(
+ #[weak]
+ flatpak_update_dialog_progress_bar,
+ async move {
+ while let Ok(state) = appstream_sync_percent_receiver.recv().await {
+ flatpak_update_dialog_progress_bar.set_fraction(state as f64 / 100.0)
+ }
+ }
+ ));
+
+ let appstream_sync_status_server_context = MainContext::default();
+ // The main loop executes the asynchronous block
+ appstream_sync_status_server_context.spawn_local(clone!(
+ #[weak]
+ flatpak_update_dialog,
+ #[weak]
+ flatpak_update_dialog_child_box,
+ #[strong]
+ packages_boxedlist,
+ #[strong]
+ system_refs_for_upgrade_vec_all,
+ #[strong]
+ user_refs_for_upgrade_vec_all,
+ #[strong]
+ system_refs_for_upgrade_vec,
+ #[strong]
+ user_refs_for_upgrade_vec,
+ #[strong]
+ viewport_bin,
+ #[strong]
+ packages_viewport,
+ async move {
+ while let Ok(state) = appstream_sync_status_receiver.recv().await {
+ match state.as_ref() {
+ "FN_OVERRIDE_SUCCESSFUL" => {
+ let flatpak_system_installation =
+ libflatpak::Installation::new_system(cancellable_no).unwrap();
+ let flatpak_system_updates = flatpak_system_installation
+ .list_installed_refs_for_update(cancellable_no)
+ .unwrap();
+ let flatpak_system_transaction = libflatpak::Transaction::for_installation(
+ &flatpak_system_installation,
+ cancellable_no,
+ )
+ .unwrap();
+ //
+ let flatpak_user_installation =
+ libflatpak::Installation::new_user(cancellable_no).unwrap();
+ let flatpak_user_updates = flatpak_user_installation
+ .list_installed_refs_for_update(cancellable_no)
+ .unwrap();
+ let flatpak_user_transaction = libflatpak::Transaction::for_installation(
+ &flatpak_user_installation,
+ cancellable_no,
+ )
+ .unwrap();
+ //
+ let mut system_last_triggered = false;
+ let mut user_last_triggered = false;
+ //
+ if !flatpak_system_updates.is_empty() || !flatpak_user_updates.is_empty() {
+ viewport_bin.set_child(Some(&packages_viewport));
+ //
+ let mut flatpak_system_updates_iter =
+ &mut flatpak_system_updates.iter().peekable();
+ //
+ while let Some(flatpak_ref) = flatpak_system_updates_iter.next() {
+ let mut remote_flatpak_ref: Option = None;
+ while let Ok(remotes) = libflatpak::Installation::list_remotes(
+ &flatpak_system_installation,
+ cancellable_no,
+ ) {
+ for remote in remotes {
+ if remote.is_disabled() {
+ continue;
+ };
+ match libflatpak::Installation::fetch_remote_ref_sync(
+ &flatpak_system_installation,
+ &match remote.name() {
+ Some(t) => t,
+ None => continue,
+ },
+ flatpak_ref.kind(),
+ &match flatpak_ref.name() {
+ Some(t) => t,
+ None => continue,
+ },
+ flatpak_ref.arch().as_deref(),
+ flatpak_ref.branch().as_deref(),
+ cancellable_no,
+ ) {
+ Ok(t) => {
+ remote_flatpak_ref = Some(t);
+ break;
+ }
+ Err(_) => continue,
+ }
+ }
+ if remote_flatpak_ref.is_some() {
+ break;
+ }
+ }
+ let flatref_struct = FlatpakRefStruct {
+ ref_name: flatpak_ref
+ .name()
+ .unwrap_or("Unknown".into())
+ .to_string(),
+ name: flatpak_ref
+ .appdata_name()
+ .unwrap_or(flatpak_ref.name().unwrap_or("Unknown".into()))
+ .to_string(),
+ arch: flatpak_ref
+ .arch()
+ .unwrap_or("Unknown Arch".into())
+ .to_string(),
+ summary: flatpak_ref
+ .appdata_summary()
+ .unwrap_or("No Summary".into())
+ .to_string(),
+ remote_name: match remote_flatpak_ref {
+ Some(ref t) => {
+ t.remote_name().unwrap_or("Unknown".into()).to_string()
+ }
+ None => "Unknown".into(),
+ },
+ installed_size_installed: flatpak_ref.installed_size(),
+ installed_size_remote: match remote_flatpak_ref {
+ Some(ref t) => t.installed_size(),
+ None => 0,
+ },
+ download_size: match remote_flatpak_ref {
+ Some(t) => t.download_size(),
+ None => 0,
+ },
+ ref_format: flatpak_ref.format_ref().unwrap().into(),
+ is_system: true,
+ is_last: flatpak_system_updates_iter.peek().is_none(),
+ };
+
+ let flatpak_row = FlatpakRefRow::new(&flatref_struct);
+
+ system_refs_for_upgrade_vec
+ .borrow_mut()
+ .push(flatpak_row.clone());
+
+ system_refs_for_upgrade_vec_all
+ .borrow_mut()
+ .push(flatpak_row.clone());
+
+ flatpak_row.connect_closure(
+ "checkbutton-toggled",
+ false,
+ closure_local!(
+ #[strong]
+ select_button,
+ #[strong]
+ update_button,
+ #[strong]
+ packages_boxedlist,
+ #[strong]
+ system_refs_for_upgrade_vec,
+ move |flatpak_row: FlatpakRefRow| {
+ if is_widget_select_all_ready(&packages_boxedlist) {
+ select_button.set_label(
+ &t!("select_button_select_all").to_string(),
+ );
+ } else {
+ select_button.set_label(
+ &t!("select_button_deselect_all").to_string(),
+ );
+ }
+ update_button.set_sensitive(!is_all_children_unmarked(
+ &packages_boxedlist,
+ ));
+ system_refs_for_upgrade_vec
+ .borrow_mut()
+ .push(flatpak_row);
+ }
+ ),
+ );
+ flatpak_row.connect_closure(
+ "checkbutton-untoggled",
+ false,
+ closure_local!(
+ #[strong]
+ select_button,
+ #[strong]
+ update_button,
+ #[strong]
+ packages_boxedlist,
+ #[strong]
+ system_refs_for_upgrade_vec,
+ move |flatpak_row: FlatpakRefRow| {
+ select_button.set_label(
+ &t!("select_button_select_all").to_string(),
+ );
+ update_button.set_sensitive(!is_all_children_unmarked(
+ &packages_boxedlist,
+ ));
+ system_refs_for_upgrade_vec.borrow_mut().retain(|x| {
+ x.flatref_ref_format()
+ != flatpak_row.flatref_ref_format()
+ });
+ }
+ ),
+ );
+
+ packages_boxedlist.append(&flatpak_row);
+ if flatpak_system_updates.is_empty()
+ || flatref_struct.is_system && flatref_struct.is_last
+ {
+ system_last_triggered = true
+ }
+ }
+ //
+ let mut flatpak_user_updates_iter =
+ &mut flatpak_user_updates.iter().peekable();
+ //
+ while let Some(flatpak_ref) = flatpak_user_updates_iter.next() {
+ let mut remote_flatpak_ref: Option = None;
+ while let Ok(remotes) = libflatpak::Installation::list_remotes(
+ &flatpak_user_installation,
+ cancellable_no,
+ ) {
+ for remote in remotes {
+ if remote.is_disabled() {
+ continue;
+ };
+ match libflatpak::Installation::fetch_remote_ref_sync(
+ &flatpak_user_installation,
+ &match remote.name() {
+ Some(t) => t,
+ None => continue,
+ },
+ flatpak_ref.kind(),
+ &match flatpak_ref.name() {
+ Some(t) => t,
+ None => continue,
+ },
+ flatpak_ref.arch().as_deref(),
+ flatpak_ref.branch().as_deref(),
+ cancellable_no,
+ ) {
+ Ok(t) => {
+ remote_flatpak_ref = Some(t);
+ break;
+ }
+ Err(_) => continue,
+ }
+ }
+ if remote_flatpak_ref.is_some() {
+ break;
+ }
+ }
+ let flatref_struct = FlatpakRefStruct {
+ ref_name: flatpak_ref
+ .name()
+ .unwrap_or("Unknown".into())
+ .to_string(),
+ name: flatpak_ref
+ .appdata_name()
+ .unwrap_or(flatpak_ref.name().unwrap_or("Unknown".into()))
+ .to_string(),
+ arch: flatpak_ref
+ .arch()
+ .unwrap_or("Unknown Arch".into())
+ .to_string(),
+ summary: flatpak_ref
+ .appdata_summary()
+ .unwrap_or("No Summary".into())
+ .to_string(),
+ remote_name: match remote_flatpak_ref {
+ Some(ref t) => {
+ t.remote_name().unwrap_or("Unknown".into()).to_string()
+ }
+ None => "Unknown".into(),
+ },
+ installed_size_installed: flatpak_ref.installed_size(),
+ installed_size_remote: match remote_flatpak_ref {
+ Some(ref t) => t.installed_size(),
+ None => 0,
+ },
+ download_size: match remote_flatpak_ref {
+ Some(t) => t.download_size(),
+ None => 0,
+ },
+ ref_format: flatpak_ref.format_ref().unwrap().into(),
+ is_system: false,
+ is_last: flatpak_user_updates_iter.peek().is_none(),
+ };
+
+ let flatpak_row = FlatpakRefRow::new(&flatref_struct);
+
+ user_refs_for_upgrade_vec
+ .borrow_mut()
+ .push(flatpak_row.clone());
+
+ user_refs_for_upgrade_vec_all
+ .borrow_mut()
+ .push(flatpak_row.clone());
+
+ flatpak_row.connect_closure(
+ "checkbutton-toggled",
+ false,
+ closure_local!(
+ #[strong]
+ select_button,
+ #[strong]
+ update_button,
+ #[strong]
+ packages_boxedlist,
+ #[strong]
+ user_refs_for_upgrade_vec,
+ move |flatpak_row: FlatpakRefRow| {
+ if is_widget_select_all_ready(&packages_boxedlist) {
+ select_button.set_label(
+ &t!("select_button_select_all").to_string(),
+ );
+ } else {
+ select_button.set_label(
+ &t!("select_button_deselect_all").to_string(),
+ );
+ }
+ update_button.set_sensitive(!is_all_children_unmarked(
+ &packages_boxedlist,
+ ));
+ user_refs_for_upgrade_vec
+ .borrow_mut()
+ .push(flatpak_row);
+ }
+ ),
+ );
+ flatpak_row.connect_closure(
+ "checkbutton-untoggled",
+ false,
+ closure_local!(
+ #[strong]
+ select_button,
+ #[strong]
+ update_button,
+ #[strong]
+ packages_boxedlist,
+ #[strong]
+ user_refs_for_upgrade_vec,
+ move |flatpak_row: FlatpakRefRow| {
+ select_button.set_label(
+ &t!("select_button_select_all").to_string(),
+ );
+ update_button.set_sensitive(!is_all_children_unmarked(
+ &packages_boxedlist,
+ ));
+ user_refs_for_upgrade_vec.borrow_mut().retain(|x| {
+ x.flatref_ref_format()
+ != flatpak_row.flatref_ref_format()
+ });
+ }
+ ),
+ );
+ packages_boxedlist.append(&flatpak_row);
+ if flatpak_user_updates.is_empty()
+ || !flatref_struct.is_system && flatref_struct.is_last
+ {
+ user_last_triggered = true
+ }
+ }
+ if user_last_triggered && system_last_triggered {
+ packages_boxedlist.set_sensitive(true);
+ }
+ }
+ flatpak_update_dialog.close();
+ }
+ "FN_OVERRIDE_FAILED" => {
+ flatpak_update_dialog_child_box.set_visible(false);
+ flatpak_update_dialog.set_extra_child(Some(
+ &Image::builder()
+ .pixel_size(128)
+ .icon_name("dialog-error-symbolic")
+ .halign(Align::Center)
+ .build(),
+ ));
+ flatpak_update_dialog.set_title(Some(
+ &t!("flatpak_update_dialog_status_failed").to_string(),
+ ));
+ flatpak_update_dialog
+ .set_response_enabled("flatpak_update_dialog_retry", true);
+ }
+ _ => flatpak_update_dialog.set_body(&state),
+ }
+ }
+ }
+ ));
+
+ searchbar.connect_search_changed(clone!(
+ #[weak]
+ searchbar,
+ #[weak]
+ packages_boxedlist,
+ move |_| {
+ let mut counter = packages_boxedlist.first_child();
+ while let Some(row) = counter {
+ if row.widget_name() == "FlatpakRefRow" {
+ if !searchbar.text().is_empty() {
+ if row
+ .property::("flatref-name")
+ .to_lowercase()
+ .contains(&searchbar.text().to_string().to_lowercase())
+ || row
+ .property::("flatref-ref-name")
+ .to_lowercase()
+ .contains(&searchbar.text().to_string().to_lowercase())
+ {
+ row.set_property("visible", true);
+ searchbar.grab_focus();
+ } else {
+ row.set_property("visible", false);
+ }
+ } else {
+ row.set_property("visible", true);
+ }
+ }
+ counter = row.next_sibling();
+ }
+ }
+ ));
+
+ main_box.append(&searchbar);
+ main_box.append(&viewport_bin);
+ main_box.append(&bottom_bar);
+
+ flatpak_update_dialog.present();
+ main_box
+}
+
+fn is_widget_select_all_ready(parent_listbox: &impl adw::prelude::IsA) -> bool {
+ let mut is_ready = false;
+ let mut child_counter = parent_listbox.borrow().first_child();
+ while let Some(child) = child_counter {
+ let next_child = child.next_sibling();
+ let downcast = child.downcast::().unwrap();
+ if !downcast.flatref_marked() {
+ is_ready = true;
+ break;
+ }
+ child_counter = next_child
+ }
+ is_ready
+}
+
+fn is_all_children_unmarked(parent_listbox: &impl adw::prelude::IsA) -> bool {
+ let mut is_all_unmarked = true;
+ let mut child_counter = parent_listbox.borrow().first_child();
+ while let Some(child) = child_counter {
+ let next_child = child.next_sibling();
+ let downcast = child.downcast::().unwrap();
+ if downcast.flatref_marked() {
+ is_all_unmarked = false;
+ break;
+ }
+ child_counter = next_child
+ }
+ is_all_unmarked
+}
+
+fn set_all_flatpak_row_marks_to(parent_listbox: &impl adw::prelude::IsA, value: bool) {
+ let mut child_counter = parent_listbox.borrow().first_child();
+ while let Some(child) = child_counter {
+ let next_child = child.next_sibling();
+ let downcast = child.downcast::().unwrap();
+ downcast.set_flatref_marked(value);
+ child_counter = next_child
+ }
+}
diff --git a/src/bin/gui/flatpak_update_page/process.rs b/src/bin/gui/flatpak_update_page/process.rs
new file mode 100644
index 0000000..202ed5a
--- /dev/null
+++ b/src/bin/gui/flatpak_update_page/process.rs
@@ -0,0 +1,541 @@
+use crate::flatpak_ref_row::FlatpakRefRow;
+use adw::gio::SimpleAction;
+use adw::prelude::*;
+use gtk::glib::*;
+use gtk::*;
+use libflatpak::prelude::*;
+use libflatpak::Transaction;
+use pretty_bytes::converter::convert;
+use serde::Serialize;
+use serde_json::Value;
+use std::cell::RefCell;
+use std::fs::OpenOptions;
+use std::io::Write;
+use std::path::Path;
+use std::process::Command;
+use std::rc::Rc;
+use std::sync::{Arc, Mutex};
+use std::{fs, thread};
+use tokio::runtime::Runtime;
+
+struct FlatpakChangesInfo {
+ system_flatref_count: u64,
+ user_flatref_count: u64,
+ total_download_size: u64,
+ total_installed_size: i64,
+}
+#[derive(Serialize)]
+struct Exclusions {
+ exclusions: Vec,
+}
+
+impl FlatpakChangesInfo {
+ fn add_system(&mut self) {
+ self.system_flatref_count += 1;
+ }
+ fn add_user(&mut self) {
+ self.user_flatref_count += 1;
+ }
+
+ fn increase_total_download_size_by(&mut self, value: u64) {
+ self.total_download_size += value;
+ }
+
+ fn increase_total_installed_size_by(&mut self, value: u64) {
+ self.total_installed_size += value as i64;
+ }
+
+ fn decrease_total_installed_size_by(&mut self, value: u64) {
+ self.total_installed_size -= value as i64;
+ }
+}
+
+pub fn flatpak_process_update(
+ system_refs_for_upgrade_vec_opt: Option<&Vec>,
+ user_refs_for_upgrade_vec_opt: Option<&Vec>,
+ system_refs_for_upgrade_vec_all: &Vec,
+ user_refs_for_upgrade_vec_all: &Vec,
+ window: adw::ApplicationWindow,
+ retry_signal_action: &SimpleAction,
+) {
+ let cancellable = libflatpak::gio::Cancellable::NONE;
+ // Emulate Flatpak Full Upgrade to get transaction info
+ let mut flatpak_changes_struct = FlatpakChangesInfo {
+ system_flatref_count: 0,
+ user_flatref_count: 0,
+ total_download_size: 0,
+ total_installed_size: 0,
+ };
+
+ let mut system_refs_for_upgrade_vec = Vec::new();
+
+ match system_refs_for_upgrade_vec_opt {
+ Some(t) => {
+ for flatpak_row in t {
+ flatpak_changes_struct.add_system();
+ //
+ let installed_size_installed = flatpak_row.flatref_installed_size_installed();
+ let installed_size_remote = flatpak_row.flatref_installed_size_installed();
+ let installed_download_size = flatpak_row.flatref_download_size();
+ let ref_format = flatpak_row.flatref_ref_format();
+ //
+ flatpak_changes_struct.decrease_total_installed_size_by(installed_size_installed);
+ flatpak_changes_struct.increase_total_installed_size_by(installed_size_remote);
+ //
+ flatpak_changes_struct.increase_total_download_size_by(installed_download_size);
+ //
+ system_refs_for_upgrade_vec.push(ref_format);
+ }
+ }
+ None => {
+ for flatpak_row in system_refs_for_upgrade_vec_all {
+ flatpak_changes_struct.add_system();
+ //
+ let installed_size_installed = flatpak_row.flatref_installed_size_installed();
+ let installed_size_remote = flatpak_row.flatref_installed_size_installed();
+ let installed_download_size = flatpak_row.flatref_download_size();
+ let ref_format = flatpak_row.flatref_ref_format();
+ //
+ flatpak_changes_struct.decrease_total_installed_size_by(installed_size_installed);
+ flatpak_changes_struct.increase_total_installed_size_by(installed_size_remote);
+ //
+ flatpak_changes_struct.increase_total_download_size_by(installed_download_size);
+ //
+ system_refs_for_upgrade_vec.push(ref_format);
+ }
+ }
+ };
+
+ let mut user_refs_for_upgrade_vec = Vec::new();
+
+ match user_refs_for_upgrade_vec_opt {
+ Some(t) => {
+ for flatpak_row in t {
+ flatpak_changes_struct.add_user();
+ //
+ let installed_size_installed = flatpak_row.flatref_installed_size_installed();
+ let installed_size_remote = flatpak_row.flatref_installed_size_installed();
+ let installed_download_size = flatpak_row.flatref_download_size();
+ let ref_format = flatpak_row.flatref_ref_format();
+ //
+ flatpak_changes_struct.decrease_total_installed_size_by(installed_size_installed);
+ flatpak_changes_struct.increase_total_installed_size_by(installed_size_remote);
+ //
+ flatpak_changes_struct.increase_total_download_size_by(installed_download_size);
+ //
+ user_refs_for_upgrade_vec.push(ref_format);
+ }
+ }
+ None => {
+ for flatpak_row in user_refs_for_upgrade_vec_all {
+ flatpak_changes_struct.add_user();
+ //
+ let installed_size_installed = flatpak_row.flatref_installed_size_installed();
+ let installed_size_remote = flatpak_row.flatref_installed_size_installed();
+ let installed_download_size = flatpak_row.flatref_download_size();
+ let ref_format = flatpak_row.flatref_ref_format();
+ //
+ flatpak_changes_struct.decrease_total_installed_size_by(installed_size_installed);
+ flatpak_changes_struct.increase_total_installed_size_by(installed_size_remote);
+ //
+ flatpak_changes_struct.increase_total_download_size_by(installed_download_size);
+ //
+ user_refs_for_upgrade_vec.push(ref_format);
+ }
+ }
+ };
+
+ let flatpak_confirm_dialog_child_box =
+ Box::builder().orientation(Orientation::Vertical).build();
+
+ let flatpak_update_dialog_badges_size_group = SizeGroup::new(SizeGroupMode::Both);
+ let flatpak_update_dialog_badges_size_group0 = SizeGroup::new(SizeGroupMode::Both);
+ let flatpak_update_dialog_badges_size_group1 = SizeGroup::new(SizeGroupMode::Both);
+
+ flatpak_confirm_dialog_child_box.append(&create_color_badge(
+ &t!("system_flatref_count_badge_label"),
+ &flatpak_changes_struct.system_flatref_count.to_string(),
+ "background-accent-bg",
+ &flatpak_update_dialog_badges_size_group,
+ &flatpak_update_dialog_badges_size_group0,
+ &flatpak_update_dialog_badges_size_group1,
+ ));
+
+ flatpak_confirm_dialog_child_box.append(&create_color_badge(
+ &t!("user_flatref_count_badge_label"),
+ &flatpak_changes_struct.user_flatref_count.to_string(),
+ "background-accent-bg",
+ &flatpak_update_dialog_badges_size_group,
+ &flatpak_update_dialog_badges_size_group0,
+ &flatpak_update_dialog_badges_size_group1,
+ ));
+
+ flatpak_confirm_dialog_child_box.append(&create_color_badge(
+ &t!("total_download_size_badge_label"),
+ &convert(flatpak_changes_struct.total_download_size as f64),
+ "background-accent-bg",
+ &flatpak_update_dialog_badges_size_group,
+ &flatpak_update_dialog_badges_size_group0,
+ &flatpak_update_dialog_badges_size_group1,
+ ));
+
+ flatpak_confirm_dialog_child_box.append(&create_color_badge(
+ &t!("total_installed_size_badge_label"),
+ &convert(flatpak_changes_struct.total_installed_size as f64),
+ "background-accent-bg",
+ &flatpak_update_dialog_badges_size_group,
+ &flatpak_update_dialog_badges_size_group0,
+ &flatpak_update_dialog_badges_size_group1,
+ ));
+
+ let flatpak_confirm_dialog = adw::MessageDialog::builder()
+ .transient_for(&window)
+ .heading(t!("flatpak_confirm_dialog_heading"))
+ .body(t!("flatpak_confirm_dialog_body"))
+ .extra_child(&flatpak_confirm_dialog_child_box)
+ .build();
+
+ flatpak_confirm_dialog.add_response(
+ "flatpak_confirm_dialog_cancel",
+ &t!("flatpak_confirm_dialog_cancel_label").to_string(),
+ );
+
+ flatpak_confirm_dialog.add_response(
+ "flatpak_confirm_dialog_confirm",
+ &t!("flatpak_confirm_dialog_confirm_label").to_string(),
+ );
+
+ flatpak_confirm_dialog.set_response_appearance(
+ "flatpak_confirm_dialog_confirm",
+ adw::ResponseAppearance::Destructive,
+ );
+
+ flatpak_confirm_dialog.set_default_response(Some("flatpak_confirm_dialog_confirm"));
+ flatpak_confirm_dialog.set_close_response("flatpak_confirm_dialog_cancel");
+
+ let retry_signal_action0 = retry_signal_action.clone();
+ flatpak_confirm_dialog
+ .clone()
+ .choose(None::<&gio::Cancellable>, move |choice| {
+ if choice == "flatpak_confirm_dialog_confirm" {
+ flatpak_run_transactions(
+ system_refs_for_upgrade_vec,
+ user_refs_for_upgrade_vec,
+ window,
+ &retry_signal_action0,
+ );
+ }
+ });
+}
+
+fn flatpak_run_transactions(
+ system_refs_for_upgrade_vec: Vec,
+ user_refs_for_upgrade_vec: Vec,
+ window: adw::ApplicationWindow,
+ retry_signal_action: &SimpleAction,
+) {
+ let (transaction_percent_sender, transaction_percent_receiver) =
+ async_channel::unbounded::();
+ let transaction_percent_sender = transaction_percent_sender.clone();
+ let (transaction_status_sender, transaction_status_receiver) =
+ async_channel::unbounded::();
+ let transaction_status_sender = transaction_status_sender.clone();
+
+ thread::spawn(move || {
+ let cancellable_no = libflatpak::gio::Cancellable::NONE;
+
+ let transaction_status_sender0 = transaction_status_sender.clone();
+ let transaction_percent_sender0 = transaction_percent_sender.clone();
+
+ let transaction_run_closure =
+ move |transaction: &libflatpak::Transaction,
+ transaction_operation: &libflatpak::TransactionOperation,
+ transaction_progress: &libflatpak::TransactionProgress| {
+ let transaction_status_sender = transaction_status_sender0.clone();
+ let transaction_percent_sender = transaction_percent_sender0.clone();
+ transaction_progress.connect_changed(clone!(@strong transaction_progress, @strong transaction_operation => move |_| {
+ let status_message = format!("{}: {}\n{}: {}\n{}: {}/{}\n{}: {}", t!("flatpak_ref"), transaction_operation.get_ref().unwrap_or(libflatpak::glib::GString::from_string_unchecked("Unknown".to_owned())), t!("flatpak_status") ,transaction_progress.status().unwrap_or(libflatpak::glib::GString::from_string_unchecked("Unknown".to_owned())), t!("flatpak_transaction_bytes_transferred"), convert(transaction_progress.bytes_transferred() as f64), convert(transaction_operation.download_size() as f64), t!("flatpak_transaction_installed_size"), convert(transaction_operation.installed_size() as f64));
+ transaction_status_sender.send_blocking(status_message).expect("transaction_status_receiver closed!");
+ transaction_percent_sender.send_blocking(transaction_progress.progress().try_into().unwrap_or(0)).expect("transaction_percent_receiver closed!");
+ }));
+ };
+
+ //
+
+ let flatpak_system_installation =
+ libflatpak::Installation::new_system(cancellable_no).unwrap();
+ let flatpak_system_transaction =
+ libflatpak::Transaction::for_installation(&flatpak_system_installation, cancellable_no)
+ .unwrap();
+
+ for ref_format in system_refs_for_upgrade_vec {
+ flatpak_system_transaction
+ .add_update(&ref_format, &[], None)
+ .unwrap();
+ }
+
+ flatpak_system_transaction.connect_new_operation(transaction_run_closure.clone());
+
+ match flatpak_system_transaction.run(cancellable_no) {
+ Ok(_) => {}
+ Err(e) => {
+ transaction_status_sender
+ .send_blocking(e.to_string())
+ .expect("transaction_sync_status_receiver closed");
+ transaction_status_sender
+ .send_blocking("FN_OVERRIDE_FAILED".to_owned())
+ .expect("transaction_sync_status_receiver closed");
+ panic!("{}", e);
+ }
+ }
+
+ //
+
+ let flatpak_user_installation = libflatpak::Installation::new_user(cancellable_no).unwrap();
+ let flatpak_user_transaction =
+ libflatpak::Transaction::for_installation(&flatpak_user_installation, cancellable_no)
+ .unwrap();
+
+ flatpak_user_transaction.connect_new_operation(transaction_run_closure);
+
+ for ref_format in user_refs_for_upgrade_vec {
+ flatpak_user_transaction
+ .add_update(&ref_format, &[], None)
+ .unwrap();
+ }
+
+ match flatpak_user_transaction.run(cancellable_no) {
+ Ok(_) => {
+ transaction_status_sender
+ .send_blocking("FN_OVERRIDE_SUCCESSFUL".to_owned())
+ .expect("transaction_sync_status_receiver closed");
+ }
+ Err(e) => {
+ transaction_status_sender
+ .send_blocking(e.to_string())
+ .expect("transaction_sync_status_receiver closed");
+ transaction_status_sender
+ .send_blocking("FN_OVERRIDE_FAILED".to_owned())
+ .expect("transaction_sync_status_receiver closed");
+ panic!("{}", e);
+ }
+ }
+ });
+
+ let log_file_path = format!(
+ "/tmp/pika-flatpak-transaction_{}.log",
+ chrono::offset::Local::now().format("%Y-%m-%d_%H:%M")
+ );
+
+ let log_file_path_clone0 = log_file_path.clone();
+
+ if !Path::new(&log_file_path).exists() {
+ match fs::File::create(&log_file_path) {
+ Ok(_) => {}
+ Err(_) => {
+ eprintln!("Warning: {} file couldn't be created", log_file_path);
+ }
+ };
+ }
+
+ let flatpak_transaction_dialog_child_box =
+ Box::builder().orientation(Orientation::Vertical).build();
+
+ let flatpak_transaction_dialog_progress_bar =
+ ProgressBar::builder().show_text(true).hexpand(true).build();
+
+ let flatpak_transaction_dialog_spinner = Spinner::builder()
+ .hexpand(true)
+ .valign(Align::Start)
+ .halign(Align::Center)
+ .spinning(true)
+ .height_request(128)
+ .width_request(128)
+ .build();
+
+ flatpak_transaction_dialog_child_box.append(&flatpak_transaction_dialog_spinner);
+ flatpak_transaction_dialog_child_box.append(&flatpak_transaction_dialog_progress_bar);
+
+ let flatpak_transaction_dialog = adw::MessageDialog::builder()
+ .transient_for(&window)
+ .extra_child(&flatpak_transaction_dialog_child_box)
+ .heading(t!("flatpak_transaction_dialog_heading"))
+ .width_request(500)
+ .build();
+
+ flatpak_transaction_dialog.add_response(
+ "flatpak_transaction_dialog_ok",
+ &t!("flatpak_transaction_dialog_ok_label").to_string(),
+ );
+
+ let flatpak_transaction_dialog_child_box_done =
+ Box::builder().orientation(Orientation::Vertical).build();
+
+ let flatpak_transaction_log_image = Image::builder()
+ .pixel_size(128)
+ .halign(Align::Center)
+ .build();
+
+ let flatpak_transaction_log_button = Button::builder()
+ .label(t!("flatpak_transaction_dialog_open_log_file_label"))
+ .halign(Align::Center)
+ .margin_start(15)
+ .margin_end(15)
+ .margin_top(15)
+ .margin_bottom(15)
+ .build();
+
+ flatpak_transaction_dialog_child_box_done.append(&flatpak_transaction_log_image);
+ flatpak_transaction_dialog_child_box_done.append(&flatpak_transaction_log_button);
+
+ flatpak_transaction_dialog.set_response_enabled("flatpak_transaction_dialog_ok", false);
+ flatpak_transaction_dialog.set_close_response("flatpak_transaction_dialog_ok");
+
+ let transaction_percent_server_context = MainContext::default();
+ // The main loop executes the asynchronous block
+ transaction_percent_server_context.spawn_local(clone!(
+ #[weak]
+ flatpak_transaction_dialog_progress_bar,
+ async move {
+ while let Ok(state) = transaction_percent_receiver.recv().await {
+ flatpak_transaction_dialog_progress_bar.set_fraction((state as f32 / 100.0).into());
+ }
+ }
+ ));
+
+ let transaction_status_server_context = MainContext::default();
+ // The main loop executes the asynchronous block
+ transaction_status_server_context.spawn_local(clone!(
+ #[weak]
+ flatpak_transaction_dialog,
+ #[weak]
+ flatpak_transaction_dialog_child_box,
+ #[strong]
+ flatpak_transaction_dialog_child_box_done,
+ #[strong]
+ flatpak_transaction_log_image,
+ async move {
+ while let Ok(state) = transaction_status_receiver.recv().await {
+ match state.as_ref() {
+ "FN_OVERRIDE_SUCCESSFUL" => {
+ flatpak_transaction_dialog_child_box.set_visible(false);
+ flatpak_transaction_log_image.set_icon_name(Some("face-cool-symbolic"));
+ flatpak_transaction_dialog
+ .set_extra_child(Some(&flatpak_transaction_dialog_child_box_done));
+ flatpak_transaction_dialog.set_title(Some(
+ &t!("flatpak_transaction_dialog_status_successful").to_string(),
+ ));
+ flatpak_transaction_dialog
+ .set_response_enabled("flatpak_transaction_dialog_ok", true);
+ }
+ "FN_OVERRIDE_FAILED" => {
+ flatpak_transaction_dialog_child_box.set_visible(false);
+ flatpak_transaction_log_image.set_icon_name(Some("dialog-error-symbolic"));
+ flatpak_transaction_dialog
+ .set_extra_child(Some(&flatpak_transaction_dialog_child_box_done));
+ flatpak_transaction_dialog.set_title(Some(
+ &t!("flatpak_transaction_dialog_status_failed").to_string(),
+ ));
+ flatpak_transaction_dialog
+ .set_response_enabled("flatpak_transaction_dialog_ok", true);
+ flatpak_transaction_dialog
+ .set_response_enabled("flatpak_transaction_dialog_open_log_file", true);
+ }
+ _ => {
+ flatpak_transaction_dialog.set_body(&state);
+ let mut log_file = OpenOptions::new()
+ .write(true)
+ .append(true)
+ .open(&log_file_path)
+ .unwrap();
+
+ if let Err(e) = writeln!(
+ log_file,
+ "[{}] {}",
+ chrono::offset::Local::now().format("%Y/%m/%d_%H:%M"),
+ state
+ ) {
+ eprintln!("Couldn't write to file: {}", e);
+ }
+ }
+ }
+ }
+ }
+ ));
+
+ let retry_signal_action0 = retry_signal_action.clone();
+
+ flatpak_transaction_log_button.connect_clicked(move |_| {
+ let _ = Command::new("xdg-open")
+ .arg(log_file_path_clone0.to_owned())
+ .spawn();
+ });
+
+ flatpak_transaction_dialog.choose(None::<&gio::Cancellable>, move |choice| {
+ match choice.as_str() {
+ "flatpak_transaction_dialog_ok" => {
+ retry_signal_action0.activate(None);
+ }
+ _ => {}
+ }
+ });
+}
+
+fn create_color_badge(
+ label0_text: &str,
+ label1_text: &str,
+ css_style: &str,
+ group_size: &SizeGroup,
+ group_size0: &SizeGroup,
+ group_size1: &SizeGroup,
+) -> ListBox {
+ let badge_box = Box::builder().build();
+
+ let label0 = Label::builder()
+ .label(label0_text)
+ .margin_start(5)
+ .margin_end(5)
+ .margin_bottom(1)
+ .margin_top(1)
+ .valign(Align::Center)
+ .halign(Align::Center)
+ .hexpand(true)
+ .vexpand(true)
+ .build();
+ group_size0.add_widget(&label0);
+
+ let label_separator = Separator::builder().build();
+
+ let label1 = Label::builder()
+ .label(label1_text)
+ .margin_start(3)
+ .margin_end(0)
+ .margin_bottom(1)
+ .margin_top(1)
+ .valign(Align::Center)
+ .halign(Align::Center)
+ .hexpand(true)
+ .vexpand(true)
+ .build();
+ group_size1.add_widget(&label1);
+
+ label1.add_css_class(css_style);
+
+ badge_box.append(&label0);
+ badge_box.append(&label_separator);
+ badge_box.append(&label1);
+
+ let boxedlist = ListBox::builder()
+ .selection_mode(SelectionMode::None)
+ .halign(Align::Center)
+ .margin_start(10)
+ .margin_end(10)
+ .margin_bottom(10)
+ .margin_top(10)
+ .build();
+
+ boxedlist.add_css_class("boxed-list");
+ boxedlist.append(&badge_box);
+ group_size.add_widget(&boxedlist);
+ boxedlist
+}
diff --git a/src/bin/gui/main.rs b/src/bin/gui/main.rs
new file mode 100644
index 0000000..bbbc648
--- /dev/null
+++ b/src/bin/gui/main.rs
@@ -0,0 +1,56 @@
+mod apt_package_row;
+mod apt_update_page;
+mod build_ui;
+mod config;
+mod flatpak_ref_row;
+mod flatpak_update_page;
+
+use crate::config::APP_ID;
+use adw::prelude::*;
+use adw::*;
+use build_ui::build_ui;
+use gdk::Display;
+use gtk::*;
+use std::boxed::Box;
+use std::env;
+
+// Init translations for current crate.
+#[macro_use]
+extern crate rust_i18n;
+i18n!("locales", fallback = "en_US");
+
+/// main function
+fn main() {
+ let current_locale = match env::var_os("LANG") {
+ Some(v) => v
+ .into_string()
+ .unwrap()
+ .chars()
+ .take_while(|&ch| ch != '.')
+ .collect::(),
+ None => panic!("$LANG is not set"),
+ };
+ rust_i18n::set_locale(¤t_locale);
+ let application = adw::Application::new(Some(APP_ID), Default::default());
+ application.connect_startup(|app| {
+ // The CSS "magic" happens here.
+ let provider = CssProvider::new();
+ provider.load_from_string(include_str!("style.css"));
+ // We give the CssProvided to the default screen so the CSS rules we added
+ // can be applied to our window.
+ gtk::style_context_add_provider_for_display(
+ &Display::default().expect("Could not connect to a display."),
+ &provider,
+ STYLE_PROVIDER_PRIORITY_APPLICATION,
+ );
+ app.connect_activate(build_ui);
+ });
+
+ //if get_current_username().unwrap() == "pikaos" {
+ // application.run();
+ //} else {
+ // println!("Error: This program can only be run via pikaos user");
+ // std::process::exit(1)
+ //}
+ application.run();
+}
diff --git a/src/bin/gui/style.css b/src/bin/gui/style.css
new file mode 100644
index 0000000..f1fbea2
--- /dev/null
+++ b/src/bin/gui/style.css
@@ -0,0 +1,67 @@
+.symbolic-accent-bg {
+ color: @accent_bg_color;
+}
+
+.size-20-font {
+ font-size: 20px;
+}
+
+.rounded-all-25 {
+ border-radius: 25px;
+}
+
+.round-all-scroll {
+ border-top-right-radius: 15px;
+ border-top-left-radius: 15px;
+ border-bottom-right-radius: 15px;
+ border-bottom-left-radius: 15px;
+ padding-top: 3px;
+ padding-right: 3px;
+ padding-left: 3px;
+ padding-bottom: 3px;
+}
+
+.background-accent-bg {
+ background: @accent_bg_color;
+ border-radius: 10px;
+ padding: 5px;
+}
+
+.background-green-bg {
+ background: green;
+ border-radius: 10px;
+ padding: 5px;
+}
+
+.background-red-bg {
+ background: #ff2a03;
+ border-radius: 10px;
+ padding: 5px;
+}
+
+.round-border-only-top {
+ border-top-right-radius: 15px;
+ border-top-left-radius: 15px;
+ border-bottom-right-radius: 0px;
+ border-bottom-left-radius: 0px;
+}
+
+.round-border-only-bottom {
+ border-top-right-radius: 0px;
+ border-top-left-radius: 0px;
+ border-bottom-right-radius: 15px;
+ border-bottom-left-radius: 15px;
+}
+
+.destructive-color-text {
+ color: @destructive_bg_color;
+}
+
+.success-color-text {
+ color: @success_bg_color;
+}
+
+.size-20-bold-text {
+ font-weight: 800;
+ font-size: 20px;
+}
\ No newline at end of file
diff --git a/src/lib/apt_install_progress_socket/mod.rs b/src/lib/apt_install_progress_socket/mod.rs
new file mode 100644
index 0000000..9a740af
--- /dev/null
+++ b/src/lib/apt_install_progress_socket/mod.rs
@@ -0,0 +1,83 @@
+use crate::pika_unixsocket_tools::*;
+use rust_apt::progress::DynInstallProgress;
+use std::process::exit;
+use tokio::io::AsyncWriteExt;
+use tokio::net::UnixStream;
+use tokio::runtime::Runtime;
+
+pub struct AptInstallProgressSocket<'a> {
+ percent_socket_path: &'a str,
+ status_socket_path: &'a str,
+}
+
+impl<'a> AptInstallProgressSocket<'a> {
+ /// Returns a new default progress instance.
+ pub fn new(percent_socket_path: &'a str, status_socket_path: &'a str) -> Self {
+ let progress = Self {
+ percent_socket_path: percent_socket_path,
+ status_socket_path: status_socket_path,
+ };
+ progress
+ }
+}
+
+impl<'a> DynInstallProgress for AptInstallProgressSocket<'a> {
+ fn status_changed(
+ &mut self,
+ _pkgname: String,
+ steps_done: u64,
+ total_steps: u64,
+ action: String,
+ ) {
+ let progress_percent: f32 = (steps_done as f32 * 100.0) / total_steps as f32;
+ Runtime::new().unwrap().block_on(send_progress_percent(
+ progress_percent,
+ self.percent_socket_path,
+ ));
+ Runtime::new()
+ .unwrap()
+ .block_on(send_progress_status(&action, self.status_socket_path));
+ }
+
+ fn error(&mut self, pkgname: String, _steps_done: u64, _total_steps: u64, error: String) {
+ let message = format!("dpkg failure on {}: {}", pkgname, error);
+ eprintln!("{}", &message);
+ Runtime::new()
+ .unwrap()
+ .block_on(send_progress_status(&message, self.status_socket_path));
+ Runtime::new()
+ .unwrap()
+ .block_on(send_failed_to_socket(self.percent_socket_path));
+ Runtime::new()
+ .unwrap()
+ .block_on(send_failed_to_socket(self.status_socket_path));
+ exit(53)
+ }
+}
+
+async fn send_progress_percent(progress_f32: f32, socket_path: &str) {
+ // Connect to the Unix socket
+ let mut stream = UnixStream::connect(socket_path)
+ .await
+ .expect("Could not connect to server");
+
+ let message = progress_f32.to_string();
+ // Send the message to the server
+ stream
+ .write_all(message.as_bytes())
+ .await
+ .expect("Failed to write to stream");
+}
+
+async fn send_progress_status(message: &str, socket_path: &str) {
+ // Connect to the Unix socket
+ let mut stream = UnixStream::connect(socket_path)
+ .await
+ .expect("Could not connect to server");
+
+ // Send the message to the server
+ stream
+ .write_all(message.as_bytes())
+ .await
+ .expect("Failed to write to stream");
+}
diff --git a/src/lib/apt_update_progress_socket/mod.rs b/src/lib/apt_update_progress_socket/mod.rs
new file mode 100644
index 0000000..f156350
--- /dev/null
+++ b/src/lib/apt_update_progress_socket/mod.rs
@@ -0,0 +1,152 @@
+use crate::pika_unixsocket_tools::*;
+use rust_apt::progress::DynAcquireProgress;
+use rust_apt::raw::{AcqTextStatus, ItemDesc, PkgAcquire};
+use std::process::exit;
+use tokio::io::AsyncWriteExt;
+use tokio::net::UnixStream;
+use tokio::runtime::Runtime;
+
+pub struct AptUpdateProgressSocket<'a> {
+ pulse_interval: usize,
+ percent_socket_path: &'a str,
+ status_socket_path: &'a str,
+}
+
+impl<'a> AptUpdateProgressSocket<'a> {
+ /// Returns a new default progress instance.
+ pub fn new(percent_socket_path: &'a str, status_socket_path: &'a str) -> Self {
+ let progress = Self {
+ pulse_interval: 0,
+ percent_socket_path: percent_socket_path,
+ status_socket_path: status_socket_path,
+ };
+ progress
+ }
+}
+
+impl<'a> DynAcquireProgress for AptUpdateProgressSocket<'a> {
+ /// Used to send the pulse interval to the apt progress class.
+ ///
+ /// Pulse Interval is in microseconds.
+ ///
+ /// Example: 1 second = 1000000 microseconds.
+ ///
+ /// Apt default is 500000 microseconds or 0.5 seconds.
+ ///
+ /// The higher the number, the less frequent pulse updates will be.
+ ///
+ /// Pulse Interval set to 0 assumes the apt defaults.
+ fn pulse_interval(&self) -> usize {
+ self.pulse_interval
+ }
+
+ /// Called when an item is confirmed to be up-to-date.
+ ///
+ /// Prints out the short description and the expected size.
+ fn hit(&mut self, item: &ItemDesc) {
+ let message = format!("Up-to-date: {} {}", item.description(), item.short_desc());
+ println!("{}", message);
+ Runtime::new()
+ .unwrap()
+ .block_on(send_progress_status(&message, self.status_socket_path));
+ }
+
+ /// Called when an Item has started to download
+ ///
+ /// Prints out the short description and the expected size.
+ fn fetch(&mut self, item: &ItemDesc) {
+ let message = format!("Fetching: {} {}", item.description(), item.short_desc());
+ println!("{}", message);
+ Runtime::new()
+ .unwrap()
+ .block_on(send_progress_status(&message, self.status_socket_path));
+ }
+
+ /// Called when an item is successfully and completely fetched.
+ ///
+ /// We don't print anything here to remain consistent with apt.
+ fn done(&mut self, item: &ItemDesc) {
+ let message = format!("Downloading: {} {}", item.description(), item.short_desc());
+ println!("{}", message);
+ Runtime::new()
+ .unwrap()
+ .block_on(send_progress_status(&message, self.status_socket_path));
+ }
+
+ /// Called when progress has started.
+ ///
+ /// Start does not pass information into the method.
+ ///
+ /// We do not print anything here to remain consistent with apt.
+ fn start(&mut self) {}
+
+ /// Called when progress has finished.
+ ///
+ /// Stop does not pass information into the method.
+ ///
+ /// prints out the bytes downloaded and the overall average line speed.
+ fn stop(&mut self, _status: &AcqTextStatus) {}
+
+ /// Called when an Item fails to download.
+ ///
+ /// Print out the ErrorText for the Item.
+ fn fail(&mut self, item: &ItemDesc) {
+ let message = format!(
+ "Download Failed: {} {}",
+ item.description(),
+ item.short_desc()
+ );
+ eprintln!("{}", &message);
+ Runtime::new()
+ .unwrap()
+ .block_on(send_progress_status(&message, self.status_socket_path));
+ Runtime::new()
+ .unwrap()
+ .block_on(send_failed_to_socket(self.percent_socket_path));
+ Runtime::new()
+ .unwrap()
+ .block_on(send_failed_to_socket(self.status_socket_path));
+ exit(53)
+ }
+
+ /// Called periodically to provide the overall progress information
+ ///
+ /// Draws the current progress.
+ /// Each line has an overall percent meter and a per active item status
+ /// meter along with an overall bandwidth and ETA indicator.
+ fn pulse(&mut self, status: &AcqTextStatus, _owner: &PkgAcquire) {
+ let progress_percent: f32 =
+ (status.current_bytes() as f32 * 100.0) / status.total_bytes() as f32;
+ Runtime::new().unwrap().block_on(send_progress_percent(
+ progress_percent,
+ self.percent_socket_path,
+ ));
+ }
+}
+
+async fn send_progress_percent(progress_f32: f32, socket_path: &str) {
+ // Connect to the Unix socket
+ let mut stream = UnixStream::connect(socket_path)
+ .await
+ .expect("Could not connect to server");
+
+ let message = progress_f32.to_string();
+ // Send the message to the server
+ stream
+ .write_all(message.as_bytes())
+ .await
+ .expect("Failed to write to stream");
+}
+
+async fn send_progress_status(message: &str, socket_path: &str) {
+ // Connect to the Unix socket
+ let mut stream = UnixStream::connect(socket_path)
+ .await
+ .expect("Could not connect to server");
+
+ // Send the message to the server
+ stream
+ .write_all(message.as_bytes())
+ .await
+ .expect("Failed to write to stream");
+}
diff --git a/src/lib/lib.rs b/src/lib/lib.rs
new file mode 100644
index 0000000..aac95e7
--- /dev/null
+++ b/src/lib/lib.rs
@@ -0,0 +1,3 @@
+pub mod apt_install_progress_socket;
+pub mod apt_update_progress_socket;
+pub mod pika_unixsocket_tools;
diff --git a/src/lib/pika_unixsocket_tools/mod.rs b/src/lib/pika_unixsocket_tools/mod.rs
new file mode 100644
index 0000000..a63b772
--- /dev/null
+++ b/src/lib/pika_unixsocket_tools/mod.rs
@@ -0,0 +1,177 @@
+use chrono;
+use std::fs;
+use std::fs::OpenOptions;
+use std::io::Write;
+use std::path::Path;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::net::{UnixListener, UnixStream};
+use tokio::task;
+
+pub async fn send_successful_to_socket(socket_path: &str) {
+ // Connect to the Unix socket
+ let mut stream = UnixStream::connect(socket_path)
+ .await
+ .expect("Could not connect to server");
+
+ let message = "FN_OVERRIDE_SUCCESSFUL";
+
+ // Send the message to the server
+ stream
+ .write_all(message.as_bytes())
+ .await
+ .expect("Failed to write to stream");
+}
+
+pub async fn send_failed_to_socket(socket_path: &str) {
+ // Connect to the Unix socket
+ let mut stream = UnixStream::connect(socket_path)
+ .await
+ .expect("Could not connect to server");
+
+ let message = "FN_OVERRIDE_FAILED";
+
+ // Send the message to the server
+ stream
+ .write_all(message.as_bytes())
+ .await
+ .expect("Failed to write to stream");
+}
+
+// Function to handle a single client connection
+pub async fn handle_client(
+ mut stream: UnixStream,
+ buffer_sender: async_channel::Sender,
+ log_file_path: String,
+) {
+ // Buffer to store incoming data
+ let mut buffer = [0; 1024];
+
+ if !Path::new(&log_file_path).exists() {
+ match fs::File::create(&log_file_path) {
+ Ok(_) => {}
+ Err(_) => {
+ eprintln!("Warning: {} file couldn't be created", log_file_path);
+ }
+ };
+ }
+
+ // Read data from the stream
+ match stream.read(&mut buffer).await {
+ Ok(size) => {
+ let message = String::from_utf8_lossy(&buffer[..size]).to_string();
+ // Send to async buffer sender
+ buffer_sender
+ .send_blocking(message.clone())
+ .expect("Buffer channel closed");
+ // Write to log file
+ let mut log_file = OpenOptions::new()
+ .write(true)
+ .append(true)
+ .open(&log_file_path)
+ .unwrap();
+
+ if let Err(e) = writeln!(
+ log_file,
+ "[{}] {}",
+ chrono::offset::Local::now().format("%Y/%m/%d_%H:%M"),
+ message
+ ) {
+ eprintln!("Couldn't write to file: {}", e);
+ }
+ }
+ Err(e) => {
+ // Print error message if reading fails
+ eprintln!("Failed to read from stream: {}", e);
+ }
+ }
+}
+
+pub async fn start_socket_server(
+ buffer_sender: async_channel::Sender,
+ socket_path: &str,
+ log_file_path: &str,
+) {
+ // Remove the socket file if it already exists
+ if Path::new(socket_path).exists() {
+ fs::remove_file(socket_path).expect("Could not remove existing socket file");
+ }
+
+ // Bind the Unix listener to the socket path
+ let listener = UnixListener::bind(socket_path).expect("Could not bind");
+
+ println!("Server listening on {}", socket_path);
+
+ // Loop to accept incoming connections
+ loop {
+ // Accept an incoming connection
+ match listener.accept().await {
+ Ok((stream, _)) => {
+ // Handle the connection in a separate task
+ task::spawn(handle_client(
+ stream,
+ buffer_sender.clone(),
+ log_file_path.to_owned(),
+ ));
+ }
+ Err(e) => {
+ // Print error message if a connection fails
+ eprintln!("Connection failed: {}", e);
+ }
+ }
+ }
+}
+
+pub async fn handle_client_no_log(
+ mut stream: UnixStream,
+ buffer_sender: async_channel::Sender,
+) {
+ // Buffer to store incoming data
+ let mut buffer = [0; 1024];
+
+ // Read data from the stream
+ match stream.read(&mut buffer).await {
+ Ok(size) => {
+ let message = String::from_utf8_lossy(&buffer[..size]).to_string();
+ // Write to log file
+
+ // Send to async buffer sender
+ buffer_sender
+ .send_blocking(message)
+ .expect("Buffer channel closed")
+ }
+ Err(e) => {
+ // Print error message if reading fails
+ eprintln!("Failed to read from stream: {}", e);
+ }
+ }
+}
+
+pub async fn start_socket_server_no_log(
+ buffer_sender: async_channel::Sender,
+ socket_path: &str,
+) {
+ // Remove the socket file if it already exists
+ if Path::new(socket_path).exists() {
+ fs::remove_file(socket_path).expect("Could not remove existing socket file");
+ }
+
+ // Bind the Unix listener to the socket path
+ let listener = UnixListener::bind(socket_path).expect("Could not bind");
+
+ println!("Server listening on {}", socket_path);
+
+ // Loop to accept incoming connections
+ loop {
+ // Accept an incoming connection
+ match listener.accept().await {
+ Ok((stream, _)) => {
+ // Handle the connection in a separate task
+ task::spawn(handle_client_no_log(stream, buffer_sender.clone()));
+ }
+ Err(e) => {
+ // Print error message if a connection fails
+ eprintln!("Connection failed: {}", e);
+ }
+ }
+ }
+}