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:
parent
48c493c4f5
commit
4f2902a847
@ -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"] }
|
||||
|
||||
|
||||
400
src/callbacks.rs
400
src/callbacks.rs
@ -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");
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
65
src/main.rs
65
src/main.rs
@ -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()?;
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -12,7 +12,6 @@ export global Data {
|
||||
callback check-lock-states();
|
||||
|
||||
callback get-new-wallpaper();
|
||||
callback clear-inactive-wallpaper();
|
||||
|
||||
in property <string> wallpaper-url;
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user