2016-06-30 03:56:12 +00:00
|
|
|
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
//
|
2016-06-23 16:48:31 +00:00
|
|
|
//! Handle input from glutin
|
|
|
|
//!
|
|
|
|
//! Certain key combinations should send some escape sequence back to the pty.
|
|
|
|
//! In order to figure that out, state about which modifier keys are pressed
|
|
|
|
//! needs to be tracked. Additionally, we need a bit of a state machine to
|
|
|
|
//! determine what to do when a non-modifier key is pressed.
|
2017-02-02 06:13:08 +00:00
|
|
|
use std::borrow::Cow;
|
2017-01-02 02:28:49 +00:00
|
|
|
use std::mem;
|
2017-05-25 16:19:00 +00:00
|
|
|
use std::process::Command;
|
2017-01-15 01:53:48 +00:00
|
|
|
use std::time::Instant;
|
2017-12-29 17:19:14 +00:00
|
|
|
use std::os::unix::process::CommandExt;
|
2017-01-02 02:28:49 +00:00
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
use copypasta::{Clipboard, Load, Buffer};
|
2017-12-28 20:36:30 +00:00
|
|
|
use glutin::{ElementState, VirtualKeyCode, MouseButton, TouchPhase, MouseScrollDelta, ModifiersState};
|
2016-06-23 16:48:31 +00:00
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
use config;
|
|
|
|
use event::{ClickState, Mouse};
|
2016-12-29 16:09:29 +00:00
|
|
|
use index::{Line, Column, Side, Point};
|
2017-02-02 06:13:08 +00:00
|
|
|
use term::SizeInfo;
|
2017-01-15 01:53:48 +00:00
|
|
|
use term::mode::{self, TermMode};
|
2017-01-02 03:09:27 +00:00
|
|
|
use util::fmt::Red;
|
2016-06-23 16:48:31 +00:00
|
|
|
|
|
|
|
/// Processes input from glutin.
|
|
|
|
///
|
|
|
|
/// An escape sequence may be emitted in case specific keys or key combinations
|
|
|
|
/// are activated.
|
|
|
|
///
|
|
|
|
/// TODO also need terminal state when processing input
|
2017-02-02 06:13:08 +00:00
|
|
|
pub struct Processor<'a, A: 'a> {
|
2016-12-26 23:33:27 +00:00
|
|
|
pub key_bindings: &'a [KeyBinding],
|
|
|
|
pub mouse_bindings: &'a [MouseBinding],
|
2017-01-15 01:53:48 +00:00
|
|
|
pub mouse_config: &'a config::Mouse,
|
2017-02-02 06:13:08 +00:00
|
|
|
pub ctx: A,
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
pub trait ActionContext {
|
|
|
|
fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, B);
|
|
|
|
fn terminal_mode(&self) -> TermMode;
|
|
|
|
fn size_info(&self) -> SizeInfo;
|
|
|
|
fn copy_selection(&self, Buffer);
|
|
|
|
fn clear_selection(&mut self);
|
2017-06-16 04:43:28 +00:00
|
|
|
fn update_selection(&mut self, point: Point, side: Side);
|
|
|
|
fn simple_selection(&mut self, point: Point, side: Side);
|
|
|
|
fn semantic_selection(&mut self, point: Point);
|
|
|
|
fn line_selection(&mut self, point: Point);
|
2017-02-02 06:13:08 +00:00
|
|
|
fn mouse_mut(&mut self) -> &mut Mouse;
|
2017-01-15 01:53:48 +00:00
|
|
|
fn mouse_coords(&self) -> Option<Point>;
|
2017-07-20 17:50:50 +00:00
|
|
|
fn received_count(&mut self) -> &mut usize;
|
|
|
|
fn suppress_chars(&mut self) -> &mut bool;
|
|
|
|
fn last_modifiers(&mut self) -> &mut ModifiersState;
|
2017-10-14 17:35:56 +00:00
|
|
|
fn change_font_size(&mut self, delta: i8);
|
|
|
|
fn reset_font_size(&mut self);
|
2016-07-02 15:32:28 +00:00
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
/// Describes a state and action to take in that state
|
2016-06-23 16:48:31 +00:00
|
|
|
///
|
2016-12-17 06:13:51 +00:00
|
|
|
/// This is the shared component of `MouseBinding` and `KeyBinding`
|
2016-11-15 17:38:50 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2016-12-27 00:00:27 +00:00
|
|
|
pub struct Binding<T> {
|
2016-06-23 16:48:31 +00:00
|
|
|
/// Modifier keys required to activate binding
|
2017-07-20 17:50:50 +00:00
|
|
|
pub mods: ModifiersState,
|
2016-11-15 17:38:50 +00:00
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
/// String to send to pty if mods and mode match
|
2016-11-15 17:38:50 +00:00
|
|
|
pub action: Action,
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
/// Terminal mode required to activate binding
|
2016-11-15 17:38:50 +00:00
|
|
|
pub mode: TermMode,
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
/// excluded terminal modes where the binding won't be activated
|
2016-11-15 17:38:50 +00:00
|
|
|
pub notmode: TermMode,
|
|
|
|
|
2016-12-27 00:00:27 +00:00
|
|
|
/// This property is used as part of the trigger detection code.
|
|
|
|
///
|
|
|
|
/// For example, this might be a key like "G", or a mouse button.
|
|
|
|
pub trigger: T,
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
|
|
|
|
2016-12-27 00:00:27 +00:00
|
|
|
/// Bindings that are triggered by a keyboard key
|
|
|
|
pub type KeyBinding = Binding<VirtualKeyCode>;
|
|
|
|
|
|
|
|
/// Bindings that are triggered by a mouse button
|
|
|
|
pub type MouseBinding = Binding<MouseButton>;
|
2016-11-15 17:38:50 +00:00
|
|
|
|
2016-12-27 00:00:27 +00:00
|
|
|
impl<T: Eq> Binding<T> {
|
2016-11-15 17:38:50 +00:00
|
|
|
#[inline]
|
|
|
|
fn is_triggered_by(
|
|
|
|
&self,
|
2017-02-02 06:13:08 +00:00
|
|
|
mode: TermMode,
|
2017-07-20 17:50:50 +00:00
|
|
|
mods: &ModifiersState,
|
2016-12-27 00:00:27 +00:00
|
|
|
input: &T
|
2016-11-15 17:38:50 +00:00
|
|
|
) -> bool {
|
2016-12-27 00:00:27 +00:00
|
|
|
// Check input first since bindings are stored in one big list. This is
|
2016-11-15 17:38:50 +00:00
|
|
|
// the most likely item to fail so prioritizing it here allows more
|
|
|
|
// checks to be short circuited.
|
2016-12-27 00:00:27 +00:00
|
|
|
self.trigger == *input &&
|
2017-02-02 06:13:08 +00:00
|
|
|
self.mode_matches(&mode) &&
|
|
|
|
self.not_mode_matches(&mode) &&
|
2016-12-27 00:00:27 +00:00
|
|
|
self.mods_match(mods)
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
2016-12-27 00:00:27 +00:00
|
|
|
}
|
2016-11-15 17:38:50 +00:00
|
|
|
|
2016-12-27 00:00:27 +00:00
|
|
|
impl<T> Binding<T> {
|
|
|
|
/// Execute the action associate with this binding
|
2016-11-15 17:38:50 +00:00
|
|
|
#[inline]
|
2017-02-02 06:13:08 +00:00
|
|
|
fn execute<A: ActionContext>(&self, ctx: &mut A) {
|
2016-12-27 00:00:27 +00:00
|
|
|
self.action.execute(ctx)
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2016-12-27 00:00:27 +00:00
|
|
|
fn mode_matches(&self, mode: &TermMode) -> bool {
|
|
|
|
self.mode.is_empty() || mode.intersects(self.mode)
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2016-12-27 00:00:27 +00:00
|
|
|
fn not_mode_matches(&self, mode: &TermMode) -> bool {
|
|
|
|
self.notmode.is_empty() || !mode.intersects(self.notmode)
|
|
|
|
}
|
|
|
|
|
2017-07-20 17:50:50 +00:00
|
|
|
/// Check that two mods descriptions for equivalence
|
|
|
|
///
|
|
|
|
/// Optimized to use single check instead of four (one per modifier)
|
2016-12-27 00:00:27 +00:00
|
|
|
#[inline]
|
2017-07-20 17:50:50 +00:00
|
|
|
fn mods_match(&self, mods: &ModifiersState) -> bool {
|
|
|
|
debug_assert!(4 == mem::size_of::<ModifiersState>());
|
|
|
|
unsafe {
|
|
|
|
mem::transmute_copy::<_, u32>(&self.mods) == mem::transmute_copy::<_, u32>(mods)
|
|
|
|
}
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2016-09-27 05:22:28 +00:00
|
|
|
pub enum Action {
|
|
|
|
/// Write an escape sequence
|
2016-11-15 17:38:50 +00:00
|
|
|
Esc(String),
|
2016-09-27 05:22:28 +00:00
|
|
|
|
|
|
|
/// Paste contents of system clipboard
|
|
|
|
Paste,
|
2016-09-27 15:27:39 +00:00
|
|
|
|
2016-12-25 23:54:01 +00:00
|
|
|
// Store current selection into clipboard
|
|
|
|
Copy,
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
/// Paste contents of selection buffer
|
|
|
|
PasteSelection,
|
2017-02-06 02:32:43 +00:00
|
|
|
|
2017-10-14 17:35:56 +00:00
|
|
|
/// Increase font size
|
|
|
|
IncreaseFontSize,
|
|
|
|
|
|
|
|
/// Decrease font size
|
|
|
|
DecreaseFontSize,
|
|
|
|
|
|
|
|
/// Reset font size to the config value
|
|
|
|
ResetFontSize,
|
|
|
|
|
2017-05-25 16:19:00 +00:00
|
|
|
/// Run given command
|
|
|
|
Command(String, Vec<String>),
|
|
|
|
|
2017-02-06 02:32:43 +00:00
|
|
|
/// Quits Alacritty.
|
|
|
|
Quit,
|
2016-09-27 05:22:28 +00:00
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
impl Action {
|
|
|
|
#[inline]
|
2017-02-02 06:13:08 +00:00
|
|
|
fn execute<A: ActionContext>(&self, ctx: &mut A) {
|
2016-11-15 17:38:50 +00:00
|
|
|
match *self {
|
2016-12-26 23:33:27 +00:00
|
|
|
Action::Esc(ref s) => {
|
2017-02-02 06:13:08 +00:00
|
|
|
ctx.write_to_pty(s.clone().into_bytes())
|
2016-12-26 23:33:27 +00:00
|
|
|
},
|
2016-12-25 23:54:01 +00:00
|
|
|
Action::Copy => {
|
2017-02-02 06:13:08 +00:00
|
|
|
ctx.copy_selection(Buffer::Primary);
|
2016-12-25 23:54:01 +00:00
|
|
|
},
|
2017-01-20 05:55:38 +00:00
|
|
|
Action::Paste => {
|
|
|
|
Clipboard::new()
|
|
|
|
.and_then(|clipboard| clipboard.load_primary() )
|
|
|
|
.map(|contents| { self.paste(ctx, contents) })
|
|
|
|
.unwrap_or_else(|err| {
|
2017-10-22 15:42:40 +00:00
|
|
|
eprintln!("Error loading data from clipboard. {}", Red(err));
|
2017-01-20 05:55:38 +00:00
|
|
|
});
|
|
|
|
},
|
2016-12-26 23:33:27 +00:00
|
|
|
Action::PasteSelection => {
|
2017-01-02 03:09:27 +00:00
|
|
|
Clipboard::new()
|
2017-01-20 05:55:38 +00:00
|
|
|
.and_then(|clipboard| clipboard.load_selection() )
|
|
|
|
.map(|contents| { self.paste(ctx, contents) })
|
2016-11-15 17:38:50 +00:00
|
|
|
.unwrap_or_else(|err| {
|
2017-01-13 07:15:06 +00:00
|
|
|
warn!("Error loading data from clipboard. {}", Red(err));
|
2016-11-15 17:38:50 +00:00
|
|
|
});
|
|
|
|
},
|
2017-05-25 16:19:00 +00:00
|
|
|
Action::Command(ref program, ref args) => {
|
|
|
|
trace!("running command: {} {:?}", program, args);
|
2017-12-29 17:19:14 +00:00
|
|
|
match Command::new(program)
|
|
|
|
.args(args)
|
|
|
|
.before_exec(|| {
|
|
|
|
// Detach forked process from Alacritty. This will cause
|
|
|
|
// init or whatever to clean up child processes for us.
|
|
|
|
unsafe { ::libc::daemon(1, 0); }
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.spawn()
|
|
|
|
{
|
2017-05-25 16:19:00 +00:00
|
|
|
Ok(child) => {
|
|
|
|
debug!("spawned new proc with pid: {}", child.id());
|
|
|
|
},
|
|
|
|
Err(err) => {
|
|
|
|
warn!("couldn't run command: {}", err);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
2017-02-06 02:32:43 +00:00
|
|
|
Action::Quit => {
|
|
|
|
// FIXME should do a more graceful shutdown
|
|
|
|
::std::process::exit(0);
|
|
|
|
},
|
2017-10-14 17:35:56 +00:00
|
|
|
Action::IncreaseFontSize => {
|
|
|
|
ctx.change_font_size(1);
|
|
|
|
},
|
|
|
|
Action::DecreaseFontSize => {
|
|
|
|
ctx.change_font_size(-1);
|
|
|
|
}
|
|
|
|
Action::ResetFontSize => {
|
|
|
|
ctx.reset_font_size();
|
|
|
|
}
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-20 05:55:38 +00:00
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
fn paste<A: ActionContext>(&self, ctx: &mut A, contents: String) {
|
2017-10-12 01:52:23 +00:00
|
|
|
if ctx.terminal_mode().contains(mode::TermMode::BRACKETED_PASTE) {
|
2017-02-02 06:13:08 +00:00
|
|
|
ctx.write_to_pty(&b"\x1b[200~"[..]);
|
|
|
|
ctx.write_to_pty(contents.into_bytes());
|
|
|
|
ctx.write_to_pty(&b"\x1b[201~"[..]);
|
2017-01-20 05:55:38 +00:00
|
|
|
} else {
|
2018-01-04 01:35:22 +00:00
|
|
|
ctx.write_to_pty(contents.replace("\n","\r").into_bytes());
|
2017-01-20 05:55:38 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&'static str> for Action {
|
|
|
|
fn from(s: &'static str) -> Action {
|
|
|
|
Action::Esc(s.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
impl<'a, A: ActionContext + 'a> Processor<'a, A> {
|
2016-11-24 04:25:37 +00:00
|
|
|
#[inline]
|
2018-01-03 16:55:39 +00:00
|
|
|
pub fn mouse_moved(&mut self, x: u32, y: u32, modifiers: ModifiersState) {
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.mouse_mut().x = x;
|
|
|
|
self.ctx.mouse_mut().y = y;
|
2016-12-12 06:02:03 +00:00
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
let size_info = self.ctx.size_info();
|
|
|
|
if let Some(point) = size_info.pixels_to_coords(x as usize, y as usize) {
|
|
|
|
let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line);
|
|
|
|
let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col);
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2017-06-06 14:10:43 +00:00
|
|
|
let cell_x = (x as usize - size_info.padding_x as usize) % size_info.cell_width as usize;
|
2017-02-02 06:13:08 +00:00
|
|
|
let half_cell_width = (size_info.cell_width / 2.0) as usize;
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
let cell_side = if cell_x > half_cell_width {
|
2016-12-22 18:43:06 +00:00
|
|
|
Side::Right
|
|
|
|
} else {
|
|
|
|
Side::Left
|
|
|
|
};
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.mouse_mut().cell_side = cell_side;
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
if self.ctx.mouse_mut().left_button_state == ElementState::Pressed {
|
2017-10-12 01:52:23 +00:00
|
|
|
let report_mode = mode::TermMode::MOUSE_REPORT_CLICK | mode::TermMode::MOUSE_MOTION;
|
2018-01-03 16:55:39 +00:00
|
|
|
if modifiers.shift || !self.ctx.terminal_mode().intersects(report_mode) {
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.update_selection(Point {
|
2017-01-02 02:28:49 +00:00
|
|
|
line: point.line,
|
|
|
|
col: point.col
|
2017-02-02 06:13:08 +00:00
|
|
|
}, cell_side);
|
2017-10-12 01:52:23 +00:00
|
|
|
} else if self.ctx.terminal_mode().contains(mode::TermMode::MOUSE_MOTION)
|
2017-01-07 04:44:51 +00:00
|
|
|
// Only report motion when changing cells
|
2017-02-02 06:13:08 +00:00
|
|
|
&& (
|
|
|
|
prev_line != self.ctx.mouse_mut().line
|
|
|
|
|| prev_col != self.ctx.mouse_mut().column
|
2018-01-06 19:39:33 +00:00
|
|
|
)
|
|
|
|
{
|
|
|
|
self.mouse_report(32, ElementState::Pressed);
|
2017-01-02 02:28:49 +00:00
|
|
|
}
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
2016-12-12 06:02:03 +00:00
|
|
|
}
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2017-01-02 02:28:49 +00:00
|
|
|
pub fn normal_mouse_report(&mut self, button: u8) {
|
2017-02-02 06:13:08 +00:00
|
|
|
let (line, column) = (self.ctx.mouse_mut().line, self.ctx.mouse_mut().column);
|
2016-12-22 18:43:06 +00:00
|
|
|
|
|
|
|
if line < Line(223) && column < Column(223) {
|
|
|
|
let msg = vec![
|
2017-09-28 00:29:44 +00:00
|
|
|
b'\x1b',
|
|
|
|
b'[',
|
|
|
|
b'M',
|
2016-12-22 18:43:06 +00:00
|
|
|
32 + button,
|
|
|
|
32 + 1 + column.0 as u8,
|
|
|
|
32 + 1 + line.0 as u8,
|
|
|
|
];
|
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.write_to_pty(msg);
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
2018-01-06 19:39:33 +00:00
|
|
|
pub fn sgr_mouse_report(&mut self, button: u8, state: ElementState) {
|
2017-02-02 06:13:08 +00:00
|
|
|
let (line, column) = (self.ctx.mouse_mut().line, self.ctx.mouse_mut().column);
|
2018-01-06 19:39:33 +00:00
|
|
|
let c = match state {
|
|
|
|
ElementState::Pressed => 'M',
|
|
|
|
ElementState::Released => 'm',
|
|
|
|
};
|
2017-01-02 02:28:49 +00:00
|
|
|
|
|
|
|
let msg = format!("\x1b[<{};{};{}{}", button, column + 1, line + 1, c);
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.write_to_pty(msg.into_bytes());
|
2017-01-02 02:28:49 +00:00
|
|
|
}
|
|
|
|
|
2018-01-06 19:39:33 +00:00
|
|
|
pub fn mouse_report(&mut self, button: u8, state: ElementState) {
|
2017-10-12 01:52:23 +00:00
|
|
|
if self.ctx.terminal_mode().contains(mode::TermMode::SGR_MOUSE) {
|
2018-01-06 19:39:33 +00:00
|
|
|
self.sgr_mouse_report(button, state);
|
2017-01-02 02:28:49 +00:00
|
|
|
} else {
|
|
|
|
self.normal_mouse_report(button);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
pub fn on_mouse_double_click(&mut self) {
|
|
|
|
if let Some(point) = self.ctx.mouse_coords() {
|
|
|
|
self.ctx.semantic_selection(point);
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
pub fn on_mouse_triple_click(&mut self) {
|
|
|
|
if let Some(point) = self.ctx.mouse_coords() {
|
|
|
|
self.ctx.line_selection(point);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-03 16:52:49 +00:00
|
|
|
pub fn on_mouse_press(&mut self, modifiers: ModifiersState) {
|
2017-01-15 01:53:48 +00:00
|
|
|
let now = Instant::now();
|
|
|
|
let elapsed = self.ctx.mouse_mut().last_click_timestamp.elapsed();
|
|
|
|
self.ctx.mouse_mut().last_click_timestamp = now;
|
|
|
|
|
|
|
|
self.ctx.mouse_mut().click_state = match self.ctx.mouse_mut().click_state {
|
|
|
|
ClickState::Click if elapsed < self.mouse_config.double_click.threshold => {
|
|
|
|
self.on_mouse_double_click();
|
|
|
|
ClickState::DoubleClick
|
|
|
|
},
|
|
|
|
ClickState::DoubleClick if elapsed < self.mouse_config.triple_click.threshold => {
|
|
|
|
self.on_mouse_triple_click();
|
|
|
|
ClickState::TripleClick
|
|
|
|
},
|
|
|
|
_ => {
|
2018-01-02 16:03:47 +00:00
|
|
|
self.ctx.clear_selection();
|
2017-10-12 01:52:23 +00:00
|
|
|
let report_modes = mode::TermMode::MOUSE_REPORT_CLICK | mode::TermMode::MOUSE_MOTION;
|
2018-01-03 16:52:49 +00:00
|
|
|
if !modifiers.shift && self.ctx.terminal_mode().intersects(report_modes) {
|
2018-01-06 19:39:33 +00:00
|
|
|
self.mouse_report(0, ElementState::Pressed);
|
2017-01-15 01:53:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ClickState::Click
|
|
|
|
}
|
|
|
|
};
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2018-01-03 16:52:49 +00:00
|
|
|
pub fn on_mouse_release(&mut self, modifiers: ModifiersState) {
|
2017-12-28 20:36:30 +00:00
|
|
|
let report_modes = mode::TermMode::MOUSE_REPORT_CLICK | mode::TermMode::MOUSE_MOTION;
|
2018-01-03 16:52:49 +00:00
|
|
|
if !modifiers.shift && self.ctx.terminal_mode().intersects(report_modes)
|
2017-12-28 20:36:30 +00:00
|
|
|
{
|
2018-01-06 19:39:33 +00:00
|
|
|
self.mouse_report(3, ElementState::Released);
|
2016-12-22 18:43:06 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-12-30 01:39:30 +00:00
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.copy_selection(Buffer::Selection);
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2016-12-26 23:33:27 +00:00
|
|
|
pub fn on_mouse_wheel(&mut self, delta: MouseScrollDelta, phase: TouchPhase) {
|
2017-12-25 21:15:41 +00:00
|
|
|
let mouse_modes = mode::TermMode::MOUSE_REPORT_CLICK | mode::TermMode::MOUSE_MOTION | mode::TermMode::SGR_MOUSE;
|
|
|
|
if !self.ctx.terminal_mode().intersects(mouse_modes | mode::TermMode::ALT_SCREEN) {
|
2017-01-31 21:45:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-11-25 03:50:34 +00:00
|
|
|
match delta {
|
2016-12-12 04:00:00 +00:00
|
|
|
MouseScrollDelta::LineDelta(_columns, lines) => {
|
2017-09-02 19:29:03 +00:00
|
|
|
let to_scroll = self.ctx.mouse_mut().lines_scrolled + lines;
|
|
|
|
let code = if to_scroll > 0.0 {
|
2016-12-12 04:00:00 +00:00
|
|
|
64
|
|
|
|
} else {
|
|
|
|
65
|
|
|
|
};
|
|
|
|
|
2017-09-02 19:29:03 +00:00
|
|
|
for _ in 0..(to_scroll.abs() as usize) {
|
2017-12-25 21:15:41 +00:00
|
|
|
self.scroll_terminal(mouse_modes, code)
|
2016-12-12 04:00:00 +00:00
|
|
|
}
|
2017-12-18 16:49:57 +00:00
|
|
|
|
2017-09-02 19:29:03 +00:00
|
|
|
self.ctx.mouse_mut().lines_scrolled = to_scroll % 1.0;
|
2016-11-25 03:50:34 +00:00
|
|
|
},
|
|
|
|
MouseScrollDelta::PixelDelta(_x, y) => {
|
|
|
|
match phase {
|
|
|
|
TouchPhase::Started => {
|
|
|
|
// Reset offset to zero
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.mouse_mut().scroll_px = 0;
|
2016-11-25 03:50:34 +00:00
|
|
|
},
|
|
|
|
TouchPhase::Moved => {
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.mouse_mut().scroll_px += y as i32;
|
|
|
|
let height = self.ctx.size_info().cell_height as i32;
|
2016-11-25 03:50:34 +00:00
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
while self.ctx.mouse_mut().scroll_px.abs() >= height {
|
2017-12-25 21:15:41 +00:00
|
|
|
let code = if self.ctx.mouse_mut().scroll_px > 0 {
|
2017-12-18 19:35:57 +00:00
|
|
|
self.ctx.mouse_mut().scroll_px -= height;
|
|
|
|
64
|
|
|
|
} else {
|
|
|
|
self.ctx.mouse_mut().scroll_px += height;
|
|
|
|
65
|
|
|
|
};
|
|
|
|
|
2017-12-25 21:15:41 +00:00
|
|
|
self.scroll_terminal(mouse_modes, code)
|
2016-11-25 03:50:34 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-25 21:15:41 +00:00
|
|
|
fn scroll_terminal(&mut self, mouse_modes: TermMode, code: u8) {
|
2017-12-29 17:37:46 +00:00
|
|
|
debug_assert!(code == 64 || code == 65);
|
|
|
|
|
2017-12-25 21:15:41 +00:00
|
|
|
let faux_scrollback_lines = self.mouse_config.faux_scrollback_lines;
|
|
|
|
if self.ctx.terminal_mode().intersects(mouse_modes) {
|
2018-01-06 19:39:33 +00:00
|
|
|
self.mouse_report(code, ElementState::Pressed);
|
2017-12-25 21:15:41 +00:00
|
|
|
} else if faux_scrollback_lines > 0 {
|
|
|
|
// Faux scrolling
|
2017-12-29 17:37:46 +00:00
|
|
|
let cmd = code + 1; // 64 + 1 = A, 65 + 1 = B
|
|
|
|
let mut content = Vec::with_capacity(faux_scrollback_lines * 3);
|
|
|
|
for _ in 0..faux_scrollback_lines {
|
|
|
|
content.push(0x1b);
|
2018-01-06 01:42:55 +00:00
|
|
|
content.push(b'O');
|
2017-12-29 17:37:46 +00:00
|
|
|
content.push(cmd);
|
2017-12-25 21:15:41 +00:00
|
|
|
}
|
2017-12-29 17:37:46 +00:00
|
|
|
self.ctx.write_to_pty(content);
|
2017-12-25 21:15:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-29 16:51:49 +00:00
|
|
|
pub fn on_focus_change(&mut self, is_focused: bool) {
|
2017-10-12 01:52:23 +00:00
|
|
|
if self.ctx.terminal_mode().contains(mode::TermMode::FOCUS_IN_OUT) {
|
2017-05-29 16:51:49 +00:00
|
|
|
let chr = if is_focused {
|
|
|
|
"I"
|
|
|
|
} else {
|
|
|
|
"O"
|
|
|
|
};
|
|
|
|
|
|
|
|
let msg = format!("\x1b[{}", chr);
|
|
|
|
self.ctx.write_to_pty(msg.into_bytes());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-03 16:52:49 +00:00
|
|
|
pub fn mouse_input(&mut self, state: ElementState, button: MouseButton, modifiers: ModifiersState) {
|
2016-11-24 04:25:37 +00:00
|
|
|
if let MouseButton::Left = button {
|
2017-02-02 06:13:08 +00:00
|
|
|
let state = mem::replace(&mut self.ctx.mouse_mut().left_button_state, state);
|
|
|
|
if self.ctx.mouse_mut().left_button_state != state {
|
|
|
|
match self.ctx.mouse_mut().left_button_state {
|
2016-11-24 04:25:37 +00:00
|
|
|
ElementState::Pressed => {
|
2018-01-03 16:52:49 +00:00
|
|
|
self.on_mouse_press(modifiers);
|
2016-11-24 04:25:37 +00:00
|
|
|
},
|
|
|
|
ElementState::Released => {
|
2018-01-03 16:52:49 +00:00
|
|
|
self.on_mouse_release(modifiers);
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 05:22:28 +00:00
|
|
|
if let ElementState::Released = state {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-07-20 17:50:50 +00:00
|
|
|
self.process_mouse_bindings(&ModifiersState::default(), button);
|
2016-09-27 05:22:28 +00:00
|
|
|
}
|
|
|
|
|
2017-07-20 17:50:50 +00:00
|
|
|
/// Process key input
|
|
|
|
///
|
|
|
|
/// If a keybinding was run, returns true. Otherwise returns false.
|
2016-12-26 23:33:27 +00:00
|
|
|
pub fn process_key(
|
2016-09-27 05:22:28 +00:00
|
|
|
&mut self,
|
|
|
|
state: ElementState,
|
|
|
|
key: Option<VirtualKeyCode>,
|
2017-07-20 17:50:50 +00:00
|
|
|
mods: &ModifiersState,
|
2016-09-27 05:22:28 +00:00
|
|
|
) {
|
2017-07-20 17:50:50 +00:00
|
|
|
match (key, state) {
|
|
|
|
(Some(key), ElementState::Pressed) => {
|
|
|
|
*self.ctx.last_modifiers() = *mods;
|
|
|
|
*self.ctx.received_count() = 0;
|
|
|
|
*self.ctx.suppress_chars() = false;
|
|
|
|
|
2017-09-28 00:29:44 +00:00
|
|
|
if self.process_key_bindings(mods, key) {
|
2017-07-20 17:50:50 +00:00
|
|
|
*self.ctx.suppress_chars() = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
(_, ElementState::Released) => *self.ctx.suppress_chars() = false,
|
|
|
|
_ => ()
|
|
|
|
}
|
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
|
2017-07-20 17:50:50 +00:00
|
|
|
/// Process a received character
|
|
|
|
pub fn received_char(&mut self, c: char) {
|
|
|
|
if !*self.ctx.suppress_chars() {
|
|
|
|
self.ctx.clear_selection();
|
2016-11-12 03:59:18 +00:00
|
|
|
|
2017-07-20 17:50:50 +00:00
|
|
|
let utf8_len = c.len_utf8();
|
|
|
|
if *self.ctx.received_count() == 0 && self.ctx.last_modifiers().alt && utf8_len == 1 {
|
|
|
|
self.ctx.write_to_pty(b"\x1b".to_vec());
|
|
|
|
}
|
2017-01-03 07:47:56 +00:00
|
|
|
|
2017-07-20 17:50:50 +00:00
|
|
|
let mut bytes = Vec::with_capacity(utf8_len);
|
|
|
|
unsafe {
|
|
|
|
bytes.set_len(utf8_len);
|
|
|
|
c.encode_utf8(&mut bytes[..]);
|
2016-11-12 03:59:18 +00:00
|
|
|
}
|
2017-01-07 06:04:51 +00:00
|
|
|
|
2017-07-20 17:50:50 +00:00
|
|
|
self.ctx.write_to_pty(bytes);
|
|
|
|
|
|
|
|
*self.ctx.received_count() += 1;
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
/// Attempts to find a binding and execute its action
|
|
|
|
///
|
|
|
|
/// The provided mode, mods, and key must match what is allowed by a binding
|
|
|
|
/// for its action to be executed.
|
|
|
|
///
|
|
|
|
/// Returns true if an action is executed.
|
2017-07-20 17:50:50 +00:00
|
|
|
fn process_key_bindings(&mut self, mods: &ModifiersState, key: VirtualKeyCode) -> bool {
|
2016-12-26 23:33:27 +00:00
|
|
|
for binding in self.key_bindings {
|
2017-07-20 17:50:50 +00:00
|
|
|
if binding.is_triggered_by(self.ctx.terminal_mode(), mods, &key) {
|
2016-11-15 17:38:50 +00:00
|
|
|
// binding was triggered; run the action
|
2016-12-26 23:33:27 +00:00
|
|
|
binding.execute(&mut self.ctx);
|
2016-11-15 17:38:50 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempts to find a binding and execute its action
|
|
|
|
///
|
|
|
|
/// The provided mode, mods, and key must match what is allowed by a binding
|
|
|
|
/// for its action to be executed.
|
|
|
|
///
|
|
|
|
/// Returns true if an action is executed.
|
2017-07-20 17:50:50 +00:00
|
|
|
fn process_mouse_bindings(&mut self, mods: &ModifiersState, button: MouseButton) -> bool {
|
2016-12-26 23:33:27 +00:00
|
|
|
for binding in self.mouse_bindings {
|
2017-07-20 17:50:50 +00:00
|
|
|
if binding.is_triggered_by(self.ctx.terminal_mode(), mods, &button) {
|
2016-11-15 17:38:50 +00:00
|
|
|
// binding was triggered; run the action
|
2016-12-26 23:33:27 +00:00
|
|
|
binding.execute(&mut self.ctx);
|
2016-11-15 17:38:50 +00:00
|
|
|
return true;
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-12 03:59:18 +00:00
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
false
|
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2017-01-15 01:53:48 +00:00
|
|
|
use std::borrow::Cow;
|
|
|
|
use std::time::Duration;
|
2016-06-23 16:48:31 +00:00
|
|
|
|
2017-07-20 17:50:50 +00:00
|
|
|
use glutin::{VirtualKeyCode, Event, WindowEvent, ElementState, MouseButton, ModifiersState};
|
2016-09-27 15:27:39 +00:00
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
use term::{SizeInfo, Term, TermMode, mode};
|
|
|
|
use event::{Mouse, ClickState};
|
|
|
|
use config::{self, Config, ClickHandler};
|
|
|
|
use index::{Point, Side};
|
2017-06-17 17:29:59 +00:00
|
|
|
use selection::Selection;
|
2017-01-15 01:53:48 +00:00
|
|
|
|
|
|
|
use super::{Action, Binding, Processor};
|
2016-06-23 16:48:31 +00:00
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
const KEY: VirtualKeyCode = VirtualKeyCode::Key0;
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
#[derive(PartialEq)]
|
|
|
|
enum MultiClick {
|
|
|
|
DoubleClick,
|
|
|
|
TripleClick,
|
|
|
|
None,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ActionContext<'a> {
|
|
|
|
pub terminal: &'a mut Term,
|
2017-06-17 17:29:59 +00:00
|
|
|
pub selection: &'a mut Option<Selection>,
|
2017-01-15 01:53:48 +00:00
|
|
|
pub size_info: &'a SizeInfo,
|
|
|
|
pub mouse: &'a mut Mouse,
|
|
|
|
pub last_action: MultiClick,
|
2017-07-20 17:50:50 +00:00
|
|
|
pub received_count: usize,
|
|
|
|
pub suppress_chars: bool,
|
|
|
|
pub last_modifiers: ModifiersState,
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl <'a>super::ActionContext for ActionContext<'a> {
|
|
|
|
fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _val: B) {
|
|
|
|
// STUBBED
|
|
|
|
}
|
|
|
|
|
|
|
|
fn terminal_mode(&self) -> TermMode {
|
|
|
|
*self.terminal.mode()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn size_info(&self) -> SizeInfo {
|
|
|
|
*self.size_info
|
|
|
|
}
|
|
|
|
|
|
|
|
fn copy_selection(&self, _buffer: ::copypasta::Buffer) {
|
|
|
|
// STUBBED
|
|
|
|
}
|
|
|
|
|
2017-06-17 17:29:59 +00:00
|
|
|
fn clear_selection(&mut self) {}
|
|
|
|
fn update_selection(&mut self, _point: Point, _side: Side) {}
|
|
|
|
fn simple_selection(&mut self, _point: Point, _side: Side) {}
|
2017-01-15 01:53:48 +00:00
|
|
|
|
|
|
|
fn semantic_selection(&mut self, _point: Point) {
|
|
|
|
// set something that we can check for here
|
|
|
|
self.last_action = MultiClick::DoubleClick;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn line_selection(&mut self, _point: Point) {
|
|
|
|
self.last_action = MultiClick::TripleClick;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn mouse_coords(&self) -> Option<Point> {
|
|
|
|
self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn mouse_mut(&mut self) -> &mut Mouse {
|
|
|
|
self.mouse
|
|
|
|
}
|
2017-07-20 17:50:50 +00:00
|
|
|
fn received_count(&mut self) -> &mut usize {
|
|
|
|
&mut self.received_count
|
|
|
|
}
|
|
|
|
fn suppress_chars(&mut self) -> &mut bool {
|
|
|
|
&mut self.suppress_chars
|
|
|
|
}
|
|
|
|
fn last_modifiers(&mut self) -> &mut ModifiersState {
|
|
|
|
&mut self.last_modifiers
|
|
|
|
}
|
2017-10-14 17:35:56 +00:00
|
|
|
fn change_font_size(&mut self, _delta: i8) {
|
|
|
|
}
|
|
|
|
fn reset_font_size(&mut self) {
|
|
|
|
}
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! test_clickstate {
|
|
|
|
{
|
|
|
|
name: $name:ident,
|
|
|
|
initial_state: $initial_state:expr,
|
|
|
|
input: $input:expr,
|
|
|
|
end_state: $end_state:pat,
|
|
|
|
last_action: $last_action:expr
|
|
|
|
} => {
|
|
|
|
#[test]
|
|
|
|
fn $name() {
|
|
|
|
let config = Config::default();
|
|
|
|
let size = SizeInfo {
|
|
|
|
width: 21.0,
|
|
|
|
height: 51.0,
|
|
|
|
cell_width: 3.0,
|
|
|
|
cell_height: 3.0,
|
2017-05-06 15:45:23 +00:00
|
|
|
padding_x: 0.0,
|
|
|
|
padding_y: 0.0,
|
2017-01-15 01:53:48 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let mut terminal = Term::new(&config, size);
|
|
|
|
|
|
|
|
let mut mouse = Mouse::default();
|
|
|
|
mouse.click_state = $initial_state;
|
|
|
|
|
2017-06-17 17:29:59 +00:00
|
|
|
let mut selection = None;
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
let context = ActionContext {
|
|
|
|
terminal: &mut terminal,
|
2017-06-17 17:29:59 +00:00
|
|
|
selection: &mut selection,
|
2017-01-15 01:53:48 +00:00
|
|
|
mouse: &mut mouse,
|
|
|
|
size_info: &size,
|
|
|
|
last_action: MultiClick::None,
|
2017-07-20 17:50:50 +00:00
|
|
|
received_count: 0,
|
|
|
|
suppress_chars: false,
|
|
|
|
last_modifiers: ModifiersState::default(),
|
2017-01-15 01:53:48 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let mut processor = Processor {
|
|
|
|
ctx: context,
|
|
|
|
mouse_config: &config::Mouse {
|
|
|
|
double_click: ClickHandler {
|
|
|
|
threshold: Duration::from_millis(1000),
|
|
|
|
},
|
|
|
|
triple_click: ClickHandler {
|
|
|
|
threshold: Duration::from_millis(1000),
|
2017-12-25 21:15:41 +00:00
|
|
|
},
|
|
|
|
faux_scrollback_lines: 1,
|
2017-01-15 01:53:48 +00:00
|
|
|
},
|
|
|
|
key_bindings: &config.key_bindings()[..],
|
|
|
|
mouse_bindings: &config.mouse_bindings()[..],
|
|
|
|
};
|
|
|
|
|
2017-12-28 20:36:30 +00:00
|
|
|
if let Event::WindowEvent { event: WindowEvent::MouseInput { state, button, modifiers, .. }, .. } = $input {
|
2018-01-03 16:52:49 +00:00
|
|
|
processor.mouse_input(state, button, modifiers);
|
2017-01-15 01:53:48 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
assert!(match mouse.click_state {
|
|
|
|
$end_state => processor.ctx.last_action == $last_action,
|
|
|
|
_ => false
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
macro_rules! test_process_binding {
|
|
|
|
{
|
|
|
|
name: $name:ident,
|
|
|
|
binding: $binding:expr,
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: $triggers:expr,
|
2016-06-23 16:48:31 +00:00
|
|
|
mode: $mode:expr,
|
|
|
|
mods: $mods:expr
|
|
|
|
} => {
|
|
|
|
#[test]
|
|
|
|
fn $name() {
|
2016-12-27 00:00:27 +00:00
|
|
|
if $triggers {
|
2017-02-02 06:13:08 +00:00
|
|
|
assert!($binding.is_triggered_by($mode, &$mods, &KEY));
|
2016-12-27 00:00:27 +00:00
|
|
|
} else {
|
2017-02-02 06:13:08 +00:00
|
|
|
assert!(!$binding.is_triggered_by($mode, &$mods, &KEY));
|
2016-12-27 00:00:27 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
test_clickstate! {
|
|
|
|
name: single_click,
|
|
|
|
initial_state: ClickState::None,
|
2017-07-20 17:50:50 +00:00
|
|
|
input: Event::WindowEvent {
|
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
|
|
|
device_id: unsafe { ::std::mem::transmute_copy(&0) },
|
2017-12-29 23:49:20 +00:00
|
|
|
modifiers: ModifiersState::default(),
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
|
|
|
window_id: unsafe { ::std::mem::transmute_copy(&0) },
|
|
|
|
},
|
2017-01-15 01:53:48 +00:00
|
|
|
end_state: ClickState::Click,
|
|
|
|
last_action: MultiClick::None
|
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: double_click,
|
|
|
|
initial_state: ClickState::Click,
|
2017-07-20 17:50:50 +00:00
|
|
|
input: Event::WindowEvent {
|
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
|
|
|
device_id: unsafe { ::std::mem::transmute_copy(&0) },
|
2017-12-29 23:49:20 +00:00
|
|
|
modifiers: ModifiersState::default(),
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
|
|
|
window_id: unsafe { ::std::mem::transmute_copy(&0) },
|
|
|
|
},
|
2017-01-15 01:53:48 +00:00
|
|
|
end_state: ClickState::DoubleClick,
|
|
|
|
last_action: MultiClick::DoubleClick
|
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: triple_click,
|
|
|
|
initial_state: ClickState::DoubleClick,
|
2017-07-20 17:50:50 +00:00
|
|
|
input: Event::WindowEvent {
|
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
|
|
|
device_id: unsafe { ::std::mem::transmute_copy(&0) },
|
2017-12-29 23:49:20 +00:00
|
|
|
modifiers: ModifiersState::default(),
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
|
|
|
window_id: unsafe { ::std::mem::transmute_copy(&0) },
|
|
|
|
},
|
2017-01-15 01:53:48 +00:00
|
|
|
end_state: ClickState::TripleClick,
|
|
|
|
last_action: MultiClick::TripleClick
|
|
|
|
}
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_shiftmod_require_shift,
|
2017-10-12 01:52:23 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[1;2D"), mode: mode::TermMode::NONE, notmode: mode::TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2017-10-12 01:52:23 +00:00
|
|
|
mode: mode::TermMode::NONE,
|
2017-07-20 17:50:50 +00:00
|
|
|
mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_shift,
|
2017-10-12 01:52:23 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[1;2D"), mode: mode::TermMode::NONE, notmode: mode::TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: false,
|
2017-10-12 01:52:23 +00:00
|
|
|
mode: mode::TermMode::NONE,
|
2017-07-20 17:50:50 +00:00
|
|
|
mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_controlmod,
|
2017-10-12 01:52:23 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState { ctrl: true, shift: false, alt: false, logo: false }, action: Action::from("\x1b[1;5D"), mode: mode::TermMode::NONE, notmode: mode::TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2017-10-12 01:52:23 +00:00
|
|
|
mode: mode::TermMode::NONE,
|
2017-07-20 17:50:50 +00:00
|
|
|
mods: ModifiersState { ctrl: true, shift: false, alt: false, logo: false }
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_not_appcursor,
|
2017-10-12 01:52:23 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[D"), mode: mode::TermMode::NONE, notmode: mode::TermMode::APP_CURSOR },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2017-10-12 01:52:23 +00:00
|
|
|
mode: mode::TermMode::NONE,
|
2017-07-20 17:50:50 +00:00
|
|
|
mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_appcursormode_nomod_require_appcursor,
|
2017-10-12 01:52:23 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: mode::TermMode::APP_CURSOR, notmode: mode::TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2017-10-12 01:52:23 +00:00
|
|
|
mode: mode::TermMode::APP_CURSOR,
|
2017-07-20 17:50:50 +00:00
|
|
|
mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_appcursor,
|
2017-10-12 01:52:23 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: mode::TermMode::APP_CURSOR, notmode: mode::TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: false,
|
2017-10-12 01:52:23 +00:00
|
|
|
mode: mode::TermMode::NONE,
|
2017-07-20 17:50:50 +00:00
|
|
|
mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
2016-07-30 01:27:45 +00:00
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_appcursormode_appkeypadmode_nomod_require_appcursor,
|
2017-10-12 01:52:23 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: mode::TermMode::APP_CURSOR, notmode: mode::TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2017-10-12 01:52:23 +00:00
|
|
|
mode: mode::TermMode::APP_CURSOR | mode::TermMode::APP_KEYPAD,
|
2017-07-20 17:50:50 +00:00
|
|
|
mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
|
2016-07-30 01:27:45 +00:00
|
|
|
}
|
2016-09-27 15:27:39 +00:00
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_fail_with_extra_mods,
|
2017-10-12 01:52:23 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: true }, action: Action::from("arst"), mode: mode::TermMode::NONE, notmode: mode::TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: false,
|
2017-10-12 01:52:23 +00:00
|
|
|
mode: mode::TermMode::NONE,
|
2017-07-20 17:50:50 +00:00
|
|
|
mods: ModifiersState { shift: false, ctrl: false, alt: true, logo: true }
|
2016-09-27 15:27:39 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|