Skip to content
Merged
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
23 changes: 23 additions & 0 deletions crates/ember-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,8 @@ impl ApplicationHandler<EmberEvent> for App {

let mut win = WindowState::new(renderer, tree);
win.px = px;
win.ax =
ember_platform::accessibility::AxTextArea::install(&window, shared.wake.clone());
if !win.spawn_session(
&mut shared,
session,
Expand Down Expand Up @@ -1767,6 +1769,7 @@ impl ApplicationHandler<EmberEvent> for App {
win.render_ema_ms
)));
}
win.refresh_ax();
let t = Instant::now();
match win.renderer.render() {
// A drawable came through — the surface is ground truth, so
Expand Down Expand Up @@ -1806,6 +1809,25 @@ impl ApplicationHandler<EmberEvent> for App {
return;
};

// Accessibility clients (dictation tools) reading or writing a
// window's terminal view: an insert pastes into that window's focused
// pane, exactly like Cmd+V; a first read starts its text updates.
for req in ember_platform::accessibility::drain() {
use ember_platform::accessibility::AxRequest;
let (AxRequest::Insert { view, .. } | AxRequest::Refresh { view }) = &req;
let Some(w) = self
.windows
.values()
.find(|w| w.ax.as_ref().is_some_and(|ax| ax.view() == *view))
else {
continue;
};
match req {
AxRequest::Insert { text, .. } => w.paste_into_focused(shared, &text),
AxRequest::Refresh { .. } => w.refresh_ax(),
}
}

// Poll every session's pixel lane, routing each one's deltas to the
// window that actually owns it (`session_window`) — "drain only the
// focused window" (the pre-multi-window shape of this function) would
Expand Down Expand Up @@ -2870,6 +2892,7 @@ fn open_window(

let mut win = WindowState::new(renderer, tree);
win.px = px;
win.ax = ember_platform::accessibility::AxTextArea::install(&window, shared.wake.clone());
win.sync_layout(shared);
win.apply_appearance(shared);
win.renderer.window().request_redraw();
Expand Down
19 changes: 19 additions & 0 deletions crates/ember-app/src/window_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,9 @@ pub(crate) struct WindowState {
/// IME composition in progress (preedit text); non-empty = composing,
/// during which raw key events are suppressed (they belong to the IME).
pub(crate) ime_preedit: String,
/// This window's view registered as an accessibility text area (macOS),
/// so dictation tools can insert into it; `None` elsewhere.
pub(crate) ax: Option<ember_platform::accessibility::AxTextArea>,
/// Command palette (Cmd+Shift+P): open flag, query, selected row index
/// (into the CURRENT filtered list).
pub(crate) palette_open: bool,
Expand Down Expand Up @@ -853,6 +856,7 @@ impl WindowState {
search_query: String::new(),
search_count: None,
ime_preedit: String::new(),
ax: None,
palette_open: false,
palette_query: String::new(),
palette_sel: 0,
Expand Down Expand Up @@ -1581,6 +1585,21 @@ impl WindowState {
self.send_to_focused(shared, bracket_paste(text, bracketed));
}

/// Publish the focused pane's screen text + cursor to the accessibility
/// text area, once a client has started reading it (no cost otherwise).
pub(crate) fn refresh_ax(&self) {
let Some(ax) = self.ax.as_ref().filter(|ax| ax.active()) else {
return;
};
let Some(grid) = self
.focused_session_id()
.and_then(|id| self.renderer.grid(&id))
else {
return;
};
ax.set_text(grid.screen_text(), grid.cursor.row, grid.cursor.col);
}

/// A file dropped from the OS (Finder / file manager) onto this window:
/// insert its shell-escaped path plus a trailing space at the focused
/// pane's prompt, through the paste path (bracketed-paste aware) —
Expand Down
2 changes: 1 addition & 1 deletion crates/ember-platform/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ arboard.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6"
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSImage", "NSResponder", "NSWorkspace"] }
objc2-foundation = { version = "0.3", features = ["NSData", "NSString", "NSProcessInfo"] }
objc2-foundation = { version = "0.3", features = ["NSData", "NSString", "NSProcessInfo", "NSRange"] }
# default features pull muda's Linux GTK backend (gtk3 → glib), which Ember
# never uses — the menu is macOS-only (NSMenu via objc2); other platforms get
# an inert stub. Dropping them keeps the vulnerable glib (RUSTSEC-2024-0429)
Expand Down
Loading
Loading