/*
* meli
*
* Copyright 2017-2018 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
/*! The application's state.
The UI crate has an Box<dyn Component>-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct.
`State` owns all the Components of the UI. In the application's main event loop, input is handed to the state in the form of `UIEvent` objects which traverse the component graph. Components decide to handle each input or not.
Input is received in the main loop from threads which listen on the stdin for user input, observe folders for file changes etc. The relevant struct is `ThreadEvent`.
*/
use super::*;
use crate::plugins::PluginManager;
use melib::backends::{AccountHash, MailboxHash, NotifyFn};
use crossbeam::channel::{unbounded, Receiver, Sender};
use smallvec::SmallVec;
use std::collections::HashMap;
use std::env;
use std::io::Write;
use std::os::unix::io::RawFd;
use std::thread;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use termion::{clear, cursor};
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
struct InputHandler {
pipe: (RawFd, RawFd),
rx: Receiver<InputCommand>,
tx: Sender<InputCommand>,
}
impl InputHandler {
fn restore(&self, tx: Sender<ThreadEvent>) {
/* Clear channel without blocking. switch_to_main_screen() issues a kill when
* returning from a fork and there's no input thread, so the newly created thread will
* receive it and die. */
//let _ = self.rx.try_iter().count();
let rx = self.rx.clone();
let pipe = self.pipe.0;
thread::Builder::new()
.name("input-thread".to_string())
.spawn(move || {
get_events(
|i| {
tx.send(ThreadEvent::Input(i)).unwrap();
},
&rx,
pipe,
)
})
.unwrap();
}
fn kill(&self) {
let _ = nix::unistd::write(self.pipe.1, &[1]);
self.tx.send(InputCommand::Kill).unwrap();
}
}
/// A context container for loaded settings, accounts, UI changes, etc.
pub struct Context {
pub accounts: Vec<Account>,
pub account_hashes: HashMap<AccountHash, usize>,
pub settings: Settings,
pub runtime_settings: Settings,
/// Areas of the screen that must be redrawn in the next render
pub dirty_areas: VecDeque<Area>,
/// Events queue that components send back to the state
pub replies: VecDeque<UIEvent>,
sender: Sender<ThreadEvent>,
receiver: Receiver<ThreadEvent>,
input: InputHandler,
work_controller: WorkController,
pub children: Vec<std::process::Child>,
pub plugin_manager: PluginManager,
pub temp_files: Vec<File>,
}
impl Context {
pub fn replies(&mut self) -> smallvec::SmallVec<[UIEvent; 8]> {
self.replies.drain(0..).collect()
}
pub fn input_kill(&self) {
self.input.kill();
}
pub fn restore_input(&self) {
self.input.restore(self.sender.clone());
}
pub fn is_online(&mut self, account_pos: usize) -> Result<()> {
let Context {
ref mut accounts,
ref mut replies,
..
} = self;
let was_online = accounts[account_pos].is_online;
let ret = accounts[account_pos].is_online();
if ret.is_ok() {
if !was_online {
for mailbox_node in accounts[account_pos].list_mailboxes() {
debug!(
"hash & mailbox: {:?} {}",
mailbox_node.hash,
accounts[account_pos][&mailbox_node.hash].name()
);
}
/* Account::watch() needs
* - work_controller to pass `work_context` to the watcher threads and then add them
* to the controller's static thread list,
* - sender to pass a RefreshEventCon