Add daemon to proxy user backgrounds
This commit is contained in:
parent
3e2743a2e6
commit
fd049483c3
15 changed files with 487 additions and 58 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,7 +2,10 @@
|
||||||
/debian/*debhelper*
|
/debian/*debhelper*
|
||||||
/debian/cosmic-greeter.substvars
|
/debian/cosmic-greeter.substvars
|
||||||
/debian/cosmic-greeter/
|
/debian/cosmic-greeter/
|
||||||
|
/debian/cosmic-greeter-daemon.substvars
|
||||||
|
/debian/cosmic-greeter-daemon/
|
||||||
/debian/files
|
/debian/files
|
||||||
|
/debian/tmp/
|
||||||
/target/
|
/target/
|
||||||
/vendor.tar
|
/vendor.tar
|
||||||
/vendor/
|
/vendor/
|
||||||
|
|
|
||||||
19
Cargo.lock
generated
19
Cargo.lock
generated
|
|
@ -811,6 +811,7 @@ dependencies = [
|
||||||
"cosmic-bg-config",
|
"cosmic-bg-config",
|
||||||
"cosmic-config",
|
"cosmic-config",
|
||||||
"cosmic-dbus-networkmanager",
|
"cosmic-dbus-networkmanager",
|
||||||
|
"cosmic-greeter-daemon",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"freedesktop_entry_parser",
|
"freedesktop_entry_parser",
|
||||||
"greetd_ipc",
|
"greetd_ipc",
|
||||||
|
|
@ -819,6 +820,7 @@ dependencies = [
|
||||||
"logind-zbus",
|
"logind-zbus",
|
||||||
"pam-client",
|
"pam-client",
|
||||||
"pwd",
|
"pwd",
|
||||||
|
"ron",
|
||||||
"shlex",
|
"shlex",
|
||||||
"tokio",
|
"tokio",
|
||||||
"upower_dbus",
|
"upower_dbus",
|
||||||
|
|
@ -826,6 +828,23 @@ dependencies = [
|
||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cosmic-greeter-daemon"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"cosmic-bg-config",
|
||||||
|
"cosmic-config",
|
||||||
|
"env_logger",
|
||||||
|
"libc",
|
||||||
|
"libcosmic",
|
||||||
|
"log",
|
||||||
|
"pwd",
|
||||||
|
"ron",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
"zbus",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-protocols"
|
name = "cosmic-protocols"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
54
Cargo.toml
54
Cargo.toml
|
|
@ -5,12 +5,19 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.31"
|
chrono = "0.4.31"
|
||||||
env_logger = "0.10.0"
|
cosmic-bg-config.workspace = true
|
||||||
|
cosmic-config = { workspace = true, features = ["calloop", "macro"] }
|
||||||
|
cosmic-greeter-daemon = { path = "daemon" }
|
||||||
|
env_logger.workspace = true
|
||||||
freedesktop_entry_parser = "1.3.0"
|
freedesktop_entry_parser = "1.3.0"
|
||||||
log = "0.4.20"
|
libcosmic = { workspace = true, features = ["tokio", "wayland"] }
|
||||||
|
log.workspace = true
|
||||||
pam-client = "0.5.0"
|
pam-client = "0.5.0"
|
||||||
pwd = "1.4.0"
|
pwd.workspace = true
|
||||||
|
ron.workspace = true
|
||||||
shlex = "1.2.0"
|
shlex = "1.2.0"
|
||||||
|
#TODO: reduce features
|
||||||
|
tokio = { workspace = true, features = ["full"] }
|
||||||
wayland-client = "0.31.1"
|
wayland-client = "0.31.1"
|
||||||
# For network status using networkmanager feature
|
# For network status using networkmanager feature
|
||||||
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||||
|
|
@ -19,29 +26,38 @@ logind-zbus = { version = "3.1.2", optional = true }
|
||||||
# For power status with upower feature
|
# For power status with upower feature
|
||||||
upower_dbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
upower_dbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||||
# Required for some features
|
# Required for some features
|
||||||
zbus = { version = "3.14.1", optional = true }
|
zbus = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dependencies.cosmic-bg-config]
|
|
||||||
git = "https://github.com/pop-os/cosmic-bg"
|
|
||||||
|
|
||||||
[dependencies.cosmic-config]
|
|
||||||
git = "https://github.com/pop-os/libcosmic"
|
|
||||||
features = ["calloop", "macro"]
|
|
||||||
|
|
||||||
[dependencies.greetd_ipc]
|
[dependencies.greetd_ipc]
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
features = ["sync-codec"]
|
features = ["sync-codec"]
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
|
||||||
git = "https://github.com/pop-os/libcosmic"
|
|
||||||
features = ["tokio", "wayland"]
|
|
||||||
|
|
||||||
[dependencies.tokio]
|
|
||||||
version = "1.33.0"
|
|
||||||
features = ["full"]
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["logind", "networkmanager", "upower"]
|
default = ["logind", "networkmanager", "upower"]
|
||||||
logind = ["logind-zbus", "zbus"]
|
logind = ["logind-zbus", "zbus"]
|
||||||
networkmanager = ["cosmic-dbus-networkmanager", "zbus"]
|
networkmanager = ["cosmic-dbus-networkmanager", "zbus"]
|
||||||
upower = ["upower_dbus", "zbus"]
|
upower = ["upower_dbus", "zbus"]
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["daemon"]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
env_logger = "0.10.0"
|
||||||
|
log = "0.4.20"
|
||||||
|
pwd = "1.4.0"
|
||||||
|
ron = "0.8"
|
||||||
|
serde = "1"
|
||||||
|
tokio = "1.33.0"
|
||||||
|
zbus = "3.14.1"
|
||||||
|
|
||||||
|
[workspace.dependencies.cosmic-bg-config]
|
||||||
|
git = "https://github.com/pop-os/cosmic-bg"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[workspace.dependencies.cosmic-config]
|
||||||
|
git = "https://github.com/pop-os/libcosmic"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[workspace.dependencies.libcosmic]
|
||||||
|
git = "https://github.com/pop-os/libcosmic"
|
||||||
|
default-features = false
|
||||||
|
|
|
||||||
20
daemon/Cargo.toml
Normal file
20
daemon/Cargo.toml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "cosmic-greeter-daemon"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cosmic-bg-config.workspace = true
|
||||||
|
cosmic-config.workspace = true
|
||||||
|
env_logger.workspace = true
|
||||||
|
libc = "0.2"
|
||||||
|
libcosmic.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
pwd.workspace = true
|
||||||
|
ron.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
zbus.workspace = true
|
||||||
|
#TODO: reduce features
|
||||||
|
tokio = { workspace = true, features = ["full"] }
|
||||||
16
daemon/src/lib.rs
Normal file
16
daemon/src/lib.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
pub use cosmic_bg_config::Color;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct UserData {
|
||||||
|
pub uid: u32,
|
||||||
|
pub name: String,
|
||||||
|
pub full_name_opt: Option<String>,
|
||||||
|
pub icon_opt: Option<Vec<u8>>,
|
||||||
|
pub wallpapers_opt: Option<Vec<(String, WallpaperData)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub enum WallpaperData {
|
||||||
|
Bytes(Vec<u8>),
|
||||||
|
Color(Color),
|
||||||
|
}
|
||||||
181
daemon/src/main.rs
Normal file
181
daemon/src/main.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
use cosmic_bg_config::Source;
|
||||||
|
use cosmic_greeter_daemon::{UserData, WallpaperData};
|
||||||
|
use std::{error::Error, fs, future::pending, io, path::Path};
|
||||||
|
use zbus::{dbus_interface, ConnectionBuilder, DBusError};
|
||||||
|
|
||||||
|
//IMPORTANT: this function is critical to the security of this proxy. It must ensure that the
|
||||||
|
// callback is executed with the permissions of the specified user id. A good test is to see if
|
||||||
|
// the /etc/shadow file can be read with a non-root user, it should fail with EPERM.
|
||||||
|
fn run_as_user<F: FnOnce() -> T, T>(user: &pwd::Passwd, f: F) -> Result<T, io::Error> {
|
||||||
|
if unsafe { libc::seteuid(user.uid) } != 0 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = f();
|
||||||
|
|
||||||
|
if unsafe { libc::seteuid(0) } != 0 {
|
||||||
|
panic!("failed to restore root user id")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DBusError, Debug)]
|
||||||
|
#[dbus_error(prefix = "com.system76.CosmicGreeter")]
|
||||||
|
enum GreeterError {
|
||||||
|
#[dbus_error(zbus_error)]
|
||||||
|
ZBus(zbus::Error),
|
||||||
|
Ron(String),
|
||||||
|
RunAsUser(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GreeterProxy;
|
||||||
|
|
||||||
|
#[dbus_interface(name = "com.system76.CosmicGreeter")]
|
||||||
|
impl GreeterProxy {
|
||||||
|
fn get_user_data(&mut self) -> Result<String, GreeterError> {
|
||||||
|
// The pwd::Passwd method is unsafe (but not labelled as such) due to using global state (libc pwent functions).
|
||||||
|
// To prevent issues, this should only be called once in the entire process space at a time
|
||||||
|
let users: Vec<_> = /* unsafe */ {
|
||||||
|
pwd::Passwd::iter()
|
||||||
|
.filter(|user| {
|
||||||
|
if user.uid < 1000 {
|
||||||
|
// Skip system accounts
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match Path::new(&user.shell).file_name().and_then(|x| x.to_str()) {
|
||||||
|
// Skip shell ending in false
|
||||||
|
Some("false") => false,
|
||||||
|
// Skip shell ending in nologin
|
||||||
|
Some("nologin") => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut user_datas = Vec::new();
|
||||||
|
for user in users {
|
||||||
|
if user.uid < 1000 {
|
||||||
|
// Skip system accounts
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match Path::new(&user.shell).file_name().and_then(|x| x.to_str()) {
|
||||||
|
// Skip shell ending in false
|
||||||
|
Some("false") => continue,
|
||||||
|
// Skip shell ending in nologin
|
||||||
|
Some("nologin") => continue,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: use accountsservice
|
||||||
|
//IMPORTANT: This file is owned by root and safe to read (it won't be a link to /etc/shadow for example)
|
||||||
|
// It may not exist if the user uses one of the system icons. In that case, we should read the
|
||||||
|
// information in /var/lib/AccountsService/users, and then read the icon path as the user
|
||||||
|
let icon_path = Path::new("/var/lib/AccountsService/icons").join(&user.name);
|
||||||
|
let icon_opt = if icon_path.is_file() {
|
||||||
|
match fs::read(&icon_path) {
|
||||||
|
Ok(icon_data) => Some(icon_data),
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("failed to read icon {:?}: {:?}", icon_path, err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
//IMPORTANT: Assume the identity of the user to ensure we don't read wallpaper file data as root
|
||||||
|
let wallpapers_opt = run_as_user(&user, || {
|
||||||
|
//TODO: use libcosmic to find this path
|
||||||
|
let wallpapers_path = Path::new(&user.dir)
|
||||||
|
.join(".local/state/cosmic/com.system76.CosmicBackground/v1/wallpapers");
|
||||||
|
if wallpapers_path.is_file() {
|
||||||
|
match fs::read_to_string(&wallpapers_path) {
|
||||||
|
Ok(wallpapers_ron) => {
|
||||||
|
match ron::from_str::<Vec<(String, Source)>>(&wallpapers_ron) {
|
||||||
|
Ok(sources) => {
|
||||||
|
let mut wallpaper_datas = Vec::new();
|
||||||
|
for (output, source) in sources {
|
||||||
|
match source {
|
||||||
|
Source::Path(path) => match fs::read(&path) {
|
||||||
|
Ok(bytes) => {
|
||||||
|
wallpaper_datas.push((
|
||||||
|
output,
|
||||||
|
WallpaperData::Bytes(bytes),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!(
|
||||||
|
"failed to read wallpaper {:?}: {:?}",
|
||||||
|
path,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Source::Color(color) => {
|
||||||
|
wallpaper_datas
|
||||||
|
.push((output, WallpaperData::Color(color)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(wallpaper_datas)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!(
|
||||||
|
"failed to parse wallpapers {:?}: {:?}",
|
||||||
|
wallpapers_path,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!(
|
||||||
|
"failed to read wallpapers {:?}: {:?}",
|
||||||
|
wallpapers_path,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(|err| GreeterError::RunAsUser(err.to_string()))?;
|
||||||
|
|
||||||
|
user_datas.push(UserData {
|
||||||
|
uid: user.uid,
|
||||||
|
name: user.name,
|
||||||
|
full_name_opt: user
|
||||||
|
.gecos
|
||||||
|
.map(|gecos| gecos.split(',').next().unwrap_or_default().to_string()),
|
||||||
|
icon_opt,
|
||||||
|
//TODO: should wallpapers come from a per-user call?
|
||||||
|
wallpapers_opt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: is ron the best choice for passing around background data?
|
||||||
|
ron::to_string(&user_datas).map_err(|err| GreeterError::Ron(err.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let _conn = ConnectionBuilder::system()?
|
||||||
|
.name("com.system76.CosmicGreeter")?
|
||||||
|
.serve_at("/com/system76/CosmicGreeter", GreeterProxy)?
|
||||||
|
.build()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
pending::<()>().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
14
dbus/com.system76.CosmicGreeter.conf
Normal file
14
dbus/com.system76.CosmicGreeter.conf
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE busconfig PUBLIC
|
||||||
|
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||||
|
<busconfig>
|
||||||
|
<policy group="cosmic-greeter">
|
||||||
|
<allow send_destination="com.system76.CosmicGreeter"/>
|
||||||
|
<allow receive_sender="com.system76.CosmicGreeter"/>
|
||||||
|
</policy>
|
||||||
|
<policy user="root">
|
||||||
|
<allow own="com.system76.CosmicGreeter"/>
|
||||||
|
<allow send_destination="com.system76.CosmicGreeter"/>
|
||||||
|
<allow receive_sender="com.system76.CosmicGreeter"/>
|
||||||
|
</policy>
|
||||||
|
</busconfig>
|
||||||
8
debian/control
vendored
8
debian/control
vendored
|
|
@ -20,8 +20,16 @@ Depends:
|
||||||
adduser,
|
adduser,
|
||||||
bash,
|
bash,
|
||||||
cosmic-comp,
|
cosmic-comp,
|
||||||
|
cosmic-greeter-daemon,
|
||||||
greetd,
|
greetd,
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
${shlibs:Depends}
|
${shlibs:Depends}
|
||||||
Provides: x-display-manager
|
Provides: x-display-manager
|
||||||
Description: Cosmic Greeter
|
Description: Cosmic Greeter
|
||||||
|
|
||||||
|
Package: cosmic-greeter-daemon
|
||||||
|
Architecture: amd64 arm64
|
||||||
|
Depends:
|
||||||
|
${misc:Depends},
|
||||||
|
${shlibs:Depends}
|
||||||
|
Description: Cosmic Greeter daemon
|
||||||
|
|
|
||||||
2
debian/cosmic-greeter-daemon.install
vendored
Normal file
2
debian/cosmic-greeter-daemon.install
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/usr/bin/cosmic-greeter-daemon
|
||||||
|
/usr/share/dbus-1/system.d/com.system76.CosmicGreeter.conf
|
||||||
9
debian/cosmic-greeter-daemon.service
vendored
Normal file
9
debian/cosmic-greeter-daemon.service
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=COSMIC Greeter Daemon
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/cosmic-greeter-daemon
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
1
debian/cosmic-greeter.install
vendored
1
debian/cosmic-greeter.install
vendored
|
|
@ -1 +1,2 @@
|
||||||
cosmic-greeter.toml /etc/greetd/
|
cosmic-greeter.toml /etc/greetd/
|
||||||
|
/usr/bin/cosmic-greeter
|
||||||
|
|
|
||||||
4
debian/cosmic-greeter.service
vendored
4
debian/cosmic-greeter.service
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=COSMIC Greeter daemon
|
Description=COSMIC Greeter
|
||||||
After=systemd-user-sessions.service plymouth-quit-wait.service
|
After=systemd-user-sessions.service plymouth-quit-wait.service cosmic-greeter-daemon.service
|
||||||
After=getty@tty1.service
|
After=getty@tty1.service
|
||||||
Conflicts=getty@tty1.service
|
Conflicts=getty@tty1.service
|
||||||
|
|
||||||
|
|
|
||||||
3
debian/rules
vendored
3
debian/rules
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/make -f
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
export DESTDIR = debian/cosmic-greeter
|
export DESTDIR = debian/tmp
|
||||||
export VENDOR ?= 1
|
export VENDOR ?= 1
|
||||||
|
|
||||||
%:
|
%:
|
||||||
|
|
@ -23,3 +23,4 @@ override_dh_auto_install:
|
||||||
|
|
||||||
override_dh_installsystemd:
|
override_dh_installsystemd:
|
||||||
dh_installsystemd -pcosmic-greeter --no-start -r cosmic-greeter.service
|
dh_installsystemd -pcosmic-greeter --no-start -r cosmic-greeter.service
|
||||||
|
dh_installsystemd -pcosmic-greeter-daemon --no-start -r cosmic-greeter-daemon.service
|
||||||
|
|
|
||||||
12
justfile
12
justfile
|
|
@ -11,6 +11,12 @@ export INSTALL_DIR := base-dir / 'share'
|
||||||
bin-src := 'target' / 'release' / name
|
bin-src := 'target' / 'release' / name
|
||||||
bin-dst := base-dir / 'bin' / name
|
bin-dst := base-dir / 'bin' / name
|
||||||
|
|
||||||
|
daemon-src := 'target' / 'release' / name + '-daemon'
|
||||||
|
daemon-dst := base-dir / 'bin' / name + '-daemon'
|
||||||
|
|
||||||
|
dbus-src := 'dbus' / APPID + '.conf'
|
||||||
|
dbus-dst := base-dir / 'share' / 'dbus-1' / 'system.d' / APPID + '.conf'
|
||||||
|
|
||||||
# Default recipe which runs `just build-release`
|
# Default recipe which runs `just build-release`
|
||||||
default: build-release
|
default: build-release
|
||||||
|
|
||||||
|
|
@ -24,7 +30,7 @@ clean-dist: clean
|
||||||
|
|
||||||
# Compiles with debug profile
|
# Compiles with debug profile
|
||||||
build-debug *args:
|
build-debug *args:
|
||||||
cargo build {{args}}
|
cargo build --all {{args}}
|
||||||
|
|
||||||
# Compiles with release profile
|
# Compiles with release profile
|
||||||
build-release *args: (build-debug '--release' args)
|
build-release *args: (build-debug '--release' args)
|
||||||
|
|
@ -46,10 +52,12 @@ run *args:
|
||||||
# Installs files
|
# Installs files
|
||||||
install:
|
install:
|
||||||
install -Dm0755 {{bin-src}} {{bin-dst}}
|
install -Dm0755 {{bin-src}} {{bin-dst}}
|
||||||
|
install -Dm0755 {{daemon-src}} {{daemon-dst}}
|
||||||
|
install -Dm0755 {{dbus-src}} {{dbus-dst}}
|
||||||
|
|
||||||
# Uninstalls installed files
|
# Uninstalls installed files
|
||||||
uninstall:
|
uninstall:
|
||||||
rm {{bin-dst}}
|
rm {{bin-dst}} {{daemon-dst}} {{dbus-dst}}
|
||||||
|
|
||||||
# Vendor dependencies locally
|
# Vendor dependencies locally
|
||||||
vendor:
|
vendor:
|
||||||
|
|
|
||||||
199
src/greeter.rs
199
src/greeter.rs
|
|
@ -10,7 +10,7 @@ use cosmic::{
|
||||||
self,
|
self,
|
||||||
wayland::{Event as WaylandEvent, LayerEvent, OutputEvent},
|
wayland::{Event as WaylandEvent, LayerEvent, OutputEvent},
|
||||||
},
|
},
|
||||||
futures::SinkExt,
|
futures::{self, SinkExt},
|
||||||
subscription,
|
subscription,
|
||||||
wayland::{
|
wayland::{
|
||||||
actions::layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings},
|
actions::layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings},
|
||||||
|
|
@ -23,14 +23,37 @@ use cosmic::{
|
||||||
iced_runtime::core::window::Id as SurfaceId,
|
iced_runtime::core::window::Id as SurfaceId,
|
||||||
style, widget, Element,
|
style, widget, Element,
|
||||||
};
|
};
|
||||||
|
use cosmic_greeter_daemon::{UserData, WallpaperData};
|
||||||
use greetd_ipc::{codec::SyncCodec, AuthMessageType, Request, Response};
|
use greetd_ipc::{codec::SyncCodec, AuthMessageType, Request, Response};
|
||||||
use std::{collections::HashMap, env, fs, io, path::Path, process, sync::Arc};
|
use std::{collections::HashMap, env, error::Error, fs, io, path::Path, process, sync::Arc};
|
||||||
use tokio::{net::UnixStream, time};
|
use tokio::{net::UnixStream, time};
|
||||||
use wayland_client::{protocol::wl_output::WlOutput, Proxy};
|
use wayland_client::{protocol::wl_output::WlOutput, Proxy};
|
||||||
|
use zbus::{dbus_proxy, Connection};
|
||||||
|
|
||||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
#[dbus_proxy(
|
||||||
|
interface = "com.system76.CosmicGreeter",
|
||||||
|
default_service = "com.system76.CosmicGreeter",
|
||||||
|
default_path = "/com/system76/CosmicGreeter"
|
||||||
|
)]
|
||||||
|
trait Greeter {
|
||||||
|
async fn get_user_data(&self) -> Result<String, zbus::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_data_dbus() -> Result<Vec<UserData>, Box<dyn Error>> {
|
||||||
|
let connection = Connection::system().await?;
|
||||||
|
|
||||||
|
// `dbus_proxy` macro creates `MyGreaterProxy` based on `Notifications` trait.
|
||||||
|
let proxy = GreeterProxy::new(&connection).await?;
|
||||||
|
let reply = proxy.get_user_data().await?;
|
||||||
|
|
||||||
|
let user_datas: Vec<UserData> = ron::from_str(&reply)?;
|
||||||
|
Ok(user_datas)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_data_fallback() -> Vec<UserData> {
|
||||||
// The pwd::Passwd method is unsafe (but not labelled as such) due to using global state (libc pwent functions).
|
// The pwd::Passwd method is unsafe (but not labelled as such) due to using global state (libc pwent functions).
|
||||||
let users: Vec<_> = /* unsafe */ {
|
/* unsafe */
|
||||||
|
{
|
||||||
pwd::Passwd::iter()
|
pwd::Passwd::iter()
|
||||||
.filter(|user| {
|
.filter(|user| {
|
||||||
if user.uid < 1000 {
|
if user.uid < 1000 {
|
||||||
|
|
@ -51,7 +74,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let icon_path = Path::new("/var/lib/AccountsService/icons").join(&user.name);
|
let icon_path = Path::new("/var/lib/AccountsService/icons").join(&user.name);
|
||||||
let icon_opt = if icon_path.is_file() {
|
let icon_opt = if icon_path.is_file() {
|
||||||
match fs::read(&icon_path) {
|
match fs::read(&icon_path) {
|
||||||
Ok(icon_data) => Some(widget::image::Handle::from_memory(icon_data)),
|
Ok(icon_data) => Some(icon_data),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("failed to read {:?}: {:?}", icon_path, err);
|
log::error!("failed to read {:?}: {:?}", icon_path, err);
|
||||||
None
|
None
|
||||||
|
|
@ -60,11 +83,33 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
(user, icon_opt)
|
|
||||||
|
UserData {
|
||||||
|
uid: user.uid,
|
||||||
|
name: user.name,
|
||||||
|
full_name_opt: user
|
||||||
|
.gecos
|
||||||
|
.map(|gecos| gecos.split(',').next().unwrap_or_default().to_string()),
|
||||||
|
icon_opt,
|
||||||
|
wallpapers_opt: None,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut user_datas = match futures::executor::block_on(async { user_data_dbus().await }) {
|
||||||
|
Ok(ok) => ok,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("failed to load user data from daemon: {}", err);
|
||||||
|
user_data_fallback()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sort user data by uid
|
||||||
|
user_datas.sort_by(|a, b| a.uid.cmp(&b.uid));
|
||||||
|
|
||||||
enum SessionType {
|
enum SessionType {
|
||||||
X11,
|
X11,
|
||||||
Wayland,
|
Wayland,
|
||||||
|
|
@ -186,13 +231,13 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
sessions
|
sessions
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: use background config
|
let fallback_background =
|
||||||
let background = widget::image::Handle::from_memory(include_bytes!("../res/background.png"));
|
widget::image::Handle::from_memory(include_bytes!("../res/background.png"));
|
||||||
|
|
||||||
let flags = Flags {
|
let flags = Flags {
|
||||||
users,
|
user_datas,
|
||||||
sessions,
|
sessions,
|
||||||
background,
|
fallback_background,
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = Settings::default().no_main_window(true);
|
let settings = Settings::default().no_main_window(true);
|
||||||
|
|
@ -287,9 +332,9 @@ fn request_command(socket: Arc<UnixStream>, request: Request) -> Command<Message
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Flags {
|
pub struct Flags {
|
||||||
users: Vec<(pwd::Passwd, Option<widget::image::Handle>)>,
|
user_datas: Vec<UserData>,
|
||||||
sessions: HashMap<String, Vec<String>>,
|
sessions: HashMap<String, Vec<String>>,
|
||||||
background: widget::image::Handle,
|
fallback_background: widget::image::Handle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -329,6 +374,7 @@ pub struct App {
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
surface_ids: HashMap<WlOutput, SurfaceId>,
|
surface_ids: HashMap<WlOutput, SurfaceId>,
|
||||||
active_surface_id_opt: Option<SurfaceId>,
|
active_surface_id_opt: Option<SurfaceId>,
|
||||||
|
surface_images: HashMap<SurfaceId, widget::image::Handle>,
|
||||||
surface_names: HashMap<SurfaceId, String>,
|
surface_names: HashMap<SurfaceId, String>,
|
||||||
text_input_ids: HashMap<SurfaceId, widget::Id>,
|
text_input_ids: HashMap<SurfaceId, widget::Id>,
|
||||||
network_icon_opt: Option<&'static str>,
|
network_icon_opt: Option<&'static str>,
|
||||||
|
|
@ -341,6 +387,55 @@ pub struct App {
|
||||||
error_opt: Option<String>,
|
error_opt: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn update_wallpapers(&mut self) {
|
||||||
|
let username = match &self.username_opt {
|
||||||
|
Some(some) => some,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_data = match self.flags.user_datas.iter().find(|x| &x.name == username) {
|
||||||
|
Some(some) => some,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let wallpapers = match &user_data.wallpapers_opt {
|
||||||
|
Some(some) => some,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (output, surface_id) in self.surface_ids.iter() {
|
||||||
|
if self.surface_images.contains_key(surface_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output_name = match self.surface_names.get(surface_id) {
|
||||||
|
Some(some) => some,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
log::info!("updating wallpaper for {:?}", output_name);
|
||||||
|
|
||||||
|
for (wallpaper_output_name, wallpaper_data) in wallpapers.iter() {
|
||||||
|
if wallpaper_output_name == output_name {
|
||||||
|
match wallpaper_data {
|
||||||
|
WallpaperData::Bytes(bytes) => {
|
||||||
|
let image = widget::image::Handle::from_memory(bytes.clone());
|
||||||
|
self.surface_images.insert(*surface_id, image);
|
||||||
|
//TODO: what to do about duplicates?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
WallpaperData::Color(color) => {
|
||||||
|
//TODO: support color sources
|
||||||
|
log::warn!("output {}: unsupported source {:?}", output.id(), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
||||||
impl cosmic::Application for App {
|
impl cosmic::Application for App {
|
||||||
/// Default async executor to use with the app.
|
/// Default async executor to use with the app.
|
||||||
|
|
@ -384,12 +479,12 @@ impl cosmic::Application for App {
|
||||||
flags,
|
flags,
|
||||||
surface_ids: HashMap::new(),
|
surface_ids: HashMap::new(),
|
||||||
active_surface_id_opt: None,
|
active_surface_id_opt: None,
|
||||||
|
surface_images: HashMap::new(),
|
||||||
surface_names: HashMap::new(),
|
surface_names: HashMap::new(),
|
||||||
text_input_ids: HashMap::new(),
|
text_input_ids: HashMap::new(),
|
||||||
network_icon_opt: None,
|
network_icon_opt: None,
|
||||||
power_info_opt: None,
|
power_info_opt: None,
|
||||||
socket_state: SocketState::Pending,
|
socket_state: SocketState::Pending,
|
||||||
//TODO: choose last used user
|
|
||||||
username_opt: None,
|
username_opt: None,
|
||||||
prompt_opt: None,
|
prompt_opt: None,
|
||||||
session_names,
|
session_names,
|
||||||
|
|
@ -437,6 +532,8 @@ impl cosmic::Application for App {
|
||||||
Some(output_info) => match output_info.name {
|
Some(output_info) => match output_info.name {
|
||||||
Some(output_name) => {
|
Some(output_name) => {
|
||||||
self.surface_names.insert(surface_id, output_name.clone());
|
self.surface_names.insert(surface_id, output_name.clone());
|
||||||
|
self.surface_images.remove(&surface_id);
|
||||||
|
self.update_wallpapers();
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::warn!("output {}: no output name", output.id());
|
log::warn!("output {}: no output name", output.id());
|
||||||
|
|
@ -477,6 +574,7 @@ impl cosmic::Application for App {
|
||||||
log::info!("output {}: removed", output.id());
|
log::info!("output {}: removed", output.id());
|
||||||
match self.surface_ids.remove(&output) {
|
match self.surface_ids.remove(&output) {
|
||||||
Some(surface_id) => {
|
Some(surface_id) => {
|
||||||
|
self.surface_images.remove(&surface_id);
|
||||||
self.surface_names.remove(&surface_id);
|
self.surface_names.remove(&surface_id);
|
||||||
self.text_input_ids.remove(&surface_id);
|
self.text_input_ids.remove(&surface_id);
|
||||||
return destroy_layer_surface(surface_id);
|
return destroy_layer_surface(surface_id);
|
||||||
|
|
@ -503,6 +601,23 @@ impl cosmic::Application for App {
|
||||||
},
|
},
|
||||||
Message::Socket(socket_state) => {
|
Message::Socket(socket_state) => {
|
||||||
self.socket_state = socket_state;
|
self.socket_state = socket_state;
|
||||||
|
match &self.socket_state {
|
||||||
|
SocketState::Open(socket) => {
|
||||||
|
// When socket is opened, request default user
|
||||||
|
//TODO: choose last used user
|
||||||
|
match self.flags.user_datas.first().map(|x| x.name.clone()) {
|
||||||
|
Some(username) => {
|
||||||
|
let socket = socket.clone();
|
||||||
|
return Command::perform(
|
||||||
|
async { message::app(Message::Username(socket, username)) },
|
||||||
|
|x| x,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message::NetworkIcon(network_icon_opt) => {
|
Message::NetworkIcon(network_icon_opt) => {
|
||||||
self.network_icon_opt = network_icon_opt;
|
self.network_icon_opt = network_icon_opt;
|
||||||
|
|
@ -526,6 +641,8 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
Message::Username(socket, username) => {
|
Message::Username(socket, username) => {
|
||||||
self.username_opt = Some(username.clone());
|
self.username_opt = Some(username.clone());
|
||||||
|
self.surface_images.clear();
|
||||||
|
self.update_wallpapers();
|
||||||
return request_command(socket, Request::CreateSession { username });
|
return request_command(socket, Request::CreateSession { username });
|
||||||
}
|
}
|
||||||
Message::Auth(socket, response) => {
|
Message::Auth(socket, response) => {
|
||||||
|
|
@ -566,6 +683,7 @@ impl cosmic::Application for App {
|
||||||
Message::Exit => {
|
Message::Exit => {
|
||||||
let mut commands = Vec::new();
|
let mut commands = Vec::new();
|
||||||
for (_output, surface_id) in self.surface_ids.drain() {
|
for (_output, surface_id) in self.surface_ids.drain() {
|
||||||
|
self.surface_images.remove(&surface_id);
|
||||||
self.surface_names.remove(&surface_id);
|
self.surface_names.remove(&surface_id);
|
||||||
self.text_input_ids.remove(&surface_id);
|
self.text_input_ids.remove(&surface_id);
|
||||||
commands.push(destroy_layer_surface(surface_id));
|
commands.push(destroy_layer_surface(surface_id));
|
||||||
|
|
@ -671,15 +789,20 @@ impl cosmic::Application for App {
|
||||||
SocketState::Open(socket) => {
|
SocketState::Open(socket) => {
|
||||||
match &self.username_opt {
|
match &self.username_opt {
|
||||||
Some(username) => {
|
Some(username) => {
|
||||||
for (user, icon_opt) in &self.flags.users {
|
for user_data in &self.flags.user_datas {
|
||||||
if &user.name == username {
|
if &user_data.name == username {
|
||||||
match icon_opt {
|
match &user_data.icon_opt {
|
||||||
Some(icon) => {
|
Some(icon) => {
|
||||||
column = column.push(
|
column = column.push(
|
||||||
widget::container(
|
widget::container(
|
||||||
widget::Image::new(icon.clone())
|
widget::Image::new(
|
||||||
.width(Length::Fixed(78.0))
|
//TODO: cache handle
|
||||||
.height(Length::Fixed(78.0)),
|
widget::image::Handle::from_memory(
|
||||||
|
icon.clone(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.width(Length::Fixed(78.0))
|
||||||
|
.height(Length::Fixed(78.0)),
|
||||||
)
|
)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.align_x(alignment::Horizontal::Center),
|
.align_x(alignment::Horizontal::Center),
|
||||||
|
|
@ -687,9 +810,8 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
match &user.gecos {
|
match &user_data.full_name_opt {
|
||||||
Some(gecos) => {
|
Some(full_name) => {
|
||||||
let full_name = gecos.split(",").next().unwrap_or("");
|
|
||||||
column = column.push(
|
column = column.push(
|
||||||
widget::container(widget::text::title4(full_name))
|
widget::container(widget::text::title4(full_name))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
@ -702,18 +824,23 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let mut row =
|
let mut row = widget::row::with_capacity(self.flags.user_datas.len())
|
||||||
widget::row::with_capacity(self.flags.users.len()).spacing(12.0);
|
.spacing(12.0);
|
||||||
for (user, icon_opt) in &self.flags.users {
|
for user_data in &self.flags.user_datas {
|
||||||
let mut column = widget::column::with_capacity(2).spacing(12.0);
|
let mut column = widget::column::with_capacity(2).spacing(12.0);
|
||||||
|
|
||||||
match icon_opt {
|
match &user_data.icon_opt {
|
||||||
Some(icon) => {
|
Some(icon) => {
|
||||||
column = column.push(
|
column = column.push(
|
||||||
widget::container(
|
widget::container(
|
||||||
widget::Image::new(icon.clone())
|
widget::Image::new(
|
||||||
.width(Length::Fixed(78.0))
|
//TODO: cache handle
|
||||||
.height(Length::Fixed(78.0)),
|
widget::image::Handle::from_memory(
|
||||||
|
icon.clone(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.width(Length::Fixed(78.0))
|
||||||
|
.height(Length::Fixed(78.0)),
|
||||||
)
|
)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.align_x(alignment::Horizontal::Center),
|
.align_x(alignment::Horizontal::Center),
|
||||||
|
|
@ -721,9 +848,8 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
match &user.gecos {
|
match &user_data.full_name_opt {
|
||||||
Some(gecos) => {
|
Some(full_name) => {
|
||||||
let full_name = gecos.split(",").next().unwrap_or("");
|
|
||||||
column = column.push(
|
column = column.push(
|
||||||
widget::container(widget::text::title4(full_name))
|
widget::container(widget::text::title4(full_name))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
@ -740,7 +866,9 @@ impl cosmic::Application for App {
|
||||||
.padding(16)
|
.padding(16)
|
||||||
.style(cosmic::theme::Container::Card),
|
.style(cosmic::theme::Container::Card),
|
||||||
)
|
)
|
||||||
.on_press(Message::Username(socket.clone(), user.name.clone())),
|
.on_press(
|
||||||
|
Message::Username(socket.clone(), user_data.name.clone()),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
column = column.push(row);
|
column = column.push(row);
|
||||||
|
|
@ -844,7 +972,10 @@ impl cosmic::Application for App {
|
||||||
.align_y(alignment::Vertical::Top)
|
.align_y(alignment::Vertical::Top)
|
||||||
.style(cosmic::theme::Container::Transparent),
|
.style(cosmic::theme::Container::Transparent),
|
||||||
)
|
)
|
||||||
.image(self.flags.background.clone())
|
.image(match self.surface_images.get(&surface_id) {
|
||||||
|
Some(some) => some.clone(),
|
||||||
|
None => self.flags.fallback_background.clone(),
|
||||||
|
})
|
||||||
.content_fit(iced::ContentFit::Cover)
|
.content_fit(iced::ContentFit::Cover)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue