3f5ecdeb3cdebbb48161c268da4eb2b1df4740a2
[nihav-player.git] / sndplay / src / command.rs
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::uninitialized() };
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::uninitialized() };
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 }