REFACTOR REFACTOR REFACTOR

Added logging
Improved control flow
Stopped blocking the goddamn runtime with my background code
Got rid of a potential double free
This commit is contained in:
CanadianBaconBoi 2026-06-16 19:39:32 +02:00
parent 48c493c4f5
commit 4f2902a847
7 changed files with 370 additions and 216 deletions

View File

@ -28,6 +28,9 @@ tokio-stream = {version = "0.1.18", features = ["fs"]}
greetd-stub = "0.3.0"
chrono = "0.4.44"
pretty_env_logger = "0.5.0"
log = {version = "0.4.32", features = ["kv"]}
pexels-api = { git = "https://github.com/houseme/pexels", rev = "b0b692a" }
reqwest = { version = "0.13.3", features = ["json", "default-tls"] }

View File

@ -1,72 +1,82 @@
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::Arc;
use anyhow::{anyhow, Context};
use greetd_ipc::{AuthMessageType, ErrorType, Request, Response};
use crate::config::Config;
use crate::input::KeyboardLockStates;
use crate::{GreeterData, GreeterDisplay, LoginOptionTileData};
use anyhow::{Context, anyhow};
use greetd_ipc::codec::TokioCodec;
use greetd_ipc::{AuthMessageType, ErrorType, Request, Response};
use i_slint_core::api::{ComponentHandle, Global, Image, Rgba8Pixel, SharedPixelBuffer, Weak};
use i_slint_core::model::{Model, VecModel};
use log::{debug, error, info, trace};
use magick_rust::MagickWand;
use pexels_api::{PexelsClient, SearchParams};
use rand::seq::SliceRandom;
use std::collections::VecDeque;
use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::Arc;
use tokio::fs::File;
use tokio::io::{AsyncBufReadExt, BufReader, Lines};
use tokio::net::UnixStream;
use crate::{GreeterData, GreeterDisplay, LoginOptionTileData};
use crate::config::Config;
use crate::input::KeyboardLockStates;
#[cfg(not(debug_assertions))]
use zbus::{Connection, proxy};
#[cfg(not(debug_assertions))]
use slint::SharedString;
use tokio_stream::StreamExt;
use tokio_stream::wrappers::ReadDirStream;
#[cfg(not(debug_assertions))]
use zbus::{Connection, proxy};
pub fn register_login_callback(data: &GreeterData, display_weak: Weak<GreeterDisplay>, tiles: Rc<VecModel<LoginOptionTileData>>) {
pub fn register_login_callback(
data: &GreeterData,
display_weak: Weak<GreeterDisplay>,
tiles: Rc<VecModel<LoginOptionTileData>>,
) {
data.on_login({
let tiles = tiles.clone();
let display_weak = display_weak.clone();
move |username, password| {
debug!(target: "ckgreeter::callbacks::login", "----- Login callback called");
let tiles = tiles.clone();
let display_weak = display_weak.clone();
debug!(target: "ckgreeter::callbacks::login", "spawning slint thread");
slint::spawn_local(async move {
debug!(target: "ckgreeter::callbacks::login", "spawned slint thread");
let display = display_weak.unwrap();
let username = username.to_string();
let password = password.to_string();
debug!(target: "ckgreeter::callbacks::login", "username: {username:?}");
let index = display.get_selected_index();
let tile = &tiles.row_data(index as usize);
let tile = match tile {
Some(tile) => {tile}
Some(tile) => tile,
None => {
display.invoke_set_error("Failed to login: Invalid index for tile".into());
error!("Failed to login: Invalid index for tile: {index}");
return;
}
};
match login(
username.as_str(),
password.as_str(),
tile.command.as_str(),
)
.await
{
match login(username.as_str(), password.as_str(), tile.command.as_str()).await {
Ok(successful) => {
if !successful {
display.invoke_set_error("Failed to login: Bad Login".into());
error!("Failed to login: Bad Login");
return;
}
if let Err(err) = slint::quit_event_loop() {
display.invoke_set_error(format!("Failed to exit greeter: {err}").into());
display
.invoke_set_error(format!("Failed to exit greeter: {err}").into());
error!("Failed to exit greeter: {err}");
}
}
Err(err) => {
display.invoke_set_error(format!("Failed to login: {}", err).into());
display.invoke_set_error(format!("Failed to login: {err}").into());
error!("Failed to login: {err}");
return;
}
};
@ -75,11 +85,7 @@ pub fn register_login_callback(data: &GreeterData, display_weak: Weak<GreeterDis
}
});
async fn login(
username: &str,
password: &str,
cmd: &str,
) -> anyhow::Result<bool> {
async fn login(username: &str, password: &str, cmd: &str) -> anyhow::Result<bool> {
if username.trim().is_empty() {
return Err(anyhow::anyhow!("Username is required"));
}
@ -88,12 +94,21 @@ pub fn register_login_callback(data: &GreeterData, display_weak: Weak<GreeterDis
return Err(anyhow::anyhow!("Command is required"));
}
let socket = std::env::var("GREETD_SOCK")
.context("GREETD_SOCK is not set")?;
debug!(target: "ckgreeter::callbacks::login", "connecting to greetd socket");
let mut stream = UnixStream::connect(socket).await.context("failed to connect to greetd socket")?;
let socket = std::env::var("GREETD_SOCK").context("GREETD_SOCK is not set")?;
let mut stream = UnixStream::connect(socket)
.await
.context("failed to connect to greetd socket")?;
debug!(target: "ckgreeter::callbacks::login", "connected to greetd socket");
let mut next_request = Request::CreateSession {
username: username.to_string(),
};
trace!(target: "ckgreeter::callbacks::login", "sending request to create session: {next_request:?}");
let mut next_request = Request::CreateSession { username: username.to_string() };
let mut starting = false;
loop {
next_request.write_to(&mut stream).await?;
@ -116,13 +131,16 @@ pub fn register_login_callback(data: &GreeterData, display_weak: Weak<GreeterDis
AuthMessageType::Error => None,
};
trace!(target: "ckgreeter::callbacks::login", "sending request: {next_request:?}");
next_request = Request::PostAuthMessageResponse { response };
}
Response::Success => {
if starting {
debug!(target: "ckgreeter::callbacks::login", "starting session");
return Ok(true);
} else {
starting = true;
trace!(target: "ckgreeter::callbacks::login", "sending request: {next_request:?}");
next_request = Request::StartSession {
env: vec![],
cmd: vec![cmd.to_string()],
@ -133,6 +151,7 @@ pub fn register_login_callback(data: &GreeterData, display_weak: Weak<GreeterDis
error_type,
description,
} => {
debug!(target: "ckgreeter::callbacks::login", "error logging in");
Request::CancelSession.write_to(&mut stream).await?;
return match error_type {
ErrorType::AuthError => Ok(false),
@ -145,9 +164,8 @@ pub fn register_login_callback(data: &GreeterData, display_weak: Weak<GreeterDis
}
pub fn register_power_callback(_data: &GreeterData) {
#[cfg(not(debug_assertions))]
_data.on_loginctl(move |
command: SharedString
| {
_data.on_loginctl(move |command: SharedString| {
debug!(target: "ckgreeter::callbacks::power", "----- Power callback called");
slint::spawn_local(async move {
if let Err(err) = handle_power_command(command.as_str()).await {
eprintln!("Power command failed: {err}");
@ -158,9 +176,16 @@ pub fn register_power_callback(_data: &GreeterData) {
#[cfg(not(debug_assertions))]
async fn handle_power_command(command: &str) -> anyhow::Result<()> {
let connection = Connection::system().await?;
let proxy = PowerManagerProxy::new(&connection).await?;
debug!(target: "ckgreeter::callbacks::power", "command: {command:?}");
trace!(target: "ckgreeter::callbacks::power", "connecting to dbus");
let connection = Connection::system().await?;
trace!(target: "ckgreeter::callbacks::power", "connected to dbus");
trace!(target: "ckgreeter::callbacks::power", "getting power manager proxy");
let proxy = PowerManagerProxy::new(&connection).await?;
trace!(target: "ckgreeter::callbacks::power", "got power manager proxy");
debug!(target: "ckgreeter::callbacks::power", "running power command: {command}");
match command {
"suspend" => proxy.suspend(&false).await?,
"poweroff" => proxy.power_off(&false).await?,
@ -189,34 +214,43 @@ pub fn register_lock_state_callback(data: &GreeterData, data_weak: Weak<GreeterD
move || {
let data_weak = data_weak.clone();
slint::spawn_local(async move {
debug!(target: "ckgreeter::callbacks::lock_state", "----- lock state callback called");
let data = data_weak.unwrap();
if let Some(states) = KeyboardLockStates::get_locks_state() {
data.set_caps_lock_state(states.caps_lock);
trace!(target: "ckgreeter::callbacks::lock_state", "caps lock state: {}", states.caps_lock);
data.set_num_lock_state(states.num_lock);
trace!(target: "ckgreeter::callbacks::lock_state", "num lock state: {}", states.num_lock);
data.set_scroll_lock_state(states.scroll_lock);
trace!(target: "ckgreeter::callbacks::lock_state", "scroll lock state: {}", states.scroll_lock)
}
})
.unwrap();
}
});
}
pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<GreeterDisplay>, data_weak: Weak<GreeterData<'static>>, cache_dir: PathBuf, config: &Config) { //TODO: Seperate this out into seperate methods, cleanup!!!
const MAX_RECENT_WALLPAPERS: usize = 10;
pub fn register_wallpaper_callback(
data: &GreeterData,
display_weak: Weak<GreeterDisplay>,
data_weak: Weak<GreeterData<'static>>,
cache_dir: PathBuf,
config: &Config,
) {
//TODO: Seperate this out into seperate methods, cleanup!!!
const MAX_RECENT_WALLPAPERS: usize = 25;
async fn read_lines<P>(filename: P) -> std::io::Result<Lines<BufReader<File>>>
where
P: AsRef<Path>,
P: AsRef<Path> + std::fmt::Debug,
{
debug!(target: "ckgreeter::callbacks::wallpaper::read_lines", "reading lines from file: {:?}", filename);
let file = File::open(filename).await?;
Ok(BufReader::new(file).lines())
}
async fn write_lines<P>(
filename: P,
lines: VecDeque<String>,
) -> std::io::Result<()>
async fn write_lines<P>(filename: &P, lines: VecDeque<String>) -> std::io::Result<()>
where
P: AsRef<Path>,
P: AsRef<Path> + std::fmt::Debug,
{
let mut contents = String::new();
@ -225,6 +259,7 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
contents.push('\n');
}
debug!(target: "ckgreeter::callbacks::wallpaper::write_lines", "writing lines to file: {:?}", filename);
std::fs::write(filename, contents)
}
@ -235,26 +270,28 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
w: u32,
h: u32,
) {
debug!(target: "ckgreeter::callbacks::wallpaper::set_background_image", "setting background image");
if !display.get_background_toggle() {
trace!(target: "ckgreeter::callbacks::wallpaper::set_background_image", "setting background image 1");
let old = data.get_wallpaper_image();
data.set_wallpaper_image(Image::from_rgba8(SharedPixelBuffer::<
Rgba8Pixel,
>::clone_from_slice(
pixels, w, h
)));
data.set_wallpaper_image(Image::from_rgba8(
SharedPixelBuffer::<Rgba8Pixel>::clone_from_slice(pixels, w, h),
));
trace!(target: "ckgreeter::callbacks::wallpaper::set_background_image", "set background image 1");
drop(old);
} else {
trace!(target: "ckgreeter::callbacks::wallpaper::set_background_image", "setting background image 2");
let old = data.get_wallpaper_image_1();
data.set_wallpaper_image_1(Image::from_rgba8(SharedPixelBuffer::<
Rgba8Pixel,
>::clone_from_slice(
pixels, w, h
)));
data.set_wallpaper_image_1(Image::from_rgba8(
SharedPixelBuffer::<Rgba8Pixel>::clone_from_slice(pixels, w, h),
));
trace!(target: "ckgreeter::callbacks::wallpaper::set_background_image", "set background image 2");
drop(old);
}
}
fn export_wand_rgba(wand: &MagickWand) -> anyhow::Result<(u32, u32, Vec<u8>)> {
trace!(target: "ckgreeter::callbacks::wallpaper::export_wand_rgba", "exporting wand");
let pixels = wand
.export_image_pixels(
0,
@ -265,6 +302,7 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
)
.ok_or_else(|| anyhow!("Failed to export image pixels"))?;
trace!(target: "ckgreeter::callbacks::wallpaper::export_wand_rgba", "exported wand");
Ok((
wand.get_image_width() as u32,
wand.get_image_height() as u32,
@ -276,24 +314,31 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
.tls_backend_rustls()
.http2_prior_knowledge()
.http2_keep_alive_timeout(std::time::Duration::from_secs(10))
.build() {
.build()
{
Ok(client) => client,
Err(err) => {
eprintln!("Failed to create reqwest client: {err}");
return;
}
};
debug!(target: "ckgreeter::callbacks::wallpaper", "reqwest client created");
if let Some(pexels_api_key) = config.pexels_api_key.clone() {
info!(target: "ckgreeter::callbacks::wallpaper", "Using pexels api");
debug!(target: "ckgreeter::callbacks::wallpaper", "pexels api key found");
let pexels_client = Arc::new(PexelsClient::new(pexels_api_key));
let pexels_query = config
.pexels_query
.clone()
.unwrap_or_else(|| String::from("nature"));
debug!(target: "ckgreeter::callbacks::wallpaper", "pexels query: {pexels_query}");
info!(target: "ckgreeter::callbacks::wallpaper", "Registering wallpaper callback");
data.on_get_new_wallpaper({
let data_weak = data_weak.clone();
let display_weak = display_weak.clone();
move || {
debug!(target: "ckgreeter::callbacks::wallpaper", "----- wallpaper callback called");
let pexels_client = pexels_client.clone();
let reqwest_client = reqwest_client.clone();
let cache_dir = cache_dir.clone();
@ -302,66 +347,101 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
let display_weak = display_weak.clone();
let data_weak = data_weak.clone();
let get_wallpaper = {
let data_weak = data_weak.clone();
let display_weak = display_weak.clone();
async move || -> anyhow::Result<()> {
slint::spawn_local(async move {
trait ResultExt<T, E> {
fn wpaper_err(self, data: &GreeterData) -> Result<T, E>;
}
impl<T, E: Display> ResultExt<T, E> for Result<T, E> {
fn wpaper_err(self, data: &GreeterData) -> Result<T, E> {
self.inspect_err(|e| {
error!(target: "ckgreeter::callbacks::wallpaper", "Failed to get wallpaper: {e}");
data.set_wallpaper_author("Error".into());
data.set_wallpaper_alt(e.to_string().into());
data.set_wallpaper_url("Failed to get wallpaper".into());
})
}
}
let data = data_weak.unwrap();
let display = display_weak.unwrap();
let last_used_log = cache_dir.join("last_used_log.txt");
if !tokio::fs::try_exists(&last_used_log).await? {
File::create(&last_used_log).await?;
debug!(target: "ckgreeter::callbacks::wallpaper", "last used log: {:?}", last_used_log);
if !tokio::fs::try_exists(&last_used_log).await.wpaper_err(&data)? {
File::create(&last_used_log).await.wpaper_err(&data)?;
debug!(target: "ckgreeter::callbacks::wallpaper", "created last used log");
}
let mut file_lines = tokio::time::timeout(std::time::Duration::from_secs(5), read_lines(&last_used_log)).await??;
let mut file_lines = tokio::time::timeout(
std::time::Duration::from_secs(5),
read_lines(&last_used_log),
)
.await.wpaper_err(&data)?.wpaper_err(&data)?;
debug!(target: "ckgreeter::callbacks::wallpaper", "read last used log");
let mut idx = 0;
let mut last_used_lines = VecDeque::with_capacity(MAX_RECENT_WALLPAPERS);
while let Some(line) = file_lines.next_line().await? {
while let Some(line) = file_lines.next_line().await.wpaper_err(&data)? {
last_used_lines.push_back(line);
idx += 1;
if idx >= MAX_RECENT_WALLPAPERS {
break;
if idx > MAX_RECENT_WALLPAPERS {
last_used_lines.pop_front();
}
}
drop(file_lines);
let mut page = 0;
loop {
debug!(target: "ckgreeter::callbacks::wallpaper", "fetched photos list from pexels api");
let mut is_photo_set: bool = false;
let mut found_photo: bool = false;
match pexels_client
.search_photos(
pexels_query.as_str(),
&SearchParams::new()
.page(1)
.per_page(11)
.page(page)
.per_page(10)
.size(pexels_api::Size::Medium)
.orientation(pexels_api::Orientation::Landscape)
).await
.orientation(pexels_api::Orientation::Landscape),
)
.await
{
Ok(photos) => {
if photos.total_results > 0 {
found_photo = true;
debug!(target: "ckgreeter::callbacks::wallpaper", "got photos from pexels api");
for photo in photos.photos {
let string_id = photo.id.to_string();
trace!(target: "ckgreeter::callbacks::wallpaper", "pexels photo id: {}", string_id);
if last_used_lines.contains(&string_id) {
trace!(target: "ckgreeter::callbacks::wallpaper", "pexels photo id already used recently");
continue;
}
is_photo_set = true;
last_used_lines.push_back(string_id);
if last_used_lines.len() > MAX_RECENT_WALLPAPERS {
last_used_lines.pop_front();
}
let image_path =
cache_dir.join(format!("{}.png", photo.id));
let image_path = cache_dir.join(format!("{}.png", photo.id));
trace!(target: "ckgreeter::callbacks::wallpaper", "pexels photo image path: {:?}", image_path);
let _ = if !tokio::fs::try_exists(&image_path).await? {
let _ = if !tokio::fs::try_exists(&image_path).await.wpaper_err(&data)? {
debug!(target: "ckgreeter::callbacks::wallpaper", "pexels photo image not found, downloading");
let response = reqwest_client
.get(photo.src.large2x.clone()) // TODO: determine which photo src size is best, maybe determine based on the monitor size to reduce memory pressure
.send()
.await?;
let image_bytes = response.bytes().await?;
.await.wpaper_err(&data)?;
let image_bytes = response.bytes().await.wpaper_err(&data)?;
debug!(target: "ckgreeter::callbacks::wallpaper", "pexels photo image downloaded");
let size = display.window().size();
let (w, h, pixels) = tokio::task::spawn_blocking(move || {
debug!(target: "ckgreeter::callbacks::wallpaper", "resizing image with ImageMagick");
let (w, h, pixels) =
tokio::task::spawn_blocking(move || {
let wand = MagickWand::new();
wand.read_image_blob(&image_bytes)?;
@ -379,12 +459,16 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
);
}
trace!(target: "ckgreeter::callbacks::wallpaper", "exporting png data");
let png_data = wand.write_image_blob("png")?;
std::fs::write(image_path, png_data)?;
trace!(target: "ckgreeter::callbacks::wallpaper", "exported png data to disk");
export_wand_rgba(&wand)
}).await??;
})
.await.wpaper_err(&data)?.wpaper_err(&data)?;
debug!(target: "ckgreeter::callbacks::wallpaper", "image resized with ImageMagick");
set_background_image(
&display,
&data,
@ -392,24 +476,24 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
w,
h,
);
debug!(target: "ckgreeter::callbacks::wallpaper", "set background image");
drop(pixels);
data.set_wallpaper_author(photo.photographer.into());
data.set_wallpaper_alt(photo.alt.unwrap_or("".into()).into());
data.set_wallpaper_url(photo.url.into());
display.invoke_switch_background();
} else {
let contents = tokio::fs::read(image_path).await?;
debug!(target: "ckgreeter::callbacks::wallpaper", "pexels photo image found, using cached");
let contents = tokio::fs::read(image_path).await.wpaper_err(&data)?;
let (w, h, pixels) = tokio::task::spawn_blocking(move || {
debug!(target: "ckgreeter::callbacks::wallpaper", "resizing image with ImageMagick");
let (w, h, pixels) =
tokio::task::spawn_blocking(move || {
let wand = MagickWand::new();
wand.read_image_blob(contents.as_slice())?;
export_wand_rgba(&wand)
}).await??;
})
.await.wpaper_err(&data)?.wpaper_err(&data)?;
debug!(target: "ckgreeter::callbacks::wallpaper", "image resized with ImageMagick");
set_background_image(
&display,
&data,
@ -418,37 +502,47 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
h,
);
drop(pixels);
debug!(target: "ckgreeter::callbacks::wallpaper", "set background image");
drop(pixels);
};
debug!(target: "ckgreeter::callbacks::wallpaper", "set pexels photo author: {}", photo.photographer);
data.set_wallpaper_author(photo.photographer.into());
data.set_wallpaper_alt(photo.alt.unwrap_or("".into()).into());
let photo_alt = photo.alt.unwrap_or("".into());
debug!(target: "ckgreeter::callbacks::wallpaper", "set pexels photo alt: {}", photo_alt);
data.set_wallpaper_alt(
photo_alt.into(),
);
debug!(target: "ckgreeter::callbacks::wallpaper", "set pexels photo url: {}", photo.url);
data.set_wallpaper_url(photo.url.into());
debug!(target: "ckgreeter::callbacks::wallpaper", "switching background to foreground");
display.invoke_switch_background();
};
break;
}
tokio::time::timeout(
std::time::Duration::from_secs(5),
write_lines(&last_used_log, last_used_lines),
)
.await
.context("timed out while writing wallpaper log")??;
}
}
Err(err) => {
eprintln!("Failed to fetch online images, using cached. {err}");
let mut photos = ReadDirStream::new(tokio::fs::read_dir(cache_dir).await?).filter(|de| match de {
error!("Failed to fetch online images, using cached. {err}");
debug!(target: "ckgreeter::callbacks::wallpaper", "getting cached wallpaper images from disk");
let mut photos =
ReadDirStream::new(tokio::fs::read_dir(&cache_dir).await.wpaper_err(&data)?)
.filter(|de| match de {
Ok(de) => {
de.path().is_file()
&& de.path().extension()
== Some(std::ffi::OsStr::new("png"))
}
Err(_) => false,
}).collect::<Vec<_>>().await;
})
.collect::<Vec<_>>()
.await;
debug!(target: "ckgreeter::callbacks::wallpaper", "got cached wallpaper images from disk");
photos.shuffle(&mut rand::rng());
debug!(target: "ckgreeter::callbacks::wallpaper", "shuffled cached wallpaper images");
let mut photos = tokio_stream::iter(photos);
@ -456,9 +550,16 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
match photo {
Ok(photo) => {
let photo_path = photo.path();
let string_id = photo_path.file_prefix().context("failed to get filename")?.to_string_lossy().to_string();
let string_id = photo_path
.file_prefix()
.context("failed to get filename").wpaper_err(&data)?
.to_string_lossy()
.to_string();
if last_used_lines.front().is_some_and(|last| last == &string_id) {
if last_used_lines
.front()
.is_some_and(|first| first == &string_id)
{
continue;
}
@ -467,12 +568,21 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
last_used_lines.pop_front();
}
let (w, h, pixels) = tokio::task::spawn_blocking(move || {
debug!(target: "ckgreeter::callbacks::wallpaper", "using cached wallpaper image: {:?}", photo_path);
debug!(target: "ckgreeter::callbacks::wallpaper", "resizing image with ImageMagick");
let (w, h, pixels) =
tokio::task::spawn_blocking(move || {
let wand = MagickWand::new();
wand.read_image(photo_path.to_str().context("failed to convert image path to UTF-8")?)?;
wand.read_image(photo_path.to_str().context(
"failed to convert image path to UTF-8",
)?)?;
export_wand_rgba(&wand)
}).await??;
})
.await.wpaper_err(&data)?.wpaper_err(&data)?;
debug!(target: "ckgreeter::callbacks::wallpaper", "image resized with ImageMagick");
set_background_image(
&display,
@ -481,16 +591,19 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
w,
h,
);
debug!(target: "ckgreeter::callbacks::wallpaper", "set background image");
drop(pixels);
data.set_wallpaper_author("Unknown".into());
data.set_wallpaper_alt("".into());
data.set_wallpaper_url("Cached wallpaper".into());
debug!(target: "ckgreeter::callbacks::wallpaper", "switching background to foreground");
display.invoke_switch_background();
break;
}
Err(_err) => {
@ -500,64 +613,36 @@ pub fn register_wallpaper_callback(data: &GreeterData, display_weak: Weak<Greete
}
}
}
Ok(())
}
};
match slint::spawn_local({
let data_weak = data_weak.clone();
async move {
match get_wallpaper().await {
Ok(_) => {}
Err(e) => {
let data = data_weak.unwrap();
report_wallpaper_error(&data, e.to_string());
}
};
}
}) {
Ok(_) => {}
Err(e) => {
let data = data_weak.unwrap();
report_wallpaper_error(&data, e.to_string());
if is_photo_set || !found_photo {
break;
}
page += 1;
}
tokio::time::timeout(
std::time::Duration::from_secs(5),
write_lines(&last_used_log, last_used_lines),
)
.await
.context("timed out while writing wallpaper log").wpaper_err(&data)?.wpaper_err(&data)?;
return anyhow::Ok(());
}).unwrap();
}
});
fn report_wallpaper_error(data: &GreeterData, error: String) {
data.set_wallpaper_author("Error".into());
data.set_wallpaper_alt(error.into());
data.set_wallpaper_url("Failed to get wallpaper".into());
}
}
}
pub fn register_clean_inactive_wallpaper_callback(data: &GreeterData, display_weak: Weak<GreeterDisplay>, data_weak: Weak<GreeterData<'static>>) {
data.on_clear_inactive_wallpaper({
let display_weak = display_weak.clone();
let data_weak = data_weak.clone();
move || {
let display = display_weak.unwrap();
let data = data_weak.unwrap();
if !display.get_background_toggle() {
let old = data.get_wallpaper_image();
data.set_wallpaper_image(Image::default());
drop(old);
} else {
let old = data.get_wallpaper_image_1();
data.set_wallpaper_image_1(Image::default());
drop(old);
}
}
})
}
pub fn register_get_default_font_size(data: &GreeterData, display_weak: Weak<GreeterDisplay>) {
data.on_get_default_font_size(move || {
debug!(target: "ckgreeter::callbacks::font_size", "----- get default font size callback called");
let display = display_weak.unwrap();
let size = display.window().size();
((size.width as f32).min(size.height as f32)) * 0.01
trace!(target: "ckgreeter::callbacks::font_size", "window size: {:?}", size);
let font_size = ((size.width as f32).min(size.height as f32)) * 0.01;
trace!(target: "ckgreeter::callbacks::font_size", "font size: {}", font_size);
font_size
});
}
@ -571,10 +656,21 @@ pub fn register_callbacks(
let display_weak = display.as_weak();
let data_weak = data.as_weak();
debug!(target: "ckgreeter::callbacks", "Registering callbacks");
register_login_callback(data, display_weak.clone(), tiles);
debug!(target: "ckgreeter::callbacks", "Registered login callback");
register_power_callback(data);
debug!(target: "ckgreeter::callbacks", "Registered power callback");
register_lock_state_callback(data, data_weak.clone());
register_wallpaper_callback(data, display_weak.clone(), data_weak.clone(), cache_dir, config);
register_clean_inactive_wallpaper_callback(data, display_weak.clone(), data_weak.clone());
debug!(target: "ckgreeter::callbacks", "Registered lock state callback");
register_wallpaper_callback(
data,
display_weak.clone(),
data_weak.clone(),
cache_dir,
config,
);
debug!(target: "ckgreeter::callbacks", "Registered clean inactive wallpaper callback");
register_get_default_font_size(data, display_weak.clone());
debug!(target: "ckgreeter::callbacks", "Registered get default font size callback");
}

View File

@ -2,11 +2,12 @@ use std::collections::HashMap;
use std::path::PathBuf;
use anyhow::{anyhow, Context};
use i_slint_core::api::{Image, ToSharedString};
use log::{debug, trace};
use serde::Deserialize;
use serde_aux::prelude::*;
use crate::LoginOptionTileData;
#[derive(Deserialize)]
#[derive(Deserialize, Debug)]
pub struct LoginOptionTileConfig {
pub(crate) image_path: PathBuf,
pub(crate) command: String,
@ -26,6 +27,7 @@ pub fn default_cache_dir() -> PathBuf {
#[derive(Deserialize)]
pub struct Config {
pub(crate) default_username: Option<String>,
#[serde(default)]
pub(crate) environments: HashMap<String, LoginOptionTileConfig>,
pub(crate) pexels_api_key: Option<String>,
pub(crate) pexels_query: Option<String>,
@ -37,11 +39,13 @@ pub struct Config {
impl Config {
pub fn get_tiles(&self) -> anyhow::Result<(Vec<LoginOptionTileData>, Option<usize>)> {
debug!(target: "ckgreeter::config_get_tiles", "Getting tiles from config");
let mut ordered = self
.environments
.iter()
.collect::<Vec<(&String, &LoginOptionTileConfig)>>();
debug!(target: "ckgreeter::config_get_tiles", "Sorting tiles");
ordered.sort_by_key(|(name, env)| {
let index = if env.index >= 0 { env.index } else { i32::MAX };
(index, name.as_str())
@ -53,6 +57,8 @@ impl Config {
let mut seen_indexes = std::collections::HashSet::new();
for (name, tile_config) in ordered {
debug!(target: "ckgreeter::config_get_tiles", "Adding tile `{}`", name);
trace!(target: "ckgreeter::config_get_tiles", "Tile config: {:#?}", tile_config);
if tile_config.index >= 0 && !seen_indexes.insert(tile_config.index) {
return Err(anyhow!(
"Duplicate login option index `{}` found near `{}`",
@ -64,6 +70,7 @@ impl Config {
if default.is_some() {
return Err(anyhow!("There can only be one default login option..."));
}
debug!(target: "ckgreeter::config_get_tiles", "Setting default tile to `{}`", name);
default = Some(tiles.len());
}
tiles.push(LoginOptionTileData {

View File

@ -18,46 +18,82 @@ slint::include_modules!();
static START: Once = Once::new();
use log::{debug, error, info};
use pretty_env_logger::env_logger::Target;
#[cfg(debug_assertions)]
fn start_debug_greetd_stub() {
use libgreetd_stub::SessionOptions;
let opts = SessionOptions {
username: std::env::var("CKGREETER_STUB_USER")
.unwrap_or_else(|_| "debug-user".to_string()),
username: {
std::env::var("CKGREETER_STUB_USER")
.unwrap_or_else(|_| "debug-user".to_string())
},
password: std::env::var("CKGREETER_STUB_PASSWORD")
.unwrap_or_else(|_| "debug-password".to_string()),
mfa: false,
};
debug!(target: "ckgreeter::init_greetd_stub", "Using username: {}", opts.username);
debug!(target: "ckgreeter::init_greetd_stub", "Using password: {}", opts.password);
debug!(target: "ckgreeter::init_greetd_stub", "Using MFA: {}", opts.mfa);
let socket_path = std::env::var("CKGREETER_STUB_SOCKET")
.unwrap_or_else(|_| "/tmp/greetd-stub.sock".to_string());
debug!(target: "ckgreeter::init_greetd_stub", "Using socket path: {}", socket_path);
let stub_socket_path = socket_path.clone();
let _stub_task = tokio::task::spawn(async move {
libgreetd_stub::start(&stub_socket_path, &opts).await
});
debug!(target: "ckgreeter::init_greetd_stub", "Stub started");
debug!(target: "ckgreeter::init_greetd_stub", "GREETD_SOCK set to {}", socket_path);
unsafe {std::env::set_var("GREETD_SOCK", socket_path)};
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
#[cfg(debug_assertions)]
pretty_env_logger::formatted_timed_builder()
.target(Target::Stdout)
.parse_filters("ckgreeter=debug")
.init();
#[cfg(not(debug_assertions))]
pretty_env_logger::formatted_timed_builder()
.target(Target::Stdout)
.parse_filters("ckgreeter=info")
.init();
info!(target: "ckgreeter::init", "Initializing...");
START.call_once(|| {
magick_wand_genesis();
});
info!(target: "ckgreeter::init", "ImageMagick initialized");
#[cfg(debug_assertions)]
{
start_debug_greetd_stub();
info!(target: "ckgreeter::init", "Debug greetd stub started");
}
const CONFIG_PATH: &str = "/etc/ckgreeter/config.toml";
let config_data = std::fs::read(CONFIG_PATH)
.with_context(|| format!("Failed to read config file at {}", CONFIG_PATH))?;
let config_path = std::env::var("CKGREETER_CONFIG").unwrap_or_else(|_| CONFIG_PATH.to_string());
info!(target: "ckgreeter::init", "Loading config from {}", config_path);
let config_data = std::fs::read(&config_path)
.with_context(|| format!("Failed to read config file at {}", config_path))?;
info!(target: "ckgreeter::init", "Config loaded");
let config: Config = toml::from_slice(&config_data)
.with_context(|| format!("Failed to parse config file at {}", CONFIG_PATH))?;
.with_context(|| format!("Failed to parse config file at {}", config_path))?;
debug!(target: "ckgreeter::init", "Config parsed");
let cache_dir = &config.background_cache_directory;
info!(target: "ckgreeter::init", "Using cache directory: {}", cache_dir.display());
std::fs::create_dir_all(cache_dir)
.with_context(|| format!("Failed to create cache directory: {}", cache_dir.display()))?;
@ -66,14 +102,25 @@ async fn main() -> anyhow::Result<()> {
let (tiles, default_tile) = config.get_tiles()?;
if tiles.len() == 0 {
error!(target: "ckgreeter::init", "No tiles found in config");
return Err(anyhow::anyhow!("No tiles found in config"));
}
debug!(target: "ckgreeter::init", "Got tiles from config");
if let Some(default_tile) = default_tile {
debug!(target: "ckgreeter::init", "Default tile: '{}', index: {}", tiles[default_tile].name, default_tile);
data.set_selected_index(default_tile as i32);
}
let tiles = Rc::new(VecModel::from(tiles));
info!(target: "ckgreeter::init", "Registering callbacks and timers...");
register_callbacks(&display, &data, tiles.clone(), cache_dir.into(), &config);
info!(target: "ckgreeter::init", "Callbacks registered");
let _timers = register_timers(&display, &data, &config);
info!(target: "ckgreeter::init", "Timers registered");
match config.default_username {
Some(username) => {
@ -85,12 +132,20 @@ async fn main() -> anyhow::Result<()> {
}
}
info!(target: "ckgreeter::init", "Setting up display...");
display.set_tiles(ModelRc::from(tiles));
debug!(target: "ckgreeter::init", "Tiles set");
data.set_wallpaper_cooldown(config.background_delay.max(5));
debug!(target: "ckgreeter::init", "Wallpaper cooldown set");
data.invoke_get_new_wallpaper();
debug!(target: "ckgreeter::init", "Wallpaper fetch invoked");
data.invoke_check_lock_states();
debug!(target: "ckgreeter::init", "Lock state check invoked");
info!(target: "ckgreeter::init", "Display setup complete");
display.show()?;
tokio::task::block_in_place(slint::run_event_loop)?;
display.hide()?;

View File

@ -1,5 +1,6 @@
use chrono::Datelike;
use i_slint_core::api::Global;
use log::debug;
use crate::{GreeterData, GreeterDisplay};
use crate::config::Config;
@ -8,6 +9,7 @@ pub fn register_timers(
data: &GreeterData,
_config: &Config,
) -> slint::Timer {
debug!(target: "ckgreeter::timers", "Registering timers...");
let data_weak = data.as_weak();
let timer = slint::Timer::default();
@ -28,5 +30,7 @@ pub fn register_timers(
},
);
debug!(target: "ckgreeter::timers", "Clock timer started");
timer
}

View File

@ -12,7 +12,6 @@ export global Data {
callback check-lock-states();
callback get-new-wallpaper();
callback clear-inactive-wallpaper();
in property <string> wallpaper-url;

View File

@ -45,9 +45,7 @@ export component GreeterDisplay inherits Window {
public function switch_background() {
background-toggle = !background-toggle;
background_timer.start();
clear_inactive_wallpaper_timer.start();
background_timer.restart();
clear_inactive_wallpaper_timer.restart();
}
full-screen: true;
@ -85,15 +83,6 @@ export component GreeterDisplay inherits Window {
self.running = false;
}
}
clear_inactive_wallpaper_timer := Timer {
interval: 1200ms;
running: false;
triggered() => {
Data.clear-inactive-wallpaper();
self.running = false;
}
}
}
background_1 := Image { // Background (secondary), needed for smooth opacity transitions
@ -307,6 +296,7 @@ export component GreeterDisplay inherits Window {
has_focus: environment_selector.has-focus;
on_click => {
Data.selected_index = self.index;
environment_selector.focus()
}
}
}