You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

417 lines
16KB

  1. /*
  2. * meli - bin.rs
  3. *
  4. * Copyright 2017-2018 Manos Pitsidianakis
  5. *
  6. * This file is part of meli.
  7. *
  8. * meli is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * meli is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with meli. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. //!
  22. //! This crate contains the frontend stuff of the application. The application entry way on
  23. //! `src/bin.rs` creates an event loop and passes input to the `ui` module.
  24. //!
  25. //! The mail handling stuff is done in the `melib` crate which includes all backend needs. The
  26. //! split is done to theoretically be able to create different frontends with the same innards.
  27. //!
  28. use std::alloc::System;
  29. use std::path::{Path, PathBuf};
  30. #[global_allocator]
  31. static GLOBAL: System = System;
  32. use ui;
  33. pub use melib::*;
  34. pub use ui::*;
  35. use nix;
  36. use std::os::raw::c_int;
  37. use xdg;
  38. fn notify(
  39. signals: &[c_int],
  40. sender: crossbeam::channel::Sender<ThreadEvent>,
  41. ) -> std::result::Result<crossbeam::channel::Receiver<c_int>, std::io::Error> {
  42. let (s, r) = crossbeam::channel::bounded(100);
  43. let signals = signal_hook::iterator::Signals::new(signals)?;
  44. std::thread::spawn(move || {
  45. let mut ctr = 0;
  46. loop {
  47. ctr %= 3;
  48. if ctr == 0 {
  49. sender.send(ThreadEvent::Pulse).unwrap();
  50. }
  51. for signal in signals.pending() {
  52. s.send(signal).unwrap();
  53. }
  54. std::thread::sleep(std::time::Duration::from_millis(100));
  55. ctr += 1;
  56. }
  57. });
  58. Ok(r)
  59. }
  60. macro_rules! error_and_exit {
  61. ($($err:expr),*) => {{
  62. return Err(MeliError::new(format!($($err),*)));
  63. }}
  64. }
  65. #[derive(Debug)]
  66. struct CommandLineArguments {
  67. create_config: Option<String>,
  68. test_config: Option<String>,
  69. config: Option<String>,
  70. help: bool,
  71. version: bool,
  72. }
  73. fn main() {
  74. ::std::process::exit(match run_app() {
  75. Ok(()) => 0,
  76. Err(err) => {
  77. eprintln!("{}", err);
  78. 1
  79. }
  80. });
  81. }
  82. fn run_app() -> Result<()> {
  83. enum CommandLineFlags {
  84. CreateConfig,
  85. TestConfig,
  86. Config,
  87. }
  88. use CommandLineFlags::*;
  89. let mut prev: Option<CommandLineFlags> = None;
  90. let mut args = CommandLineArguments {
  91. create_config: None,
  92. test_config: None,
  93. config: None,
  94. help: false,
  95. version: false,
  96. };
  97. for i in std::env::args().skip(1) {
  98. match i.as_str() {
  99. "--test-config" => match prev {
  100. None => prev = Some(TestConfig),
  101. Some(CreateConfig) => error_and_exit!("invalid value for flag `--create-config`"),
  102. Some(Config) => error_and_exit!("invalid value for flag `--config`"),
  103. Some(TestConfig) => error_and_exit!("invalid value for flag `--test-config`"),
  104. },
  105. "--create-config" => match prev {
  106. None => prev = Some(CreateConfig),
  107. Some(CreateConfig) => error_and_exit!("invalid value for flag `--create-config`"),
  108. Some(TestConfig) => error_and_exit!("invalid value for flag `--test-config`"),
  109. Some(Config) => error_and_exit!("invalid value for flag `--config`"),
  110. },
  111. "--config" | "-c" => match prev {
  112. None => prev = Some(Config),
  113. Some(CreateConfig) if args.create_config.is_none() => {
  114. args.config = Some(String::new());
  115. prev = Some(Config);
  116. }
  117. Some(CreateConfig) => error_and_exit!("invalid value for flag `--create-config`"),
  118. Some(Config) => error_and_exit!("invalid value for flag `--config`"),
  119. Some(TestConfig) => error_and_exit!("invalid value for flag `--test-config`"),
  120. },
  121. "--help" | "-h" => {
  122. args.help = true;
  123. }
  124. "--version" | "-v" => {
  125. args.version = true;
  126. }
  127. e => match prev {
  128. None => error_and_exit!("error: value without command {}", e),
  129. Some(CreateConfig) if args.create_config.is_none() => {
  130. args.create_config = Some(i);
  131. prev = None;
  132. }
  133. Some(Config) if args.config.is_none() => {
  134. args.config = Some(i);
  135. prev = None;
  136. }
  137. Some(TestConfig) if args.test_config.is_none() => {
  138. args.test_config = Some(i);
  139. prev = None;
  140. }
  141. Some(TestConfig) => error_and_exit!("Duplicate value for flag `--test-config`"),
  142. Some(CreateConfig) => error_and_exit!("Duplicate value for flag `--create-config`"),
  143. Some(Config) => error_and_exit!("Duplicate value for flag `--config`"),
  144. },
  145. }
  146. }
  147. if args.help {
  148. println!("usage:\tmeli [--create-config[ PATH]] [--config[ PATH]|-c[ PATH]]");
  149. println!("\tmeli --help");
  150. println!("\tmeli --version");
  151. println!("");
  152. println!("\t--help, -h\t\tshow this message and exit");
  153. println!("\t--version, -v\t\tprint version and exit");
  154. println!("\t--create-config[ PATH]\tCreate 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");
  155. println!(
  156. "\t--test-config PATH\tTest a configuration file for syntax issues or missing options."
  157. );
  158. println!("\t--config PATH, -c PATH\tUse specified configuration file");
  159. return Ok(());
  160. }
  161. if args.version {
  162. println!("meli {}", option_env!("CARGO_PKG_VERSION").unwrap_or("0.0"));
  163. return Ok(());
  164. }
  165. match prev {
  166. None => {}
  167. Some(CreateConfig) if args.create_config.is_none() => args.create_config = Some("".into()),
  168. Some(CreateConfig) => error_and_exit!("Duplicate value for flag `--create-config`"),
  169. Some(Config) => error_and_exit!("error: flag without value: `--config`"),
  170. Some(TestConfig) => error_and_exit!("error: flag without value: `--test-config`"),
  171. };
  172. if let Some(config_path) = args.test_config.as_ref() {
  173. ui::conf::FileSettings::validate(config_path)?;
  174. return Ok(());
  175. }
  176. if let Some(config_path) = args.create_config.as_mut() {
  177. let config_path: PathBuf = if config_path.is_empty() {
  178. let xdg_dirs = xdg::BaseDirectories::with_prefix("meli").unwrap();
  179. xdg_dirs.place_config_file("config").map_err(|e| {
  180. MeliError::new(format!(
  181. "Cannot create configuration directory in {}:\n{}",
  182. xdg_dirs.get_config_home().display(),
  183. e
  184. ))
  185. })?
  186. } else {
  187. Path::new(config_path).to_path_buf()
  188. };
  189. if config_path.exists() {
  190. return Err(MeliError::new(format!("File `{}` already exists.\nMaybe you meant to specify another path with --create-config=PATH", config_path.display())));
  191. }
  192. ui::conf::create_config_file(&config_path)?;
  193. return Ok(());
  194. }
  195. if let Some(config_location) = args.config.as_ref() {
  196. std::env::set_var("MELI_CONFIG", config_location);
  197. }
  198. /* Create the application State. */
  199. let mut state = State::new()?;
  200. let receiver = state.receiver();
  201. let sender = state.sender();
  202. /* Catch SIGWINCH to handle terminal resizing */
  203. let signals = &[
  204. /* Catch SIGWINCH to handle terminal resizing */
  205. signal_hook::SIGWINCH,
  206. /* Catch SIGCHLD to handle embed applications status change */
  207. signal_hook::SIGCHLD,
  208. ];
  209. let signal_recvr = notify(signals, sender)?;
  210. let window = Box::new(Tabbed::new(vec![
  211. Box::new(listing::Listing::new(&state.context.accounts)),
  212. Box::new(ContactList::new(&state.context)),
  213. Box::new(StatusPanel::new()),
  214. ]));
  215. let status_bar = Box::new(StatusBar::new(window));
  216. state.register_component(status_bar);
  217. let xdg_notifications = Box::new(ui::components::notifications::XDGNotifications {});
  218. state.register_component(xdg_notifications);
  219. state.register_component(Box::new(
  220. ui::components::notifications::NotificationFilter {},
  221. ));
  222. /* Keep track of the input mode. See ui::UIMode for details */
  223. 'main: loop {
  224. state.render();
  225. 'inner: loop {
  226. /* Check if any components have sent reply events to State. */
  227. let events: Vec<UIEvent> = state.context.replies();
  228. for e in events {
  229. state.rcv_event(e);
  230. }
  231. state.redraw();
  232. /* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */
  233. crossbeam::select! {
  234. recv(receiver) -> r => {
  235. match r {
  236. Ok(ThreadEvent::Pulse) => {},
  237. _ => {debug!(&r);}
  238. }
  239. match r.unwrap() {
  240. ThreadEvent::Input(Key::Ctrl('z')) => {
  241. state.switch_to_main_screen();
  242. //_thread_handler.join().expect("Couldn't join on the associated thread");
  243. let self_pid = nix::unistd::Pid::this();
  244. nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap();
  245. state.switch_to_alternate_screen();
  246. state.restore_input();
  247. // BUG: thread sends input event after one received key
  248. state.update_size();
  249. state.render();
  250. state.redraw();
  251. },
  252. ThreadEvent::Input(Key::Ctrl('l')) => {
  253. /* Manual screen redraw */
  254. state.update_size();
  255. state.render();
  256. state.redraw();
  257. },
  258. ThreadEvent::InputRaw(raw_input @ (Key::Ctrl('l'), _)) => {
  259. /* Manual screen redraw */
  260. state.update_size();
  261. state.render();
  262. state.redraw();
  263. state.rcv_event(UIEvent::EmbedInput(raw_input));
  264. state.redraw();
  265. },
  266. ThreadEvent::Input(k) => {
  267. match state.mode {
  268. UIMode::Normal => {
  269. match k {
  270. Key::Char('q') | Key::Char('Q') => {
  271. if state.can_quit_cleanly() {
  272. drop(state);
  273. break 'main;
  274. } else {
  275. state.redraw();
  276. }
  277. },
  278. Key::Char(' ') => {
  279. state.mode = UIMode::Execute;
  280. state.rcv_event(UIEvent::ChangeMode(UIMode::Execute));
  281. state.redraw();
  282. }
  283. key => {
  284. state.rcv_event(UIEvent::Input(key));
  285. state.redraw();
  286. },
  287. }
  288. },
  289. UIMode::Insert => {
  290. match k {
  291. Key::Char('\n') | Key::Esc => {
  292. state.mode = UIMode::Normal;
  293. state.rcv_event(UIEvent::ChangeMode(UIMode::Normal));
  294. state.redraw();
  295. },
  296. k => {
  297. state.rcv_event(UIEvent::InsertInput(k));
  298. state.redraw();
  299. },
  300. }
  301. }
  302. UIMode::Execute => {
  303. match k {
  304. Key::Char('\n') | Key::Esc => {
  305. state.mode = UIMode::Normal;
  306. state.rcv_event(UIEvent::ChangeMode(UIMode::Normal));
  307. state.redraw();
  308. },
  309. k => {
  310. state.rcv_event(UIEvent::ExInput(k));
  311. state.redraw();
  312. },
  313. }
  314. },
  315. UIMode::Embed => state.redraw(),
  316. UIMode::Fork => {
  317. break 'inner; // `goto` 'reap loop, and wait on child.
  318. },
  319. }
  320. },
  321. ThreadEvent::InputRaw(raw_input) => {
  322. state.rcv_event(UIEvent::EmbedInput(raw_input));
  323. state.redraw();
  324. },
  325. ThreadEvent::RefreshMailbox(event) => {
  326. state.refresh_event(*event);
  327. state.redraw();
  328. },
  329. ThreadEvent::UIEvent(UIEvent::ChangeMode(f)) => {
  330. state.mode = f;
  331. if f == UIMode::Fork {
  332. break 'inner; // `goto` 'reap loop, and wait on child.
  333. }
  334. }
  335. ThreadEvent::UIEvent(e) => {
  336. state.rcv_event(e);
  337. state.render();
  338. },
  339. ThreadEvent::Pulse => {
  340. state.check_accounts();
  341. state.redraw();
  342. },
  343. ThreadEvent::ThreadJoin(id) => {
  344. state.join(id);
  345. },
  346. }
  347. },
  348. recv(signal_recvr) -> sig => {
  349. match sig.unwrap() {
  350. signal_hook::SIGWINCH => {
  351. if state.mode != UIMode::Fork {
  352. state.update_size();
  353. state.render();
  354. state.redraw();
  355. }
  356. },
  357. _ => {}
  358. }
  359. },
  360. }
  361. } // end of 'inner
  362. 'reap: loop {
  363. match state.try_wait_on_child() {
  364. Some(true) => {
  365. state.restore_input();
  366. state.switch_to_alternate_screen();
  367. }
  368. Some(false) => {
  369. use std::{thread, time};
  370. let ten_millis = time::Duration::from_millis(1500);
  371. thread::sleep(ten_millis);
  372. continue 'reap;
  373. }
  374. None => {
  375. state.mode = UIMode::Normal;
  376. state.render();
  377. break 'reap;
  378. }
  379. }
  380. }
  381. }
  382. Ok(())
  383. }