Output mouse events from ncurses backend

This commit is contained in:
Alexandre Bury 2017-10-10 17:50:44 -07:00
parent 976728fd25
commit 9497ded014

View File

@ -2,23 +2,27 @@ extern crate ncurses;
use self::super::find_closest; use self::super::find_closest;
use backend; use backend;
use event::{Event, Key}; use event::{Event, Key, MouseButton, MouseEvent};
use std::cell::{RefCell, Cell}; use std::cell::{Cell, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
use theme::{Color, ColorPair, Effect}; use theme::{Color, ColorPair, Effect};
use utf8; use utf8;
use vec::Vec2;
pub struct Concrete { pub struct Concrete {
current_style: Cell<ColorPair>, current_style: Cell<ColorPair>,
pairs: RefCell<HashMap<ColorPair, i16>>, pairs: RefCell<HashMap<ColorPair, i16>>,
last_mouse_button: Option<MouseButton>,
event_queue: Vec<Event>,
} }
impl Concrete { impl Concrete {
/// Save a new color pair. /// Save a new color pair.
fn insert_color(&self, pairs: &mut HashMap<ColorPair, i16>, fn insert_color(
pair: ColorPair) &self, pairs: &mut HashMap<ColorPair, i16>, pair: ColorPair
-> i16 { ) -> i16 {
let n = 1 + pairs.len() as i16; let n = 1 + pairs.len() as i16;
let target = if ncurses::COLOR_PAIRS() > n as i32 { let target = if ncurses::COLOR_PAIRS() > n as i32 {
// We still have plenty of space for everyone. // We still have plenty of space for everyone.
@ -31,15 +35,16 @@ impl Concrete {
target target
}; };
pairs.insert(pair, target); pairs.insert(pair, target);
ncurses::init_pair(target, ncurses::init_pair(
find_closest(&pair.front), target,
find_closest(&pair.back)); find_closest(&pair.front),
find_closest(&pair.back),
);
target target
} }
/// Checks the pair in the cache, or re-define a color if needed. /// Checks the pair in the cache, or re-define a color if needed.
fn get_or_create(&self, pair: ColorPair) -> i16 { fn get_or_create(&self, pair: ColorPair) -> i16 {
let mut pairs = self.pairs.borrow_mut(); let mut pairs = self.pairs.borrow_mut();
// Find if we have this color in stock // Find if we have this color in stock
@ -52,33 +57,247 @@ impl Concrete {
} }
fn set_colors(&self, pair: ColorPair) { fn set_colors(&self, pair: ColorPair) {
let i = self.get_or_create(pair); let i = self.get_or_create(pair);
self.current_style.set(pair); self.current_style.set(pair);
let style = ncurses::COLOR_PAIR(i); let style = ncurses::COLOR_PAIR(i);
ncurses::attron(style); ncurses::attron(style);
} }
fn parse_mouse_event(&mut self) -> Event {
let mut mevent = ncurses::MEVENT {
id: 0,
x: 0,
y: 0,
z: 0,
bstate: 0,
};
if ncurses::getmouse(&mut mevent as *mut ncurses::MEVENT)
== ncurses::OK
{
// eprintln!("{:032b}", mevent.bstate);
// Currently unused
let _shift = (mevent.bstate & ncurses::BUTTON_SHIFT as u32) != 0;
let _alt = (mevent.bstate & ncurses::BUTTON_ALT as u32) != 0;
let _ctrl = (mevent.bstate & ncurses::BUTTON_CTRL as u32) != 0;
mevent.bstate &= !(ncurses::BUTTON_SHIFT | ncurses::BUTTON_ALT
| ncurses::BUTTON_CTRL) as u32;
let make_event = |event| {
Event::Mouse {
offset: Vec2::zero(),
position: Vec2::new(mevent.x as usize, mevent.y as usize),
event: event,
}
};
if mevent.bstate == ncurses::REPORT_MOUSE_POSITION as u32 {
// The event is either a mouse drag event,
// or a weird double-release event. :S
self.last_mouse_button
.map(|b| MouseEvent::Hold(b))
.map(&make_event)
.unwrap_or(Event::Unknown(vec![]))
} else {
// Identify the button
let mut bare_event = (mevent.bstate & ((1 << 25) - 1)) as i32;
let mut event = None;
while bare_event != 0 {
let single_event = 1 << bare_event.trailing_zeros();
bare_event ^= single_event;
// Process single_event
get_event(single_event, |e| if event.is_none() {
event = Some(e);
} else {
self.event_queue.push(make_event(e));
});
}
if let Some(event) = event {
if let Some(btn) = event.button() {
self.last_mouse_button = Some(btn);
}
make_event(event)
} else {
debug!("No event parsed?...");
Event::Unknown(vec![])
}
}
} else {
debug!("Ncurses event not recognized.");
Event::Unknown(vec![])
}
}
fn parse_ncurses_char(&mut self, ch: i32) -> Event {
match ch {
// Value sent by ncurses when nothing happens
-1 => Event::Refresh,
// Values under 256 are chars and control values
//
// Tab is '\t'
9 => Event::Key(Key::Tab),
// Treat '\n' and the numpad Enter the same
10 | ncurses::KEY_ENTER => Event::Key(Key::Enter),
// This is the escape key when pressed by itself.
// When used for control sequences, it should have been caught earlier.
27 => Event::Key(Key::Esc),
// `Backspace` sends 127, but Ctrl-H sends `Backspace`
127 | ncurses::KEY_BACKSPACE => Event::Key(Key::Backspace),
410 => Event::WindowResize,
// Values 512 and above are probably extensions
// Those keys don't seem to be documented...
520 => Event::Alt(Key::Del),
521 => Event::AltShift(Key::Del),
522 => Event::Ctrl(Key::Del),
523 => Event::CtrlShift(Key::Del),
//
// 524?
526 => Event::Alt(Key::Down),
527 => Event::AltShift(Key::Down),
528 => Event::Ctrl(Key::Down),
529 => Event::CtrlShift(Key::Down),
530 => Event::CtrlAlt(Key::Down),
531 => Event::Alt(Key::End),
532 => Event::AltShift(Key::End),
533 => Event::Ctrl(Key::End),
534 => Event::CtrlShift(Key::End),
535 => Event::CtrlAlt(Key::End),
536 => Event::Alt(Key::Home),
537 => Event::AltShift(Key::Home),
538 => Event::Ctrl(Key::Home),
539 => Event::CtrlShift(Key::Home),
540 => Event::CtrlAlt(Key::Home),
541 => Event::Alt(Key::Ins),
542 => Event::AltShift(Key::Ins),
543 => Event::Ctrl(Key::Ins),
// 544: CtrlShiftIns?
545 => Event::CtrlAlt(Key::Ins),
546 => Event::Alt(Key::Left),
547 => Event::AltShift(Key::Left),
548 => Event::Ctrl(Key::Left),
549 => Event::CtrlShift(Key::Left),
550 => Event::CtrlAlt(Key::Left),
551 => Event::Alt(Key::PageDown),
552 => Event::AltShift(Key::PageDown),
553 => Event::Ctrl(Key::PageDown),
554 => Event::CtrlShift(Key::PageDown),
555 => Event::CtrlAlt(Key::PageDown),
556 => Event::Alt(Key::PageUp),
557 => Event::AltShift(Key::PageUp),
558 => Event::Ctrl(Key::PageUp),
559 => Event::CtrlShift(Key::PageUp),
560 => Event::CtrlAlt(Key::PageUp),
561 => Event::Alt(Key::Right),
562 => Event::AltShift(Key::Right),
563 => Event::Ctrl(Key::Right),
564 => Event::CtrlShift(Key::Right),
565 => Event::CtrlAlt(Key::Right),
// 566?
567 => Event::Alt(Key::Up),
568 => Event::AltShift(Key::Up),
569 => Event::Ctrl(Key::Up),
570 => Event::CtrlShift(Key::Up),
571 => Event::CtrlAlt(Key::Up),
ncurses::KEY_MOUSE => self.parse_mouse_event(),
ncurses::KEY_B2 => Event::Key(Key::NumpadCenter),
ncurses::KEY_DC => Event::Key(Key::Del),
ncurses::KEY_IC => Event::Key(Key::Ins),
ncurses::KEY_BTAB => Event::Shift(Key::Tab),
ncurses::KEY_SLEFT => Event::Shift(Key::Left),
ncurses::KEY_SRIGHT => Event::Shift(Key::Right),
ncurses::KEY_LEFT => Event::Key(Key::Left),
ncurses::KEY_RIGHT => Event::Key(Key::Right),
ncurses::KEY_UP => Event::Key(Key::Up),
ncurses::KEY_DOWN => Event::Key(Key::Down),
ncurses::KEY_SR => Event::Shift(Key::Up),
ncurses::KEY_SF => Event::Shift(Key::Down),
ncurses::KEY_PPAGE => Event::Key(Key::PageUp),
ncurses::KEY_NPAGE => Event::Key(Key::PageDown),
ncurses::KEY_HOME => Event::Key(Key::Home),
ncurses::KEY_END => Event::Key(Key::End),
ncurses::KEY_SHOME => Event::Shift(Key::Home),
ncurses::KEY_SEND => Event::Shift(Key::End),
ncurses::KEY_SDC => Event::Shift(Key::Del),
ncurses::KEY_SNEXT => Event::Shift(Key::PageDown),
ncurses::KEY_SPREVIOUS => Event::Shift(Key::PageUp),
// All Fn keys use the same enum with associated number
f @ ncurses::KEY_F1...ncurses::KEY_F12 => {
Event::Key(Key::from_f((f - ncurses::KEY_F0) as u8))
}
f @ 277...288 => Event::Shift(Key::from_f((f - 276) as u8)),
f @ 289...300 => Event::Ctrl(Key::from_f((f - 288) as u8)),
f @ 301...312 => Event::CtrlShift(Key::from_f((f - 300) as u8)),
f @ 313...324 => Event::Alt(Key::from_f((f - 312) as u8)),
// Values 8-10 (H,I,J) are used by other commands,
// so we probably won't receive them. Meh~
c @ 1...25 => Event::CtrlChar((b'a' + (c - 1) as u8) as char),
other => {
// Split the i32 into 4 bytes
Event::Unknown(
(0..4)
.map(|i| ((other >> (8 * i)) & 0xFF) as u8)
.collect(),
)
}
}
}
} }
impl backend::Backend for Concrete { impl backend::Backend for Concrete {
fn init() -> Self { fn init() -> Self {
// Change the locale. For some reasons it's mandatory to get some UTF-8 support.
ncurses::setlocale(ncurses::LcCategory::all, "");
// The delay is the time ncurses wait after pressing ESC // The delay is the time ncurses wait after pressing ESC
// to see if it's an escape sequence. // to see if it's an escape sequence.
// Default delay is way too long. 25 is imperceptible yet works fine. // Default delay is way too long. 25 is imperceptible yet works fine.
ncurses::setlocale(ncurses::LcCategory::all, "");
::std::env::set_var("ESCDELAY", "25"); ::std::env::set_var("ESCDELAY", "25");
ncurses::initscr(); ncurses::initscr();
ncurses::keypad(ncurses::stdscr(), true); ncurses::keypad(ncurses::stdscr(), true);
// This disables mouse click detection,
// and provides 0-delay access to mouse presses.
ncurses::mouseinterval(0);
// Listen to all mouse events.
ncurses::mousemask(
(ncurses::ALL_MOUSE_EVENTS | ncurses::REPORT_MOUSE_POSITION)
as u32,
None,
);
ncurses::noecho(); ncurses::noecho();
ncurses::cbreak(); ncurses::cbreak();
ncurses::start_color(); ncurses::start_color();
// Pick up background and text color from the terminal theme.
ncurses::use_default_colors(); ncurses::use_default_colors();
// No cursor
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE); ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
// This asks the terminal to provide us with mouse drag events
// (Mouse move when a button is pressed).
// Replacing 1002 with 1003 would give us ANY mouse move.
println!("\x1B[?1002h");
Concrete { Concrete {
current_style: Cell::new(ColorPair::from_256colors(0, 0)), current_style: Cell::new(ColorPair::from_256colors(0, 0)),
pairs: RefCell::new(HashMap::new()), pairs: RefCell::new(HashMap::new()),
last_mouse_button: None,
event_queue: Vec::new(),
} }
} }
@ -94,6 +313,7 @@ impl backend::Backend for Concrete {
} }
fn finish(&mut self) { fn finish(&mut self) {
println!("\x1B[?1002l");
ncurses::endwin(); ncurses::endwin();
} }
@ -123,9 +343,9 @@ impl backend::Backend for Concrete {
fn clear(&self, color: Color) { fn clear(&self, color: Color) {
let id = self.get_or_create(ColorPair { let id = self.get_or_create(ColorPair {
front: color, front: color,
back: color, back: color,
}); });
ncurses::wbkgd(ncurses::stdscr(), ncurses::COLOR_PAIR(id)); ncurses::wbkgd(ncurses::stdscr(), ncurses::COLOR_PAIR(id));
ncurses::clear(); ncurses::clear();
@ -140,16 +360,19 @@ impl backend::Backend for Concrete {
} }
fn poll_event(&mut self) -> Event { fn poll_event(&mut self) -> Event {
let ch: i32 = ncurses::getch(); self.event_queue.pop().unwrap_or_else(|| {
let ch: i32 = ncurses::getch();
// Is it a UTF-8 starting point? // Is it a UTF-8 starting point?
if 32 <= ch && ch <= 255 && ch != 127 { if 32 <= ch && ch <= 255 && ch != 127 {
Event::Char(utf8::read_char(ch as u8, Event::Char(
|| Some(ncurses::getch() as u8)) utf8::read_char(ch as u8, || Some(ncurses::getch() as u8))
.unwrap()) .unwrap(),
} else { )
parse_ncurses_char(ch) } else {
} self.parse_ncurses_char(ch)
}
})
} }
fn set_refresh_rate(&mut self, fps: u32) { fn set_refresh_rate(&mut self, fps: u32) {
@ -162,126 +385,85 @@ impl backend::Backend for Concrete {
} }
/// Returns the Key enum corresponding to the given ncurses event. /// Returns the Key enum corresponding to the given ncurses event.
fn parse_ncurses_char(ch: i32) -> Event { fn get_button(bare_event: i32) -> MouseButton {
match ch { match bare_event {
// Value sent by ncurses when nothing happens ncurses::BUTTON1_RELEASED |
-1 => Event::Refresh, ncurses::BUTTON1_PRESSED |
ncurses::BUTTON1_CLICKED |
// Values under 256 are chars and control values ncurses::BUTTON1_DOUBLE_CLICKED |
// ncurses::BUTTON1_TRIPLE_CLICKED => MouseButton::Left,
// Tab is '\t' ncurses::BUTTON2_RELEASED |
9 => Event::Key(Key::Tab), ncurses::BUTTON2_PRESSED |
// Treat '\n' and the numpad Enter the same ncurses::BUTTON2_CLICKED |
10 | ncurses::BUTTON2_DOUBLE_CLICKED |
ncurses::KEY_ENTER => Event::Key(Key::Enter), ncurses::BUTTON2_TRIPLE_CLICKED => MouseButton::Middle,
// This is the escape key when pressed by itself. ncurses::BUTTON3_RELEASED |
// When used for control sequences, it should have been caught earlier. ncurses::BUTTON3_PRESSED |
27 => Event::Key(Key::Esc), ncurses::BUTTON3_CLICKED |
// `Backspace` sends 127, but Ctrl-H sends `Backspace` ncurses::BUTTON3_DOUBLE_CLICKED |
127 | ncurses::BUTTON3_TRIPLE_CLICKED => MouseButton::Right,
ncurses::KEY_BACKSPACE => Event::Key(Key::Backspace), ncurses::BUTTON4_RELEASED |
ncurses::BUTTON4_PRESSED |
410 => Event::WindowResize, ncurses::BUTTON4_CLICKED |
ncurses::BUTTON4_DOUBLE_CLICKED |
// Values 512 and above are probably extensions ncurses::BUTTON4_TRIPLE_CLICKED => MouseButton::Button4,
// Those keys don't seem to be documented... ncurses::BUTTON5_RELEASED |
520 => Event::Alt(Key::Del), ncurses::BUTTON5_PRESSED |
521 => Event::AltShift(Key::Del), ncurses::BUTTON5_CLICKED |
522 => Event::Ctrl(Key::Del), ncurses::BUTTON5_DOUBLE_CLICKED |
523 => Event::CtrlShift(Key::Del), ncurses::BUTTON5_TRIPLE_CLICKED => MouseButton::Button5,
// _ => MouseButton::Other,
// 524? }
526 => Event::Alt(Key::Down), }
527 => Event::AltShift(Key::Down),
528 => Event::Ctrl(Key::Down), /// Parse the given code into one or more event.
529 => Event::CtrlShift(Key::Down), ///
530 => Event::CtrlAlt(Key::Down), /// If the given event code should expend into multiple events
/// (for instance click expends into PRESS + RELEASE),
531 => Event::Alt(Key::End), /// the returned Vec will include those queued events.
532 => Event::AltShift(Key::End), ///
533 => Event::Ctrl(Key::End), /// The main event is returned separately to avoid allocation in most cases.
534 => Event::CtrlShift(Key::End), fn get_event<F>(bare_event: i32, mut f: F)
535 => Event::CtrlAlt(Key::End), where
F: FnMut(MouseEvent),
536 => Event::Alt(Key::Home), {
537 => Event::AltShift(Key::Home), let button = get_button(bare_event);
538 => Event::Ctrl(Key::Home), match bare_event {
539 => Event::CtrlShift(Key::Home), ncurses::BUTTON4_PRESSED => f(MouseEvent::WheelUp),
540 => Event::CtrlAlt(Key::Home), ncurses::BUTTON5_PRESSED => f(MouseEvent::WheelDown),
ncurses::BUTTON1_RELEASED |
541 => Event::Alt(Key::Ins), ncurses::BUTTON2_RELEASED |
542 => Event::AltShift(Key::Ins), ncurses::BUTTON3_RELEASED |
543 => Event::Ctrl(Key::Ins), ncurses::BUTTON4_RELEASED |
// 544: CtrlShiftIns? ncurses::BUTTON5_RELEASED => f(MouseEvent::Release(button)),
545 => Event::CtrlAlt(Key::Ins), ncurses::BUTTON1_PRESSED |
ncurses::BUTTON2_PRESSED |
546 => Event::Alt(Key::Left), ncurses::BUTTON3_PRESSED => f(MouseEvent::Press(button)),
547 => Event::AltShift(Key::Left), ncurses::BUTTON1_CLICKED |
548 => Event::Ctrl(Key::Left), ncurses::BUTTON2_CLICKED |
549 => Event::CtrlShift(Key::Left), ncurses::BUTTON3_CLICKED |
550 => Event::CtrlAlt(Key::Left), ncurses::BUTTON4_CLICKED |
ncurses::BUTTON5_CLICKED => {
551 => Event::Alt(Key::PageDown), f(MouseEvent::Press(button));
552 => Event::AltShift(Key::PageDown), f(MouseEvent::Release(button));
553 => Event::Ctrl(Key::PageDown), }
554 => Event::CtrlShift(Key::PageDown), // Well, we disabled click detection
555 => Event::CtrlAlt(Key::PageDown), ncurses::BUTTON1_DOUBLE_CLICKED |
ncurses::BUTTON2_DOUBLE_CLICKED |
556 => Event::Alt(Key::PageUp), ncurses::BUTTON3_DOUBLE_CLICKED |
557 => Event::AltShift(Key::PageUp), ncurses::BUTTON4_DOUBLE_CLICKED |
558 => Event::Ctrl(Key::PageUp), ncurses::BUTTON5_DOUBLE_CLICKED => for _ in 0..2 {
559 => Event::CtrlShift(Key::PageUp), f(MouseEvent::Press(button));
560 => Event::CtrlAlt(Key::PageUp), f(MouseEvent::Release(button));
},
561 => Event::Alt(Key::Right), ncurses::BUTTON1_TRIPLE_CLICKED |
562 => Event::AltShift(Key::Right), ncurses::BUTTON2_TRIPLE_CLICKED |
563 => Event::Ctrl(Key::Right), ncurses::BUTTON3_TRIPLE_CLICKED |
564 => Event::CtrlShift(Key::Right), ncurses::BUTTON4_TRIPLE_CLICKED |
565 => Event::CtrlAlt(Key::Right), ncurses::BUTTON5_TRIPLE_CLICKED => for _ in 0..3 {
// 566? f(MouseEvent::Press(button));
567 => Event::Alt(Key::Up), f(MouseEvent::Release(button));
568 => Event::AltShift(Key::Up), },
569 => Event::Ctrl(Key::Up), _ => debug!("Unknown event: {:032b}", bare_event),
570 => Event::CtrlShift(Key::Up),
571 => Event::CtrlAlt(Key::Up),
ncurses::KEY_B2 => Event::Key(Key::NumpadCenter),
ncurses::KEY_DC => Event::Key(Key::Del),
ncurses::KEY_IC => Event::Key(Key::Ins),
ncurses::KEY_BTAB => Event::Shift(Key::Tab),
ncurses::KEY_SLEFT => Event::Shift(Key::Left),
ncurses::KEY_SRIGHT => Event::Shift(Key::Right),
ncurses::KEY_LEFT => Event::Key(Key::Left),
ncurses::KEY_RIGHT => Event::Key(Key::Right),
ncurses::KEY_UP => Event::Key(Key::Up),
ncurses::KEY_DOWN => Event::Key(Key::Down),
ncurses::KEY_SR => Event::Shift(Key::Up),
ncurses::KEY_SF => Event::Shift(Key::Down),
ncurses::KEY_PPAGE => Event::Key(Key::PageUp),
ncurses::KEY_NPAGE => Event::Key(Key::PageDown),
ncurses::KEY_HOME => Event::Key(Key::Home),
ncurses::KEY_END => Event::Key(Key::End),
ncurses::KEY_SHOME => Event::Shift(Key::Home),
ncurses::KEY_SEND => Event::Shift(Key::End),
ncurses::KEY_SDC => Event::Shift(Key::Del),
ncurses::KEY_SNEXT => Event::Shift(Key::PageDown),
ncurses::KEY_SPREVIOUS => Event::Shift(Key::PageUp),
// All Fn keys use the same enum with associated number
f @ ncurses::KEY_F1...ncurses::KEY_F12 => {
Event::Key(Key::from_f((f - ncurses::KEY_F0) as u8))
}
f @ 277...288 => Event::Shift(Key::from_f((f - 276) as u8)),
f @ 289...300 => Event::Ctrl(Key::from_f((f - 288) as u8)),
f @ 301...312 => Event::CtrlShift(Key::from_f((f - 300) as u8)),
f @ 313...324 => Event::Alt(Key::from_f((f - 312) as u8)),
// Values 8-10 (H,I,J) are used by other commands,
// so we probably won't receive them. Meh~
c @ 1...25 => Event::CtrlChar((b'a' + (c - 1) as u8) as char),
other => {
// Split the i32 into 4 bytes
Event::Unknown((0..4)
.map(|i| ((other >> (8 * i)) & 0xFF) as u8)
.collect())
}
} }
} }