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/cosmic-greeter.substvars
|
||||
/debian/cosmic-greeter/
|
||||
/debian/cosmic-greeter-daemon.substvars
|
||||
/debian/cosmic-greeter-daemon/
|
||||
/debian/files
|
||||
/debian/tmp/
|
||||
/target/
|
||||
/vendor.tar
|
||||
/vendor/
|
||||
|
|
|
|||
19
Cargo.lock
generated
19
Cargo.lock
generated
|
|
@ -811,6 +811,7 @@ dependencies = [
|
|||
"cosmic-bg-config",
|
||||
"cosmic-config",
|
||||
"cosmic-dbus-networkmanager",
|
||||
"cosmic-greeter-daemon",
|
||||
"env_logger",
|
||||
"freedesktop_entry_parser",
|
||||
"greetd_ipc",
|
||||
|
|
@ -819,6 +820,7 @@ dependencies = [
|
|||
"logind-zbus",
|
||||
"pam-client",
|
||||
"pwd",
|
||||
"ron",
|
||||
"shlex",
|
||||
"tokio",
|
||||
"upower_dbus",
|
||||
|
|
@ -826,6 +828,23 @@ dependencies = [
|
|||
"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]]
|
||||
name = "cosmic-protocols"
|
||||
version = "0.1.0"
|
||||
|
|
|
|||
54
Cargo.toml
54
Cargo.toml
|
|
@ -5,12 +5,19 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
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"
|
||||
log = "0.4.20"
|
||||
libcosmic = { workspace = true, features = ["tokio", "wayland"] }
|
||||
log.workspace = true
|
||||
pam-client = "0.5.0"
|
||||
pwd = "1.4.0"
|
||||
pwd.workspace = true
|
||||
ron.workspace = true
|
||||
shlex = "1.2.0"
|
||||
#TODO: reduce features
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
wayland-client = "0.31.1"
|
||||
# For network status using networkmanager feature
|
||||
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
|
||||
upower_dbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
# Required for some features
|
||||
zbus = { version = "3.14.1", 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"]
|
||||
zbus = { workspace = true, optional = true }
|
||||
|
||||
[dependencies.greetd_ipc]
|
||||
version = "0.9.0"
|
||||
features = ["sync-codec"]
|
||||
|
||||
[dependencies.libcosmic]
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
features = ["tokio", "wayland"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.33.0"
|
||||
features = ["full"]
|
||||
|
||||
[features]
|
||||
default = ["logind", "networkmanager", "upower"]
|
||||
logind = ["logind-zbus", "zbus"]
|
||||
networkmanager = ["cosmic-dbus-networkmanager", "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,
|
||||
bash,
|
||||
cosmic-comp,
|
||||
cosmic-greeter-daemon,
|
||||
greetd,
|
||||
${misc:Depends},
|
||||
${shlibs:Depends}
|
||||
Provides: x-display-manager
|
||||
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/
|
||||
/usr/bin/cosmic-greeter
|
||||
|
|
|
|||
4
debian/cosmic-greeter.service
vendored
4
debian/cosmic-greeter.service
vendored
|
|
@ -1,6 +1,6 @@
|
|||
[Unit]
|
||||
Description=COSMIC Greeter daemon
|
||||
After=systemd-user-sessions.service plymouth-quit-wait.service
|
||||
Description=COSMIC Greeter
|
||||
After=systemd-user-sessions.service plymouth-quit-wait.service cosmic-greeter-daemon.service
|
||||
After=getty@tty1.service
|
||||
Conflicts=getty@tty1.service
|
||||
|
||||
|
|
|
|||
3
debian/rules
vendored
3
debian/rules
vendored
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
export DESTDIR = debian/cosmic-greeter
|
||||
export DESTDIR = debian/tmp
|
||||
export VENDOR ?= 1
|
||||
|
||||
%:
|
||||
|
|
@ -23,3 +23,4 @@ override_dh_auto_install:
|
|||
|
||||
override_dh_installsystemd:
|
||||
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-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: build-release
|
||||
|
||||
|
|
@ -24,7 +30,7 @@ clean-dist: clean
|
|||
|
||||
# Compiles with debug profile
|
||||
build-debug *args:
|
||||
cargo build {{args}}
|
||||
cargo build --all {{args}}
|
||||
|
||||
# Compiles with release profile
|
||||
build-release *args: (build-debug '--release' args)
|
||||
|
|
@ -46,10 +52,12 @@ run *args:
|
|||
# Installs files
|
||||
install:
|
||||
install -Dm0755 {{bin-src}} {{bin-dst}}
|
||||
install -Dm0755 {{daemon-src}} {{daemon-dst}}
|
||||
install -Dm0755 {{dbus-src}} {{dbus-dst}}
|
||||
|
||||
# Uninstalls installed files
|
||||
uninstall:
|
||||
rm {{bin-dst}}
|
||||
rm {{bin-dst}} {{daemon-dst}} {{dbus-dst}}
|
||||
|
||||
# Vendor dependencies locally
|
||||
vendor:
|
||||
|
|
|
|||
191
src/greeter.rs
191
src/greeter.rs
|
|
@ -10,7 +10,7 @@ use cosmic::{
|
|||
self,
|
||||
wayland::{Event as WaylandEvent, LayerEvent, OutputEvent},
|
||||
},
|
||||
futures::SinkExt,
|
||||
futures::{self, SinkExt},
|
||||
subscription,
|
||||
wayland::{
|
||||
actions::layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings},
|
||||
|
|
@ -23,14 +23,37 @@ use cosmic::{
|
|||
iced_runtime::core::window::Id as SurfaceId,
|
||||
style, widget, Element,
|
||||
};
|
||||
use cosmic_greeter_daemon::{UserData, WallpaperData};
|
||||
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 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).
|
||||
let users: Vec<_> = /* unsafe */ {
|
||||
/* unsafe */
|
||||
{
|
||||
pwd::Passwd::iter()
|
||||
.filter(|user| {
|
||||
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_opt = if icon_path.is_file() {
|
||||
match fs::read(&icon_path) {
|
||||
Ok(icon_data) => Some(widget::image::Handle::from_memory(icon_data)),
|
||||
Ok(icon_data) => Some(icon_data),
|
||||
Err(err) => {
|
||||
log::error!("failed to read {:?}: {:?}", icon_path, err);
|
||||
None
|
||||
|
|
@ -60,11 +83,33 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
} else {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
X11,
|
||||
Wayland,
|
||||
|
|
@ -186,13 +231,13 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
sessions
|
||||
};
|
||||
|
||||
//TODO: use background config
|
||||
let background = widget::image::Handle::from_memory(include_bytes!("../res/background.png"));
|
||||
let fallback_background =
|
||||
widget::image::Handle::from_memory(include_bytes!("../res/background.png"));
|
||||
|
||||
let flags = Flags {
|
||||
users,
|
||||
user_datas,
|
||||
sessions,
|
||||
background,
|
||||
fallback_background,
|
||||
};
|
||||
|
||||
let settings = Settings::default().no_main_window(true);
|
||||
|
|
@ -287,9 +332,9 @@ fn request_command(socket: Arc<UnixStream>, request: Request) -> Command<Message
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct Flags {
|
||||
users: Vec<(pwd::Passwd, Option<widget::image::Handle>)>,
|
||||
user_datas: Vec<UserData>,
|
||||
sessions: HashMap<String, Vec<String>>,
|
||||
background: widget::image::Handle,
|
||||
fallback_background: widget::image::Handle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -329,6 +374,7 @@ pub struct App {
|
|||
flags: Flags,
|
||||
surface_ids: HashMap<WlOutput, SurfaceId>,
|
||||
active_surface_id_opt: Option<SurfaceId>,
|
||||
surface_images: HashMap<SurfaceId, widget::image::Handle>,
|
||||
surface_names: HashMap<SurfaceId, String>,
|
||||
text_input_ids: HashMap<SurfaceId, widget::Id>,
|
||||
network_icon_opt: Option<&'static str>,
|
||||
|
|
@ -341,6 +387,55 @@ pub struct App {
|
|||
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.
|
||||
impl cosmic::Application for App {
|
||||
/// Default async executor to use with the app.
|
||||
|
|
@ -384,12 +479,12 @@ impl cosmic::Application for App {
|
|||
flags,
|
||||
surface_ids: HashMap::new(),
|
||||
active_surface_id_opt: None,
|
||||
surface_images: HashMap::new(),
|
||||
surface_names: HashMap::new(),
|
||||
text_input_ids: HashMap::new(),
|
||||
network_icon_opt: None,
|
||||
power_info_opt: None,
|
||||
socket_state: SocketState::Pending,
|
||||
//TODO: choose last used user
|
||||
username_opt: None,
|
||||
prompt_opt: None,
|
||||
session_names,
|
||||
|
|
@ -437,6 +532,8 @@ impl cosmic::Application for App {
|
|||
Some(output_info) => match output_info.name {
|
||||
Some(output_name) => {
|
||||
self.surface_names.insert(surface_id, output_name.clone());
|
||||
self.surface_images.remove(&surface_id);
|
||||
self.update_wallpapers();
|
||||
}
|
||||
None => {
|
||||
log::warn!("output {}: no output name", output.id());
|
||||
|
|
@ -477,6 +574,7 @@ impl cosmic::Application for App {
|
|||
log::info!("output {}: removed", output.id());
|
||||
match self.surface_ids.remove(&output) {
|
||||
Some(surface_id) => {
|
||||
self.surface_images.remove(&surface_id);
|
||||
self.surface_names.remove(&surface_id);
|
||||
self.text_input_ids.remove(&surface_id);
|
||||
return destroy_layer_surface(surface_id);
|
||||
|
|
@ -503,6 +601,23 @@ impl cosmic::Application for App {
|
|||
},
|
||||
Message::Socket(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) => {
|
||||
self.network_icon_opt = network_icon_opt;
|
||||
|
|
@ -526,6 +641,8 @@ impl cosmic::Application for App {
|
|||
}
|
||||
Message::Username(socket, username) => {
|
||||
self.username_opt = Some(username.clone());
|
||||
self.surface_images.clear();
|
||||
self.update_wallpapers();
|
||||
return request_command(socket, Request::CreateSession { username });
|
||||
}
|
||||
Message::Auth(socket, response) => {
|
||||
|
|
@ -566,6 +683,7 @@ impl cosmic::Application for App {
|
|||
Message::Exit => {
|
||||
let mut commands = Vec::new();
|
||||
for (_output, surface_id) in self.surface_ids.drain() {
|
||||
self.surface_images.remove(&surface_id);
|
||||
self.surface_names.remove(&surface_id);
|
||||
self.text_input_ids.remove(&surface_id);
|
||||
commands.push(destroy_layer_surface(surface_id));
|
||||
|
|
@ -671,13 +789,18 @@ impl cosmic::Application for App {
|
|||
SocketState::Open(socket) => {
|
||||
match &self.username_opt {
|
||||
Some(username) => {
|
||||
for (user, icon_opt) in &self.flags.users {
|
||||
if &user.name == username {
|
||||
match icon_opt {
|
||||
for user_data in &self.flags.user_datas {
|
||||
if &user_data.name == username {
|
||||
match &user_data.icon_opt {
|
||||
Some(icon) => {
|
||||
column = column.push(
|
||||
widget::container(
|
||||
widget::Image::new(icon.clone())
|
||||
widget::Image::new(
|
||||
//TODO: cache handle
|
||||
widget::image::Handle::from_memory(
|
||||
icon.clone(),
|
||||
),
|
||||
)
|
||||
.width(Length::Fixed(78.0))
|
||||
.height(Length::Fixed(78.0)),
|
||||
)
|
||||
|
|
@ -687,9 +810,8 @@ impl cosmic::Application for App {
|
|||
}
|
||||
None => {}
|
||||
}
|
||||
match &user.gecos {
|
||||
Some(gecos) => {
|
||||
let full_name = gecos.split(",").next().unwrap_or("");
|
||||
match &user_data.full_name_opt {
|
||||
Some(full_name) => {
|
||||
column = column.push(
|
||||
widget::container(widget::text::title4(full_name))
|
||||
.width(Length::Fill)
|
||||
|
|
@ -702,16 +824,21 @@ impl cosmic::Application for App {
|
|||
}
|
||||
}
|
||||
None => {
|
||||
let mut row =
|
||||
widget::row::with_capacity(self.flags.users.len()).spacing(12.0);
|
||||
for (user, icon_opt) in &self.flags.users {
|
||||
let mut row = widget::row::with_capacity(self.flags.user_datas.len())
|
||||
.spacing(12.0);
|
||||
for user_data in &self.flags.user_datas {
|
||||
let mut column = widget::column::with_capacity(2).spacing(12.0);
|
||||
|
||||
match icon_opt {
|
||||
match &user_data.icon_opt {
|
||||
Some(icon) => {
|
||||
column = column.push(
|
||||
widget::container(
|
||||
widget::Image::new(icon.clone())
|
||||
widget::Image::new(
|
||||
//TODO: cache handle
|
||||
widget::image::Handle::from_memory(
|
||||
icon.clone(),
|
||||
),
|
||||
)
|
||||
.width(Length::Fixed(78.0))
|
||||
.height(Length::Fixed(78.0)),
|
||||
)
|
||||
|
|
@ -721,9 +848,8 @@ impl cosmic::Application for App {
|
|||
}
|
||||
None => {}
|
||||
}
|
||||
match &user.gecos {
|
||||
Some(gecos) => {
|
||||
let full_name = gecos.split(",").next().unwrap_or("");
|
||||
match &user_data.full_name_opt {
|
||||
Some(full_name) => {
|
||||
column = column.push(
|
||||
widget::container(widget::text::title4(full_name))
|
||||
.width(Length::Fill)
|
||||
|
|
@ -740,7 +866,9 @@ impl cosmic::Application for App {
|
|||
.padding(16)
|
||||
.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);
|
||||
|
|
@ -844,7 +972,10 @@ impl cosmic::Application for App {
|
|||
.align_y(alignment::Vertical::Top)
|
||||
.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)
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue