Refactored event handling for power commands and exit signal.

This commit is contained in:
Antoine POPINEAU 2023-11-10 20:27:04 +01:00 committed by Antoine POPINEAU
parent 551a08af75
commit 45e98216c4
7 changed files with 63 additions and 51 deletions

View File

@ -2,48 +2,64 @@ use std::time::Duration;
use crossterm::event::{Event as TermEvent, EventStream, KeyEvent};
use futures::{future::FutureExt, StreamExt};
use tokio::sync::mpsc;
use tokio::{
process::Command,
sync::mpsc::{self, Sender},
};
use crate::AuthStatus;
const FRAME_RATE: f64 = 2.0;
pub enum Event {
Key(KeyEvent),
Render,
PowerCommand(Command),
Exit(AuthStatus),
}
pub struct Events {
rx: mpsc::Receiver<Event>,
tx: mpsc::Sender<Event>,
}
impl Events {
pub async fn new() -> Events {
let (tx, rx) = mpsc::channel(10);
tokio::task::spawn(async move {
let mut stream = EventStream::new();
let mut render_interval = tokio::time::interval(Duration::from_secs_f64(1.0 / FRAME_RATE));
tokio::task::spawn({
let tx = tx.clone();
loop {
let render = render_interval.tick();
let event = stream.next().fuse();
async move {
let mut stream = EventStream::new();
let mut render_interval = tokio::time::interval(Duration::from_secs_f64(1.0 / FRAME_RATE));
tokio::select! {
event = event => {
if let Some(Ok(TermEvent::Key(event))) = event {
let _ = tx.send(Event::Key(event)).await;
let _ = tx.send(Event::Render).await;
loop {
let render = render_interval.tick();
let event = stream.next().fuse();
tokio::select! {
event = event => {
if let Some(Ok(TermEvent::Key(event))) = event {
let _ = tx.send(Event::Key(event)).await;
let _ = tx.send(Event::Render).await;
}
}
}
_ = render => { let _ = tx.send(Event::Render).await; },
_ = render => { let _ = tx.send(Event::Render).await; },
}
}
}
});
Events { rx }
Events { rx, tx }
}
pub async fn next(&mut self) -> Option<Event> {
self.rx.recv().await
}
pub fn sender(&self) -> Sender<Event> {
self.tx.clone()
}
}

View File

@ -16,12 +16,12 @@ use getopts::{Matches, Options};
use i18n_embed::DesktopLanguageRequester;
use tokio::{
net::UnixStream,
process::Command,
sync::{Notify, RwLock, RwLockWriteGuard},
sync::{mpsc::Sender, RwLock, RwLockWriteGuard},
};
use zeroize::Zeroize;
use crate::{
event::Event,
info::{
get_issue, get_last_session, get_last_session_path, get_last_user_name, get_last_user_session, get_last_user_session_path, get_last_user_username, get_min_max_uids, get_sessions, get_users,
},
@ -95,6 +95,7 @@ pub struct Greeter {
pub config: Option<Matches>,
pub socket: String,
pub stream: Option<Arc<RwLock<UnixStream>>>,
pub events: Option<Sender<Event>>,
// Current mode of the application, will define what actions are permitted.
pub mode: Mode,
@ -148,10 +149,6 @@ pub struct Greeter {
// Menu for power options.
pub powers: Menu<Power>,
// Power command that was selected.
pub power_command: Option<Command>,
// Channel to notify of a power command selection.
pub power_command_notify: Arc<Notify>,
// Whether to prefix the power commands with `setsid`.
pub power_setsid: bool,
@ -170,9 +167,10 @@ impl Drop for Greeter {
}
impl Greeter {
pub async fn new() -> Self {
pub async fn new(events: Sender<Event>) -> Self {
let mut greeter = Self::default();
greeter.events = Some(events);
greeter.set_locale();
greeter.powers = Menu {

View File

@ -159,7 +159,7 @@ pub fn delete_last_user_session_path(username: &str) {
}
pub fn delete_last_user_session(username: &str) {
let _ = fs::remove_file(&format!("{LAST_SESSION}-{username}"));
let _ = fs::remove_file(format!("{LAST_SESSION}-{username}"));
}
pub fn get_users(min_uid: u16, max_uid: u16) -> Vec<User> {

View File

@ -7,6 +7,7 @@ use tokio::sync::{
};
use crate::{
event::Event,
info::{delete_last_user_session, delete_last_user_session_path, write_last_user_session, write_last_user_session_path, write_last_username},
ui::sessions::{Session, SessionSource, SessionType},
AuthStatus, Greeter, Mode,
@ -125,7 +126,9 @@ impl Ipc {
}
}
crate::exit(greeter, AuthStatus::Success).await;
if let Some(ref sender) = greeter.events {
let _ = sender.send(Event::Exit(AuthStatus::Success)).await;
}
} else {
let command = greeter.session_source.command(greeter).map(str::to_string);

View File

@ -45,9 +45,11 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, input: KeyEvent, ipc: Ipc) ->
modifiers: KeyModifiers::CONTROL,
..
} => {
use crate::greeter::AuthStatus;
use crate::{AuthStatus, Event};
crate::exit(&mut greeter, AuthStatus::Cancel).await;
if let Some(ref sender) = greeter.events {
let _ = sender.send(Event::Exit(AuthStatus::Cancel)).await;
}
}
// Depending on the active screen, pressing Escape will either return to the
@ -259,7 +261,7 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, input: KeyEvent, ipc: Ipc) ->
let power_command = greeter.powers.options.get(greeter.powers.selected).cloned();
if let Some(command) = power_command {
power(&mut greeter, command.action);
power(&mut greeter, command.action).await;
}
greeter.mode = greeter.previous_mode;

View File

@ -38,7 +38,8 @@ async fn main() {
}
async fn run() -> Result<(), Box<dyn Error>> {
let greeter = Greeter::new().await;
let mut events = Events::new().await;
let greeter = Greeter::new(events.sender()).await;
let mut stdout = io::stdout();
register_panic_handler();
@ -51,7 +52,6 @@ async fn run() -> Result<(), Box<dyn Error>> {
terminal.clear()?;
let mut events = Events::new().await;
let ipc = Ipc::new();
if greeter.remember && !greeter.username.value.is_empty() {
@ -75,23 +75,6 @@ async fn run() -> Result<(), Box<dyn Error>> {
}
});
tokio::task::spawn({
let greeter = greeter.clone();
let notify = greeter.read().await.power_command_notify.clone();
async move {
loop {
notify.notified().await;
let command = greeter.write().await.power_command.take();
if let Some(command) = command {
power::run(&greeter, command).await;
}
}
}
});
loop {
if let Some(status) = greeter.read().await.exit {
return Err(status.into());
@ -100,12 +83,21 @@ async fn run() -> Result<(), Box<dyn Error>> {
match events.next().await {
Some(Event::Render) => ui::draw(greeter.clone(), &mut terminal).await?,
Some(Event::Key(key)) => keyboard::handle(greeter.clone(), key, ipc.clone()).await?,
Some(Event::Exit(status)) => {
crate::exit(&mut *greeter.write().await, status).await;
}
Some(Event::PowerCommand(command)) => {
power::run(&greeter, command).await;
}
_ => {}
}
}
}
pub async fn exit(greeter: &mut Greeter, status: AuthStatus) {
async fn exit(greeter: &mut Greeter, status: AuthStatus) {
match status {
AuthStatus::Success => {}
AuthStatus::Cancel | AuthStatus::Failure => Ipc::cancel(greeter).await,

View File

@ -2,7 +2,7 @@ use std::{process::Stdio, sync::Arc};
use tokio::{process::Command, sync::RwLock};
use crate::{ui::power::Power, Greeter, Mode};
use crate::{event::Event, ui::power::Power, Greeter, Mode};
#[derive(SmartDefault, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PowerOption {
@ -11,7 +11,7 @@ pub enum PowerOption {
Reboot,
}
pub fn power(greeter: &mut Greeter, option: PowerOption) {
pub async fn power(greeter: &mut Greeter, option: PowerOption) {
let command = match greeter.powers.options.iter().find(|opt| opt.action == option) {
None => None,
@ -54,8 +54,9 @@ pub fn power(greeter: &mut Greeter, option: PowerOption) {
command.stdout(Stdio::null());
command.stderr(Stdio::null());
greeter.power_command = Some(command);
greeter.power_command_notify.notify_one();
if let Some(ref sender) = greeter.events {
let _ = sender.send(Event::PowerCommand(command)).await;
}
}
}