diff --git a/docs/changelog.md b/docs/changelog.md index 031a865fb..1288bbc4b 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -41,6 +41,8 @@ As features stabilize some brief notes about them will accumulate here. event. * [pane:inject_output](config/lua/pane/inject_output.md) method * [ResetTerminal](config/lua/keyassignment/ResetTerminal.md) key assignment +* Support for Utf8 mouse reporting (DECSET 1005). + [#2613](https://github.com/wez/wezterm/issues/2613) #### Fixed * Wayland: key repeat gets stuck after pressing two keys in quick succession. diff --git a/term/src/terminalstate/mod.rs b/term/src/terminalstate/mod.rs index 833e59957..e28873302 100644 --- a/term/src/terminalstate/mod.rs +++ b/term/src/terminalstate/mod.rs @@ -55,6 +55,7 @@ pub(crate) enum CharSet { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum MouseEncoding { X10, + Utf8, SGR, SgrPixels, } @@ -1760,6 +1761,25 @@ impl TerminalState { ); } + Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::Utf8Mouse)) => { + self.mouse_encoding = MouseEncoding::Utf8; + self.last_mouse_move.take(); + } + Mode::ResetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::Utf8Mouse)) => { + self.mouse_encoding = MouseEncoding::X10; + self.last_mouse_move.take(); + } + Mode::QueryDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::Utf8Mouse)) => { + self.decqrm_response( + mode, + true, + match self.mouse_encoding { + MouseEncoding::Utf8 => true, + _ => false, + }, + ); + } + Mode::SetDecPrivateMode(DecPrivateMode::Code( DecPrivateModeCode::SixelScrollsRight, )) => { @@ -1821,9 +1841,6 @@ impl TerminalState { DecPrivateModeCode::XTermAltSendsEscape, )) => {} - Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::Utf8Mouse)) - | Mode::ResetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::Utf8Mouse)) => {} - Mode::SetDecPrivateMode(DecPrivateMode::Unspecified(_)) | Mode::ResetDecPrivateMode(DecPrivateMode::Unspecified(_)) | Mode::SaveDecPrivateMode(DecPrivateMode::Unspecified(_)) diff --git a/term/src/terminalstate/mouse.rs b/term/src/terminalstate/mouse.rs index be115425c..4fd069100 100644 --- a/term/src/terminalstate/mouse.rs +++ b/term/src/terminalstate/mouse.rs @@ -4,15 +4,38 @@ use crate::TerminalState; use anyhow::bail; impl TerminalState { - /// Encode a coordinate value using X10 encoding. - /// X10 has a theoretical maximum coordinate value of 255-33, but - /// because we emit UTF-8 we are effectively capped at the maximum - /// single byte character value of 127, with coordinates capping - /// out at 127-33. - /// This isn't "fixable" in X10 encoding, applications should - /// use the superior SGR mouse encoding scheme instead. - fn legacy_mouse_coord(position: i64) -> char { - position.max(0).saturating_add(1 + 32).min(127) as u8 as char + /// Encode a coordinate value using X10 encoding or Utf8 encoding. + /// Out of bounds coords are reported as the 0 byte value. + fn encode_coord(&self, value: i64, dest: &mut Vec) { + // Convert to 1-based and offset into the printable character range + let value = value + 1 + 32; + if self.mouse_encoding == MouseEncoding::Utf8 { + if value < 0x800 { + let mut utf8 = [0; 2]; + dest.extend_from_slice( + (char::from_u32(value as u32).unwrap()) + .encode_utf8(&mut utf8) + .as_bytes(), + ); + } else { + // out of range + dest.push(0); + } + } else if value < 0x100 { + dest.push(value as u8); + } else { + // out of range + dest.push(0); + } + } + + fn encode_x10_or_utf8(&mut self, event: MouseEvent, button: i8) -> anyhow::Result<()> { + let mut buf = vec![b'\x1b', b'[', b'M', (32 + button) as u8]; + self.encode_coord(event.x as i64, &mut buf); + self.encode_coord(event.y, &mut buf); + self.writer.write(&buf)?; + self.writer.flush()?; + Ok(()) } fn mouse_report_button_number(&self, event: &MouseEvent) -> (i8, MouseButton) { @@ -76,14 +99,7 @@ impl TerminalState { )?; self.writer.flush()?; } else if self.mouse_tracking || self.button_event_mouse || self.any_event_mouse { - write!( - self.writer, - "\x1b[M{}{}{}", - (32 + button) as u8 as char, - Self::legacy_mouse_coord(event.x as i64), - Self::legacy_mouse_coord(event.y), - )?; - self.writer.flush()?; + self.encode_x10_or_utf8(event, button)?; } else if self.screen.is_alt_screen_active() { // Send cursor keys instead (equivalent to xterm's alternateScroll mode) for _ in 0..self.config.alternate_buffer_wheel_scroll_speed() { @@ -132,14 +148,7 @@ impl TerminalState { )?; self.writer.flush()?; } else { - write!( - self.writer, - "\x1b[M{}{}{}", - (32 + button) as u8 as char, - Self::legacy_mouse_coord(event.x as i64), - Self::legacy_mouse_coord(event.y), - )?; - self.writer.flush()?; + self.encode_x10_or_utf8(event, button)?; } Ok(()) @@ -176,14 +185,7 @@ impl TerminalState { self.writer.flush()?; } else { let release_button = 3; - write!( - self.writer, - "\x1b[M{}{}{}", - (32 + release_button) as u8 as char, - Self::legacy_mouse_coord(event.x as i64), - Self::legacy_mouse_coord(event.y), - )?; - self.writer.flush()?; + self.encode_x10_or_utf8(event, release_button)?; } } } @@ -242,14 +244,7 @@ impl TerminalState { )?; self.writer.flush()?; } else { - write!( - self.writer, - "\x1b[M{}{}{}", - (32 + button) as u8 as char, - Self::legacy_mouse_coord(event.x as i64), - Self::legacy_mouse_coord(event.y), - )?; - self.writer.flush()?; + self.encode_x10_or_utf8(event, button)?; } } Ok(())