aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
blob: d139daf6aa1c1d2545b0925f1fbe5052ecfb5746 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 Robin Jarry

mod app;
mod cmd;
mod config;
mod worker;

use std::io;
use std::panic;
use std::sync::Arc;

use anyhow::Result;
use clap::Parser;
use crossterm::cursor;
use crossterm::event::{
    DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode, KeyEventKind,
};
use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, SetTitle,
};
use crossterm::ExecutableCommand;
use futures::future::FutureExt;
use futures::StreamExt;
use tokio::sync::mpsc::{channel, Receiver};
use tokio::sync::Mutex;
use tui::backend::{Backend, CrosstermBackend};
use tui::Terminal;

use crate::app::App;
use crate::config::Config;

/// A pretty good email client
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    /// Enabled account (by default, all accounts are enabled).
    #[arg(short, long = "account")]
    accounts: Vec<String>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let default_hook = panic::take_hook();
    panic::set_hook(Box::new(move |info| {
        restore_term().unwrap();
        default_hook(info);
    }));

    // Parse command line arguments and configuration files.
    let args = Args::parse();
    let config = Arc::new(Mutex::new(Config::parse(args.accounts)?));
    let (updates_tx, updates_rx) = channel(1);
    let mut app = App::new(config.clone(), updates_tx);

    // Start email handling workers in the background.
    for account in config.lock().await.accounts.values() {
        app.start_worker(account.clone());
    }

    // The UI must run in the "main" thread.
    ui_loop(config, &app, updates_rx).await?;

    app.stop_workers().await;

    restore_term()?;

    Ok(())
}

async fn ui_loop(
    config: Arc<Mutex<Config>>,
    app: &App,
    mut app_updates: Receiver<()>,
) -> Result<()> {
    // Terminal initialization.
    enable_raw_mode()?;

    let mut backend = CrosstermBackend::new(io::stdout());

    backend.execute(EnterAlternateScreen)?;
    if config.lock().await.ui.mouse_enabled {
        backend.execute(EnableMouseCapture)?;
    }
    backend.hide_cursor()?;
    backend.execute(SetTitle("aerc"))?;

    let terminal = Terminal::new(backend)?;

    let mut term_events = EventStream::new();

    loop {
        // TODO: draw ui here
        tokio::select!(
            t = app_updates.recv() => match t {
                Some(_) => (),
                None => break,
            },
            e = term_events.next().fuse() => match e {
                Some(Ok(event)) => match event {
                    Event::Key(key) => {
                        if key.kind == KeyEventKind::Press {
                            println!("key {:?}\r", key);
                            if key.code == KeyCode::Esc {
                                break;
                            }
                        }
                    }
                    Event::Mouse(mouse) => {
                        println!("mouse {:?}\r", mouse);
                    }
                    Event::Paste(buf) => {
                        println!("paste {:?}\r", buf);
                    }
                    _ => ()
                }
                Some(Err(e)) => println!("Error: {:?}\r", e),
                None => break,
            }
        );
    }

    Ok(())
}

fn restore_term() -> Result<()> {
    crossterm::execute!(
        io::stdout(),
        LeaveAlternateScreen,
        DisableMouseCapture,
        cursor::Show
    )?;
    disable_raw_mode()?;
    Ok(())
}