aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/a11y.rs72
-rw-r--r--src/ui/mru.rs10
2 files changed, 81 insertions, 1 deletions
diff --git a/src/a11y.rs b/src/a11y.rs
index f6553138..76d66f0f 100644
--- a/src/a11y.rs
+++ b/src/a11y.rs
@@ -7,9 +7,12 @@ use accesskit::{
};
use accesskit_unix::Adapter;
use calloop::LoopHandle;
+use niri_config::MruScope;
use crate::layout::workspace::WorkspaceId;
use crate::niri::{KeyboardFocus, Niri, State};
+use crate::utils::with_toplevel_role;
+use crate::window::mapped::MappedId;
const ID_ROOT: NodeId = NodeId(0);
const ID_ANNOUNCEMENT: NodeId = NodeId(1);
@@ -22,6 +25,9 @@ pub struct A11y {
event_loop: LoopHandle<'static, State>,
focus: NodeId,
workspace_id: Option<WorkspaceId>,
+ mru_selection: Option<MappedId>,
+ mru_scope: Option<MruScope>,
+ last_mru_title: String,
last_announcement: String,
to_accesskit: Option<mpsc::SyncSender<TreeUpdate>>,
}
@@ -38,6 +44,9 @@ impl A11y {
event_loop,
focus: ID_ROOT,
workspace_id: None,
+ mru_selection: None,
+ mru_scope: None,
+ last_mru_title: String::new(),
last_announcement: String::new(),
to_accesskit: None,
}
@@ -129,9 +138,30 @@ impl Niri {
self.a11y.workspace_id = ws_id;
let focus = self.a11y_focus();
+
+ // Check if the MRU selection changed.
+ let mut update_mru_selection = false;
+ if focus == ID_MRU {
+ let current = self.window_mru_ui.current_window_id();
+ if self.a11y.mru_selection != current {
+ update_mru_selection = true;
+ self.a11y.mru_selection = current;
+ }
+
+ // If there's no window title to announce, check if there's a scope change.
+ let scope = self.window_mru_ui.scope();
+ if !update_mru_selection && self.a11y.mru_scope != Some(scope) {
+ announcement = Some(self.window_mru_ui.a11y_scope_text());
+ }
+ self.a11y.mru_scope = Some(scope);
+ } else {
+ self.a11y.mru_scope = None;
+ self.a11y.mru_selection = None;
+ }
+
let update_focus = self.a11y.focus != focus;
- if !(announcement.is_some() || update_focus) {
+ if !(announcement.is_some() || update_focus || update_mru_selection) {
return;
}
@@ -150,6 +180,43 @@ impl Niri {
nodes.push((ID_ANNOUNCEMENT, node));
}
+ if focus == ID_MRU {
+ // Ideally MRU would be a Group with a child Button for a window, but I've no idea how
+ // to make it work reliably. When I did it that way, there were two issues:
+ //
+ // 1. Alt-Tab would always start reading from "Recent windows grouping" instead of the
+ // window title.
+ // 2. When Alt-Tab became empty (e.g. switching scope to something empty), Orca would
+ // completely stop reading any child buttons for the remainder of the session.
+ //
+ // I've no idea what to do about these and where they even come from. So, just flip the
+ // MRU node between Group and Button, which seems to work fine.
+ if update_mru_selection {
+ if let Some(id) = self.a11y.mru_selection {
+ if let Some((_, mapped)) = self.layout.windows().find(|(_, m)| m.id() == id) {
+ with_toplevel_role(mapped.toplevel(), |role| {
+ let mut title = role.title.as_deref().unwrap_or("Unknown").to_owned();
+ // Change title on match to ensure we announce same-titled windows.
+ if self.a11y.last_mru_title == title {
+ title.push(' ');
+ }
+ self.a11y.last_mru_title = title;
+
+ let mut mru = Node::new(Role::Button);
+ mru.set_label(&*self.a11y.last_mru_title);
+ nodes.push((ID_MRU, mru));
+ });
+ }
+ } else {
+ let mut mru = Node::new(Role::Group);
+ // Announce the current scope in the empty text to make it clear.
+ let scope = self.window_mru_ui.a11y_scope_text();
+ mru.set_label(format!("Recent windows empty, {scope}"));
+ nodes.push((ID_MRU, mru));
+ }
+ }
+ }
+
let update = TreeUpdate {
nodes,
tree: None,
@@ -259,6 +326,9 @@ impl Niri {
let focus = self.a11y_focus();
+ // NOTE: we don't fill in current MRU selection here to avoid duplicating code; it should
+ // get updated right away anyway.
+
TreeUpdate {
nodes: vec![
(ID_ROOT, root),
diff --git a/src/ui/mru.rs b/src/ui/mru.rs
index 5e7e24df..e7fe9227 100644
--- a/src/ui/mru.rs
+++ b/src/ui/mru.rs
@@ -1224,6 +1224,16 @@ impl WindowMruUi {
_ => None,
}
}
+
+ #[cfg(feature = "dbus")]
+ pub fn a11y_scope_text(&self) -> String {
+ let scope = match self.scope() {
+ MruScope::All => "all",
+ MruScope::Output => "output",
+ MruScope::Workspace => "workspace",
+ };
+ format!("Scope {scope}")
+ }
}
fn compute_view_offset(cur_x: f64, working_width: f64, new_col_x: f64, new_col_width: f64) -> f64 {