Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ follow [Semantic Versioning](https://semver.org).

## [Unreleased]

### Fixed

- OSC 52 clipboard reads now work as documented. Since 0.5.0, a program
asking for the clipboard's contents got no reply at all: the request was
dropped before Ember's own setting was consulted, so `osc52_read = true`
had no effect, and with it off a program waiting for an answer could hang.
Ember now answers every read: with the clipboard's contents when
`osc52_read = true`, and with an empty reply otherwise (still the default),
so nothing is shared unless you opt in.

## [0.6.1] - 2026-09-25

### Fixed
Expand Down
23 changes: 23 additions & 0 deletions crates/ember-session/src/local_pty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,29 @@ mod tests {
);
}

/// The same promise, through the real path: the read's escape bytes go into
/// the engine, not a synthetic event into the listener. The engine has its
/// own OSC 52 gate ahead of the listener, and its default drops reads, which
/// left the pane silent while the listener-only test above kept passing.
#[test]
fn osc52_read_through_the_engine_replies_empty() {
let (tx, _rx) = mpsc::channel();
let outbox = Arc::new(Mutex::new(Vec::new()));
let listener = EmberListener {
events: tx,
outbox: Arc::clone(&outbox),
osc52_read: false,
palette: crate::palette::Palette::dark(),
};
let mut proj = AlacrittyProjection::new(GridDims::new(80, 24), listener);
proj.advance(b"\x1b]52;c;?\x07");
let got = String::from_utf8(outbox.lock().unwrap().clone()).unwrap();
assert_eq!(
got, "\x1b]52;c;\x07",
"an OSC 52 read must reach the listener and get the empty reply, not silence"
);
}

/// Reconstruct row 0's text from the frame lane until `needle` appears or we
/// time out. Proves the full path: shell → PTY → engine → projection → lane.
#[test]
Expand Down
13 changes: 11 additions & 2 deletions crates/ember-session/src/projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use alacritty_terminal::index::{Column, Direction, Line, Point, Side};
use alacritty_terminal::term::cell::{Cell, Flags};
use alacritty_terminal::term::search::RegexSearch;
use alacritty_terminal::term::test::TermSize;
use alacritty_terminal::term::{Config, Term, TermDamage, TermMode};
use alacritty_terminal::term::{Config, Osc52, Term, TermDamage, TermMode};
use alacritty_terminal::vte::ansi::{CursorShape as AlacCursorShape, Processor};
use ember_core::{
Attrs, CellContent, CellPatch, CursorShape, CursorState, GridDelta, GridDims, MarkStatus,
Expand Down Expand Up @@ -129,7 +129,16 @@ enum Scanned {
impl<L: EventListener> AlacrittyProjection<L> {
pub fn new(dims: GridDims, listener: L) -> Self {
let size = TermSize::new(dims.columns as usize, dims.screen_lines as usize);
let term = Term::new(Config::default(), &size, listener);
// The engine has its own OSC 52 gate ahead of the listener, and its
// default (`OnlyCopy`) drops reads there, so a read would get no reply
// at all. Let both through: the listener is the one gate on clipboard
// contents (`osc52_read`, off by default = an empty reply), which is
// what keeps a well-behaved program from hanging on its query.
let config = Config {
osc52: Osc52::CopyPaste,
..Config::default()
};
let term = Term::new(config, &size, listener);
Self {
term,
parser: Processor::new(),
Expand Down
Loading