| 1 | use libc::{termios, tcgetattr, tcsetattr}; |
| 2 | use std::sync::mpsc; |
| 3 | use std::io::{Read, BufRead}; |
| 4 | use std::thread; |
| 5 | use nihav_core::frame::NATimePoint; |
| 6 | |
| 7 | #[derive(Clone,Copy,Debug,PartialEq)] |
| 8 | pub enum Command { |
| 9 | Debug, |
| 10 | Mute, |
| 11 | VolumeUp, |
| 12 | VolumeDown, |
| 13 | Pause, |
| 14 | Back(u8), |
| 15 | Forward(u8), |
| 16 | Seek(u64), |
| 17 | Repeat, |
| 18 | Next, |
| 19 | PauseDisplay, |
| 20 | ResumeDisplay, |
| 21 | Quit, |
| 22 | } |
| 23 | |
| 24 | pub struct CmdLineState { |
| 25 | orig_state: termios |
| 26 | } |
| 27 | impl CmdLineState { |
| 28 | pub fn new() -> Self { |
| 29 | let mut orig_state: termios = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; |
| 30 | unsafe { tcgetattr(0, &mut orig_state); } |
| 31 | let mut new_state = orig_state; |
| 32 | new_state.c_lflag &= !(libc::ECHO | libc::ICANON); |
| 33 | unsafe { tcsetattr(0, 0, &new_state); } |
| 34 | Self { orig_state } |
| 35 | } |
| 36 | pub fn new_normal() -> Self { |
| 37 | let mut orig_state: termios = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; |
| 38 | unsafe { tcgetattr(0, &mut orig_state); } |
| 39 | let mut new_state = orig_state; |
| 40 | new_state.c_lflag |= libc::ECHO | libc::ICANON; |
| 41 | unsafe { tcsetattr(0, 0, &new_state); } |
| 42 | Self { orig_state } |
| 43 | } |
| 44 | pub fn restore(&self) { |
| 45 | unsafe { tcsetattr(0, 0, &self.orig_state); } |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | pub fn start_reader() -> (thread::JoinHandle<()>, mpsc::Receiver<Command>) { |
| 50 | let (sender, cmd_receiver) = mpsc::sync_channel(100); |
| 51 | (thread::spawn(move || { |
| 52 | let stdin = std::io::stdin(); |
| 53 | let mut file = stdin.lock(); |
| 54 | let mut ch = [0u8; 8]; |
| 55 | loop { |
| 56 | /* |
| 57 | \e char -> alt-char (including alt-[) |
| 58 | \e [ char -> A - up B - down C - right D - left F - end H - home (or \e[OF/OH) |
| 59 | \e [ O P-S -> F1-F4 |
| 60 | \e [ num ~ -> 5 - PgUp, 6 - PgDn, 15, 17-24 - F5,F6-F12 |
| 61 | */ |
| 62 | match file.read(&mut ch) { |
| 63 | Ok(1) => { |
| 64 | match ch[0] { |
| 65 | b'\n' => { sender.send(Command::Next).unwrap(); }, |
| 66 | b' ' => { sender.send(Command::Pause).unwrap(); }, |
| 67 | b'q' | b'Q' | 0o33 => { |
| 68 | sender.send(Command::Quit).unwrap(); |
| 69 | break; |
| 70 | }, |
| 71 | b'+' => { sender.send(Command::VolumeUp).unwrap(); }, |
| 72 | b'-' => { sender.send(Command::VolumeDown).unwrap(); }, |
| 73 | b'd' | b'D' => { sender.send(Command::Debug).unwrap(); }, |
| 74 | b'm' | b'M' => { sender.send(Command::Mute).unwrap(); }, |
| 75 | b'j' | b'J' => { |
| 76 | sender.send(Command::PauseDisplay).unwrap(); |
| 77 | let cstate = CmdLineState::new_normal(); |
| 78 | // wait so that the main thread stops displaying |
| 79 | thread::sleep(std::time::Duration::from_millis(500)); |
| 80 | print!("\nJump to: "); |
| 81 | let mut str = String::new(); |
| 82 | let ret = file.read_line(&mut str); |
| 83 | cstate.restore(); |
| 84 | sender.send(Command::ResumeDisplay).unwrap(); |
| 85 | |
| 86 | if ret.is_ok() && str.len() > 1 { |
| 87 | str.pop(); // newline |
| 88 | if let Ok(NATimePoint::Milliseconds(time)) = str.parse::<NATimePoint>() { |
| 89 | sender.send(Command::Seek(time)).unwrap(); |
| 90 | } else { |
| 91 | println!("wrong time"); |
| 92 | } |
| 93 | } |
| 94 | }, |
| 95 | _ => {}, |
| 96 | }; |
| 97 | }, |
| 98 | Ok(3) => { |
| 99 | if ch[0] == 0o33 { |
| 100 | if ch[1] == b'[' { |
| 101 | match ch[2] { |
| 102 | b'D' => { sender.send(Command::Back(1)).unwrap(); }, |
| 103 | b'B' => { sender.send(Command::Back(2)).unwrap(); }, |
| 104 | b'C' => { sender.send(Command::Forward(1)).unwrap(); }, |
| 105 | b'A' => { sender.send(Command::Forward(2)).unwrap(); }, |
| 106 | b'F' => { sender.send(Command::Repeat).unwrap(); }, |
| 107 | b'H' => { sender.send(Command::Next).unwrap(); }, |
| 108 | _ => {}, |
| 109 | }; |
| 110 | } else if ch[1] == b'O' { |
| 111 | match ch[2] { |
| 112 | b'F' => { sender.send(Command::Repeat).unwrap(); }, |
| 113 | b'H' => { sender.send(Command::Next).unwrap(); }, |
| 114 | _ => {}, |
| 115 | }; |
| 116 | } |
| 117 | } |
| 118 | }, |
| 119 | Ok(4) => { |
| 120 | if ch[0] == 0o33 && ch[1] == b'[' && ch[3] == b'~' { |
| 121 | match ch[2] { |
| 122 | b'5' => { sender.send(Command::Forward(3)).unwrap(); }, |
| 123 | b'6' => { sender.send(Command::Back(3)).unwrap(); }, |
| 124 | _ => {}, |
| 125 | }; |
| 126 | } |
| 127 | }, |
| 128 | Ok(_) => {}, |
| 129 | Err(_) => break, |
| 130 | } |
| 131 | } |
| 132 | }), cmd_receiver) |
| 133 | } |