2017-09-01 12:24:32 +00:00
/*
2017-09-07 20:00:08 +00:00
* meli - bin . rs
2017-09-01 12:24:32 +00:00
*
2018-07-16 12:26:06 +00:00
* Copyright 2017 - 2018 Manos Pitsidianakis
2017-09-07 20:00:08 +00:00
*
2017-09-01 12:24:32 +00:00
* 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/>.
* /
2018-07-18 07:42:52 +00:00
2019-07-17 17:35:11 +00:00
//!
//! This crate contains the frontend stuff of the application. The application entry way on
//! `src/bin.rs` creates an event loop and passes input to the `ui` module.
//!
//! The mail handling stuff is done in the `melib` crate which includes all backend needs. The
//! split is done to theoretically be able to create different frontends with the same innards.
//!
2018-07-18 07:42:52 +00:00
2018-08-05 09:14:26 +00:00
use std ::alloc ::System ;
2019-07-11 08:45:09 +00:00
use std ::io ::Write ;
use std ::path ::{ Path , PathBuf } ;
2018-08-05 09:14:26 +00:00
#[ global_allocator ]
static GLOBAL : System = System ;
2019-04-04 11:21:52 +00:00
use ui ;
2018-07-16 08:08:04 +00:00
2018-07-11 15:58:57 +00:00
pub use melib ::* ;
2018-08-06 19:20:34 +00:00
pub use ui ::* ;
2017-09-01 12:24:32 +00:00
2019-04-04 11:21:52 +00:00
use nix ;
2019-09-09 09:53:39 +00:00
use std ::os ::raw ::c_int ;
2019-07-11 08:45:09 +00:00
use xdg ;
2019-09-09 09:53:39 +00:00
fn notify (
signals : & [ c_int ] ,
2019-09-22 08:00:05 +00:00
sender : crossbeam ::channel ::Sender < ThreadEvent > ,
2019-09-09 09:53:39 +00:00
) -> std ::result ::Result < crossbeam ::channel ::Receiver < c_int > , std ::io ::Error > {
let ( s , r ) = crossbeam ::channel ::bounded ( 100 ) ;
let signals = signal_hook ::iterator ::Signals ::new ( signals ) ? ;
std ::thread ::spawn ( move | | {
2019-09-22 08:00:05 +00:00
let mut ctr = 0 ;
loop {
ctr % = 3 ;
if ctr = = 0 {
sender . send ( ThreadEvent ::Pulse ) . unwrap ( ) ;
}
for signal in signals . pending ( ) {
s . send ( signal ) . unwrap ( ) ;
}
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 100 ) ) ;
ctr + = 1 ;
2019-09-09 09:53:39 +00:00
}
} ) ;
Ok ( r )
}
2019-07-11 08:45:09 +00:00
macro_rules ! error_and_exit {
( $( $err :expr ) , * ) = > { {
2019-11-15 22:33:22 +00:00
return Err ( MeliError ::new ( format! ( $( $err ) , * ) ) ) ;
2019-07-11 08:45:09 +00:00
} }
}
#[ derive(Debug) ]
struct CommandLineArguments {
create_config : Option < String > ,
config : Option < String > ,
help : bool ,
2019-08-01 09:44:30 +00:00
version : bool ,
2019-07-11 08:45:09 +00:00
}
2018-07-24 10:28:15 +00:00
2019-11-15 22:33:22 +00:00
fn main ( ) {
::std ::process ::exit ( match run_app ( ) {
Ok ( ( ) ) = > 0 ,
Err ( err ) = > {
eprintln! ( " {} " , err ) ;
1
}
} ) ;
}
fn run_app ( ) -> Result < ( ) > {
2019-07-11 08:45:09 +00:00
enum CommandLineFlags {
CreateConfig ,
Config ,
}
use CommandLineFlags ::* ;
let mut prev : Option < CommandLineFlags > = None ;
let mut args = CommandLineArguments {
create_config : None ,
config : None ,
help : false ,
2019-08-01 09:44:30 +00:00
version : false ,
2019-07-11 08:45:09 +00:00
} ;
for i in std ::env ::args ( ) . skip ( 1 ) {
match i . as_str ( ) {
" --create-config " = > match prev {
None = > prev = Some ( CreateConfig ) ,
Some ( CreateConfig ) = > error_and_exit! ( " invalid value for flag `--create-config` " ) ,
Some ( Config ) = > error_and_exit! ( " invalid value for flag `--config` " ) ,
} ,
" --config " | " -c " = > match prev {
None = > prev = Some ( Config ) ,
Some ( CreateConfig ) if args . create_config . is_none ( ) = > {
args . config = Some ( String ::new ( ) ) ;
prev = Some ( Config ) ;
}
Some ( CreateConfig ) = > error_and_exit! ( " Duplicate value for flag `--create-config` " ) ,
Some ( Config ) = > error_and_exit! ( " invalid value for flag `--config` " ) ,
} ,
2019-08-01 09:44:30 +00:00
" --help " | " -h " = > {
args . help = true ;
}
" --version " | " -v " = > {
args . version = true ;
}
2019-07-11 08:45:09 +00:00
e = > match prev {
None = > error_and_exit! ( " error: value without command {} " , e ) ,
Some ( CreateConfig ) if args . create_config . is_none ( ) = > {
args . create_config = Some ( i ) ;
prev = None ;
}
Some ( Config ) if args . config . is_none ( ) = > {
args . config = Some ( i ) ;
prev = None ;
}
Some ( CreateConfig ) = > error_and_exit! ( " Duplicate value for flag `--create-config` " ) ,
Some ( Config ) = > error_and_exit! ( " Duplicate value for flag `--config` " ) ,
} ,
}
}
if args . help {
println! ( " usage: \t meli [--create-config[ PATH]] [--config[ PATH]|-c[ PATH]] " ) ;
println! ( " \t meli --help " ) ;
2019-08-01 09:44:30 +00:00
println! ( " \t meli --version " ) ;
2019-07-11 08:45:09 +00:00
println! ( " " ) ;
2019-08-01 09:44:30 +00:00
println! ( " \t --help, -h \t \t show this message and exit " ) ;
println! ( " \t --version, -v \t \t print version and exit " ) ;
2019-07-11 08:45:09 +00:00
println! ( " \t --create-config[ PATH] \t Create a sample configuration file with available configuration options. If PATH is not specified, meli will try to create it in $XDG_CONFIG_HOME/meli/config " ) ;
println! ( " \t --config PATH, -c PATH \t Use specified configuration file " ) ;
2019-11-15 22:33:22 +00:00
return Ok ( ( ) ) ;
2019-07-11 08:45:09 +00:00
}
2019-08-01 09:44:30 +00:00
if args . version {
println! ( " meli {} " , option_env! ( " CARGO_PKG_VERSION " ) . unwrap_or ( " 0.0 " ) ) ;
2019-11-15 22:33:22 +00:00
return Ok ( ( ) ) ;
2019-08-01 09:44:30 +00:00
}
2019-07-11 08:45:09 +00:00
match prev {
None = > { }
Some ( CreateConfig ) if args . create_config . is_none ( ) = > args . create_config = Some ( " " . into ( ) ) ,
Some ( CreateConfig ) = > error_and_exit! ( " Duplicate value for flag `--create-config` " ) ,
Some ( Config ) = > error_and_exit! ( " error: flag without value: --config " ) ,
} ;
if let Some ( config_path ) = args . create_config . as_mut ( ) {
let config_path : PathBuf = if config_path . is_empty ( ) {
let xdg_dirs = xdg ::BaseDirectories ::with_prefix ( " meli " ) . unwrap ( ) ;
2019-11-15 22:33:22 +00:00
xdg_dirs . place_config_file ( " config " ) . map_err ( | e | {
MeliError ::new ( format! (
" Cannot create configuration directory in {}: \n {} " ,
xdg_dirs . get_config_home ( ) . display ( ) ,
e
) )
} ) ?
2019-07-11 08:45:09 +00:00
} else {
Path ::new ( config_path ) . to_path_buf ( )
} ;
if config_path . exists ( ) {
2019-11-15 22:33:22 +00:00
return Err ( MeliError ::new ( format! ( " File ` {} ` already exists. \n Maybe you meant to specify another path with --create-config=PATH " , config_path . display ( ) ) ) ) ;
2019-07-11 08:45:09 +00:00
}
let mut file = std ::fs ::OpenOptions ::new ( )
. write ( true )
. create_new ( true )
. open ( config_path . as_path ( ) )
2019-11-15 22:33:22 +00:00
. map_err ( | e | MeliError ::new ( format! ( " Could not create config file: \n {} " , e ) ) ) ? ;
2019-07-11 08:45:09 +00:00
file . write_all ( include_bytes! ( " ../sample-config " ) )
2019-11-15 22:33:22 +00:00
. map_err ( | e | MeliError ::new ( format! ( " Could not write to config file: \n {} " , e ) ) ) ? ;
2019-07-11 08:45:09 +00:00
println! ( " Written example configuration to {} " , config_path . display ( ) ) ;
2019-11-15 22:33:22 +00:00
return Ok ( ( ) ) ;
2019-07-11 08:45:09 +00:00
}
if let Some ( config_location ) = args . config . as_ref ( ) {
std ::env ::set_var ( " MELI_CONFIG " , config_location ) ;
}
2018-07-11 14:07:51 +00:00
2019-09-22 08:00:05 +00:00
/* Create the application State. */
2019-11-15 22:33:22 +00:00
let mut state = State ::new ( ) ? ;
2019-09-22 08:00:05 +00:00
let receiver = state . receiver ( ) ;
let sender = state . sender ( ) ;
2018-07-16 12:26:06 +00:00
/* Catch SIGWINCH to handle terminal resizing */
2019-09-09 09:53:39 +00:00
let signals = & [
/* Catch SIGWINCH to handle terminal resizing */
signal_hook ::SIGWINCH ,
2019-11-05 06:32:27 +00:00
/* Catch SIGCHLD to handle embed applications status change */
signal_hook ::SIGCHLD ,
2019-09-09 09:53:39 +00:00
] ;
2019-09-22 08:00:05 +00:00
let signal_recvr = notify ( signals , sender ) ? ;
2018-07-11 14:07:51 +00:00
2019-04-10 19:01:02 +00:00
let window = Box ::new ( Tabbed ::new ( vec! [
2019-04-28 22:48:50 +00:00
Box ::new ( listing ::Listing ::new ( & state . context . accounts ) ) ,
2019-10-26 12:58:56 +00:00
Box ::new ( ContactList ::new ( & state . context ) ) ,
Box ::new ( StatusPanel ::new ( ) ) ,
2019-03-14 10:19:25 +00:00
] ) ) ;
2018-08-11 15:00:21 +00:00
2019-04-10 19:01:02 +00:00
let status_bar = Box ::new ( StatusBar ::new ( window ) ) ;
state . register_component ( status_bar ) ;
2018-07-13 15:38:57 +00:00
2019-04-10 19:01:02 +00:00
let xdg_notifications = Box ::new ( ui ::components ::notifications ::XDGNotifications { } ) ;
state . register_component ( xdg_notifications ) ;
state . register_component ( Box ::new (
2019-03-14 10:19:25 +00:00
ui ::components ::notifications ::NotificationFilter { } ,
2019-04-10 19:01:02 +00:00
) ) ;
2018-07-17 14:16:16 +00:00
2018-07-16 12:26:06 +00:00
/* Keep track of the input mode. See ui::UIMode for details */
2017-09-16 12:05:28 +00:00
' main : loop {
2018-07-14 18:41:38 +00:00
state . render ( ) ;
2017-07-23 11:01:17 +00:00
2017-09-16 12:05:28 +00:00
' inner : loop {
2019-04-10 19:01:02 +00:00
/* Check if any components have sent reply events to State. */
2018-07-20 09:44:04 +00:00
let events : Vec < UIEvent > = state . context . replies ( ) ;
2018-07-16 08:08:04 +00:00
for e in events {
state . rcv_event ( e ) ;
}
2018-10-14 16:49:16 +00:00
state . redraw ( ) ;
2018-07-21 08:20:13 +00:00
/* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */
2019-09-09 09:53:39 +00:00
crossbeam ::select! {
recv ( receiver ) -> r = > {
2019-09-22 08:00:05 +00:00
match r {
Ok ( ThreadEvent ::Pulse ) = > { } ,
_ = > { debug! ( & r ) ; }
}
match r . unwrap ( ) {
2018-07-24 10:28:15 +00:00
ThreadEvent ::Input ( Key ::Ctrl ( 'z' ) ) = > {
2018-08-07 12:01:15 +00:00
state . switch_to_main_screen ( ) ;
2018-07-24 10:28:15 +00:00
//_thread_handler.join().expect("Couldn't join on the associated thread");
let self_pid = nix ::unistd ::Pid ::this ( ) ;
nix ::sys ::signal ::kill ( self_pid , nix ::sys ::signal ::Signal ::SIGSTOP ) . unwrap ( ) ;
2018-08-07 12:01:15 +00:00
state . switch_to_alternate_screen ( ) ;
2018-08-16 13:32:47 +00:00
state . restore_input ( ) ;
2018-07-24 10:28:15 +00:00
// BUG: thread sends input event after one received key
state . update_size ( ) ;
state . render ( ) ;
state . redraw ( ) ;
} ,
2019-10-03 16:11:28 +00:00
ThreadEvent ::Input ( Key ::Ctrl ( 'l' ) ) = > {
/* Manual screen redraw */
state . update_size ( ) ;
state . render ( ) ;
state . redraw ( ) ;
} ,
2018-07-16 10:36:28 +00:00
ThreadEvent ::Input ( k ) = > {
2018-07-21 08:20:13 +00:00
match state . mode {
2018-07-16 10:36:28 +00:00
UIMode ::Normal = > {
match k {
Key ::Char ( 'q' ) | Key ::Char ( 'Q' ) = > {
2019-09-27 10:14:16 +00:00
if state . can_quit_cleanly ( ) {
drop ( state ) ;
break 'main ;
} else {
state . redraw ( ) ;
}
2018-07-16 10:36:28 +00:00
} ,
2019-03-25 11:41:39 +00:00
Key ::Char ( ' ' ) = > {
2018-07-21 08:20:13 +00:00
state . mode = UIMode ::Execute ;
2019-04-10 20:37:20 +00:00
state . rcv_event ( UIEvent ::ChangeMode ( UIMode ::Execute ) ) ;
2018-07-16 10:36:28 +00:00
state . redraw ( ) ;
}
key = > {
2019-04-10 20:37:20 +00:00
state . rcv_event ( UIEvent ::Input ( key ) ) ;
2018-07-16 10:36:28 +00:00
state . redraw ( ) ;
} ,
}
2018-07-14 22:27:13 +00:00
} ,
2019-02-25 09:11:56 +00:00
UIMode ::Insert = > {
match k {
Key ::Char ( '\n' ) | Key ::Esc = > {
state . mode = UIMode ::Normal ;
2019-04-10 20:37:20 +00:00
state . rcv_event ( UIEvent ::ChangeMode ( UIMode ::Normal ) ) ;
2019-02-25 09:11:56 +00:00
state . redraw ( ) ;
} ,
k = > {
2019-04-10 20:37:20 +00:00
state . rcv_event ( UIEvent ::InsertInput ( k ) ) ;
2019-02-25 09:11:56 +00:00
state . redraw ( ) ;
} ,
}
}
2018-07-16 10:36:28 +00:00
UIMode ::Execute = > {
match k {
Key ::Char ( '\n' ) | Key ::Esc = > {
2018-07-21 08:20:13 +00:00
state . mode = UIMode ::Normal ;
2019-04-10 20:37:20 +00:00
state . rcv_event ( UIEvent ::ChangeMode ( UIMode ::Normal ) ) ;
2018-07-16 10:36:28 +00:00
state . redraw ( ) ;
} ,
2018-08-07 13:14:06 +00:00
k = > {
2019-04-10 20:37:20 +00:00
state . rcv_event ( UIEvent ::ExInput ( k ) ) ;
2018-07-16 10:36:28 +00:00
state . redraw ( ) ;
} ,
}
2018-07-16 08:08:04 +00:00
} ,
2019-11-05 06:32:27 +00:00
UIMode ::Embed = > state . redraw ( ) ,
2018-07-21 08:20:13 +00:00
UIMode ::Fork = > {
break 'inner ; // `goto` 'reap loop, and wait on child.
} ,
2018-07-14 22:27:13 +00:00
}
2018-07-13 15:38:57 +00:00
} ,
2019-11-05 06:32:27 +00:00
ThreadEvent ::InputRaw ( raw_input ) = > {
state . rcv_event ( UIEvent ::EmbedInput ( raw_input ) ) ;
state . redraw ( ) ;
} ,
2018-09-05 13:08:11 +00:00
ThreadEvent ::RefreshMailbox ( event ) = > {
2018-09-12 12:10:19 +00:00
state . refresh_event ( * event ) ;
2018-07-17 14:16:16 +00:00
state . redraw ( ) ;
2018-07-16 10:36:28 +00:00
} ,
2019-04-10 20:37:20 +00:00
ThreadEvent ::UIEvent ( UIEvent ::ChangeMode ( f ) ) = > {
2018-07-21 08:20:13 +00:00
state . mode = f ;
2019-02-25 09:11:56 +00:00
if f = = UIMode ::Fork {
break 'inner ; // `goto` 'reap loop, and wait on child.
}
2018-07-21 08:20:13 +00:00
}
2018-08-06 10:33:10 +00:00
ThreadEvent ::UIEvent ( e ) = > {
2019-04-10 20:37:20 +00:00
state . rcv_event ( e ) ;
2018-07-16 10:36:28 +00:00
state . render ( ) ;
2018-07-13 15:38:57 +00:00
} ,
2019-09-11 14:57:55 +00:00
ThreadEvent ::Pulse = > {
state . redraw ( ) ;
} ,
2018-08-06 11:58:54 +00:00
ThreadEvent ::ThreadJoin ( id ) = > {
state . join ( id ) ;
} ,
2017-09-28 15:06:35 +00:00
}
2017-09-01 12:24:32 +00:00
} ,
2019-09-09 09:53:39 +00:00
recv ( signal_recvr ) -> sig = > {
match sig . unwrap ( ) {
signal_hook ::SIGWINCH = > {
if state . mode ! = UIMode ::Fork {
state . update_size ( ) ;
state . render ( ) ;
state . redraw ( ) ;
}
} ,
_ = > { }
2018-07-16 10:36:28 +00:00
}
2018-07-13 15:38:57 +00:00
} ,
2017-07-23 11:01:17 +00:00
}
2018-07-21 08:20:13 +00:00
} // end of 'inner
' reap : loop {
match state . try_wait_on_child ( ) {
Some ( true ) = > {
2018-08-16 13:32:47 +00:00
state . restore_input ( ) ;
2018-09-03 22:49:29 +00:00
state . switch_to_alternate_screen ( ) ;
2018-07-27 18:37:56 +00:00
}
2018-07-21 08:20:13 +00:00
Some ( false ) = > {
use std ::{ thread , time } ;
2018-07-21 14:29:29 +00:00
let ten_millis = time ::Duration ::from_millis ( 1500 ) ;
2018-07-21 08:20:13 +00:00
thread ::sleep ( ten_millis ) ;
2018-07-24 10:28:15 +00:00
2018-07-21 08:20:13 +00:00
continue 'reap ;
2018-07-27 18:37:56 +00:00
}
None = > {
2018-09-03 22:49:29 +00:00
state . mode = UIMode ::Normal ;
state . render ( ) ;
2018-07-27 18:37:56 +00:00
break 'reap ;
}
2018-07-21 08:20:13 +00:00
}
2017-07-23 11:01:17 +00:00
}
}
2019-09-09 09:53:39 +00:00
Ok ( ( ) )
2017-07-23 11:01:17 +00:00
}