Combine some logic between greeter and locker

This commit is contained in:
Jeremy Soller 2025-05-09 16:10:03 -06:00
parent c9913834f2
commit 8812240f50
4 changed files with 418 additions and 384 deletions

View file

@ -10,10 +10,7 @@ use cosmic::{
Element, executor,
iced::{
self, Length, Subscription, alignment,
event::{
self,
wayland::{Event as WaylandEvent, OutputEvent, SessionLockEvent},
},
event::wayland::{OutputEvent, SessionLockEvent},
futures::{self, SinkExt},
platform_specific::shell::wayland::commands::session_lock::{
destroy_lock_surface, get_lock_surface, lock, unlock,
@ -23,11 +20,10 @@ use cosmic::{
widget,
};
use cosmic_config::CosmicConfigEntry;
use cosmic_greeter_daemon::{BgSource, TimeAppletConfig, UserData};
use cosmic_greeter_daemon::{TimeAppletConfig, UserData};
use std::time::Duration;
use std::{
any::TypeId,
collections::HashMap,
env,
ffi::{CStr, CString},
fs,
@ -39,6 +35,8 @@ use std::{
use tokio::{sync::mpsc, task};
use wayland_client::{Proxy, protocol::wl_output::WlOutput};
use crate::common::{self, Common};
fn lockfile_opt() -> Option<PathBuf> {
let runtime_dir = dirs::runtime_dir()?;
let session_id = env::var("XDG_SESSION_ID").ok()?;
@ -191,26 +189,28 @@ pub struct Flags {
#[derive(Clone, Debug)]
pub enum Message {
None,
Common(common::Message),
OutputEvent(OutputEvent, WlOutput),
SessionLockEvent(SessionLockEvent),
Channel(mpsc::Sender<String>),
BackgroundState(cosmic_bg_config::state::State),
TimeAppletConfig(TimeAppletConfig),
Focus(SurfaceId),
Inhibit(Arc<OwnedFd>),
NetworkIcon(Option<&'static str>),
PowerInfo(Option<(String, f64)>),
Prompt(String, bool, Option<String>),
Submit(String),
Surface(surface::Action),
Suspend,
Error(String),
Lock,
Tick,
Tz(chrono_tz::Tz),
Unlock,
}
impl From<common::Message> for Message {
fn from(message: common::Message) -> Self {
Self::Common(message)
}
}
#[derive(Clone, Debug)]
enum State {
Locking,
@ -233,29 +233,17 @@ impl Drop for State {
/// The [`App`] stores application-specific state.
pub struct App {
core: Core,
common: Common<Message>,
flags: Flags,
state: State,
output_names: HashMap<WlOutput, String>,
surface_ids: HashMap<WlOutput, SurfaceId>,
subsurface_rects: HashMap<WlOutput, Rectangle>,
active_surface_id_opt: Option<SurfaceId>,
surface_images: HashMap<SurfaceId, widget::image::Handle>,
surface_names: HashMap<SurfaceId, String>,
text_input_ids: HashMap<String, widget::Id>,
inhibit_opt: Option<Arc<OwnedFd>>,
network_icon_opt: Option<&'static str>,
power_info_opt: Option<(String, f64)>,
value_tx_opt: Option<mpsc::Sender<String>>,
prompt_opt: Option<(String, bool, Option<String>)>,
error_opt: Option<String>,
time: crate::time::Time,
window_size: HashMap<SurfaceId, Size>,
}
impl App {
fn menu(&self, surface_id: SurfaceId) -> Element<Message> {
let window_width = self
.common
.window_size
.get(&surface_id)
.map(|s| s.width)
@ -267,15 +255,15 @@ impl App {
};
let left_element = {
let military_time = self.flags.user_data.time_applet_config.military_time;
let date_time_column = self.time.date_time_widget(military_time);
let date_time_column = self.common.time.date_time_widget(military_time);
let mut status_row = widget::row::with_capacity(2).padding(16.0).spacing(12.0);
if let Some(network_icon) = self.network_icon_opt {
if let Some(network_icon) = self.common.network_icon_opt {
status_row = status_row.push(widget::icon::from_name(network_icon));
}
if let Some((power_icon, power_percent)) = &self.power_info_opt {
if let Some((power_icon, power_percent)) = &self.common.power_info_opt {
status_row = status_row.push(iced::widget::row![
widget::icon::from_name(power_icon.clone()),
widget::text(format!("{:.0}%", power_percent)),
@ -339,19 +327,20 @@ impl App {
.align_x(alignment::Horizontal::Center),
);
match &self.prompt_opt {
match &self.common.prompt_opt {
Some((prompt, secret, value_opt)) => match value_opt {
Some(value) => {
let text_input_id = self
.common
.surface_names
.get(&surface_id)
.and_then(|id| self.text_input_ids.get(id))
.and_then(|id| self.common.text_input_ids.get(id))
.cloned()
.unwrap_or_else(|| cosmic::widget::Id::new("text_input"));
let mut text_input = widget::secure_input(
prompt.clone(),
"",
&self.common.input,
Some(Message::Prompt(
prompt.clone(),
!*secret,
@ -360,7 +349,7 @@ impl App {
*secret,
)
.id(text_input_id)
.manage_value(true)
.on_input(|input| common::Message::Input(input).into())
.on_submit(Message::Submit);
if *secret {
@ -376,7 +365,7 @@ impl App {
None => {}
}
if let Some(error) = &self.error_opt {
if let Some(error) = &self.common.error_opt {
column = column.push(widget::text(error));
}
@ -414,51 +403,6 @@ impl App {
.class(cosmic::theme::Container::Transparent)
.into()
}
//TODO: cache wallpapers by source?
fn update_wallpapers(&mut self) {
let user_data = &self.flags.user_data;
for (_output, surface_id) in self.surface_ids.iter() {
if self.surface_images.contains_key(surface_id) {
continue;
}
let Some(output_name) = self.surface_names.get(surface_id) else {
continue;
};
log::info!("updating wallpaper for {:?}", output_name);
for (wallpaper_output_name, wallpaper_source) in user_data.bg_state.wallpapers.iter() {
if wallpaper_output_name == output_name {
match wallpaper_source {
BgSource::Path(path) => {
match user_data.bg_path_data.get(path) {
Some(bytes) => {
let image = widget::image::Handle::from_bytes(bytes.clone());
self.surface_images.insert(*surface_id, image);
//TODO: what to do about duplicates?
}
None => {
log::warn!(
"output {}: failed to find wallpaper data for source {:?}",
output_name,
path
);
}
}
break;
}
BgSource::Color(color) => {
//TODO: support color sources
log::warn!("output {}: unsupported source {:?}", output_name, color);
}
}
}
}
}
}
}
/// Implement [`cosmic::Application`] to integrate with COSMIC.
@ -476,22 +420,20 @@ impl cosmic::Application for App {
const APP_ID: &'static str = "com.system76.CosmicGreeter";
fn core(&self) -> &Core {
&self.core
&self.common.core
}
fn core_mut(&mut self) -> &mut Core {
&mut self.core
&mut self.common.core
}
/// Creates the application, and optionally emits command on initialize.
fn init(mut core: Core, flags: Self::Flags) -> (Self, Task<Self::Message>) {
core.window.show_window_menu = false;
core.window.show_headerbar = false;
// XXX must be false or define custom style to have transparent bg
core.window.sharp_corners = false;
core.window.show_maximize = false;
core.window.show_minimize = false;
core.window.use_template = false;
fn init(core: Core, flags: Self::Flags) -> (Self, Task<Self::Message>) {
let (mut common, common_task) = Common::init(core);
common.on_output_event = Some(Box::new(|output_event, output| {
Message::OutputEvent(output_event, output)
}));
common.on_session_lock_event = Some(Box::new(|evt| Message::SessionLockEvent(evt)));
let already_locked = match flags.lockfile_opt {
Some(ref lockfile) => lockfile.exists(),
@ -499,24 +441,11 @@ impl cosmic::Application for App {
};
let mut app = App {
core,
common,
flags,
state: State::Unlocked,
surface_ids: HashMap::new(),
active_surface_id_opt: None,
output_names: HashMap::new(),
surface_images: HashMap::new(),
surface_names: HashMap::new(),
text_input_ids: HashMap::new(),
subsurface_rects: HashMap::new(),
inhibit_opt: None,
network_icon_opt: None,
power_info_opt: None,
value_tx_opt: None,
prompt_opt: None,
error_opt: None,
time: crate::time::Time::new(),
window_size: HashMap::new(),
};
let task = if cfg!(feature = "logind") {
@ -536,20 +465,16 @@ impl cosmic::Application for App {
lock()
};
(
app,
Task::batch(vec![
task,
crate::time::tick().map(|_| cosmic::Action::App(Message::Tick)),
crate::time::tz_updates().map(|tz| cosmic::Action::App(Message::Tz(tz))),
]),
)
(app, Task::batch([task, common_task]))
}
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message {
Message::None => {}
Message::Common(common_message) => {
return self.common.update(common_message);
}
Message::OutputEvent(output_event, output) => {
match output_event {
OutputEvent::Created(output_info_opt) => {
@ -559,7 +484,7 @@ impl cosmic::Application for App {
let subsurface_id = SurfaceId::unique();
if let Some(old_surface_id) =
self.surface_ids.insert(output.clone(), surface_id)
self.common.surface_ids.insert(output.clone(), surface_id)
{
//TODO: remove old surface?
log::warn!(
@ -580,16 +505,21 @@ impl cosmic::Application for App {
match output_info_opt {
Some(output_info) => match output_info.name {
Some(output_name) => {
self.output_names
self.common
.output_names
.insert(output.clone(), output_name.clone());
self.surface_names.insert(surface_id, output_name.clone());
self.surface_names
self.common
.surface_names
.insert(surface_id, output_name.clone());
self.common
.surface_names
.insert(subsurface_id, output_name.clone());
self.surface_images.remove(&surface_id);
self.update_wallpapers();
self.common.surface_images.remove(&surface_id);
self.common.update_wallpapers(&self.flags.user_data);
let text_input_id =
widget::Id::new(format!("input-{output_name}",));
self.text_input_ids
self.common
.text_input_ids
.insert(output_name.clone(), text_input_id.clone());
}
None => {
@ -615,12 +545,13 @@ impl cosmic::Application for App {
Size::new(unwrapped_size.0 as f32, unwrapped_size.1 as f32 - 32.),
)
};
self.window_size.insert(
self.common.window_size.insert(
surface_id,
Size::new(unwrapped_size.0 as f32, unwrapped_size.1 as f32),
);
self.subsurface_rects
self.common
.subsurface_rects
.insert(output.clone(), Rectangle::new(loc, sub_size));
let msg = cosmic::surface::action::subsurface(
@ -651,13 +582,13 @@ impl cosmic::Application for App {
}
OutputEvent::Removed => {
log::info!("output {}: removed", output.id());
match self.surface_ids.remove(&output) {
match self.common.surface_ids.remove(&output) {
Some(surface_id) => {
self.surface_images.remove(&surface_id);
self.surface_names.remove(&surface_id);
self.window_size.remove(&surface_id);
if let Some(n) = self.surface_names.remove(&surface_id) {
self.text_input_ids.remove(&n);
self.common.surface_images.remove(&surface_id);
self.common.surface_names.remove(&surface_id);
self.common.window_size.remove(&surface_id);
if let Some(n) = self.common.surface_names.remove(&surface_id) {
self.common.text_input_ids.remove(&n);
}
if matches!(self.state, State::Locked { .. }) {
return destroy_lock_surface(surface_id);
@ -685,7 +616,8 @@ impl cosmic::Application for App {
} else {
(Point::ORIGIN, Size::new(1920., 1080.))
};
self.subsurface_rects
self.common
.subsurface_rects
.insert(output.clone(), Rectangle::new(loc, sub_size));
log::info!("output {}: info update", output.id());
@ -768,7 +700,7 @@ impl cosmic::Application for App {
)
.abortable();
let mut commands = Vec::with_capacity(self.surface_ids.len() + 1);
let mut commands = Vec::with_capacity(self.common.surface_ids.len() + 1);
commands.push(locked_task);
self.state = State::Locked {
@ -779,19 +711,22 @@ impl cosmic::Application for App {
self.inhibit_opt = None;
// Create lock surfaces
for (output, surface_id) in self.surface_ids.iter() {
for (output, surface_id) in self.common.surface_ids.iter() {
commands.push(get_lock_surface(*surface_id, output.clone()));
if let Some((rect, name)) = self
.common
.subsurface_rects
.get(output)
.copied()
.zip(self.output_names.get(output))
.zip(self.common.output_names.get(output))
{
let subsurface_id = SurfaceId::unique();
let surface_id = *surface_id;
self.surface_names.insert(surface_id, name.clone());
self.surface_names.insert(subsurface_id, name.clone());
self.common.surface_names.insert(surface_id, name.clone());
self.common
.surface_names
.insert(subsurface_id, name.clone());
let msg = cosmic::surface::action::subsurface(
move |_: &mut App| SctkSubsurfaceSettings {
parent: surface_id,
@ -822,9 +757,9 @@ impl cosmic::Application for App {
self.state = State::Unlocked;
let mut commands = Vec::new();
for (_output, surface_id) in self.surface_ids.iter() {
self.surface_names.remove(surface_id);
self.window_size.remove(surface_id);
for (_output, surface_id) in self.common.surface_ids.iter() {
self.common.surface_names.remove(surface_id);
self.common.window_size.remove(surface_id);
commands.push(destroy_lock_surface(*surface_id));
}
if cfg!(feature = "logind") {
@ -843,11 +778,10 @@ impl cosmic::Application for App {
self.value_tx_opt = Some(value_tx);
}
Message::BackgroundState(bg_state) => {
eprintln!("{:#?}", bg_state);
self.flags.user_data.bg_state = bg_state;
self.flags.user_data.load_wallpapers_as_user();
self.surface_images.clear();
self.update_wallpapers();
self.common.surface_images.clear();
self.common.update_wallpapers(&self.flags.user_data);
}
Message::TimeAppletConfig(config) => {
self.flags.user_data.time_applet_config = config;
@ -855,51 +789,37 @@ impl cosmic::Application for App {
Message::Inhibit(inhibit) => {
self.inhibit_opt = Some(inhibit);
}
Message::NetworkIcon(network_icon_opt) => {
self.network_icon_opt = network_icon_opt;
}
Message::PowerInfo(power_info_opt) => {
self.power_info_opt = power_info_opt;
}
Message::Focus(surface_id) => {
self.active_surface_id_opt = Some(surface_id);
self.active_surface_id_opt = Some(surface_id);
if let Some(text_input_id) = self
.surface_names
.get(&surface_id)
.and_then(|id| self.text_input_ids.get(id))
{
return widget::text_input::focus(text_input_id.clone());
}
}
Message::Prompt(prompt, secret, value_opt) => {
let prompt_was_none = self.prompt_opt.is_none();
self.prompt_opt = Some((prompt, secret, value_opt));
let prompt_was_none = self.common.prompt_opt.is_none();
self.common.prompt_opt = Some((prompt, secret, value_opt));
if prompt_was_none {
if let Some(surface_id) = self.active_surface_id_opt {
if let Some(surface_id) = self.common.active_surface_id_opt {
if let Some(text_input_id) = self
.common
.surface_names
.get(&surface_id)
.and_then(|id| self.text_input_ids.get(id))
.and_then(|id| self.common.text_input_ids.get(id))
{
log::error!("focus surface found id {:?}", text_input_id);
log::info!("focus surface found id {:?}", text_input_id);
return widget::text_input::focus(text_input_id.clone());
}
}
}
}
Message::Submit(value) => match self.value_tx_opt.take() {
Some(value_tx) => {
// Clear errors
self.error_opt = None;
return cosmic::task::future(async move {
value_tx.send(value).await.unwrap();
Message::Channel(value_tx)
});
Message::Submit(value) => {
self.common.input.clear();
match self.value_tx_opt.take() {
Some(value_tx) => {
// Clear errors
self.common.error_opt = None;
return cosmic::task::future(async move {
value_tx.send(value).await.unwrap();
Message::Channel(value_tx)
});
}
None => log::warn!("tried to submit when value_tx_opt not set"),
}
None => log::warn!("tried to submit when value_tx_opt not set"),
},
}
Message::Suspend => {
#[cfg(feature = "logind")]
return cosmic::Task::future(async move { crate::logind::suspend().await.err() })
@ -909,14 +829,14 @@ impl cosmic::Application for App {
});
}
Message::Error(error) => {
self.error_opt = Some(error);
self.common.error_opt = Some(error);
}
Message::Lock => match self.state {
State::Unlocked => {
log::info!("session locking");
self.state = State::Locking;
// Clear errors
self.error_opt = None;
self.common.error_opt = None;
// Clear value_tx
self.value_tx_opt = None;
// Try to create lockfile when locking
@ -941,7 +861,7 @@ impl cosmic::Application for App {
log::info!("sessing unlocking");
self.state = State::Unlocking;
// Clear errors
self.error_opt = None;
self.common.error_opt = None;
// Clear value_tx
self.value_tx_opt = None;
// Try to delete lockfile when unlocking
@ -952,11 +872,11 @@ impl cosmic::Application for App {
}
// Destroy lock surfaces
let mut commands = Vec::with_capacity(self.surface_ids.len() + 1);
let mut commands = Vec::with_capacity(self.common.surface_ids.len() + 1);
for (_output, surface_id) in self.surface_ids.iter() {
self.surface_names.remove(surface_id);
self.window_size.remove(&surface_id);
for (_output, surface_id) in self.common.surface_ids.iter() {
self.common.surface_names.remove(surface_id);
self.common.window_size.remove(&surface_id);
commands.push(destroy_lock_surface(*surface_id));
}
@ -979,12 +899,6 @@ impl cosmic::Application for App {
cosmic::app::Action::Surface(a),
));
}
Message::Tick => {
self.time.tick();
}
Message::Tz(tz) => {
self.time.set_tz(tz);
}
}
Task::none()
}
@ -997,6 +911,7 @@ impl cosmic::Application for App {
/// Creates a view after each update.
fn view_window(&self, surface_id: SurfaceId) -> Element<Self::Message> {
let img = self
.common
.surface_images
.get(&surface_id)
.unwrap_or(&self.flags.fallback_background);
@ -1010,19 +925,7 @@ impl cosmic::Application for App {
fn subscription(&self) -> Subscription<Self::Message> {
let mut subscriptions = Vec::with_capacity(7);
subscriptions.push(event::listen_with(|event, _, id| match event {
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(
wayland_event,
)) => match wayland_event {
WaylandEvent::Output(output_event, output) => {
Some(Message::OutputEvent(output_event, output))
}
WaylandEvent::SessionLock(evt) => Some(Message::SessionLockEvent(evt)),
_ => None,
},
iced::Event::Window(iced::window::Event::Focused) => Some(Message::Focus(id)),
_ => None,
}));
subscriptions.push(self.common.subscription().map(Message::from));
struct BackgroundSubscription;
subscriptions.push(
@ -1059,16 +962,6 @@ impl cosmic::Application for App {
subscriptions.push(crate::logind::subscription());
}
#[cfg(feature = "networkmanager")]
{
subscriptions.push(crate::networkmanager::subscription().map(Message::NetworkIcon));
}
#[cfg(feature = "upower")]
{
subscriptions.push(crate::upower::subscription().map(Message::PowerInfo));
}
Subscription::batch(subscriptions)
}
}