use glib::Object;
use greetd_ipc::AuthMessageType::Secret;
use greetd_ipc::ErrorType::AuthError;
use greetd_ipc::{Request, Response};
use gtk::glib;
use crate::TEXT_DOMAIN;
static G_LOG_DOMAIN: &str = "phrog-lockscreen";
glib::wrapper! {
pub struct Lockscreen(ObjectSubclass<imp::Lockscreen>)
@extends libphosh::Lockscreen, gtk::Widget, gtk::Window, gtk::Bin;
}
impl Lockscreen {
pub fn new() -> Self {
Object::builder().build()
}
}
impl Default for Lockscreen {
fn default() -> Self {
Self::new()
}
}
mod imp {
use super::{tr, G_LOG_DOMAIN};
use crate::lockscreen::fake_greetd_interaction;
use crate::shell::Shell;
use crate::user_session_page::UserSessionPage;
use crate::APP_ID;
use anyhow::{anyhow, Context};
use async_channel::{Receiver, Sender};
use glib::{error, g_message, info, warn};
use greetd_ipc::codec::SyncCodec;
use greetd_ipc::{AuthMessageType, ErrorType, Request, Response};
use gtk::gio::Settings;
use gtk::glib::{clone, closure_local, timeout_add_once, ObjectExt, Properties};
use gtk::prelude::SettingsExtManual;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{gio, glib};
use libphosh::prelude::*;
use libphosh::subclass::lockscreen::LockscreenImpl;
use libphosh::LockscreenPage;
use std::cell::{OnceCell, RefCell};
use std::os::unix::net::UnixStream;
use std::time::Duration;
const QUIT_DELAY: u64 = 500;
#[derive(Default, Properties)]
#[properties(wrapper_type = super::Lockscreen)]
pub struct Lockscreen {
#[property(get, set)]
pub user_session_page: OnceCell<UserSessionPage>,
greetd: RefCell<Option<(Sender<Request>, Receiver<Response>)>>,
session: RefCell<Option<String>>,
}
#[glib::object_subclass]
impl ObjectSubclass for Lockscreen {
const NAME: &'static str = "PhrogLockscreen";
type Type = super::Lockscreen;
type ParentType = libphosh::Lockscreen;
}
fn run_greetd() -> (Sender<Request>, Receiver<Response>) {
let (greetd_req_send, greetd_req_recv) = async_channel::bounded::<Request>(1);
let (greetd_resp_send, greetd_resp_recv) = async_channel::bounded(1);
gio::spawn_blocking(move || {
let mut sock = std::env::var("GREETD_SOCK")
.ok()
.and_then(|path| UnixStream::connect(path).ok());
while let Ok(req) = greetd_req_recv.recv_blocking() {
let resp = if let Some(ref mut sock) = sock {
req.write_to(sock)
.and_then(|_| Response::read_from(sock))
.unwrap_or_else(|err| Response::Error {
error_type: ErrorType::Error,
description: err.to_string(),
})
} else {
Response::Error {
error_type: ErrorType::Error,
description: "Greetd not connected".into(),
}
};
if let Err(err) = greetd_resp_send.send_blocking(resp) {
error!("error sending greetd response on channel: {}", err);
continue;
}
}
});
(greetd_req_send, greetd_resp_recv)
}
#[glib::derived_properties]
impl ObjectImpl for Lockscreen {
fn constructed(&self) {
let self_obj = self.obj();
let usp = UserSessionPage::new();
self_obj.set_unlock_status("");
self_obj.add_extra_page(&usp);
self_obj.set_default_page(LockscreenPage::Extra);
self_obj.connect_page_notify(clone!(@weak self as this => move |ls| {
glib::spawn_future_local(clone!(@weak ls => async move {
if ls.page() == LockscreenPage::Unlock {
this.obj().set_default_page(LockscreenPage::Unlock);
this.create_session().await;
} else {
this.obj().set_default_page(LockscreenPage::Extra);
this.cancel_session().await;
this.session.replace(None);
this.obj().set_sensitive(true);
}
}));
}));
usp.connect_ready_notify(clone!(@weak self_obj => move |usp| {
let shell = Shell::default();
let user_count = usp.imp().box_users.children().len();
let session_count = shell.sessions().map_or(0, |s| s.n_items());
if session_count == 1 && user_count == 1 {
self_obj.set_page(LockscreenPage::Unlock);
}
}));
usp.connect_closure(
"login",
false,
closure_local!(@watch self_obj => move |_: UserSessionPage| {
self_obj.set_page(LockscreenPage::Unlock);
}),
);
self.user_session_page.set(usp).unwrap();
self.parent_constructed();
}
}
impl Lockscreen {
async fn cancel_session(&self) {
if self.session.borrow().is_none() {
return;
}
if let Err(err) = self.greetd_req(Request::CancelSession).await {
warn!("greetd CancelSession failed: {}", err);
}
}
async fn create_session(&self) {
let user = self.user_session_page.get().unwrap().username();
if user.is_none() || self.session.borrow().eq(&user) {
return;
}
self.session.replace(user.clone());
let username = user.unwrap();
info!("creating greetd session for user {}", username);
self.obj().set_unlock_status("Please wait…");
self.obj().set_sensitive(false);
let mut req = Some(Request::CreateSession { username });
while let Some(next_req) = req.take() {
req = self.greetd_interaction(next_req).await;
}
}
async fn start_session(&self) -> anyhow::Result<()> {
let session = self.user_session_page.get().unwrap().session();
let settings = Settings::new(APP_ID);
if let Err(err) =
settings.set("last-user", self.session.clone().take().unwrap_or_default())
{
warn!("setting last-user failed {}", err);
}
if let Err(err) = settings.set("last-session", session.id()) {
warn!("setting last-session failed {}", err);
}
self.greetd_req(Request::StartSession {
cmd: vec![session.command()],
env: vec![
format!("XDG_SESSION_TYPE={}", session.session_type()),
format!("XDG_CURRENT_DESKTOP={}", session.desktop_names()),
format!("XDG_SESSION_DESKTOP={}", session.id()),
format!("GDMSESSION={}", session.id()),
],
})
.await
.context("start session")?;
Ok(())
}
async fn greetd_req(&self, req: Request) -> anyhow::Result<Response> {
if Shell::default().fake_greetd() {
return fake_greetd_interaction(req);
}
if self.greetd.borrow().is_none() {
self.greetd.set(Some(run_greetd()));
}
let (sender, receiver) = self.greetd.clone().take().unwrap();
sender.send(req).await.context("send greetd request")?;
match receiver.recv().await.context("receive greetd response")? {
Response::Error {
error_type: ErrorType::Error,
description,
} => Err(anyhow!("greetd error: {}", description)),
resp => Ok(resp),
}
}
async fn greetd_interaction(&self, req: Request) -> Option<Request> {
let resp = self.greetd_req(req).await;
if let Err(err) = resp {
error!("failed to send greetd request: {:?}", err);
self.obj().set_unlock_status(&tr("Error, please try again"));
self.obj().set_sensitive(true);
return None;
}
match resp.unwrap() {
Response::AuthMessage {
auth_message_type,
auth_message,
} => {
self.obj().set_unlock_status(&auth_message);
if let AuthMessageType::Error = auth_message_type {
self.obj().shake_pin_entry();
glib::timeout_future_seconds(1).await;
} else {
self.obj().set_sensitive(true);
}
match auth_message_type {
AuthMessageType::Info | AuthMessageType::Error => {
return Some(Request::PostAuthMessageResponse { response: None });
}
_ => {
}
}
}
Response::Success => {
self.obj().set_unlock_status("Success. Logging in…");
self.start_session().await.unwrap();
g_message!("phrog", "launched session, exiting in {}ms", QUIT_DELAY);
Shell::default().fade_out(0);
timeout_add_once(Duration::from_millis(QUIT_DELAY), || {
gtk::main_quit();
});
}
Response::Error {
error_type: ErrorType::AuthError,
description,
} => {
warn!("auth error: '{}'", description);
self.obj()
.set_unlock_status(&tr("Login failed, please try again"));
self.obj().shake_pin_entry();
self.cancel_session().await;
glib::timeout_future_seconds(1).await;
return Some(Request::CreateSession {
username: self.user_session_page.get()?.username()?,
});
}
v => error!("unexpected response to start session: {:?}", v),
}
None
}
}
impl WidgetImpl for Lockscreen {}
impl ContainerImpl for Lockscreen {}
impl BinImpl for Lockscreen {}
impl WindowImpl for Lockscreen {}
impl LockscreenImpl for Lockscreen {
fn unlock_submit(&self) {
glib::spawn_future_local(clone!(@weak self as this => async move {
this.obj().set_unlock_status("Please wait…");
this.obj().set_sensitive(false);
let mut req = Some(Request::PostAuthMessageResponse {
response: Some(this.obj().pin_entry().to_string())
});
while let Some(next_req) = req.take() {
req = this.greetd_interaction(next_req).await;
}
this.obj().clear_pin_entry();
}));
}
}
}
fn tr(msgid: &str) -> String {
gettextrs::dgettext(TEXT_DOMAIN, msgid)
}
fn fake_greetd_interaction(req: Request) -> anyhow::Result<Response> {
match req {
Request::CreateSession { .. } => anyhow::Ok(Response::AuthMessage {
auth_message_type: Secret,
auth_message: tr("Password:"),
}),
Request::PostAuthMessageResponse { response } => {
if response.is_none() || response.unwrap() != "0" {
anyhow::Ok(Response::Error {
error_type: AuthError,
description: tr("Incorrect password (hint: it's '0')"),
})
} else {
anyhow::Ok(Response::Success)
}
}
_ => anyhow::Ok(Response::Success),
}
}