parent
07222706c7
commit
a7cd7806f2
|
|
@ -1547,10 +1547,7 @@ impl AppState {
|
|||
else {
|
||||
return false;
|
||||
};
|
||||
if !rt
|
||||
.input_state()
|
||||
.is_some_and(crate::pane::InputState::mouse_reporting_enabled)
|
||||
{
|
||||
if rt.wheel_routing() != Some(crate::pane::WheelRouting::MouseReport) {
|
||||
return false;
|
||||
}
|
||||
rt.scroll_reset();
|
||||
|
|
|
|||
|
|
@ -2120,14 +2120,7 @@ impl PaneRuntime {
|
|||
}
|
||||
|
||||
pub fn wheel_routing(&self) -> Option<WheelRouting> {
|
||||
let input_state = self.input_state()?;
|
||||
Some(if input_state.mouse_reporting_enabled() {
|
||||
WheelRouting::MouseReport
|
||||
} else if input_state.alternate_screen && input_state.mouse_alternate_scroll {
|
||||
WheelRouting::AlternateScroll
|
||||
} else {
|
||||
WheelRouting::HostScroll
|
||||
})
|
||||
self.terminal.wheel_routing()
|
||||
}
|
||||
|
||||
pub fn encode_mouse_button(
|
||||
|
|
|
|||
|
|
@ -183,6 +183,10 @@ impl PaneTerminal {
|
|||
self.ghostty.input_state()
|
||||
}
|
||||
|
||||
pub fn wheel_routing(&self) -> Option<crate::pane::WheelRouting> {
|
||||
self.ghostty.wheel_routing()
|
||||
}
|
||||
|
||||
pub fn cursor_state(&self) -> Option<TerminalCursorState> {
|
||||
self.ghostty.cursor_state()
|
||||
}
|
||||
|
|
@ -870,6 +874,29 @@ impl GhosttyPaneTerminal {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn wheel_routing(&self) -> Option<crate::pane::WheelRouting> {
|
||||
let Ok(core) = self.core.lock() else {
|
||||
return None;
|
||||
};
|
||||
let alternate_screen =
|
||||
core.terminal.active_screen().ok()? == crate::ghostty::ActiveScreen::Alternate;
|
||||
let mouse_alternate_scroll = core
|
||||
.terminal
|
||||
.mode_get(crate::ghostty::MODE_MOUSE_ALTERNATE_SCROLL)
|
||||
.ok()?;
|
||||
let mouse_reporting = core.terminal.mode_get(MODE_MOUSE_ANY_MOTION).ok()?
|
||||
|| core.terminal.mode_get(MODE_MOUSE_BUTTON_MOTION).ok()?
|
||||
|| core.terminal.mode_get(MODE_MOUSE_PRESS_RELEASE).ok()?
|
||||
|| core.terminal.mode_get(MODE_MOUSE_X10).ok()?;
|
||||
Some(if mouse_reporting {
|
||||
crate::pane::WheelRouting::MouseReport
|
||||
} else if alternate_screen && mouse_alternate_scroll {
|
||||
crate::pane::WheelRouting::AlternateScroll
|
||||
} else {
|
||||
crate::pane::WheelRouting::HostScroll
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cursor_state(&self) -> Option<TerminalCursorState> {
|
||||
let mut core = self.core.lock().ok()?;
|
||||
let GhosttyPaneCore {
|
||||
|
|
|
|||
|
|
@ -2795,7 +2795,7 @@ impl HeadlessServer {
|
|||
return false;
|
||||
};
|
||||
let prepare_started = crate::render_prof::timer();
|
||||
let Some(prepared) = client.render_state.prepare_frame(&frame) else {
|
||||
let Some(prepared) = client.render_state.prepare_frame(frame) else {
|
||||
client.render_pending = false;
|
||||
crate::render_prof::event("retained_send.skip_identical");
|
||||
crate::render_prof::duration_since("retained_send.prepare_frame", prepare_started);
|
||||
|
|
@ -2831,7 +2831,7 @@ impl HeadlessServer {
|
|||
match writer.render.try_send(serialized) {
|
||||
Ok(()) => {
|
||||
client.render_pending = false;
|
||||
client.render_state.commit_sent_frame(frame, prepared);
|
||||
client.render_state.commit_sent_frame(prepared);
|
||||
crate::render_prof::event("retained_send.sent");
|
||||
crate::render_prof::duration_since("retained_send.try_send", send_started);
|
||||
true
|
||||
|
|
@ -3007,21 +3007,21 @@ impl HeadlessServer {
|
|||
commit_graphics_cache = false;
|
||||
}
|
||||
|
||||
let max_frame_size = if frame.graphics.is_empty() {
|
||||
MAX_FRAME_SIZE
|
||||
} else {
|
||||
MAX_GRAPHICS_FRAME_SIZE
|
||||
};
|
||||
let has_graphics = !frame.graphics.is_empty();
|
||||
let prepare_started = crate::render_prof::timer();
|
||||
let Some(mut prepared) = client.render_state.prepare_frame(&frame) else {
|
||||
let Some(mut prepared) = client.render_state.prepare_frame(frame) else {
|
||||
client.render_pending = false;
|
||||
crate::render_prof::event("full_render.skip_identical");
|
||||
crate::render_prof::duration_since("full_render.prepare_frame", prepare_started);
|
||||
continue;
|
||||
};
|
||||
crate::render_prof::duration_since("full_render.prepare_frame", prepare_started);
|
||||
let mut frame_to_commit = frame.clone();
|
||||
|
||||
let max_frame_size = if frame.graphics.is_empty() {
|
||||
MAX_FRAME_SIZE
|
||||
} else {
|
||||
MAX_GRAPHICS_FRAME_SIZE
|
||||
};
|
||||
let serialize_started = crate::render_prof::timer();
|
||||
let serialized = match Self::frame_server_message_with_max(
|
||||
prepared.message(),
|
||||
|
|
@ -3031,17 +3031,22 @@ impl HeadlessServer {
|
|||
crate::render_prof::duration_since("full_render.serialize", serialize_started);
|
||||
framed
|
||||
}
|
||||
Err(protocol::FramingError::Oversized { claimed, max })
|
||||
if !frame.graphics.is_empty() =>
|
||||
{
|
||||
Err(protocol::FramingError::Oversized { claimed, max }) if has_graphics => {
|
||||
warn!(
|
||||
client_id,
|
||||
claimed, max, "dropping graphics from oversized frame for client"
|
||||
);
|
||||
let mut text_only_frame = frame.clone();
|
||||
let Some(mut text_only_frame) = prepared.into_frame() else {
|
||||
crate::render_prof::event("full_render.serialize_error");
|
||||
crate::render_prof::duration_since(
|
||||
"full_render.serialize",
|
||||
serialize_started,
|
||||
);
|
||||
continue;
|
||||
};
|
||||
text_only_frame.graphics.clear();
|
||||
let Some(text_only_prepared) =
|
||||
client.render_state.prepare_frame(&text_only_frame)
|
||||
client.render_state.prepare_frame(text_only_frame)
|
||||
else {
|
||||
client.render_pending = false;
|
||||
crate::render_prof::event("full_render.skip_identical_text_only");
|
||||
|
|
@ -3065,7 +3070,6 @@ impl HeadlessServer {
|
|||
}
|
||||
};
|
||||
prepared = text_only_prepared;
|
||||
frame_to_commit = text_only_frame;
|
||||
commit_graphics_cache = false;
|
||||
crate::render_prof::duration_since("full_render.serialize", serialize_started);
|
||||
framed
|
||||
|
|
@ -3097,9 +3101,7 @@ impl HeadlessServer {
|
|||
client.graphics_cache = next_graphics_cache;
|
||||
client.graphics_surface_reset_pending = false;
|
||||
}
|
||||
client
|
||||
.render_state
|
||||
.commit_sent_frame(frame_to_commit, prepared);
|
||||
client.render_state.commit_sent_frame(prepared);
|
||||
crate::render_prof::event("full_render.sent");
|
||||
crate::render_prof::duration_since("full_render.try_send", send_started);
|
||||
}
|
||||
|
|
@ -4504,6 +4506,52 @@ next_tab = ""
|
|||
rt.shutdown_timeout(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_attach_page_key_forwards_in_alternate_screen_without_mouse_reporting() {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("test runtime");
|
||||
let _runtime_guard = rt.enter();
|
||||
let mut bytes = b"\x1b[?1049h".to_vec();
|
||||
for line in 0..80 {
|
||||
bytes.extend_from_slice(format!("line {line:02}\r\n").as_bytes());
|
||||
}
|
||||
let (runtime, mut input_rx) =
|
||||
crate::terminal::TerminalRuntime::test_with_channel_and_scrollback_bytes(
|
||||
20, 5, 4096, &bytes, 4,
|
||||
);
|
||||
runtime.scroll_up(3);
|
||||
|
||||
apply_terminal_attach_scroll(
|
||||
&runtime,
|
||||
AttachScrollSource::PageKey {
|
||||
input: b"\x1b[5~".to_vec(),
|
||||
},
|
||||
AttachScrollDirection::Up,
|
||||
4,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
)
|
||||
.expect("page key forward");
|
||||
|
||||
assert_eq!(
|
||||
runtime
|
||||
.scroll_metrics()
|
||||
.expect("scroll metrics")
|
||||
.offset_from_bottom,
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
input_rx.try_recv().expect("forwarded page key"),
|
||||
Bytes::from_static(b"\x1b[5~")
|
||||
);
|
||||
drop(runtime);
|
||||
drop(_runtime_guard);
|
||||
rt.shutdown_timeout(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headless_scheduled_tasks_expire_agent_metadata() {
|
||||
let mut server = test_headless_server();
|
||||
|
|
@ -6384,9 +6432,9 @@ next_tab = ""
|
|||
frame.cells[hyperlink_idx].hyperlink = Some(0);
|
||||
let prepared = client
|
||||
.render_state
|
||||
.prepare_frame(&frame)
|
||||
.prepare_frame(frame)
|
||||
.expect("hyperlink frame differs");
|
||||
client.render_state.commit_sent_frame(frame, prepared);
|
||||
client.render_state.commit_sent_frame(prepared);
|
||||
|
||||
let runtime = server
|
||||
.app
|
||||
|
|
|
|||
|
|
@ -41,25 +41,24 @@ impl ClientRenderState {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn prepare_frame(&mut self, frame: &FrameData) -> Option<PreparedRender> {
|
||||
pub(crate) fn prepare_frame(&mut self, frame: FrameData) -> Option<PreparedRender> {
|
||||
match self {
|
||||
Self::Semantic { last_frame } => {
|
||||
if last_frame.as_ref() == Some(frame) {
|
||||
if last_frame.as_ref() == Some(&frame) {
|
||||
crate::render_prof::event("prepare_frame.semantic.skip_current");
|
||||
return None;
|
||||
}
|
||||
crate::render_prof::event("prepare_frame.semantic.changed");
|
||||
Some(PreparedRender {
|
||||
message: ServerMessage::Frame(frame.clone()),
|
||||
encoded: None,
|
||||
Some(PreparedRender::Semantic {
|
||||
message: ServerMessage::Frame(frame),
|
||||
})
|
||||
}
|
||||
Self::TerminalAnsi { blit_encoder, seq } => {
|
||||
if blit_encoder.is_current(frame) {
|
||||
if blit_encoder.is_current(&frame) {
|
||||
crate::render_prof::event("prepare_frame.ansi.skip_current");
|
||||
return None;
|
||||
}
|
||||
let mut encoded = blit_encoder.encode(frame, false);
|
||||
let mut encoded = blit_encoder.encode(&frame, false);
|
||||
crate::render_prof::event("prepare_frame.ansi.changed");
|
||||
crate::render_prof::counter("prepare_frame.ansi.bytes", encoded.bytes.len() as u64);
|
||||
if encoded.full {
|
||||
|
|
@ -72,7 +71,7 @@ impl ClientRenderState {
|
|||
"prepare_frame.graphics.bytes",
|
||||
frame.graphics.len() as u64,
|
||||
);
|
||||
Some(PreparedRender {
|
||||
Some(PreparedRender::TerminalAnsi {
|
||||
message: ServerMessage::Terminal(TerminalFrame {
|
||||
seq: *seq + 1,
|
||||
width: frame.width,
|
||||
|
|
@ -80,6 +79,7 @@ impl ClientRenderState {
|
|||
full: encoded.full,
|
||||
bytes: encoded.bytes.clone(),
|
||||
}),
|
||||
frame,
|
||||
encoded: Some(encoded),
|
||||
})
|
||||
}
|
||||
|
|
@ -93,10 +93,22 @@ impl ClientRenderState {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn commit_sent_frame(&mut self, frame: FrameData, prepared: PreparedRender) {
|
||||
match (self, prepared.encoded) {
|
||||
(Self::Semantic { last_frame }, None) => *last_frame = Some(frame),
|
||||
(Self::TerminalAnsi { blit_encoder, seq }, Some(encoded)) => {
|
||||
pub(crate) fn commit_sent_frame(&mut self, prepared: PreparedRender) {
|
||||
match (self, prepared) {
|
||||
(
|
||||
Self::Semantic { last_frame },
|
||||
PreparedRender::Semantic {
|
||||
message: ServerMessage::Frame(frame),
|
||||
},
|
||||
) => *last_frame = Some(frame),
|
||||
(
|
||||
Self::TerminalAnsi { blit_encoder, seq },
|
||||
PreparedRender::TerminalAnsi {
|
||||
frame,
|
||||
encoded: Some(encoded),
|
||||
..
|
||||
},
|
||||
) => {
|
||||
blit_encoder.commit(frame, encoded);
|
||||
*seq += 1;
|
||||
}
|
||||
|
|
@ -138,14 +150,32 @@ fn rfind_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
|
|||
}
|
||||
|
||||
/// A prepared client render message plus any baseline state needed after send.
|
||||
pub(crate) struct PreparedRender {
|
||||
message: ServerMessage,
|
||||
encoded: Option<EncodedBlit>,
|
||||
pub(crate) enum PreparedRender {
|
||||
Semantic {
|
||||
message: ServerMessage,
|
||||
},
|
||||
TerminalAnsi {
|
||||
message: ServerMessage,
|
||||
frame: FrameData,
|
||||
encoded: Option<EncodedBlit>,
|
||||
},
|
||||
}
|
||||
|
||||
impl PreparedRender {
|
||||
pub(crate) fn message(&self) -> &ServerMessage {
|
||||
&self.message
|
||||
match self {
|
||||
Self::Semantic { message } | Self::TerminalAnsi { message, .. } => message,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_frame(self) -> Option<FrameData> {
|
||||
match self {
|
||||
Self::Semantic {
|
||||
message: ServerMessage::Frame(frame),
|
||||
} => Some(frame),
|
||||
Self::TerminalAnsi { frame, .. } => Some(frame),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue