diff --git a/Cargo.lock b/Cargo.lock index 300c5249..d6b8e411 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,17 @@ dependencies = [ "winit", ] +[[package]] +name = "accounts-zbus" +version = "0.1.0" +source = "git+https://github.com/pop-os/dbus-settings-bindings#0eee63a96c8b1f6555ca797b5c12545c372b1a1b" +dependencies = [ + "futures-channel", + "futures-util", + "tracing", + "zbus 4.4.0", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -445,7 +456,7 @@ checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.2.0", + "fastrand 2.3.0", "futures-lite 2.5.0", "slab", ] @@ -494,7 +505,7 @@ dependencies = [ "futures-lite 2.5.0", "parking", "polling 3.7.4", - "rustix 0.38.41", + "rustix 0.38.42", "slab", "tracing", "windows-sys 0.59.0", @@ -544,7 +555,7 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.41", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -563,7 +574,7 @@ dependencies = [ "cfg-if", "event-listener 5.3.1", "futures-lite 2.5.0", - "rustix 0.38.41", + "rustix 0.38.42", "tracing", ] @@ -590,7 +601,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.41", + "rustix 0.38.42", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -624,7 +635,7 @@ name = "atomicwrites" version = "0.4.2" source = "git+https://github.com/jackpot51/rust-atomicwrites#043ab4859d53ffd3d55334685303d8df39c9f768" dependencies = [ - "rustix 0.38.41", + "rustix 0.38.42", "tempfile", "windows-sys 0.48.0", ] @@ -857,7 +868,7 @@ dependencies = [ [[package]] name = "bluez-zbus" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#62100129240d164e39fff16bda34faad520936de" +source = "git+https://github.com/pop-os/dbus-settings-bindings#0eee63a96c8b1f6555ca797b5c12545c372b1a1b" dependencies = [ "futures-channel", "futures-util", @@ -996,7 +1007,7 @@ dependencies = [ "bitflags 2.6.0", "log", "polling 3.7.4", - "rustix 0.38.41", + "rustix 0.38.42", "slab", "thiserror 1.0.69", ] @@ -1008,16 +1019,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix 0.38.41", + "rustix 0.38.42", "wayland-backend", "wayland-client", ] [[package]] name = "cc" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "jobserver", "libc", @@ -1080,9 +1091,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1106,9 +1117,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -1116,9 +1127,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -1140,9 +1151,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clipboard-win" @@ -1348,18 +1359,18 @@ dependencies = [ [[package]] name = "const_format" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" dependencies = [ "proc-macro2", "quote", @@ -1462,7 +1473,7 @@ dependencies = [ [[package]] name = "cosmic-comp-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-comp#e3b41c5c554cc5aadf44183866ff4df7c2957d09" +source = "git+https://github.com/pop-os/cosmic-comp#9b50d0f50606891440cf9844304ae3c8b14034db" dependencies = [ "cosmic-config", "input", @@ -1472,7 +1483,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1494,7 +1505,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "quote", "syn 1.0.109", @@ -1503,7 +1514,7 @@ dependencies = [ [[package]] name = "cosmic-dbus-networkmanager" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#62100129240d164e39fff16bda34faad520936de" +source = "git+https://github.com/pop-os/dbus-settings-bindings#0eee63a96c8b1f6555ca797b5c12545c372b1a1b" dependencies = [ "bitflags 2.6.0", "derive_builder", @@ -1514,6 +1525,19 @@ dependencies = [ "zvariant 4.2.0", ] +[[package]] +name = "cosmic-freedesktop-icons" +version = "0.2.6" +source = "git+https://github.com/pop-os/freedesktop-icons#3a202b17c8d4d63e2f073210260773855276c604" +dependencies = [ + "dirs", + "once_cell", + "rust-ini", + "thiserror 1.0.69", + "tracing", + "xdg", +] + [[package]] name = "cosmic-idle-config" version = "0.1.0" @@ -1537,7 +1561,7 @@ dependencies = [ [[package]] name = "cosmic-panel-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-panel#734e3fafe2eafea1cb3cece7d0b4ddf72a2c4323" +source = "git+https://github.com/pop-os/cosmic-panel#1c9c4e2a2cf27efd0ca77b5ec21bc6f7fa92d9da" dependencies = [ "anyhow", "cosmic-config", @@ -1594,6 +1618,7 @@ dependencies = [ name = "cosmic-settings" version = "0.1.0" dependencies = [ + "accounts-zbus", "anyhow", "as-result", "ashpd 0.9.2", @@ -1642,6 +1667,7 @@ dependencies = [ "regex", "ron", "rust-embed", + "rustix 0.38.42", "secure-string", "serde", "slab", @@ -1660,12 +1686,13 @@ dependencies = [ "ustr", "xkb-data", "zbus 4.4.0", + "zbus_polkit", ] [[package]] name = "cosmic-settings-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-settings-daemon#f079ab7f98132787de385934915547f333dfbddc" +source = "git+https://github.com/pop-os/cosmic-settings-daemon#e87f5859a2e932ef14746affd01cc67ed4f93cf1" dependencies = [ "cosmic-config", "serde", @@ -1678,7 +1705,7 @@ dependencies = [ [[package]] name = "cosmic-settings-daemon" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#62100129240d164e39fff16bda34faad520936de" +source = "git+https://github.com/pop-os/dbus-settings-bindings#0eee63a96c8b1f6555ca797b5c12545c372b1a1b" dependencies = [ "zbus 4.4.0", ] @@ -1710,7 +1737,7 @@ dependencies = [ "libpulse-binding", "log", "pipewire", - "rustix 0.38.41", + "rustix 0.38.42", "secure-string", "thiserror 1.0.69", "tokio", @@ -1764,7 +1791,7 @@ dependencies = [ "rayon", "rustc-hash 1.1.0", "rustybuzz", - "self_cell 1.0.4", + "self_cell 1.1.0", "smol_str", "swash", "sys-locale", @@ -1778,7 +1805,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "almost", "cosmic-config", @@ -2186,7 +2213,7 @@ dependencies = [ "bytemuck", "drm-ffi", "drm-fourcc", - "rustix 0.38.41", + "rustix 0.38.42", ] [[package]] @@ -2196,7 +2223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" dependencies = [ "drm-sys", - "rustix 0.38.41", + "rustix 0.38.42", ] [[package]] @@ -2382,15 +2409,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] @@ -2705,7 +2732,7 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 2.2.0", + "fastrand 2.3.0", "futures-core", "futures-io", "parking", @@ -3052,7 +3079,7 @@ checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" [[package]] name = "hostname1-zbus" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#62100129240d164e39fff16bda34faad520936de" +source = "git+https://github.com/pop-os/dbus-settings-bindings#0eee63a96c8b1f6555ca797b5c12545c372b1a1b" dependencies = [ "zbus 4.4.0", ] @@ -3153,7 +3180,7 @@ dependencies = [ [[package]] name = "iced" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "dnd", "iced_accessibility", @@ -3171,7 +3198,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "accesskit", "accesskit_winit", @@ -3180,7 +3207,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "bitflags 2.6.0", "bytes", @@ -3205,7 +3232,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "futures", "iced_core", @@ -3231,7 +3258,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "bitflags 2.6.0", "bytemuck", @@ -3253,7 +3280,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -3265,7 +3292,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "bytes", "cosmic-client-toolkit", @@ -3281,7 +3308,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "bytemuck", "cosmic-text", @@ -3297,7 +3324,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "as-raw-xcb-connection", "bitflags 2.6.0", @@ -3314,7 +3341,7 @@ dependencies = [ "raw-window-handle", "resvg", "rustc-hash 2.1.0", - "rustix 0.38.41", + "rustix 0.38.42", "thiserror 1.0.69", "tiny-xlib", "wayland-backend", @@ -3328,7 +3355,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "cosmic-client-toolkit", "dnd", @@ -3347,7 +3374,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "cosmic-client-toolkit", "dnd", @@ -4069,9 +4096,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -4127,9 +4154,9 @@ dependencies = [ [[package]] name = "jxl-grid" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13a28ba2734da33624db4b426b44750a7b1238e6cba65d27b7d84bf3cba7f507" +checksum = "80f25f406f215f07f0b994801bc2d6adbfcd5710fcdd8f12d662e80700469e6c" dependencies = [ "tracing", ] @@ -4324,25 +4351,25 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#ff0ba4860c9ba732e601485f1952fde5fe5f6952" +source = "git+https://github.com/pop-os/libcosmic#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "apply", "ashpd 0.9.2", "chrono", "cosmic-client-toolkit", "cosmic-config", + "cosmic-freedesktop-icons", "cosmic-settings-daemon", "cosmic-theme", "css-color", "derive_setters", - "freedesktop-icons", "iced", "iced_accessibility", "iced_core", @@ -4504,7 +4531,7 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "locale1" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#62100129240d164e39fff16bda34faad520936de" +source = "git+https://github.com/pop-os/dbus-settings-bindings#0eee63a96c8b1f6555ca797b5c12545c372b1a1b" dependencies = [ "zbus 4.4.0", ] @@ -4578,9 +4605,9 @@ dependencies = [ [[package]] name = "lyon_algorithms" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3bca95f9a4955b3e4a821fbbcd5edfbd9be2a9a50bb5758173e5358bfb4c623" +checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08" dependencies = [ "lyon_path", "num-traits", @@ -5555,7 +5582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.2.0", + "fastrand 2.3.0", "futures-io", ] @@ -5595,9 +5622,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.14" +version = "0.17.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -5632,7 +5659,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.41", + "rustix 0.38.42", "tracing", "windows-sys 0.59.0", ] @@ -5737,7 +5764,7 @@ dependencies = [ "hex", "lazy_static", "procfs-core", - "rustix 0.38.41", + "rustix 0.38.42", ] [[package]] @@ -6260,15 +6287,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6356,14 +6383,14 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" dependencies = [ - "self_cell 1.0.4", + "self_cell 1.1.0", ] [[package]] name = "self_cell" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] name = "serde" @@ -6603,7 +6630,7 @@ dependencies = [ "log", "memmap2 0.9.5", "pkg-config", - "rustix 0.38.41", + "rustix 0.38.42", "thiserror 1.0.69", "wayland-backend", "wayland-client", @@ -6667,7 +6694,7 @@ dependencies = [ "cocoa", "core-graphics", "drm", - "fastrand 2.2.0", + "fastrand 2.3.0", "foreign-types", "js-sys", "log", @@ -6675,7 +6702,7 @@ dependencies = [ "objc", "raw-window-handle", "redox_syscall 0.4.1", - "rustix 0.38.41", + "rustix 0.38.42", "tiny-xlib", "wasm-bindgen", "wayland-backend", @@ -6868,7 +6895,7 @@ dependencies = [ "serde", "serde_json", "superblock", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -6933,9 +6960,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.2.0", + "fastrand 2.3.0", "once_cell", - "rustix 0.38.41", + "rustix 0.38.42", "windows-sys 0.59.0", ] @@ -6965,11 +6992,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.4", + "thiserror-impl 2.0.6", ] [[package]] @@ -6985,9 +7012,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", @@ -7049,7 +7076,7 @@ dependencies = [ [[package]] name = "timedate-zbus" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#62100129240d164e39fff16bda34faad520936de" +source = "git+https://github.com/pop-os/dbus-settings-bindings#0eee63a96c8b1f6555ca797b5c12545c372b1a1b" dependencies = [ "zbus 4.4.0", ] @@ -7158,9 +7185,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -7436,7 +7463,7 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "upower_dbus" version = "0.3.2" -source = "git+https://github.com/pop-os/dbus-settings-bindings#62100129240d164e39fff16bda34faad520936de" +source = "git+https://github.com/pop-os/dbus-settings-bindings#0eee63a96c8b1f6555ca797b5c12545c372b1a1b" dependencies = [ "serde", "serde_repr", @@ -7587,9 +7614,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -7598,13 +7625,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.90", @@ -7613,9 +7639,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -7626,9 +7652,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7636,9 +7662,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -7649,9 +7675,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-timer" @@ -7676,7 +7702,7 @@ checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.41", + "rustix 0.38.42", "scoped-tls", "smallvec", "wayland-sys", @@ -7689,7 +7715,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ "bitflags 2.6.0", - "rustix 0.38.41", + "rustix 0.38.42", "wayland-backend", "wayland-scanner", ] @@ -7711,7 +7737,7 @@ version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" dependencies = [ - "rustix 0.38.41", + "rustix 0.38.42", "wayland-client", "xcursor", ] @@ -7776,7 +7802,7 @@ dependencies = [ "bitflags 2.6.0", "downcast-rs", "io-lifetimes 2.0.4", - "rustix 0.38.41", + "rustix 0.38.42", "wayland-backend", "wayland-scanner", ] @@ -7795,9 +7821,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -8347,7 +8373,7 @@ dependencies = [ "pin-project", "raw-window-handle", "redox_syscall 0.4.1", - "rustix 0.38.41", + "rustix 0.38.42", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -8431,7 +8457,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix 0.38.41", + "rustix 0.38.42", "x11rb-protocol", ] @@ -8466,7 +8492,7 @@ dependencies = [ [[package]] name = "xdg-shell-wrapper-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-panel#734e3fafe2eafea1cb3cece7d0b4ddf72a2c4323" +source = "git+https://github.com/pop-os/cosmic-panel#1c9c4e2a2cf27efd0ca77b5ec21bc6f7fa92d9da" dependencies = [ "serde", "wayland-protocols-wlr", @@ -8696,6 +8722,19 @@ dependencies = [ "zvariant 4.2.0", ] +[[package]] +name = "zbus_polkit" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00a29bfa927b29f91b7feb4e1990f2dd1b4604072f493dc2f074cf59e4e0ba90" +dependencies = [ + "enumflags2", + "serde", + "serde_repr", + "static_assertions", + "zbus 4.4.0", +] + [[package]] name = "zeno" version = "0.2.3" diff --git a/cosmic-settings/Cargo.toml b/cosmic-settings/Cargo.toml index 6ec003d3..5ef3ccfb 100644 --- a/cosmic-settings/Cargo.toml +++ b/cosmic-settings/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" license = "GPL-3.0-only" [dependencies] +accounts-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } anyhow = "1.0" as-result = "0.2.1" ashpd = { version = "0.9", default-features = false, features = [ @@ -57,7 +58,7 @@ sunrise = "1.0.1" tachyonix = "0.3.1" timedate-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } tokio = { workspace = true, features = ["fs", "io-util", "sync"] } -tracing = "0.1.40" +tracing = "0.1.41" tracing-subscriber = "0.3.18" udev = { version = "0.9.0", optional = true } upower_dbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } @@ -65,10 +66,12 @@ bluez-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optiona url = "2.5.2" xkb-data = "0.2.1" zbus = { version = "4.4.0", features = ["tokio"], optional = true } +zbus_polkit = { version = "4.0.0" } ustr = "1.0.0" fontdb = "0.16.2" fixed_decimal = "0.5.6" mime = "0.3.17" +rustix = "0.38.41" [dependencies.cosmic-settings-subscriptions] git = "https://github.com/pop-os/cosmic-settings-subscriptions" @@ -104,6 +107,7 @@ linux = [ "page-power", "page-region", "page-sound", + "page-users", "page-window-management", "page-workspaces", "xdg-portal", @@ -129,6 +133,7 @@ page-networking = [ page-power = ["dep:upower_dbus", "dep:zbus"] page-region = ["dep:lichen-system", "dep:locale1"] page-sound = ["dep:cosmic-settings-subscriptions"] +page-users = ["dep:accounts-zbus"] page-window-management = ["dep:cosmic-settings-config"] page-workspaces = ["dep:cosmic-comp-config"] diff --git a/cosmic-settings/src/app.rs b/cosmic-settings/src/app.rs index 5c22c661..0e896319 100644 --- a/cosmic-settings/src/app.rs +++ b/cosmic-settings/src/app.rs @@ -108,6 +108,7 @@ impl SettingsApp { PageCommands::Time => self.pages.page_id::(), #[cfg(feature = "page-input")] PageCommands::Touchpad => self.pages.page_id::(), + #[cfg(feature = "page-users")] PageCommands::Users => self.pages.page_id::(), #[cfg(feature = "page-networking")] PageCommands::Vpn => self.pages.page_id::(), @@ -504,6 +505,13 @@ impl cosmic::Application for SettingsApp { } } + #[cfg(feature = "page-users")] + crate::pages::Message::User(message) => { + if let Some(page) = self.pages.page_mut::() { + return page.update(message).map(Into::into); + } + } + #[cfg(feature = "page-input")] crate::pages::Message::SystemShortcuts(message) => { if let Some(page) = self diff --git a/cosmic-settings/src/main.rs b/cosmic-settings/src/main.rs index 0f95bb37..e34d87d1 100644 --- a/cosmic-settings/src/main.rs +++ b/cosmic-settings/src/main.rs @@ -92,6 +92,7 @@ pub enum PageCommands { #[cfg(feature = "page-input")] Touchpad, /// Users settings page + #[cfg(feature = "page-users")] Users, /// VPN settings page #[cfg(feature = "page-networking")] diff --git a/cosmic-settings/src/pages/mod.rs b/cosmic-settings/src/pages/mod.rs index 7b54e262..3516c434 100644 --- a/cosmic-settings/src/pages/mod.rs +++ b/cosmic-settings/src/pages/mod.rs @@ -69,6 +69,8 @@ pub enum Message { Region(time::region::Message), #[cfg(feature = "page-sound")] Sound(sound::Message), + #[cfg(feature = "page-users")] + User(system::users::Message), #[cfg(feature = "page-input")] SystemShortcuts(input::keyboard::shortcuts::ShortcutMessage), #[cfg(feature = "page-input")] diff --git a/cosmic-settings/src/pages/system/default_apps.rs b/cosmic-settings/src/pages/system/default_apps.rs index 7d2252ac..0da04bc9 100644 --- a/cosmic-settings/src/pages/system/default_apps.rs +++ b/cosmic-settings/src/pages/system/default_apps.rs @@ -100,6 +100,10 @@ impl page::Page for Page { &mut self, _sender: mpsc::Sender, ) -> Task { + if let Some(handle) = self.on_enter_handle.take() { + handle.abort(); + } + let (task, on_enter_handle) = Task::future(async move { let mut list = mime_apps::List::default(); list.load_from_paths(&mime_apps::list_paths()); diff --git a/cosmic-settings/src/pages/system/mod.rs b/cosmic-settings/src/pages/system/mod.rs index d9cb2b86..064f2b30 100644 --- a/cosmic-settings/src/pages/system/mod.rs +++ b/cosmic-settings/src/pages/system/mod.rs @@ -6,6 +6,7 @@ pub mod about; #[cfg(feature = "page-default-apps")] pub mod default_apps; pub mod firmware; +#[cfg(feature = "page-users")] pub mod users; use cosmic_settings_page as page; @@ -29,16 +30,23 @@ impl page::AutoBind for Page { fn sub_pages( mut page: page::Insert, ) -> page::Insert { - page = page.sub_page::(); + #[cfg(feature = "page-users")] + { + page = page.sub_page::(); + } + #[cfg(feature = "page-about")] { page = page.sub_page::(); } + page = page.sub_page::(); + #[cfg(feature = "page-default-apps")] { page = page.sub_page::(); } + page } } diff --git a/cosmic-settings/src/pages/system/users.rs b/cosmic-settings/src/pages/system/users.rs deleted file mode 100644 index f9066ba8..00000000 --- a/cosmic-settings/src/pages/system/users.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: GPL-3.0-only - -use cosmic_settings_page::Section; -use cosmic_settings_page::{self as page, section}; -use slotmap::SlotMap; - -#[derive(Default)] -pub struct Page { - entity: page::Entity, -} - -impl page::Page for Page { - fn set_id(&mut self, entity: page::Entity) { - self.entity = entity; - } - - fn content( - &self, - sections: &mut SlotMap>, - ) -> Option { - Some(vec![sections.insert(Section::default())]) - } - - fn info(&self) -> page::Info { - page::Info::new("users", "system-users-symbolic") - .title(fl!("users")) - .description(fl!("users", "desc")) - } -} - -impl page::AutoBind for Page {} diff --git a/cosmic-settings/src/pages/system/users/getent.rs b/cosmic-settings/src/pages/system/users/getent.rs new file mode 100644 index 00000000..60c8a537 --- /dev/null +++ b/cosmic-settings/src/pages/system/users/getent.rs @@ -0,0 +1,137 @@ +// Copyright 2024 System76 +// SPDX-License-Identifier: GPL-3.0-only + +use std::io::{BufRead, BufReader}; +use std::process::Stdio; +use std::str::FromStr; + +pub fn passwd(range: (u64, u64)) -> Vec { + let spawn_res = std::process::Command::new("getent") + .arg("passwd") + .stdin(Stdio::null()) + .stderr(Stdio::null()) + .stdout(Stdio::piped()) + .spawn(); + + let mut users = Vec::new(); + + if let Ok(mut child) = spawn_res { + let stdout = child.stdout.take().unwrap(); + let mut reader = BufReader::new(stdout); + let mut line = String::new(); + + loop { + line.clear(); + match reader.read_line(&mut line) { + Ok(0) | Err(_) => break, + _ => (), + } + + if let Ok(user) = line.trim().parse::() { + if user.uid >= range.0 && user.uid <= range.1 { + users.push(user); + } + } + } + } + + users +} + +pub fn group() -> Vec { + let spawn_res = std::process::Command::new("getent") + .arg("group") + .stdin(Stdio::null()) + .stderr(Stdio::null()) + .stdout(Stdio::piped()) + .spawn(); + + let mut groups = Vec::new(); + + if let Ok(mut child) = spawn_res { + let stdout = child.stdout.take().unwrap(); + let mut reader = BufReader::new(stdout); + let mut line = String::new(); + + loop { + line.clear(); + match reader.read_line(&mut line) { + Ok(0) | Err(_) => break, + _ => (), + } + + if let Ok(group) = line.trim().parse::() { + groups.push(group); + } + } + } + + groups +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Group { + pub uid: u64, + pub name: Box, + pub users: Vec>, +} + +impl FromStr for Group { + type Err = (); + + fn from_str(line: &str) -> Result { + let mut fields = line.split(':'); + + Ok(Group { + name: fields.next().ok_or(())?.into(), + uid: fields.nth(1).ok_or(())?.parse().map_err(|_| ())?, + users: fields + .next() + .ok_or(())? + .split(',') + .map(Box::from) + .collect::>(), + }) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct PasswdUser { + pub uid: u64, + pub username: Box, + pub full_name: Box, +} + +impl FromStr for PasswdUser { + type Err = (); + + fn from_str(line: &str) -> Result { + let mut fields = line.split(':'); + + Ok(PasswdUser { + username: fields.next().ok_or(())?.into(), + uid: fields.nth(1).ok_or(())?.parse().map_err(|_| ())?, + full_name: fields.nth(1).ok_or(())?.split(',').next().ok_or(())?.into(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::PasswdUser; + + #[test] + fn passwd() { + const EXAMPLE: &str = + "speech-dispatcher:x:109:29:Speech Dispatcher,,,:/run/speech-dispatcher:/bin/false"; + + assert_eq!( + EXAMPLE.parse::(), + Ok(PasswdUser { + username: Box::from("speech-dispatcher"), + uid: 109, + full_name: Box::from("Speech Dispatcher") + }) + ); + } +} diff --git a/cosmic-settings/src/pages/system/users/mod.rs b/cosmic-settings/src/pages/system/users/mod.rs new file mode 100644 index 00000000..13c25abd --- /dev/null +++ b/cosmic-settings/src/pages/system/users/mod.rs @@ -0,0 +1,818 @@ +// Copyright 2024 System76 +// SPDX-License-Identifier: GPL-3.0-only + +mod getent; + +use cosmic::{ + dialog::file_chooser, + iced::{Alignment, Length}, + theme, + widget::{self, column, icon, settings, text}, + Apply, Element, +}; +use cosmic_settings_page::{self as page, section, Section}; +use slab::Slab; +use slotmap::SlotMap; +use std::{ + collections::HashMap, + future::Future, + io::{BufRead, BufReader}, + path::PathBuf, + sync::Arc, +}; +use url::Url; +use zbus_polkit::policykit1::CheckAuthorizationFlags; + +use crate::pages; + +const DEFAULT_ICON_FILE: &str = "/usr/share/pixmaps/faces/pop-robot.png"; +const USERS_ADMIN_POLKIT_POLICY_ID: &str = "com.system76.CosmicSettings.Users.Admin"; + +#[derive(Clone, Debug, Default)] +pub struct User { + id: u64, + profile_icon: Option, + full_name: String, + password: String, + username: String, + full_name_edit: bool, + password_edit: bool, + username_edit: bool, + is_admin: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum EditorField { + FullName, + Password, + Username, +} + +#[derive(Clone, Debug)] +pub enum Dialog { + AddNewUser(User), +} + +#[derive(Clone, Debug)] +pub struct Page { + on_enter_handle: Option, + current_user_id: u64, + entity: page::Entity, + users: Vec, + selected_user_idx: Option, + dialog: Option, + default_icon: icon::Handle, + password_label: String, + username_label: String, + fullname_label: String, +} + +impl Default for Page { + fn default() -> Self { + Self { + on_enter_handle: None, + current_user_id: 0, + entity: page::Entity::default(), + users: Vec::default(), + selected_user_idx: None, + dialog: None, + default_icon: icon::from_path(PathBuf::from(DEFAULT_ICON_FILE)), + password_label: crate::fl!("password"), + username_label: crate::fl!("username"), + fullname_label: crate::fl!("full-name"), + } + } +} +#[derive(Clone, Debug)] +pub enum Message { + ApplyEdit(usize, EditorField), + ChangedAccountType(u64, bool), + DeletedUser(u64), + Dialog(Option), + Edit(usize, EditorField, String), + LoadedIcon(u64, icon::Handle), + LoadPage(u64, Vec), + NewUser(String, String, String, bool), + None, + SelectProfileImage(u64), + SelectedProfileImage(u64, Arc>), + SelectUser(usize), + SelectedUserDelete(u64), + SelectedUserSetAdmin(u64, bool), + ToggleEdit(usize, EditorField), +} + +impl From for crate::app::Message { + fn from(message: Message) -> Self { + crate::pages::Message::User(message).into() + } +} + +impl From for crate::pages::Message { + fn from(message: Message) -> Self { + crate::pages::Message::User(message) + } +} + +impl page::Page for Page { + fn set_id(&mut self, entity: page::Entity) { + self.entity = entity; + } + + fn content( + &self, + sections: &mut SlotMap>, + ) -> Option { + Some(vec![sections.insert(user_list())]) + } + + fn info(&self) -> page::Info { + page::Info::new("users", "system-users-symbolic") + .title(fl!("users")) + .description(fl!("users", "desc")) + } + + fn dialog(&self) -> Option> { + let dialog = self.dialog.as_ref()?; + let theme = cosmic::theme::active(); + let theme = theme.cosmic(); + + let dialog_element = match dialog { + Dialog::AddNewUser(user) => { + let full_name_input = widget::container( + widget::text_input("", &user.full_name) + .label(&self.fullname_label) + .on_input(|value| { + Message::Dialog(Some(Dialog::AddNewUser(User { + full_name: value, + ..user.clone() + }))) + }), + ) + .padding([0, theme.space_s().into()]); + + let username_input = widget::container( + widget::text_input("", &user.username) + .label(&self.username_label) + .on_input(|value| { + Message::Dialog(Some(Dialog::AddNewUser(User { + username: value, + ..user.clone() + }))) + }), + ) + .padding([0, theme.space_s().into()]); + + let password_input = widget::container( + widget::text_input("", &user.password) + .password() + .label(&self.password_label) + .on_input(|value| { + Message::Dialog(Some(Dialog::AddNewUser(User { + password: value, + ..user.clone() + }))) + }), + ) + .padding([0, theme.space_s().into()]); + + let admin_toggler = widget::toggler(user.is_admin).on_toggle(|value| { + Message::Dialog(Some(Dialog::AddNewUser(User { + is_admin: value, + ..user.clone() + }))) + }); + + let add_user_button = widget::button::suggested(fl!("add-user")) + .on_press(()) + .apply(Element::from) + .map(|_| { + Message::NewUser( + user.username.clone(), + user.full_name.clone(), + user.password.clone(), + user.is_admin, + ) + }); + + let cancel_button = + widget::button::standard(fl!("cancel")).on_press(Message::Dialog(None)); + + widget::dialog() + .title(fl!("add-user")) + .control( + widget::ListColumn::default() + .add(full_name_input) + .add(username_input) + .add(password_input) + .add(settings::item_row(vec![ + column::with_capacity(2) + .push(text::body(crate::fl!("administrator"))) + .push(text::caption(crate::fl!("administrator", "desc"))) + .into(), + widget::horizontal_space().width(Length::Fill).into(), + admin_toggler.into(), + ])), + ) + .primary_action(add_user_button) + .secondary_action(cancel_button) + .apply(cosmic::Element::from) + } + }; + + dialog_element.map(crate::pages::Message::User).into() + } + + fn on_enter( + &mut self, + _sender: tokio::sync::mpsc::Sender, + ) -> cosmic::Task { + if let Some(handle) = self.on_enter_handle.take() { + handle.abort(); + } + + let (task, handle) = cosmic::task::future(async { Self::reload().await }).abortable(); + self.on_enter_handle = Some(handle); + task + } + + fn on_leave(&mut self) -> cosmic::Task { + if let Some(handle) = self.on_enter_handle.take() { + handle.abort(); + } + + cosmic::Task::none() + } +} + +impl Page { + pub async fn reload() -> Message { + let passwd_users = getent::passwd(uid_range()); + let mut users = Vec::with_capacity(passwd_users.len()); + let groups = getent::group(); + + let uid = rustix::process::getuid().as_raw() as u64; + + let admin_group = groups.iter().find(|g| &*g.name == "sudo"); + + let Ok(conn) = zbus::Connection::system().await else { + tracing::error!("unable to access dbus system service"); + return Message::LoadPage(uid, Vec::new()); + }; + + for user in passwd_users { + let Ok(user_proxy) = accounts_zbus::UserProxy::from_uid(&conn, user.uid).await else { + continue; + }; + + users.push(User { + id: user.uid, + profile_icon: user_proxy + .icon_file() + .await + .ok() + .map(|path| icon::from_path(PathBuf::from(path))), + is_admin: match user_proxy.account_type().await { + Ok(1) => true, + Ok(_) => false, + Err(_) => { + admin_group.map_or(false, |group| group.users.contains(&user.username)) + } + }, + username: String::from(user.username), + full_name: String::from(user.full_name), + password: String::new(), + full_name_edit: false, + password_edit: false, + username_edit: false, + }); + } + + Message::LoadPage(uid, users) + } + + pub fn update(&mut self, message: Message) -> cosmic::Task { + match message { + Message::None => (), + + Message::ChangedAccountType(uid, is_admin) => { + for user in &mut self.users { + if user.id == uid { + user.is_admin = is_admin; + return cosmic::Task::none(); + } + } + } + + Message::LoadedIcon(uid, handle) => { + for user in &mut self.users { + if user.id == uid { + user.profile_icon = Some(handle); + return cosmic::Task::none(); + } + } + } + + Message::SelectProfileImage(uid) => { + return cosmic::task::future(async move { + let dialog_result = file_chooser::open::Dialog::new() + .title(fl!("wallpaper", "folder-dialog")) + .accept_label(fl!("dialog-add")) + .modal(false) + .open_file() + .await + .map(|response| response.url().to_owned()); + + Message::SelectedProfileImage(uid, Arc::new(dialog_result)) + }); + } + + Message::SelectedProfileImage(uid, image_result) => { + let url = match Arc::into_inner(image_result).unwrap() { + Ok(url) => url, + Err(why) => { + tracing::error!(?why, "failed to get image file"); + return cosmic::Task::none(); + } + }; + + return cosmic::task::future(async move { + let Ok(conn) = zbus::Connection::system().await else { + return Message::None; + }; + + let Ok(user) = accounts_zbus::UserProxy::from_uid(&conn, uid).await else { + return Message::None; + }; + + let Ok(path) = url.to_file_path() else { + tracing::error!("selected image is not a file path"); + return Message::None; + }; + + let result = request_permission_on_denial(&conn, || { + user.set_icon_file(path.to_str().unwrap()) + }) + .await; + + if let Err(why) = result { + tracing::error!(?why, "failed to set profile icon"); + return Message::None; + } + + Message::LoadedIcon(uid, icon::from_path(path)) + }); + } + + Message::Edit(id, field, value) => { + if let Some(user) = self.users.get_mut(id) { + match field { + EditorField::FullName => user.full_name = value, + EditorField::Password => user.password = value, + EditorField::Username => user.username = value, + } + } + } + + Message::ToggleEdit(id, field) => { + if let Some(user) = self.users.get_mut(id) { + match field { + EditorField::FullName => user.full_name_edit = !user.full_name_edit, + EditorField::Password => user.password_edit = !user.password_edit, + EditorField::Username => user.username_edit = !user.username_edit, + } + } + } + + Message::ApplyEdit(id, field) => { + if let Some(user) = self.users.get_mut(id) { + let uid = user.id; + + match field { + EditorField::FullName => { + let full_name = user.full_name.clone(); + return cosmic::Task::future(async move { + let Ok(conn) = zbus::Connection::system().await else { + return; + }; + + let Ok(user) = accounts_zbus::UserProxy::from_uid(&conn, uid).await + else { + return; + }; + + _ = request_permission_on_denial(&conn, || { + user.set_real_name(&full_name) + }) + .await; + }) + .discard(); + } + + EditorField::Password => { + let password = std::mem::take(&mut user.password); + + return cosmic::Task::future(async move { + let Ok(conn) = zbus::Connection::system().await else { + return; + }; + + let Ok(user) = accounts_zbus::UserProxy::from_uid(&conn, uid).await + else { + return; + }; + + match request_permission_on_denial(&conn, || { + user.set_password(&password, "") + }) + .await + { + Err(why) => { + tracing::error!(?why, "failed to set password"); + } + + Ok(_) => (), + } + }) + .discard(); + } + + EditorField::Username => { + let username = user.username.clone(); + return cosmic::Task::future(async move { + let Ok(conn) = zbus::Connection::system().await else { + return; + }; + + let Ok(user) = accounts_zbus::UserProxy::from_uid(&conn, uid).await + else { + return; + }; + + _ = request_permission_on_denial(&conn, || { + user.set_user_name(&username) + }) + .await; + }) + .discard(); + } + } + } + } + + Message::LoadPage(uid, users) => { + self.current_user_id = uid; + self.users = users; + } + + Message::SelectUser(user_idx) => { + match self.selected_user_idx { + Some(currently_selected_idx) if currently_selected_idx == user_idx => { + self.selected_user_idx = None; + } + _ => { + self.selected_user_idx = Some(user_idx); + } + }; + } + + Message::SelectedUserDelete(uid) => { + return cosmic::task::future(async move { + let Ok(conn) = zbus::Connection::system().await else { + return Message::None; + }; + + let accounts = accounts_zbus::AccountsProxy::new(&conn).await.unwrap(); + + let result = request_permission_on_denial(&conn, || { + accounts.delete_user(uid as i64, false) + }) + .await; + + if let Err(why) = result { + tracing::error!(?why, "failed to delete user account"); + return Message::None; + } + + Message::DeletedUser(uid) + }); + } + + Message::DeletedUser(uid) => { + self.users.retain(|user| user.id != uid); + } + + Message::Dialog(dialog) => { + self.dialog = dialog; + } + + Message::NewUser(username, full_name, password, is_admin) => { + self.dialog = None; + + return cosmic::task::future(async move { + let Ok(conn) = zbus::Connection::system().await else { + return Message::None; + }; + + let accounts = accounts_zbus::AccountsProxy::new(&conn).await.unwrap(); + + let user_result = request_permission_on_denial(&conn, || { + accounts.create_user(&username, &full_name, if is_admin { 1 } else { 0 }) + }) + .await; + + let user_object_path = match user_result { + Ok(path) => path, + + Err(why) => { + tracing::error!(?why, "failed to create user account"); + return Message::None; + } + }; + + match accounts_zbus::UserProxy::new(&conn, user_object_path).await { + Ok(user) => { + _ = user.set_password(&password, "").await; + _ = user.set_icon_file(DEFAULT_ICON_FILE).await + } + + Err(why) => { + tracing::error!(?why, "failed to get user by object path"); + } + } + + Self::reload().await + }); + } + + Message::SelectedUserSetAdmin(uid, is_admin) => { + return cosmic::task::future(async move { + let Ok(conn) = zbus::Connection::system().await else { + return Message::None; + }; + + let Ok(user) = accounts_zbus::UserProxy::from_uid(&conn, uid).await else { + return Message::None; + }; + + let result = request_permission_on_denial(&conn, || async { + user.set_account_type(if is_admin { 1 } else { 0 }).await + }) + .await; + + if let Err(why) = result { + tracing::error!(?why, "failed to change account type of user"); + return Message::None; + } + + Message::ChangedAccountType(uid, is_admin) + }); + } + }; + + cosmic::Task::none() + } +} + +impl page::AutoBind for Page {} + +fn user_list() -> Section { + let mut descriptions = Slab::new(); + + let user_type_standard = descriptions.insert(fl!("users", "standard")); + let user_type_admin = descriptions.insert(fl!("users", "admin")); + + Section::default() + .descriptions(descriptions) + .view::(move |_binder, page, section| { + let descriptions = §ion.descriptions; + + let theme = cosmic::theme::active(); + let theme = theme.cosmic(); + + let cosmic::cosmic_theme::Spacing { + space_xxs, space_m, .. + } = theme::active().cosmic().spacing; + + let users_list = page + .users + .iter() + .enumerate() + .flat_map(|(idx, user)| { + let expanded = + matches!(page.selected_user_idx, Some(user_idx) if user_idx == idx); + + let username = + widget::editable_input("", &user.username, user.username_edit, move |_| { + Message::ToggleEdit(idx, EditorField::Username) + }) + .on_input(move |name| Message::Edit(idx, EditorField::Username, name)) + .on_submit(Message::ApplyEdit(idx, EditorField::Username)); + + let password = + widget::editable_input("", &user.password, user.password_edit, move |_| { + Message::ToggleEdit(idx, EditorField::Password) + }) + .on_input(move |pass| Message::Edit(idx, EditorField::Password, pass)) + .on_submit(Message::ApplyEdit(idx, EditorField::Password)) + .password(); + + let fullname = widget::editable_input( + "", + &user.full_name, + user.full_name_edit, + move |_| Message::ToggleEdit(idx, EditorField::FullName), + ) + .on_input(move |name| Message::Edit(idx, EditorField::FullName, name)) + .on_submit(Message::ApplyEdit(idx, EditorField::FullName)); + + let fullname_text = text::body(&user.full_name); + + let account_type = text::caption(if user.is_admin { + &descriptions[user_type_admin] + } else { + &descriptions[user_type_standard] + }); + + let expanded_details = expanded.then(|| { + let mut details_list = widget::list_column() + .add(settings::item(&page.fullname_label, fullname)) + .add(settings::item(&page.username_label, username)) + .add(settings::item(&page.password_label, password)) + .add(settings::item_row(vec![ + column::with_capacity(2) + .push(text::body(crate::fl!("administrator"))) + .push(text::caption(crate::fl!("administrator", "desc"))) + .into(), + widget::horizontal_space().width(Length::Fill).into(), + widget::toggler(user.is_admin) + .on_toggle(|enabled| { + Message::SelectedUserSetAdmin(user.id, enabled) + }) + .into(), + ])); + + if page.users.len() > 1 { + details_list = details_list.add(settings::item_row(vec![ + widget::horizontal_space().width(Length::Fill).into(), + widget::button::destructive(crate::fl!("remove-user")) + .on_press(Message::SelectedUserDelete(user.id)) + .into(), + ])); + } + + details_list.apply(cosmic::Element::from) + }); + + let profile_icon_handle = user + .profile_icon + .clone() + .unwrap_or_else(|| page.default_icon.clone()); + + let profile_icon = widget::button::icon(profile_icon_handle) + .large() + .on_press(Message::SelectProfileImage(user.id)); + + let account_details_content = settings::item_row(vec![ + widget::row::with_capacity(2) + .push(profile_icon) + .push( + column::with_capacity(2) + .push(fullname_text) + .push(account_type), + ) + .align_y(Alignment::Center) + .spacing(theme.space_xxs()) + .into(), + widget::horizontal_space().width(Length::Fill).into(), + icon::from_name(if expanded { + "go-up-symbolic" + } else { + "go-next-symbolic" + }) + .icon() + .size(16) + .into(), + ]); + + let account_details = Some( + widget::button::custom(account_details_content) + .padding([space_xxs, space_m]) + .on_press(Message::SelectUser(idx)) + .class(cosmic::theme::Button::ListItem) + .selected(expanded) + .apply(cosmic::Element::from), + ); + + vec![account_details, expanded_details] + }) + .flatten() + .fold( + widget::list_column() + .spacing(0) + .padding(0) + .divider_padding(0) + .list_item_padding(0), + widget::ListColumn::add, + ) + .apply(|list| cosmic::Element::from(settings::section::with_column(list))); + + let add_user = widget::button::standard(crate::fl!("add-user")) + .on_press(Message::Dialog(Some(Dialog::AddNewUser(User::default())))) + .apply(widget::container) + .width(Length::Fill) + .align_x(Alignment::End); + + widget::column::with_capacity(2) + .push(users_list) + .push(add_user) + .spacing(space_m) + .apply(Element::from) + .map(crate::pages::Message::User) + }) +} + +async fn check_authorization(conn: &zbus::Connection) -> anyhow::Result<()> { + let proxy = zbus_polkit::policykit1::AuthorityProxy::new(conn).await?; + let subject = zbus_polkit::policykit1::Subject::new_for_owner(std::process::id(), None, None)?; + proxy + .check_authorization( + &subject, + USERS_ADMIN_POLKIT_POLICY_ID, + &HashMap::new(), + CheckAuthorizationFlags::AllowUserInteraction.into(), + "", + ) + .await?; + Ok(()) +} + +fn uid_range() -> (u64, u64) { + let (mut min, mut max) = (1000, 60000); + let Ok(file) = std::fs::File::open("/etc/login.defs") else { + return (min, max); + }; + + let mut reader = BufReader::new(file); + let mut line = String::new(); + + loop { + line.clear(); + match reader.read_line(&mut line) { + Ok(0) | Err(_) => break, + _ => (), + } + + let line = line.trim(); + + let variable = if line.starts_with("UID_MIN ") { + &mut min + } else if line.starts_with("UID_MAX ") { + &mut max + } else { + continue; + }; + + if let Some(value) = line + .split_ascii_whitespace() + .nth(1) + .and_then(|value| value.parse::().ok()) + { + *variable = value; + } + } + + (min, max) +} + +async fn request_permission_on_denial( + conn: &zbus::Connection, + action: Fun, +) -> zbus::Result +where + Fun: Fn() -> Fut, + Fut: Future>, +{ + match action().await { + Ok(value) => Ok(value), + Err(why) => { + if permission_was_denied(&why) { + _ = check_authorization(conn).await; + action().await + } else { + Err(why) + } + } + } +} + +fn permission_was_denied(result: &zbus::Error) -> bool { + match result { + zbus::Error::MethodError(name, _, _) + if name.as_str() == "org.freedesktop.Accounts.Error.PermissionDenied" => + { + true + } + _ => false, + } +} diff --git a/i18n/en/cosmic_settings.ftl b/i18n/en/cosmic_settings.ftl index 089daac3..1c72ce7d 100644 --- a/i18n/en/cosmic_settings.ftl +++ b/i18n/en/cosmic_settings.ftl @@ -772,6 +772,18 @@ firmware = Firmware users = Users .desc = Authentication and user accounts. + .admin = Admin + .standard = Standard + .profile-add = Choose profile image + +administrator = Administrator + .desc = Administrators can change settings for all users, add and remove other users. + +add-user = Add user +remove-user = Remove user +full-name = Full name +username = Username +password = Password ## System: Default Applications diff --git a/justfile b/justfile index 4ec9dfc7..699014b6 100644 --- a/justfile +++ b/justfile @@ -20,6 +20,12 @@ metainfo := appid + '.metainfo.xml' metainfo-src := 'resources' / metainfo metainfo-dst := clean(rootdir / prefix) / 'share' / 'metainfo' / metainfo +polkit-actions-src := 'resources' / 'polkit-1' / 'actions' +polkit-actions-dst := clean(rootdir / prefix) / 'share' / 'polkit-1' / 'actions' + +policy-users-src := polkit-actions-src / appid + '.Users.policy' +policy-users-dst := polkit-actions-dst / appid + '.Users.policy' + polkit-rules-src := 'resources' / 'polkit-1' / 'rules.d' / 'cosmic-settings.rules' polkit-rules-dst := clean(rootdir / prefix) / 'share' / 'polkit-1' / 'rules.d' / 'cosmic-settings.rules' @@ -91,10 +97,12 @@ install-desktop-entries: install -Dm0644 'resources/{{entry-workspaces}}' '{{appdir}}/{{entry-workspaces}}' # Install everything -install: install-desktop-entries (install-bin bin-src bin-dest) (install-file metainfo-src metainfo-dst) (install-file polkit-rules-src polkit-rules-dst) +install: install-desktop-entries (install-bin bin-src bin-dest) (install-file metainfo-src metainfo-dst) install-polkit-files find 'resources'/'default_schema' -type f -exec echo {} \; | rev | cut -d'/' -f-3 | rev | xargs -d '\n' -I {} install -Dm0644 'resources'/'default_schema'/{} {{default-schema-target}}/{} find 'resources'/'icons' -type f -exec echo {} \; | rev | cut -d'/' -f-3 | rev | xargs -d '\n' -I {} install -Dm0644 'resources'/'icons'/{} {{iconsdir}}/{} +install-polkit-files: (install-file polkit-rules-src polkit-rules-dst) (install-file policy-users-src policy-users-dst) + [private] install-cmd options src dest: install {{options}} {{src}} {{dest}} diff --git a/resources/polkit-1/actions/com.system76.CosmicSettings.Users.policy b/resources/polkit-1/actions/com.system76.CosmicSettings.Users.policy new file mode 100644 index 00000000..44c83962 --- /dev/null +++ b/resources/polkit-1/actions/com.system76.CosmicSettings.Users.policy @@ -0,0 +1,21 @@ + + + + + System76 + https://system76.com/ + + + Manage user accounts + Authentication is required to change user data + + no + no + auth_admin_keep + + org.freedesktop.accounts.user-administration org.freedesktop.accounts.change-own-user-data org.freedesktop.accounts.change-own-password org.freedesktop.realmd.configure-realm org.freedesktop.realmd.login-policy org.freedesktop.MalcontentControl.administration com.endlessm.ParentalControls.AppFilter.ReadAny com.endlessm.ParentalControls.AppFilter.ChangeAny com.endlessm.ParentalControls.AppFilter.ReadOwn com.endlessm.ParentalControls.AppFilter.ChangeOwn + + +