1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 05:12:40 +03:00

change text cursor to fa_lock when entering passwords

There are caveats to determining this, but when we think
password entry is enabled, switch the cursor to the font-awesome
lock glyph instead of the normal cursor sprite.

fa_lock is used because it is monochrome and can thus be tinted
to the configured cursor color, and it respects blinking/easing.

refs: https://github.com/wez/wezterm/issues/2460
This commit is contained in:
Wez Furlong 2022-09-02 09:00:28 -07:00
parent 14c613a064
commit 1b9ea2de3f
10 changed files with 165 additions and 22 deletions

18
Cargo.lock generated
View File

@ -2621,6 +2621,7 @@ dependencies = [
"async-trait",
"base64",
"bintree",
"bitflags",
"chrono",
"config",
"crossbeam",
@ -2636,6 +2637,7 @@ dependencies = [
"metrics",
"mlua",
"names",
"nix 0.25.0",
"ntapi",
"percent-encoding",
"portable-pty",
@ -2749,6 +2751,20 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nix"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
dependencies = [
"autocfg",
"bitflags",
"cfg-if 1.0.0",
"libc",
"memoffset",
"pin-utils",
]
[[package]]
name = "no-std-compat"
version = "0.4.1"
@ -3401,7 +3417,7 @@ dependencies = [
"lazy_static",
"libc",
"log",
"nix 0.24.2",
"nix 0.25.0",
"serde",
"serde_derive",
"serial",

View File

@ -155,6 +155,9 @@ pub struct Config {
#[dynamic(default = "default_clean_exits")]
pub clean_exit_codes: Vec<u32>,
#[dynamic(default = "default_true")]
pub detect_password_input: bool,
/// Specifies a map of environment variables that should be set
/// when spawning commands in the local domain.
/// This is not used when working with remote domains.

View File

@ -20,6 +20,7 @@ As features stabilize some brief notes about them will accumulate here.
* [`user-var-changed` event](config/lua/window-events/user-var-changed.md) allows triggering lua code in response to user vars being changed
* `CTRL-SHIFT-U` activates a new Emoij/Unicodes/NerdFont character picker modal overlay. Fuzzy search by name or hex unicode codepoint value, or browse with keys. `CTRL-r` to cycle the browser between categories. `Enter` to select an item, copy it to the clipboard and send it to the active pane as input. `Esc` to cancel.
* `CTRL-SHIFT-P` is now a default assignment for [PaneSelect](config/lua/keyassignment/PaneSelect.md)
* Cursor now changes to a lock glyph to indicate when local echo is disabled for password entry. Detection is limited to local unix processes and cannot work with tmux. Use `detect_password_input=false` to disable this. [#2460](https://github.com/wez/wezterm/issues/2460)
#### Changed

View File

@ -0,0 +1,15 @@
# `detect_password_input = true`
*Since: nightly builds only*
When set to `true`, on unix systems, for local panes, wezterm will query the
*termios* associated with the PTY to see whether local echo is disabled and
canonical input is enabled.
If those conditions are met, then the text cursor will be changed to a lock
to give a visual cue that what you type will not be echoed to the screen.
This technique only works for local processes on unix systems, and will not
work *through* other processes that themselves use PTYs. Most notably, this
will not work with tmux or remote processes spawned via ssh.

View File

@ -11,6 +11,7 @@ anyhow = "1.0"
async-trait = "0.1"
base64 = "0.13"
bintree = { path = "../bintree" }
bitflags = "1.3"
chrono = { version = "0.4", features = ["serde"] }
config = { path = "../config" }
crossbeam = "0.8"
@ -24,6 +25,7 @@ luahelper = { path = "../luahelper" }
metrics = { version="0.17", features=["std"]}
mlua = "0.8.3"
names = { version = "0.12", default-features = false }
nix = {version="0.25", features=["term"]}
percent-encoding = "2"
portable-pty = { path = "../pty", features = ["serde_support"]}
procinfo = { path = "../procinfo" }

View File

@ -16,7 +16,7 @@ use rangeset::RangeSet;
use smol::channel::{bounded, Receiver, TryRecvError};
use std::borrow::Cow;
use std::cell::{RefCell, RefMut};
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::convert::TryInto;
use std::io::{Result as IoResult, Write};
use std::ops::Range;
@ -27,6 +27,7 @@ use termwiz::escape::{Action, DeviceControlMode};
use termwiz::input::KeyboardEncoding;
use termwiz::surface::{Line, SequenceNo};
use url::Url;
use wezterm_dynamic::Value;
use wezterm_term::color::ColorPalette;
use wezterm_term::{
Alert, AlertHandler, Clipboard, DownloadHandler, KeyCode, KeyModifiers, MouseEvent,
@ -72,6 +73,26 @@ impl Pane for LocalPane {
self.pane_id
}
fn get_metadata(&self) -> Value {
let mut map: BTreeMap<Value, Value> = BTreeMap::new();
if let Some(tio) = self.pty.borrow().get_termios() {
use nix::sys::termios::LocalFlags;
// Detect whether we might be in password input mode.
// If local echo is disabled and canonical input mode
// is enabled, then we assume that we're in some kind
// of password-entry mode.
let pw_input = !tio.local_flags.contains(LocalFlags::ECHO)
&& tio.local_flags.contains(LocalFlags::ICANON);
map.insert(
Value::String("password_input".to_string()),
Value::Bool(pw_input),
);
}
Value::Object(map.into())
}
fn get_cursor_position(&self) -> StableCursorPosition {
let mut cursor = terminal_get_cursor_position(&mut self.terminal.borrow_mut());
if self.tmux_domain.borrow().is_some() {

View File

@ -14,7 +14,7 @@ downcast-rs = "1.0"
filedescriptor = { version="0.8", path = "../filedescriptor" }
log = "0.4"
libc = "0.2"
nix = "0.24"
nix = {version="0.25", features=["term"]}
shell-words = "1.1"
serde_derive = {version="1.0", optional=true}
serde = {version="1.0", optional=true}

View File

@ -105,6 +105,13 @@ pub trait MasterPty {
/// of the process group or session leader
#[cfg(unix)]
fn process_group_leader(&self) -> Option<libc::pid_t>;
/// If applicable to the type of the tty, return the termios
/// associated with the stream
#[cfg(unix)]
fn get_termios(&self) -> Option<nix::sys::termios::Termios> {
None
}
}
/// Represents a child process spawned into the pty.

View File

@ -329,6 +329,10 @@ impl MasterPty for UnixMasterPty {
_ => None,
}
}
fn get_termios(&self) -> Option<nix::sys::termios::Termios> {
nix::sys::termios::tcgetattr(self.fd.0.as_raw_fd()).ok()
}
}
/// Represents the master end of a pty.

View File

@ -41,6 +41,7 @@ use termwiz::cell::{unicode_column_width, Blink};
use termwiz::cellcluster::CellCluster;
use termwiz::surface::{CursorShape, CursorVisibility, SequenceNo};
use wezterm_bidi::Direction;
use wezterm_dynamic::Value;
use wezterm_font::shaper::PresentationWidth;
use wezterm_font::units::{IntPixelLength, PixelLength};
use wezterm_font::{ClearShapeCache, GlyphInfo, LoadedFont};
@ -170,6 +171,7 @@ pub struct LineQuadCacheKey {
/// Only is_some() if the y value matches this row.
pub cursor: Option<StableCursorPosition>,
pub reverse_video: bool,
pub password_input: bool,
}
pub struct LineQuadCacheValue {
@ -258,6 +260,7 @@ pub struct RenderScreenLineOpenGLParams<'a> {
pub render_metrics: RenderMetrics,
pub shape_key: Option<LineToEleShapeCacheKey>,
pub password_input: bool,
}
pub struct ComputeCellFgBgParams<'a> {
@ -1117,6 +1120,7 @@ impl super::TermWindow {
use_pixel_positioning: self.config.experimental_pixel_positioning,
render_metrics: self.render_metrics,
shape_key: None,
password_input: false,
},
layers,
)?;
@ -1676,7 +1680,7 @@ impl super::TermWindow {
// Constrain to the pane width!
let selrange = selrange.start..selrange.end.min(self.dims.cols);
let (cursor, composing) = if self.cursor.y == stable_row {
let (cursor, composing, password_input) = if self.cursor.y == stable_row {
(
Some(StableCursorPosition {
y: 0,
@ -1689,15 +1693,30 @@ impl super::TermWindow {
} else {
None
},
if self.term_window.config.detect_password_input {
match self.pos.pane.get_metadata() {
Value::Object(obj) => {
match obj.get(&Value::String("password_input".to_string()))
{
Some(Value::Bool(b)) => *b,
_ => false,
}
}
_ => false,
}
} else {
false
},
)
} else {
(None, None)
(None, None, false)
};
let shape_hash = self.term_window.shape_hash_for_line(line);
let quad_key = LineQuadCacheKey {
pane_id: self.pane_id,
password_input,
pane_is_active: self.pos.is_active,
config_generation: self.term_window.config.generation(),
shape_generation: self.term_window.shape_generation,
@ -1782,6 +1801,7 @@ impl super::TermWindow {
.experimental_pixel_positioning,
render_metrics: self.term_window.render_metrics,
shape_key: Some(shape_key),
password_input,
},
&mut TripleLayerQuadAllocator::Heap(&mut buf),
)?;
@ -2613,6 +2633,7 @@ impl super::TermWindow {
if !cursor_range.is_empty() {
let (fg_color, bg_color) = if let Some(c) = params.line.get_cell(cursor_range.start) {
let attrs = c.attrs();
let bg_color = params.palette.resolve_bg(attrs.background()).to_linear();
let fg_color = resolve_fg_color_attr(
@ -2656,26 +2677,63 @@ impl super::TermWindow {
if cursor_shape.is_some() {
let mut quad = layers.allocate(0)?;
quad.set_position(
pos_x,
pos_y,
pos_x + (cursor_range.end - cursor_range.start) as f32 * cell_width,
pos_y + cell_height,
);
quad.set_hsv(hsv);
quad.set_has_color(false);
quad.set_texture(
gl_state
.glyph_cache
.borrow_mut()
.cursor_sprite(
cursor_shape,
&params.render_metrics,
(cursor_range.end - cursor_range.start) as u8,
)?
.texture_coords(),
);
let mut draw_basic = true;
if params.password_input {
let attrs = params
.line
.get_cell(cursor_range.start)
.map(|cell| cell.attrs().clone())
.unwrap_or_else(|| CellAttributes::blank());
let glyph = self.resolve_lock_glyph(
&TextStyle::default(),
&attrs,
params.font.as_ref(),
gl_state,
&params.render_metrics,
)?;
if let Some(sprite) = &glyph.texture {
let width = sprite.coords.size.width as f32 * glyph.scale as f32;
let height =
sprite.coords.size.height as f32 * glyph.scale as f32 * height_scale;
let pos_y = pos_y
+ cell_height
+ (params.render_metrics.descender.get() as f32
- (glyph.y_offset + glyph.bearing_y).get() as f32)
* height_scale;
let pos_x = pos_x + (glyph.x_offset + glyph.bearing_x).get() as f32;
quad.set_position(pos_x, pos_y, pos_x + width, pos_y + height);
quad.set_texture(sprite.texture_coords());
draw_basic = false;
}
}
if draw_basic {
quad.set_position(
pos_x,
pos_y,
pos_x + (cursor_range.end - cursor_range.start) as f32 * cell_width,
pos_y + cell_height,
);
quad.set_texture(
gl_state
.glyph_cache
.borrow_mut()
.cursor_sprite(
cursor_shape,
&params.render_metrics,
(cursor_range.end - cursor_range.start) as u8,
)?
.texture_coords(),
);
}
quad.set_fg_color(cursor_border_color);
quad.set_alt_color_and_mix_value(cursor_border_color_alt, cursor_border_mix);
@ -2968,6 +3026,22 @@ impl super::TermWindow {
Ok(())
}
fn resolve_lock_glyph(
&self,
style: &TextStyle,
attrs: &CellAttributes,
font: Option<&Rc<LoadedFont>>,
gl_state: &RenderState,
metrics: &RenderMetrics,
) -> anyhow::Result<Rc<CachedGlyph<SrgbTexture2d>>> {
let fa_lock = "\u{f023}";
let line = Line::from_text(fa_lock, attrs, 0, None);
let cluster = line.cluster(None);
let shape_info =
self.cached_cluster_shape(style, &cluster[0], gl_state, &line, font, metrics)?;
Ok(Rc::clone(&shape_info[0].glyph))
}
pub fn populate_block_quad(
&self,
block: BlockKey,