From 64aef00372b08183fc01a3361c17e82ea8c51d5e Mon Sep 17 00:00:00 2001 From: DataTriny Date: Fri, 29 Oct 2021 19:13:40 +0200 Subject: [PATCH 01/69] AT-SPI adapter scaffolding --- Cargo.toml | 1 + platforms/linux/Cargo.toml | 17 + platforms/linux/examples/hello_world.rs | 81 ++++ platforms/linux/src/atspi/interfaces.rs | 128 ++++++ platforms/linux/src/atspi/mod.rs | 553 ++++++++++++++++++++++++ platforms/linux/src/atspi/proxies.rs | 37 ++ platforms/linux/src/lib.rs | 7 + platforms/linux/src/manager.rs | 59 +++ platforms/linux/src/node.rs | 57 +++ 9 files changed, 940 insertions(+) create mode 100644 platforms/linux/Cargo.toml create mode 100644 platforms/linux/examples/hello_world.rs create mode 100644 platforms/linux/src/atspi/interfaces.rs create mode 100644 platforms/linux/src/atspi/mod.rs create mode 100644 platforms/linux/src/atspi/proxies.rs create mode 100644 platforms/linux/src/lib.rs create mode 100644 platforms/linux/src/manager.rs create mode 100644 platforms/linux/src/node.rs diff --git a/Cargo.toml b/Cargo.toml index e15971ba5..8c0be21af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "common", "consumer", + "platforms/linux", "platforms/macos", "platforms/windows", "platforms/winit", diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml new file mode 100644 index 000000000..9262eb9a7 --- /dev/null +++ b/platforms/linux/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "accesskit_linux" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +accesskit_consumer = { path = "../../consumer" } +accesskit_schema = { path = "../../schema" } +serde = "1.0" +x11rb = "0.8.1" +zbus = "2.0.0-beta.7" +zvariant = "2.9.0" + +[dev-dependencies] +winit = "0.25.0" diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs new file mode 100644 index 000000000..efcf7dffd --- /dev/null +++ b/platforms/linux/examples/hello_world.rs @@ -0,0 +1,81 @@ +use accesskit_schema::{Node, NodeId, Role, StringEncoding, Tree, TreeId, TreeUpdate}; +use accesskit_linux::Manager; +use std::num::NonZeroU64; +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +const WINDOW_TITLE: &str = "Hello world"; + +const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(1) }); +const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(2) }); +const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(3) }); + +fn get_tree(is_window_focused: bool) -> Tree { + Tree { + focus: is_window_focused.then(|| unsafe { FOCUS }), + ..Tree::new(TreeId("test".into()), StringEncoding::Utf8) + } +} + +fn get_button_1(name: &str) -> Node { + Node { + name: Some(name.into()), + focusable: true, + ..Node::new(BUTTON_1_ID, Role::Button) + } +} + +fn get_button_2(name: &str) -> Node { + Node { + name: Some(name.into()), + focusable: true, + ..Node::new(BUTTON_2_ID, Role::Button) + } +} + +fn get_initial_state() -> TreeUpdate { + let root = Node { + children: Box::new([BUTTON_1_ID, BUTTON_2_ID]), + name: Some(WINDOW_TITLE.into()), + ..Node::new(WINDOW_ID, Role::Window) + }; + let button_1 = get_button_1("Button 1"); + let button_2 = get_button_2("Button 2"); + TreeUpdate { + clear: None, + nodes: vec![root, button_1, button_2], + tree: Some(get_tree(false)), + root: Some(WINDOW_ID), + } +} + +static mut FOCUS: NodeId = BUTTON_1_ID; + +fn main() { + let manager = Manager::new(get_initial_state()); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title(WINDOW_TITLE) + .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + } if window_id == window.id() => *control_flow = ControlFlow::Exit, + Event::MainEventsCleared => { + window.request_redraw(); + } + _ => (), + } + }); +} diff --git a/platforms/linux/src/atspi/interfaces.rs b/platforms/linux/src/atspi/interfaces.rs new file mode 100644 index 000000000..e58237a48 --- /dev/null +++ b/platforms/linux/src/atspi/interfaces.rs @@ -0,0 +1,128 @@ +use crate::atspi::{OwnedObjectAddress, Role}; + +pub trait ApplicationInterface { + fn toolkit_name(&self) -> &str; + + fn toolkit_version(&self) -> &str; + + fn id(&self) -> i32; + + fn set_id(&mut self, id: i32); + + fn locale(&self, lctype: u32) -> &str; + + fn register_event_listener(&mut self, event: String); + + fn deregister_event_listener(&mut self, event: String); +} + +pub struct ApplicationInterfaceObject(T); + +#[dbus_interface(name = "org.a11y.atspi.Application")] +impl ApplicationInterfaceObject { + #[dbus_interface(property)] + fn toolkit_name(&self) -> &str { + self.0.toolkit_name() + } + + #[dbus_interface(property)] + fn version(&self) -> &str { + self.0.toolkit_version() + } + + #[dbus_interface(property)] + fn atspi_version(&self) -> &str { + "2.1" + } + + #[dbus_interface(property)] + fn id(&self) -> i32 { + self.0.id() + } + + #[dbus_interface(property)] + fn set_id(&mut self, id: i32) { + self.0.set_id(id) + } + + fn get_locale(&self, lctype: u32) -> &str { + self.0.locale(lctype) + } + + fn register_event_listener(&self, _event: String) {} + + fn deregister_event_listener(&self, _event: String) {} +} + +pub trait AccessibleInterface { + fn name(&self) -> String; + + fn description(&self) -> String; + + fn parent(&self) -> Option; + + fn child_count(&self) -> usize; + + fn locale(&self) -> &str; + + fn accessible_id(&self) -> String; + + fn child_at_index(&self, index: usize) -> Option; + + fn children(&self) -> Vec; + + fn index_in_parent(&self) -> Option; + + fn role(&self) -> Role; +} + +pub struct AccessibleInterfaceObject(T); + +#[dbus_interface(name = "org.a11y.atspi.Accessible")] +impl AccessibleInterfaceObject { + #[dbus_interface(property)] + fn name(&self) -> String { + self.0.name() + } + + #[dbus_interface(property)] + fn description(&self) -> String { + self.0.description() + } + + #[dbus_interface(property)] + fn parent(&self) -> OwnedObjectAddress { + self.0.parent().unwrap() + } + + #[dbus_interface(property)] + fn child_count(&self) -> i32 { + self.0.child_count() as i32 + } + + #[dbus_interface(property)] + fn locale(&self) -> &str { + "" + } + + #[dbus_interface(property)] + fn accessible_id(&self) -> String { + self.0.accessible_id() + } + + fn get_child_at_index(&self, index: i32) -> () {//ObjectAddress { + self.0.child_at_index(index as usize).unwrap(); + } + + fn get_children(&self) -> () {//&[ObjectAddress] { + self.0.children(); + } + + fn get_index_in_parent(&self) -> i32 { + self.0.index_in_parent().unwrap() as i32 + } + + fn get_role(&self) -> Role { + self.0.role() + } +} diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs new file mode 100644 index 000000000..824a65586 --- /dev/null +++ b/platforms/linux/src/atspi/mod.rs @@ -0,0 +1,553 @@ +use crate::atspi::proxies::BusProxy; +use serde::{Deserialize, Serialize}; +use std::env::var; +use x11rb::{ + connection::Connection as _, + protocol::xproto::{AtomEnum, ConnectionExt} +}; +use zbus::{ + blocking::{Connection, ConnectionBuilder}, + names::{OwnedUniqueName, UniqueName}, + Address +}; +use zvariant::{ + derive::{Type, Value}, + ObjectPath, OwnedObjectPath +}; + +pub mod interfaces; +pub mod proxies; + +#[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] +pub struct ObjectAddress<'a> { + #[serde(borrow)] + bus_name: UniqueName<'a>, + #[serde(borrow)] + path: ObjectPath<'a>, +} + +impl<'a> ObjectAddress<'a> { + pub fn new(bus_name: UniqueName<'a>, path: ObjectPath<'a>) -> ObjectAddress<'a> { + Self { bus_name, path } + } + + pub fn null(bus_name: UniqueName<'a>) -> ObjectAddress<'a> { + Self { + bus_name, + path: ObjectPath::from_str_unchecked("/org/a11y/atspi/null") + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] +pub struct OwnedObjectAddress { + bus_name: OwnedUniqueName, + path: OwnedObjectPath, +} + +impl OwnedObjectAddress { + pub fn new(bus_name: S, path: O) -> Self + where + S: Into, + O: Into + { + Self { + bus_name: bus_name.into(), + path: path.into() + } + } +} + +/// Enumeration used by interface #AtspiAccessible to specify the role +/// of an #AtspiAccessible object. +#[derive(Clone, Copy, Debug, Deserialize, Serialize, Type)] +pub enum Role { + /// A role indicating an error condition, such as + /// uninitialized Role data. + Invalid, + /// Object is a label indicating the keyboard + /// accelerators for the parent. + AcceleratorLabel, + /// Object is used to alert the user about something. + Alert, + /// Object contains a dynamic or moving image of some kind. + Animation, + /// Object is a 2d directional indicator. + Arrow, + /// Object contains one or more dates, usually arranged + /// into a 2d list. + Calendar, + /// Object that can be drawn into and is used to trap events. + Canvas, + /// A choice that can be checked or unchecked and + /// provides a separate indicator for the current state. + CheckBox, + /// A menu item that behaves like a check box. See @CHECK_BOX. + CheckMenuItem, + /// A specialized dialog that lets the user choose a color. + ColorChooser, + /// The header for a column of data. + ColumnHeader, + /// A list of choices the user can select from. + ComboBox, + /// An object which allows entry of a date. + DateEditor, + /// An inconifed internal frame within a DESKTOP_PANE. + DesktopIcon, + /// A pane that supports internal frames and + /// iconified versions of those internal frames. + DesktopFrame, + /// An object that allows a value to be changed via rotating a + /// visual element, or which displays a value via such a rotating element. + Dial, + /// A top level window with title bar and a border. + Dialog, + /// A pane that allows the user to navigate through + /// and select the contents of a directory. + DirectoryPane, + /// A specialized dialog that displays the files in + /// the directory and lets the user select a file, browse a different + /// directory, or specify a filename. + DrawingArea, + /// An object used for drawing custom user interface elements. + FileChooser, + /// A object that fills up space in a user interface. + Filler, + /// Don't use, reserved for future use. + FocusTraversable, + /// Allows selection of a display font. + FontChooser, + /// A top level window with a title bar, border, menubar, etc. + Frame, + /// A pane that is guaranteed to be painted on top of + /// all panes beneath it. + GlassPane, + /// A document container for HTML, whose children + /// represent the document content. + HtmlContainer, + /// A small fixed size picture, typically used to decorate + /// components. + Icon, + /// An image, typically static. + Image, + /// A frame-like object that is clipped by a desktop pane. + InternalFrame, + /// An object used to present an icon or short string in an interface. + Label, + /// A specialized pane that allows its children to be + /// drawn in layers, providing a form of stacking order. + LayeredPane, + /// An object that presents a list of objects to the user and + /// allows the user to select one or more of them. + List, + /// An object that represents an element of a list. + ListItem, + /// An object usually found inside a menu bar that contains a + /// list of actions the user can choose from. + Menu, + /// An object usually drawn at the top of the primary + /// dialog box of an application that contains a list of menus the user can + /// choose from. + MenuBar, + /// An object usually contained in a menu that presents + /// an action the user can choose. + MenuItem, + /// A specialized pane whose primary use is inside a dialog. + OptionPane, + /// An object that is a child of a page tab list. + PageTab, + /// An object that presents a series of panels (or page tabs), + /// one at a time,through some mechanism provided by the object. + PageTabList, + /// A generic container that is often used to group objects. + Panel, + /// A text object uses for passwords, or other places + /// where the text content is not shown visibly to the user. + PasswordText, + /// A temporary window that is usually used to offer the + /// user a list of choices, and then hides when the user selects one of those + /// choices. + PopupMenu, + /// An object used to indicate how much of a task has been completed. + ProgressBar, + /// An object the user can manipulate to tell the + /// application to do something. + PushButton, + /// A specialized check box that will cause other radio buttons + /// in the same group to become unchecked when this one is checked. + RadioButton, + /// Object is both a menu item and a 'radio button' + /// See @RADIO_BUTTON. + RadioMenuItem, + /// A specialized pane that has a glass pane and a + /// layered pane as its children. + RootPane, + /// The header for a row of data. + RowHeader, + /// An object usually used to allow a user to + /// incrementally view a large amount of data by moving the bounds of a + /// viewport along a one-dimensional axis. + ScrollBar, + /// An object that allows a user to incrementally view + /// a large amount of information. @SCROLL_PANE objects are usually + /// accompanied by @SCROLL_BAR controllers, on which the + /// @RELATION_CONTROLLER_FOR and @RELATION_CONTROLLED_BY + /// reciprocal relations are set. See #get_relation_set. + ScrollPane, + /// An object usually contained in a menu to provide a + /// visible and logical separation of the contents in a menu. + Separator, + /// An object that allows the user to select from a bounded range. + Slider, + /// An object which allows one of a set of choices to + /// be selected, and which displays the current choice. Unlike + /// @SCROLL_BAR, @SLIDER objects need not control + /// 'viewport'-like objects. + SpinButton, + /// A specialized panel that presents two other panels + /// at the same time. + SplitPane, + /// Object displays non-quantitative status information + /// (c.f. @PROGRESS_BAR) + StatusBar, + /// An object used to repesent information in terms of rows and columns. + Table, + /// A 'cell' or discrete child within a Table. Note: + /// Table cells need not have @TABLE_CELL, other + /// #AtspiRoleType values are valid as well. + TableCell, + /// An object which labels a particular column + /// in an #AtspiTable. + TableColumnHeader, + /// An object which labels a particular row in a + /// #AtspiTable. #AtspiTable rows and columns may also be labelled via the + /// @RELATION_LABEL_FOR/@RELATION_LABELLED_BY relationships. + /// See #get_relation_set. + TableRowHeader, + /// Object allows menu to be removed from menubar + /// and shown in its own window. + TearoffMenuItem, + /// An object that emulates a terminal. + Terminal, + /// An interactive widget that supports multiple lines of text + /// and optionally accepts user input, but whose purpose is not to solicit user + /// input. Thus @TEXT is appropriate for the text view in a plain text + /// editor but inappropriate for an input field in a dialog box or web form. For + /// widgets whose purpose is to solicit input from the user, see @ENTRY + /// and @PASSWORD_TEXT. For generic objects which display a brief amount + /// of textual information, see @STATIC. + Text, + /// A specialized push button that can be checked or + /// unchecked, but does not procide a separate indicator for the current + /// state. + ToggleButton, + /// A bar or palette usually composed of push buttons or + /// toggle buttons. + ToolBar, + /// An object that provides information about another object. + ToolTip, + /// An object used to repsent hierarchical information to the user. + Tree, + /// An object that presents both tabular and + /// hierarchical info to the user. + TreeTable, + /// The object contains some #AtspiAccessible information, + /// but its role is not known. + Unknown, + /// An object usually used in a scroll pane, or to + /// otherwise clip a larger object or content renderer to a specific + /// onscreen viewport. + Viewport, + /// A top level window with no title or border. + Window, + /// Means that the role for this item is known, but not + /// included in the core enumeration. Deprecated since 2.24. + Extended, + /// An object that serves as a document header. + Header, + /// An object that serves as a document footer. + Footer, + /// An object which is contains a single paragraph of + /// text content. See also @TEXT. + Paragraph, + /// An object which describes margins and tab stops, etc. + /// for text objects which it controls (should have + /// @RELATION_CONTROLLER_FOR relation to such). + Ruler, + /// An object corresponding to the toplevel accessible + /// of an application, which may contain @FRAME objects or other + /// accessible objects. Children of #AccessibleDesktop objects are generally + /// @APPLICATION objects. + Application, + /// The object is a dialog or list containing items + /// for insertion into an entry widget, for instance a list of words for + /// completion of a text entry. + Autocomplete, + /// The object is an editable text object in a toolbar. + Editbar, + /// The object is an embedded component container. This + /// role is a "grouping" hint that the contained objects share a context + /// which is different from the container in which this accessible is + /// embedded. In particular, it is used for some kinds of document embedding, + /// and for embedding of out-of-process component, "panel applets", etc. + Embedded, + /// The object is a component whose textual content may be + /// entered or modified by the user, provided @STATE_EDITABLE is present. + /// A readonly @ENTRY object (i.e. where @STATE_EDITABLE is + /// not present) implies a read-only 'text field' in a form, as opposed to a + /// title, label, or caption. + Entry, + /// The object is a graphical depiction of quantitative data. + /// It may contain multiple subelements whose attributes and/or description + /// may be queried to obtain both the quantitative data and information about + /// how the data is being presented. The @LABELLED_BY relation is + /// particularly important in interpreting objects of this type, as is the + /// accessible description property. See @CAPTION. + Chart, + /// The object contains descriptive information, usually + /// textual, about another user interface element such as a table, chart, or + /// image. + Caption, + /// The object is a visual frame or container which + /// contains a view of document content. #AtspiDocument frames may occur within + /// another #AtspiDocument instance, in which case the second document may be + /// said to be embedded in the containing instance. HTML frames are often + /// DOCUMENT_FRAME: Either this object, or a singleton descendant, + /// should implement the #AtspiDocument interface. + DocumentFrame, + /// The object serves as a heading for content which + /// follows it in a document. The 'heading level' of the heading, if + /// availabe, may be obtained by querying the object's attributes. + Heading, + /// The object is a containing instance which encapsulates a + /// page of information. @PAGE is used in documents and content which + /// support a paginated navigation model. + Page, + /// The object is a containing instance of document content + /// which constitutes a particular 'logical' section of the document. The + /// type of content within a section, and the nature of the section division + /// itself, may be obtained by querying the object's attributes. Sections + /// may be nested. + Section, + /// The object is redundant with another object in + /// the hierarchy, and is exposed for purely technical reasons. Objects of + /// this role should be ignored by clients, if they are encountered at all. + RedundantObject, + /// The object is a containing instance of document content + /// which has within it components with which the user can interact in order + /// to input information; i.e. the object is a container for pushbuttons, + /// comboboxes, text input fields, and other 'GUI' components. @FORM + /// should not, in general, be used for toplevel GUI containers or dialogs, + /// but should be reserved for 'GUI' containers which occur within document + /// content, for instance within Web documents, presentations, or text + /// documents. Unlike other GUI containers and dialogs which occur inside + /// application instances, @FORM containers' components are + /// associated with the current document, rather than the current foreground + /// application or viewer instance. + Form, + /// The object is a hypertext anchor, i.e. a "link" in a + /// hypertext document. Such objects are distinct from 'inline' content + /// which may also use the #AtspiHypertext/#AtspiHyperlink interfacesto indicate + /// the range/location within a text object where an inline or embedded object + /// lies. + Link, + /// The object is a window or similar viewport + /// which is used to allow composition or input of a 'complex character', + /// in other words it is an "input method window". + InputMethodWindow, + /// A row in a table. + TableRow, + /// An object that represents an element of a tree. + TreeItem, + /// A document frame which contains a spreadsheet. + DocumentSpreadsheet, + /// A document frame which contains a + /// presentation or slide content. + DocumentPresentation, + /// A document frame which contains textual content, + /// such as found in a word processing application. + DocumentText, + /// A document frame which contains HTML or other + /// markup suitable for display in a web browser. + DocumentWeb, + /// A document frame which contains email content + /// to be displayed or composed either in plain text or HTML. + DocumentEmail, + /// An object found within a document and designed to + /// present a comment, note, or other annotation. In some cases, this object + /// might not be visible until activated. + Comment, + /// A non-collapsible list of choices the user can select from. + ListBox, + /// A group of related widgets. This group typically has a label. + Grouping, + /// An image map object. Usually a graphic with multiple + /// hotspots, where each hotspot can be activated resulting in the loading of + /// another document or section of a document. + ImageMap, + /// A transitory object designed to present a + /// message to the user, typically at the desktop level rather than inside a + /// particular application. + Notification, + /// An object designed to present a message to the user + /// within an existing window. + InfoBar, + /// A bar that serves as a level indicator to, for + /// instance, show the strength of a password or the state of a battery. @Since: 2.8 + LevelBar, + /// A bar that serves as the title of a window or a + /// dialog. @Since: 2.12 + TitleBar, + /// An object which contains a text section + /// that is quoted from another source. @Since: 2.12 + BlockQuote, + /// An object which represents an audio + /// element. @Since: 2.12 + Audio, + /// An object which represents a video element. @Since: 2.12 + Video, + /// A definition of a term or concept. @Since: 2.12 + Definition, + /// A section of a page that consists of a + /// composition that forms an independent part of a document, page, or + /// site. Examples: A blog entry, a news story, a forum post. + /// @Since: 2.12 + Article, + /// A region of a web page intended as a + /// navigational landmark. This is designed to allow Assistive + /// Technologies to provide quick navigation among key regions within a + /// document. @Since: 2.12 + Landmark, + /// A text widget or container holding log content, such + /// as chat history and error logs. In this role there is a + /// relationship between the arrival of new items in the log and the + /// reading order. The log contains a meaningful sequence and new + /// information is added only to the end of the log, not at arbitrary + /// points. @Since: 2.12 + Log, + /// A container where non-essential information + /// changes frequently. Common usages of marquee include stock tickers + /// and ad banners. The primary difference between a marquee and a log + /// is that logs usually have a meaningful order or sequence of + /// important content changes. @Since: 2.12 + Marquee, + /// A text widget or container that holds a mathematical + /// expression. @Since: 2.12 + Math, + /// A widget whose purpose is to display a rating, + /// such as the number of stars associated with a song in a media + /// player. Objects of this role should also implement + /// AtspiValue. @Since: 2.12 + Rating, + /// An object containing a numerical counter which + /// indicates an amount of elapsed time from a start point, or the time + /// remaining until an end point. @Since: 2.12 + Timer, + /// A generic non-container object whose purpose is to display + /// a brief amount of information to the user and whose role is known by the + /// implementor but lacks semantic value for the user. Examples in which + /// @STATIC is appropriate include the message displayed in a message + /// box and an image used as an alternative means to display text. + /// @STATIC should not be applied to widgets which are traditionally + /// interactive, objects which display a significant amount of content, or any + /// object which has an accessible relation pointing to another object. The + /// displayed information, as a general rule, should be exposed through the + /// accessible name of the object. For labels which describe another widget, see + /// @LABEL. For text views, see @TEXT. For generic + /// containers, see @PANEL. For objects whose role is not known by the + /// implementor, see @UNKNOWN. @Since: 2.16. + Static, + /// An object that represents a mathematical fraction. @Since: 2.16. + MathFraction, + /// An object that represents a mathematical expression + /// displayed with a radical. @Since: 2.16. + MathRoot, + /// An object that contains text that is displayed as a + /// subscript. @Since: 2.16. + Subscript, + /// An object that contains text that is displayed as a + /// superscript. @Since: 2.16. + Superscript, + /// An object that represents a list of term-value + /// groups. A term-value group represents an individual description and consist + /// of one or more names (@DESCRIPTION_TERM) followed by one or more + /// values (@DESCRIPTION_VALUE). For each list, there should not be + /// more than one group with the same term name. @Since: 2.26. + DescriptionList, + /// An object that represents a term or phrase + /// with a corresponding definition. @Since: 2.26. + DescriptionTerm, + /// An object that represents the description, + /// definition, or value of a term. @Since: 2.26. + DescriptionValue, + /// An object that contains the text of a footnote. @Since: 2.26. + Footnote, + /// Content previously deleted or proposed to be + /// deleted, e.g. in revision history or a content view providing suggestions + /// from reviewers. @Since: 2.34. + ContentDeletion, + /// Content previously inserted or proposed to be + /// inserted, e.g. in revision history or a content view providing suggestions + /// from reviewers. @Since: 2.34. + ContentInsertion, + /// A run of content that is marked or highlighted, such as for + /// reference purposes, or to call it out as having a special purpose. If the + /// marked content has an associated section in the document elaborating on the + /// reason for the mark, then %RELATION_DETAILS should be used on the mark + /// to point to that associated section. In addition, the reciprocal relation + /// %RELATION_DETAILS_FOR should be used on the associated content section + /// to point back to the mark. @Since: 2.36. + Mark, + /// A container for content that is called out as a proposed + /// change from the current version of the document, such as by a reviewer of the + /// content. This role should include either %CONTENT_DELETION and/or + /// %CONTENT_INSERTION children, in any order, to indicate what the + /// actual change is. @Since: 2.36 + Suggestion, + /// Not a valid role, used for finding end of enumeration. + LastDefined, +} + +fn spi_display_name() -> Option { + var("AT_SPI_DISPLAY").ok().or( + match var("DISPLAY") { + Ok(display_env) if display_env.len() > 0 => { + match (display_env.rfind(':'), display_env.rfind('.')) { + (Some(i), Some(j)) if j > i => + Some(display_env[..j].to_string()), + _ => Some(display_env) + } + }, + _ => None + }) +} + +fn a11y_bus_address_from_x11() -> Option { + let (bridge_display, screen_num) = x11rb::connect(Some(&spi_display_name()?)).ok()?; + let root_window = &bridge_display.setup().roots[screen_num].root; + let reply = bridge_display.intern_atom(false, b"AT_SPI_BUS").ok()?; + let at_spi_bus = reply.reply().ok()?; + let address = bridge_display.get_property(false, *root_window, at_spi_bus.atom, AtomEnum::STRING, 0, 1024).ok()? + .reply().map_or(None, |data| String::from_utf8(data.value).ok()); + address +} + +fn a11y_bus_address_from_dbus() -> Option { + let session_bus = Connection::session().ok()?; + BusProxy::new(&session_bus).ok()? + .get_address().ok() +} + +pub fn a11y_bus() -> Option { + let mut address = match var("AT_SPI_BUS_ADDRESS") { + Ok(address) if address.len() > 0 => Some(address), + _ => None + }; + if address.is_none() && var("WAYLAND_DISPLAY").is_err() { + address = a11y_bus_address_from_x11(); + } + if address.is_none() { + address = a11y_bus_address_from_dbus(); + } + ConnectionBuilder::address(Address::Unix(address?.into())).ok()?.build().ok() +} diff --git a/platforms/linux/src/atspi/proxies.rs b/platforms/linux/src/atspi/proxies.rs new file mode 100644 index 000000000..3794f2530 --- /dev/null +++ b/platforms/linux/src/atspi/proxies.rs @@ -0,0 +1,37 @@ +use crate::atspi::{ObjectAddress, OwnedObjectAddress}; +use zbus::{Result, dbus_proxy}; + +#[dbus_proxy( + default_service = "org.a11y.Bus", + default_path = "/org/a11y/bus", + interface = "org.a11y.Bus", + gen_async = false +)] +pub trait Bus { + fn get_address(&self) -> Result; +} + +#[dbus_proxy(interface = "org.a11y.atspi.Socket")] +trait Socket { + fn embed<'a>(&self, plug: ObjectAddress<'a>) -> Result; + + fn unembed<'a>(&self, plug: ObjectAddress<'a>) -> Result<()>; + + #[dbus_proxy(signal)] + fn available(&self, socket: ObjectAddress<'_>) -> Result<()>; +} + +#[dbus_proxy(interface = "org.a11y.Status")] +pub trait Status { + #[dbus_proxy(property)] + fn is_enabled(&self) -> Result; + + #[DBusProxy(property)] + fn set_is_enabled(&self, value: bool) -> Result<()>; + + #[dbus_proxy(property)] + fn screen_reader_enabled(&self) -> Result; + + #[DBusProxy(property)] + fn set_screen_reader_enabled(&self, value: bool) -> Result<()>; +} diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs new file mode 100644 index 000000000..dc0186c07 --- /dev/null +++ b/platforms/linux/src/lib.rs @@ -0,0 +1,7 @@ +#[macro_use] extern crate zbus; + +mod atspi; +mod manager; +mod node; + +pub use manager::Manager; \ No newline at end of file diff --git a/platforms/linux/src/manager.rs b/platforms/linux/src/manager.rs new file mode 100644 index 000000000..32be23f7f --- /dev/null +++ b/platforms/linux/src/manager.rs @@ -0,0 +1,59 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use std::sync::Arc; + +use accesskit_consumer::{Tree, TreeChange}; +use accesskit_schema::TreeUpdate; + +use crate::atspi::a11y_bus; +use crate::node::PlatformNode; + +use zbus::blocking::Connection; + +pub struct Manager { + atspi_bus: Connection, + tree: Arc, +} + +impl Manager { + pub fn new(initial_state: TreeUpdate) -> Self { + Self { + atspi_bus: a11y_bus().unwrap(), + tree: Tree::new(initial_state), + } + } + + pub fn update(&self, update: TreeUpdate) { + self.tree.update_and_process_changes(update, |change| { + match change { + TreeChange::FocusMoved { + old_node: _, + new_node, + } => { + if let Some(new_node) = new_node { + //let platform_node = PlatformNode::new(&new_node, self.hwnd); + //let el: IRawElementProviderSimple = platform_node.into(); + //unsafe { UiaRaiseAutomationEvent(el, UIA_AutomationFocusChangedEventId) } + // .unwrap(); + } + } + TreeChange::NodeUpdated { old_node, new_node } => { + //let old_node = ResolvedPlatformNode::new(old_node, self.hwnd); + //let new_node = ResolvedPlatformNode::new(new_node, self.hwnd); + //new_node.raise_property_changes(&old_node); + } + // TODO: handle other events + _ => (), + }; + }); + } + + fn root_platform_node(&self) -> PlatformNode { + let reader = self.tree.read(); + let node = reader.root(); + PlatformNode::new(&node) + } +} diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs new file mode 100644 index 000000000..c216886f1 --- /dev/null +++ b/platforms/linux/src/node.rs @@ -0,0 +1,57 @@ +use accesskit_consumer::{Node, WeakNode}; +use crate::atspi::interfaces::AccessibleInterface; + +pub struct PlatformNode(WeakNode); + +impl PlatformNode { + pub(crate) fn new(node: &Node) -> Self { + Self(node.downgrade()) + } +} + +impl AccessibleInterface for PlatformNode { + fn name(&self) -> String { + self.0.map(|node| { + match node.name() { + None => String::new(), + Some(name) => name.to_string() + } + }).unwrap() + } + + fn description(&self) -> String { + todo!() + } + + fn parent(&self) -> Option { + todo!() + } + + fn child_count(&self) -> usize { + todo!() + } + + fn locale(&self) -> &str { + todo!() + } + + fn accessible_id(&self) -> String { + todo!() + } + + fn child_at_index(&self, index: usize) -> Option { + todo!() + } + + fn children(&self) -> Vec { + todo!() + } + + fn index_in_parent(&self) -> Option { + todo!() + } + + fn role(&self) -> crate::atspi::Role { + todo!() + } +} \ No newline at end of file From 9770914a1daf95bfd18c409de19d3987ee99d601 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 8 Nov 2021 20:17:48 +0100 Subject: [PATCH 02/69] Major refactoring --- platforms/linux/examples/hello_world.rs | 2 +- platforms/linux/src/atspi/bus.rs | 88 ++++++++++++ platforms/linux/src/atspi/interfaces.rs | 128 ------------------ .../linux/src/atspi/interfaces/accessible.rs | 91 +++++++++++++ .../linux/src/atspi/interfaces/application.rs | 111 +++++++++++++++ platforms/linux/src/atspi/interfaces/mod.rs | 5 + platforms/linux/src/atspi/mod.rs | 84 ++---------- platforms/linux/src/atspi/object_address.rs | 46 +++++++ platforms/linux/src/atspi/object_id.rs | 29 ++++ platforms/linux/src/atspi/proxies.rs | 7 +- platforms/linux/src/lib.rs | 2 + platforms/linux/src/manager.rs | 19 +-- platforms/linux/src/node.rs | 82 +++++++++-- 13 files changed, 467 insertions(+), 227 deletions(-) create mode 100644 platforms/linux/src/atspi/bus.rs delete mode 100644 platforms/linux/src/atspi/interfaces.rs create mode 100644 platforms/linux/src/atspi/interfaces/accessible.rs create mode 100644 platforms/linux/src/atspi/interfaces/application.rs create mode 100644 platforms/linux/src/atspi/interfaces/mod.rs create mode 100644 platforms/linux/src/atspi/object_address.rs create mode 100644 platforms/linux/src/atspi/object_id.rs diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index efcf7dffd..594cf675f 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -55,7 +55,7 @@ fn get_initial_state() -> TreeUpdate { static mut FOCUS: NodeId = BUTTON_1_ID; fn main() { - let manager = Manager::new(get_initial_state()); + let manager = Manager::new(String::from("hello_world"), String::from("ExampleUI"), String::from("0.1.0"), get_initial_state()).unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs new file mode 100644 index 000000000..ab2be0fd5 --- /dev/null +++ b/platforms/linux/src/atspi/bus.rs @@ -0,0 +1,88 @@ +use crate::atspi::{ + interfaces::*, + proxies::{BusProxy, SocketProxy}, + ObjectAddress +}; +use std::{ + env::var, + os::unix::net::{SocketAddr, UnixStream}, + str::FromStr +}; +use x11rb::{ + connection::Connection as _, + protocol::xproto::{AtomEnum, ConnectionExt} +}; +use zbus::{ + blocking::{ConnectionBuilder, Connection}, + Address +}; + +pub struct Bus(Connection); + +impl Bus { + pub fn a11y_bus() -> Option { + Some(Bus(a11y_bus()?)) + } + + pub fn register_root(&mut self, root: T) + where T: AccessibleInterface + ApplicationInterface + Send + Sync + 'static + { + self.0.object_server_mut().at("/org/a11y/atspi/accessible/root", ApplicationInterfaceObject(root)).unwrap(); + SocketProxy::new(&self.0) + .unwrap() + .embed(ObjectAddress::root( + self.0.unique_name().unwrap().as_ref())); + } +} + +fn spi_display_name() -> Option { + var("AT_SPI_DISPLAY").ok().or( + match var("DISPLAY") { + Ok(mut display_env) if display_env.len() > 0 => { + match (display_env.rfind(':'), display_env.rfind('.')) { + (Some(i), Some(j)) if j > i => { + display_env.truncate(j); + Some(display_env) + }, + _ => Some(display_env) + } + }, + _ => None + }) +} + +fn a11y_bus_address_from_x11() -> Option { + let (bridge_display, screen_num) = x11rb::connect(Some(&spi_display_name()?)).ok()?; + let root_window = &bridge_display.setup().roots[screen_num].root; + let reply = bridge_display.intern_atom(false, b"AT_SPI_BUS").ok()?; + let at_spi_bus = reply.reply().ok()?; + let address = bridge_display.get_property(false, *root_window, at_spi_bus.atom, AtomEnum::STRING, 0, 1024).ok()? + .reply().map_or(None, |data| String::from_utf8(data.value).ok()); + address +} + +fn a11y_bus_address_from_dbus() -> Option { + let session_bus = Connection::session().ok()?; + BusProxy::new(&session_bus).ok()? + .get_address().ok() +} + +fn a11y_bus() -> Option { + let mut address = match var("AT_SPI_BUS_ADDRESS") { + Ok(address) if address.len() > 0 => Some(address), + _ => None + }; + if address.is_none() && var("WAYLAND_DISPLAY").is_err() { + address = a11y_bus_address_from_x11(); + } + if address.is_none() { + address = a11y_bus_address_from_dbus(); + } + let address = address?; + let guid_index = address.find(',').unwrap_or(address.len()); + if address.starts_with("unix:abstract=") { + ConnectionBuilder::unix_stream(UnixStream::connect_addr(&SocketAddr::from_abstract_namespace(address[14..guid_index].as_bytes()).ok()?).ok()?).build().ok() + } else { + ConnectionBuilder::address(Address::from_str(address.get(0..guid_index)?).ok()?).ok()?.build().ok() + } +} diff --git a/platforms/linux/src/atspi/interfaces.rs b/platforms/linux/src/atspi/interfaces.rs deleted file mode 100644 index e58237a48..000000000 --- a/platforms/linux/src/atspi/interfaces.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::atspi::{OwnedObjectAddress, Role}; - -pub trait ApplicationInterface { - fn toolkit_name(&self) -> &str; - - fn toolkit_version(&self) -> &str; - - fn id(&self) -> i32; - - fn set_id(&mut self, id: i32); - - fn locale(&self, lctype: u32) -> &str; - - fn register_event_listener(&mut self, event: String); - - fn deregister_event_listener(&mut self, event: String); -} - -pub struct ApplicationInterfaceObject(T); - -#[dbus_interface(name = "org.a11y.atspi.Application")] -impl ApplicationInterfaceObject { - #[dbus_interface(property)] - fn toolkit_name(&self) -> &str { - self.0.toolkit_name() - } - - #[dbus_interface(property)] - fn version(&self) -> &str { - self.0.toolkit_version() - } - - #[dbus_interface(property)] - fn atspi_version(&self) -> &str { - "2.1" - } - - #[dbus_interface(property)] - fn id(&self) -> i32 { - self.0.id() - } - - #[dbus_interface(property)] - fn set_id(&mut self, id: i32) { - self.0.set_id(id) - } - - fn get_locale(&self, lctype: u32) -> &str { - self.0.locale(lctype) - } - - fn register_event_listener(&self, _event: String) {} - - fn deregister_event_listener(&self, _event: String) {} -} - -pub trait AccessibleInterface { - fn name(&self) -> String; - - fn description(&self) -> String; - - fn parent(&self) -> Option; - - fn child_count(&self) -> usize; - - fn locale(&self) -> &str; - - fn accessible_id(&self) -> String; - - fn child_at_index(&self, index: usize) -> Option; - - fn children(&self) -> Vec; - - fn index_in_parent(&self) -> Option; - - fn role(&self) -> Role; -} - -pub struct AccessibleInterfaceObject(T); - -#[dbus_interface(name = "org.a11y.atspi.Accessible")] -impl AccessibleInterfaceObject { - #[dbus_interface(property)] - fn name(&self) -> String { - self.0.name() - } - - #[dbus_interface(property)] - fn description(&self) -> String { - self.0.description() - } - - #[dbus_interface(property)] - fn parent(&self) -> OwnedObjectAddress { - self.0.parent().unwrap() - } - - #[dbus_interface(property)] - fn child_count(&self) -> i32 { - self.0.child_count() as i32 - } - - #[dbus_interface(property)] - fn locale(&self) -> &str { - "" - } - - #[dbus_interface(property)] - fn accessible_id(&self) -> String { - self.0.accessible_id() - } - - fn get_child_at_index(&self, index: i32) -> () {//ObjectAddress { - self.0.child_at_index(index as usize).unwrap(); - } - - fn get_children(&self) -> () {//&[ObjectAddress] { - self.0.children(); - } - - fn get_index_in_parent(&self) -> i32 { - self.0.index_in_parent().unwrap() as i32 - } - - fn get_role(&self) -> Role { - self.0.role() - } -} diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs new file mode 100644 index 000000000..3f900d699 --- /dev/null +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -0,0 +1,91 @@ +use crate::atspi::{ObjectAddress, ObjectId, Role}; +use std::convert::TryInto; +use zbus::names::OwnedUniqueName; +use zvariant::Str; + +pub trait AccessibleInterface { + fn name(&self) -> Str; + + fn description(&self) -> Str; + + fn parent(&self) -> Option; + + fn child_count(&self) -> usize; + + fn locale(&self) -> Str; + + fn id(&self) -> ObjectId; + + fn child_at_index(&self, index: usize) -> Option; + + fn children(&self) -> Vec; + + fn index_in_parent(&self) -> Option; + + fn role(&self) -> Role; +} + +pub struct AccessibleInterfaceObject { + bus_name: OwnedUniqueName, + object: T, +} + +#[dbus_interface(name = "org.a11y.atspi.Accessible")] +impl AccessibleInterfaceObject +where T: AccessibleInterface + Send + Sync + 'static +{ + #[dbus_interface(property)] + fn name(&self) -> Str { + self.object.name() + } + + #[dbus_interface(property)] + fn description(&self) -> Str { + self.object.description() + } + + #[dbus_interface(property)] + fn parent(&self) -> ObjectAddress { + if let Some(parent) = self.object.parent() { + ObjectAddress::accessible(self.bus_name.as_ref(), parent) + } else { + ObjectAddress::null(self.bus_name.as_ref()) + } + } + + #[dbus_interface(property)] + fn child_count(&self) -> i32 { + self.object.child_count().try_into().unwrap_or(0) + } + + #[dbus_interface(property)] + fn locale(&self) -> Str { + self.object.locale() + } + + #[dbus_interface(property)] + fn accessible_id(&self) -> ObjectId { + self.object.id() + } + + fn get_child_at_index(&self, index: i32) -> ObjectAddress { + index + .try_into() + .ok() + .map(|index| self.object.child_at_index(index)) + .flatten() + .map_or_else(|| ObjectAddress::null(self.bus_name.as_ref()), |id| ObjectAddress::accessible(self.bus_name.as_ref(), id)) + } + + fn get_children(&self) -> () {//&[ObjectAddress] { + self.object.children(); + } + + fn get_index_in_parent(&self) -> i32 { + self.object.index_in_parent().map_or(-1, |index| index.try_into().unwrap_or(-1)) + } + + fn get_role(&self) -> Role { + self.object.role() + } +} diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/linux/src/atspi/interfaces/application.rs new file mode 100644 index 000000000..ecc746857 --- /dev/null +++ b/platforms/linux/src/atspi/interfaces/application.rs @@ -0,0 +1,111 @@ +use crate::atspi::{ + interfaces::AccessibleInterface, + ObjectId, Role +}; +use zvariant::Str; + +pub trait ApplicationInterface { + fn name(&self) -> Str; + + fn children(&self) -> Vec; + + fn toolkit_name(&self) -> Str; + + fn toolkit_version(&self) -> Str; + + fn id(&self) -> i32; + + fn set_id(&mut self, id: i32); + + fn locale(&self, lctype: u32) -> Str; + + fn register_event_listener(&mut self, event: String); + + fn deregister_event_listener(&mut self, event: String); +} + +impl AccessibleInterface for T +where T: ApplicationInterface +{ + fn name(&self) -> Str { + self.name() + } + + fn description(&self) -> Str { + Str::default() + } + + fn parent(&self) -> Option { + todo!() + } + + fn child_count(&self) -> usize { + todo!() + } + + fn locale(&self) -> Str { + Str::default() + } + + fn id(&self) -> ObjectId { + unsafe { + ObjectId::from_str_unchecked("root") + } + } + + fn child_at_index(&self, index: usize) -> Option { + todo!() + } + + fn children(&self) -> Vec { + todo!() + } + + fn index_in_parent(&self) -> Option { + None + } + + fn role(&self) -> Role { + Role::Application + } +} + +pub struct ApplicationInterfaceObject(pub T); + +#[dbus_interface(name = "org.a11y.atspi.Application")] +impl ApplicationInterfaceObject +where T: ApplicationInterface + Send + Sync + 'static +{ + #[dbus_interface(property)] + fn toolkit_name(&self) -> Str { + self.0.toolkit_name() + } + + #[dbus_interface(property)] + fn version(&self) -> Str { + self.0.toolkit_version() + } + + #[dbus_interface(property)] + fn atspi_version(&self) -> &str { + "2.1" + } + + #[dbus_interface(property)] + fn id(&self) -> i32 { + self.0.id() + } + + #[dbus_interface(property)] + fn set_id(&mut self, id: i32) { + self.0.set_id(id) + } + + fn get_locale(&self, lctype: u32) -> Str { + self.0.locale(lctype) + } + + fn register_event_listener(&self, _event: String) {} + + fn deregister_event_listener(&self, _event: String) {} +} diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs new file mode 100644 index 000000000..0b1408aa4 --- /dev/null +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -0,0 +1,5 @@ +mod accessible; +mod application; + +pub use accessible::*; +pub use application::*; diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs index 824a65586..22aac9166 100644 --- a/platforms/linux/src/atspi/mod.rs +++ b/platforms/linux/src/atspi/mod.rs @@ -1,44 +1,16 @@ -use crate::atspi::proxies::BusProxy; use serde::{Deserialize, Serialize}; -use std::env::var; -use x11rb::{ - connection::Connection as _, - protocol::xproto::{AtomEnum, ConnectionExt} -}; -use zbus::{ - blocking::{Connection, ConnectionBuilder}, - names::{OwnedUniqueName, UniqueName}, - Address -}; +use zbus::names::OwnedUniqueName; use zvariant::{ derive::{Type, Value}, - ObjectPath, OwnedObjectPath + OwnedObjectPath }; +mod bus; pub mod interfaces; +mod object_address; +mod object_id; pub mod proxies; -#[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] -pub struct ObjectAddress<'a> { - #[serde(borrow)] - bus_name: UniqueName<'a>, - #[serde(borrow)] - path: ObjectPath<'a>, -} - -impl<'a> ObjectAddress<'a> { - pub fn new(bus_name: UniqueName<'a>, path: ObjectPath<'a>) -> ObjectAddress<'a> { - Self { bus_name, path } - } - - pub fn null(bus_name: UniqueName<'a>) -> ObjectAddress<'a> { - Self { - bus_name, - path: ObjectPath::from_str_unchecked("/org/a11y/atspi/null") - } - } -} - #[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] pub struct OwnedObjectAddress { bus_name: OwnedUniqueName, @@ -508,46 +480,6 @@ pub enum Role { LastDefined, } -fn spi_display_name() -> Option { - var("AT_SPI_DISPLAY").ok().or( - match var("DISPLAY") { - Ok(display_env) if display_env.len() > 0 => { - match (display_env.rfind(':'), display_env.rfind('.')) { - (Some(i), Some(j)) if j > i => - Some(display_env[..j].to_string()), - _ => Some(display_env) - } - }, - _ => None - }) -} - -fn a11y_bus_address_from_x11() -> Option { - let (bridge_display, screen_num) = x11rb::connect(Some(&spi_display_name()?)).ok()?; - let root_window = &bridge_display.setup().roots[screen_num].root; - let reply = bridge_display.intern_atom(false, b"AT_SPI_BUS").ok()?; - let at_spi_bus = reply.reply().ok()?; - let address = bridge_display.get_property(false, *root_window, at_spi_bus.atom, AtomEnum::STRING, 0, 1024).ok()? - .reply().map_or(None, |data| String::from_utf8(data.value).ok()); - address -} - -fn a11y_bus_address_from_dbus() -> Option { - let session_bus = Connection::session().ok()?; - BusProxy::new(&session_bus).ok()? - .get_address().ok() -} - -pub fn a11y_bus() -> Option { - let mut address = match var("AT_SPI_BUS_ADDRESS") { - Ok(address) if address.len() > 0 => Some(address), - _ => None - }; - if address.is_none() && var("WAYLAND_DISPLAY").is_err() { - address = a11y_bus_address_from_x11(); - } - if address.is_none() { - address = a11y_bus_address_from_dbus(); - } - ConnectionBuilder::address(Address::Unix(address?.into())).ok()?.build().ok() -} +pub use bus::Bus; +pub use object_address::*; +pub use object_id::*; diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs new file mode 100644 index 000000000..7b5f84fe3 --- /dev/null +++ b/platforms/linux/src/atspi/object_address.rs @@ -0,0 +1,46 @@ +use crate::atspi::ObjectId; +use serde::{Deserialize, Serialize}; +use zbus::names::UniqueName; +use zvariant::{ + derive::{Type, Value}, + ObjectPath +}; + +pub const ACCESSIBLE_PATH_PREFIX: &'static str = "/org/a11y/atspi/accessible/"; +pub const NULL_PATH: &'static str = "/org/a11y/atspi/null"; +pub const ROOT_PATH: &'static str = "/org/a11y/atspi/accessible/root"; + +#[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] +pub struct ObjectAddress<'a> { + #[serde(borrow)] + bus_name: UniqueName<'a>, + #[serde(borrow)] + path: ObjectPath<'a>, +} + +impl<'a> ObjectAddress<'a> { + pub fn new(bus_name: UniqueName<'a>, path: ObjectPath<'a>) -> ObjectAddress<'a> { + Self { bus_name, path } + } + + pub fn accessible(bus_name: UniqueName<'a>, id: ObjectId) -> ObjectAddress<'a> { + Self { + bus_name, + path: ObjectPath::from_string_unchecked(format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str())) + } + } + + pub fn null(bus_name: UniqueName<'a>) -> ObjectAddress<'a> { + Self { + bus_name, + path: ObjectPath::from_str_unchecked(NULL_PATH) + } + } + + pub fn root(bus_name: UniqueName<'a>) -> ObjectAddress<'a> { + Self { + bus_name, + path: ObjectPath::from_str_unchecked(ROOT_PATH) + } + } +} diff --git a/platforms/linux/src/atspi/object_id.rs b/platforms/linux/src/atspi/object_id.rs new file mode 100644 index 000000000..d2732563b --- /dev/null +++ b/platforms/linux/src/atspi/object_id.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; +use std::num::NonZeroU64; +use zvariant::{ + derive::{Type, Value}, + Str +}; + +#[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] +pub struct ObjectId<'a>(#[serde(borrow)] Str<'a>); + +impl<'a> ObjectId<'a> { + pub unsafe fn from_str_unchecked(id: &'a str) -> ObjectId<'a> { + Self(Str::from(id)) + } + + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl From for ObjectId<'static> { + fn from(value: NonZeroU64) -> Self { + Self(Str::from(value.to_string())) + } +} diff --git a/platforms/linux/src/atspi/proxies.rs b/platforms/linux/src/atspi/proxies.rs index 3794f2530..5e9c49228 100644 --- a/platforms/linux/src/atspi/proxies.rs +++ b/platforms/linux/src/atspi/proxies.rs @@ -11,7 +11,12 @@ pub trait Bus { fn get_address(&self) -> Result; } -#[dbus_proxy(interface = "org.a11y.atspi.Socket")] +#[dbus_proxy( + default_path = "/org/a11y/atspi/socket", + default_service = "org.a11y.atspi.Register", + gen_async = false, + interface = "org.a11y.atspi.Socket" +)] trait Socket { fn embed<'a>(&self, plug: ObjectAddress<'a>) -> Result; diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs index dc0186c07..5d87409df 100644 --- a/platforms/linux/src/lib.rs +++ b/platforms/linux/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(unix_socket_abstract)] + #[macro_use] extern crate zbus; mod atspi; diff --git a/platforms/linux/src/manager.rs b/platforms/linux/src/manager.rs index 32be23f7f..23e268eff 100644 --- a/platforms/linux/src/manager.rs +++ b/platforms/linux/src/manager.rs @@ -8,22 +8,23 @@ use std::sync::Arc; use accesskit_consumer::{Tree, TreeChange}; use accesskit_schema::TreeUpdate; -use crate::atspi::a11y_bus; -use crate::node::PlatformNode; - -use zbus::blocking::Connection; +use crate::atspi::Bus; +use crate::node::{PlatformNode, RootPlatformNode}; pub struct Manager { - atspi_bus: Connection, + atspi_bus: Bus, tree: Arc, } impl Manager { - pub fn new(initial_state: TreeUpdate) -> Self { - Self { - atspi_bus: a11y_bus().unwrap(), + pub fn new(app_name: String, toolkit_name: String, toolkit_version: String, initial_state: TreeUpdate) -> Option { + let mut atspi_bus = Bus::a11y_bus()?; + let app_node = RootPlatformNode::new(app_name, toolkit_name, toolkit_version); + atspi_bus.register_root(app_node); + Some(Self { + atspi_bus, tree: Tree::new(initial_state), - } + }) } pub fn update(&self, update: TreeUpdate) { diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index c216886f1..e5e7f97b8 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -1,5 +1,9 @@ use accesskit_consumer::{Node, WeakNode}; -use crate::atspi::interfaces::AccessibleInterface; +use crate::atspi::{ + interfaces::{AccessibleInterface, ApplicationInterface}, + ObjectId, Role +}; +use zvariant::Str; pub struct PlatformNode(WeakNode); @@ -10,20 +14,20 @@ impl PlatformNode { } impl AccessibleInterface for PlatformNode { - fn name(&self) -> String { + fn name(&self) -> Str { self.0.map(|node| { match node.name() { - None => String::new(), - Some(name) => name.to_string() + None => Str::default(), + Some(name) => Str::from(name.to_string()) } }).unwrap() } - fn description(&self) -> String { + fn description(&self) -> Str { todo!() } - fn parent(&self) -> Option { + fn parent(&self) -> Option { todo!() } @@ -31,19 +35,19 @@ impl AccessibleInterface for PlatformNode { todo!() } - fn locale(&self) -> &str { + fn locale(&self) -> Str { todo!() } - fn accessible_id(&self) -> String { + fn id(&self) -> ObjectId { todo!() } - fn child_at_index(&self, index: usize) -> Option { + fn child_at_index(&self, index: usize) -> Option { todo!() } - fn children(&self) -> Vec { + fn children(&self) -> Vec { todo!() } @@ -51,7 +55,61 @@ impl AccessibleInterface for PlatformNode { todo!() } - fn role(&self) -> crate::atspi::Role { + fn role(&self) -> Role { todo!() } -} \ No newline at end of file +} + +pub struct RootPlatformNode { + app_name: String, + app_id: i32, + toolkit_name: String, + toolkit_version: String, +} + +impl RootPlatformNode { + pub fn new(app_name: String, toolkit_name: String, toolkit_version: String) -> Self { + Self { + app_name, + app_id: -1, + toolkit_name, + toolkit_version + } + } +} + +impl ApplicationInterface for RootPlatformNode { + fn name(&self) -> Str { + Str::from(&self.app_name) + } + + fn children(&self) -> Vec { + Vec::new() + } + + fn toolkit_name(&self) -> Str { + Str::from(&self.toolkit_name) + } + + fn toolkit_version(&self) -> Str { + Str::from(&self.toolkit_version) + } + + fn id(&self) -> i32 { + self.app_id + } + + fn set_id(&mut self, id: i32) { + self.app_id = id; + } + + fn locale(&self, lctype: u32) -> Str { + Str::default() + } + + fn register_event_listener(&mut self, event: String) { + } + + fn deregister_event_listener(&mut self, event: String) { + } +} From 68d122cad403da528b8ab38ab5b4207115e4cc1b Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sat, 13 Nov 2021 20:19:16 +0100 Subject: [PATCH 03/69] Improve Application interface, implement basic Accessible interface members for nodes --- platforms/linux/Cargo.toml | 1 + platforms/linux/src/atspi/bus.rs | 28 +++- .../linux/src/atspi/interfaces/accessible.rs | 69 ++++++---- .../linux/src/atspi/interfaces/application.rs | 127 ++++++++++++++---- platforms/linux/src/atspi/interfaces/mod.rs | 5 + platforms/linux/src/atspi/mod.rs | 30 ++--- platforms/linux/src/atspi/object_address.rs | 24 +++- platforms/linux/src/atspi/object_id.rs | 13 ++ platforms/linux/src/atspi/object_ref.rs | 23 ++++ platforms/linux/src/atspi/proxies.rs | 9 +- platforms/linux/src/lib.rs | 5 + platforms/linux/src/manager.rs | 20 ++- platforms/linux/src/node.rs | 117 ++++++++++------ 13 files changed, 342 insertions(+), 129 deletions(-) create mode 100644 platforms/linux/src/atspi/object_ref.rs diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index 9262eb9a7..1629f1ea2 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] accesskit_consumer = { path = "../../consumer" } accesskit_schema = { path = "../../schema" } +parking_lot = "0.11.1" serde = "1.0" x11rb = "0.8.1" zbus = "2.0.0-beta.7" diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index ab2be0fd5..5ddc45114 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -1,11 +1,18 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + use crate::atspi::{ interfaces::*, - proxies::{BusProxy, SocketProxy}, - ObjectAddress + object_address::*, + proxies::{BusProxy, SocketProxy} }; +use parking_lot::RwLock; use std::{ env::var, os::unix::net::{SocketAddr, UnixStream}, + sync::Arc, str::FromStr }; use x11rb::{ @@ -24,14 +31,25 @@ impl Bus { Some(Bus(a11y_bus()?)) } + pub fn register_accessible(&mut self, object: T) -> bool + where T: AccessibleInterface + Send + Sync + 'static + { + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, object.id().as_str()); + self.0.object_server_mut().at(path, AccessibleInterfaceObject::new(self.0.unique_name().unwrap().to_owned(), object)).unwrap() + } + pub fn register_root(&mut self, root: T) where T: AccessibleInterface + ApplicationInterface + Send + Sync + 'static { - self.0.object_server_mut().at("/org/a11y/atspi/accessible/root", ApplicationInterfaceObject(root)).unwrap(); - SocketProxy::new(&self.0) + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, AccessibleInterface::id(&root).as_str()); + let root = Arc::new(RwLock::new(root)); + self.0.object_server_mut().at(path, ApplicationInterfaceObject(ApplicationObjectWrapper(root.clone()))).unwrap(); + self.register_accessible(ApplicationObjectWrapper(root.clone())); + let desktop_address = SocketProxy::new(&self.0) .unwrap() .embed(ObjectAddress::root( - self.0.unique_name().unwrap().as_ref())); + self.0.unique_name().unwrap().as_ref())).unwrap(); + root.write().set_desktop(desktop_address); } } diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 3f900d699..7af4d1624 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -1,24 +1,28 @@ -use crate::atspi::{ObjectAddress, ObjectId, Role}; +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use crate::atspi::{ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role}; use std::convert::TryInto; use zbus::names::OwnedUniqueName; -use zvariant::Str; pub trait AccessibleInterface { - fn name(&self) -> Str; + fn name(&self) -> String; - fn description(&self) -> Str; + fn description(&self) -> String; - fn parent(&self) -> Option; + fn parent(&self) -> Option; fn child_count(&self) -> usize; - fn locale(&self) -> Str; + fn locale(&self) -> String; fn id(&self) -> ObjectId; - fn child_at_index(&self, index: usize) -> Option; + fn child_at_index(&self, index: usize) -> Option; - fn children(&self) -> Vec; + fn children(&self) -> Vec; fn index_in_parent(&self) -> Option; @@ -30,26 +34,35 @@ pub struct AccessibleInterfaceObject { object: T, } +impl AccessibleInterfaceObject { + pub fn new(bus_name: OwnedUniqueName, object: T) -> Self { + Self { + bus_name, + object + } + } +} + #[dbus_interface(name = "org.a11y.atspi.Accessible")] impl AccessibleInterfaceObject where T: AccessibleInterface + Send + Sync + 'static { #[dbus_interface(property)] - fn name(&self) -> Str { + fn name(&self) -> String { self.object.name() } #[dbus_interface(property)] - fn description(&self) -> Str { + fn description(&self) -> String { self.object.description() } #[dbus_interface(property)] - fn parent(&self) -> ObjectAddress { - if let Some(parent) = self.object.parent() { - ObjectAddress::accessible(self.bus_name.as_ref(), parent) - } else { - ObjectAddress::null(self.bus_name.as_ref()) + fn parent(&self) -> OwnedObjectAddress { + match self.object.parent() { + Some(ObjectRef::Managed(id)) => ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), + Some(ObjectRef::Unmanaged(address)) => address, + None => ObjectAddress::null(self.bus_name.as_ref()).into() } } @@ -59,7 +72,7 @@ where T: AccessibleInterface + Send + Sync + 'static } #[dbus_interface(property)] - fn locale(&self) -> Str { + fn locale(&self) -> String { self.object.locale() } @@ -68,21 +81,25 @@ where T: AccessibleInterface + Send + Sync + 'static self.object.id() } - fn get_child_at_index(&self, index: i32) -> ObjectAddress { - index - .try_into() - .ok() - .map(|index| self.object.child_at_index(index)) - .flatten() - .map_or_else(|| ObjectAddress::null(self.bus_name.as_ref()), |id| ObjectAddress::accessible(self.bus_name.as_ref(), id)) + fn get_child_at_index(&self, index: i32) -> OwnedObjectAddress { + match index.try_into().ok().map(|index| self.object.child_at_index(index)).flatten() { + Some(ObjectRef::Managed(id)) => ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), + Some(ObjectRef::Unmanaged(address)) => address, + None => ObjectAddress::null(self.bus_name.as_ref()).into() + } } - fn get_children(&self) -> () {//&[ObjectAddress] { - self.object.children(); + fn get_children(&self) -> Vec { + self.object.children().into_iter().map(|child| { + match child { + ObjectRef::Managed(id) => ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), + ObjectRef::Unmanaged(address) => address + } + }).collect() } fn get_index_in_parent(&self) -> i32 { - self.object.index_in_parent().map_or(-1, |index| index.try_into().unwrap_or(-1)) + self.object.index_in_parent().map(|index| index.try_into().ok()).flatten().unwrap_or(-1) } fn get_role(&self) -> Role { diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/linux/src/atspi/interfaces/application.rs index ecc746857..8d3cf4978 100644 --- a/platforms/linux/src/atspi/interfaces/application.rs +++ b/platforms/linux/src/atspi/interfaces/application.rs @@ -1,23 +1,37 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + use crate::atspi::{ interfaces::AccessibleInterface, - ObjectId, Role + ObjectId, ObjectRef, OwnedObjectAddress, Role }; -use zvariant::Str; +use parking_lot::RwLock; +use std::sync::Arc; pub trait ApplicationInterface { - fn name(&self) -> Str; + fn name(&self) -> String; + + fn child_count(&self) -> usize; + + fn child_at_index(&self, index: usize) -> Option; - fn children(&self) -> Vec; + fn children(&self) -> Vec; - fn toolkit_name(&self) -> Str; + fn toolkit_name(&self) -> String; - fn toolkit_version(&self) -> Str; + fn toolkit_version(&self) -> String; - fn id(&self) -> i32; + fn id(&self) -> Option; fn set_id(&mut self, id: i32); - fn locale(&self, lctype: u32) -> Str; + fn locale(&self, lctype: u32) -> String; + + fn desktop(&self) -> Option; + + fn set_desktop(&mut self, address: OwnedObjectAddress); fn register_event_listener(&mut self, event: String); @@ -27,38 +41,36 @@ pub trait ApplicationInterface { impl AccessibleInterface for T where T: ApplicationInterface { - fn name(&self) -> Str { + fn name(&self) -> String { self.name() } - fn description(&self) -> Str { - Str::default() + fn description(&self) -> String { + String::new() } - fn parent(&self) -> Option { - todo!() + fn parent(&self) -> Option { + self.desktop().map(|desktop| desktop.into()) } fn child_count(&self) -> usize { - todo!() + self.child_count() } - fn locale(&self) -> Str { - Str::default() + fn locale(&self) -> String { + String::new() } - fn id(&self) -> ObjectId { - unsafe { - ObjectId::from_str_unchecked("root") - } + fn id(&self) -> ObjectId<'static> { + ObjectId::root() } - fn child_at_index(&self, index: usize) -> Option { - todo!() + fn child_at_index(&self, index: usize) -> Option { + self.child_at_index(index) } - fn children(&self) -> Vec { - todo!() + fn children(&self) -> Vec { + self.children() } fn index_in_parent(&self) -> Option { @@ -70,6 +82,65 @@ where T: ApplicationInterface } } +#[derive(Clone, Debug)] +pub struct ApplicationObjectWrapper(pub Arc>); + +impl ApplicationInterface for ApplicationObjectWrapper +where T: ApplicationInterface +{ + fn name(&self) -> String { + self.0.read().name() + } + + fn child_count(&self) -> usize { + self.0.read().child_count() + } + + fn child_at_index(&self, index: usize) -> Option { + self.0.read().child_at_index(index) + } + + fn children(&self) -> Vec { + self.0.read().children() + } + + fn toolkit_name(&self) -> String { + self.0.read().toolkit_name() + } + + fn toolkit_version(&self) -> String { + self.0.read().toolkit_version() + } + + fn id(&self) -> Option { + self.0.read().id() + } + + fn set_id(&mut self, id: i32) { + self.0.write().set_id(id); + } + + fn locale(&self, lctype: u32) -> String { + self.0.read().locale(lctype) + } + + fn desktop(&self) -> Option { + self.0.write().desktop() + } + + fn set_desktop(&mut self, address: OwnedObjectAddress) { + self.0.write().set_desktop(address) + } + + fn register_event_listener(&mut self, event: String) { + self.0.write().register_event_listener(event) + } + + fn deregister_event_listener(&mut self, event: String) { + self.0.write().deregister_event_listener(event) + } +} + pub struct ApplicationInterfaceObject(pub T); #[dbus_interface(name = "org.a11y.atspi.Application")] @@ -77,12 +148,12 @@ impl ApplicationInterfaceObject where T: ApplicationInterface + Send + Sync + 'static { #[dbus_interface(property)] - fn toolkit_name(&self) -> Str { + fn toolkit_name(&self) -> String { self.0.toolkit_name() } #[dbus_interface(property)] - fn version(&self) -> Str { + fn version(&self) -> String { self.0.toolkit_version() } @@ -93,7 +164,7 @@ where T: ApplicationInterface + Send + Sync + 'static #[dbus_interface(property)] fn id(&self) -> i32 { - self.0.id() + self.0.id().unwrap_or(-1) } #[dbus_interface(property)] @@ -101,7 +172,7 @@ where T: ApplicationInterface + Send + Sync + 'static self.0.set_id(id) } - fn get_locale(&self, lctype: u32) -> Str { + fn get_locale(&self, lctype: u32) -> String { self.0.locale(lctype) } diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index 0b1408aa4..1b32290be 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -1,3 +1,8 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + mod accessible; mod application; diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs index 22aac9166..0e5de1a5a 100644 --- a/platforms/linux/src/atspi/mod.rs +++ b/platforms/linux/src/atspi/mod.rs @@ -1,35 +1,20 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + use serde::{Deserialize, Serialize}; -use zbus::names::OwnedUniqueName; use zvariant::{ - derive::{Type, Value}, - OwnedObjectPath + derive::Type }; mod bus; pub mod interfaces; mod object_address; mod object_id; +mod object_ref; pub mod proxies; -#[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] -pub struct OwnedObjectAddress { - bus_name: OwnedUniqueName, - path: OwnedObjectPath, -} - -impl OwnedObjectAddress { - pub fn new(bus_name: S, path: O) -> Self - where - S: Into, - O: Into - { - Self { - bus_name: bus_name.into(), - path: path.into() - } - } -} - /// Enumeration used by interface #AtspiAccessible to specify the role /// of an #AtspiAccessible object. #[derive(Clone, Copy, Debug, Deserialize, Serialize, Type)] @@ -483,3 +468,4 @@ pub enum Role { pub use bus::Bus; pub use object_address::*; pub use object_id::*; +pub use object_ref::*; diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs index 7b5f84fe3..b3065fadc 100644 --- a/platforms/linux/src/atspi/object_address.rs +++ b/platforms/linux/src/atspi/object_address.rs @@ -1,9 +1,14 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + use crate::atspi::ObjectId; use serde::{Deserialize, Serialize}; -use zbus::names::UniqueName; +use zbus::names::{OwnedUniqueName, UniqueName}; use zvariant::{ derive::{Type, Value}, - ObjectPath + ObjectPath, OwnedObjectPath }; pub const ACCESSIBLE_PATH_PREFIX: &'static str = "/org/a11y/atspi/accessible/"; @@ -44,3 +49,18 @@ impl<'a> ObjectAddress<'a> { } } } + +#[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] +pub struct OwnedObjectAddress { + bus_name: OwnedUniqueName, + path: OwnedObjectPath, +} + +impl From> for OwnedObjectAddress { + fn from(value: ObjectAddress) -> OwnedObjectAddress { + OwnedObjectAddress { + bus_name: value.bus_name.into(), + path: value.path.into() + } + } +} diff --git a/platforms/linux/src/atspi/object_id.rs b/platforms/linux/src/atspi/object_id.rs index d2732563b..e04b3fa03 100644 --- a/platforms/linux/src/atspi/object_id.rs +++ b/platforms/linux/src/atspi/object_id.rs @@ -1,3 +1,8 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + use serde::{Deserialize, Serialize}; use std::num::NonZeroU64; use zvariant::{ @@ -13,6 +18,10 @@ impl<'a> ObjectId<'a> { Self(Str::from(id)) } + pub fn root() -> ObjectId<'static> { + ObjectId(Str::from("root")) + } + pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } @@ -20,6 +29,10 @@ impl<'a> ObjectId<'a> { pub fn as_str(&self) -> &str { self.0.as_str() } + + pub fn to_owned(&self) -> ObjectId<'static> { + ObjectId(self.0.to_owned()) + } } impl From for ObjectId<'static> { diff --git a/platforms/linux/src/atspi/object_ref.rs b/platforms/linux/src/atspi/object_ref.rs new file mode 100644 index 000000000..24250b611 --- /dev/null +++ b/platforms/linux/src/atspi/object_ref.rs @@ -0,0 +1,23 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use crate::atspi::{ObjectId, OwnedObjectAddress}; + +pub enum ObjectRef { + Managed(ObjectId<'static>), + Unmanaged(OwnedObjectAddress), +} + +impl From> for ObjectRef { + fn from(value: ObjectId<'static>) -> ObjectRef { + ObjectRef::Managed(value) + } +} + +impl From for ObjectRef { + fn from(value: OwnedObjectAddress) -> ObjectRef { + ObjectRef::Unmanaged(value) + } +} diff --git a/platforms/linux/src/atspi/proxies.rs b/platforms/linux/src/atspi/proxies.rs index 5e9c49228..96d78107a 100644 --- a/platforms/linux/src/atspi/proxies.rs +++ b/platforms/linux/src/atspi/proxies.rs @@ -1,3 +1,8 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + use crate::atspi::{ObjectAddress, OwnedObjectAddress}; use zbus::{Result, dbus_proxy}; @@ -12,8 +17,8 @@ pub trait Bus { } #[dbus_proxy( - default_path = "/org/a11y/atspi/socket", - default_service = "org.a11y.atspi.Register", + default_path = "/org/a11y/atspi/accessible/root", + default_service = "org.a11y.atspi.Registry", gen_async = false, interface = "org.a11y.atspi.Socket" )] diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs index 5d87409df..60816265a 100644 --- a/platforms/linux/src/lib.rs +++ b/platforms/linux/src/lib.rs @@ -1,3 +1,8 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + #![feature(unix_socket_abstract)] #[macro_use] extern crate zbus; diff --git a/platforms/linux/src/manager.rs b/platforms/linux/src/manager.rs index 23e268eff..ee478cc53 100644 --- a/platforms/linux/src/manager.rs +++ b/platforms/linux/src/manager.rs @@ -5,7 +5,7 @@ use std::sync::Arc; -use accesskit_consumer::{Tree, TreeChange}; +use accesskit_consumer::{Node, Tree, TreeChange}; use accesskit_schema::TreeUpdate; use crate::atspi::Bus; @@ -19,11 +19,25 @@ pub struct Manager { impl Manager { pub fn new(app_name: String, toolkit_name: String, toolkit_version: String, initial_state: TreeUpdate) -> Option { let mut atspi_bus = Bus::a11y_bus()?; - let app_node = RootPlatformNode::new(app_name, toolkit_name, toolkit_version); + let tree = Tree::new(initial_state); + let app_node = RootPlatformNode::new(app_name, toolkit_name, toolkit_version, tree.clone()); + let mut objects_to_add = Vec::new(); + + fn add_children(node: Node, to_add: &mut Vec) { + for child in node.unignored_children() { + to_add.push(PlatformNode::new(&child)); + add_children(child, to_add); + } + } + + add_children(tree.read().root(), &mut objects_to_add); + for node in objects_to_add { + atspi_bus.register_accessible(node); + } atspi_bus.register_root(app_node); Some(Self { atspi_bus, - tree: Tree::new(initial_state), + tree }) } diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index e5e7f97b8..f3a9da151 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -1,9 +1,15 @@ -use accesskit_consumer::{Node, WeakNode}; +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use accesskit_consumer::{Node, Tree, WeakNode}; +use accesskit_schema::Role; use crate::atspi::{ interfaces::{AccessibleInterface, ApplicationInterface}, - ObjectId, Role + ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole }; -use zvariant::Str; +use std::sync::Arc; pub struct PlatformNode(WeakNode); @@ -14,40 +20,39 @@ impl PlatformNode { } impl AccessibleInterface for PlatformNode { - fn name(&self) -> Str { - self.0.map(|node| { - match node.name() { - None => Str::default(), - Some(name) => Str::from(name.to_string()) - } - }).unwrap() + fn name(&self) -> String { + self.0.map(|node| node.name().map(|name| name.to_string())).flatten().unwrap_or(String::new()) } - fn description(&self) -> Str { - todo!() + fn description(&self) -> String { + String::new() } - fn parent(&self) -> Option { - todo!() + fn parent(&self) -> Option { + Some(self + .0 + .map(|node| node.parent().map(|parent| ObjectId::from(parent.id().0).to_owned().into())) + .flatten() + .unwrap_or(ObjectId::root().into())) } fn child_count(&self) -> usize { - todo!() + self.0.map(|node| node.unignored_children().count()).unwrap_or(0) } - fn locale(&self) -> Str { - todo!() + fn locale(&self) -> String { + String::new() } - fn id(&self) -> ObjectId { - todo!() + fn id(&self) -> ObjectId<'static> { + self.0.map(|node| ObjectId::from(node.id().0).to_owned()).unwrap() } - fn child_at_index(&self, index: usize) -> Option { - todo!() + fn child_at_index(&self, index: usize) -> Option { + self.0.map(|node| node.unignored_children().nth(index).map(|child| ObjectId::from(child.id().0).to_owned().into())).flatten() } - fn children(&self) -> Vec { + fn children(&self) -> Vec { todo!() } @@ -55,23 +60,33 @@ impl AccessibleInterface for PlatformNode { todo!() } - fn role(&self) -> Role { - todo!() + fn role(&self) -> AtspiRole { + self.0.map(|node| { + match node.role() { + Role::Button => AtspiRole::PushButton, + Role::Window => AtspiRole::Frame, + _ => unimplemented!() + } + }).unwrap_or(AtspiRole::Invalid) } } pub struct RootPlatformNode { app_name: String, - app_id: i32, + app_id: Option, + desktop_address: Option, + tree: Arc, toolkit_name: String, toolkit_version: String, } impl RootPlatformNode { - pub fn new(app_name: String, toolkit_name: String, toolkit_version: String) -> Self { + pub fn new(app_name: String, toolkit_name: String, toolkit_version: String, tree: Arc) -> Self { Self { app_name, - app_id: -1, + app_id: None, + desktop_address: None, + tree, toolkit_name, toolkit_version } @@ -79,37 +94,57 @@ impl RootPlatformNode { } impl ApplicationInterface for RootPlatformNode { - fn name(&self) -> Str { - Str::from(&self.app_name) + fn name(&self) -> String { + self.app_name.clone() + } + + fn child_count(&self) -> usize { + 1 } - fn children(&self) -> Vec { - Vec::new() + fn child_at_index(&self, index: usize) -> Option { + if index == 0 { + Some(ObjectId::from(self.tree.read().root().id().0).to_owned().into()) + } else { + None + } } - fn toolkit_name(&self) -> Str { - Str::from(&self.toolkit_name) + fn children(&self) -> Vec { + vec![ObjectId::from(self.tree.read().root().id().0).to_owned().into()] } - fn toolkit_version(&self) -> Str { - Str::from(&self.toolkit_version) + fn toolkit_name(&self) -> String { + self.toolkit_name.clone() } - fn id(&self) -> i32 { + fn toolkit_version(&self) -> String { + self.toolkit_version.clone() + } + + fn id(&self) -> Option { self.app_id } fn set_id(&mut self, id: i32) { - self.app_id = id; + self.app_id = Some(id); + } + + fn locale(&self, lctype: u32) -> String { + String::new() + } + + fn desktop(&self) -> Option { + self.desktop_address.clone() } - fn locale(&self, lctype: u32) -> Str { - Str::default() + fn set_desktop(&mut self, address: OwnedObjectAddress) { + self.desktop_address = Some(address); } - fn register_event_listener(&mut self, event: String) { + fn register_event_listener(&mut self, _: String) { } - fn deregister_event_listener(&mut self, event: String) { + fn deregister_event_listener(&mut self, _: String) { } } From 2505ca72daded58a3123fe16c922344319e80140 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 5 Dec 2021 21:08:02 +0100 Subject: [PATCH 04/69] Experiment with states and events --- platforms/linux/Cargo.toml | 2 + platforms/linux/examples/hello_world.rs | 64 ++-- .../linux/src/{manager.rs => adapter.rs} | 43 ++- platforms/linux/src/atspi/bus.rs | 90 +++++- .../linux/src/atspi/interfaces/accessible.rs | 45 ++- .../linux/src/atspi/interfaces/application.rs | 54 ++-- .../linux/src/atspi/interfaces/events.rs | 150 +++++++++ platforms/linux/src/atspi/interfaces/mod.rs | 16 + platforms/linux/src/atspi/mod.rs | 4 +- platforms/linux/src/atspi/object_id.rs | 8 +- platforms/linux/src/atspi/object_ref.rs | 7 + platforms/linux/src/atspi/state.rs | 301 ++++++++++++++++++ platforms/linux/src/lib.rs | 4 +- platforms/linux/src/node.rs | 56 +++- 14 files changed, 746 insertions(+), 98 deletions(-) rename platforms/linux/src/{manager.rs => adapter.rs} (57%) create mode 100644 platforms/linux/src/atspi/interfaces/events.rs create mode 100644 platforms/linux/src/atspi/state.rs diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index 1629f1ea2..9d1433c8f 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -8,6 +8,8 @@ edition = "2018" [dependencies] accesskit_consumer = { path = "../../consumer" } accesskit_schema = { path = "../../schema" } +async-io = "1.4.1" +enumflags2 = "0.7.1" parking_lot = "0.11.1" serde = "1.0" x11rb = "0.8.1" diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index 594cf675f..69c4b70a5 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -1,8 +1,13 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + use accesskit_schema::{Node, NodeId, Role, StringEncoding, Tree, TreeId, TreeUpdate}; -use accesskit_linux::Manager; +use accesskit_linux::Adapter; use std::num::NonZeroU64; use winit::{ - event::{Event, WindowEvent}, + event::{Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; @@ -20,19 +25,11 @@ fn get_tree(is_window_focused: bool) -> Tree { } } -fn get_button_1(name: &str) -> Node { - Node { - name: Some(name.into()), - focusable: true, - ..Node::new(BUTTON_1_ID, Role::Button) - } -} - -fn get_button_2(name: &str) -> Node { +fn make_button(id: NodeId, name: &str) -> Node { Node { name: Some(name.into()), focusable: true, - ..Node::new(BUTTON_2_ID, Role::Button) + ..Node::new(id, Role::Button) } } @@ -42,8 +39,8 @@ fn get_initial_state() -> TreeUpdate { name: Some(WINDOW_TITLE.into()), ..Node::new(WINDOW_ID, Role::Window) }; - let button_1 = get_button_1("Button 1"); - let button_2 = get_button_2("Button 2"); + let button_1 = make_button(BUTTON_1_ID, "Button 1"); + let button_2 = make_button(BUTTON_2_ID, "Button 2"); TreeUpdate { clear: None, nodes: vec![root, button_1, button_2], @@ -55,12 +52,11 @@ fn get_initial_state() -> TreeUpdate { static mut FOCUS: NodeId = BUTTON_1_ID; fn main() { - let manager = Manager::new(String::from("hello_world"), String::from("ExampleUI"), String::from("0.1.0"), get_initial_state()).unwrap(); + let adapter = Adapter::new(String::from("hello_world"), String::from("ExampleUI"), String::from("0.1.0"), get_initial_state()).unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() .with_title(WINDOW_TITLE) - .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .build(&event_loop) .unwrap(); @@ -69,9 +65,41 @@ fn main() { match event { Event::WindowEvent { - event: WindowEvent::CloseRequested, + event, window_id, - } if window_id == window.id() => *control_flow = ControlFlow::Exit, + } if window_id == window.id() => { + match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::Focused(true) => adapter.window_activated(WINDOW_ID), + WindowEvent::Focused(false) => adapter.window_deactivated(WINDOW_ID), + WindowEvent::KeyboardInput { input, .. } => { + match input { + KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Tab), .. + } => { + unsafe { + FOCUS = if FOCUS == BUTTON_1_ID { + BUTTON_2_ID + } else { + BUTTON_1_ID + }; + adapter.update(TreeUpdate { + clear: None, + nodes: vec![], + tree: Some(Tree { + focus: Some(FOCUS), + ..Tree::new(TreeId("test".into()), StringEncoding::Utf8) + }), + root: Some(WINDOW_ID) + }); + } + }, + _ => { } + } + }, + _ => (), + } + } Event::MainEventsCleared => { window.request_redraw(); } diff --git a/platforms/linux/src/manager.rs b/platforms/linux/src/adapter.rs similarity index 57% rename from platforms/linux/src/manager.rs rename to platforms/linux/src/adapter.rs index ee478cc53..e341ec08d 100644 --- a/platforms/linux/src/manager.rs +++ b/platforms/linux/src/adapter.rs @@ -6,17 +6,20 @@ use std::sync::Arc; use accesskit_consumer::{Node, Tree, TreeChange}; -use accesskit_schema::TreeUpdate; +use accesskit_schema::{NodeId, TreeUpdate}; -use crate::atspi::Bus; +use crate::atspi::{ + interfaces::{ObjectEvent, WindowEvent}, + Bus +}; use crate::node::{PlatformNode, RootPlatformNode}; -pub struct Manager { +pub struct Adapter { atspi_bus: Bus, tree: Arc, } -impl Manager { +impl Adapter { pub fn new(app_name: String, toolkit_name: String, toolkit_version: String, initial_state: TreeUpdate) -> Option { let mut atspi_bus = Bus::a11y_bus()?; let tree = Tree::new(initial_state); @@ -30,11 +33,12 @@ impl Manager { } } + objects_to_add.push(PlatformNode::new(&tree.read().root())); add_children(tree.read().root(), &mut objects_to_add); for node in objects_to_add { - atspi_bus.register_accessible(node); + atspi_bus.register_accessible_interface(node); } - atspi_bus.register_root(app_node); + atspi_bus.register_application_interface(app_node); Some(Self { atspi_bus, tree @@ -45,14 +49,17 @@ impl Manager { self.tree.update_and_process_changes(update, |change| { match change { TreeChange::FocusMoved { - old_node: _, + old_node, new_node, } => { + if let Some(old_node) = old_node { + let old_node = PlatformNode::new(&old_node); + self.atspi_bus.emit_object_event(&old_node, ObjectEvent::FocusLost).unwrap(); + } if let Some(new_node) = new_node { - //let platform_node = PlatformNode::new(&new_node, self.hwnd); - //let el: IRawElementProviderSimple = platform_node.into(); - //unsafe { UiaRaiseAutomationEvent(el, UIA_AutomationFocusChangedEventId) } - // .unwrap(); + let new_node = PlatformNode::new(&new_node); + self.atspi_bus.emit_object_event(&new_node, ObjectEvent::FocusGained).unwrap(); + self.atspi_bus.emit_focus_event(&new_node).unwrap(); } } TreeChange::NodeUpdated { old_node, new_node } => { @@ -66,6 +73,20 @@ impl Manager { }); } + pub fn window_activated(&self, window_id: NodeId) { + let reader = self.tree.read(); + let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); + self.atspi_bus.emit_object_event(&node, ObjectEvent::Activated); + self.atspi_bus.emit_window_event(&node, WindowEvent::Activated); + } + + pub fn window_deactivated(&self, window_id: NodeId) { + let reader = self.tree.read(); + let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); + self.atspi_bus.emit_object_event(&node, ObjectEvent::Deactivated); + self.atspi_bus.emit_window_event(&node, WindowEvent::Deactivated); + } + fn root_platform_node(&self) -> PlatformNode { let reader = self.tree.read(); let node = reader.root(); diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 5ddc45114..f72e9b50b 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -3,6 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. +use async_io::block_on; use crate::atspi::{ interfaces::*, object_address::*, @@ -21,7 +22,7 @@ use x11rb::{ }; use zbus::{ blocking::{ConnectionBuilder, Connection}, - Address + Address, InterfaceDeref, Result }; pub struct Bus(Connection); @@ -31,25 +32,86 @@ impl Bus { Some(Bus(a11y_bus()?)) } - pub fn register_accessible(&mut self, object: T) -> bool - where T: AccessibleInterface + Send + Sync + 'static + pub fn register_accessible_interface(&mut self, object: T) -> Result + where T: Accessible { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, object.id().as_str()); - self.0.object_server_mut().at(path, AccessibleInterfaceObject::new(self.0.unique_name().unwrap().to_owned(), object)).unwrap() + if self.0.object_server_mut().at(path.clone(), AccessibleInterface::new(self.0.unique_name().unwrap().to_owned(), object.clone()))? { + let interfaces = object.interfaces(); + if interfaces.contains(Interface::FocusEvents) { + self.register_focus_events_interface(&path)?; + } + if interfaces.contains(Interface::ObjectEvents) { + self.register_object_events_interface(&path)?; + } + if interfaces.contains(Interface::WindowEvents) { + self.register_window_events_interface(&path, object) + } else { + Ok(true) + } + } else { + Ok(false) + } } - pub fn register_root(&mut self, root: T) - where T: AccessibleInterface + ApplicationInterface + Send + Sync + 'static + pub fn register_application_interface(&mut self, root: T) -> Result + where T: Application { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, AccessibleInterface::id(&root).as_str()); + println!("Registering on {:?}", self.0.unique_name().unwrap()); + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, Accessible::id(&root).as_str()); let root = Arc::new(RwLock::new(root)); - self.0.object_server_mut().at(path, ApplicationInterfaceObject(ApplicationObjectWrapper(root.clone()))).unwrap(); - self.register_accessible(ApplicationObjectWrapper(root.clone())); - let desktop_address = SocketProxy::new(&self.0) - .unwrap() - .embed(ObjectAddress::root( - self.0.unique_name().unwrap().as_ref())).unwrap(); - root.write().set_desktop(desktop_address); + let registered = self.0.object_server_mut().at(path, ApplicationInterface(ApplicationInterfaceWrapper(root.clone())))?; + if registered && self.register_accessible_interface(ApplicationInterfaceWrapper(root.clone()))? { + let desktop_address = SocketProxy::new(&self.0) + .unwrap() + .embed(ObjectAddress::root( + self.0.unique_name().unwrap().as_ref()))?; + root.write().set_desktop(desktop_address); + Ok(true) + } else { + Ok(false) + } + } + + fn register_focus_events_interface(&mut self, path: &str) -> Result { + self.0.object_server_mut().at(path, FocusEventsInterface { }) + } + + fn register_object_events_interface(&mut self, path: &str) -> Result { + self.0.object_server_mut().at(path, ObjectEventsInterface { }) + } + + fn register_window_events_interface(&mut self, path: &str, object: T) -> Result + where T: Accessible + { + self.0.object_server_mut().at(path, WindowEventsInterface(object)) + } + + pub fn emit_focus_event(&self, target: &T) -> Result<()> + where T: Accessible + { + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); + self.0.object_server().with(path, |iface: InterfaceDeref<'_, FocusEventsInterface>, ctxt| { + block_on(iface.focused(&ctxt)) + }) + } + + pub fn emit_object_event(&self, target: &T, event: ObjectEvent) -> Result<()> + where T: Accessible + { + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); + self.0.object_server().with(path, |iface: InterfaceDeref<'_, ObjectEventsInterface>, ctxt| { + block_on(iface.emit(event, &ctxt)) + }) + } + + pub fn emit_window_event(&self, target: &T, event: WindowEvent) -> Result<()> + where T: Accessible + { + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); + self.0.object_server().with(path, |iface: InterfaceDeref<'_, WindowEventsInterface::>, ctxt| { + block_on(iface.emit(event, &ctxt)) + }) } } diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 7af4d1624..0accac9ef 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -3,11 +3,14 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role}; +use crate::atspi::{ + interfaces::{Interface, Interfaces}, + ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role, StateSet +}; use std::convert::TryInto; use zbus::names::OwnedUniqueName; -pub trait AccessibleInterface { +pub trait Accessible: Clone + Send + Sync + 'static { fn name(&self) -> String; fn description(&self) -> String; @@ -27,14 +30,18 @@ pub trait AccessibleInterface { fn index_in_parent(&self) -> Option; fn role(&self) -> Role; + + fn state(&self) -> StateSet; + + fn interfaces(&self) -> Interfaces; } -pub struct AccessibleInterfaceObject { +pub struct AccessibleInterface { bus_name: OwnedUniqueName, object: T, } -impl AccessibleInterfaceObject { +impl AccessibleInterface { pub fn new(bus_name: OwnedUniqueName, object: T) -> Self { Self { bus_name, @@ -43,10 +50,13 @@ impl AccessibleInterfaceObject { } } +const INTERFACES: &[&'static str] = &[ + "org.a11y.atspi.Accessible", + "org.a11y.atspi.Application" +]; + #[dbus_interface(name = "org.a11y.atspi.Accessible")] -impl AccessibleInterfaceObject -where T: AccessibleInterface + Send + Sync + 'static -{ +impl AccessibleInterface { #[dbus_interface(property)] fn name(&self) -> String { self.object.name() @@ -81,12 +91,12 @@ where T: AccessibleInterface + Send + Sync + 'static self.object.id() } - fn get_child_at_index(&self, index: i32) -> OwnedObjectAddress { - match index.try_into().ok().map(|index| self.object.child_at_index(index)).flatten() { + fn get_child_at_index(&self, index: i32) -> (OwnedObjectAddress,) { + (match index.try_into().ok().map(|index| self.object.child_at_index(index)).flatten() { Some(ObjectRef::Managed(id)) => ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), Some(ObjectRef::Unmanaged(address)) => address, None => ObjectAddress::null(self.bus_name.as_ref()).into() - } + },) } fn get_children(&self) -> Vec { @@ -105,4 +115,19 @@ where T: AccessibleInterface + Send + Sync + 'static fn get_role(&self) -> Role { self.object.role() } + + fn get_state(&self) -> StateSet { + self.object.state() + } + + fn get_interfaces(&self) -> Vec<&'static str> { + let mut interfaces = Vec::with_capacity(INTERFACES.len()); + for interface in self.object.interfaces().iter() { + if interface > Interface::Application { + break; + } + interfaces.push(INTERFACES[(interface as u8).trailing_zeros() as usize]); + } + interfaces + } } diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/linux/src/atspi/interfaces/application.rs index 8d3cf4978..fcb21707e 100644 --- a/platforms/linux/src/atspi/interfaces/application.rs +++ b/platforms/linux/src/atspi/interfaces/application.rs @@ -4,13 +4,13 @@ // the LICENSE-MIT file), at your option. use crate::atspi::{ - interfaces::AccessibleInterface, - ObjectId, ObjectRef, OwnedObjectAddress, Role + interfaces::{Accessible, Interface, Interfaces}, + ObjectId, ObjectRef, OwnedObjectAddress, Role, StateSet }; use parking_lot::RwLock; use std::sync::Arc; -pub trait ApplicationInterface { +pub trait Application: Accessible { fn name(&self) -> String; fn child_count(&self) -> usize; @@ -38,11 +38,9 @@ pub trait ApplicationInterface { fn deregister_event_listener(&mut self, event: String); } -impl AccessibleInterface for T -where T: ApplicationInterface -{ +impl Accessible for T { fn name(&self) -> String { - self.name() + Application::name(self) } fn description(&self) -> String { @@ -54,7 +52,7 @@ where T: ApplicationInterface } fn child_count(&self) -> usize { - self.child_count() + Application::child_count(self) } fn locale(&self) -> String { @@ -66,11 +64,11 @@ where T: ApplicationInterface } fn child_at_index(&self, index: usize) -> Option { - self.child_at_index(index) + Application::child_at_index(self, index) } fn children(&self) -> Vec { - self.children() + Application::children(self) } fn index_in_parent(&self) -> Option { @@ -80,28 +78,34 @@ where T: ApplicationInterface fn role(&self) -> Role { Role::Application } + + fn state(&self) -> StateSet { + StateSet::empty() + } + + fn interfaces(&self) -> Interfaces { + Interface::Accessible | Interface::Application + } } #[derive(Clone, Debug)] -pub struct ApplicationObjectWrapper(pub Arc>); +pub struct ApplicationInterfaceWrapper(pub Arc>); -impl ApplicationInterface for ApplicationObjectWrapper -where T: ApplicationInterface -{ +impl Application for ApplicationInterfaceWrapper { fn name(&self) -> String { - self.0.read().name() + Application::name(&*self.0.read()) } fn child_count(&self) -> usize { - self.0.read().child_count() + Application::child_count(&*self.0.read()) } fn child_at_index(&self, index: usize) -> Option { - self.0.read().child_at_index(index) + Application::child_at_index(&*self.0.read(), index) } fn children(&self) -> Vec { - self.0.read().children() + Application::children(&*self.0.read()) } fn toolkit_name(&self) -> String { @@ -113,7 +117,7 @@ where T: ApplicationInterface } fn id(&self) -> Option { - self.0.read().id() + Application::id(&*self.0.read()) } fn set_id(&mut self, id: i32) { @@ -121,7 +125,7 @@ where T: ApplicationInterface } fn locale(&self, lctype: u32) -> String { - self.0.read().locale(lctype) + Application::locale(&*self.0.read(), lctype) } fn desktop(&self) -> Option { @@ -141,12 +145,10 @@ where T: ApplicationInterface } } -pub struct ApplicationInterfaceObject(pub T); +pub struct ApplicationInterface(pub T); #[dbus_interface(name = "org.a11y.atspi.Application")] -impl ApplicationInterfaceObject -where T: ApplicationInterface + Send + Sync + 'static -{ +impl ApplicationInterface { #[dbus_interface(property)] fn toolkit_name(&self) -> String { self.0.toolkit_name() @@ -164,7 +166,7 @@ where T: ApplicationInterface + Send + Sync + 'static #[dbus_interface(property)] fn id(&self) -> i32 { - self.0.id().unwrap_or(-1) + Application::id(&self.0).unwrap_or(-1) } #[dbus_interface(property)] @@ -173,7 +175,7 @@ where T: ApplicationInterface + Send + Sync + 'static } fn get_locale(&self, lctype: u32) -> String { - self.0.locale(lctype) + Application::locale(&self.0, lctype) } fn register_event_listener(&self, _event: String) {} diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs new file mode 100644 index 000000000..0c345aa3b --- /dev/null +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -0,0 +1,150 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use crate::atspi::interfaces::Accessible; +use std::collections::HashMap; +use zbus::{SignalContext, Result, dbus_interface}; +use zvariant::Value; + +pub struct FocusEventsInterface; + +impl FocusEventsInterface { + pub async fn focused(&self, ctxt: &SignalContext<'_>) -> Result<()> { + FocusEventsInterface::focus(ctxt, "", 0, 0, 0i32.into(), HashMap::new()).await + } +} + +#[dbus_interface(name = "org.a11y.atspi.Event.Focus")] +impl FocusEventsInterface { + #[dbus_interface(signal)] + async fn focus( + ctxt: &SignalContext<'_>, + minor: &str, + detail1: i32, + detail2: i32, + any_data: Value<'_>, + properties: HashMap> + ) -> Result<()>; +} + +pub enum ObjectEvent { + Activated, + Deactivated, + FocusGained, + FocusLost, +} + +pub struct ObjectEventsInterface; + +impl ObjectEventsInterface { + pub async fn emit(&self, event: ObjectEvent, ctxt: &SignalContext<'_>) -> Result<()> { + let properties = HashMap::new(); + match event { + ObjectEvent::Activated => + ObjectEventsInterface::state_changed(ctxt, "active", 1, 0, 0i32.into(), properties).await, + ObjectEvent::Deactivated => + ObjectEventsInterface::state_changed(ctxt, "active", 0, 0, 0i32.into(), properties).await, + ObjectEvent::FocusGained => + ObjectEventsInterface::state_changed(ctxt, "focused", 1, 0, 0i32.into(), properties).await, + ObjectEvent::FocusLost => + ObjectEventsInterface::state_changed(ctxt, "focused", 0, 0, 0i32.into(), properties).await + } + } +} + +#[dbus_interface(name = "org.a11y.atspi.Event.Object")] +impl ObjectEventsInterface { + #[dbus_interface(signal)] + async fn state_changed( + ctxt: &SignalContext<'_>, + minor: &str, + detail1: i32, + detail2: i32, + any_data: Value<'_>, + properties: HashMap> + ) -> Result<()>; +} + +pub enum WindowEvent { + Activated, + Closed, + Created, + Deactivated, + Destroyed +} + +pub struct WindowEventsInterface(pub T); + +impl WindowEventsInterface { + pub async fn emit(&self, event: WindowEvent, ctxt: &SignalContext<'_>) -> Result<()> { + let name = self.0.name().into(); + let properties = HashMap::new(); + match event { + WindowEvent::Activated => + WindowEventsInterface::::activate(ctxt, "", 0, 0, name, properties).await, + WindowEvent::Closed => + WindowEventsInterface::::close(ctxt, "", 0, 0, name, properties).await, + WindowEvent::Created => + WindowEventsInterface::::create(ctxt, "", 0, 0, name, properties).await, + WindowEvent::Deactivated => + WindowEventsInterface::::deactivate(ctxt, "", 0, 0, name, properties).await, + WindowEvent::Destroyed => + WindowEventsInterface::::destroy(ctxt, "", 0, 0, name, properties).await + } + } +} + +#[dbus_interface(name = "org.a11y.atspi.Event.Window")] +impl WindowEventsInterface { + #[dbus_interface(signal)] + async fn activate( + ctxt: &SignalContext<'_>, + minor: &str, + detail1: i32, + detail2: i32, + any_data: Value<'_>, + properties: HashMap> + ) -> Result<()>; + + #[dbus_interface(signal)] + async fn close( + ctxt: &SignalContext<'_>, + minor: &str, + detail1: i32, + detail2: i32, + any_data: Value<'_>, + properties: HashMap> + ) -> Result<()>; + + #[dbus_interface(signal)] + async fn create( + ctxt: &SignalContext<'_>, + minor: &str, + detail1: i32, + detail2: i32, + any_data: Value<'_>, + properties: HashMap> + ) -> Result<()>; + + #[dbus_interface(signal)] + async fn deactivate( + ctxt: &SignalContext<'_>, + minor: &str, + detail1: i32, + detail2: i32, + any_data: Value<'_>, + properties: HashMap> + ) -> Result<()>; + + #[dbus_interface(signal)] + async fn destroy( + ctxt: &SignalContext<'_>, + minor: &str, + detail1: i32, + detail2: i32, + any_data: Value<'_>, + properties: HashMap> + ) -> Result<()>; +} diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index 1b32290be..6bd913ed0 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -3,8 +3,24 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. +use enumflags2::{BitFlags, bitflags}; mod accessible; mod application; +mod events; + +#[bitflags] +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, PartialOrd)] +pub enum Interface { + Accessible, + Application, + FocusEvents, + ObjectEvents, + WindowEvents, +} + +pub type Interfaces = BitFlags; pub use accessible::*; pub use application::*; +pub use events::*; diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs index 0e5de1a5a..c8c8d3fe2 100644 --- a/platforms/linux/src/atspi/mod.rs +++ b/platforms/linux/src/atspi/mod.rs @@ -14,10 +14,11 @@ mod object_address; mod object_id; mod object_ref; pub mod proxies; +mod state; /// Enumeration used by interface #AtspiAccessible to specify the role /// of an #AtspiAccessible object. -#[derive(Clone, Copy, Debug, Deserialize, Serialize, Type)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, Type)] pub enum Role { /// A role indicating an error condition, such as /// uninitialized Role data. @@ -469,3 +470,4 @@ pub use bus::Bus; pub use object_address::*; pub use object_id::*; pub use object_ref::*; +pub use state::*; diff --git a/platforms/linux/src/atspi/object_id.rs b/platforms/linux/src/atspi/object_id.rs index e04b3fa03..5b2937db0 100644 --- a/platforms/linux/src/atspi/object_id.rs +++ b/platforms/linux/src/atspi/object_id.rs @@ -3,8 +3,8 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. +use accesskit_schema::NodeId; use serde::{Deserialize, Serialize}; -use std::num::NonZeroU64; use zvariant::{ derive::{Type, Value}, Str @@ -35,8 +35,8 @@ impl<'a> ObjectId<'a> { } } -impl From for ObjectId<'static> { - fn from(value: NonZeroU64) -> Self { - Self(Str::from(value.to_string())) +impl From for ObjectId<'static> { + fn from(value: NodeId) -> Self { + Self(Str::from(value.0.to_string())) } } diff --git a/platforms/linux/src/atspi/object_ref.rs b/platforms/linux/src/atspi/object_ref.rs index 24250b611..86486e06f 100644 --- a/platforms/linux/src/atspi/object_ref.rs +++ b/platforms/linux/src/atspi/object_ref.rs @@ -3,6 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. +use accesskit_schema::NodeId; use crate::atspi::{ObjectId, OwnedObjectAddress}; pub enum ObjectRef { @@ -10,6 +11,12 @@ pub enum ObjectRef { Unmanaged(OwnedObjectAddress), } +impl From for ObjectRef { + fn from(value: NodeId) -> ObjectRef { + ObjectRef::Managed(value.into()) + } +} + impl From> for ObjectRef { fn from(value: ObjectId<'static>) -> ObjectRef { ObjectRef::Managed(value) diff --git a/platforms/linux/src/atspi/state.rs b/platforms/linux/src/atspi/state.rs new file mode 100644 index 000000000..9b4d933a8 --- /dev/null +++ b/platforms/linux/src/atspi/state.rs @@ -0,0 +1,301 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use enumflags2::{bitflags, BitFlags, FromBitsError}; +use serde::{ + de::{Deserialize, Deserializer, SeqAccess, Visitor, self}, + ser::{Serialize, Serializer, SerializeSeq} +}; +use std::fmt; +use zvariant::{Signature, Type}; + +/// Enumeration used by various interfaces indicating every possible state +/// an #AtspiAccessible object can assume. +#[bitflags] +#[repr(u64)] +#[derive(Clone, Copy, Debug)] +pub enum State { + /// Indicates an invalid state - probably an error condition. + Invalid, + /// Indicates a window is currently the active window, or + /// an object is the active subelement within a container or table. + /// @ACTIVE should not be used for objects which have + /// #FOCUSABLE or #SELECTABLE: Those objects should use + /// @FOCUSED and @SELECTED respectively. + /// @ACTIVE is a means to indicate that an object which is not + /// focusable and not selectable is the currently-active item within its + /// parent container. + Active, + /// Indicates that the object is armed. + Armed, + /// Indicates the current object is busy, i.e. onscreen + /// representation is in the process of changing, or the object is + /// temporarily unavailable for interaction due to activity already in progress. + Busy, + /// Indicates this object is currently checked. + Checked, + /// Indicates this object is collapsed. + Collapsed, + /// Indicates that this object no longer has a valid + /// backing widget (for instance, if its peer object has been destroyed). + Defunct, + /// Indicates the user can change the contents of this object. + Editable, + /// Indicates that this object is enabled, i.e. that it + /// currently reflects some application state. Objects that are "greyed out" + /// may lack this state, and may lack the @SENSITIVE if direct + /// user interaction cannot cause them to acquire @ENABLED. + /// See @SENSITIVE. + Enabled, + /// Indicates this object allows progressive + /// disclosure of its children. + Expandable, + /// Indicates this object is expanded. + Expanded, + /// Indicates this object can accept keyboard focus, + /// which means all events resulting from typing on the keyboard will + /// normally be passed to it when it has focus. + Focusable, + /// Indicates this object currently has the keyboard focus. + Focused, + /// Indicates that the object has an associated tooltip. + HasTooltip, + /// Indicates the orientation of this object is horizontal. + Horizontal, + /// Indicates this object is minimized and is + /// represented only by an icon. + Iconified, + /// Indicates something must be done with this object + /// before the user can interact with an object in a different window. + Modal, + /// Indicates this (text) object can contain multiple + /// lines of text. + MultiLine, + /// Indicates this object allows more than one of + /// its children to be selected at the same time, or in the case of text + /// objects, that the object supports non-contiguous text selections. + Multiselectable, + /// Indicates this object paints every pixel within its + /// rectangular region. It also indicates an alpha value of unity, if it + /// supports alpha blending. + Opaque, + /// Indicates this object is currently pressed. + Pressed, + /// Indicates the size of this object's size is not fixed. + Resizable, + /// Indicates this object is the child of an object + /// that allows its children to be selected and that this child is one of + /// those children that can be selected. + Selectable, + /// Indicates this object is the child of an object that + /// allows its children to be selected and that this child is one of those + /// children that has been selected. + Selected, + /// Indicates this object is sensitive, e.g. to user + /// interaction. @SENSITIVE usually accompanies. + /// @ENABLED for user-actionable controls, but may be found in the + /// absence of @ENABLED if the current visible state of the control + /// is "disconnected" from the application state. In such cases, direct user + /// interaction can often result in the object gaining @SENSITIVE, + /// for instance if a user makes an explicit selection using an object whose + /// current state is ambiguous or undefined. See @ENABLED, + /// @INDETERMINATE. + Sensitive, + /// Indicates this object, the object's parent, the + /// object's parent's parent, and so on, are all 'shown' to the end-user, + /// i.e. subject to "exposure" if blocking or obscuring objects do not + /// interpose between this object and the top of the window stack. + Showing, + /// Indicates this (text) object can contain only a + /// single line of text. + SingleLine, + /// Indicates that the information returned for this object + /// may no longer be synchronized with the application state. This can occur + /// if the object has @TRANSIENT, and can also occur towards the + /// end of the object peer's lifecycle. + Stale, + /// Indicates this object is transient. + Transient, + /// Indicates the orientation of this object is vertical; + /// for example this state may appear on such objects as scrollbars, text + /// objects (with vertical text flow), separators, etc. + Vertical, + /// Indicates this object is visible, e.g. has been + /// explicitly marked for exposure to the user. @VISIBLE is no + /// guarantee that the object is actually unobscured on the screen, only that + /// it is 'potentially' visible, barring obstruction, being scrolled or clipped + /// out of the field of view, or having an ancestor container that has not yet + /// made visible. A widget is potentially onscreen if it has both + /// @VISIBLE and @SHOWING. The absence of + /// @VISIBLE and @SHOWING is + /// semantically equivalent to saying that an object is 'hidden'. + Visible, + /// Indicates that "active-descendant-changed" + /// event is sent when children become 'active' (i.e. are selected or + /// navigated to onscreen). Used to prevent need to enumerate all children + /// in very large containers, like tables. The presence of + /// @MANAGES_DESCENDANTS is an indication to the client that the + /// children should not, and need not, be enumerated by the client. + /// Objects implementing this state are expected to provide relevant state + /// notifications to listening clients, for instance notifications of + /// visibility changes and activation of their contained child objects, without + /// the client having previously requested references to those children. + ManagesDescendants, + /// Indicates that a check box or other boolean + /// indicator is in a state other than checked or not checked. This + /// usually means that the boolean value reflected or controlled by the + /// object does not apply consistently to the entire current context. + /// For example, a checkbox for the "Bold" attribute of text may have + /// @INDETERMINATE if the currently selected text contains a mixture + /// of weight attributes. In many cases interacting with a + /// @INDETERMINATE object will cause the context's corresponding + /// boolean attribute to be homogenized, whereupon the object will lose + /// @INDETERMINATE and a corresponding state-changed event will be + /// fired. + Indeterminate, + /// Indicates that user interaction with this object is + /// 'required' from the user, for instance before completing the + /// processing of a form. + Required, + /// Indicates that an object's onscreen content + /// is truncated, e.g. a text value in a spreadsheet cell. + Truncated, + /// Indicates this object's visual representation is + /// dynamic, not static. This state may be applied to an object during an + /// animated 'effect' and be removed from the object once its visual + /// representation becomes static. Some applications, notably content viewers, + /// may not be able to detect all kinds of animated content. Therefore the + /// absence of this state should not be taken as + /// definitive evidence that the object's visual representation is + /// static; this state is advisory. + Animated, + /// This object has indicated an error condition + /// due to failure of input validation. For instance, a form control may + /// acquire this state in response to invalid or malformed user input. + InvalidEntry, + /// This state indicates that the object + /// in question implements some form of typeahead or + /// pre-selection behavior whereby entering the first character of one or more + /// sub-elements causes those elements to scroll into view or become + /// selected. Subsequent character input may narrow the selection further as + /// long as one or more sub-elements match the string. This state is normally + /// only useful and encountered on objects that implement #AtspiSelection. + /// In some cases the typeahead behavior may result in full or partial + /// completion of the data in the input field, in which case + /// these input events may trigger text-changed events from the source. + SupportsAutocompletion, + /// This state indicates that the object in + /// question supports text selection. It should only be exposed on objects + /// which implement the #AtspiText interface, in order to distinguish this state + /// from @SELECTABLE, which infers that the object in question is a + /// selectable child of an object which implements #AtspiSelection. While + /// similar, text selection and subelement selection are distinct operations. + SelectableText, + /// This state indicates that the object in question is + /// the 'default' interaction object in a dialog, i.e. the one that gets + /// activated if the user presses "Enter" when the dialog is initially + /// posted. + IsDefault, + /// This state indicates that the object (typically a + /// hyperlink) has already been activated or invoked, with the result that + /// some backing data has been downloaded or rendered. + Visited, + /// Indicates this object has the potential to + /// be checked, such as a checkbox or toggle-able table cell. @Since: + /// 2.12 + Checkable, + /// Indicates that the object has a popup + /// context menu or sub-level menu which may or may not be + /// showing. This means that activation renders conditional content. + /// Note that ordinary tooltips are not considered popups in this + /// context. @Since: 2.12 + HasPopup, + /// Indicates that an object which is ENABLED and + /// SENSITIVE has a value which can be read, but not modified, by the + /// user. @Since: 2.16 + ReadOnly, + /// This value of the enumeration should not be used + /// as a parameter, it indicates the number of items in the #AtspiStateType + /// enumeration. + LastDefined, +} + +pub struct StateSet(BitFlags); + +impl StateSet { + pub fn from_bits(bits: u64) -> Result> { + Ok(BitFlags::::from_bits(bits)?.into()) + } + + pub fn empty() -> StateSet { + BitFlags::::empty().into() + } + + pub fn bits(&self) -> u64 { + self.0.bits() + } + + pub fn insert>>(&mut self, other: B) { + self.0.insert(other); + } +} + +impl<'de> Deserialize<'de> for StateSet { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + struct StateSetVisitor; + + impl<'de> Visitor<'de> for StateSetVisitor { + type Value = StateSet; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence comprised of two u32 that represents a valid StateSet") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de> + { + match SeqAccess::next_element::>(&mut seq)? { + Some(vec) => { + let len = vec.len(); + if len != 2 { + return Err(de::Error::invalid_length(len, &"Vec with two elements")); + } + Ok(StateSet::from_bits(0).unwrap()) + }, + None => Err(de::Error::custom("Vec with two elements")) + } + } + } + + deserializer.deserialize_seq(StateSetVisitor) + } +} + +impl Serialize for StateSet { + fn serialize(&self, serializer: S) -> Result + where S: Serializer + { + let mut seq = serializer.serialize_seq(Some(2))?; + let bits = self.bits(); + seq.serialize_element(&(bits as u32))?; + seq.serialize_element(&((bits >> 32) as u32))?; + seq.end() + } +} + +impl Type for StateSet { + fn signature() -> Signature<'static> { + Signature::from_str_unchecked("au") + } +} + +impl From> for StateSet { + fn from(value: BitFlags) -> Self { + Self(value) + } +} diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs index 60816265a..8f7702f8e 100644 --- a/platforms/linux/src/lib.rs +++ b/platforms/linux/src/lib.rs @@ -7,8 +7,8 @@ #[macro_use] extern crate zbus; +mod adapter; mod atspi; -mod manager; mod node; -pub use manager::Manager; \ No newline at end of file +pub use adapter::Adapter; diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index f3a9da151..351c839fa 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -6,11 +6,12 @@ use accesskit_consumer::{Node, Tree, WeakNode}; use accesskit_schema::Role; use crate::atspi::{ - interfaces::{AccessibleInterface, ApplicationInterface}, - ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole + interfaces::{Accessible, Application, Interface, Interfaces}, + ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole, State, StateSet }; use std::sync::Arc; +#[derive(Clone)] pub struct PlatformNode(WeakNode); impl PlatformNode { @@ -19,7 +20,7 @@ impl PlatformNode { } } -impl AccessibleInterface for PlatformNode { +impl Accessible for PlatformNode { fn name(&self) -> String { self.0.map(|node| node.name().map(|name| name.to_string())).flatten().unwrap_or(String::new()) } @@ -31,13 +32,13 @@ impl AccessibleInterface for PlatformNode { fn parent(&self) -> Option { Some(self .0 - .map(|node| node.parent().map(|parent| ObjectId::from(parent.id().0).to_owned().into())) + .map(|node| node.parent().map(|parent| parent.id().into())) .flatten() .unwrap_or(ObjectId::root().into())) } fn child_count(&self) -> usize { - self.0.map(|node| node.unignored_children().count()).unwrap_or(0) + self.0.map(|node| node.children().count()).unwrap_or(0) } fn locale(&self) -> String { @@ -45,19 +46,19 @@ impl AccessibleInterface for PlatformNode { } fn id(&self) -> ObjectId<'static> { - self.0.map(|node| ObjectId::from(node.id().0).to_owned()).unwrap() + self.0.map(|node| node.id().into()).unwrap() } fn child_at_index(&self, index: usize) -> Option { - self.0.map(|node| node.unignored_children().nth(index).map(|child| ObjectId::from(child.id().0).to_owned().into())).flatten() + self.0.map(|node| node.children().nth(index).map(|child| child.id().into())).flatten() } fn children(&self) -> Vec { - todo!() + self.0.map(|node| node.children().map(|child| child.id().into()).collect()).unwrap_or(Vec::new()) } fn index_in_parent(&self) -> Option { - todo!() + self.0.map(|node| node.parent_and_index().map(|(_, index)| index)).flatten() } fn role(&self) -> AtspiRole { @@ -69,8 +70,39 @@ impl AccessibleInterface for PlatformNode { } }).unwrap_or(AtspiRole::Invalid) } + + fn state(&self) -> StateSet { + self.0.map(|node| { + if node.role() == Role::Window { + (State::Active | State::Sensitive | State::Showing | State::Visible).into() + } else { + let mut state: StateSet = (State::Enabled | State::Sensitive | State::Showing | State::Visible).into(); + if node.data().focusable { + state.insert(State::Focusable); + if node.is_focused() { + state.insert(State::Focused); + } + } + state + } + }).unwrap() + } + + fn interfaces(&self) -> Interfaces { + self.0.map(|node| { + let mut interfaces: Interfaces = Interface::Accessible | Interface::ObjectEvents; + if node.role() == Role::Window { + interfaces.insert(Interface::WindowEvents); + } + if node.data().focusable { + interfaces.insert(Interface::FocusEvents); + } + interfaces + }).unwrap() + } } +#[derive(Clone)] pub struct RootPlatformNode { app_name: String, app_id: Option, @@ -93,7 +125,7 @@ impl RootPlatformNode { } } -impl ApplicationInterface for RootPlatformNode { +impl Application for RootPlatformNode { fn name(&self) -> String { self.app_name.clone() } @@ -104,14 +136,14 @@ impl ApplicationInterface for RootPlatformNode { fn child_at_index(&self, index: usize) -> Option { if index == 0 { - Some(ObjectId::from(self.tree.read().root().id().0).to_owned().into()) + Some(self.tree.read().root().id().into()) } else { None } } fn children(&self) -> Vec { - vec![ObjectId::from(self.tree.read().root().id().0).to_owned().into()] + vec![self.tree.read().root().id().into()] } fn toolkit_name(&self) -> String { From 065b451ef1b291c92224ccbe9b997941d18acf5d Mon Sep 17 00:00:00 2001 From: DataTriny Date: Thu, 9 Dec 2021 17:43:59 +0100 Subject: [PATCH 05/69] Fix accesskit_linux after rebase --- platforms/linux/Cargo.toml | 2 +- platforms/linux/examples/hello_world.rs | 20 ++++++++------------ platforms/linux/src/adapter.rs | 2 +- platforms/linux/src/atspi/object_id.rs | 2 +- platforms/linux/src/atspi/object_ref.rs | 2 +- platforms/linux/src/node.rs | 2 +- 6 files changed, 13 insertions(+), 17 deletions(-) diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index 9d1433c8f..d1176674c 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -6,8 +6,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +accesskit = { path = "../../common" } accesskit_consumer = { path = "../../consumer" } -accesskit_schema = { path = "../../schema" } async-io = "1.4.1" enumflags2 = "0.7.1" parking_lot = "0.11.1" diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index 69c4b70a5..cc8b350ee 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit_schema::{Node, NodeId, Role, StringEncoding, Tree, TreeId, TreeUpdate}; +use accesskit::{Node, NodeId, Role, StringEncoding, Tree, TreeId, TreeUpdate}; use accesskit_linux::Adapter; use std::num::NonZeroU64; use winit::{ @@ -18,10 +18,9 @@ const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(1) }); const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(3) }); -fn get_tree(is_window_focused: bool) -> Tree { +fn get_tree() -> Tree { Tree { - focus: is_window_focused.then(|| unsafe { FOCUS }), - ..Tree::new(TreeId("test".into()), StringEncoding::Utf8) + ..Tree::new(TreeId("test".into()), WINDOW_ID, StringEncoding::Utf8) } } @@ -35,7 +34,7 @@ fn make_button(id: NodeId, name: &str) -> Node { fn get_initial_state() -> TreeUpdate { let root = Node { - children: Box::new([BUTTON_1_ID, BUTTON_2_ID]), + children: vec![BUTTON_1_ID, BUTTON_2_ID], name: Some(WINDOW_TITLE.into()), ..Node::new(WINDOW_ID, Role::Window) }; @@ -44,8 +43,8 @@ fn get_initial_state() -> TreeUpdate { TreeUpdate { clear: None, nodes: vec![root, button_1, button_2], - tree: Some(get_tree(false)), - root: Some(WINDOW_ID), + tree: Some(get_tree()), + focus: Some(unsafe { FOCUS }), } } @@ -86,11 +85,8 @@ fn main() { adapter.update(TreeUpdate { clear: None, nodes: vec![], - tree: Some(Tree { - focus: Some(FOCUS), - ..Tree::new(TreeId("test".into()), StringEncoding::Utf8) - }), - root: Some(WINDOW_ID) + focus: Some(FOCUS), + tree: None }); } }, diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index e341ec08d..4c3e4c9ef 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -5,8 +5,8 @@ use std::sync::Arc; +use accesskit::{NodeId, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChange}; -use accesskit_schema::{NodeId, TreeUpdate}; use crate::atspi::{ interfaces::{ObjectEvent, WindowEvent}, diff --git a/platforms/linux/src/atspi/object_id.rs b/platforms/linux/src/atspi/object_id.rs index 5b2937db0..f88a77589 100644 --- a/platforms/linux/src/atspi/object_id.rs +++ b/platforms/linux/src/atspi/object_id.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit_schema::NodeId; +use accesskit::NodeId; use serde::{Deserialize, Serialize}; use zvariant::{ derive::{Type, Value}, diff --git a/platforms/linux/src/atspi/object_ref.rs b/platforms/linux/src/atspi/object_ref.rs index 86486e06f..8fe9e7aa9 100644 --- a/platforms/linux/src/atspi/object_ref.rs +++ b/platforms/linux/src/atspi/object_ref.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit_schema::NodeId; +use accesskit::NodeId; use crate::atspi::{ObjectId, OwnedObjectAddress}; pub enum ObjectRef { diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 351c839fa..1c0f9b2bb 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -3,8 +3,8 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. +use accesskit::Role; use accesskit_consumer::{Node, Tree, WeakNode}; -use accesskit_schema::Role; use crate::atspi::{ interfaces::{Accessible, Application, Interface, Interfaces}, ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole, State, StateSet From 438002e990743f17c12de5255ec411a0d27219c2 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Thu, 9 Dec 2021 19:49:27 +0100 Subject: [PATCH 06/69] Implement role translation --- platforms/linux/src/node.rs | 191 +++++++++++++++++++++++++++++++++++- 1 file changed, 190 insertions(+), 1 deletion(-) diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 1c0f9b2bb..801e9a1b9 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -64,9 +64,198 @@ impl Accessible for PlatformNode { fn role(&self) -> AtspiRole { self.0.map(|node| { match node.role() { + Role::Alert => AtspiRole::Notification, + Role::AlertDialog => AtspiRole::Alert, + Role::Comment | Role::Suggestion => AtspiRole::Section, + // TODO: See how to represent ARIA role="application" + Role::Application => AtspiRole::Embedded, + Role::Article => AtspiRole::Article, + Role::Audio => AtspiRole::Audio, + Role::Banner | Role::Header => AtspiRole::Landmark, + Role::Blockquote => AtspiRole::BlockQuote, + Role::Caret => AtspiRole::Unknown, Role::Button => AtspiRole::PushButton, + Role::Canvas => AtspiRole::Canvas, + Role::Caption => AtspiRole::Caption, + Role::Cell => AtspiRole::TableCell, + Role::CheckBox => AtspiRole::CheckBox, + Role::Switch => AtspiRole::ToggleButton, + Role::ColorWell => AtspiRole::PushButton, + Role::Column => AtspiRole::Unknown, + Role::ColumnHeader => AtspiRole::ColumnHeader, + Role::ComboBoxGrouping | Role::ComboBoxMenuButton => AtspiRole::ComboBox, + Role::Complementary => AtspiRole::Landmark, + Role::ContentDeletion => AtspiRole::ContentDeletion, + Role::ContentInsertion => AtspiRole::ContentInsertion, + Role::ContentInfo | Role::Footer => AtspiRole::Landmark, + Role::Date | Role::DateTime => AtspiRole::DateEditor, + Role::Definition | Role::DescriptionListDetail => AtspiRole::DescriptionValue, + Role::DescriptionList => AtspiRole::DescriptionList, + Role::DescriptionListTerm => AtspiRole::DescriptionTerm, + Role::Details => AtspiRole::Panel, + Role::Dialog => AtspiRole::Dialog, + Role::Directory => AtspiRole::List, + Role::DisclosureTriangle => AtspiRole::ToggleButton, + Role::DocCover => AtspiRole::Image, + Role::DocBackLink | Role::DocBiblioRef | Role::DocGlossRef | Role::DocNoteRef => AtspiRole::Link, + Role::DocBiblioEntry | Role::DocEndnote => AtspiRole::ListItem, + Role::DocNotice | Role::DocTip => AtspiRole::Comment, + Role::DocFootnote => AtspiRole::Footnote, + Role::DocPageBreak => AtspiRole::Separator, + Role::DocPageFooter => AtspiRole::Footer, + Role::DocPageHeader => AtspiRole::Header, + Role::DocAcknowledgements | Role::DocAfterword | Role::DocAppendix | Role::DocBibliography | Role::DocChapter | Role::DocConclusion | Role::DocCredits | Role::DocEndnotes | Role::DocEpilogue | Role::DocErrata | Role::DocForeword | Role::DocGlossary | Role::DocIndex | Role::DocIntroduction | Role::DocPageList | Role::DocPart | Role::DocPreface | Role::DocPrologue | Role::DocToc => AtspiRole::Landmark, + Role::DocAbstract | Role::DocColophon | Role::DocCredit | Role::DocDedication | Role::DocEpigraph | Role::DocExample | Role::DocPullquote | Role::DocQna => AtspiRole::Section, + Role::DocSubtitle => AtspiRole::Heading, + Role::Document => AtspiRole::DocumentFrame, + Role::EmbeddedObject => AtspiRole::Embedded, + // TODO: Forms which lack an accessible name are no longer + // exposed as forms. http://crbug.com/874384. Forms which have accessible + // names should be exposed as `AtspiRole::Landmark` according to Core AAM. + Role::Form => AtspiRole::Form, + Role::Figure | Role::Feed => AtspiRole::Panel, + Role::GenericContainer | Role::FooterAsNonLandmark | Role::HeaderAsNonLandmark | Role::Ruby => AtspiRole::Section, + Role::GraphicsDocument => AtspiRole::DocumentFrame, + Role::GraphicsObject => AtspiRole::Panel, + Role::GraphicsSymbol => AtspiRole::Image, + Role::Grid => AtspiRole::Table, + Role::Group => AtspiRole::Panel, + Role::Heading => AtspiRole::Heading, + Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame, + Role::Image => { + if node.unignored_children().next().is_some() { + AtspiRole::ImageMap + } else { + AtspiRole::Image + } + }, + Role::InlineTextBox => AtspiRole::Static, + Role::InputTime => AtspiRole::DateEditor, + Role::LabelText | Role::Legend => AtspiRole::Label, + // Layout table objects are treated the same as `Role::GenericContainer`. + Role::LayoutTable => AtspiRole::Section, + Role::LayoutTableCell => AtspiRole::Section, + Role::LayoutTableRow => AtspiRole::Section, + // TODO: Having a separate accessible object for line breaks + // is inconsistent with other implementations. http://crbug.com/873144#c1. + Role::LineBreak => AtspiRole::Static, + Role::Link => AtspiRole::Link, + Role::List => AtspiRole::List, + Role::ListBox => AtspiRole::ListBox, + // TODO: Use `AtspiRole::MenuItem' inside a combo box, see how + // ax_platform_node_win.cc code does this. + Role::ListBoxOption => AtspiRole::ListItem, + Role::ListGrid => AtspiRole::Table, + Role::ListItem => AtspiRole::ListItem, + // Regular list markers only expose their alternative text, but do not + // expose their descendants, and the descendants should be ignored. This + // is because the alternative text depends on the counter style and can + // be different from the actual (visual) marker text, and hence, + // inconsistent with the descendants. We treat a list marker as non-text + // only if it still has non-ignored descendants, which happens only when => + // - The list marker itself is ignored but the descendants are not + // - Or the list marker contains images + Role::ListMarker => { + if node.unignored_children().next().is_none() { + AtspiRole::Static + } else { + AtspiRole::Panel + } + }, + Role::Log => AtspiRole::Log, + Role::Main => AtspiRole::Landmark, + Role::Mark => AtspiRole::Static, + Role::Math => AtspiRole::Math, + Role::Marquee => AtspiRole::Marquee, + Role::Menu | Role::MenuListPopup => AtspiRole::Menu, + Role::MenuBar => AtspiRole::MenuBar, + Role::MenuItem | Role::MenuListOption => AtspiRole::MenuItem, + Role::MenuItemCheckBox => AtspiRole::CheckMenuItem, + Role::MenuItemRadio => AtspiRole::RadioMenuItem, + Role::Meter => AtspiRole::LevelBar, + Role::Navigation => AtspiRole::Landmark, + Role::Note => AtspiRole::Comment, + Role::Pane | Role::ScrollView => AtspiRole::Panel, + Role::Paragraph => AtspiRole::Paragraph, + Role::PdfActionableHighlight => AtspiRole::PushButton, + Role::PdfRoot => AtspiRole::DocumentFrame, + Role::PluginObject => AtspiRole::Embedded, + Role::PopupButton => { + if node.data().html_tag.as_ref().map_or(false, |tag| tag.as_ref() == "select") { + AtspiRole::ComboBox + } else { + AtspiRole::PushButton + } + }, + Role::Portal => AtspiRole::PushButton, + Role::Pre => AtspiRole::Section, + Role::ProgressIndicator => AtspiRole::ProgressBar, + Role::RadioButton => AtspiRole::RadioButton, + Role::RadioGroup => AtspiRole::Panel, + Role::Region => AtspiRole::Landmark, + Role::RootWebArea => AtspiRole::DocumentWeb, + Role::Row => AtspiRole::TableRow, + Role::RowGroup => AtspiRole::Panel, + Role::RowHeader => AtspiRole::RowHeader, + // TODO: Generally exposed as description on (`Role::Ruby`) element, not + // as its own object in the tree. + // However, it's possible to make a `Role::RubyAnnotation` element show up in the + // AX tree, for example by adding tabindex="0" to the source or + // element or making the source element the target of an aria-owns. + // Therefore, browser side needs to gracefully handle it if it actually + // shows up in the tree. + Role::RubyAnnotation => AtspiRole::Static, + Role::Section => AtspiRole::Section, + Role::ScrollBar => AtspiRole::ScrollBar, + Role::Search => AtspiRole::Landmark, + Role::Slider => AtspiRole::Slider, + Role::SpinButton => AtspiRole::SpinButton, + Role::Splitter => AtspiRole::Separator, + Role::StaticText => AtspiRole::Static, + Role::Status => AtspiRole::StatusBar, + // ax::mojom::Role::kSubscript => + // AtspiRole::Subscript, + // ax::mojom::Role::kSuperscript => + // AtspiRole::Superscript, + Role::SvgRoot => AtspiRole::DocumentFrame, + Role::Tab => AtspiRole::PageTab, + Role::Table => AtspiRole::Table, + // TODO: This mapping is correct, but it doesn't seem to be + // used. We don't necessarily want to always expose these containers, but + // we must do so if they are focusable. http://crbug.com/874043 + Role::TableHeaderContainer => AtspiRole::Panel, + Role::TabList => AtspiRole::PageTabList, + Role::TabPanel => AtspiRole::ScrollPane, + // TODO: This mapping should also be applied to the dfn + // element. http://crbug.com/874411 + Role::Term => AtspiRole::DescriptionTerm, + Role::TitleBar => AtspiRole::TitleBar, + Role::TextField | Role::SearchBox => { + if node.data().protected { + AtspiRole::PasswordText + } else { + AtspiRole::Entry + } + }, + Role::TextFieldWithComboBox => AtspiRole::ComboBox, + Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => AtspiRole::Static, + Role::Timer => AtspiRole::Timer, + Role::ToggleButton => AtspiRole::ToggleButton, + Role::Toolbar => AtspiRole::ToolBar, + Role::Tooltip => AtspiRole::ToolTip, + Role::Tree => AtspiRole::Tree, + Role::TreeItem => AtspiRole::TreeItem, + Role::TreeGrid => AtspiRole::TreeTable, + Role::Video => AtspiRole::Video, + // In AT-SPI, elements with `AtspiRole::Frame` are windows with titles and + // buttons, while those with `AtspiRole::Window` are windows without those + // elements. Role::Window => AtspiRole::Frame, - _ => unimplemented!() + Role::Client | Role::WebView => AtspiRole::Panel, + Role::FigureCaption => AtspiRole::Caption, + // TODO: Are there special cases to consider? + Role::Presentation | Role::Unknown => AtspiRole::Unknown, + Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject, } }).unwrap_or(AtspiRole::Invalid) } From a5081c2cc195ce64744656dfc417113b598b126a Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 12 Dec 2021 16:53:58 +0100 Subject: [PATCH 07/69] Improve node state --- platforms/linux/Cargo.toml | 1 + platforms/linux/src/adapter.rs | 16 +++- platforms/linux/src/atspi/state.rs | 12 +-- platforms/linux/src/lib.rs | 1 + platforms/linux/src/node.rs | 132 ++++++++++++++++++++++++++--- 5 files changed, 142 insertions(+), 20 deletions(-) diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index d1176674c..c9d88d00b 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -10,6 +10,7 @@ accesskit = { path = "../../common" } accesskit_consumer = { path = "../../consumer" } async-io = "1.4.1" enumflags2 = "0.7.1" +lazy_static = "1.4.0" parking_lot = "0.11.1" serde = "1.0" x11rb = "0.8.1" diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 4c3e4c9ef..fd84a8318 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use accesskit::{NodeId, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChange}; @@ -14,6 +14,10 @@ use crate::atspi::{ }; use crate::node::{PlatformNode, RootPlatformNode}; +lazy_static! { + pub(crate) static ref CURRENT_ACTIVE_WINDOW: Arc>> = Arc::new(Mutex::new(None)); +} + pub struct Adapter { atspi_bus: Bus, tree: Arc, @@ -76,13 +80,21 @@ impl Adapter { pub fn window_activated(&self, window_id: NodeId) { let reader = self.tree.read(); let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); - self.atspi_bus.emit_object_event(&node, ObjectEvent::Activated); + if let Ok(mut current_active) = CURRENT_ACTIVE_WINDOW.lock() { + *current_active = Some(window_id); + } self.atspi_bus.emit_window_event(&node, WindowEvent::Activated); + self.atspi_bus.emit_object_event(&node, ObjectEvent::Activated); } pub fn window_deactivated(&self, window_id: NodeId) { let reader = self.tree.read(); let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); + if let Ok(mut current_active) = CURRENT_ACTIVE_WINDOW.lock() { + if *current_active == Some(window_id) { + *current_active = None; + } + } self.atspi_bus.emit_object_event(&node, ObjectEvent::Deactivated); self.atspi_bus.emit_window_event(&node, WindowEvent::Deactivated); } diff --git a/platforms/linux/src/atspi/state.rs b/platforms/linux/src/atspi/state.rs index 9b4d933a8..05f7ffc0d 100644 --- a/platforms/linux/src/atspi/state.rs +++ b/platforms/linux/src/atspi/state.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use enumflags2::{bitflags, BitFlags, FromBitsError}; +use enumflags2::{bitflags, BitFlag, BitFlags, FromBitsError}; use serde::{ de::{Deserialize, Deserializer, SeqAccess, Visitor, self}, ser::{Serialize, Serializer, SerializeSeq} @@ -226,11 +226,11 @@ pub struct StateSet(BitFlags); impl StateSet { pub fn from_bits(bits: u64) -> Result> { - Ok(BitFlags::::from_bits(bits)?.into()) + Ok(StateSet(BitFlags::from_bits(bits)?)) } pub fn empty() -> StateSet { - BitFlags::::empty().into() + StateSet(State::empty()) } pub fn bits(&self) -> u64 { @@ -294,8 +294,8 @@ impl Type for StateSet { } } -impl From> for StateSet { - fn from(value: BitFlags) -> Self { - Self(value) +impl From for StateSet { + fn from(value: State) -> Self { + Self(value.into()) } } diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs index 8f7702f8e..d0184524b 100644 --- a/platforms/linux/src/lib.rs +++ b/platforms/linux/src/lib.rs @@ -5,6 +5,7 @@ #![feature(unix_socket_abstract)] +#[macro_use] extern crate lazy_static; #[macro_use] extern crate zbus; mod adapter; diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 801e9a1b9..9cbfc1856 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::Role; +use accesskit::{AriaCurrent, CheckedState, InvalidState, Orientation, Role}; use accesskit_consumer::{Node, Tree, WeakNode}; use crate::atspi::{ interfaces::{Accessible, Application, Interface, Interfaces}, @@ -261,21 +261,129 @@ impl Accessible for PlatformNode { } fn state(&self) -> StateSet { + let platform_role = self.role(); self.0.map(|node| { - if node.role() == Role::Window { - (State::Active | State::Sensitive | State::Showing | State::Visible).into() - } else { - let mut state: StateSet = (State::Enabled | State::Sensitive | State::Showing | State::Visible).into(); - if node.data().focusable { - state.insert(State::Focusable); - if node.is_focused() { - state.insert(State::Focused); - } + let data = node.data(); + let mut state = StateSet::empty(); + if let Ok(current_active) = crate::adapter::CURRENT_ACTIVE_WINDOW.lock() { + if node.role() == Role::Window && *current_active == Some(data.id) { + state.insert(State::Active); } - state } - }).unwrap() + if let Some(expanded) = data.expanded { + state.insert(State::Expandable); + if expanded { + state.insert(State::Expanded); + } + } + if data.default { + state.insert(State::IsDefault); + } + if data.editable && !data.read_only { + state.insert(State::Editable); + } + // TODO: Focus and selection. + if data.focusable { + state.insert(State::Focusable); + } + match data.orientation { + Some(Orientation::Horizontal) => state.insert(State::Horizontal), + Some(Orientation::Vertical) => state.insert(State::Vertical), + _ => {} + } + if !node.is_invisible_or_ignored() { + state.insert(State::Visible); + // if (!delegate_->IsOffscreen() && !is_minimized) + state.insert(State::Showing); + } + if data.multiselectable { + state.insert(State::Multiselectable); + } + if data.required { + state.insert(State::Required); + } + if data.visited { + state.insert(State::Visited); + } + if let Some(InvalidState::True | InvalidState::Other(_)) = data.invalid_state { + state.insert(State::InvalidEntry); + } + match data.aria_current { + None | Some(AriaCurrent::False) => {}, + _ => state.insert(State::Active) + } + if platform_role != AtspiRole::ToggleButton && data.checked_state.is_some() { + state.insert(State::Checkable); + } + if data.has_popup.is_some() { + state.insert(State::HasPopup); + } + if data.busy { + state.insert(State::Busy); + } + if data.modal { + state.insert(State::Modal); + } + if let Some(selected) = data.selected { + if !data.disabled { + state.insert(State::Selectable); + } + if selected { + state.insert(State::Selected); + } + } + // if (IsTextField()) { + // state.insert(State::SelectableText); + // match node.data().multiline { + // true => state.insert(State::MultiLine), + // false => state.insert(State::SingleLine) + // } + // } + + // Special case for indeterminate progressbar. + if node.role() == Role::ProgressIndicator && data.value_for_range.is_none() { + state.insert(State::Indeterminate); + } + + if data.auto_complete.as_ref().map_or(false, |auto_complete| auto_complete.as_ref().is_empty()) || data.autofill_available { + state.insert(State::SupportsAutocompletion); + } + + // Checked state + match data.checked_state { + Some(CheckedState::Mixed) => state.insert(State::Indeterminate), + Some(CheckedState::True) => { + if platform_role == AtspiRole::ToggleButton { + state.insert(State::Pressed); + } else { + state.insert(State::Checked); + } + }, + _ => {} + } + + if data.role.is_read_only_supported() && node.is_read_only_or_disabled() { + state.insert(State::ReadOnly); + } else { + state.insert(State::Enabled); + state.insert(State::Sensitive); + } + + if node.is_focused() { + state.insert(State::Focused); + } + + // It is insufficient to compare with g_current_activedescendant due to both + // timing and event ordering for objects which implement AtkSelection and also + // have an active descendant. For instance, if we check the state set of a + // selectable child, it will only have ATK_STATE_FOCUSED if we've processed + // the activedescendant change. + // if (GetActiveDescendantOfCurrentFocused() == atk_object) + // state.insert(State::Focused); + state + }).unwrap_or(State::Defunct.into()) } + fn interfaces(&self) -> Interfaces { self.0.map(|node| { From bc197fe6ae6c09f436ded526125380391a720a45 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 12 Dec 2021 17:15:12 +0100 Subject: [PATCH 08/69] Improve example event handling --- common/src/lib.rs | 33 ++++++++++++++ platforms/linux/examples/hello_world.rs | 58 ++++++++++++++----------- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 1a9da492b..ce52205fa 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -250,6 +250,39 @@ impl Default for Role { } } +impl Role { + pub fn is_read_only_supported(&self) -> bool { + // https://www.w3.org/TR/wai-aria-1.1/#aria-readonly + // Roles that support aria-readonly + match self { + Role::CheckBox | Role::ColorWell | Role::ComboBoxGrouping | Role::ComboBoxMenuButton | Role::Date | Role::DateTime | Role::Grid | Role::InputTime | Role::ListBox | Role::MenuItemCheckBox | Role::MenuItemRadio | Role::MenuListPopup | Role::PopupButton | Role::RadioButton | Role::RadioGroup | Role::SearchBox | Role::Slider | Role::SpinButton | Role::Switch | Role::TextField | Role::TextFieldWithComboBox | Role::ToggleButton | Role::TreeGrid => true, + // https://www.w3.org/TR/wai-aria-1.1/#aria-readonly + // ARIA-1.1+ 'gridcell', supports aria-readonly, but 'cell' does not. + // + // https://www.w3.org/TR/wai-aria-1.1/#columnheader + // https://www.w3.org/TR/wai-aria-1.1/#rowheader + // While the [columnheader|rowheader] role can be used in both interactive + // grids and non-interactive tables, the use of aria-readonly and + // aria-required is only applicable to interactive elements. + // Therefore, [...] user agents SHOULD NOT expose either property to + // assistive technologies unless the columnheader descends from a grid. + Role::Cell | Role::RowHeader | Role::ColumnHeader => false, + _ => false + } + } + + pub fn should_have_read_only_state_by_default(&self) -> bool { + match self { + Role::Article | Role::Definition | Role::DescriptionList | Role::DescriptionListTerm | Role::Directory | Role::Document | Role::GraphicsDocument | Role::Image | Role::List | Role::ListItem | Role::PdfRoot | Role::ProgressIndicator | Role::RootWebArea | Role::Term | Role::Timer | Role::Toolbar | Role::Tooltip => true, + // TODO(aleventhal) this changed between ARIA 1.0 and 1.1. + // We need to determine whether grids/treegrids should really be readonly + // or editable by default. + Role::Grid => false, + _ => false + } + } +} + /// An action to be taken on an accessibility node. /// /// In contrast to [`DefaultActionVerb`], these describe what happens to the diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index cc8b350ee..720612d86 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -7,7 +7,7 @@ use accesskit::{Node, NodeId, Role, StringEncoding, Tree, TreeId, TreeUpdate}; use accesskit_linux::Adapter; use std::num::NonZeroU64; use winit::{ - event::{Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; @@ -44,7 +44,7 @@ fn get_initial_state() -> TreeUpdate { clear: None, nodes: vec![root, button_1, button_2], tree: Some(get_tree()), - focus: Some(unsafe { FOCUS }), + focus: None, } } @@ -69,29 +69,37 @@ fn main() { } if window_id == window.id() => { match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::Focused(true) => adapter.window_activated(WINDOW_ID), - WindowEvent::Focused(false) => adapter.window_deactivated(WINDOW_ID), - WindowEvent::KeyboardInput { input, .. } => { - match input { - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Tab), .. - } => { - unsafe { - FOCUS = if FOCUS == BUTTON_1_ID { - BUTTON_2_ID - } else { - BUTTON_1_ID - }; - adapter.update(TreeUpdate { - clear: None, - nodes: vec![], - focus: Some(FOCUS), - tree: None - }); - } - }, - _ => { } - } + WindowEvent::Focused(true) => { + adapter.window_activated(WINDOW_ID); + adapter.update(TreeUpdate { + clear: None, + nodes: vec![], + focus: Some(unsafe { FOCUS }), + tree: None + }); + }, + WindowEvent::Focused(false) => { + adapter.window_deactivated(WINDOW_ID); + }, + WindowEvent::KeyboardInput { + input: KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Tab), + state: ElementState::Pressed, + .. }, + .. } => { + unsafe { + FOCUS = if FOCUS == BUTTON_1_ID { + BUTTON_2_ID + } else { + BUTTON_1_ID + }; + adapter.update(TreeUpdate { + clear: None, + nodes: vec![], + focus: Some(FOCUS), + tree: None + }); + } }, _ => (), } From 61d6611df881a86205764ef7bebfff84869a6ac1 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 13 Dec 2021 18:28:43 +0100 Subject: [PATCH 09/69] Improve focus and active window tracking --- platforms/linux/examples/hello_world.rs | 8 +--- platforms/linux/src/adapter.rs | 49 +++++++++++++++++-------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index 720612d86..66f3cf8c3 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -69,18 +69,14 @@ fn main() { } if window_id == window.id() => { match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::Focused(true) => { - adapter.window_activated(WINDOW_ID); + WindowEvent::Focused(window_has_focus) => { adapter.update(TreeUpdate { clear: None, nodes: vec![], - focus: Some(unsafe { FOCUS }), + focus: window_has_focus.then(|| unsafe { FOCUS }), tree: None }); }, - WindowEvent::Focused(false) => { - adapter.window_deactivated(WINDOW_ID); - }, WindowEvent::KeyboardInput { input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::Tab), diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index fd84a8318..d22ac2de8 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex}; -use accesskit::{NodeId, TreeUpdate}; +use accesskit::{NodeId, Role, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ @@ -60,10 +60,23 @@ impl Adapter { let old_node = PlatformNode::new(&old_node); self.atspi_bus.emit_object_event(&old_node, ObjectEvent::FocusLost).unwrap(); } - if let Some(new_node) = new_node { - let new_node = PlatformNode::new(&new_node); - self.atspi_bus.emit_object_event(&new_node, ObjectEvent::FocusGained).unwrap(); - self.atspi_bus.emit_focus_event(&new_node).unwrap(); + if let Ok(mut active_window) = CURRENT_ACTIVE_WINDOW.lock() { + let node_window = new_node.map(|node| containing_window(node)).flatten(); + let node_window_id = node_window.map(|node| node.id()); + if let Some(active_window_id) = *active_window { + if node_window_id != Some(active_window_id) { + self.window_deactivated(active_window_id); + } + } + *active_window = node_window_id; + if let Some(new_node) = new_node { + if let Some(node_window_id) = node_window_id { + self.window_activated(node_window_id); + } + let new_node = PlatformNode::new(&new_node); + self.atspi_bus.emit_object_event(&new_node, ObjectEvent::FocusGained).unwrap(); + self.atspi_bus.emit_focus_event(&new_node).unwrap(); + } } } TreeChange::NodeUpdated { old_node, new_node } => { @@ -77,24 +90,16 @@ impl Adapter { }); } - pub fn window_activated(&self, window_id: NodeId) { + fn window_activated(&self, window_id: NodeId) { let reader = self.tree.read(); let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); - if let Ok(mut current_active) = CURRENT_ACTIVE_WINDOW.lock() { - *current_active = Some(window_id); - } self.atspi_bus.emit_window_event(&node, WindowEvent::Activated); self.atspi_bus.emit_object_event(&node, ObjectEvent::Activated); } - pub fn window_deactivated(&self, window_id: NodeId) { + fn window_deactivated(&self, window_id: NodeId) { let reader = self.tree.read(); let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); - if let Ok(mut current_active) = CURRENT_ACTIVE_WINDOW.lock() { - if *current_active == Some(window_id) { - *current_active = None; - } - } self.atspi_bus.emit_object_event(&node, ObjectEvent::Deactivated); self.atspi_bus.emit_window_event(&node, WindowEvent::Deactivated); } @@ -105,3 +110,17 @@ impl Adapter { PlatformNode::new(&node) } } + +fn containing_window(node: Node) -> Option { + const WINDOW_ROLES: &[Role] = &[Role::AlertDialog, Role::Dialog, Role::Window]; + if WINDOW_ROLES.contains(&node.role()) { + Some(node) + } else { + while let Some(node) = node.parent() { + if WINDOW_ROLES.contains(&node.role()) { + return Some(node); + } + } + None + } +} From d55ea1d3f32cd5e1db2a15d776ac2eb3b9d48eed Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 13 Dec 2021 20:08:54 +0100 Subject: [PATCH 10/69] Implement basic support for name change event --- platforms/linux/examples/hello_world.rs | 20 +++++++++++++++++++ platforms/linux/src/adapter.rs | 8 +++++--- .../linux/src/atspi/interfaces/events.rs | 15 +++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index 66f3cf8c3..86b2caf1e 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -97,6 +97,26 @@ fn main() { }); } }, + WindowEvent::KeyboardInput { + input: KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Return), + state: ElementState::Released, + .. }, + .. } => { + unsafe { + let updated_node = if FOCUS == BUTTON_1_ID { + make_button(BUTTON_1_ID, "You pressed button 1") + } else { + make_button(BUTTON_2_ID, "You pressed button 2") + }; + adapter.update(TreeUpdate { + clear: None, + nodes: vec![updated_node], + focus: Some(FOCUS), + tree: None + }); + } + } _ => (), } } diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index d22ac2de8..a4a04afd1 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -80,9 +80,11 @@ impl Adapter { } } TreeChange::NodeUpdated { old_node, new_node } => { - //let old_node = ResolvedPlatformNode::new(old_node, self.hwnd); - //let new_node = ResolvedPlatformNode::new(new_node, self.hwnd); - //new_node.raise_property_changes(&old_node); + if let Some(name) = new_node.name() { + if old_node.name() != Some(name) { + self.atspi_bus.emit_object_event(&PlatformNode::new(&new_node), ObjectEvent::NameChanged(name.to_string())); + } + } } // TODO: handle other events _ => (), diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index 0c345aa3b..868419af5 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -34,6 +34,7 @@ pub enum ObjectEvent { Deactivated, FocusGained, FocusLost, + NameChanged(String), } pub struct ObjectEventsInterface; @@ -49,13 +50,25 @@ impl ObjectEventsInterface { ObjectEvent::FocusGained => ObjectEventsInterface::state_changed(ctxt, "focused", 1, 0, 0i32.into(), properties).await, ObjectEvent::FocusLost => - ObjectEventsInterface::state_changed(ctxt, "focused", 0, 0, 0i32.into(), properties).await + ObjectEventsInterface::state_changed(ctxt, "focused", 0, 0, 0i32.into(), properties).await, + ObjectEvent::NameChanged(name) => + ObjectEventsInterface::property_change(ctxt, "accessible-name", 0, 0, name.into(), properties).await } } } #[dbus_interface(name = "org.a11y.atspi.Event.Object")] impl ObjectEventsInterface { + #[dbus_interface(signal)] + async fn property_change( + ctxt: &SignalContext<'_>, + minor: &str, + detail1: i32, + detail2: i32, + any_data: Value<'_>, + properties: HashMap> + ) -> Result<()>; + #[dbus_interface(signal)] async fn state_changed( ctxt: &SignalContext<'_>, From 705bcb6a8bfdd9bebdf31fddabbc40e871c12988 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Thu, 16 Dec 2021 20:13:57 +0100 Subject: [PATCH 11/69] Refactor object state change events --- platforms/linux/src/adapter.rs | 10 +++---- .../linux/src/atspi/interfaces/events.rs | 27 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index a4a04afd1..8186bab8d 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -10,7 +10,7 @@ use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ interfaces::{ObjectEvent, WindowEvent}, - Bus + Bus, State }; use crate::node::{PlatformNode, RootPlatformNode}; @@ -58,7 +58,7 @@ impl Adapter { } => { if let Some(old_node) = old_node { let old_node = PlatformNode::new(&old_node); - self.atspi_bus.emit_object_event(&old_node, ObjectEvent::FocusLost).unwrap(); + self.atspi_bus.emit_object_event(&old_node, ObjectEvent::StateChanged(State::Focused, false)).unwrap(); } if let Ok(mut active_window) = CURRENT_ACTIVE_WINDOW.lock() { let node_window = new_node.map(|node| containing_window(node)).flatten(); @@ -74,7 +74,7 @@ impl Adapter { self.window_activated(node_window_id); } let new_node = PlatformNode::new(&new_node); - self.atspi_bus.emit_object_event(&new_node, ObjectEvent::FocusGained).unwrap(); + self.atspi_bus.emit_object_event(&new_node, ObjectEvent::StateChanged(State::Focused, true)).unwrap(); self.atspi_bus.emit_focus_event(&new_node).unwrap(); } } @@ -96,13 +96,13 @@ impl Adapter { let reader = self.tree.read(); let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); self.atspi_bus.emit_window_event(&node, WindowEvent::Activated); - self.atspi_bus.emit_object_event(&node, ObjectEvent::Activated); + self.atspi_bus.emit_object_event(&node, ObjectEvent::StateChanged(State::Active, true)); } fn window_deactivated(&self, window_id: NodeId) { let reader = self.tree.read(); let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); - self.atspi_bus.emit_object_event(&node, ObjectEvent::Deactivated); + self.atspi_bus.emit_object_event(&node, ObjectEvent::StateChanged(State::Active, false)); self.atspi_bus.emit_window_event(&node, WindowEvent::Deactivated); } diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index 868419af5..891ef57ec 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -3,7 +3,10 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::interfaces::Accessible; +use crate::atspi::{ + interfaces::Accessible, + State +}; use std::collections::HashMap; use zbus::{SignalContext, Result, dbus_interface}; use zvariant::Value; @@ -30,10 +33,7 @@ impl FocusEventsInterface { } pub enum ObjectEvent { - Activated, - Deactivated, - FocusGained, - FocusLost, + StateChanged(State, bool), NameChanged(String), } @@ -43,16 +43,15 @@ impl ObjectEventsInterface { pub async fn emit(&self, event: ObjectEvent, ctxt: &SignalContext<'_>) -> Result<()> { let properties = HashMap::new(); match event { - ObjectEvent::Activated => - ObjectEventsInterface::state_changed(ctxt, "active", 1, 0, 0i32.into(), properties).await, - ObjectEvent::Deactivated => - ObjectEventsInterface::state_changed(ctxt, "active", 0, 0, 0i32.into(), properties).await, - ObjectEvent::FocusGained => - ObjectEventsInterface::state_changed(ctxt, "focused", 1, 0, 0i32.into(), properties).await, - ObjectEvent::FocusLost => - ObjectEventsInterface::state_changed(ctxt, "focused", 0, 0, 0i32.into(), properties).await, + ObjectEvent::StateChanged(State::Active, is_active) => { + ObjectEventsInterface::state_changed(ctxt, "active", is_active as i32, 0, 0i32.into(), properties).await + }, + ObjectEvent::StateChanged(State::Focused, is_focused) => { + ObjectEventsInterface::state_changed(ctxt, "focused", is_focused as i32, 0, 0i32.into(), properties).await + }, ObjectEvent::NameChanged(name) => - ObjectEventsInterface::property_change(ctxt, "accessible-name", 0, 0, name.into(), properties).await + ObjectEventsInterface::property_change(ctxt, "accessible-name", 0, 0, name.into(), properties).await, + _ => unimplemented!() } } } From dd72c229763e224ff2fe170df76914247857afe4 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Fri, 17 Dec 2021 21:05:48 +0100 Subject: [PATCH 12/69] Implement application deregistering --- platforms/linux/src/adapter.rs | 6 ++--- platforms/linux/src/atspi/bus.rs | 45 +++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 8186bab8d..406e94951 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -18,12 +18,12 @@ lazy_static! { pub(crate) static ref CURRENT_ACTIVE_WINDOW: Arc>> = Arc::new(Mutex::new(None)); } -pub struct Adapter { - atspi_bus: Bus, +pub struct Adapter<'a> { + atspi_bus: Bus<'a>, tree: Arc, } -impl Adapter { +impl<'a> Adapter<'a> { pub fn new(app_name: String, toolkit_name: String, toolkit_version: String, initial_state: TreeUpdate) -> Option { let mut atspi_bus = Bus::a11y_bus()?; let tree = Tree::new(initial_state); diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index f72e9b50b..428adf165 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -25,18 +25,26 @@ use zbus::{ Address, InterfaceDeref, Result }; -pub struct Bus(Connection); +pub struct Bus<'a> { + conn: Connection, + socket_proxy: SocketProxy<'a> +} -impl Bus { +impl<'a> Bus<'a> { pub fn a11y_bus() -> Option { - Some(Bus(a11y_bus()?)) + let conn = a11y_bus()?; + let socket_proxy = SocketProxy::new(&conn).ok()?; + Some(Bus { + conn, + socket_proxy + }) } pub fn register_accessible_interface(&mut self, object: T) -> Result where T: Accessible { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, object.id().as_str()); - if self.0.object_server_mut().at(path.clone(), AccessibleInterface::new(self.0.unique_name().unwrap().to_owned(), object.clone()))? { + if self.conn.object_server_mut().at(path.clone(), AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), object.clone()))? { let interfaces = object.interfaces(); if interfaces.contains(Interface::FocusEvents) { self.register_focus_events_interface(&path)?; @@ -57,15 +65,14 @@ impl Bus { pub fn register_application_interface(&mut self, root: T) -> Result where T: Application { - println!("Registering on {:?}", self.0.unique_name().unwrap()); + println!("Registering on {:?}", self.conn.unique_name().unwrap()); let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, Accessible::id(&root).as_str()); let root = Arc::new(RwLock::new(root)); - let registered = self.0.object_server_mut().at(path, ApplicationInterface(ApplicationInterfaceWrapper(root.clone())))?; + let registered = self.conn.object_server_mut().at(path, ApplicationInterface(ApplicationInterfaceWrapper(root.clone())))?; if registered && self.register_accessible_interface(ApplicationInterfaceWrapper(root.clone()))? { - let desktop_address = SocketProxy::new(&self.0) - .unwrap() + let desktop_address = self.socket_proxy .embed(ObjectAddress::root( - self.0.unique_name().unwrap().as_ref()))?; + self.conn.unique_name().unwrap().as_ref()))?; root.write().set_desktop(desktop_address); Ok(true) } else { @@ -74,24 +81,24 @@ impl Bus { } fn register_focus_events_interface(&mut self, path: &str) -> Result { - self.0.object_server_mut().at(path, FocusEventsInterface { }) + self.conn.object_server_mut().at(path, FocusEventsInterface { }) } fn register_object_events_interface(&mut self, path: &str) -> Result { - self.0.object_server_mut().at(path, ObjectEventsInterface { }) + self.conn.object_server_mut().at(path, ObjectEventsInterface { }) } fn register_window_events_interface(&mut self, path: &str, object: T) -> Result where T: Accessible { - self.0.object_server_mut().at(path, WindowEventsInterface(object)) + self.conn.object_server_mut().at(path, WindowEventsInterface(object)) } pub fn emit_focus_event(&self, target: &T) -> Result<()> where T: Accessible { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.0.object_server().with(path, |iface: InterfaceDeref<'_, FocusEventsInterface>, ctxt| { + self.conn.object_server().with(path, |iface: InterfaceDeref<'_, FocusEventsInterface>, ctxt| { block_on(iface.focused(&ctxt)) }) } @@ -100,7 +107,7 @@ impl Bus { where T: Accessible { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.0.object_server().with(path, |iface: InterfaceDeref<'_, ObjectEventsInterface>, ctxt| { + self.conn.object_server().with(path, |iface: InterfaceDeref<'_, ObjectEventsInterface>, ctxt| { block_on(iface.emit(event, &ctxt)) }) } @@ -109,12 +116,20 @@ impl Bus { where T: Accessible { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.0.object_server().with(path, |iface: InterfaceDeref<'_, WindowEventsInterface::>, ctxt| { + self.conn.object_server().with(path, |iface: InterfaceDeref<'_, WindowEventsInterface::>, ctxt| { block_on(iface.emit(event, &ctxt)) }) } } +impl Drop for Bus<'_> { + fn drop(&mut self) { + let _ = self.socket_proxy + .unembed( + ObjectAddress::root(self.conn.unique_name().unwrap().as_ref())); + } +} + fn spi_display_name() -> Option { var("AT_SPI_DISPLAY").ok().or( match var("DISPLAY") { From 1f55b5fd903616ea98f91626cad128e1ba7270ae Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 20 Dec 2021 14:35:07 +0100 Subject: [PATCH 13/69] Generalize object state change events --- platforms/linux/Cargo.toml | 1 + platforms/linux/src/adapter.rs | 13 ++++++++++-- platforms/linux/src/atspi/bus.rs | 14 +++++++++++++ .../linux/src/atspi/interfaces/events.rs | 15 +++++++------ platforms/linux/src/atspi/state.rs | 21 ++++++++++++++++++- 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index c9d88d00b..f4ccd3997 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -13,6 +13,7 @@ enumflags2 = "0.7.1" lazy_static = "1.4.0" parking_lot = "0.11.1" serde = "1.0" +strum = { version = "0.23.0", features = ["derive"] } x11rb = "0.8.1" zbus = "2.0.0-beta.7" zvariant = "2.9.0" diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 406e94951..615ecc9e3 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -9,7 +9,7 @@ use accesskit::{NodeId, Role, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ - interfaces::{ObjectEvent, WindowEvent}, + interfaces::{Accessible, ObjectEvent, WindowEvent}, Bus, State }; use crate::node::{PlatformNode, RootPlatformNode}; @@ -80,11 +80,20 @@ impl<'a> Adapter<'a> { } } TreeChange::NodeUpdated { old_node, new_node } => { + let old_state = PlatformNode::new(&old_node).state(); + let new_platform_node = PlatformNode::new(&new_node); + let new_state = new_platform_node.state(); + let changed_states = old_state ^ new_state; + let mut events = Vec::new(); + for state in changed_states.iter() { + events.push(ObjectEvent::StateChanged(state, new_state.contains(state))); + } if let Some(name) = new_node.name() { if old_node.name() != Some(name) { - self.atspi_bus.emit_object_event(&PlatformNode::new(&new_node), ObjectEvent::NameChanged(name.to_string())); + events.push(ObjectEvent::NameChanged(name.to_string())); } } + self.atspi_bus.emit_object_events(&new_platform_node, events); } // TODO: handle other events _ => (), diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 428adf165..ef14fd7f7 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -112,6 +112,20 @@ impl<'a> Bus<'a> { }) } + pub fn emit_object_events(&self, target: &T, events: Vec) -> Result<()> + where T: Accessible + { + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); + self.conn.object_server().with(path, |iface: InterfaceDeref<'_, ObjectEventsInterface>, ctxt| { + block_on(async { + for event in events { + iface.emit(event, &ctxt).await?; + } + Ok(()) + }) + }) + } + pub fn emit_window_event(&self, target: &T, event: WindowEvent) -> Result<()> where T: Accessible { diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index 891ef57ec..5504b9477 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -7,7 +7,10 @@ use crate::atspi::{ interfaces::Accessible, State }; -use std::collections::HashMap; +use std::{ + collections::HashMap, + convert::AsRef +}; use zbus::{SignalContext, Result, dbus_interface}; use zvariant::Value; @@ -43,15 +46,11 @@ impl ObjectEventsInterface { pub async fn emit(&self, event: ObjectEvent, ctxt: &SignalContext<'_>) -> Result<()> { let properties = HashMap::new(); match event { - ObjectEvent::StateChanged(State::Active, is_active) => { - ObjectEventsInterface::state_changed(ctxt, "active", is_active as i32, 0, 0i32.into(), properties).await - }, - ObjectEvent::StateChanged(State::Focused, is_focused) => { - ObjectEventsInterface::state_changed(ctxt, "focused", is_focused as i32, 0, 0i32.into(), properties).await + ObjectEvent::StateChanged(state, value) => { + ObjectEventsInterface::state_changed(ctxt, state.as_ref(), value as i32, 0, 0i32.into(), properties).await }, ObjectEvent::NameChanged(name) => - ObjectEventsInterface::property_change(ctxt, "accessible-name", 0, 0, name.into(), properties).await, - _ => unimplemented!() + ObjectEventsInterface::property_change(ctxt, "accessible-name", 0, 0, name.into(), properties).await } } } diff --git a/platforms/linux/src/atspi/state.rs b/platforms/linux/src/atspi/state.rs index 05f7ffc0d..528d0e9c3 100644 --- a/platforms/linux/src/atspi/state.rs +++ b/platforms/linux/src/atspi/state.rs @@ -9,13 +9,15 @@ use serde::{ ser::{Serialize, Serializer, SerializeSeq} }; use std::fmt; +use strum::AsRefStr; use zvariant::{Signature, Type}; /// Enumeration used by various interfaces indicating every possible state /// an #AtspiAccessible object can assume. #[bitflags] #[repr(u64)] -#[derive(Clone, Copy, Debug)] +#[derive(AsRefStr, Clone, Copy, Debug)] +#[strum(serialize_all = "kebab-case")] pub enum State { /// Indicates an invalid state - probably an error condition. Invalid, @@ -222,6 +224,7 @@ pub enum State { LastDefined, } +#[derive(Clone, Copy, Debug)] pub struct StateSet(BitFlags); impl StateSet { @@ -237,9 +240,17 @@ impl StateSet { self.0.bits() } + pub fn contains>>(self, other: B) -> bool { + self.0.contains(other) + } + pub fn insert>>(&mut self, other: B) { self.0.insert(other); } + + pub fn iter(self) -> impl Iterator { + self.0.iter() + } } impl<'de> Deserialize<'de> for StateSet { @@ -299,3 +310,11 @@ impl From for StateSet { Self(value.into()) } } + +impl std::ops::BitXor for StateSet { + type Output = StateSet; + + fn bitxor(self, other: Self) -> Self::Output { + StateSet(self.0 ^ other.0) + } +} From 59009e008be380d901d39d26eab9465c033d1d03 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 20 Dec 2021 14:57:14 +0100 Subject: [PATCH 14/69] Run cargo fmt --- platforms/linux/examples/hello_world.rs | 111 +-- platforms/linux/src/adapter.rs | 51 +- platforms/linux/src/atspi/bus.rs | 184 +++-- .../linux/src/atspi/interfaces/accessible.rs | 59 +- .../linux/src/atspi/interfaces/application.rs | 2 +- .../linux/src/atspi/interfaces/events.rs | 78 +- platforms/linux/src/atspi/interfaces/mod.rs | 2 +- platforms/linux/src/atspi/mod.rs | 24 +- platforms/linux/src/atspi/object_address.rs | 14 +- platforms/linux/src/atspi/object_id.rs | 2 +- platforms/linux/src/atspi/object_ref.rs | 2 +- platforms/linux/src/atspi/proxies.rs | 2 +- platforms/linux/src/atspi/state.rs | 49 +- platforms/linux/src/lib.rs | 6 +- platforms/linux/src/node.rs | 733 ++++++++++-------- 15 files changed, 742 insertions(+), 577 deletions(-) diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index 86b2caf1e..fae51a098 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -51,7 +51,13 @@ fn get_initial_state() -> TreeUpdate { static mut FOCUS: NodeId = BUTTON_1_ID; fn main() { - let adapter = Adapter::new(String::from("hello_world"), String::from("ExampleUI"), String::from("0.1.0"), get_initial_state()).unwrap(); + let adapter = Adapter::new( + String::from("hello_world"), + String::from("ExampleUI"), + String::from("0.1.0"), + get_initial_state(), + ) + .unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() @@ -63,63 +69,60 @@ fn main() { *control_flow = ControlFlow::Wait; match event { - Event::WindowEvent { - event, - window_id, - } if window_id == window.id() => { - match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::Focused(window_has_focus) => { - adapter.update(TreeUpdate { - clear: None, - nodes: vec![], - focus: window_has_focus.then(|| unsafe { FOCUS }), - tree: None - }); - }, - WindowEvent::KeyboardInput { - input: KeyboardInput { + Event::WindowEvent { event, window_id } if window_id == window.id() => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::Focused(window_has_focus) => { + adapter.update(TreeUpdate { + clear: None, + nodes: vec![], + focus: window_has_focus.then(|| unsafe { FOCUS }), + tree: None, + }); + } + WindowEvent::KeyboardInput { + input: + KeyboardInput { virtual_keycode: Some(VirtualKeyCode::Tab), state: ElementState::Pressed, - .. }, - .. } => { - unsafe { - FOCUS = if FOCUS == BUTTON_1_ID { - BUTTON_2_ID - } else { - BUTTON_1_ID - }; - adapter.update(TreeUpdate { - clear: None, - nodes: vec![], - focus: Some(FOCUS), - tree: None - }); - } - }, - WindowEvent::KeyboardInput { - input: KeyboardInput { + .. + }, + .. + } => unsafe { + FOCUS = if FOCUS == BUTTON_1_ID { + BUTTON_2_ID + } else { + BUTTON_1_ID + }; + adapter.update(TreeUpdate { + clear: None, + nodes: vec![], + focus: Some(FOCUS), + tree: None, + }); + }, + WindowEvent::KeyboardInput { + input: + KeyboardInput { virtual_keycode: Some(VirtualKeyCode::Return), state: ElementState::Released, - .. }, - .. } => { - unsafe { - let updated_node = if FOCUS == BUTTON_1_ID { - make_button(BUTTON_1_ID, "You pressed button 1") - } else { - make_button(BUTTON_2_ID, "You pressed button 2") - }; - adapter.update(TreeUpdate { - clear: None, - nodes: vec![updated_node], - focus: Some(FOCUS), - tree: None - }); - } - } - _ => (), - } - } + .. + }, + .. + } => unsafe { + let updated_node = if FOCUS == BUTTON_1_ID { + make_button(BUTTON_1_ID, "You pressed button 1") + } else { + make_button(BUTTON_2_ID, "You pressed button 2") + }; + adapter.update(TreeUpdate { + clear: None, + nodes: vec![updated_node], + focus: Some(FOCUS), + tree: None, + }); + }, + _ => (), + }, Event::MainEventsCleared => { window.request_redraw(); } diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 615ecc9e3..c2413c2bc 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -10,12 +10,13 @@ use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ interfaces::{Accessible, ObjectEvent, WindowEvent}, - Bus, State + Bus, State, }; use crate::node::{PlatformNode, RootPlatformNode}; lazy_static! { - pub(crate) static ref CURRENT_ACTIVE_WINDOW: Arc>> = Arc::new(Mutex::new(None)); + pub(crate) static ref CURRENT_ACTIVE_WINDOW: Arc>> = + Arc::new(Mutex::new(None)); } pub struct Adapter<'a> { @@ -24,7 +25,12 @@ pub struct Adapter<'a> { } impl<'a> Adapter<'a> { - pub fn new(app_name: String, toolkit_name: String, toolkit_version: String, initial_state: TreeUpdate) -> Option { + pub fn new( + app_name: String, + toolkit_name: String, + toolkit_version: String, + initial_state: TreeUpdate, + ) -> Option { let mut atspi_bus = Bus::a11y_bus()?; let tree = Tree::new(initial_state); let app_node = RootPlatformNode::new(app_name, toolkit_name, toolkit_version, tree.clone()); @@ -43,22 +49,21 @@ impl<'a> Adapter<'a> { atspi_bus.register_accessible_interface(node); } atspi_bus.register_application_interface(app_node); - Some(Self { - atspi_bus, - tree - }) + Some(Self { atspi_bus, tree }) } pub fn update(&self, update: TreeUpdate) { self.tree.update_and_process_changes(update, |change| { match change { - TreeChange::FocusMoved { - old_node, - new_node, - } => { + TreeChange::FocusMoved { old_node, new_node } => { if let Some(old_node) = old_node { let old_node = PlatformNode::new(&old_node); - self.atspi_bus.emit_object_event(&old_node, ObjectEvent::StateChanged(State::Focused, false)).unwrap(); + self.atspi_bus + .emit_object_event( + &old_node, + ObjectEvent::StateChanged(State::Focused, false), + ) + .unwrap(); } if let Ok(mut active_window) = CURRENT_ACTIVE_WINDOW.lock() { let node_window = new_node.map(|node| containing_window(node)).flatten(); @@ -74,7 +79,12 @@ impl<'a> Adapter<'a> { self.window_activated(node_window_id); } let new_node = PlatformNode::new(&new_node); - self.atspi_bus.emit_object_event(&new_node, ObjectEvent::StateChanged(State::Focused, true)).unwrap(); + self.atspi_bus + .emit_object_event( + &new_node, + ObjectEvent::StateChanged(State::Focused, true), + ) + .unwrap(); self.atspi_bus.emit_focus_event(&new_node).unwrap(); } } @@ -93,7 +103,8 @@ impl<'a> Adapter<'a> { events.push(ObjectEvent::NameChanged(name.to_string())); } } - self.atspi_bus.emit_object_events(&new_platform_node, events); + self.atspi_bus + .emit_object_events(&new_platform_node, events); } // TODO: handle other events _ => (), @@ -104,15 +115,19 @@ impl<'a> Adapter<'a> { fn window_activated(&self, window_id: NodeId) { let reader = self.tree.read(); let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); - self.atspi_bus.emit_window_event(&node, WindowEvent::Activated); - self.atspi_bus.emit_object_event(&node, ObjectEvent::StateChanged(State::Active, true)); + self.atspi_bus + .emit_window_event(&node, WindowEvent::Activated); + self.atspi_bus + .emit_object_event(&node, ObjectEvent::StateChanged(State::Active, true)); } fn window_deactivated(&self, window_id: NodeId) { let reader = self.tree.read(); let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); - self.atspi_bus.emit_object_event(&node, ObjectEvent::StateChanged(State::Active, false)); - self.atspi_bus.emit_window_event(&node, WindowEvent::Deactivated); + self.atspi_bus + .emit_object_event(&node, ObjectEvent::StateChanged(State::Active, false)); + self.atspi_bus + .emit_window_event(&node, WindowEvent::Deactivated); } fn root_platform_node(&self) -> PlatformNode { diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index ef14fd7f7..5589ae4b5 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -3,48 +3,49 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use async_io::block_on; use crate::atspi::{ interfaces::*, object_address::*, - proxies::{BusProxy, SocketProxy} + proxies::{BusProxy, SocketProxy}, }; +use async_io::block_on; use parking_lot::RwLock; use std::{ env::var, os::unix::net::{SocketAddr, UnixStream}, + str::FromStr, sync::Arc, - str::FromStr }; use x11rb::{ connection::Connection as _, - protocol::xproto::{AtomEnum, ConnectionExt} + protocol::xproto::{AtomEnum, ConnectionExt}, }; use zbus::{ - blocking::{ConnectionBuilder, Connection}, - Address, InterfaceDeref, Result + blocking::{Connection, ConnectionBuilder}, + Address, InterfaceDeref, Result, }; pub struct Bus<'a> { conn: Connection, - socket_proxy: SocketProxy<'a> + socket_proxy: SocketProxy<'a>, } impl<'a> Bus<'a> { pub fn a11y_bus() -> Option { let conn = a11y_bus()?; let socket_proxy = SocketProxy::new(&conn).ok()?; - Some(Bus { - conn, - socket_proxy - }) + Some(Bus { conn, socket_proxy }) } pub fn register_accessible_interface(&mut self, object: T) -> Result - where T: Accessible + where + T: Accessible, { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, object.id().as_str()); - if self.conn.object_server_mut().at(path.clone(), AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), object.clone()))? { + if self.conn.object_server_mut().at( + path.clone(), + AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), object.clone()), + )? { let interfaces = object.interfaces(); if interfaces.contains(Interface::FocusEvents) { self.register_focus_events_interface(&path)?; @@ -63,16 +64,26 @@ impl<'a> Bus<'a> { } pub fn register_application_interface(&mut self, root: T) -> Result - where T: Application + where + T: Application, { println!("Registering on {:?}", self.conn.unique_name().unwrap()); - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, Accessible::id(&root).as_str()); + let path = format!( + "{}{}", + ACCESSIBLE_PATH_PREFIX, + Accessible::id(&root).as_str() + ); let root = Arc::new(RwLock::new(root)); - let registered = self.conn.object_server_mut().at(path, ApplicationInterface(ApplicationInterfaceWrapper(root.clone())))?; - if registered && self.register_accessible_interface(ApplicationInterfaceWrapper(root.clone()))? { - let desktop_address = self.socket_proxy - .embed(ObjectAddress::root( - self.conn.unique_name().unwrap().as_ref()))?; + let registered = self.conn.object_server_mut().at( + path, + ApplicationInterface(ApplicationInterfaceWrapper(root.clone())), + )?; + if registered + && self.register_accessible_interface(ApplicationInterfaceWrapper(root.clone()))? + { + let desktop_address = self.socket_proxy.embed(ObjectAddress::root( + self.conn.unique_name().unwrap().as_ref(), + ))?; root.write().set_desktop(desktop_address); Ok(true) } else { @@ -81,83 +92,103 @@ impl<'a> Bus<'a> { } fn register_focus_events_interface(&mut self, path: &str) -> Result { - self.conn.object_server_mut().at(path, FocusEventsInterface { }) + self.conn + .object_server_mut() + .at(path, FocusEventsInterface {}) } fn register_object_events_interface(&mut self, path: &str) -> Result { - self.conn.object_server_mut().at(path, ObjectEventsInterface { }) + self.conn + .object_server_mut() + .at(path, ObjectEventsInterface {}) } fn register_window_events_interface(&mut self, path: &str, object: T) -> Result - where T: Accessible + where + T: Accessible, { - self.conn.object_server_mut().at(path, WindowEventsInterface(object)) + self.conn + .object_server_mut() + .at(path, WindowEventsInterface(object)) } pub fn emit_focus_event(&self, target: &T) -> Result<()> - where T: Accessible + where + T: Accessible, { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.conn.object_server().with(path, |iface: InterfaceDeref<'_, FocusEventsInterface>, ctxt| { - block_on(iface.focused(&ctxt)) - }) + self.conn.object_server().with( + path, + |iface: InterfaceDeref<'_, FocusEventsInterface>, ctxt| block_on(iface.focused(&ctxt)), + ) } pub fn emit_object_event(&self, target: &T, event: ObjectEvent) -> Result<()> - where T: Accessible + where + T: Accessible, { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.conn.object_server().with(path, |iface: InterfaceDeref<'_, ObjectEventsInterface>, ctxt| { - block_on(iface.emit(event, &ctxt)) - }) + self.conn.object_server().with( + path, + |iface: InterfaceDeref<'_, ObjectEventsInterface>, ctxt| { + block_on(iface.emit(event, &ctxt)) + }, + ) } pub fn emit_object_events(&self, target: &T, events: Vec) -> Result<()> - where T: Accessible + where + T: Accessible, { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.conn.object_server().with(path, |iface: InterfaceDeref<'_, ObjectEventsInterface>, ctxt| { - block_on(async { - for event in events { - iface.emit(event, &ctxt).await?; - } - Ok(()) - }) - }) + self.conn.object_server().with( + path, + |iface: InterfaceDeref<'_, ObjectEventsInterface>, ctxt| { + block_on(async { + for event in events { + iface.emit(event, &ctxt).await?; + } + Ok(()) + }) + }, + ) } pub fn emit_window_event(&self, target: &T, event: WindowEvent) -> Result<()> - where T: Accessible + where + T: Accessible, { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.conn.object_server().with(path, |iface: InterfaceDeref<'_, WindowEventsInterface::>, ctxt| { - block_on(iface.emit(event, &ctxt)) - }) + self.conn.object_server().with( + path, + |iface: InterfaceDeref<'_, WindowEventsInterface>, ctxt| { + block_on(iface.emit(event, &ctxt)) + }, + ) } } impl Drop for Bus<'_> { fn drop(&mut self) { - let _ = self.socket_proxy - .unembed( - ObjectAddress::root(self.conn.unique_name().unwrap().as_ref())); + let _ = self.socket_proxy.unembed(ObjectAddress::root( + self.conn.unique_name().unwrap().as_ref(), + )); } } fn spi_display_name() -> Option { - var("AT_SPI_DISPLAY").ok().or( - match var("DISPLAY") { - Ok(mut display_env) if display_env.len() > 0 => { - match (display_env.rfind(':'), display_env.rfind('.')) { - (Some(i), Some(j)) if j > i => { - display_env.truncate(j); - Some(display_env) - }, - _ => Some(display_env) + var("AT_SPI_DISPLAY").ok().or(match var("DISPLAY") { + Ok(mut display_env) if display_env.len() > 0 => { + match (display_env.rfind(':'), display_env.rfind('.')) { + (Some(i), Some(j)) if j > i => { + display_env.truncate(j); + Some(display_env) } - }, - _ => None - }) + _ => Some(display_env), + } + } + _ => None, + }) } fn a11y_bus_address_from_x11() -> Option { @@ -165,21 +196,30 @@ fn a11y_bus_address_from_x11() -> Option { let root_window = &bridge_display.setup().roots[screen_num].root; let reply = bridge_display.intern_atom(false, b"AT_SPI_BUS").ok()?; let at_spi_bus = reply.reply().ok()?; - let address = bridge_display.get_property(false, *root_window, at_spi_bus.atom, AtomEnum::STRING, 0, 1024).ok()? - .reply().map_or(None, |data| String::from_utf8(data.value).ok()); + let address = bridge_display + .get_property( + false, + *root_window, + at_spi_bus.atom, + AtomEnum::STRING, + 0, + 1024, + ) + .ok()? + .reply() + .map_or(None, |data| String::from_utf8(data.value).ok()); address } fn a11y_bus_address_from_dbus() -> Option { let session_bus = Connection::session().ok()?; - BusProxy::new(&session_bus).ok()? - .get_address().ok() + BusProxy::new(&session_bus).ok()?.get_address().ok() } fn a11y_bus() -> Option { let mut address = match var("AT_SPI_BUS_ADDRESS") { Ok(address) if address.len() > 0 => Some(address), - _ => None + _ => None, }; if address.is_none() && var("WAYLAND_DISPLAY").is_err() { address = a11y_bus_address_from_x11(); @@ -190,8 +230,18 @@ fn a11y_bus() -> Option { let address = address?; let guid_index = address.find(',').unwrap_or(address.len()); if address.starts_with("unix:abstract=") { - ConnectionBuilder::unix_stream(UnixStream::connect_addr(&SocketAddr::from_abstract_namespace(address[14..guid_index].as_bytes()).ok()?).ok()?).build().ok() + ConnectionBuilder::unix_stream( + UnixStream::connect_addr( + &SocketAddr::from_abstract_namespace(address[14..guid_index].as_bytes()).ok()?, + ) + .ok()?, + ) + .build() + .ok() } else { - ConnectionBuilder::address(Address::from_str(address.get(0..guid_index)?).ok()?).ok()?.build().ok() + ConnectionBuilder::address(Address::from_str(address.get(0..guid_index)?).ok()?) + .ok()? + .build() + .ok() } } diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 0accac9ef..378c313ac 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -5,7 +5,7 @@ use crate::atspi::{ interfaces::{Interface, Interfaces}, - ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role, StateSet + ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role, StateSet, }; use std::convert::TryInto; use zbus::names::OwnedUniqueName; @@ -43,17 +43,11 @@ pub struct AccessibleInterface { impl AccessibleInterface { pub fn new(bus_name: OwnedUniqueName, object: T) -> Self { - Self { - bus_name, - object - } + Self { bus_name, object } } } -const INTERFACES: &[&'static str] = &[ - "org.a11y.atspi.Accessible", - "org.a11y.atspi.Application" -]; +const INTERFACES: &[&'static str] = &["org.a11y.atspi.Accessible", "org.a11y.atspi.Application"]; #[dbus_interface(name = "org.a11y.atspi.Accessible")] impl AccessibleInterface { @@ -70,9 +64,11 @@ impl AccessibleInterface { #[dbus_interface(property)] fn parent(&self) -> OwnedObjectAddress { match self.object.parent() { - Some(ObjectRef::Managed(id)) => ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), + Some(ObjectRef::Managed(id)) => { + ObjectAddress::accessible(self.bus_name.as_ref(), id).into() + } Some(ObjectRef::Unmanaged(address)) => address, - None => ObjectAddress::null(self.bus_name.as_ref()).into() + None => ObjectAddress::null(self.bus_name.as_ref()).into(), } } @@ -92,24 +88,41 @@ impl AccessibleInterface { } fn get_child_at_index(&self, index: i32) -> (OwnedObjectAddress,) { - (match index.try_into().ok().map(|index| self.object.child_at_index(index)).flatten() { - Some(ObjectRef::Managed(id)) => ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), - Some(ObjectRef::Unmanaged(address)) => address, - None => ObjectAddress::null(self.bus_name.as_ref()).into() - },) + ( + match index + .try_into() + .ok() + .map(|index| self.object.child_at_index(index)) + .flatten() + { + Some(ObjectRef::Managed(id)) => { + ObjectAddress::accessible(self.bus_name.as_ref(), id).into() + } + Some(ObjectRef::Unmanaged(address)) => address, + None => ObjectAddress::null(self.bus_name.as_ref()).into(), + }, + ) } fn get_children(&self) -> Vec { - self.object.children().into_iter().map(|child| { - match child { - ObjectRef::Managed(id) => ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), - ObjectRef::Unmanaged(address) => address - } - }).collect() + self.object + .children() + .into_iter() + .map(|child| match child { + ObjectRef::Managed(id) => { + ObjectAddress::accessible(self.bus_name.as_ref(), id).into() + } + ObjectRef::Unmanaged(address) => address, + }) + .collect() } fn get_index_in_parent(&self) -> i32 { - self.object.index_in_parent().map(|index| index.try_into().ok()).flatten().unwrap_or(-1) + self.object + .index_in_parent() + .map(|index| index.try_into().ok()) + .flatten() + .unwrap_or(-1) } fn get_role(&self) -> Role { diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/linux/src/atspi/interfaces/application.rs index fcb21707e..d8c771115 100644 --- a/platforms/linux/src/atspi/interfaces/application.rs +++ b/platforms/linux/src/atspi/interfaces/application.rs @@ -5,7 +5,7 @@ use crate::atspi::{ interfaces::{Accessible, Interface, Interfaces}, - ObjectId, ObjectRef, OwnedObjectAddress, Role, StateSet + ObjectId, ObjectRef, OwnedObjectAddress, Role, StateSet, }; use parking_lot::RwLock; use std::sync::Arc; diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index 5504b9477..87e5b46d3 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -3,15 +3,9 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{ - interfaces::Accessible, - State -}; -use std::{ - collections::HashMap, - convert::AsRef -}; -use zbus::{SignalContext, Result, dbus_interface}; +use crate::atspi::{interfaces::Accessible, State}; +use std::{collections::HashMap, convert::AsRef}; +use zbus::{dbus_interface, Result, SignalContext}; use zvariant::Value; pub struct FocusEventsInterface; @@ -31,7 +25,7 @@ impl FocusEventsInterface { detail1: i32, detail2: i32, any_data: Value<'_>, - properties: HashMap> + properties: HashMap>, ) -> Result<()>; } @@ -47,10 +41,27 @@ impl ObjectEventsInterface { let properties = HashMap::new(); match event { ObjectEvent::StateChanged(state, value) => { - ObjectEventsInterface::state_changed(ctxt, state.as_ref(), value as i32, 0, 0i32.into(), properties).await - }, - ObjectEvent::NameChanged(name) => - ObjectEventsInterface::property_change(ctxt, "accessible-name", 0, 0, name.into(), properties).await + ObjectEventsInterface::state_changed( + ctxt, + state.as_ref(), + value as i32, + 0, + 0i32.into(), + properties, + ) + .await + } + ObjectEvent::NameChanged(name) => { + ObjectEventsInterface::property_change( + ctxt, + "accessible-name", + 0, + 0, + name.into(), + properties, + ) + .await + } } } } @@ -64,7 +75,7 @@ impl ObjectEventsInterface { detail1: i32, detail2: i32, any_data: Value<'_>, - properties: HashMap> + properties: HashMap>, ) -> Result<()>; #[dbus_interface(signal)] @@ -74,7 +85,7 @@ impl ObjectEventsInterface { detail1: i32, detail2: i32, any_data: Value<'_>, - properties: HashMap> + properties: HashMap>, ) -> Result<()>; } @@ -83,7 +94,7 @@ pub enum WindowEvent { Closed, Created, Deactivated, - Destroyed + Destroyed, } pub struct WindowEventsInterface(pub T); @@ -93,16 +104,21 @@ impl WindowEventsInterface { let name = self.0.name().into(); let properties = HashMap::new(); match event { - WindowEvent::Activated => - WindowEventsInterface::::activate(ctxt, "", 0, 0, name, properties).await, - WindowEvent::Closed => - WindowEventsInterface::::close(ctxt, "", 0, 0, name, properties).await, - WindowEvent::Created => - WindowEventsInterface::::create(ctxt, "", 0, 0, name, properties).await, - WindowEvent::Deactivated => - WindowEventsInterface::::deactivate(ctxt, "", 0, 0, name, properties).await, - WindowEvent::Destroyed => + WindowEvent::Activated => { + WindowEventsInterface::::activate(ctxt, "", 0, 0, name, properties).await + } + WindowEvent::Closed => { + WindowEventsInterface::::close(ctxt, "", 0, 0, name, properties).await + } + WindowEvent::Created => { + WindowEventsInterface::::create(ctxt, "", 0, 0, name, properties).await + } + WindowEvent::Deactivated => { + WindowEventsInterface::::deactivate(ctxt, "", 0, 0, name, properties).await + } + WindowEvent::Destroyed => { WindowEventsInterface::::destroy(ctxt, "", 0, 0, name, properties).await + } } } } @@ -116,7 +132,7 @@ impl WindowEventsInterface { detail1: i32, detail2: i32, any_data: Value<'_>, - properties: HashMap> + properties: HashMap>, ) -> Result<()>; #[dbus_interface(signal)] @@ -126,7 +142,7 @@ impl WindowEventsInterface { detail1: i32, detail2: i32, any_data: Value<'_>, - properties: HashMap> + properties: HashMap>, ) -> Result<()>; #[dbus_interface(signal)] @@ -136,7 +152,7 @@ impl WindowEventsInterface { detail1: i32, detail2: i32, any_data: Value<'_>, - properties: HashMap> + properties: HashMap>, ) -> Result<()>; #[dbus_interface(signal)] @@ -146,7 +162,7 @@ impl WindowEventsInterface { detail1: i32, detail2: i32, any_data: Value<'_>, - properties: HashMap> + properties: HashMap>, ) -> Result<()>; #[dbus_interface(signal)] @@ -156,6 +172,6 @@ impl WindowEventsInterface { detail1: i32, detail2: i32, any_data: Value<'_>, - properties: HashMap> + properties: HashMap>, ) -> Result<()>; } diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index 6bd913ed0..4f09356db 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use enumflags2::{BitFlags, bitflags}; +use enumflags2::{bitflags, BitFlags}; mod accessible; mod application; mod events; diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs index c8c8d3fe2..c3db77ccf 100644 --- a/platforms/linux/src/atspi/mod.rs +++ b/platforms/linux/src/atspi/mod.rs @@ -4,9 +4,7 @@ // the LICENSE-MIT file), at your option. use serde::{Deserialize, Serialize}; -use zvariant::{ - derive::Type -}; +use zvariant::derive::Type; mod bus; pub mod interfaces; @@ -149,7 +147,7 @@ pub enum Role { /// An object that allows a user to incrementally view /// a large amount of information. @SCROLL_PANE objects are usually /// accompanied by @SCROLL_BAR controllers, on which the - /// @RELATION_CONTROLLER_FOR and @RELATION_CONTROLLED_BY + /// @RELATION_CONTROLLER_FOR and @RELATION_CONTROLLED_BY /// reciprocal relations are set. See #get_relation_set. ScrollPane, /// An object usually contained in a menu to provide a @@ -159,7 +157,7 @@ pub enum Role { Slider, /// An object which allows one of a set of choices to /// be selected, and which displays the current choice. Unlike - /// @SCROLL_BAR, @SLIDER objects need not control + /// @SCROLL_BAR, @SLIDER objects need not control /// 'viewport'-like objects. SpinButton, /// A specialized panel that presents two other panels @@ -171,7 +169,7 @@ pub enum Role { /// An object used to repesent information in terms of rows and columns. Table, /// A 'cell' or discrete child within a Table. Note: - /// Table cells need not have @TABLE_CELL, other + /// Table cells need not have @TABLE_CELL, other /// #AtspiRoleType values are valid as well. TableCell, /// An object which labels a particular column @@ -209,7 +207,7 @@ pub enum Role { /// An object that presents both tabular and /// hierarchical info to the user. TreeTable, - /// The object contains some #AtspiAccessible information, + /// The object contains some #AtspiAccessible information, /// but its role is not known. Unknown, /// An object usually used in a scroll pane, or to @@ -229,7 +227,7 @@ pub enum Role { /// text content. See also @TEXT. Paragraph, /// An object which describes margins and tab stops, etc. - /// for text objects which it controls (should have + /// for text objects which it controls (should have /// @RELATION_CONTROLLER_FOR relation to such). Ruler, /// An object corresponding to the toplevel accessible @@ -251,14 +249,14 @@ pub enum Role { Embedded, /// The object is a component whose textual content may be /// entered or modified by the user, provided @STATE_EDITABLE is present. - /// A readonly @ENTRY object (i.e. where @STATE_EDITABLE is - /// not present) implies a read-only 'text field' in a form, as opposed to a + /// A readonly @ENTRY object (i.e. where @STATE_EDITABLE is + /// not present) implies a read-only 'text field' in a form, as opposed to a /// title, label, or caption. Entry, /// The object is a graphical depiction of quantitative data. /// It may contain multiple subelements whose attributes and/or description /// may be queried to obtain both the quantitative data and information about - /// how the data is being presented. The @LABELLED_BY relation is + /// how the data is being presented. The @LABELLED_BY relation is /// particularly important in interpreting objects of this type, as is the /// accessible description property. See @CAPTION. Chart, @@ -270,7 +268,7 @@ pub enum Role { /// contains a view of document content. #AtspiDocument frames may occur within /// another #AtspiDocument instance, in which case the second document may be /// said to be embedded in the containing instance. HTML frames are often - /// DOCUMENT_FRAME: Either this object, or a singleton descendant, + /// DOCUMENT_FRAME: Either this object, or a singleton descendant, /// should implement the #AtspiDocument interface. DocumentFrame, /// The object serves as a heading for content which @@ -300,7 +298,7 @@ pub enum Role { /// content, for instance within Web documents, presentations, or text /// documents. Unlike other GUI containers and dialogs which occur inside /// application instances, @FORM containers' components are - /// associated with the current document, rather than the current foreground + /// associated with the current document, rather than the current foreground /// application or viewer instance. Form, /// The object is a hypertext anchor, i.e. a "link" in a diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs index b3065fadc..3989b1c81 100644 --- a/platforms/linux/src/atspi/object_address.rs +++ b/platforms/linux/src/atspi/object_address.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use zbus::names::{OwnedUniqueName, UniqueName}; use zvariant::{ derive::{Type, Value}, - ObjectPath, OwnedObjectPath + ObjectPath, OwnedObjectPath, }; pub const ACCESSIBLE_PATH_PREFIX: &'static str = "/org/a11y/atspi/accessible/"; @@ -31,21 +31,25 @@ impl<'a> ObjectAddress<'a> { pub fn accessible(bus_name: UniqueName<'a>, id: ObjectId) -> ObjectAddress<'a> { Self { bus_name, - path: ObjectPath::from_string_unchecked(format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str())) + path: ObjectPath::from_string_unchecked(format!( + "{}{}", + ACCESSIBLE_PATH_PREFIX, + id.as_str() + )), } } pub fn null(bus_name: UniqueName<'a>) -> ObjectAddress<'a> { Self { bus_name, - path: ObjectPath::from_str_unchecked(NULL_PATH) + path: ObjectPath::from_str_unchecked(NULL_PATH), } } pub fn root(bus_name: UniqueName<'a>) -> ObjectAddress<'a> { Self { bus_name, - path: ObjectPath::from_str_unchecked(ROOT_PATH) + path: ObjectPath::from_str_unchecked(ROOT_PATH), } } } @@ -60,7 +64,7 @@ impl From> for OwnedObjectAddress { fn from(value: ObjectAddress) -> OwnedObjectAddress { OwnedObjectAddress { bus_name: value.bus_name.into(), - path: value.path.into() + path: value.path.into(), } } } diff --git a/platforms/linux/src/atspi/object_id.rs b/platforms/linux/src/atspi/object_id.rs index f88a77589..58d167f8c 100644 --- a/platforms/linux/src/atspi/object_id.rs +++ b/platforms/linux/src/atspi/object_id.rs @@ -7,7 +7,7 @@ use accesskit::NodeId; use serde::{Deserialize, Serialize}; use zvariant::{ derive::{Type, Value}, - Str + Str, }; #[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] diff --git a/platforms/linux/src/atspi/object_ref.rs b/platforms/linux/src/atspi/object_ref.rs index 8fe9e7aa9..f88be0deb 100644 --- a/platforms/linux/src/atspi/object_ref.rs +++ b/platforms/linux/src/atspi/object_ref.rs @@ -3,8 +3,8 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::NodeId; use crate::atspi::{ObjectId, OwnedObjectAddress}; +use accesskit::NodeId; pub enum ObjectRef { Managed(ObjectId<'static>), diff --git a/platforms/linux/src/atspi/proxies.rs b/platforms/linux/src/atspi/proxies.rs index 96d78107a..de512663e 100644 --- a/platforms/linux/src/atspi/proxies.rs +++ b/platforms/linux/src/atspi/proxies.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use crate::atspi::{ObjectAddress, OwnedObjectAddress}; -use zbus::{Result, dbus_proxy}; +use zbus::{dbus_proxy, Result}; #[dbus_proxy( default_service = "org.a11y.Bus", diff --git a/platforms/linux/src/atspi/state.rs b/platforms/linux/src/atspi/state.rs index 528d0e9c3..0dd656d54 100644 --- a/platforms/linux/src/atspi/state.rs +++ b/platforms/linux/src/atspi/state.rs @@ -5,14 +5,14 @@ use enumflags2::{bitflags, BitFlag, BitFlags, FromBitsError}; use serde::{ - de::{Deserialize, Deserializer, SeqAccess, Visitor, self}, - ser::{Serialize, Serializer, SerializeSeq} + de::{self, Deserialize, Deserializer, SeqAccess, Visitor}, + ser::{Serialize, SerializeSeq, Serializer}, }; use std::fmt; use strum::AsRefStr; use zvariant::{Signature, Type}; -/// Enumeration used by various interfaces indicating every possible state +/// Enumeration used by various interfaces indicating every possible state /// an #AtspiAccessible object can assume. #[bitflags] #[repr(u64)] @@ -48,7 +48,7 @@ pub enum State { /// Indicates that this object is enabled, i.e. that it /// currently reflects some application state. Objects that are "greyed out" /// may lack this state, and may lack the @SENSITIVE if direct - /// user interaction cannot cause them to acquire @ENABLED. + /// user interaction cannot cause them to acquire @ENABLED. /// See @SENSITIVE. Enabled, /// Indicates this object allows progressive @@ -97,10 +97,10 @@ pub enum State { Selected, /// Indicates this object is sensitive, e.g. to user /// interaction. @SENSITIVE usually accompanies. - /// @ENABLED for user-actionable controls, but may be found in the - /// absence of @ENABLED if the current visible state of the control + /// @ENABLED for user-actionable controls, but may be found in the + /// absence of @ENABLED if the current visible state of the control /// is "disconnected" from the application state. In such cases, direct user - /// interaction can often result in the object gaining @SENSITIVE, + /// interaction can often result in the object gaining @SENSITIVE, /// for instance if a user makes an explicit selection using an object whose /// current state is ambiguous or undefined. See @ENABLED, /// @INDETERMINATE. @@ -115,7 +115,7 @@ pub enum State { SingleLine, /// Indicates that the information returned for this object /// may no longer be synchronized with the application state. This can occur - /// if the object has @TRANSIENT, and can also occur towards the + /// if the object has @TRANSIENT, and can also occur towards the /// end of the object peer's lifecycle. Stale, /// Indicates this object is transient. @@ -125,12 +125,12 @@ pub enum State { /// objects (with vertical text flow), separators, etc. Vertical, /// Indicates this object is visible, e.g. has been - /// explicitly marked for exposure to the user. @VISIBLE is no - /// guarantee that the object is actually unobscured on the screen, only that - /// it is 'potentially' visible, barring obstruction, being scrolled or clipped - /// out of the field of view, or having an ancestor container that has not yet - /// made visible. A widget is potentially onscreen if it has both - /// @VISIBLE and @SHOWING. The absence of + /// explicitly marked for exposure to the user. @VISIBLE is no + /// guarantee that the object is actually unobscured on the screen, only that + /// it is 'potentially' visible, barring obstruction, being scrolled or clipped + /// out of the field of view, or having an ancestor container that has not yet + /// made visible. A widget is potentially onscreen if it has both + /// @VISIBLE and @SHOWING. The absence of /// @VISIBLE and @SHOWING is /// semantically equivalent to saying that an object is 'hidden'. Visible, @@ -141,8 +141,8 @@ pub enum State { /// @MANAGES_DESCENDANTS is an indication to the client that the /// children should not, and need not, be enumerated by the client. /// Objects implementing this state are expected to provide relevant state - /// notifications to listening clients, for instance notifications of - /// visibility changes and activation of their contained child objects, without + /// notifications to listening clients, for instance notifications of + /// visibility changes and activation of their contained child objects, without /// the client having previously requested references to those children. ManagesDescendants, /// Indicates that a check box or other boolean @@ -192,7 +192,7 @@ pub enum State { /// question supports text selection. It should only be exposed on objects /// which implement the #AtspiText interface, in order to distinguish this state /// from @SELECTABLE, which infers that the object in question is a - /// selectable child of an object which implements #AtspiSelection. While + /// selectable child of an object which implements #AtspiSelection. While /// similar, text selection and subelement selection are distinct operations. SelectableText, /// This state indicates that the object in question is @@ -255,7 +255,8 @@ impl StateSet { impl<'de> Deserialize<'de> for StateSet { fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> + where + D: Deserializer<'de>, { struct StateSetVisitor; @@ -263,12 +264,13 @@ impl<'de> Deserialize<'de> for StateSet { type Value = StateSet; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a sequence comprised of two u32 that represents a valid StateSet") + formatter + .write_str("a sequence comprised of two u32 that represents a valid StateSet") } fn visit_seq(self, mut seq: A) -> Result where - A: SeqAccess<'de> + A: SeqAccess<'de>, { match SeqAccess::next_element::>(&mut seq)? { Some(vec) => { @@ -277,8 +279,8 @@ impl<'de> Deserialize<'de> for StateSet { return Err(de::Error::invalid_length(len, &"Vec with two elements")); } Ok(StateSet::from_bits(0).unwrap()) - }, - None => Err(de::Error::custom("Vec with two elements")) + } + None => Err(de::Error::custom("Vec with two elements")), } } } @@ -289,7 +291,8 @@ impl<'de> Deserialize<'de> for StateSet { impl Serialize for StateSet { fn serialize(&self, serializer: S) -> Result - where S: Serializer + where + S: Serializer, { let mut seq = serializer.serialize_seq(Some(2))?; let bits = self.bits(); diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs index d0184524b..2836f9da7 100644 --- a/platforms/linux/src/lib.rs +++ b/platforms/linux/src/lib.rs @@ -5,8 +5,10 @@ #![feature(unix_socket_abstract)] -#[macro_use] extern crate lazy_static; -#[macro_use] extern crate zbus; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate zbus; mod adapter; mod atspi; diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 9cbfc1856..234a47d5f 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -3,12 +3,12 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{AriaCurrent, CheckedState, InvalidState, Orientation, Role}; -use accesskit_consumer::{Node, Tree, WeakNode}; use crate::atspi::{ interfaces::{Accessible, Application, Interface, Interfaces}, - ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole, State, StateSet + ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole, State, StateSet, }; +use accesskit::{AriaCurrent, CheckedState, InvalidState, Orientation, Role}; +use accesskit_consumer::{Node, Tree, WeakNode}; use std::sync::Arc; #[derive(Clone)] @@ -22,7 +22,10 @@ impl PlatformNode { impl Accessible for PlatformNode { fn name(&self) -> String { - self.0.map(|node| node.name().map(|name| name.to_string())).flatten().unwrap_or(String::new()) + self.0 + .map(|node| node.name().map(|name| name.to_string())) + .flatten() + .unwrap_or(String::new()) } fn description(&self) -> String { @@ -30,11 +33,12 @@ impl Accessible for PlatformNode { } fn parent(&self) -> Option { - Some(self - .0 - .map(|node| node.parent().map(|parent| parent.id().into())) - .flatten() - .unwrap_or(ObjectId::root().into())) + Some( + self.0 + .map(|node| node.parent().map(|parent| parent.id().into())) + .flatten() + .unwrap_or(ObjectId::root().into()), + ) } fn child_count(&self) -> usize { @@ -50,352 +54,406 @@ impl Accessible for PlatformNode { } fn child_at_index(&self, index: usize) -> Option { - self.0.map(|node| node.children().nth(index).map(|child| child.id().into())).flatten() + self.0 + .map(|node| node.children().nth(index).map(|child| child.id().into())) + .flatten() } fn children(&self) -> Vec { - self.0.map(|node| node.children().map(|child| child.id().into()).collect()).unwrap_or(Vec::new()) + self.0 + .map(|node| node.children().map(|child| child.id().into()).collect()) + .unwrap_or(Vec::new()) } fn index_in_parent(&self) -> Option { - self.0.map(|node| node.parent_and_index().map(|(_, index)| index)).flatten() + self.0 + .map(|node| node.parent_and_index().map(|(_, index)| index)) + .flatten() } fn role(&self) -> AtspiRole { - self.0.map(|node| { - match node.role() { - Role::Alert => AtspiRole::Notification, - Role::AlertDialog => AtspiRole::Alert, - Role::Comment | Role::Suggestion => AtspiRole::Section, - // TODO: See how to represent ARIA role="application" - Role::Application => AtspiRole::Embedded, - Role::Article => AtspiRole::Article, - Role::Audio => AtspiRole::Audio, - Role::Banner | Role::Header => AtspiRole::Landmark, - Role::Blockquote => AtspiRole::BlockQuote, - Role::Caret => AtspiRole::Unknown, - Role::Button => AtspiRole::PushButton, - Role::Canvas => AtspiRole::Canvas, - Role::Caption => AtspiRole::Caption, - Role::Cell => AtspiRole::TableCell, - Role::CheckBox => AtspiRole::CheckBox, - Role::Switch => AtspiRole::ToggleButton, - Role::ColorWell => AtspiRole::PushButton, - Role::Column => AtspiRole::Unknown, - Role::ColumnHeader => AtspiRole::ColumnHeader, - Role::ComboBoxGrouping | Role::ComboBoxMenuButton => AtspiRole::ComboBox, - Role::Complementary => AtspiRole::Landmark, - Role::ContentDeletion => AtspiRole::ContentDeletion, - Role::ContentInsertion => AtspiRole::ContentInsertion, - Role::ContentInfo | Role::Footer => AtspiRole::Landmark, - Role::Date | Role::DateTime => AtspiRole::DateEditor, - Role::Definition | Role::DescriptionListDetail => AtspiRole::DescriptionValue, - Role::DescriptionList => AtspiRole::DescriptionList, - Role::DescriptionListTerm => AtspiRole::DescriptionTerm, - Role::Details => AtspiRole::Panel, - Role::Dialog => AtspiRole::Dialog, - Role::Directory => AtspiRole::List, - Role::DisclosureTriangle => AtspiRole::ToggleButton, - Role::DocCover => AtspiRole::Image, - Role::DocBackLink | Role::DocBiblioRef | Role::DocGlossRef | Role::DocNoteRef => AtspiRole::Link, - Role::DocBiblioEntry | Role::DocEndnote => AtspiRole::ListItem, - Role::DocNotice | Role::DocTip => AtspiRole::Comment, - Role::DocFootnote => AtspiRole::Footnote, - Role::DocPageBreak => AtspiRole::Separator, - Role::DocPageFooter => AtspiRole::Footer, - Role::DocPageHeader => AtspiRole::Header, - Role::DocAcknowledgements | Role::DocAfterword | Role::DocAppendix | Role::DocBibliography | Role::DocChapter | Role::DocConclusion | Role::DocCredits | Role::DocEndnotes | Role::DocEpilogue | Role::DocErrata | Role::DocForeword | Role::DocGlossary | Role::DocIndex | Role::DocIntroduction | Role::DocPageList | Role::DocPart | Role::DocPreface | Role::DocPrologue | Role::DocToc => AtspiRole::Landmark, - Role::DocAbstract | Role::DocColophon | Role::DocCredit | Role::DocDedication | Role::DocEpigraph | Role::DocExample | Role::DocPullquote | Role::DocQna => AtspiRole::Section, - Role::DocSubtitle => AtspiRole::Heading, - Role::Document => AtspiRole::DocumentFrame, - Role::EmbeddedObject => AtspiRole::Embedded, - // TODO: Forms which lack an accessible name are no longer - // exposed as forms. http://crbug.com/874384. Forms which have accessible - // names should be exposed as `AtspiRole::Landmark` according to Core AAM. - Role::Form => AtspiRole::Form, - Role::Figure | Role::Feed => AtspiRole::Panel, - Role::GenericContainer | Role::FooterAsNonLandmark | Role::HeaderAsNonLandmark | Role::Ruby => AtspiRole::Section, - Role::GraphicsDocument => AtspiRole::DocumentFrame, - Role::GraphicsObject => AtspiRole::Panel, - Role::GraphicsSymbol => AtspiRole::Image, - Role::Grid => AtspiRole::Table, - Role::Group => AtspiRole::Panel, - Role::Heading => AtspiRole::Heading, - Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame, - Role::Image => { - if node.unignored_children().next().is_some() { - AtspiRole::ImageMap - } else { - AtspiRole::Image + self.0 + .map(|node| { + match node.role() { + Role::Alert => AtspiRole::Notification, + Role::AlertDialog => AtspiRole::Alert, + Role::Comment | Role::Suggestion => AtspiRole::Section, + // TODO: See how to represent ARIA role="application" + Role::Application => AtspiRole::Embedded, + Role::Article => AtspiRole::Article, + Role::Audio => AtspiRole::Audio, + Role::Banner | Role::Header => AtspiRole::Landmark, + Role::Blockquote => AtspiRole::BlockQuote, + Role::Caret => AtspiRole::Unknown, + Role::Button => AtspiRole::PushButton, + Role::Canvas => AtspiRole::Canvas, + Role::Caption => AtspiRole::Caption, + Role::Cell => AtspiRole::TableCell, + Role::CheckBox => AtspiRole::CheckBox, + Role::Switch => AtspiRole::ToggleButton, + Role::ColorWell => AtspiRole::PushButton, + Role::Column => AtspiRole::Unknown, + Role::ColumnHeader => AtspiRole::ColumnHeader, + Role::ComboBoxGrouping | Role::ComboBoxMenuButton => AtspiRole::ComboBox, + Role::Complementary => AtspiRole::Landmark, + Role::ContentDeletion => AtspiRole::ContentDeletion, + Role::ContentInsertion => AtspiRole::ContentInsertion, + Role::ContentInfo | Role::Footer => AtspiRole::Landmark, + Role::Date | Role::DateTime => AtspiRole::DateEditor, + Role::Definition | Role::DescriptionListDetail => AtspiRole::DescriptionValue, + Role::DescriptionList => AtspiRole::DescriptionList, + Role::DescriptionListTerm => AtspiRole::DescriptionTerm, + Role::Details => AtspiRole::Panel, + Role::Dialog => AtspiRole::Dialog, + Role::Directory => AtspiRole::List, + Role::DisclosureTriangle => AtspiRole::ToggleButton, + Role::DocCover => AtspiRole::Image, + Role::DocBackLink + | Role::DocBiblioRef + | Role::DocGlossRef + | Role::DocNoteRef => AtspiRole::Link, + Role::DocBiblioEntry | Role::DocEndnote => AtspiRole::ListItem, + Role::DocNotice | Role::DocTip => AtspiRole::Comment, + Role::DocFootnote => AtspiRole::Footnote, + Role::DocPageBreak => AtspiRole::Separator, + Role::DocPageFooter => AtspiRole::Footer, + Role::DocPageHeader => AtspiRole::Header, + Role::DocAcknowledgements + | Role::DocAfterword + | Role::DocAppendix + | Role::DocBibliography + | Role::DocChapter + | Role::DocConclusion + | Role::DocCredits + | Role::DocEndnotes + | Role::DocEpilogue + | Role::DocErrata + | Role::DocForeword + | Role::DocGlossary + | Role::DocIndex + | Role::DocIntroduction + | Role::DocPageList + | Role::DocPart + | Role::DocPreface + | Role::DocPrologue + | Role::DocToc => AtspiRole::Landmark, + Role::DocAbstract + | Role::DocColophon + | Role::DocCredit + | Role::DocDedication + | Role::DocEpigraph + | Role::DocExample + | Role::DocPullquote + | Role::DocQna => AtspiRole::Section, + Role::DocSubtitle => AtspiRole::Heading, + Role::Document => AtspiRole::DocumentFrame, + Role::EmbeddedObject => AtspiRole::Embedded, + // TODO: Forms which lack an accessible name are no longer + // exposed as forms. http://crbug.com/874384. Forms which have accessible + // names should be exposed as `AtspiRole::Landmark` according to Core AAM. + Role::Form => AtspiRole::Form, + Role::Figure | Role::Feed => AtspiRole::Panel, + Role::GenericContainer + | Role::FooterAsNonLandmark + | Role::HeaderAsNonLandmark + | Role::Ruby => AtspiRole::Section, + Role::GraphicsDocument => AtspiRole::DocumentFrame, + Role::GraphicsObject => AtspiRole::Panel, + Role::GraphicsSymbol => AtspiRole::Image, + Role::Grid => AtspiRole::Table, + Role::Group => AtspiRole::Panel, + Role::Heading => AtspiRole::Heading, + Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame, + Role::Image => { + if node.unignored_children().next().is_some() { + AtspiRole::ImageMap + } else { + AtspiRole::Image + } } - }, - Role::InlineTextBox => AtspiRole::Static, - Role::InputTime => AtspiRole::DateEditor, - Role::LabelText | Role::Legend => AtspiRole::Label, - // Layout table objects are treated the same as `Role::GenericContainer`. - Role::LayoutTable => AtspiRole::Section, - Role::LayoutTableCell => AtspiRole::Section, - Role::LayoutTableRow => AtspiRole::Section, - // TODO: Having a separate accessible object for line breaks - // is inconsistent with other implementations. http://crbug.com/873144#c1. - Role::LineBreak => AtspiRole::Static, - Role::Link => AtspiRole::Link, - Role::List => AtspiRole::List, - Role::ListBox => AtspiRole::ListBox, - // TODO: Use `AtspiRole::MenuItem' inside a combo box, see how - // ax_platform_node_win.cc code does this. - Role::ListBoxOption => AtspiRole::ListItem, - Role::ListGrid => AtspiRole::Table, - Role::ListItem => AtspiRole::ListItem, - // Regular list markers only expose their alternative text, but do not - // expose their descendants, and the descendants should be ignored. This - // is because the alternative text depends on the counter style and can - // be different from the actual (visual) marker text, and hence, - // inconsistent with the descendants. We treat a list marker as non-text - // only if it still has non-ignored descendants, which happens only when => - // - The list marker itself is ignored but the descendants are not - // - Or the list marker contains images - Role::ListMarker => { - if node.unignored_children().next().is_none() { - AtspiRole::Static - } else { - AtspiRole::Panel + Role::InlineTextBox => AtspiRole::Static, + Role::InputTime => AtspiRole::DateEditor, + Role::LabelText | Role::Legend => AtspiRole::Label, + // Layout table objects are treated the same as `Role::GenericContainer`. + Role::LayoutTable => AtspiRole::Section, + Role::LayoutTableCell => AtspiRole::Section, + Role::LayoutTableRow => AtspiRole::Section, + // TODO: Having a separate accessible object for line breaks + // is inconsistent with other implementations. http://crbug.com/873144#c1. + Role::LineBreak => AtspiRole::Static, + Role::Link => AtspiRole::Link, + Role::List => AtspiRole::List, + Role::ListBox => AtspiRole::ListBox, + // TODO: Use `AtspiRole::MenuItem' inside a combo box, see how + // ax_platform_node_win.cc code does this. + Role::ListBoxOption => AtspiRole::ListItem, + Role::ListGrid => AtspiRole::Table, + Role::ListItem => AtspiRole::ListItem, + // Regular list markers only expose their alternative text, but do not + // expose their descendants, and the descendants should be ignored. This + // is because the alternative text depends on the counter style and can + // be different from the actual (visual) marker text, and hence, + // inconsistent with the descendants. We treat a list marker as non-text + // only if it still has non-ignored descendants, which happens only when => + // - The list marker itself is ignored but the descendants are not + // - Or the list marker contains images + Role::ListMarker => { + if node.unignored_children().next().is_none() { + AtspiRole::Static + } else { + AtspiRole::Panel + } } - }, - Role::Log => AtspiRole::Log, - Role::Main => AtspiRole::Landmark, - Role::Mark => AtspiRole::Static, - Role::Math => AtspiRole::Math, - Role::Marquee => AtspiRole::Marquee, - Role::Menu | Role::MenuListPopup => AtspiRole::Menu, - Role::MenuBar => AtspiRole::MenuBar, - Role::MenuItem | Role::MenuListOption => AtspiRole::MenuItem, - Role::MenuItemCheckBox => AtspiRole::CheckMenuItem, - Role::MenuItemRadio => AtspiRole::RadioMenuItem, - Role::Meter => AtspiRole::LevelBar, - Role::Navigation => AtspiRole::Landmark, - Role::Note => AtspiRole::Comment, - Role::Pane | Role::ScrollView => AtspiRole::Panel, - Role::Paragraph => AtspiRole::Paragraph, - Role::PdfActionableHighlight => AtspiRole::PushButton, - Role::PdfRoot => AtspiRole::DocumentFrame, - Role::PluginObject => AtspiRole::Embedded, - Role::PopupButton => { - if node.data().html_tag.as_ref().map_or(false, |tag| tag.as_ref() == "select") { - AtspiRole::ComboBox - } else { - AtspiRole::PushButton + Role::Log => AtspiRole::Log, + Role::Main => AtspiRole::Landmark, + Role::Mark => AtspiRole::Static, + Role::Math => AtspiRole::Math, + Role::Marquee => AtspiRole::Marquee, + Role::Menu | Role::MenuListPopup => AtspiRole::Menu, + Role::MenuBar => AtspiRole::MenuBar, + Role::MenuItem | Role::MenuListOption => AtspiRole::MenuItem, + Role::MenuItemCheckBox => AtspiRole::CheckMenuItem, + Role::MenuItemRadio => AtspiRole::RadioMenuItem, + Role::Meter => AtspiRole::LevelBar, + Role::Navigation => AtspiRole::Landmark, + Role::Note => AtspiRole::Comment, + Role::Pane | Role::ScrollView => AtspiRole::Panel, + Role::Paragraph => AtspiRole::Paragraph, + Role::PdfActionableHighlight => AtspiRole::PushButton, + Role::PdfRoot => AtspiRole::DocumentFrame, + Role::PluginObject => AtspiRole::Embedded, + Role::PopupButton => { + if node + .data() + .html_tag + .as_ref() + .map_or(false, |tag| tag.as_ref() == "select") + { + AtspiRole::ComboBox + } else { + AtspiRole::PushButton + } } - }, - Role::Portal => AtspiRole::PushButton, - Role::Pre => AtspiRole::Section, - Role::ProgressIndicator => AtspiRole::ProgressBar, - Role::RadioButton => AtspiRole::RadioButton, - Role::RadioGroup => AtspiRole::Panel, - Role::Region => AtspiRole::Landmark, - Role::RootWebArea => AtspiRole::DocumentWeb, - Role::Row => AtspiRole::TableRow, - Role::RowGroup => AtspiRole::Panel, - Role::RowHeader => AtspiRole::RowHeader, - // TODO: Generally exposed as description on (`Role::Ruby`) element, not - // as its own object in the tree. - // However, it's possible to make a `Role::RubyAnnotation` element show up in the - // AX tree, for example by adding tabindex="0" to the source or - // element or making the source element the target of an aria-owns. - // Therefore, browser side needs to gracefully handle it if it actually - // shows up in the tree. - Role::RubyAnnotation => AtspiRole::Static, - Role::Section => AtspiRole::Section, - Role::ScrollBar => AtspiRole::ScrollBar, - Role::Search => AtspiRole::Landmark, - Role::Slider => AtspiRole::Slider, - Role::SpinButton => AtspiRole::SpinButton, - Role::Splitter => AtspiRole::Separator, - Role::StaticText => AtspiRole::Static, - Role::Status => AtspiRole::StatusBar, - // ax::mojom::Role::kSubscript => - // AtspiRole::Subscript, - // ax::mojom::Role::kSuperscript => - // AtspiRole::Superscript, - Role::SvgRoot => AtspiRole::DocumentFrame, - Role::Tab => AtspiRole::PageTab, - Role::Table => AtspiRole::Table, - // TODO: This mapping is correct, but it doesn't seem to be - // used. We don't necessarily want to always expose these containers, but - // we must do so if they are focusable. http://crbug.com/874043 - Role::TableHeaderContainer => AtspiRole::Panel, - Role::TabList => AtspiRole::PageTabList, - Role::TabPanel => AtspiRole::ScrollPane, - // TODO: This mapping should also be applied to the dfn - // element. http://crbug.com/874411 - Role::Term => AtspiRole::DescriptionTerm, - Role::TitleBar => AtspiRole::TitleBar, - Role::TextField | Role::SearchBox => { - if node.data().protected { - AtspiRole::PasswordText - } else { - AtspiRole::Entry + Role::Portal => AtspiRole::PushButton, + Role::Pre => AtspiRole::Section, + Role::ProgressIndicator => AtspiRole::ProgressBar, + Role::RadioButton => AtspiRole::RadioButton, + Role::RadioGroup => AtspiRole::Panel, + Role::Region => AtspiRole::Landmark, + Role::RootWebArea => AtspiRole::DocumentWeb, + Role::Row => AtspiRole::TableRow, + Role::RowGroup => AtspiRole::Panel, + Role::RowHeader => AtspiRole::RowHeader, + // TODO: Generally exposed as description on (`Role::Ruby`) element, not + // as its own object in the tree. + // However, it's possible to make a `Role::RubyAnnotation` element show up in the + // AX tree, for example by adding tabindex="0" to the source or + // element or making the source element the target of an aria-owns. + // Therefore, browser side needs to gracefully handle it if it actually + // shows up in the tree. + Role::RubyAnnotation => AtspiRole::Static, + Role::Section => AtspiRole::Section, + Role::ScrollBar => AtspiRole::ScrollBar, + Role::Search => AtspiRole::Landmark, + Role::Slider => AtspiRole::Slider, + Role::SpinButton => AtspiRole::SpinButton, + Role::Splitter => AtspiRole::Separator, + Role::StaticText => AtspiRole::Static, + Role::Status => AtspiRole::StatusBar, + // ax::mojom::Role::kSubscript => + // AtspiRole::Subscript, + // ax::mojom::Role::kSuperscript => + // AtspiRole::Superscript, + Role::SvgRoot => AtspiRole::DocumentFrame, + Role::Tab => AtspiRole::PageTab, + Role::Table => AtspiRole::Table, + // TODO: This mapping is correct, but it doesn't seem to be + // used. We don't necessarily want to always expose these containers, but + // we must do so if they are focusable. http://crbug.com/874043 + Role::TableHeaderContainer => AtspiRole::Panel, + Role::TabList => AtspiRole::PageTabList, + Role::TabPanel => AtspiRole::ScrollPane, + // TODO: This mapping should also be applied to the dfn + // element. http://crbug.com/874411 + Role::Term => AtspiRole::DescriptionTerm, + Role::TitleBar => AtspiRole::TitleBar, + Role::TextField | Role::SearchBox => { + if node.data().protected { + AtspiRole::PasswordText + } else { + AtspiRole::Entry + } } - }, - Role::TextFieldWithComboBox => AtspiRole::ComboBox, - Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => AtspiRole::Static, - Role::Timer => AtspiRole::Timer, - Role::ToggleButton => AtspiRole::ToggleButton, - Role::Toolbar => AtspiRole::ToolBar, - Role::Tooltip => AtspiRole::ToolTip, - Role::Tree => AtspiRole::Tree, - Role::TreeItem => AtspiRole::TreeItem, - Role::TreeGrid => AtspiRole::TreeTable, - Role::Video => AtspiRole::Video, - // In AT-SPI, elements with `AtspiRole::Frame` are windows with titles and - // buttons, while those with `AtspiRole::Window` are windows without those - // elements. - Role::Window => AtspiRole::Frame, - Role::Client | Role::WebView => AtspiRole::Panel, - Role::FigureCaption => AtspiRole::Caption, - // TODO: Are there special cases to consider? - Role::Presentation | Role::Unknown => AtspiRole::Unknown, - Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject, - } - }).unwrap_or(AtspiRole::Invalid) + Role::TextFieldWithComboBox => AtspiRole::ComboBox, + Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => { + AtspiRole::Static + } + Role::Timer => AtspiRole::Timer, + Role::ToggleButton => AtspiRole::ToggleButton, + Role::Toolbar => AtspiRole::ToolBar, + Role::Tooltip => AtspiRole::ToolTip, + Role::Tree => AtspiRole::Tree, + Role::TreeItem => AtspiRole::TreeItem, + Role::TreeGrid => AtspiRole::TreeTable, + Role::Video => AtspiRole::Video, + // In AT-SPI, elements with `AtspiRole::Frame` are windows with titles and + // buttons, while those with `AtspiRole::Window` are windows without those + // elements. + Role::Window => AtspiRole::Frame, + Role::Client | Role::WebView => AtspiRole::Panel, + Role::FigureCaption => AtspiRole::Caption, + // TODO: Are there special cases to consider? + Role::Presentation | Role::Unknown => AtspiRole::Unknown, + Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject, + } + }) + .unwrap_or(AtspiRole::Invalid) } fn state(&self) -> StateSet { let platform_role = self.role(); - self.0.map(|node| { - let data = node.data(); - let mut state = StateSet::empty(); - if let Ok(current_active) = crate::adapter::CURRENT_ACTIVE_WINDOW.lock() { - if node.role() == Role::Window && *current_active == Some(data.id) { - state.insert(State::Active); + self.0 + .map(|node| { + let data = node.data(); + let mut state = StateSet::empty(); + if let Ok(current_active) = crate::adapter::CURRENT_ACTIVE_WINDOW.lock() { + if node.role() == Role::Window && *current_active == Some(data.id) { + state.insert(State::Active); + } + } + if let Some(expanded) = data.expanded { + state.insert(State::Expandable); + if expanded { + state.insert(State::Expanded); + } + } + if data.default { + state.insert(State::IsDefault); + } + if data.editable && !data.read_only { + state.insert(State::Editable); + } + // TODO: Focus and selection. + if data.focusable { + state.insert(State::Focusable); + } + match data.orientation { + Some(Orientation::Horizontal) => state.insert(State::Horizontal), + Some(Orientation::Vertical) => state.insert(State::Vertical), + _ => {} + } + if !node.is_invisible_or_ignored() { + state.insert(State::Visible); + // if (!delegate_->IsOffscreen() && !is_minimized) + state.insert(State::Showing); + } + if data.multiselectable { + state.insert(State::Multiselectable); + } + if data.required { + state.insert(State::Required); + } + if data.visited { + state.insert(State::Visited); + } + if let Some(InvalidState::True | InvalidState::Other(_)) = data.invalid_state { + state.insert(State::InvalidEntry); } - } - if let Some(expanded) = data.expanded { - state.insert(State::Expandable); - if expanded { - state.insert(State::Expanded); + match data.aria_current { + None | Some(AriaCurrent::False) => {} + _ => state.insert(State::Active), } - } - if data.default { - state.insert(State::IsDefault); - } - if data.editable && !data.read_only { - state.insert(State::Editable); - } - // TODO: Focus and selection. - if data.focusable { - state.insert(State::Focusable); - } - match data.orientation { - Some(Orientation::Horizontal) => state.insert(State::Horizontal), - Some(Orientation::Vertical) => state.insert(State::Vertical), - _ => {} - } - if !node.is_invisible_or_ignored() { - state.insert(State::Visible); - // if (!delegate_->IsOffscreen() && !is_minimized) - state.insert(State::Showing); - } - if data.multiselectable { - state.insert(State::Multiselectable); - } - if data.required { - state.insert(State::Required); - } - if data.visited { - state.insert(State::Visited); - } - if let Some(InvalidState::True | InvalidState::Other(_)) = data.invalid_state { - state.insert(State::InvalidEntry); - } - match data.aria_current { - None | Some(AriaCurrent::False) => {}, - _ => state.insert(State::Active) - } - if platform_role != AtspiRole::ToggleButton && data.checked_state.is_some() { - state.insert(State::Checkable); - } - if data.has_popup.is_some() { - state.insert(State::HasPopup); - } - if data.busy { - state.insert(State::Busy); - } - if data.modal { - state.insert(State::Modal); - } - if let Some(selected) = data.selected { - if !data.disabled { - state.insert(State::Selectable); + if platform_role != AtspiRole::ToggleButton && data.checked_state.is_some() { + state.insert(State::Checkable); } - if selected { - state.insert(State::Selected); + if data.has_popup.is_some() { + state.insert(State::HasPopup); } - } - // if (IsTextField()) { - // state.insert(State::SelectableText); - // match node.data().multiline { - // true => state.insert(State::MultiLine), - // false => state.insert(State::SingleLine) - // } - // } - - // Special case for indeterminate progressbar. - if node.role() == Role::ProgressIndicator && data.value_for_range.is_none() { - state.insert(State::Indeterminate); - } - - if data.auto_complete.as_ref().map_or(false, |auto_complete| auto_complete.as_ref().is_empty()) || data.autofill_available { - state.insert(State::SupportsAutocompletion); - } - - // Checked state - match data.checked_state { - Some(CheckedState::Mixed) => state.insert(State::Indeterminate), - Some(CheckedState::True) => { - if platform_role == AtspiRole::ToggleButton { - state.insert(State::Pressed); - } else { - state.insert(State::Checked); + if data.busy { + state.insert(State::Busy); + } + if data.modal { + state.insert(State::Modal); + } + if let Some(selected) = data.selected { + if !data.disabled { + state.insert(State::Selectable); + } + if selected { + state.insert(State::Selected); + } + } + // if (IsTextField()) { + // state.insert(State::SelectableText); + // match node.data().multiline { + // true => state.insert(State::MultiLine), + // false => state.insert(State::SingleLine) + // } + // } + + // Special case for indeterminate progressbar. + if node.role() == Role::ProgressIndicator && data.value_for_range.is_none() { + state.insert(State::Indeterminate); + } + + if data + .auto_complete + .as_ref() + .map_or(false, |auto_complete| auto_complete.as_ref().is_empty()) + || data.autofill_available + { + state.insert(State::SupportsAutocompletion); + } + + // Checked state + match data.checked_state { + Some(CheckedState::Mixed) => state.insert(State::Indeterminate), + Some(CheckedState::True) => { + if platform_role == AtspiRole::ToggleButton { + state.insert(State::Pressed); + } else { + state.insert(State::Checked); + } } - }, - _ => {} - } - - if data.role.is_read_only_supported() && node.is_read_only_or_disabled() { - state.insert(State::ReadOnly); - } else { - state.insert(State::Enabled); - state.insert(State::Sensitive); - } - - if node.is_focused() { - state.insert(State::Focused); - } - - // It is insufficient to compare with g_current_activedescendant due to both - // timing and event ordering for objects which implement AtkSelection and also - // have an active descendant. For instance, if we check the state set of a - // selectable child, it will only have ATK_STATE_FOCUSED if we've processed - // the activedescendant change. - // if (GetActiveDescendantOfCurrentFocused() == atk_object) - // state.insert(State::Focused); - state - }).unwrap_or(State::Defunct.into()) + _ => {} + } + + if data.role.is_read_only_supported() && node.is_read_only_or_disabled() { + state.insert(State::ReadOnly); + } else { + state.insert(State::Enabled); + state.insert(State::Sensitive); + } + + if node.is_focused() { + state.insert(State::Focused); + } + + // It is insufficient to compare with g_current_activedescendant due to both + // timing and event ordering for objects which implement AtkSelection and also + // have an active descendant. For instance, if we check the state set of a + // selectable child, it will only have ATK_STATE_FOCUSED if we've processed + // the activedescendant change. + // if (GetActiveDescendantOfCurrentFocused() == atk_object) + // state.insert(State::Focused); + state + }) + .unwrap_or(State::Defunct.into()) } - fn interfaces(&self) -> Interfaces { - self.0.map(|node| { - let mut interfaces: Interfaces = Interface::Accessible | Interface::ObjectEvents; - if node.role() == Role::Window { - interfaces.insert(Interface::WindowEvents); - } - if node.data().focusable { - interfaces.insert(Interface::FocusEvents); - } - interfaces - }).unwrap() + self.0 + .map(|node| { + let mut interfaces: Interfaces = Interface::Accessible | Interface::ObjectEvents; + if node.role() == Role::Window { + interfaces.insert(Interface::WindowEvents); + } + if node.data().focusable { + interfaces.insert(Interface::FocusEvents); + } + interfaces + }) + .unwrap() } } @@ -410,14 +468,19 @@ pub struct RootPlatformNode { } impl RootPlatformNode { - pub fn new(app_name: String, toolkit_name: String, toolkit_version: String, tree: Arc) -> Self { + pub fn new( + app_name: String, + toolkit_name: String, + toolkit_version: String, + tree: Arc, + ) -> Self { Self { app_name, app_id: None, desktop_address: None, tree, toolkit_name, - toolkit_version + toolkit_version, } } } @@ -471,9 +534,7 @@ impl Application for RootPlatformNode { self.desktop_address = Some(address); } - fn register_event_listener(&mut self, _: String) { - } + fn register_event_listener(&mut self, _: String) {} - fn deregister_event_listener(&mut self, _: String) { - } + fn deregister_event_listener(&mut self, _: String) {} } From 447aa60d97e2a7e7b65e3d62a900c6e128089b0e Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 22 Dec 2021 01:36:52 +0100 Subject: [PATCH 15/69] Refactor node state method --- platforms/linux/src/node.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 234a47d5f..0ddf4f1f1 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -376,32 +376,31 @@ impl Accessible for PlatformNode { state.insert(State::Modal); } if let Some(selected) = data.selected { - if !data.disabled { + if !node.is_disabled() { state.insert(State::Selectable); } if selected { state.insert(State::Selected); } } - // if (IsTextField()) { - // state.insert(State::SelectableText); - // match node.data().multiline { - // true => state.insert(State::MultiLine), - // false => state.insert(State::SingleLine) - // } - // } + if node.is_text_field() { + state.insert(State::SelectableText); + match node.data().multiline { + true => state.insert(State::MultiLine), + false => state.insert(State::SingleLine) + } + } // Special case for indeterminate progressbar. if node.role() == Role::ProgressIndicator && data.value_for_range.is_none() { state.insert(State::Indeterminate); } - if data + let has_suggestion = data .auto_complete .as_ref() - .map_or(false, |auto_complete| auto_complete.as_ref().is_empty()) - || data.autofill_available - { + .map_or(false, |a| !a.as_ref().is_empty()); + if has_suggestion || data.autofill_available { state.insert(State::SupportsAutocompletion); } @@ -418,7 +417,7 @@ impl Accessible for PlatformNode { _ => {} } - if data.role.is_read_only_supported() && node.is_read_only_or_disabled() { + if node.is_read_only_supported() && node.is_read_only_or_disabled() { state.insert(State::ReadOnly); } else { state.insert(State::Enabled); From 992ef7012b8c55435432c38338af2e92eed09564 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 22 Dec 2021 19:01:41 +0100 Subject: [PATCH 16/69] Various fixes after rebase --- platforms/linux/Cargo.toml | 4 ++-- platforms/linux/examples/hello_world.rs | 11 ++++++++++- platforms/linux/src/adapter.rs | 9 +++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index f4ccd3997..ebe6702be 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -6,8 +6,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -accesskit = { path = "../../common" } -accesskit_consumer = { path = "../../consumer" } +accesskit = { version = "0.1.1", path = "../../common" } +accesskit_consumer = { version = "0.1.1", path = "../../consumer" } async-io = "1.4.1" enumflags2 = "0.7.1" lazy_static = "1.4.0" diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index fae51a098..09a5bd69b 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -3,7 +3,9 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{Node, NodeId, Role, StringEncoding, Tree, TreeId, TreeUpdate}; +use accesskit::{ + ActionHandler, ActionRequest, Node, NodeId, Role, StringEncoding, Tree, TreeId, TreeUpdate +}; use accesskit_linux::Adapter; use std::num::NonZeroU64; use winit::{ @@ -18,6 +20,12 @@ const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(1) }); const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(3) }); +struct NullActionHandler; + +impl ActionHandler for NullActionHandler { + fn do_action(&self, _request: ActionRequest) {} +} + fn get_tree() -> Tree { Tree { ..Tree::new(TreeId("test".into()), WINDOW_ID, StringEncoding::Utf8) @@ -56,6 +64,7 @@ fn main() { String::from("ExampleUI"), String::from("0.1.0"), get_initial_state(), + Box::new(NullActionHandler {}), ) .unwrap(); let event_loop = EventLoop::new(); diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index c2413c2bc..2ed9fb40d 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex}; -use accesskit::{NodeId, Role, TreeUpdate}; +use accesskit::{ActionHandler, NodeId, Role, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ @@ -30,9 +30,10 @@ impl<'a> Adapter<'a> { toolkit_name: String, toolkit_version: String, initial_state: TreeUpdate, + action_handler: Box, ) -> Option { let mut atspi_bus = Bus::a11y_bus()?; - let tree = Tree::new(initial_state); + let tree = Tree::new(initial_state, action_handler); let app_node = RootPlatformNode::new(app_name, toolkit_name, toolkit_version, tree.clone()); let mut objects_to_add = Vec::new(); @@ -99,8 +100,8 @@ impl<'a> Adapter<'a> { events.push(ObjectEvent::StateChanged(state, new_state.contains(state))); } if let Some(name) = new_node.name() { - if old_node.name() != Some(name) { - events.push(ObjectEvent::NameChanged(name.to_string())); + if old_node.name().as_ref() != Some(&name) { + events.push(ObjectEvent::NameChanged(name)); } } self.atspi_bus From 051451738acb28adb2cb05aed454cf4921674677 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Fri, 5 Aug 2022 19:25:08 +0200 Subject: [PATCH 17/69] Rebase and bump dependencies --- platforms/linux/Cargo.toml | 10 +-- platforms/linux/examples/hello_world.rs | 16 ++-- platforms/linux/src/atspi/bus.rs | 84 +++++++++------------ platforms/linux/src/atspi/mod.rs | 2 +- platforms/linux/src/atspi/object_address.rs | 5 +- platforms/linux/src/atspi/object_id.rs | 5 +- platforms/linux/src/lib.rs | 2 - platforms/linux/src/node.rs | 2 +- 8 files changed, 49 insertions(+), 77 deletions(-) diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index ebe6702be..f25b6c984 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -6,8 +6,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -accesskit = { version = "0.1.1", path = "../../common" } -accesskit_consumer = { version = "0.1.1", path = "../../consumer" } +accesskit = { version = "0.4.0", path = "../../common" } +accesskit_consumer = { version = "0.4.0", path = "../../consumer" } async-io = "1.4.1" enumflags2 = "0.7.1" lazy_static = "1.4.0" @@ -15,8 +15,8 @@ parking_lot = "0.11.1" serde = "1.0" strum = { version = "0.23.0", features = ["derive"] } x11rb = "0.8.1" -zbus = "2.0.0-beta.7" -zvariant = "2.9.0" +zbus = "2.3.2" +zvariant = "3.3.1" [dev-dependencies] -winit = "0.25.0" +winit = "0.26.1" diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index 09a5bd69b..b5ddf458a 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -4,10 +4,10 @@ // the LICENSE-MIT file), at your option. use accesskit::{ - ActionHandler, ActionRequest, Node, NodeId, Role, StringEncoding, Tree, TreeId, TreeUpdate + ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate }; use accesskit_linux::Adapter; -use std::num::NonZeroU64; +use std::num::NonZeroU128; use winit::{ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -16,9 +16,9 @@ use winit::{ const WINDOW_TITLE: &str = "Hello world"; -const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(1) }); -const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(2) }); -const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(3) }); +const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(1) }); +const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); +const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); struct NullActionHandler; @@ -28,7 +28,7 @@ impl ActionHandler for NullActionHandler { fn get_tree() -> Tree { Tree { - ..Tree::new(TreeId("test".into()), WINDOW_ID, StringEncoding::Utf8) + ..Tree::new(WINDOW_ID) } } @@ -49,7 +49,6 @@ fn get_initial_state() -> TreeUpdate { let button_1 = make_button(BUTTON_1_ID, "Button 1"); let button_2 = make_button(BUTTON_2_ID, "Button 2"); TreeUpdate { - clear: None, nodes: vec![root, button_1, button_2], tree: Some(get_tree()), focus: None, @@ -82,7 +81,6 @@ fn main() { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::Focused(window_has_focus) => { adapter.update(TreeUpdate { - clear: None, nodes: vec![], focus: window_has_focus.then(|| unsafe { FOCUS }), tree: None, @@ -103,7 +101,6 @@ fn main() { BUTTON_1_ID }; adapter.update(TreeUpdate { - clear: None, nodes: vec![], focus: Some(FOCUS), tree: None, @@ -124,7 +121,6 @@ fn main() { make_button(BUTTON_2_ID, "You pressed button 2") }; adapter.update(TreeUpdate { - clear: None, nodes: vec![updated_node], focus: Some(FOCUS), tree: None, diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 5589ae4b5..234f7f2f9 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -11,9 +11,8 @@ use crate::atspi::{ use async_io::block_on; use parking_lot::RwLock; use std::{ + convert::TryInto, env::var, - os::unix::net::{SocketAddr, UnixStream}, - str::FromStr, sync::Arc, }; use x11rb::{ @@ -22,7 +21,7 @@ use x11rb::{ }; use zbus::{ blocking::{Connection, ConnectionBuilder}, - Address, InterfaceDeref, Result, + Address, Result, }; pub struct Bus<'a> { @@ -42,7 +41,7 @@ impl<'a> Bus<'a> { T: Accessible, { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, object.id().as_str()); - if self.conn.object_server_mut().at( + if self.conn.object_server().at( path.clone(), AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), object.clone()), )? { @@ -74,7 +73,7 @@ impl<'a> Bus<'a> { Accessible::id(&root).as_str() ); let root = Arc::new(RwLock::new(root)); - let registered = self.conn.object_server_mut().at( + let registered = self.conn.object_server().at( path, ApplicationInterface(ApplicationInterfaceWrapper(root.clone())), )?; @@ -93,13 +92,13 @@ impl<'a> Bus<'a> { fn register_focus_events_interface(&mut self, path: &str) -> Result { self.conn - .object_server_mut() + .object_server() .at(path, FocusEventsInterface {}) } fn register_object_events_interface(&mut self, path: &str) -> Result { self.conn - .object_server_mut() + .object_server() .at(path, ObjectEventsInterface {}) } @@ -108,7 +107,7 @@ impl<'a> Bus<'a> { T: Accessible, { self.conn - .object_server_mut() + .object_server() .at(path, WindowEventsInterface(object)) } @@ -117,10 +116,11 @@ impl<'a> Bus<'a> { T: Accessible, { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.conn.object_server().with( - path, - |iface: InterfaceDeref<'_, FocusEventsInterface>, ctxt| block_on(iface.focused(&ctxt)), - ) + let iface_ref = self.conn.object_server() + .interface::<_, FocusEventsInterface>(path) + .unwrap(); + let iface = iface_ref.get(); + block_on(iface.focused(iface_ref.signal_context())) } pub fn emit_object_event(&self, target: &T, event: ObjectEvent) -> Result<()> @@ -128,12 +128,11 @@ impl<'a> Bus<'a> { T: Accessible, { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.conn.object_server().with( - path, - |iface: InterfaceDeref<'_, ObjectEventsInterface>, ctxt| { - block_on(iface.emit(event, &ctxt)) - }, - ) + let iface_ref = self.conn.object_server() + .interface::<_, ObjectEventsInterface>(path) + .unwrap(); + let iface = iface_ref.get(); + block_on(iface.emit(event, iface_ref.signal_context())) } pub fn emit_object_events(&self, target: &T, events: Vec) -> Result<()> @@ -141,17 +140,15 @@ impl<'a> Bus<'a> { T: Accessible, { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.conn.object_server().with( - path, - |iface: InterfaceDeref<'_, ObjectEventsInterface>, ctxt| { - block_on(async { - for event in events { - iface.emit(event, &ctxt).await?; - } - Ok(()) - }) - }, - ) + let iface_ref = self.conn.object_server() + .interface::<_, ObjectEventsInterface>(path) + .unwrap(); + block_on(async { + for event in events { + iface_ref.get().emit(event, iface_ref.signal_context()).await?; + } + Ok(()) + }) } pub fn emit_window_event(&self, target: &T, event: WindowEvent) -> Result<()> @@ -159,12 +156,11 @@ impl<'a> Bus<'a> { T: Accessible, { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - self.conn.object_server().with( - path, - |iface: InterfaceDeref<'_, WindowEventsInterface>, ctxt| { - block_on(iface.emit(event, &ctxt)) - }, - ) + let iface_ref = self.conn.object_server() + .interface::<_, WindowEventsInterface>(path) + .unwrap(); + let iface = iface_ref.get(); + block_on(iface.emit(event, iface_ref.signal_context())) } } @@ -227,21 +223,9 @@ fn a11y_bus() -> Option { if address.is_none() { address = a11y_bus_address_from_dbus(); } - let address = address?; - let guid_index = address.find(',').unwrap_or(address.len()); - if address.starts_with("unix:abstract=") { - ConnectionBuilder::unix_stream( - UnixStream::connect_addr( - &SocketAddr::from_abstract_namespace(address[14..guid_index].as_bytes()).ok()?, - ) - .ok()?, - ) + let address: Address = address?.as_str().try_into().ok()?; + ConnectionBuilder::address(address) + .ok()? .build() .ok() - } else { - ConnectionBuilder::address(Address::from_str(address.get(0..guid_index)?).ok()?) - .ok()? - .build() - .ok() - } } diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs index c3db77ccf..71b5d3ece 100644 --- a/platforms/linux/src/atspi/mod.rs +++ b/platforms/linux/src/atspi/mod.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use serde::{Deserialize, Serialize}; -use zvariant::derive::Type; +use zvariant::Type; mod bus; pub mod interfaces; diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs index 3989b1c81..ac6d895cd 100644 --- a/platforms/linux/src/atspi/object_address.rs +++ b/platforms/linux/src/atspi/object_address.rs @@ -6,10 +6,7 @@ use crate::atspi::ObjectId; use serde::{Deserialize, Serialize}; use zbus::names::{OwnedUniqueName, UniqueName}; -use zvariant::{ - derive::{Type, Value}, - ObjectPath, OwnedObjectPath, -}; +use zvariant::{ObjectPath, OwnedObjectPath, Type, Value}; pub const ACCESSIBLE_PATH_PREFIX: &'static str = "/org/a11y/atspi/accessible/"; pub const NULL_PATH: &'static str = "/org/a11y/atspi/null"; diff --git a/platforms/linux/src/atspi/object_id.rs b/platforms/linux/src/atspi/object_id.rs index 58d167f8c..a0a1adf4b 100644 --- a/platforms/linux/src/atspi/object_id.rs +++ b/platforms/linux/src/atspi/object_id.rs @@ -5,10 +5,7 @@ use accesskit::NodeId; use serde::{Deserialize, Serialize}; -use zvariant::{ - derive::{Type, Value}, - Str, -}; +use zvariant::{Str, Type, Value}; #[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] pub struct ObjectId<'a>(#[serde(borrow)] Str<'a>); diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs index 2836f9da7..886bcd8ef 100644 --- a/platforms/linux/src/lib.rs +++ b/platforms/linux/src/lib.rs @@ -3,8 +3,6 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -#![feature(unix_socket_abstract)] - #[macro_use] extern crate lazy_static; #[macro_use] diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 0ddf4f1f1..f7392d87d 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -392,7 +392,7 @@ impl Accessible for PlatformNode { } // Special case for indeterminate progressbar. - if node.role() == Role::ProgressIndicator && data.value_for_range.is_none() { + if node.role() == Role::ProgressIndicator && data.numeric_value.is_none() { state.insert(State::Indeterminate); } From 3e63b01c27eab6e40af5280747a45be99920c9d9 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 14 Aug 2022 23:06:13 +0200 Subject: [PATCH 18/69] Simplify D-Bus interfaces by getting rid of traits --- platforms/linux/src/adapter.rs | 51 +- platforms/linux/src/atspi/bus.rs | 73 +-- .../linux/src/atspi/interfaces/accessible.rs | 207 ++++---- .../linux/src/atspi/interfaces/application.rs | 169 +------ .../linux/src/atspi/interfaces/events.rs | 21 +- platforms/linux/src/lib.rs | 1 + platforms/linux/src/node.rs | 450 ++++++++---------- 7 files changed, 412 insertions(+), 560 deletions(-) diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 2ed9fb40d..f907bca93 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -9,10 +9,11 @@ use accesskit::{ActionHandler, NodeId, Role, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ - interfaces::{Accessible, ObjectEvent, WindowEvent}, + interfaces::{ObjectEvent, WindowEvent}, Bus, State, }; -use crate::node::{PlatformNode, RootPlatformNode}; +use crate::node::{AppState, PlatformNode, PlatformRootNode, ResolvedPlatformNode}; +use parking_lot::RwLock; lazy_static! { pub(crate) static ref CURRENT_ACTIVE_WINDOW: Arc>> = @@ -21,6 +22,7 @@ lazy_static! { pub struct Adapter<'a> { atspi_bus: Bus<'a>, + app_state: Arc>, tree: Arc, } @@ -34,23 +36,30 @@ impl<'a> Adapter<'a> { ) -> Option { let mut atspi_bus = Bus::a11y_bus()?; let tree = Tree::new(initial_state, action_handler); - let app_node = RootPlatformNode::new(app_name, toolkit_name, toolkit_version, tree.clone()); - let mut objects_to_add = Vec::new(); + { + let reader = tree.read(); + let mut objects_to_add = Vec::new(); - fn add_children(node: Node, to_add: &mut Vec) { - for child in node.unignored_children() { - to_add.push(PlatformNode::new(&child)); - add_children(child, to_add); + fn add_children<'b>(node: Node<'b>, to_add: &mut Vec>) { + for child in node.unignored_children() { + to_add.push(ResolvedPlatformNode::new(child)); + add_children(child, to_add); + } } - } - objects_to_add.push(PlatformNode::new(&tree.read().root())); - add_children(tree.read().root(), &mut objects_to_add); - for node in objects_to_add { - atspi_bus.register_accessible_interface(node); + objects_to_add.push(ResolvedPlatformNode::new(reader.root())); + add_children(reader.root(), &mut objects_to_add); + for node in objects_to_add { + atspi_bus.register_node(node); + } } - atspi_bus.register_application_interface(app_node); - Some(Self { atspi_bus, tree }) + let app_state = Arc::new(RwLock::new(AppState::new(app_name, toolkit_name, toolkit_version))); + atspi_bus.register_root_node(PlatformRootNode::new(Arc::downgrade(&app_state), Arc::downgrade(&tree))); + Some(Self { + atspi_bus, + app_state, + tree, + }) } pub fn update(&self, update: TreeUpdate) { @@ -58,7 +67,7 @@ impl<'a> Adapter<'a> { match change { TreeChange::FocusMoved { old_node, new_node } => { if let Some(old_node) = old_node { - let old_node = PlatformNode::new(&old_node); + let old_node = ResolvedPlatformNode::new(old_node); self.atspi_bus .emit_object_event( &old_node, @@ -79,7 +88,7 @@ impl<'a> Adapter<'a> { if let Some(node_window_id) = node_window_id { self.window_activated(node_window_id); } - let new_node = PlatformNode::new(&new_node); + let new_node = ResolvedPlatformNode::new(new_node); self.atspi_bus .emit_object_event( &new_node, @@ -91,8 +100,8 @@ impl<'a> Adapter<'a> { } } TreeChange::NodeUpdated { old_node, new_node } => { - let old_state = PlatformNode::new(&old_node).state(); - let new_platform_node = PlatformNode::new(&new_node); + let old_state = ResolvedPlatformNode::new(old_node).state(); + let new_platform_node = ResolvedPlatformNode::new(new_node); let new_state = new_platform_node.state(); let changed_states = old_state ^ new_state; let mut events = Vec::new(); @@ -115,7 +124,7 @@ impl<'a> Adapter<'a> { fn window_activated(&self, window_id: NodeId) { let reader = self.tree.read(); - let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); + let node = ResolvedPlatformNode::new(reader.node_by_id(window_id).unwrap()); self.atspi_bus .emit_window_event(&node, WindowEvent::Activated); self.atspi_bus @@ -124,7 +133,7 @@ impl<'a> Adapter<'a> { fn window_deactivated(&self, window_id: NodeId) { let reader = self.tree.read(); - let node = PlatformNode::new(&reader.node_by_id(window_id).unwrap()); + let node = ResolvedPlatformNode::new(reader.node_by_id(window_id).unwrap()); self.atspi_bus .emit_object_event(&node, ObjectEvent::StateChanged(State::Active, false)); self.atspi_bus diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 234f7f2f9..0ad05f5e3 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -7,13 +7,13 @@ use crate::atspi::{ interfaces::*, object_address::*, proxies::{BusProxy, SocketProxy}, + ObjectId }; +use crate::{PlatformNode, PlatformRootNode, ResolvedPlatformNode}; use async_io::block_on; -use parking_lot::RwLock; use std::{ convert::TryInto, env::var, - sync::Arc, }; use x11rb::{ connection::Connection as _, @@ -36,24 +36,21 @@ impl<'a> Bus<'a> { Some(Bus { conn, socket_proxy }) } - pub fn register_accessible_interface(&mut self, object: T) -> Result - where - T: Accessible, - { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, object.id().as_str()); + pub fn register_node(&mut self, node: ResolvedPlatformNode) -> Result { + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, node.id().as_str()); if self.conn.object_server().at( path.clone(), - AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), object.clone()), + AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), node.downgrade()), )? { - let interfaces = object.interfaces(); + let interfaces = node.interfaces(); if interfaces.contains(Interface::FocusEvents) { - self.register_focus_events_interface(&path)?; + self.register_focus_events(&path)?; } if interfaces.contains(Interface::ObjectEvents) { - self.register_object_events_interface(&path)?; + self.register_object_events(&path)?; } if interfaces.contains(Interface::WindowEvents) { - self.register_window_events_interface(&path, object) + self.register_window_events(&path, node.downgrade()) } else { Ok(true) } @@ -62,59 +59,50 @@ impl<'a> Bus<'a> { } } - pub fn register_application_interface(&mut self, root: T) -> Result - where - T: Application, - { + pub fn register_root_node(&mut self, node: PlatformRootNode) -> Result { println!("Registering on {:?}", self.conn.unique_name().unwrap()); let path = format!( "{}{}", ACCESSIBLE_PATH_PREFIX, - Accessible::id(&root).as_str() + ObjectId::root().as_str() ); - let root = Arc::new(RwLock::new(root)); let registered = self.conn.object_server().at( + path.clone(), + ApplicationInterface(node.clone()), + )? && self.conn.object_server().at( path, - ApplicationInterface(ApplicationInterfaceWrapper(root.clone())), + AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), node.clone()), )?; - if registered - && self.register_accessible_interface(ApplicationInterfaceWrapper(root.clone()))? - { + if registered { let desktop_address = self.socket_proxy.embed(ObjectAddress::root( self.conn.unique_name().unwrap().as_ref(), ))?; - root.write().set_desktop(desktop_address); + node.state.upgrade().map(|state| state.write().desktop_address = Some(desktop_address)); Ok(true) } else { Ok(false) } } - fn register_focus_events_interface(&mut self, path: &str) -> Result { + fn register_focus_events(&mut self, path: &str) -> Result { self.conn .object_server() .at(path, FocusEventsInterface {}) } - fn register_object_events_interface(&mut self, path: &str) -> Result { + fn register_object_events(&mut self, path: &str) -> Result { self.conn .object_server() .at(path, ObjectEventsInterface {}) } - fn register_window_events_interface(&mut self, path: &str, object: T) -> Result - where - T: Accessible, - { + fn register_window_events(&mut self, path: &str, node: PlatformNode) -> Result { self.conn .object_server() - .at(path, WindowEventsInterface(object)) + .at(path, WindowEventsInterface(node)) } - pub fn emit_focus_event(&self, target: &T) -> Result<()> - where - T: Accessible, - { + pub fn emit_focus_event(&self, target: &ResolvedPlatformNode) -> Result<()> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); let iface_ref = self.conn.object_server() .interface::<_, FocusEventsInterface>(path) @@ -123,10 +111,7 @@ impl<'a> Bus<'a> { block_on(iface.focused(iface_ref.signal_context())) } - pub fn emit_object_event(&self, target: &T, event: ObjectEvent) -> Result<()> - where - T: Accessible, - { + pub fn emit_object_event(&self, target: &ResolvedPlatformNode, event: ObjectEvent) -> Result<()> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); let iface_ref = self.conn.object_server() .interface::<_, ObjectEventsInterface>(path) @@ -135,10 +120,7 @@ impl<'a> Bus<'a> { block_on(iface.emit(event, iface_ref.signal_context())) } - pub fn emit_object_events(&self, target: &T, events: Vec) -> Result<()> - where - T: Accessible, - { + pub fn emit_object_events(&self, target: &ResolvedPlatformNode, events: Vec) -> Result<()> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); let iface_ref = self.conn.object_server() .interface::<_, ObjectEventsInterface>(path) @@ -151,13 +133,10 @@ impl<'a> Bus<'a> { }) } - pub fn emit_window_event(&self, target: &T, event: WindowEvent) -> Result<()> - where - T: Accessible, - { + pub fn emit_window_event(&self, target: &ResolvedPlatformNode, event: WindowEvent) -> Result<()> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); let iface_ref = self.conn.object_server() - .interface::<_, WindowEventsInterface>(path) + .interface::<_, WindowEventsInterface>(path) .unwrap(); let iface = iface_ref.get(); block_on(iface.emit(event, iface_ref.signal_context())) diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 378c313ac..ef3cb0425 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -3,67 +3,44 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. +use crate::{PlatformNode, PlatformRootNode}; use crate::atspi::{ interfaces::{Interface, Interfaces}, - ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role, StateSet, + ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role, State, StateSet, }; use std::convert::TryInto; -use zbus::names::OwnedUniqueName; - -pub trait Accessible: Clone + Send + Sync + 'static { - fn name(&self) -> String; - - fn description(&self) -> String; - - fn parent(&self) -> Option; - - fn child_count(&self) -> usize; - - fn locale(&self) -> String; - - fn id(&self) -> ObjectId; - - fn child_at_index(&self, index: usize) -> Option; - - fn children(&self) -> Vec; - - fn index_in_parent(&self) -> Option; - - fn role(&self) -> Role; - - fn state(&self) -> StateSet; - - fn interfaces(&self) -> Interfaces; -} +use zbus::{fdo, names::OwnedUniqueName}; pub struct AccessibleInterface { bus_name: OwnedUniqueName, - object: T, + node: T, } impl AccessibleInterface { - pub fn new(bus_name: OwnedUniqueName, object: T) -> Self { - Self { bus_name, object } + pub fn new(bus_name: OwnedUniqueName, node: T) -> Self { + Self { bus_name, node } } } const INTERFACES: &[&'static str] = &["org.a11y.atspi.Accessible", "org.a11y.atspi.Application"]; #[dbus_interface(name = "org.a11y.atspi.Accessible")] -impl AccessibleInterface { +impl AccessibleInterface { #[dbus_interface(property)] fn name(&self) -> String { - self.object.name() + self.node.resolve(|node| node.name()) + .unwrap_or(String::new()) } #[dbus_interface(property)] fn description(&self) -> String { - self.object.description() + self.node.resolve(|node| node.description()) + .unwrap_or(String::new()) } #[dbus_interface(property)] fn parent(&self) -> OwnedObjectAddress { - match self.object.parent() { + match self.node.resolve(|node| node.parent()).ok().flatten() { Some(ObjectRef::Managed(id)) => { ObjectAddress::accessible(self.bus_name.as_ref(), id).into() } @@ -74,73 +51,139 @@ impl AccessibleInterface { #[dbus_interface(property)] fn child_count(&self) -> i32 { - self.object.child_count().try_into().unwrap_or(0) + self.node.resolve(|node| node.child_count()) + .map_or(0, |count| count.try_into().unwrap_or(0)) } #[dbus_interface(property)] fn locale(&self) -> String { - self.object.locale() + self.node.resolve(|node| node.locale()) + .unwrap_or(String::new()) } #[dbus_interface(property)] fn accessible_id(&self) -> ObjectId { - self.object.id() - } - - fn get_child_at_index(&self, index: i32) -> (OwnedObjectAddress,) { - ( - match index - .try_into() - .ok() - .map(|index| self.object.child_at_index(index)) - .flatten() - { - Some(ObjectRef::Managed(id)) => { - ObjectAddress::accessible(self.bus_name.as_ref(), id).into() - } - Some(ObjectRef::Unmanaged(address)) => address, - None => ObjectAddress::null(self.bus_name.as_ref()).into(), - }, - ) - } - - fn get_children(&self) -> Vec { - self.object - .children() - .into_iter() - .map(|child| match child { - ObjectRef::Managed(id) => { - ObjectAddress::accessible(self.bus_name.as_ref(), id).into() + self.node.resolve(|node| node.id()) + .unwrap_or(unsafe { ObjectId::from_str_unchecked("") }) + } + + fn get_child_at_index(&self, index: i32) -> fdo::Result { + let index = index.try_into().map_err(|_| fdo::Error::InvalidArgs("Index can't be negative.".into()))?; + self.node.resolve(|node| match node.child_at_index(index) { + Some(ObjectRef::Managed(id)) => + ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), + Some(ObjectRef::Unmanaged(address)) => address, + _ => ObjectAddress::null(self.bus_name.as_ref()).into() + }) + } + + fn get_children(&self) -> fdo::Result> { + self.node.resolve(|node| { + node.children() + .into_iter() + .map(|child| match child { + ObjectRef::Managed(id) => + ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), + ObjectRef::Unmanaged(address) => address, + }).collect() + }) + } + + fn get_index_in_parent(&self) -> fdo::Result { + let index = self.node.resolve(|node| node.index_in_parent())?; + if let Some(index) = index { + index.try_into().map_err(|_| fdo::Error::Failed("Index is too big.".into())) + } else { + Ok(-1) + } + } + + fn get_role(&self) -> fdo::Result { + self.node.resolve(|node| node.role()) + } + + fn get_state(&self) -> fdo::Result { + self.node.resolve(|node| node.state()) + } + + fn get_interfaces(&self) -> fdo::Result> { + self.node.resolve(|node| { + let mut interfaces = Vec::with_capacity(INTERFACES.len()); + for interface in node.interfaces().iter() { + if interface > Interface::Application { + break; } - ObjectRef::Unmanaged(address) => address, - }) - .collect() + interfaces.push(INTERFACES[(interface as u8).trailing_zeros() as usize]); + } + interfaces + }) + } +} + +#[dbus_interface(name = "org.a11y.atspi.Accessible")] +impl AccessibleInterface { + #[dbus_interface(property)] + fn name(&self) -> String { + self.node.state.upgrade() + .map(|state| state.read().name.clone()) + .unwrap_or(String::new()) + } + + #[dbus_interface(property)] + fn description(&self) -> &str { + "" + } + + #[dbus_interface(property)] + fn parent(&self) -> OwnedObjectAddress { + self.node.state.upgrade() + .and_then(|state| state.read().desktop_address.clone()) + .unwrap_or_else(|| ObjectAddress::null(self.bus_name.as_ref()).into()) + } + + #[dbus_interface(property)] + fn child_count(&self) -> i32 { + 1 + } + + #[dbus_interface(property)] + fn locale(&self) -> String { + String::new() + } + + #[dbus_interface(property)] + fn accessible_id(&self) -> ObjectId { + ObjectId::root() + } + + fn get_child_at_index(&self, index: i32) -> fdo::Result { + if index != 0 { + return Ok(ObjectAddress::null(self.bus_name.as_ref()).into()); + } + self.node.tree.upgrade().map(|tree| ObjectAddress::accessible(self.bus_name.as_ref(), tree.read().root().id().into()).into()) + .ok_or(fdo::Error::UnknownObject("".into())) + } + + fn get_children(&self) -> fdo::Result> { + self.node.tree.upgrade().map(|tree| vec![ObjectAddress::accessible(self.bus_name.as_ref(), tree.read().root().id().into()).into()]) + .ok_or(fdo::Error::UnknownObject("".into())) } fn get_index_in_parent(&self) -> i32 { - self.object - .index_in_parent() - .map(|index| index.try_into().ok()) - .flatten() - .unwrap_or(-1) + -1 } fn get_role(&self) -> Role { - self.object.role() + Role::Application } fn get_state(&self) -> StateSet { - self.object.state() + let mut state = StateSet::empty(); + state.insert(State::Showing | State::Visible); + state } fn get_interfaces(&self) -> Vec<&'static str> { - let mut interfaces = Vec::with_capacity(INTERFACES.len()); - for interface in self.object.interfaces().iter() { - if interface > Interface::Application { - break; - } - interfaces.push(INTERFACES[(interface as u8).trailing_zeros() as usize]); - } - interfaces + vec![INTERFACES[0], INTERFACES[1]] } } diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/linux/src/atspi/interfaces/application.rs index d8c771115..eba1d9f17 100644 --- a/platforms/linux/src/atspi/interfaces/application.rs +++ b/platforms/linux/src/atspi/interfaces/application.rs @@ -3,160 +3,25 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{ - interfaces::{Accessible, Interface, Interfaces}, - ObjectId, ObjectRef, OwnedObjectAddress, Role, StateSet, -}; -use parking_lot::RwLock; -use std::sync::Arc; +use crate::PlatformRootNode; +use zbus::fdo; -pub trait Application: Accessible { - fn name(&self) -> String; - - fn child_count(&self) -> usize; - - fn child_at_index(&self, index: usize) -> Option; - - fn children(&self) -> Vec; - - fn toolkit_name(&self) -> String; - - fn toolkit_version(&self) -> String; - - fn id(&self) -> Option; - - fn set_id(&mut self, id: i32); - - fn locale(&self, lctype: u32) -> String; - - fn desktop(&self) -> Option; - - fn set_desktop(&mut self, address: OwnedObjectAddress); - - fn register_event_listener(&mut self, event: String); - - fn deregister_event_listener(&mut self, event: String); -} - -impl Accessible for T { - fn name(&self) -> String { - Application::name(self) - } - - fn description(&self) -> String { - String::new() - } - - fn parent(&self) -> Option { - self.desktop().map(|desktop| desktop.into()) - } - - fn child_count(&self) -> usize { - Application::child_count(self) - } - - fn locale(&self) -> String { - String::new() - } - - fn id(&self) -> ObjectId<'static> { - ObjectId::root() - } - - fn child_at_index(&self, index: usize) -> Option { - Application::child_at_index(self, index) - } - - fn children(&self) -> Vec { - Application::children(self) - } - - fn index_in_parent(&self) -> Option { - None - } - - fn role(&self) -> Role { - Role::Application - } - - fn state(&self) -> StateSet { - StateSet::empty() - } - - fn interfaces(&self) -> Interfaces { - Interface::Accessible | Interface::Application - } -} - -#[derive(Clone, Debug)] -pub struct ApplicationInterfaceWrapper(pub Arc>); - -impl Application for ApplicationInterfaceWrapper { - fn name(&self) -> String { - Application::name(&*self.0.read()) - } - - fn child_count(&self) -> usize { - Application::child_count(&*self.0.read()) - } - - fn child_at_index(&self, index: usize) -> Option { - Application::child_at_index(&*self.0.read(), index) - } - - fn children(&self) -> Vec { - Application::children(&*self.0.read()) - } - - fn toolkit_name(&self) -> String { - self.0.read().toolkit_name() - } - - fn toolkit_version(&self) -> String { - self.0.read().toolkit_version() - } - - fn id(&self) -> Option { - Application::id(&*self.0.read()) - } - - fn set_id(&mut self, id: i32) { - self.0.write().set_id(id); - } - - fn locale(&self, lctype: u32) -> String { - Application::locale(&*self.0.read(), lctype) - } - - fn desktop(&self) -> Option { - self.0.write().desktop() - } - - fn set_desktop(&mut self, address: OwnedObjectAddress) { - self.0.write().set_desktop(address) - } - - fn register_event_listener(&mut self, event: String) { - self.0.write().register_event_listener(event) - } - - fn deregister_event_listener(&mut self, event: String) { - self.0.write().deregister_event_listener(event) - } -} - -pub struct ApplicationInterface(pub T); +pub struct ApplicationInterface(pub(crate) PlatformRootNode); #[dbus_interface(name = "org.a11y.atspi.Application")] -impl ApplicationInterface { +impl ApplicationInterface { #[dbus_interface(property)] fn toolkit_name(&self) -> String { - self.0.toolkit_name() + self.0.state.upgrade() + .map(|state| state.read().toolkit_name.clone()) + .unwrap_or(String::new()) } #[dbus_interface(property)] fn version(&self) -> String { - self.0.toolkit_version() + self.0.state.upgrade() + .map(|state| state.read().toolkit_version.clone()) + .unwrap_or(String::new()) } #[dbus_interface(property)] @@ -166,16 +31,20 @@ impl ApplicationInterface { #[dbus_interface(property)] fn id(&self) -> i32 { - Application::id(&self.0).unwrap_or(-1) + self.0.state.upgrade() + .and_then(|state| state.read().id) + .unwrap_or(-1) } #[dbus_interface(property)] - fn set_id(&mut self, id: i32) { - self.0.set_id(id) + fn set_id(&mut self, id: i32) -> fdo::Result<()> { + self.0.state.upgrade() + .map(|state| state.write().id = Some(id)) + .ok_or(fdo::Error::UnknownObject("".into())) } - fn get_locale(&self, lctype: u32) -> String { - Application::locale(&self.0, lctype) + fn get_locale(&self, lctype: u32) -> fdo::Result { + Ok(String::new()) } fn register_event_listener(&self, _event: String) {} diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index 87e5b46d3..0275258ea 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -3,7 +3,8 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{interfaces::Accessible, State}; +use crate::atspi::State; +use crate::PlatformNode; use std::{collections::HashMap, convert::AsRef}; use zbus::{dbus_interface, Result, SignalContext}; use zvariant::Value; @@ -97,34 +98,34 @@ pub enum WindowEvent { Destroyed, } -pub struct WindowEventsInterface(pub T); +pub struct WindowEventsInterface(pub(crate) PlatformNode); -impl WindowEventsInterface { +impl WindowEventsInterface { pub async fn emit(&self, event: WindowEvent, ctxt: &SignalContext<'_>) -> Result<()> { - let name = self.0.name().into(); + let name = self.0.resolve(|node| node.name().into())?; let properties = HashMap::new(); match event { WindowEvent::Activated => { - WindowEventsInterface::::activate(ctxt, "", 0, 0, name, properties).await + WindowEventsInterface::activate(ctxt, "", 0, 0, name, properties).await } WindowEvent::Closed => { - WindowEventsInterface::::close(ctxt, "", 0, 0, name, properties).await + WindowEventsInterface::close(ctxt, "", 0, 0, name, properties).await } WindowEvent::Created => { - WindowEventsInterface::::create(ctxt, "", 0, 0, name, properties).await + WindowEventsInterface::create(ctxt, "", 0, 0, name, properties).await } WindowEvent::Deactivated => { - WindowEventsInterface::::deactivate(ctxt, "", 0, 0, name, properties).await + WindowEventsInterface::deactivate(ctxt, "", 0, 0, name, properties).await } WindowEvent::Destroyed => { - WindowEventsInterface::::destroy(ctxt, "", 0, 0, name, properties).await + WindowEventsInterface::destroy(ctxt, "", 0, 0, name, properties).await } } } } #[dbus_interface(name = "org.a11y.atspi.Event.Window")] -impl WindowEventsInterface { +impl WindowEventsInterface { #[dbus_interface(signal)] async fn activate( ctxt: &SignalContext<'_>, diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs index 886bcd8ef..d925d0116 100644 --- a/platforms/linux/src/lib.rs +++ b/platforms/linux/src/lib.rs @@ -13,3 +13,4 @@ mod atspi; mod node; pub use adapter::Adapter; +pub(crate) use node::{PlatformNode, PlatformRootNode, ResolvedPlatformNode}; diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index f7392d87d..ddabef187 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -4,78 +4,68 @@ // the LICENSE-MIT file), at your option. use crate::atspi::{ - interfaces::{Accessible, Application, Interface, Interfaces}, + interfaces::{Interface, Interfaces}, ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole, State, StateSet, }; use accesskit::{AriaCurrent, CheckedState, InvalidState, Orientation, Role}; use accesskit_consumer::{Node, Tree, WeakNode}; -use std::sync::Arc; +use parking_lot::RwLock; +use std::sync::Weak; +use zbus::fdo; -#[derive(Clone)] -pub struct PlatformNode(WeakNode); +pub(crate) struct ResolvedPlatformNode<'a> { + node: Node<'a>, +} -impl PlatformNode { - pub(crate) fn new(node: &Node) -> Self { - Self(node.downgrade()) +impl ResolvedPlatformNode<'_> { + pub(crate) fn new(node: Node) -> ResolvedPlatformNode { + ResolvedPlatformNode { node } } -} -impl Accessible for PlatformNode { - fn name(&self) -> String { - self.0 - .map(|node| node.name().map(|name| name.to_string())) - .flatten() + pub(crate) fn downgrade(&self) -> PlatformNode { + PlatformNode::new(&self.node) + } + + pub(crate) fn name(&self) -> String { + self.node.name().map(|name| name.to_string()) .unwrap_or(String::new()) } - fn description(&self) -> String { + pub(crate) fn description(&self) -> String { String::new() } - fn parent(&self) -> Option { - Some( - self.0 - .map(|node| node.parent().map(|parent| parent.id().into())) - .flatten() - .unwrap_or(ObjectId::root().into()), - ) + pub(crate) fn parent(&self) -> Option { + self.node.parent().map(|parent| parent.id().into()) } - fn child_count(&self) -> usize { - self.0.map(|node| node.children().count()).unwrap_or(0) + pub(crate) fn child_count(&self) -> usize { + self.node.children().count() } - fn locale(&self) -> String { + pub(crate) fn locale(&self) -> String { String::new() } - fn id(&self) -> ObjectId<'static> { - self.0.map(|node| node.id().into()).unwrap() + pub(crate) fn id(&self) -> ObjectId<'static> { + self.node.id().into() } - fn child_at_index(&self, index: usize) -> Option { - self.0 - .map(|node| node.children().nth(index).map(|child| child.id().into())) - .flatten() + pub(crate) fn child_at_index(&self, index: usize) -> Option { + self.node.children().nth(index).map(|child| child.id().into()) } - fn children(&self) -> Vec { - self.0 - .map(|node| node.children().map(|child| child.id().into()).collect()) - .unwrap_or(Vec::new()) + pub(crate) fn children(&self) -> Vec { + self.node.children().map(|child| child.id().into()).collect() } - fn index_in_parent(&self) -> Option { - self.0 - .map(|node| node.parent_and_index().map(|(_, index)| index)) - .flatten() + pub(crate) fn index_in_parent(&self) -> Option { + self.node.parent_and_index().map(|(_, index)| index) } - fn role(&self) -> AtspiRole { - self.0 - .map(|node| { - match node.role() { - Role::Alert => AtspiRole::Notification, + pub(crate) fn role(&self) -> AtspiRole { + match self.node.role() { + Role::Alert => AtspiRole::Notification, Role::AlertDialog => AtspiRole::Alert, Role::Comment | Role::Suggestion => AtspiRole::Section, // TODO: See how to represent ARIA role="application" @@ -165,7 +155,7 @@ impl Accessible for PlatformNode { Role::Heading => AtspiRole::Heading, Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame, Role::Image => { - if node.unignored_children().next().is_some() { + if self.node.unignored_children().next().is_some() { AtspiRole::ImageMap } else { AtspiRole::Image @@ -198,7 +188,7 @@ impl Accessible for PlatformNode { // - The list marker itself is ignored but the descendants are not // - Or the list marker contains images Role::ListMarker => { - if node.unignored_children().next().is_none() { + if self.node.unignored_children().next().is_none() { AtspiRole::Static } else { AtspiRole::Panel @@ -223,7 +213,7 @@ impl Accessible for PlatformNode { Role::PdfRoot => AtspiRole::DocumentFrame, Role::PluginObject => AtspiRole::Embedded, Role::PopupButton => { - if node + if self.node .data() .html_tag .as_ref() @@ -278,7 +268,7 @@ impl Accessible for PlatformNode { Role::Term => AtspiRole::DescriptionTerm, Role::TitleBar => AtspiRole::TitleBar, Role::TextField | Role::SearchBox => { - if node.data().protected { + if self.node.data().protected { AtspiRole::PasswordText } else { AtspiRole::Entry @@ -306,234 +296,194 @@ impl Accessible for PlatformNode { Role::Presentation | Role::Unknown => AtspiRole::Unknown, Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject, } - }) - .unwrap_or(AtspiRole::Invalid) } - fn state(&self) -> StateSet { + pub(crate) fn state(&self) -> StateSet { let platform_role = self.role(); - self.0 - .map(|node| { - let data = node.data(); - let mut state = StateSet::empty(); - if let Ok(current_active) = crate::adapter::CURRENT_ACTIVE_WINDOW.lock() { - if node.role() == Role::Window && *current_active == Some(data.id) { - state.insert(State::Active); - } - } - if let Some(expanded) = data.expanded { - state.insert(State::Expandable); - if expanded { - state.insert(State::Expanded); - } - } - if data.default { - state.insert(State::IsDefault); - } - if data.editable && !data.read_only { - state.insert(State::Editable); - } - // TODO: Focus and selection. - if data.focusable { - state.insert(State::Focusable); - } - match data.orientation { - Some(Orientation::Horizontal) => state.insert(State::Horizontal), - Some(Orientation::Vertical) => state.insert(State::Vertical), - _ => {} - } - if !node.is_invisible_or_ignored() { - state.insert(State::Visible); - // if (!delegate_->IsOffscreen() && !is_minimized) - state.insert(State::Showing); - } - if data.multiselectable { - state.insert(State::Multiselectable); - } - if data.required { - state.insert(State::Required); - } - if data.visited { - state.insert(State::Visited); - } - if let Some(InvalidState::True | InvalidState::Other(_)) = data.invalid_state { - state.insert(State::InvalidEntry); - } - match data.aria_current { - None | Some(AriaCurrent::False) => {} - _ => state.insert(State::Active), - } - if platform_role != AtspiRole::ToggleButton && data.checked_state.is_some() { - state.insert(State::Checkable); - } - if data.has_popup.is_some() { - state.insert(State::HasPopup); - } - if data.busy { - state.insert(State::Busy); - } - if data.modal { - state.insert(State::Modal); - } - if let Some(selected) = data.selected { - if !node.is_disabled() { - state.insert(State::Selectable); - } - if selected { - state.insert(State::Selected); - } - } - if node.is_text_field() { - state.insert(State::SelectableText); - match node.data().multiline { - true => state.insert(State::MultiLine), - false => state.insert(State::SingleLine) - } - } - - // Special case for indeterminate progressbar. - if node.role() == Role::ProgressIndicator && data.numeric_value.is_none() { - state.insert(State::Indeterminate); - } + let data = self.node.data(); + let mut state = StateSet::empty(); + if let Ok(current_active) = crate::adapter::CURRENT_ACTIVE_WINDOW.lock() { + if self.node.role() == Role::Window && *current_active == Some(data.id) { + state.insert(State::Active); + } + } + if let Some(expanded) = data.expanded { + state.insert(State::Expandable); + if expanded { + state.insert(State::Expanded); + } + } + if data.default { + state.insert(State::IsDefault); + } + if data.editable && !data.read_only { + state.insert(State::Editable); + } + // TODO: Focus and selection. + if data.focusable { + state.insert(State::Focusable); + } + match data.orientation { + Some(Orientation::Horizontal) => state.insert(State::Horizontal), + Some(Orientation::Vertical) => state.insert(State::Vertical), + _ => {} + } + if !self.node.is_invisible_or_ignored() { + state.insert(State::Visible); + // if (!delegate_->IsOffscreen() && !is_minimized) + state.insert(State::Showing); + } + if data.multiselectable { + state.insert(State::Multiselectable); + } + if data.required { + state.insert(State::Required); + } + if data.visited { + state.insert(State::Visited); + } + if let Some(InvalidState::True | InvalidState::Other(_)) = data.invalid_state { + state.insert(State::InvalidEntry); + } + match data.aria_current { + None | Some(AriaCurrent::False) => {} + _ => state.insert(State::Active), + } + if platform_role != AtspiRole::ToggleButton && data.checked_state.is_some() { + state.insert(State::Checkable); + } + if data.has_popup.is_some() { + state.insert(State::HasPopup); + } + if data.busy { + state.insert(State::Busy); + } + if data.modal { + state.insert(State::Modal); + } + if let Some(selected) = data.selected { + if !self.node.is_disabled() { + state.insert(State::Selectable); + } + if selected { + state.insert(State::Selected); + } + } + if self.node.is_text_field() { + state.insert(State::SelectableText); + match self.node.data().multiline { + true => state.insert(State::MultiLine), + false => state.insert(State::SingleLine) + } + } - let has_suggestion = data - .auto_complete - .as_ref() - .map_or(false, |a| !a.as_ref().is_empty()); - if has_suggestion || data.autofill_available { - state.insert(State::SupportsAutocompletion); - } + // Special case for indeterminate progressbar. + if self.node.role() == Role::ProgressIndicator && data.numeric_value.is_none() { + state.insert(State::Indeterminate); + } - // Checked state - match data.checked_state { - Some(CheckedState::Mixed) => state.insert(State::Indeterminate), - Some(CheckedState::True) => { - if platform_role == AtspiRole::ToggleButton { - state.insert(State::Pressed); - } else { - state.insert(State::Checked); - } - } - _ => {} - } + let has_suggestion = data + .auto_complete + .as_ref() + .map_or(false, |a| !a.as_ref().is_empty()); + if has_suggestion || data.autofill_available { + state.insert(State::SupportsAutocompletion); + } - if node.is_read_only_supported() && node.is_read_only_or_disabled() { - state.insert(State::ReadOnly); + // Checked state + match data.checked_state { + Some(CheckedState::Mixed) => state.insert(State::Indeterminate), + Some(CheckedState::True) => { + if platform_role == AtspiRole::ToggleButton { + state.insert(State::Pressed); } else { - state.insert(State::Enabled); - state.insert(State::Sensitive); - } - - if node.is_focused() { - state.insert(State::Focused); - } - - // It is insufficient to compare with g_current_activedescendant due to both - // timing and event ordering for objects which implement AtkSelection and also - // have an active descendant. For instance, if we check the state set of a - // selectable child, it will only have ATK_STATE_FOCUSED if we've processed - // the activedescendant change. - // if (GetActiveDescendantOfCurrentFocused() == atk_object) - // state.insert(State::Focused); - state - }) - .unwrap_or(State::Defunct.into()) - } - - fn interfaces(&self) -> Interfaces { - self.0 - .map(|node| { - let mut interfaces: Interfaces = Interface::Accessible | Interface::ObjectEvents; - if node.role() == Role::Window { - interfaces.insert(Interface::WindowEvents); + state.insert(State::Checked); } - if node.data().focusable { - interfaces.insert(Interface::FocusEvents); - } - interfaces - }) - .unwrap() - } -} - -#[derive(Clone)] -pub struct RootPlatformNode { - app_name: String, - app_id: Option, - desktop_address: Option, - tree: Arc, - toolkit_name: String, - toolkit_version: String, -} - -impl RootPlatformNode { - pub fn new( - app_name: String, - toolkit_name: String, - toolkit_version: String, - tree: Arc, - ) -> Self { - Self { - app_name, - app_id: None, - desktop_address: None, - tree, - toolkit_name, - toolkit_version, + } + _ => {} } - } -} - -impl Application for RootPlatformNode { - fn name(&self) -> String { - self.app_name.clone() - } - - fn child_count(&self) -> usize { - 1 - } - fn child_at_index(&self, index: usize) -> Option { - if index == 0 { - Some(self.tree.read().root().id().into()) + if self.node.is_read_only_supported() && self.node.is_read_only_or_disabled() { + state.insert(State::ReadOnly); } else { - None + state.insert(State::Enabled); + state.insert(State::Sensitive); } - } - fn children(&self) -> Vec { - vec![self.tree.read().root().id().into()] - } + if self.node.is_focused() { + state.insert(State::Focused); + } - fn toolkit_name(&self) -> String { - self.toolkit_name.clone() + // It is insufficient to compare with g_current_activedescendant due to both + // timing and event ordering for objects which implement AtkSelection and also + // have an active descendant. For instance, if we check the state set of a + // selectable child, it will only have ATK_STATE_FOCUSED if we've processed + // the activedescendant change. + // if (GetActiveDescendantOfCurrentFocused() == atk_object) + // state.insert(State::Focused); + state } - fn toolkit_version(&self) -> String { - self.toolkit_version.clone() + pub(crate) fn interfaces(&self) -> Interfaces { + let mut interfaces: Interfaces = Interface::Accessible | Interface::ObjectEvents; + if self.node.role() == Role::Window { + interfaces.insert(Interface::WindowEvents); + } + if self.node.data().focusable { + interfaces.insert(Interface::FocusEvents); + } + interfaces } +} - fn id(&self) -> Option { - self.app_id - } +#[derive(Clone)] +pub(crate) struct PlatformNode(WeakNode); - fn set_id(&mut self, id: i32) { - self.app_id = Some(id); +impl PlatformNode { + pub(crate) fn new(node: &Node) -> Self { + Self(node.downgrade()) } - fn locale(&self, lctype: u32) -> String { - String::new() + pub(crate) fn resolve(&self, f: F) -> fdo::Result + where + for<'a> F: FnOnce(ResolvedPlatformNode<'a>) -> T, + { + self.0 + .map(|node| f(ResolvedPlatformNode::new(node))) + .ok_or(fdo::Error::UnknownObject( + "".into(), + )) } +} - fn desktop(&self) -> Option { - self.desktop_address.clone() - } +pub(crate) struct AppState { + pub name: String, + pub toolkit_name: String, + pub toolkit_version: String, + pub id: Option, + pub desktop_address: Option, +} - fn set_desktop(&mut self, address: OwnedObjectAddress) { - self.desktop_address = Some(address); +impl AppState { + pub(crate) fn new(name: String, toolkit_name: String, toolkit_version: String) -> Self { + Self { + name, + toolkit_name, + toolkit_version, + id: None, + desktop_address: None, + } } +} - fn register_event_listener(&mut self, _: String) {} +#[derive(Clone)] +pub(crate) struct PlatformRootNode { + pub state: Weak>, + pub tree: Weak +} - fn deregister_event_listener(&mut self, _: String) {} +impl PlatformRootNode { + pub(crate) fn new(state: Weak>, tree: Weak) -> Self { + Self { state, tree } + } } From 1be5ddacf91f5c08fc9bb3d1365c49c8b5a511fa Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 14 Aug 2022 23:10:52 +0200 Subject: [PATCH 19/69] cargo fmt --- platforms/linux/examples/hello_world.rs | 4 +- platforms/linux/src/adapter.rs | 13 +- platforms/linux/src/atspi/bus.rs | 89 ++-- .../linux/src/atspi/interfaces/accessible.rs | 67 ++- .../linux/src/atspi/interfaces/application.rs | 16 +- platforms/linux/src/node.rs | 480 +++++++++--------- 6 files changed, 367 insertions(+), 302 deletions(-) diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index b5ddf458a..d5904872e 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -3,9 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{ - ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate -}; +use accesskit::{ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; use accesskit_linux::Adapter; use std::num::NonZeroU128; use winit::{ diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index f907bca93..58bad3e8f 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -53,9 +53,16 @@ impl<'a> Adapter<'a> { atspi_bus.register_node(node); } } - let app_state = Arc::new(RwLock::new(AppState::new(app_name, toolkit_name, toolkit_version))); - atspi_bus.register_root_node(PlatformRootNode::new(Arc::downgrade(&app_state), Arc::downgrade(&tree))); - Some(Self { + let app_state = Arc::new(RwLock::new(AppState::new( + app_name, + toolkit_name, + toolkit_version, + ))); + atspi_bus.register_root_node(PlatformRootNode::new( + Arc::downgrade(&app_state), + Arc::downgrade(&tree), + )); + Some(Self { atspi_bus, app_state, tree, diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 0ad05f5e3..330ec6421 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -7,14 +7,11 @@ use crate::atspi::{ interfaces::*, object_address::*, proxies::{BusProxy, SocketProxy}, - ObjectId + ObjectId, }; use crate::{PlatformNode, PlatformRootNode, ResolvedPlatformNode}; use async_io::block_on; -use std::{ - convert::TryInto, - env::var, -}; +use std::{convert::TryInto, env::var}; use x11rb::{ connection::Connection as _, protocol::xproto::{AtomEnum, ConnectionExt}, @@ -40,7 +37,10 @@ impl<'a> Bus<'a> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, node.id().as_str()); if self.conn.object_server().at( path.clone(), - AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), node.downgrade()), + AccessibleInterface::new( + self.conn.unique_name().unwrap().to_owned(), + node.downgrade(), + ), )? { let interfaces = node.interfaces(); if interfaces.contains(Interface::FocusEvents) { @@ -61,23 +61,22 @@ impl<'a> Bus<'a> { pub fn register_root_node(&mut self, node: PlatformRootNode) -> Result { println!("Registering on {:?}", self.conn.unique_name().unwrap()); - let path = format!( - "{}{}", - ACCESSIBLE_PATH_PREFIX, - ObjectId::root().as_str() - ); - let registered = self.conn.object_server().at( - path.clone(), - ApplicationInterface(node.clone()), - )? && self.conn.object_server().at( - path, - AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), node.clone()), - )?; + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, ObjectId::root().as_str()); + let registered = self + .conn + .object_server() + .at(path.clone(), ApplicationInterface(node.clone()))? + && self.conn.object_server().at( + path, + AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), node.clone()), + )?; if registered { let desktop_address = self.socket_proxy.embed(ObjectAddress::root( self.conn.unique_name().unwrap().as_ref(), ))?; - node.state.upgrade().map(|state| state.write().desktop_address = Some(desktop_address)); + node.state + .upgrade() + .map(|state| state.write().desktop_address = Some(desktop_address)); Ok(true) } else { Ok(false) @@ -85,15 +84,11 @@ impl<'a> Bus<'a> { } fn register_focus_events(&mut self, path: &str) -> Result { - self.conn - .object_server() - .at(path, FocusEventsInterface {}) + self.conn.object_server().at(path, FocusEventsInterface {}) } fn register_object_events(&mut self, path: &str) -> Result { - self.conn - .object_server() - .at(path, ObjectEventsInterface {}) + self.conn.object_server().at(path, ObjectEventsInterface {}) } fn register_window_events(&mut self, path: &str, node: PlatformNode) -> Result { @@ -104,38 +99,61 @@ impl<'a> Bus<'a> { pub fn emit_focus_event(&self, target: &ResolvedPlatformNode) -> Result<()> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - let iface_ref = self.conn.object_server() + let iface_ref = self + .conn + .object_server() .interface::<_, FocusEventsInterface>(path) .unwrap(); let iface = iface_ref.get(); block_on(iface.focused(iface_ref.signal_context())) } - pub fn emit_object_event(&self, target: &ResolvedPlatformNode, event: ObjectEvent) -> Result<()> { + pub fn emit_object_event( + &self, + target: &ResolvedPlatformNode, + event: ObjectEvent, + ) -> Result<()> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - let iface_ref = self.conn.object_server() + let iface_ref = self + .conn + .object_server() .interface::<_, ObjectEventsInterface>(path) .unwrap(); let iface = iface_ref.get(); block_on(iface.emit(event, iface_ref.signal_context())) } - pub fn emit_object_events(&self, target: &ResolvedPlatformNode, events: Vec) -> Result<()> { + pub fn emit_object_events( + &self, + target: &ResolvedPlatformNode, + events: Vec, + ) -> Result<()> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - let iface_ref = self.conn.object_server() + let iface_ref = self + .conn + .object_server() .interface::<_, ObjectEventsInterface>(path) .unwrap(); block_on(async { for event in events { - iface_ref.get().emit(event, iface_ref.signal_context()).await?; + iface_ref + .get() + .emit(event, iface_ref.signal_context()) + .await?; } Ok(()) }) } - pub fn emit_window_event(&self, target: &ResolvedPlatformNode, event: WindowEvent) -> Result<()> { + pub fn emit_window_event( + &self, + target: &ResolvedPlatformNode, + event: WindowEvent, + ) -> Result<()> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - let iface_ref = self.conn.object_server() + let iface_ref = self + .conn + .object_server() .interface::<_, WindowEventsInterface>(path) .unwrap(); let iface = iface_ref.get(); @@ -203,8 +221,5 @@ fn a11y_bus() -> Option { address = a11y_bus_address_from_dbus(); } let address: Address = address?.as_str().try_into().ok()?; - ConnectionBuilder::address(address) - .ok()? - .build() - .ok() + ConnectionBuilder::address(address).ok()?.build().ok() } diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index ef3cb0425..78fbcad34 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -3,11 +3,11 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::{PlatformNode, PlatformRootNode}; use crate::atspi::{ interfaces::{Interface, Interfaces}, ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role, State, StateSet, }; +use crate::{PlatformNode, PlatformRootNode}; use std::convert::TryInto; use zbus::{fdo, names::OwnedUniqueName}; @@ -28,13 +28,15 @@ const INTERFACES: &[&'static str] = &["org.a11y.atspi.Accessible", "org.a11y.ats impl AccessibleInterface { #[dbus_interface(property)] fn name(&self) -> String { - self.node.resolve(|node| node.name()) + self.node + .resolve(|node| node.name()) .unwrap_or(String::new()) } #[dbus_interface(property)] fn description(&self) -> String { - self.node.resolve(|node| node.description()) + self.node + .resolve(|node| node.description()) .unwrap_or(String::new()) } @@ -51,29 +53,35 @@ impl AccessibleInterface { #[dbus_interface(property)] fn child_count(&self) -> i32 { - self.node.resolve(|node| node.child_count()) + self.node + .resolve(|node| node.child_count()) .map_or(0, |count| count.try_into().unwrap_or(0)) } #[dbus_interface(property)] fn locale(&self) -> String { - self.node.resolve(|node| node.locale()) + self.node + .resolve(|node| node.locale()) .unwrap_or(String::new()) } #[dbus_interface(property)] fn accessible_id(&self) -> ObjectId { - self.node.resolve(|node| node.id()) + self.node + .resolve(|node| node.id()) .unwrap_or(unsafe { ObjectId::from_str_unchecked("") }) } fn get_child_at_index(&self, index: i32) -> fdo::Result { - let index = index.try_into().map_err(|_| fdo::Error::InvalidArgs("Index can't be negative.".into()))?; + let index = index + .try_into() + .map_err(|_| fdo::Error::InvalidArgs("Index can't be negative.".into()))?; self.node.resolve(|node| match node.child_at_index(index) { - Some(ObjectRef::Managed(id)) => - ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), + Some(ObjectRef::Managed(id)) => { + ObjectAddress::accessible(self.bus_name.as_ref(), id).into() + } Some(ObjectRef::Unmanaged(address)) => address, - _ => ObjectAddress::null(self.bus_name.as_ref()).into() + _ => ObjectAddress::null(self.bus_name.as_ref()).into(), }) } @@ -82,17 +90,21 @@ impl AccessibleInterface { node.children() .into_iter() .map(|child| match child { - ObjectRef::Managed(id) => - ObjectAddress::accessible(self.bus_name.as_ref(), id).into(), + ObjectRef::Managed(id) => { + ObjectAddress::accessible(self.bus_name.as_ref(), id).into() + } ObjectRef::Unmanaged(address) => address, - }).collect() + }) + .collect() }) } fn get_index_in_parent(&self) -> fdo::Result { let index = self.node.resolve(|node| node.index_in_parent())?; if let Some(index) = index { - index.try_into().map_err(|_| fdo::Error::Failed("Index is too big.".into())) + index + .try_into() + .map_err(|_| fdo::Error::Failed("Index is too big.".into())) } else { Ok(-1) } @@ -124,7 +136,9 @@ impl AccessibleInterface { impl AccessibleInterface { #[dbus_interface(property)] fn name(&self) -> String { - self.node.state.upgrade() + self.node + .state + .upgrade() .map(|state| state.read().name.clone()) .unwrap_or(String::new()) } @@ -136,7 +150,9 @@ impl AccessibleInterface { #[dbus_interface(property)] fn parent(&self) -> OwnedObjectAddress { - self.node.state.upgrade() + self.node + .state + .upgrade() .and_then(|state| state.read().desktop_address.clone()) .unwrap_or_else(|| ObjectAddress::null(self.bus_name.as_ref()).into()) } @@ -160,12 +176,27 @@ impl AccessibleInterface { if index != 0 { return Ok(ObjectAddress::null(self.bus_name.as_ref()).into()); } - self.node.tree.upgrade().map(|tree| ObjectAddress::accessible(self.bus_name.as_ref(), tree.read().root().id().into()).into()) + self.node + .tree + .upgrade() + .map(|tree| { + ObjectAddress::accessible(self.bus_name.as_ref(), tree.read().root().id().into()) + .into() + }) .ok_or(fdo::Error::UnknownObject("".into())) } fn get_children(&self) -> fdo::Result> { - self.node.tree.upgrade().map(|tree| vec![ObjectAddress::accessible(self.bus_name.as_ref(), tree.read().root().id().into()).into()]) + self.node + .tree + .upgrade() + .map(|tree| { + vec![ObjectAddress::accessible( + self.bus_name.as_ref(), + tree.read().root().id().into(), + ) + .into()] + }) .ok_or(fdo::Error::UnknownObject("".into())) } diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/linux/src/atspi/interfaces/application.rs index eba1d9f17..e60251733 100644 --- a/platforms/linux/src/atspi/interfaces/application.rs +++ b/platforms/linux/src/atspi/interfaces/application.rs @@ -12,14 +12,18 @@ pub struct ApplicationInterface(pub(crate) PlatformRootNode); impl ApplicationInterface { #[dbus_interface(property)] fn toolkit_name(&self) -> String { - self.0.state.upgrade() + self.0 + .state + .upgrade() .map(|state| state.read().toolkit_name.clone()) .unwrap_or(String::new()) } #[dbus_interface(property)] fn version(&self) -> String { - self.0.state.upgrade() + self.0 + .state + .upgrade() .map(|state| state.read().toolkit_version.clone()) .unwrap_or(String::new()) } @@ -31,14 +35,18 @@ impl ApplicationInterface { #[dbus_interface(property)] fn id(&self) -> i32 { - self.0.state.upgrade() + self.0 + .state + .upgrade() .and_then(|state| state.read().id) .unwrap_or(-1) } #[dbus_interface(property)] fn set_id(&mut self, id: i32) -> fdo::Result<()> { - self.0.state.upgrade() + self.0 + .state + .upgrade() .map(|state| state.write().id = Some(id)) .ok_or(fdo::Error::UnknownObject("".into())) } diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index ddabef187..30e3f5afd 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -27,7 +27,9 @@ impl ResolvedPlatformNode<'_> { } pub(crate) fn name(&self) -> String { - self.node.name().map(|name| name.to_string()) + self.node + .name() + .map(|name| name.to_string()) .unwrap_or(String::new()) } @@ -52,11 +54,17 @@ impl ResolvedPlatformNode<'_> { } pub(crate) fn child_at_index(&self, index: usize) -> Option { - self.node.children().nth(index).map(|child| child.id().into()) + self.node + .children() + .nth(index) + .map(|child| child.id().into()) } pub(crate) fn children(&self) -> Vec { - self.node.children().map(|child| child.id().into()).collect() + self.node + .children() + .map(|child| child.id().into()) + .collect() } pub(crate) fn index_in_parent(&self) -> Option { @@ -66,236 +74,236 @@ impl ResolvedPlatformNode<'_> { pub(crate) fn role(&self) -> AtspiRole { match self.node.role() { Role::Alert => AtspiRole::Notification, - Role::AlertDialog => AtspiRole::Alert, - Role::Comment | Role::Suggestion => AtspiRole::Section, - // TODO: See how to represent ARIA role="application" - Role::Application => AtspiRole::Embedded, - Role::Article => AtspiRole::Article, - Role::Audio => AtspiRole::Audio, - Role::Banner | Role::Header => AtspiRole::Landmark, - Role::Blockquote => AtspiRole::BlockQuote, - Role::Caret => AtspiRole::Unknown, - Role::Button => AtspiRole::PushButton, - Role::Canvas => AtspiRole::Canvas, - Role::Caption => AtspiRole::Caption, - Role::Cell => AtspiRole::TableCell, - Role::CheckBox => AtspiRole::CheckBox, - Role::Switch => AtspiRole::ToggleButton, - Role::ColorWell => AtspiRole::PushButton, - Role::Column => AtspiRole::Unknown, - Role::ColumnHeader => AtspiRole::ColumnHeader, - Role::ComboBoxGrouping | Role::ComboBoxMenuButton => AtspiRole::ComboBox, - Role::Complementary => AtspiRole::Landmark, - Role::ContentDeletion => AtspiRole::ContentDeletion, - Role::ContentInsertion => AtspiRole::ContentInsertion, - Role::ContentInfo | Role::Footer => AtspiRole::Landmark, - Role::Date | Role::DateTime => AtspiRole::DateEditor, - Role::Definition | Role::DescriptionListDetail => AtspiRole::DescriptionValue, - Role::DescriptionList => AtspiRole::DescriptionList, - Role::DescriptionListTerm => AtspiRole::DescriptionTerm, - Role::Details => AtspiRole::Panel, - Role::Dialog => AtspiRole::Dialog, - Role::Directory => AtspiRole::List, - Role::DisclosureTriangle => AtspiRole::ToggleButton, - Role::DocCover => AtspiRole::Image, - Role::DocBackLink - | Role::DocBiblioRef - | Role::DocGlossRef - | Role::DocNoteRef => AtspiRole::Link, - Role::DocBiblioEntry | Role::DocEndnote => AtspiRole::ListItem, - Role::DocNotice | Role::DocTip => AtspiRole::Comment, - Role::DocFootnote => AtspiRole::Footnote, - Role::DocPageBreak => AtspiRole::Separator, - Role::DocPageFooter => AtspiRole::Footer, - Role::DocPageHeader => AtspiRole::Header, - Role::DocAcknowledgements - | Role::DocAfterword - | Role::DocAppendix - | Role::DocBibliography - | Role::DocChapter - | Role::DocConclusion - | Role::DocCredits - | Role::DocEndnotes - | Role::DocEpilogue - | Role::DocErrata - | Role::DocForeword - | Role::DocGlossary - | Role::DocIndex - | Role::DocIntroduction - | Role::DocPageList - | Role::DocPart - | Role::DocPreface - | Role::DocPrologue - | Role::DocToc => AtspiRole::Landmark, - Role::DocAbstract - | Role::DocColophon - | Role::DocCredit - | Role::DocDedication - | Role::DocEpigraph - | Role::DocExample - | Role::DocPullquote - | Role::DocQna => AtspiRole::Section, - Role::DocSubtitle => AtspiRole::Heading, - Role::Document => AtspiRole::DocumentFrame, - Role::EmbeddedObject => AtspiRole::Embedded, - // TODO: Forms which lack an accessible name are no longer - // exposed as forms. http://crbug.com/874384. Forms which have accessible - // names should be exposed as `AtspiRole::Landmark` according to Core AAM. - Role::Form => AtspiRole::Form, - Role::Figure | Role::Feed => AtspiRole::Panel, - Role::GenericContainer - | Role::FooterAsNonLandmark - | Role::HeaderAsNonLandmark - | Role::Ruby => AtspiRole::Section, - Role::GraphicsDocument => AtspiRole::DocumentFrame, - Role::GraphicsObject => AtspiRole::Panel, - Role::GraphicsSymbol => AtspiRole::Image, - Role::Grid => AtspiRole::Table, - Role::Group => AtspiRole::Panel, - Role::Heading => AtspiRole::Heading, - Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame, - Role::Image => { - if self.node.unignored_children().next().is_some() { - AtspiRole::ImageMap - } else { - AtspiRole::Image - } - } - Role::InlineTextBox => AtspiRole::Static, - Role::InputTime => AtspiRole::DateEditor, - Role::LabelText | Role::Legend => AtspiRole::Label, - // Layout table objects are treated the same as `Role::GenericContainer`. - Role::LayoutTable => AtspiRole::Section, - Role::LayoutTableCell => AtspiRole::Section, - Role::LayoutTableRow => AtspiRole::Section, - // TODO: Having a separate accessible object for line breaks - // is inconsistent with other implementations. http://crbug.com/873144#c1. - Role::LineBreak => AtspiRole::Static, - Role::Link => AtspiRole::Link, - Role::List => AtspiRole::List, - Role::ListBox => AtspiRole::ListBox, - // TODO: Use `AtspiRole::MenuItem' inside a combo box, see how - // ax_platform_node_win.cc code does this. - Role::ListBoxOption => AtspiRole::ListItem, - Role::ListGrid => AtspiRole::Table, - Role::ListItem => AtspiRole::ListItem, - // Regular list markers only expose their alternative text, but do not - // expose their descendants, and the descendants should be ignored. This - // is because the alternative text depends on the counter style and can - // be different from the actual (visual) marker text, and hence, - // inconsistent with the descendants. We treat a list marker as non-text - // only if it still has non-ignored descendants, which happens only when => - // - The list marker itself is ignored but the descendants are not - // - Or the list marker contains images - Role::ListMarker => { - if self.node.unignored_children().next().is_none() { - AtspiRole::Static - } else { - AtspiRole::Panel - } - } - Role::Log => AtspiRole::Log, - Role::Main => AtspiRole::Landmark, - Role::Mark => AtspiRole::Static, - Role::Math => AtspiRole::Math, - Role::Marquee => AtspiRole::Marquee, - Role::Menu | Role::MenuListPopup => AtspiRole::Menu, - Role::MenuBar => AtspiRole::MenuBar, - Role::MenuItem | Role::MenuListOption => AtspiRole::MenuItem, - Role::MenuItemCheckBox => AtspiRole::CheckMenuItem, - Role::MenuItemRadio => AtspiRole::RadioMenuItem, - Role::Meter => AtspiRole::LevelBar, - Role::Navigation => AtspiRole::Landmark, - Role::Note => AtspiRole::Comment, - Role::Pane | Role::ScrollView => AtspiRole::Panel, - Role::Paragraph => AtspiRole::Paragraph, - Role::PdfActionableHighlight => AtspiRole::PushButton, - Role::PdfRoot => AtspiRole::DocumentFrame, - Role::PluginObject => AtspiRole::Embedded, - Role::PopupButton => { - if self.node - .data() - .html_tag - .as_ref() - .map_or(false, |tag| tag.as_ref() == "select") - { - AtspiRole::ComboBox - } else { - AtspiRole::PushButton - } - } - Role::Portal => AtspiRole::PushButton, - Role::Pre => AtspiRole::Section, - Role::ProgressIndicator => AtspiRole::ProgressBar, - Role::RadioButton => AtspiRole::RadioButton, - Role::RadioGroup => AtspiRole::Panel, - Role::Region => AtspiRole::Landmark, - Role::RootWebArea => AtspiRole::DocumentWeb, - Role::Row => AtspiRole::TableRow, - Role::RowGroup => AtspiRole::Panel, - Role::RowHeader => AtspiRole::RowHeader, - // TODO: Generally exposed as description on (`Role::Ruby`) element, not - // as its own object in the tree. - // However, it's possible to make a `Role::RubyAnnotation` element show up in the - // AX tree, for example by adding tabindex="0" to the source or - // element or making the source element the target of an aria-owns. - // Therefore, browser side needs to gracefully handle it if it actually - // shows up in the tree. - Role::RubyAnnotation => AtspiRole::Static, - Role::Section => AtspiRole::Section, - Role::ScrollBar => AtspiRole::ScrollBar, - Role::Search => AtspiRole::Landmark, - Role::Slider => AtspiRole::Slider, - Role::SpinButton => AtspiRole::SpinButton, - Role::Splitter => AtspiRole::Separator, - Role::StaticText => AtspiRole::Static, - Role::Status => AtspiRole::StatusBar, - // ax::mojom::Role::kSubscript => - // AtspiRole::Subscript, - // ax::mojom::Role::kSuperscript => - // AtspiRole::Superscript, - Role::SvgRoot => AtspiRole::DocumentFrame, - Role::Tab => AtspiRole::PageTab, - Role::Table => AtspiRole::Table, - // TODO: This mapping is correct, but it doesn't seem to be - // used. We don't necessarily want to always expose these containers, but - // we must do so if they are focusable. http://crbug.com/874043 - Role::TableHeaderContainer => AtspiRole::Panel, - Role::TabList => AtspiRole::PageTabList, - Role::TabPanel => AtspiRole::ScrollPane, - // TODO: This mapping should also be applied to the dfn - // element. http://crbug.com/874411 - Role::Term => AtspiRole::DescriptionTerm, - Role::TitleBar => AtspiRole::TitleBar, - Role::TextField | Role::SearchBox => { - if self.node.data().protected { - AtspiRole::PasswordText - } else { - AtspiRole::Entry - } - } - Role::TextFieldWithComboBox => AtspiRole::ComboBox, - Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => { - AtspiRole::Static - } - Role::Timer => AtspiRole::Timer, - Role::ToggleButton => AtspiRole::ToggleButton, - Role::Toolbar => AtspiRole::ToolBar, - Role::Tooltip => AtspiRole::ToolTip, - Role::Tree => AtspiRole::Tree, - Role::TreeItem => AtspiRole::TreeItem, - Role::TreeGrid => AtspiRole::TreeTable, - Role::Video => AtspiRole::Video, - // In AT-SPI, elements with `AtspiRole::Frame` are windows with titles and - // buttons, while those with `AtspiRole::Window` are windows without those - // elements. - Role::Window => AtspiRole::Frame, - Role::Client | Role::WebView => AtspiRole::Panel, - Role::FigureCaption => AtspiRole::Caption, - // TODO: Are there special cases to consider? - Role::Presentation | Role::Unknown => AtspiRole::Unknown, - Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject, + Role::AlertDialog => AtspiRole::Alert, + Role::Comment | Role::Suggestion => AtspiRole::Section, + // TODO: See how to represent ARIA role="application" + Role::Application => AtspiRole::Embedded, + Role::Article => AtspiRole::Article, + Role::Audio => AtspiRole::Audio, + Role::Banner | Role::Header => AtspiRole::Landmark, + Role::Blockquote => AtspiRole::BlockQuote, + Role::Caret => AtspiRole::Unknown, + Role::Button => AtspiRole::PushButton, + Role::Canvas => AtspiRole::Canvas, + Role::Caption => AtspiRole::Caption, + Role::Cell => AtspiRole::TableCell, + Role::CheckBox => AtspiRole::CheckBox, + Role::Switch => AtspiRole::ToggleButton, + Role::ColorWell => AtspiRole::PushButton, + Role::Column => AtspiRole::Unknown, + Role::ColumnHeader => AtspiRole::ColumnHeader, + Role::ComboBoxGrouping | Role::ComboBoxMenuButton => AtspiRole::ComboBox, + Role::Complementary => AtspiRole::Landmark, + Role::ContentDeletion => AtspiRole::ContentDeletion, + Role::ContentInsertion => AtspiRole::ContentInsertion, + Role::ContentInfo | Role::Footer => AtspiRole::Landmark, + Role::Date | Role::DateTime => AtspiRole::DateEditor, + Role::Definition | Role::DescriptionListDetail => AtspiRole::DescriptionValue, + Role::DescriptionList => AtspiRole::DescriptionList, + Role::DescriptionListTerm => AtspiRole::DescriptionTerm, + Role::Details => AtspiRole::Panel, + Role::Dialog => AtspiRole::Dialog, + Role::Directory => AtspiRole::List, + Role::DisclosureTriangle => AtspiRole::ToggleButton, + Role::DocCover => AtspiRole::Image, + Role::DocBackLink | Role::DocBiblioRef | Role::DocGlossRef | Role::DocNoteRef => { + AtspiRole::Link + } + Role::DocBiblioEntry | Role::DocEndnote => AtspiRole::ListItem, + Role::DocNotice | Role::DocTip => AtspiRole::Comment, + Role::DocFootnote => AtspiRole::Footnote, + Role::DocPageBreak => AtspiRole::Separator, + Role::DocPageFooter => AtspiRole::Footer, + Role::DocPageHeader => AtspiRole::Header, + Role::DocAcknowledgements + | Role::DocAfterword + | Role::DocAppendix + | Role::DocBibliography + | Role::DocChapter + | Role::DocConclusion + | Role::DocCredits + | Role::DocEndnotes + | Role::DocEpilogue + | Role::DocErrata + | Role::DocForeword + | Role::DocGlossary + | Role::DocIndex + | Role::DocIntroduction + | Role::DocPageList + | Role::DocPart + | Role::DocPreface + | Role::DocPrologue + | Role::DocToc => AtspiRole::Landmark, + Role::DocAbstract + | Role::DocColophon + | Role::DocCredit + | Role::DocDedication + | Role::DocEpigraph + | Role::DocExample + | Role::DocPullquote + | Role::DocQna => AtspiRole::Section, + Role::DocSubtitle => AtspiRole::Heading, + Role::Document => AtspiRole::DocumentFrame, + Role::EmbeddedObject => AtspiRole::Embedded, + // TODO: Forms which lack an accessible name are no longer + // exposed as forms. http://crbug.com/874384. Forms which have accessible + // names should be exposed as `AtspiRole::Landmark` according to Core AAM. + Role::Form => AtspiRole::Form, + Role::Figure | Role::Feed => AtspiRole::Panel, + Role::GenericContainer + | Role::FooterAsNonLandmark + | Role::HeaderAsNonLandmark + | Role::Ruby => AtspiRole::Section, + Role::GraphicsDocument => AtspiRole::DocumentFrame, + Role::GraphicsObject => AtspiRole::Panel, + Role::GraphicsSymbol => AtspiRole::Image, + Role::Grid => AtspiRole::Table, + Role::Group => AtspiRole::Panel, + Role::Heading => AtspiRole::Heading, + Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame, + Role::Image => { + if self.node.unignored_children().next().is_some() { + AtspiRole::ImageMap + } else { + AtspiRole::Image + } + } + Role::InlineTextBox => AtspiRole::Static, + Role::InputTime => AtspiRole::DateEditor, + Role::LabelText | Role::Legend => AtspiRole::Label, + // Layout table objects are treated the same as `Role::GenericContainer`. + Role::LayoutTable => AtspiRole::Section, + Role::LayoutTableCell => AtspiRole::Section, + Role::LayoutTableRow => AtspiRole::Section, + // TODO: Having a separate accessible object for line breaks + // is inconsistent with other implementations. http://crbug.com/873144#c1. + Role::LineBreak => AtspiRole::Static, + Role::Link => AtspiRole::Link, + Role::List => AtspiRole::List, + Role::ListBox => AtspiRole::ListBox, + // TODO: Use `AtspiRole::MenuItem' inside a combo box, see how + // ax_platform_node_win.cc code does this. + Role::ListBoxOption => AtspiRole::ListItem, + Role::ListGrid => AtspiRole::Table, + Role::ListItem => AtspiRole::ListItem, + // Regular list markers only expose their alternative text, but do not + // expose their descendants, and the descendants should be ignored. This + // is because the alternative text depends on the counter style and can + // be different from the actual (visual) marker text, and hence, + // inconsistent with the descendants. We treat a list marker as non-text + // only if it still has non-ignored descendants, which happens only when => + // - The list marker itself is ignored but the descendants are not + // - Or the list marker contains images + Role::ListMarker => { + if self.node.unignored_children().next().is_none() { + AtspiRole::Static + } else { + AtspiRole::Panel + } + } + Role::Log => AtspiRole::Log, + Role::Main => AtspiRole::Landmark, + Role::Mark => AtspiRole::Static, + Role::Math => AtspiRole::Math, + Role::Marquee => AtspiRole::Marquee, + Role::Menu | Role::MenuListPopup => AtspiRole::Menu, + Role::MenuBar => AtspiRole::MenuBar, + Role::MenuItem | Role::MenuListOption => AtspiRole::MenuItem, + Role::MenuItemCheckBox => AtspiRole::CheckMenuItem, + Role::MenuItemRadio => AtspiRole::RadioMenuItem, + Role::Meter => AtspiRole::LevelBar, + Role::Navigation => AtspiRole::Landmark, + Role::Note => AtspiRole::Comment, + Role::Pane | Role::ScrollView => AtspiRole::Panel, + Role::Paragraph => AtspiRole::Paragraph, + Role::PdfActionableHighlight => AtspiRole::PushButton, + Role::PdfRoot => AtspiRole::DocumentFrame, + Role::PluginObject => AtspiRole::Embedded, + Role::PopupButton => { + if self + .node + .data() + .html_tag + .as_ref() + .map_or(false, |tag| tag.as_ref() == "select") + { + AtspiRole::ComboBox + } else { + AtspiRole::PushButton + } + } + Role::Portal => AtspiRole::PushButton, + Role::Pre => AtspiRole::Section, + Role::ProgressIndicator => AtspiRole::ProgressBar, + Role::RadioButton => AtspiRole::RadioButton, + Role::RadioGroup => AtspiRole::Panel, + Role::Region => AtspiRole::Landmark, + Role::RootWebArea => AtspiRole::DocumentWeb, + Role::Row => AtspiRole::TableRow, + Role::RowGroup => AtspiRole::Panel, + Role::RowHeader => AtspiRole::RowHeader, + // TODO: Generally exposed as description on (`Role::Ruby`) element, not + // as its own object in the tree. + // However, it's possible to make a `Role::RubyAnnotation` element show up in the + // AX tree, for example by adding tabindex="0" to the source or + // element or making the source element the target of an aria-owns. + // Therefore, browser side needs to gracefully handle it if it actually + // shows up in the tree. + Role::RubyAnnotation => AtspiRole::Static, + Role::Section => AtspiRole::Section, + Role::ScrollBar => AtspiRole::ScrollBar, + Role::Search => AtspiRole::Landmark, + Role::Slider => AtspiRole::Slider, + Role::SpinButton => AtspiRole::SpinButton, + Role::Splitter => AtspiRole::Separator, + Role::StaticText => AtspiRole::Static, + Role::Status => AtspiRole::StatusBar, + // ax::mojom::Role::kSubscript => + // AtspiRole::Subscript, + // ax::mojom::Role::kSuperscript => + // AtspiRole::Superscript, + Role::SvgRoot => AtspiRole::DocumentFrame, + Role::Tab => AtspiRole::PageTab, + Role::Table => AtspiRole::Table, + // TODO: This mapping is correct, but it doesn't seem to be + // used. We don't necessarily want to always expose these containers, but + // we must do so if they are focusable. http://crbug.com/874043 + Role::TableHeaderContainer => AtspiRole::Panel, + Role::TabList => AtspiRole::PageTabList, + Role::TabPanel => AtspiRole::ScrollPane, + // TODO: This mapping should also be applied to the dfn + // element. http://crbug.com/874411 + Role::Term => AtspiRole::DescriptionTerm, + Role::TitleBar => AtspiRole::TitleBar, + Role::TextField | Role::SearchBox => { + if self.node.data().protected { + AtspiRole::PasswordText + } else { + AtspiRole::Entry } + } + Role::TextFieldWithComboBox => AtspiRole::ComboBox, + Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => { + AtspiRole::Static + } + Role::Timer => AtspiRole::Timer, + Role::ToggleButton => AtspiRole::ToggleButton, + Role::Toolbar => AtspiRole::ToolBar, + Role::Tooltip => AtspiRole::ToolTip, + Role::Tree => AtspiRole::Tree, + Role::TreeItem => AtspiRole::TreeItem, + Role::TreeGrid => AtspiRole::TreeTable, + Role::Video => AtspiRole::Video, + // In AT-SPI, elements with `AtspiRole::Frame` are windows with titles and + // buttons, while those with `AtspiRole::Window` are windows without those + // elements. + Role::Window => AtspiRole::Frame, + Role::Client | Role::WebView => AtspiRole::Panel, + Role::FigureCaption => AtspiRole::Caption, + // TODO: Are there special cases to consider? + Role::Presentation | Role::Unknown => AtspiRole::Unknown, + Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject, + } } pub(crate) fn state(&self) -> StateSet { @@ -373,7 +381,7 @@ impl ResolvedPlatformNode<'_> { state.insert(State::SelectableText); match self.node.data().multiline { true => state.insert(State::MultiLine), - false => state.insert(State::SingleLine) + false => state.insert(State::SingleLine), } } @@ -450,9 +458,7 @@ impl PlatformNode { { self.0 .map(|node| f(ResolvedPlatformNode::new(node))) - .ok_or(fdo::Error::UnknownObject( - "".into(), - )) + .ok_or(fdo::Error::UnknownObject("".into())) } } @@ -479,7 +485,7 @@ impl AppState { #[derive(Clone)] pub(crate) struct PlatformRootNode { pub state: Weak>, - pub tree: Weak + pub tree: Weak, } impl PlatformRootNode { From c3a89b5efb1f65f6509163fab246f0083f611ac0 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 15 Aug 2022 14:12:54 +0200 Subject: [PATCH 20/69] Improve Interfaces --- .../linux/src/atspi/interfaces/accessible.rs | 19 +-- platforms/linux/src/atspi/interfaces/mod.rs | 117 +++++++++++++++++- platforms/linux/src/node.rs | 2 +- 3 files changed, 118 insertions(+), 20 deletions(-) diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 78fbcad34..162157be6 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -22,8 +22,6 @@ impl AccessibleInterface { } } -const INTERFACES: &[&'static str] = &["org.a11y.atspi.Accessible", "org.a11y.atspi.Application"]; - #[dbus_interface(name = "org.a11y.atspi.Accessible")] impl AccessibleInterface { #[dbus_interface(property)] @@ -118,17 +116,8 @@ impl AccessibleInterface { self.node.resolve(|node| node.state()) } - fn get_interfaces(&self) -> fdo::Result> { - self.node.resolve(|node| { - let mut interfaces = Vec::with_capacity(INTERFACES.len()); - for interface in node.interfaces().iter() { - if interface > Interface::Application { - break; - } - interfaces.push(INTERFACES[(interface as u8).trailing_zeros() as usize]); - } - interfaces - }) + fn get_interfaces(&self) -> fdo::Result { + self.node.resolve(|node| node.interfaces()) } } @@ -214,7 +203,7 @@ impl AccessibleInterface { state } - fn get_interfaces(&self) -> Vec<&'static str> { - vec![INTERFACES[0], INTERFACES[1]] + fn get_interfaces(&self) -> Interfaces { + Interfaces::new(Interface::Accessible | Interface::Application) } } diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index 4f09356db..292df3730 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -3,14 +3,21 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use enumflags2::{bitflags, BitFlags}; mod accessible; mod application; mod events; +use enumflags2::{bitflags, BitFlags, FromBitsError}; +use serde::{ + de::{self, Deserialize, Deserializer, SeqAccess, Visitor}, + ser::{Serialize, SerializeSeq, Serializer}, +}; +use std::fmt; +use zvariant::{Signature, Type}; + #[bitflags] -#[repr(u8)] -#[derive(Clone, Copy, PartialEq, PartialOrd)] +#[repr(u32)] +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] pub enum Interface { Accessible, Application, @@ -19,7 +26,109 @@ pub enum Interface { WindowEvents, } -pub type Interfaces = BitFlags; +#[derive(Clone, Copy, Debug)] +pub struct Interfaces(BitFlags); + +impl Interfaces { + pub fn new>>(value: B) -> Self { + Self(value.into()) + } + + pub fn from_bits(bits: u32) -> Result> { + Ok(Interfaces(BitFlags::from_bits(bits)?)) + } + + pub fn contains>>(self, other: B) -> bool { + self.0.contains(other) + } + + pub fn insert>>(&mut self, other: B) { + self.0.insert(other); + } + + pub fn iter(self) -> impl Iterator { + self.0.iter() + } +} + +const INTERFACE_NAMES: &[&str] = &["org.a11y.atspi.Accessible", "org.a11y.atspi.Application"]; + +impl<'de> Deserialize<'de> for Interfaces { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct InterfacesVisitor; + + impl<'de> Visitor<'de> for InterfacesVisitor { + type Value = Interfaces; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence comprised of D-Bus interface names") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + match SeqAccess::next_element::>(&mut seq)? { + Some(names) => { + let mut bits = 0; + for name in names { + if let Ok(index) = INTERFACE_NAMES.binary_search(&name.as_str()) { + bits &= 2u32.pow(index as u32); + } + } + Ok(Interfaces::from_bits(bits).unwrap()) + } + None => Err(de::Error::custom("Vec containing D-Bus interface names")), + } + } + } + + deserializer.deserialize_seq(InterfacesVisitor) + } +} + +impl Serialize for Interfaces { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut interfaces = Vec::with_capacity(INTERFACE_NAMES.len()); + for interface in self.iter() { + if interface > Interface::Application { + break; + } + interfaces.push(INTERFACE_NAMES[(interface as u32).trailing_zeros() as usize]); + } + let mut seq = serializer.serialize_seq(Some(interfaces.len()))?; + for interface in interfaces { + seq.serialize_element(interface)?; + } + seq.end() + } +} + +impl Type for Interfaces { + fn signature() -> Signature<'static> { + Signature::from_str_unchecked("as") + } +} + +impl From for Interfaces { + fn from(value: Interface) -> Self { + Self(value.into()) + } +} + +impl std::ops::BitXor for Interfaces { + type Output = Interfaces; + + fn bitxor(self, other: Self) -> Self::Output { + Interfaces(self.0 ^ other.0) + } +} pub use accessible::*; pub use application::*; diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 30e3f5afd..a89a385b3 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -433,7 +433,7 @@ impl ResolvedPlatformNode<'_> { } pub(crate) fn interfaces(&self) -> Interfaces { - let mut interfaces: Interfaces = Interface::Accessible | Interface::ObjectEvents; + let mut interfaces = Interfaces::new(Interface::Accessible | Interface::ObjectEvents); if self.node.role() == Role::Window { interfaces.insert(Interface::WindowEvents); } From a019089799574d661775bc6c4c1e75d3aca6e487 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 15 Aug 2022 14:31:21 +0200 Subject: [PATCH 21/69] Fix state property for the root node --- platforms/linux/src/atspi/interfaces/accessible.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 162157be6..8ec3bd4a6 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -198,9 +198,7 @@ impl AccessibleInterface { } fn get_state(&self) -> StateSet { - let mut state = StateSet::empty(); - state.insert(State::Showing | State::Visible); - state + StateSet::empty() } fn get_interfaces(&self) -> Interfaces { From 5e50f5197358f3ad49d98d1f754df0809338cbec Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 15 Aug 2022 16:54:20 +0200 Subject: [PATCH 22/69] Implement org.a11y.atspi.Value --- platforms/linux/src/atspi/bus.rs | 9 ++++ platforms/linux/src/atspi/interfaces/mod.rs | 11 +++- platforms/linux/src/atspi/interfaces/value.rs | 54 +++++++++++++++++++ platforms/linux/src/node.rs | 23 ++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 platforms/linux/src/atspi/interfaces/value.rs diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 330ec6421..1b45e8ab1 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -43,6 +43,9 @@ impl<'a> Bus<'a> { ), )? { let interfaces = node.interfaces(); + if interfaces.contains(Interface::Value) { + self.register_value(&path, node.downgrade())?; + } if interfaces.contains(Interface::FocusEvents) { self.register_focus_events(&path)?; } @@ -91,6 +94,12 @@ impl<'a> Bus<'a> { self.conn.object_server().at(path, ObjectEventsInterface {}) } + fn register_value(&mut self, path: &str, node: PlatformNode) -> Result { + self.conn + .object_server() + .at(path, ValueInterface::new(node)) + } + fn register_window_events(&mut self, path: &str, node: PlatformNode) -> Result { self.conn .object_server() diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index 292df3730..225318804 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -6,6 +6,7 @@ mod accessible; mod application; mod events; +mod value; use enumflags2::{bitflags, BitFlags, FromBitsError}; use serde::{ @@ -21,6 +22,7 @@ use zvariant::{Signature, Type}; pub enum Interface { Accessible, Application, + Value, FocusEvents, ObjectEvents, WindowEvents, @@ -51,7 +53,11 @@ impl Interfaces { } } -const INTERFACE_NAMES: &[&str] = &["org.a11y.atspi.Accessible", "org.a11y.atspi.Application"]; +const INTERFACE_NAMES: &[&str] = &[ + "org.a11y.atspi.Accessible", + "org.a11y.atspi.Application", + "org.a11y.atspi.Value", +]; impl<'de> Deserialize<'de> for Interfaces { fn deserialize(deserializer: D) -> Result @@ -97,7 +103,7 @@ impl Serialize for Interfaces { { let mut interfaces = Vec::with_capacity(INTERFACE_NAMES.len()); for interface in self.iter() { - if interface > Interface::Application { + if interface > Interface::Value { break; } interfaces.push(INTERFACE_NAMES[(interface as u32).trailing_zeros() as usize]); @@ -133,3 +139,4 @@ impl std::ops::BitXor for Interfaces { pub use accessible::*; pub use application::*; pub use events::*; +pub use value::*; diff --git a/platforms/linux/src/atspi/interfaces/value.rs b/platforms/linux/src/atspi/interfaces/value.rs new file mode 100644 index 000000000..d33fadbdc --- /dev/null +++ b/platforms/linux/src/atspi/interfaces/value.rs @@ -0,0 +1,54 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use crate::PlatformNode; + +pub struct ValueInterface { + node: PlatformNode, +} + +impl ValueInterface { + pub(crate) fn new(node: PlatformNode) -> Self { + Self { node } + } +} + +#[dbus_interface(name = "org.a11y.atspi.Value")] +impl ValueInterface { + #[dbus_interface(property)] + fn minimum_value(&self) -> f64 { + self.node + .resolve(|resolved| resolved.minimum_value()) + .unwrap() + } + + #[dbus_interface(property)] + fn maximum_value(&self) -> f64 { + self.node + .resolve(|resolved| resolved.maximum_value()) + .unwrap() + } + + #[dbus_interface(property)] + fn minimum_increment(&self) -> f64 { + self.node + .resolve(|resolved| resolved.minimum_increment()) + .unwrap() + } + + #[dbus_interface(property)] + fn current_value(&self) -> f64 { + self.node + .resolve(|resolved| resolved.current_value()) + .unwrap() + } + + #[dbus_interface(property)] + fn set_current_value(&self, value: f64) { + self.node + .resolve(|resolved| resolved.set_current_value(value)) + .unwrap(); + } +} diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index a89a385b3..e99e13a09 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -434,6 +434,9 @@ impl ResolvedPlatformNode<'_> { pub(crate) fn interfaces(&self) -> Interfaces { let mut interfaces = Interfaces::new(Interface::Accessible | Interface::ObjectEvents); + if self.node.numeric_value().is_some() { + interfaces.insert(Interface::Value); + } if self.node.role() == Role::Window { interfaces.insert(Interface::WindowEvents); } @@ -442,6 +445,26 @@ impl ResolvedPlatformNode<'_> { } interfaces } + + pub fn minimum_value(&self) -> f64 { + self.node.min_numeric_value().unwrap_or(std::f64::MIN) + } + + pub fn maximum_value(&self) -> f64 { + self.node.max_numeric_value().unwrap_or(std::f64::MAX) + } + + pub fn minimum_increment(&self) -> f64 { + self.node.numeric_value_step().unwrap_or(0.0) + } + + pub fn current_value(&self) -> f64 { + self.node.numeric_value().unwrap_or(0.0) + } + + pub fn set_current_value(&self, value: f64) { + self.node.set_numeric_value(value) + } } #[derive(Clone)] From f8da04c6af6e70eb45f17190da109df915f8ddda Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 15 Aug 2022 18:49:57 +0200 Subject: [PATCH 23/69] Remove unused Application interface methods --- platforms/linux/src/atspi/interfaces/application.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/linux/src/atspi/interfaces/application.rs index e60251733..54e86dfef 100644 --- a/platforms/linux/src/atspi/interfaces/application.rs +++ b/platforms/linux/src/atspi/interfaces/application.rs @@ -50,12 +50,4 @@ impl ApplicationInterface { .map(|state| state.write().id = Some(id)) .ok_or(fdo::Error::UnknownObject("".into())) } - - fn get_locale(&self, lctype: u32) -> fdo::Result { - Ok(String::new()) - } - - fn register_event_listener(&self, _event: String) {} - - fn deregister_event_listener(&self, _event: String) {} } From 1cb6005d94de5e0c70935e0f8b02489a7627570f Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 17 Aug 2022 22:50:34 +0200 Subject: [PATCH 24/69] Refactor and improve events --- platforms/linux/examples/hello_world.rs | 36 ++-- platforms/linux/src/adapter.rs | 153 ++++++++------ platforms/linux/src/atspi/bus.rs | 160 ++++++++------- .../linux/src/atspi/interfaces/events.rs | 193 ++++-------------- platforms/linux/src/atspi/interfaces/mod.rs | 6 - platforms/linux/src/node.rs | 62 ++++-- 6 files changed, 279 insertions(+), 331 deletions(-) diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index d5904872e..ada288068 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -78,11 +78,13 @@ fn main() { Event::WindowEvent { event, window_id } if window_id == window.id() => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::Focused(window_has_focus) => { - adapter.update(TreeUpdate { - nodes: vec![], - focus: window_has_focus.then(|| unsafe { FOCUS }), - tree: None, - }); + adapter + .update(TreeUpdate { + nodes: vec![], + focus: window_has_focus.then(|| unsafe { FOCUS }), + tree: None, + }) + .raise(); } WindowEvent::KeyboardInput { input: @@ -98,11 +100,13 @@ fn main() { } else { BUTTON_1_ID }; - adapter.update(TreeUpdate { - nodes: vec![], - focus: Some(FOCUS), - tree: None, - }); + adapter + .update(TreeUpdate { + nodes: vec![], + focus: Some(FOCUS), + tree: None, + }) + .raise(); }, WindowEvent::KeyboardInput { input: @@ -118,11 +122,13 @@ fn main() { } else { make_button(BUTTON_2_ID, "You pressed button 2") }; - adapter.update(TreeUpdate { - nodes: vec![updated_node], - focus: Some(FOCUS), - tree: None, - }); + adapter + .update(TreeUpdate { + nodes: vec![updated_node], + focus: Some(FOCUS), + tree: None, + }) + .raise(); }, _ => (), }, diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 58bad3e8f..4f1ab6c3d 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -3,23 +3,18 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use accesskit::{ActionHandler, NodeId, Role, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ - interfaces::{ObjectEvent, WindowEvent}, + interfaces::{EventKind, ObjectEvent, QueuedEvent, WindowEvent}, Bus, State, }; use crate::node::{AppState, PlatformNode, PlatformRootNode, ResolvedPlatformNode}; use parking_lot::RwLock; -lazy_static! { - pub(crate) static ref CURRENT_ACTIVE_WINDOW: Arc>> = - Arc::new(Mutex::new(None)); -} - pub struct Adapter<'a> { atspi_bus: Bus<'a>, app_state: Arc>, @@ -69,82 +64,85 @@ impl<'a> Adapter<'a> { }) } - pub fn update(&self, update: TreeUpdate) { + pub fn update(&self, update: TreeUpdate) -> QueuedEvents { + let mut queue = Vec::new(); self.tree.update_and_process_changes(update, |change| { match change { TreeChange::FocusMoved { old_node, new_node } => { - if let Some(old_node) = old_node { - let old_node = ResolvedPlatformNode::new(old_node); - self.atspi_bus - .emit_object_event( - &old_node, - ObjectEvent::StateChanged(State::Focused, false), - ) - .unwrap(); - } - if let Ok(mut active_window) = CURRENT_ACTIVE_WINDOW.lock() { - let node_window = new_node.map(|node| containing_window(node)).flatten(); - let node_window_id = node_window.map(|node| node.id()); - if let Some(active_window_id) = *active_window { - if node_window_id != Some(active_window_id) { - self.window_deactivated(active_window_id); - } + let old_window = old_node.and_then(|node| containing_window(node)); + let new_window = new_node.and_then(|node| containing_window(node)); + if old_window.map(|n| n.id()) != new_window.map(|n| n.id()) { + if let Some(window) = old_window { + self.window_deactivated(&ResolvedPlatformNode::new(window), &mut queue); } - *active_window = node_window_id; - if let Some(new_node) = new_node { - if let Some(node_window_id) = node_window_id { - self.window_activated(node_window_id); - } - let new_node = ResolvedPlatformNode::new(new_node); - self.atspi_bus - .emit_object_event( - &new_node, - ObjectEvent::StateChanged(State::Focused, true), - ) - .unwrap(); - self.atspi_bus.emit_focus_event(&new_node).unwrap(); + if let Some(window) = new_window { + self.window_activated(&ResolvedPlatformNode::new(window), &mut queue); } } - } - TreeChange::NodeUpdated { old_node, new_node } => { - let old_state = ResolvedPlatformNode::new(old_node).state(); - let new_platform_node = ResolvedPlatformNode::new(new_node); - let new_state = new_platform_node.state(); - let changed_states = old_state ^ new_state; - let mut events = Vec::new(); - for state in changed_states.iter() { - events.push(ObjectEvent::StateChanged(state, new_state.contains(state))); + if let Some(node) = new_node.map(|node| ResolvedPlatformNode::new(node)) { + queue.push(QueuedEvent { + target: node.id(), + kind: EventKind::Object(ObjectEvent::StateChanged( + State::Focused, + true, + )), + }); + queue.push(QueuedEvent { + target: node.id(), + kind: EventKind::Focus, + }); } - if let Some(name) = new_node.name() { - if old_node.name().as_ref() != Some(&name) { - events.push(ObjectEvent::NameChanged(name)); - } + if let Some(node) = old_node.map(|node| ResolvedPlatformNode::new(node)) { + queue.push(QueuedEvent { + target: node.id(), + kind: EventKind::Object(ObjectEvent::StateChanged( + State::Focused, + false, + )), + }); } - self.atspi_bus - .emit_object_events(&new_platform_node, events); + } + TreeChange::NodeUpdated { old_node, new_node } => { + let old_node = ResolvedPlatformNode::new(old_node); + let new_node = ResolvedPlatformNode::new(new_node); + new_node.enqueue_changes(&mut queue, &old_node); } // TODO: handle other events _ => (), }; }); + QueuedEvents { + bus: self.atspi_bus.clone(), + queue, + } } - fn window_activated(&self, window_id: NodeId) { - let reader = self.tree.read(); - let node = ResolvedPlatformNode::new(reader.node_by_id(window_id).unwrap()); - self.atspi_bus - .emit_window_event(&node, WindowEvent::Activated); - self.atspi_bus - .emit_object_event(&node, ObjectEvent::StateChanged(State::Active, true)); + fn window_activated(&self, window: &ResolvedPlatformNode, queue: &mut Vec) { + queue.push(QueuedEvent { + target: window.id(), + kind: EventKind::Window { + window_name: window.name(), + event: WindowEvent::Activated, + }, + }); + queue.push(QueuedEvent { + target: window.id(), + kind: EventKind::Object(ObjectEvent::StateChanged(State::Active, true)), + }); } - fn window_deactivated(&self, window_id: NodeId) { - let reader = self.tree.read(); - let node = ResolvedPlatformNode::new(reader.node_by_id(window_id).unwrap()); - self.atspi_bus - .emit_object_event(&node, ObjectEvent::StateChanged(State::Active, false)); - self.atspi_bus - .emit_window_event(&node, WindowEvent::Deactivated); + fn window_deactivated(&self, window: &ResolvedPlatformNode, queue: &mut Vec) { + queue.push(QueuedEvent { + target: window.id(), + kind: EventKind::Window { + window_name: window.name(), + event: WindowEvent::Deactivated, + }, + }); + queue.push(QueuedEvent { + target: window.id(), + kind: EventKind::Object(ObjectEvent::StateChanged(State::Active, false)), + }); } fn root_platform_node(&self) -> PlatformNode { @@ -167,3 +165,26 @@ fn containing_window(node: Node) -> Option { None } } + +#[must_use = "events must be explicitly raised"] +pub struct QueuedEvents<'a> { + bus: Bus<'a>, + queue: Vec, +} + +impl<'a> QueuedEvents<'a> { + pub fn raise(&self) { + for event in &self.queue { + let _ = match &event.kind { + EventKind::Focus => self.bus.emit_focus_event(&event.target), + EventKind::Object(data) => self.bus.emit_object_event(&event.target, data), + EventKind::Window { + window_name, + event: window_event, + } => self + .bus + .emit_window_event(&event.target, &window_name, window_event), + }; + } + } +} diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 1b45e8ab1..023eaf9d3 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -10,17 +10,23 @@ use crate::atspi::{ ObjectId, }; use crate::{PlatformNode, PlatformRootNode, ResolvedPlatformNode}; -use async_io::block_on; -use std::{convert::TryInto, env::var}; +use std::{ + collections::HashMap, + convert::{AsRef, TryInto}, + env::var, +}; use x11rb::{ connection::Connection as _, protocol::xproto::{AtomEnum, ConnectionExt}, }; use zbus::{ blocking::{Connection, ConnectionBuilder}, + names::{BusName, InterfaceName, MemberName}, Address, Result, }; +use zvariant::Value; +#[derive(Clone)] pub struct Bus<'a> { conn: Connection, socket_proxy: SocketProxy<'a>, @@ -44,18 +50,9 @@ impl<'a> Bus<'a> { )? { let interfaces = node.interfaces(); if interfaces.contains(Interface::Value) { - self.register_value(&path, node.downgrade())?; - } - if interfaces.contains(Interface::FocusEvents) { - self.register_focus_events(&path)?; - } - if interfaces.contains(Interface::ObjectEvents) { - self.register_object_events(&path)?; - } - if interfaces.contains(Interface::WindowEvents) { - self.register_window_events(&path, node.downgrade()) + self.register_value(&path, node.downgrade()) } else { - Ok(true) + Ok(false) } } else { Ok(false) @@ -86,87 +83,94 @@ impl<'a> Bus<'a> { } } - fn register_focus_events(&mut self, path: &str) -> Result { - self.conn.object_server().at(path, FocusEventsInterface {}) - } - - fn register_object_events(&mut self, path: &str) -> Result { - self.conn.object_server().at(path, ObjectEventsInterface {}) - } - fn register_value(&mut self, path: &str, node: PlatformNode) -> Result { self.conn .object_server() .at(path, ValueInterface::new(node)) } - fn register_window_events(&mut self, path: &str, node: PlatformNode) -> Result { - self.conn - .object_server() - .at(path, WindowEventsInterface(node)) - } - - pub fn emit_focus_event(&self, target: &ResolvedPlatformNode) -> Result<()> { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - let iface_ref = self - .conn - .object_server() - .interface::<_, FocusEventsInterface>(path) - .unwrap(); - let iface = iface_ref.get(); - block_on(iface.focused(iface_ref.signal_context())) + pub fn emit_focus_event(&self, target: &ObjectId) -> Result<()> { + self.emit_event( + target, + "org.a11y.atspi.Event.Focus", + "Focus", + EventData { + minor: "", + detail1: 0, + detail2: 0, + any_data: 0i32.into(), + properties: HashMap::new(), + }, + ) } - pub fn emit_object_event( - &self, - target: &ResolvedPlatformNode, - event: ObjectEvent, - ) -> Result<()> { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - let iface_ref = self - .conn - .object_server() - .interface::<_, ObjectEventsInterface>(path) - .unwrap(); - let iface = iface_ref.get(); - block_on(iface.emit(event, iface_ref.signal_context())) + pub fn emit_object_event(&self, target: &ObjectId, event: &ObjectEvent) -> Result<()> { + let interface = "org.a11y.atspi.Event.Object"; + let signal = event.as_ref(); + let properties = HashMap::new(); + match event { + ObjectEvent::StateChanged(state, value) => self.emit_event( + target, + interface, + signal, + EventData { + minor: state.as_ref(), + detail1: *value as i32, + detail2: 0, + any_data: 0i32.into(), + properties, + }, + ), + ObjectEvent::PropertyChanged(property, value) => self.emit_event( + target, + interface, + signal, + EventData { + minor: property.as_ref(), + detail1: 0, + detail2: 0, + any_data: value.clone(), + properties, + }, + ), + } } - pub fn emit_object_events( + pub fn emit_window_event( &self, - target: &ResolvedPlatformNode, - events: Vec, + target: &ObjectId, + window_name: &str, + event: &WindowEvent, ) -> Result<()> { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - let iface_ref = self - .conn - .object_server() - .interface::<_, ObjectEventsInterface>(path) - .unwrap(); - block_on(async { - for event in events { - iface_ref - .get() - .emit(event, iface_ref.signal_context()) - .await?; - } - Ok(()) - }) + self.emit_event( + target, + "org.a11y.atspi.Event.Window", + event.as_ref(), + EventData { + minor: "", + detail1: 0, + detail2: 0, + any_data: Value::from(window_name).into(), + properties: HashMap::new(), + }, + ) } - pub fn emit_window_event( + fn emit_event( &self, - target: &ResolvedPlatformNode, - event: WindowEvent, + id: &ObjectId, + interface: &str, + signal_name: &str, + body: EventData, ) -> Result<()> { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, target.id().as_str()); - let iface_ref = self - .conn - .object_server() - .interface::<_, WindowEventsInterface>(path) - .unwrap(); - let iface = iface_ref.get(); - block_on(iface.emit(event, iface_ref.signal_context())) + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str()); + self.conn.emit_signal( + Option::::None, + path, + InterfaceName::from_str_unchecked(interface), + MemberName::from_str_unchecked(signal_name), + &body, + ) } } diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index 0275258ea..e74e19d19 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -3,176 +3,61 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::State; -use crate::PlatformNode; -use std::{collections::HashMap, convert::AsRef}; -use zbus::{dbus_interface, Result, SignalContext}; -use zvariant::Value; +use crate::atspi::{ObjectId, State}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use strum::AsRefStr; +use zvariant::{OwnedValue, Type, Value}; -pub struct FocusEventsInterface; +pub struct QueuedEvent { + pub target: ObjectId<'static>, + pub kind: EventKind, +} -impl FocusEventsInterface { - pub async fn focused(&self, ctxt: &SignalContext<'_>) -> Result<()> { - FocusEventsInterface::focus(ctxt, "", 0, 0, 0i32.into(), HashMap::new()).await - } +pub enum EventKind { + Focus, + Object(ObjectEvent), + Window { + window_name: String, + event: WindowEvent, + }, } -#[dbus_interface(name = "org.a11y.atspi.Event.Focus")] -impl FocusEventsInterface { - #[dbus_interface(signal)] - async fn focus( - ctxt: &SignalContext<'_>, - minor: &str, - detail1: i32, - detail2: i32, - any_data: Value<'_>, - properties: HashMap>, - ) -> Result<()>; +#[derive(AsRefStr)] +#[strum(serialize_all = "kebab-case")] +pub enum Property { + AccessibleName, + AccessibleDescription, + AccessibleParent, + AccessibleRole, } +#[derive(AsRefStr)] pub enum ObjectEvent { StateChanged(State, bool), - NameChanged(String), -} - -pub struct ObjectEventsInterface; - -impl ObjectEventsInterface { - pub async fn emit(&self, event: ObjectEvent, ctxt: &SignalContext<'_>) -> Result<()> { - let properties = HashMap::new(); - match event { - ObjectEvent::StateChanged(state, value) => { - ObjectEventsInterface::state_changed( - ctxt, - state.as_ref(), - value as i32, - 0, - 0i32.into(), - properties, - ) - .await - } - ObjectEvent::NameChanged(name) => { - ObjectEventsInterface::property_change( - ctxt, - "accessible-name", - 0, - 0, - name.into(), - properties, - ) - .await - } - } - } -} - -#[dbus_interface(name = "org.a11y.atspi.Event.Object")] -impl ObjectEventsInterface { - #[dbus_interface(signal)] - async fn property_change( - ctxt: &SignalContext<'_>, - minor: &str, - detail1: i32, - detail2: i32, - any_data: Value<'_>, - properties: HashMap>, - ) -> Result<()>; - - #[dbus_interface(signal)] - async fn state_changed( - ctxt: &SignalContext<'_>, - minor: &str, - detail1: i32, - detail2: i32, - any_data: Value<'_>, - properties: HashMap>, - ) -> Result<()>; + #[strum(serialize = "PropertyChange")] + PropertyChanged(Property, OwnedValue), } +#[derive(AsRefStr)] pub enum WindowEvent { + #[strum(serialize = "Activate")] Activated, + #[strum(serialize = "Close")] Closed, + #[strum(serialize = "Create")] Created, + #[strum(serialize = "Deactivate")] Deactivated, + #[strum(serialize = "Destroy")] Destroyed, } -pub struct WindowEventsInterface(pub(crate) PlatformNode); - -impl WindowEventsInterface { - pub async fn emit(&self, event: WindowEvent, ctxt: &SignalContext<'_>) -> Result<()> { - let name = self.0.resolve(|node| node.name().into())?; - let properties = HashMap::new(); - match event { - WindowEvent::Activated => { - WindowEventsInterface::activate(ctxt, "", 0, 0, name, properties).await - } - WindowEvent::Closed => { - WindowEventsInterface::close(ctxt, "", 0, 0, name, properties).await - } - WindowEvent::Created => { - WindowEventsInterface::create(ctxt, "", 0, 0, name, properties).await - } - WindowEvent::Deactivated => { - WindowEventsInterface::deactivate(ctxt, "", 0, 0, name, properties).await - } - WindowEvent::Destroyed => { - WindowEventsInterface::destroy(ctxt, "", 0, 0, name, properties).await - } - } - } -} - -#[dbus_interface(name = "org.a11y.atspi.Event.Window")] -impl WindowEventsInterface { - #[dbus_interface(signal)] - async fn activate( - ctxt: &SignalContext<'_>, - minor: &str, - detail1: i32, - detail2: i32, - any_data: Value<'_>, - properties: HashMap>, - ) -> Result<()>; - - #[dbus_interface(signal)] - async fn close( - ctxt: &SignalContext<'_>, - minor: &str, - detail1: i32, - detail2: i32, - any_data: Value<'_>, - properties: HashMap>, - ) -> Result<()>; - - #[dbus_interface(signal)] - async fn create( - ctxt: &SignalContext<'_>, - minor: &str, - detail1: i32, - detail2: i32, - any_data: Value<'_>, - properties: HashMap>, - ) -> Result<()>; - - #[dbus_interface(signal)] - async fn deactivate( - ctxt: &SignalContext<'_>, - minor: &str, - detail1: i32, - detail2: i32, - any_data: Value<'_>, - properties: HashMap>, - ) -> Result<()>; - - #[dbus_interface(signal)] - async fn destroy( - ctxt: &SignalContext<'_>, - minor: &str, - detail1: i32, - detail2: i32, - any_data: Value<'_>, - properties: HashMap>, - ) -> Result<()>; +#[derive(Deserialize, Serialize, Type)] +pub struct EventData<'a> { + pub minor: &'a str, + pub detail1: i32, + pub detail2: i32, + pub any_data: OwnedValue, + pub properties: HashMap, } diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index 225318804..0147860e2 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -23,9 +23,6 @@ pub enum Interface { Accessible, Application, Value, - FocusEvents, - ObjectEvents, - WindowEvents, } #[derive(Clone, Copy, Debug)] @@ -103,9 +100,6 @@ impl Serialize for Interfaces { { let mut interfaces = Vec::with_capacity(INTERFACE_NAMES.len()); for interface in self.iter() { - if interface > Interface::Value { - break; - } interfaces.push(INTERFACE_NAMES[(interface as u32).trailing_zeros() as usize]); } let mut seq = serializer.serialize_seq(Some(interfaces.len()))?; diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index e99e13a09..29dc942fb 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use crate::atspi::{ - interfaces::{Interface, Interfaces}, + interfaces::{EventKind, Interface, Interfaces, ObjectEvent, Property, QueuedEvent}, ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole, State, StateSet, }; use accesskit::{AriaCurrent, CheckedState, InvalidState, Orientation, Role}; @@ -12,6 +12,7 @@ use accesskit_consumer::{Node, Tree, WeakNode}; use parking_lot::RwLock; use std::sync::Weak; use zbus::fdo; +use zvariant::Value; pub(crate) struct ResolvedPlatformNode<'a> { node: Node<'a>, @@ -310,10 +311,8 @@ impl ResolvedPlatformNode<'_> { let platform_role = self.role(); let data = self.node.data(); let mut state = StateSet::empty(); - if let Ok(current_active) = crate::adapter::CURRENT_ACTIVE_WINDOW.lock() { - if self.node.role() == Role::Window && *current_active == Some(data.id) { - state.insert(State::Active); - } + if self.node.role() == Role::Window && self.node.parent().is_none() { + state.insert(State::Active); } if let Some(expanded) = data.expanded { state.insert(State::Expandable); @@ -433,16 +432,10 @@ impl ResolvedPlatformNode<'_> { } pub(crate) fn interfaces(&self) -> Interfaces { - let mut interfaces = Interfaces::new(Interface::Accessible | Interface::ObjectEvents); + let mut interfaces = Interfaces::new(Interface::Accessible); if self.node.numeric_value().is_some() { interfaces.insert(Interface::Value); } - if self.node.role() == Role::Window { - interfaces.insert(Interface::WindowEvents); - } - if self.node.data().focusable { - interfaces.insert(Interface::FocusEvents); - } interfaces } @@ -465,6 +458,51 @@ impl ResolvedPlatformNode<'_> { pub fn set_current_value(&self, value: f64) { self.node.set_numeric_value(value) } + + pub(crate) fn enqueue_changes(&self, queue: &mut Vec, old: &ResolvedPlatformNode) { + let old_state = old.state(); + let new_state = self.state(); + let changed_states = old_state ^ new_state; + for state in changed_states.iter() { + queue.push(QueuedEvent { + target: self.id(), + kind: EventKind::Object(ObjectEvent::StateChanged( + state, + new_state.contains(state), + )), + }); + } + let name = self.name(); + if name != old.name() { + queue.push(QueuedEvent { + target: self.id(), + kind: EventKind::Object(ObjectEvent::PropertyChanged( + Property::AccessibleName, + Value::from(name).into(), + )), + }); + } + let description = self.description(); + if description != old.description() { + queue.push(QueuedEvent { + target: self.id(), + kind: EventKind::Object(ObjectEvent::PropertyChanged( + Property::AccessibleDescription, + Value::from(description).into(), + )), + }); + } + let role = self.role(); + if role != old.role() { + queue.push(QueuedEvent { + target: self.id(), + kind: EventKind::Object(ObjectEvent::PropertyChanged( + Property::AccessibleRole, + Value::from(role as u32).into(), + )), + }); + } + } } #[derive(Clone)] From e9b9a12de9449b95693c9e51700a9cc8b651cafd Mon Sep 17 00:00:00 2001 From: DataTriny Date: Thu, 18 Aug 2022 23:57:54 +0200 Subject: [PATCH 25/69] Fix most warnings and visibility issues --- platforms/linux/src/adapter.rs | 18 +++++---- platforms/linux/src/atspi/bus.rs | 2 +- .../linux/src/atspi/interfaces/accessible.rs | 4 +- .../linux/src/atspi/interfaces/application.rs | 2 +- .../linux/src/atspi/interfaces/events.rs | 14 +++---- platforms/linux/src/atspi/interfaces/mod.rs | 12 +++--- platforms/linux/src/atspi/interfaces/value.rs | 4 +- platforms/linux/src/atspi/mod.rs | 16 ++++---- platforms/linux/src/atspi/object_address.rs | 6 +-- platforms/linux/src/atspi/object_ref.rs | 2 +- platforms/linux/src/atspi/proxies.rs | 6 +-- platforms/linux/src/atspi/state.rs | 4 +- platforms/linux/src/lib.rs | 2 - platforms/linux/src/node.rs | 38 +++++++++---------- 14 files changed, 65 insertions(+), 65 deletions(-) diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 4f1ab6c3d..9faa6df27 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -5,7 +5,7 @@ use std::sync::Arc; -use accesskit::{ActionHandler, NodeId, Role, TreeUpdate}; +use accesskit::{ActionHandler, Role, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ @@ -17,7 +17,7 @@ use parking_lot::RwLock; pub struct Adapter<'a> { atspi_bus: Bus<'a>, - app_state: Arc>, + _app_state: Arc>, tree: Arc, } @@ -45,7 +45,7 @@ impl<'a> Adapter<'a> { objects_to_add.push(ResolvedPlatformNode::new(reader.root())); add_children(reader.root(), &mut objects_to_add); for node in objects_to_add { - atspi_bus.register_node(node); + atspi_bus.register_node(node).ok()?; } } let app_state = Arc::new(RwLock::new(AppState::new( @@ -53,13 +53,15 @@ impl<'a> Adapter<'a> { toolkit_name, toolkit_version, ))); - atspi_bus.register_root_node(PlatformRootNode::new( - Arc::downgrade(&app_state), - Arc::downgrade(&tree), - )); + atspi_bus + .register_root_node(PlatformRootNode::new( + Arc::downgrade(&app_state), + Arc::downgrade(&tree), + )) + .ok()?; Some(Self { atspi_bus, - app_state, + _app_state: app_state, tree, }) } diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 023eaf9d3..32dd74dc0 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -27,7 +27,7 @@ use zbus::{ use zvariant::Value; #[derive(Clone)] -pub struct Bus<'a> { +pub(crate) struct Bus<'a> { conn: Connection, socket_proxy: SocketProxy<'a>, } diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 8ec3bd4a6..a159b5b78 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -5,13 +5,13 @@ use crate::atspi::{ interfaces::{Interface, Interfaces}, - ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role, State, StateSet, + ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role, StateSet, }; use crate::{PlatformNode, PlatformRootNode}; use std::convert::TryInto; use zbus::{fdo, names::OwnedUniqueName}; -pub struct AccessibleInterface { +pub(crate) struct AccessibleInterface { bus_name: OwnedUniqueName, node: T, } diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/linux/src/atspi/interfaces/application.rs index 54e86dfef..31c9ea34e 100644 --- a/platforms/linux/src/atspi/interfaces/application.rs +++ b/platforms/linux/src/atspi/interfaces/application.rs @@ -6,7 +6,7 @@ use crate::PlatformRootNode; use zbus::fdo; -pub struct ApplicationInterface(pub(crate) PlatformRootNode); +pub(crate) struct ApplicationInterface(pub PlatformRootNode); #[dbus_interface(name = "org.a11y.atspi.Application")] impl ApplicationInterface { diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index e74e19d19..affb1e480 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -7,14 +7,14 @@ use crate::atspi::{ObjectId, State}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use strum::AsRefStr; -use zvariant::{OwnedValue, Type, Value}; +use zvariant::{OwnedValue, Type}; -pub struct QueuedEvent { +pub(crate) struct QueuedEvent { pub target: ObjectId<'static>, pub kind: EventKind, } -pub enum EventKind { +pub(crate) enum EventKind { Focus, Object(ObjectEvent), Window { @@ -25,7 +25,7 @@ pub enum EventKind { #[derive(AsRefStr)] #[strum(serialize_all = "kebab-case")] -pub enum Property { +pub(crate) enum Property { AccessibleName, AccessibleDescription, AccessibleParent, @@ -33,14 +33,14 @@ pub enum Property { } #[derive(AsRefStr)] -pub enum ObjectEvent { +pub(crate) enum ObjectEvent { StateChanged(State, bool), #[strum(serialize = "PropertyChange")] PropertyChanged(Property, OwnedValue), } #[derive(AsRefStr)] -pub enum WindowEvent { +pub(crate) enum WindowEvent { #[strum(serialize = "Activate")] Activated, #[strum(serialize = "Close")] @@ -54,7 +54,7 @@ pub enum WindowEvent { } #[derive(Deserialize, Serialize, Type)] -pub struct EventData<'a> { +pub(crate) struct EventData<'a> { pub minor: &'a str, pub detail1: i32, pub detail2: i32, diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index 0147860e2..581ee6420 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -19,14 +19,14 @@ use zvariant::{Signature, Type}; #[bitflags] #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] -pub enum Interface { +pub(crate) enum Interface { Accessible, Application, Value, } #[derive(Clone, Copy, Debug)] -pub struct Interfaces(BitFlags); +pub(crate) struct Interfaces(BitFlags); impl Interfaces { pub fn new>>(value: B) -> Self { @@ -130,7 +130,7 @@ impl std::ops::BitXor for Interfaces { } } -pub use accessible::*; -pub use application::*; -pub use events::*; -pub use value::*; +pub(crate) use accessible::*; +pub(crate) use application::*; +pub(crate) use events::*; +pub(crate) use value::*; diff --git a/platforms/linux/src/atspi/interfaces/value.rs b/platforms/linux/src/atspi/interfaces/value.rs index d33fadbdc..46a14f4d0 100644 --- a/platforms/linux/src/atspi/interfaces/value.rs +++ b/platforms/linux/src/atspi/interfaces/value.rs @@ -5,12 +5,12 @@ use crate::PlatformNode; -pub struct ValueInterface { +pub(crate) struct ValueInterface { node: PlatformNode, } impl ValueInterface { - pub(crate) fn new(node: PlatformNode) -> Self { + pub fn new(node: PlatformNode) -> Self { Self { node } } } diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs index 71b5d3ece..8ac012177 100644 --- a/platforms/linux/src/atspi/mod.rs +++ b/platforms/linux/src/atspi/mod.rs @@ -7,17 +7,17 @@ use serde::{Deserialize, Serialize}; use zvariant::Type; mod bus; -pub mod interfaces; +pub(crate) mod interfaces; mod object_address; mod object_id; mod object_ref; -pub mod proxies; +pub(crate) mod proxies; mod state; /// Enumeration used by interface #AtspiAccessible to specify the role /// of an #AtspiAccessible object. #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, Type)] -pub enum Role { +pub(crate) enum Role { /// A role indicating an error condition, such as /// uninitialized Role data. Invalid, @@ -464,8 +464,8 @@ pub enum Role { LastDefined, } -pub use bus::Bus; -pub use object_address::*; -pub use object_id::*; -pub use object_ref::*; -pub use state::*; +pub(crate) use bus::Bus; +pub(crate) use object_address::*; +pub(crate) use object_id::*; +pub(crate) use object_ref::*; +pub(crate) use state::*; diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs index ac6d895cd..0dfe1e4f5 100644 --- a/platforms/linux/src/atspi/object_address.rs +++ b/platforms/linux/src/atspi/object_address.rs @@ -8,9 +8,9 @@ use serde::{Deserialize, Serialize}; use zbus::names::{OwnedUniqueName, UniqueName}; use zvariant::{ObjectPath, OwnedObjectPath, Type, Value}; -pub const ACCESSIBLE_PATH_PREFIX: &'static str = "/org/a11y/atspi/accessible/"; -pub const NULL_PATH: &'static str = "/org/a11y/atspi/null"; -pub const ROOT_PATH: &'static str = "/org/a11y/atspi/accessible/root"; +pub(crate) const ACCESSIBLE_PATH_PREFIX: &'static str = "/org/a11y/atspi/accessible/"; +pub(crate) const NULL_PATH: &'static str = "/org/a11y/atspi/null"; +pub(crate) const ROOT_PATH: &'static str = "/org/a11y/atspi/accessible/root"; #[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] pub struct ObjectAddress<'a> { diff --git a/platforms/linux/src/atspi/object_ref.rs b/platforms/linux/src/atspi/object_ref.rs index f88be0deb..92fd391f4 100644 --- a/platforms/linux/src/atspi/object_ref.rs +++ b/platforms/linux/src/atspi/object_ref.rs @@ -6,7 +6,7 @@ use crate::atspi::{ObjectId, OwnedObjectAddress}; use accesskit::NodeId; -pub enum ObjectRef { +pub(crate) enum ObjectRef { Managed(ObjectId<'static>), Unmanaged(OwnedObjectAddress), } diff --git a/platforms/linux/src/atspi/proxies.rs b/platforms/linux/src/atspi/proxies.rs index de512663e..61f7abf2f 100644 --- a/platforms/linux/src/atspi/proxies.rs +++ b/platforms/linux/src/atspi/proxies.rs @@ -12,7 +12,7 @@ use zbus::{dbus_proxy, Result}; interface = "org.a11y.Bus", gen_async = false )] -pub trait Bus { +pub(crate) trait Bus { fn get_address(&self) -> Result; } @@ -22,7 +22,7 @@ pub trait Bus { gen_async = false, interface = "org.a11y.atspi.Socket" )] -trait Socket { +pub(crate) trait Socket { fn embed<'a>(&self, plug: ObjectAddress<'a>) -> Result; fn unembed<'a>(&self, plug: ObjectAddress<'a>) -> Result<()>; @@ -32,7 +32,7 @@ trait Socket { } #[dbus_proxy(interface = "org.a11y.Status")] -pub trait Status { +pub(crate) trait Status { #[dbus_proxy(property)] fn is_enabled(&self) -> Result; diff --git a/platforms/linux/src/atspi/state.rs b/platforms/linux/src/atspi/state.rs index 0dd656d54..7b14189ae 100644 --- a/platforms/linux/src/atspi/state.rs +++ b/platforms/linux/src/atspi/state.rs @@ -18,7 +18,7 @@ use zvariant::{Signature, Type}; #[repr(u64)] #[derive(AsRefStr, Clone, Copy, Debug)] #[strum(serialize_all = "kebab-case")] -pub enum State { +pub(crate) enum State { /// Indicates an invalid state - probably an error condition. Invalid, /// Indicates a window is currently the active window, or @@ -225,7 +225,7 @@ pub enum State { } #[derive(Clone, Copy, Debug)] -pub struct StateSet(BitFlags); +pub(crate) struct StateSet(BitFlags); impl StateSet { pub fn from_bits(bits: u64) -> Result> { diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs index d925d0116..2c015e09a 100644 --- a/platforms/linux/src/lib.rs +++ b/platforms/linux/src/lib.rs @@ -3,8 +3,6 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -#[macro_use] -extern crate lazy_static; #[macro_use] extern crate zbus; diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 29dc942fb..d34f6a136 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -19,60 +19,60 @@ pub(crate) struct ResolvedPlatformNode<'a> { } impl ResolvedPlatformNode<'_> { - pub(crate) fn new(node: Node) -> ResolvedPlatformNode { + pub fn new(node: Node) -> ResolvedPlatformNode { ResolvedPlatformNode { node } } - pub(crate) fn downgrade(&self) -> PlatformNode { + pub fn downgrade(&self) -> PlatformNode { PlatformNode::new(&self.node) } - pub(crate) fn name(&self) -> String { + pub fn name(&self) -> String { self.node .name() .map(|name| name.to_string()) .unwrap_or(String::new()) } - pub(crate) fn description(&self) -> String { + pub fn description(&self) -> String { String::new() } - pub(crate) fn parent(&self) -> Option { + pub fn parent(&self) -> Option { self.node.parent().map(|parent| parent.id().into()) } - pub(crate) fn child_count(&self) -> usize { + pub fn child_count(&self) -> usize { self.node.children().count() } - pub(crate) fn locale(&self) -> String { + pub fn locale(&self) -> String { String::new() } - pub(crate) fn id(&self) -> ObjectId<'static> { + pub fn id(&self) -> ObjectId<'static> { self.node.id().into() } - pub(crate) fn child_at_index(&self, index: usize) -> Option { + pub fn child_at_index(&self, index: usize) -> Option { self.node .children() .nth(index) .map(|child| child.id().into()) } - pub(crate) fn children(&self) -> Vec { + pub fn children(&self) -> Vec { self.node .children() .map(|child| child.id().into()) .collect() } - pub(crate) fn index_in_parent(&self) -> Option { + pub fn index_in_parent(&self) -> Option { self.node.parent_and_index().map(|(_, index)| index) } - pub(crate) fn role(&self) -> AtspiRole { + pub fn role(&self) -> AtspiRole { match self.node.role() { Role::Alert => AtspiRole::Notification, Role::AlertDialog => AtspiRole::Alert, @@ -307,7 +307,7 @@ impl ResolvedPlatformNode<'_> { } } - pub(crate) fn state(&self) -> StateSet { + pub fn state(&self) -> StateSet { let platform_role = self.role(); let data = self.node.data(); let mut state = StateSet::empty(); @@ -431,7 +431,7 @@ impl ResolvedPlatformNode<'_> { state } - pub(crate) fn interfaces(&self) -> Interfaces { + pub fn interfaces(&self) -> Interfaces { let mut interfaces = Interfaces::new(Interface::Accessible); if self.node.numeric_value().is_some() { interfaces.insert(Interface::Value); @@ -459,7 +459,7 @@ impl ResolvedPlatformNode<'_> { self.node.set_numeric_value(value) } - pub(crate) fn enqueue_changes(&self, queue: &mut Vec, old: &ResolvedPlatformNode) { + pub fn enqueue_changes(&self, queue: &mut Vec, old: &ResolvedPlatformNode) { let old_state = old.state(); let new_state = self.state(); let changed_states = old_state ^ new_state; @@ -509,11 +509,11 @@ impl ResolvedPlatformNode<'_> { pub(crate) struct PlatformNode(WeakNode); impl PlatformNode { - pub(crate) fn new(node: &Node) -> Self { + pub fn new(node: &Node) -> Self { Self(node.downgrade()) } - pub(crate) fn resolve(&self, f: F) -> fdo::Result + pub fn resolve(&self, f: F) -> fdo::Result where for<'a> F: FnOnce(ResolvedPlatformNode<'a>) -> T, { @@ -532,7 +532,7 @@ pub(crate) struct AppState { } impl AppState { - pub(crate) fn new(name: String, toolkit_name: String, toolkit_version: String) -> Self { + pub fn new(name: String, toolkit_name: String, toolkit_version: String) -> Self { Self { name, toolkit_name, @@ -550,7 +550,7 @@ pub(crate) struct PlatformRootNode { } impl PlatformRootNode { - pub(crate) fn new(state: Weak>, tree: Weak) -> Self { + pub fn new(state: Weak>, tree: Weak) -> Self { Self { state, tree } } } From 33e6793d980b85d97dcfa73354eaf52d35567758 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 21 Aug 2022 14:11:29 +0200 Subject: [PATCH 26/69] Simplify QueuedEvent and raise parent changed event --- platforms/linux/src/adapter.rs | 62 +++++++------------ platforms/linux/src/atspi/bus.rs | 27 ++++++-- .../linux/src/atspi/interfaces/accessible.rs | 10 +-- .../linux/src/atspi/interfaces/events.rs | 34 +++++----- platforms/linux/src/atspi/mod.rs | 1 + platforms/linux/src/atspi/object_address.rs | 6 +- platforms/linux/src/atspi/object_id.rs | 2 +- platforms/linux/src/atspi/object_ref.rs | 1 + platforms/linux/src/node.rs | 46 +++++++------- 9 files changed, 100 insertions(+), 89 deletions(-) diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 9faa6df27..b0c9a8912 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -9,7 +9,7 @@ use accesskit::{ActionHandler, Role, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ - interfaces::{EventKind, ObjectEvent, QueuedEvent, WindowEvent}, + interfaces::{ObjectEvent, QueuedEvent, WindowEvent}, Bus, State, }; use crate::node::{AppState, PlatformNode, PlatformRootNode, ResolvedPlatformNode}; @@ -82,25 +82,16 @@ impl<'a> Adapter<'a> { } } if let Some(node) = new_node.map(|node| ResolvedPlatformNode::new(node)) { - queue.push(QueuedEvent { + queue.push(QueuedEvent::Object { target: node.id(), - kind: EventKind::Object(ObjectEvent::StateChanged( - State::Focused, - true, - )), - }); - queue.push(QueuedEvent { - target: node.id(), - kind: EventKind::Focus, + event: ObjectEvent::StateChanged(State::Focused, true), }); + queue.push(QueuedEvent::Focus(node.id())); } if let Some(node) = old_node.map(|node| ResolvedPlatformNode::new(node)) { - queue.push(QueuedEvent { + queue.push(QueuedEvent::Object { target: node.id(), - kind: EventKind::Object(ObjectEvent::StateChanged( - State::Focused, - false, - )), + event: ObjectEvent::StateChanged(State::Focused, false), }); } } @@ -120,30 +111,26 @@ impl<'a> Adapter<'a> { } fn window_activated(&self, window: &ResolvedPlatformNode, queue: &mut Vec) { - queue.push(QueuedEvent { + queue.push(QueuedEvent::Window { target: window.id(), - kind: EventKind::Window { - window_name: window.name(), - event: WindowEvent::Activated, - }, + name: window.name(), + event: WindowEvent::Activated, }); - queue.push(QueuedEvent { + queue.push(QueuedEvent::Object { target: window.id(), - kind: EventKind::Object(ObjectEvent::StateChanged(State::Active, true)), + event: ObjectEvent::StateChanged(State::Active, true), }); } fn window_deactivated(&self, window: &ResolvedPlatformNode, queue: &mut Vec) { - queue.push(QueuedEvent { + queue.push(QueuedEvent::Window { target: window.id(), - kind: EventKind::Window { - window_name: window.name(), - event: WindowEvent::Deactivated, - }, + name: window.name(), + event: WindowEvent::Deactivated, }); - queue.push(QueuedEvent { + queue.push(QueuedEvent::Object { target: window.id(), - kind: EventKind::Object(ObjectEvent::StateChanged(State::Active, false)), + event: ObjectEvent::StateChanged(State::Active, false), }); } @@ -177,15 +164,14 @@ pub struct QueuedEvents<'a> { impl<'a> QueuedEvents<'a> { pub fn raise(&self) { for event in &self.queue { - let _ = match &event.kind { - EventKind::Focus => self.bus.emit_focus_event(&event.target), - EventKind::Object(data) => self.bus.emit_object_event(&event.target, data), - EventKind::Window { - window_name, - event: window_event, - } => self - .bus - .emit_window_event(&event.target, &window_name, window_event), + let _ = match &event { + QueuedEvent::Focus(target) => self.bus.emit_focus_event(target), + QueuedEvent::Object { target, event } => self.bus.emit_object_event(target, event), + QueuedEvent::Window { + target, + name, + event, + } => self.bus.emit_window_event(target, name, event), }; } } diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 32dd74dc0..8ee33a8b6 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -7,7 +7,7 @@ use crate::atspi::{ interfaces::*, object_address::*, proxies::{BusProxy, SocketProxy}, - ObjectId, + ObjectId, ObjectRef, }; use crate::{PlatformNode, PlatformRootNode, ResolvedPlatformNode}; use std::{ @@ -24,7 +24,7 @@ use zbus::{ names::{BusName, InterfaceName, MemberName}, Address, Result, }; -use zvariant::Value; +use zvariant::{OwnedValue, Str, Value}; #[derive(Clone)] pub(crate) struct Bus<'a> { @@ -121,7 +121,7 @@ impl<'a> Bus<'a> { properties, }, ), - ObjectEvent::PropertyChanged(property, value) => self.emit_event( + ObjectEvent::PropertyChanged(property) => self.emit_event( target, interface, signal, @@ -129,7 +129,26 @@ impl<'a> Bus<'a> { minor: property.as_ref(), detail1: 0, detail2: 0, - any_data: value.clone(), + any_data: match property { + Property::Name(value) => Str::from(value).into(), + Property::Description(value) => Str::from(value).into(), + Property::Parent(Some(ObjectRef::Managed(parent))) => { + OwnedObjectAddress::from(ObjectAddress::accessible( + self.conn.unique_name().unwrap().into(), + parent, + )) + .into() + } + Property::Parent(Some(ObjectRef::Unmanaged(parent))) => { + parent.clone().into() + } + Property::Parent(None) => OwnedObjectAddress::from(ObjectAddress::root( + self.conn.unique_name().unwrap().into(), + )) + .into(), + Property::Role(value) => OwnedValue::from(*value as u32), + } + .into(), properties, }, ), diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index a159b5b78..30448c75f 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -42,7 +42,7 @@ impl AccessibleInterface { fn parent(&self) -> OwnedObjectAddress { match self.node.resolve(|node| node.parent()).ok().flatten() { Some(ObjectRef::Managed(id)) => { - ObjectAddress::accessible(self.bus_name.as_ref(), id).into() + ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() } Some(ObjectRef::Unmanaged(address)) => address, None => ObjectAddress::null(self.bus_name.as_ref()).into(), @@ -76,7 +76,7 @@ impl AccessibleInterface { .map_err(|_| fdo::Error::InvalidArgs("Index can't be negative.".into()))?; self.node.resolve(|node| match node.child_at_index(index) { Some(ObjectRef::Managed(id)) => { - ObjectAddress::accessible(self.bus_name.as_ref(), id).into() + ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() } Some(ObjectRef::Unmanaged(address)) => address, _ => ObjectAddress::null(self.bus_name.as_ref()).into(), @@ -89,7 +89,7 @@ impl AccessibleInterface { .into_iter() .map(|child| match child { ObjectRef::Managed(id) => { - ObjectAddress::accessible(self.bus_name.as_ref(), id).into() + ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() } ObjectRef::Unmanaged(address) => address, }) @@ -169,7 +169,7 @@ impl AccessibleInterface { .tree .upgrade() .map(|tree| { - ObjectAddress::accessible(self.bus_name.as_ref(), tree.read().root().id().into()) + ObjectAddress::accessible(self.bus_name.as_ref(), &tree.read().root().id().into()) .into() }) .ok_or(fdo::Error::UnknownObject("".into())) @@ -182,7 +182,7 @@ impl AccessibleInterface { .map(|tree| { vec![ObjectAddress::accessible( self.bus_name.as_ref(), - tree.read().root().id().into(), + &tree.read().root().id().into(), ) .into()] }) diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index affb1e480..c754aed5e 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -3,40 +3,42 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{ObjectId, State}; +use crate::atspi::{ObjectId, ObjectRef, Role, State}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use strum::AsRefStr; use zvariant::{OwnedValue, Type}; -pub(crate) struct QueuedEvent { - pub target: ObjectId<'static>, - pub kind: EventKind, -} - -pub(crate) enum EventKind { - Focus, - Object(ObjectEvent), +pub(crate) enum QueuedEvent { + Focus(ObjectId<'static>), + Object { + target: ObjectId<'static>, + event: ObjectEvent, + }, Window { - window_name: String, + target: ObjectId<'static>, + name: String, event: WindowEvent, }, } #[derive(AsRefStr)] -#[strum(serialize_all = "kebab-case")] pub(crate) enum Property { - AccessibleName, - AccessibleDescription, - AccessibleParent, - AccessibleRole, + #[strum(serialize = "accessible-name")] + Name(String), + #[strum(serialize = "accessible-description")] + Description(String), + #[strum(serialize = "accessible-parent")] + Parent(Option), + #[strum(serialize = "accessible-role")] + Role(Role), } #[derive(AsRefStr)] pub(crate) enum ObjectEvent { StateChanged(State, bool), #[strum(serialize = "PropertyChange")] - PropertyChanged(Property, OwnedValue), + PropertyChanged(Property), } #[derive(AsRefStr)] diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs index 8ac012177..a7bf02975 100644 --- a/platforms/linux/src/atspi/mod.rs +++ b/platforms/linux/src/atspi/mod.rs @@ -16,6 +16,7 @@ mod state; /// Enumeration used by interface #AtspiAccessible to specify the role /// of an #AtspiAccessible object. +#[repr(u32)] #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, Type)] pub(crate) enum Role { /// A role indicating an error condition, such as diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs index 0dfe1e4f5..6ddbd3d0c 100644 --- a/platforms/linux/src/atspi/object_address.rs +++ b/platforms/linux/src/atspi/object_address.rs @@ -6,7 +6,7 @@ use crate::atspi::ObjectId; use serde::{Deserialize, Serialize}; use zbus::names::{OwnedUniqueName, UniqueName}; -use zvariant::{ObjectPath, OwnedObjectPath, Type, Value}; +use zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type, Value}; pub(crate) const ACCESSIBLE_PATH_PREFIX: &'static str = "/org/a11y/atspi/accessible/"; pub(crate) const NULL_PATH: &'static str = "/org/a11y/atspi/null"; @@ -25,7 +25,7 @@ impl<'a> ObjectAddress<'a> { Self { bus_name, path } } - pub fn accessible(bus_name: UniqueName<'a>, id: ObjectId) -> ObjectAddress<'a> { + pub fn accessible(bus_name: UniqueName<'a>, id: &ObjectId) -> ObjectAddress<'a> { Self { bus_name, path: ObjectPath::from_string_unchecked(format!( @@ -51,7 +51,7 @@ impl<'a> ObjectAddress<'a> { } } -#[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] +#[derive(Clone, Debug, Deserialize, OwnedValue, PartialEq, Serialize, Type, Value)] pub struct OwnedObjectAddress { bus_name: OwnedUniqueName, path: OwnedObjectPath, diff --git a/platforms/linux/src/atspi/object_id.rs b/platforms/linux/src/atspi/object_id.rs index a0a1adf4b..037cf97d8 100644 --- a/platforms/linux/src/atspi/object_id.rs +++ b/platforms/linux/src/atspi/object_id.rs @@ -7,7 +7,7 @@ use accesskit::NodeId; use serde::{Deserialize, Serialize}; use zvariant::{Str, Type, Value}; -#[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Type, Value)] pub struct ObjectId<'a>(#[serde(borrow)] Str<'a>); impl<'a> ObjectId<'a> { diff --git a/platforms/linux/src/atspi/object_ref.rs b/platforms/linux/src/atspi/object_ref.rs index 92fd391f4..8fbb9228f 100644 --- a/platforms/linux/src/atspi/object_ref.rs +++ b/platforms/linux/src/atspi/object_ref.rs @@ -6,6 +6,7 @@ use crate::atspi::{ObjectId, OwnedObjectAddress}; use accesskit::NodeId; +#[derive(Debug, PartialEq)] pub(crate) enum ObjectRef { Managed(ObjectId<'static>), Unmanaged(OwnedObjectAddress), diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index d34f6a136..244bddb77 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use crate::atspi::{ - interfaces::{EventKind, Interface, Interfaces, ObjectEvent, Property, QueuedEvent}, + interfaces::{Interface, Interfaces, ObjectEvent, Property, QueuedEvent}, ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole, State, StateSet, }; use accesskit::{AriaCurrent, CheckedState, InvalidState, Orientation, Role}; @@ -12,7 +12,6 @@ use accesskit_consumer::{Node, Tree, WeakNode}; use parking_lot::RwLock; use std::sync::Weak; use zbus::fdo; -use zvariant::Value; pub(crate) struct ResolvedPlatformNode<'a> { node: Node<'a>, @@ -460,46 +459,49 @@ impl ResolvedPlatformNode<'_> { } pub fn enqueue_changes(&self, queue: &mut Vec, old: &ResolvedPlatformNode) { + self.enqueue_state_changes(queue, old); + self.enqueue_property_changes(queue, old); + } + + fn enqueue_state_changes(&self, queue: &mut Vec, old: &ResolvedPlatformNode) { let old_state = old.state(); let new_state = self.state(); let changed_states = old_state ^ new_state; for state in changed_states.iter() { - queue.push(QueuedEvent { + queue.push(QueuedEvent::Object { target: self.id(), - kind: EventKind::Object(ObjectEvent::StateChanged( - state, - new_state.contains(state), - )), + event: ObjectEvent::StateChanged(state, new_state.contains(state)), }); } + } + + fn enqueue_property_changes(&self, queue: &mut Vec, old: &ResolvedPlatformNode) { let name = self.name(); if name != old.name() { - queue.push(QueuedEvent { + queue.push(QueuedEvent::Object { target: self.id(), - kind: EventKind::Object(ObjectEvent::PropertyChanged( - Property::AccessibleName, - Value::from(name).into(), - )), + event: ObjectEvent::PropertyChanged(Property::Name(name)), }); } let description = self.description(); if description != old.description() { - queue.push(QueuedEvent { + queue.push(QueuedEvent::Object { + target: self.id(), + event: ObjectEvent::PropertyChanged(Property::Description(description)), + }); + } + let parent = self.parent(); + if parent != old.parent() { + queue.push(QueuedEvent::Object { target: self.id(), - kind: EventKind::Object(ObjectEvent::PropertyChanged( - Property::AccessibleDescription, - Value::from(description).into(), - )), + event: ObjectEvent::PropertyChanged(Property::Parent(parent)), }); } let role = self.role(); if role != old.role() { - queue.push(QueuedEvent { + queue.push(QueuedEvent::Object { target: self.id(), - kind: EventKind::Object(ObjectEvent::PropertyChanged( - Property::AccessibleRole, - Value::from(role as u32).into(), - )), + event: ObjectEvent::PropertyChanged(Property::Role(role)), }); } } From 5aa40156f84dc63e7ddc81fd796061ee2fea8f1d Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 21 Aug 2022 20:02:09 +0200 Subject: [PATCH 27/69] Improve D-Bus interfaces management --- platforms/linux/src/adapter.rs | 83 ++++++++++++++++----- platforms/linux/src/atspi/bus.rs | 64 +++++++--------- platforms/linux/src/atspi/interfaces/mod.rs | 8 ++ platforms/linux/src/lib.rs | 2 +- 4 files changed, 100 insertions(+), 57 deletions(-) diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index b0c9a8912..cad3e443a 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -9,8 +9,11 @@ use accesskit::{ActionHandler, Role, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ - interfaces::{ObjectEvent, QueuedEvent, WindowEvent}, - Bus, State, + interfaces::{ + AccessibleInterface, Interface, Interfaces, ObjectEvent, QueuedEvent, ValueInterface, + WindowEvent, + }, + Bus, State, ACCESSIBLE_PATH_PREFIX, }; use crate::node::{AppState, PlatformNode, PlatformRootNode, ResolvedPlatformNode}; use parking_lot::RwLock; @@ -31,8 +34,24 @@ impl<'a> Adapter<'a> { ) -> Option { let mut atspi_bus = Bus::a11y_bus()?; let tree = Tree::new(initial_state, action_handler); + let app_state = Arc::new(RwLock::new(AppState::new( + app_name, + toolkit_name, + toolkit_version, + ))); + atspi_bus + .register_root_node(PlatformRootNode::new( + Arc::downgrade(&app_state), + Arc::downgrade(&tree), + )) + .ok()?; + let adapter = Adapter { + atspi_bus, + _app_state: app_state, + tree, + }; { - let reader = tree.read(); + let reader = adapter.tree.read(); let mut objects_to_add = Vec::new(); fn add_children<'b>(node: Node<'b>, to_add: &mut Vec>) { @@ -45,25 +64,42 @@ impl<'a> Adapter<'a> { objects_to_add.push(ResolvedPlatformNode::new(reader.root())); add_children(reader.root(), &mut objects_to_add); for node in objects_to_add { - atspi_bus.register_node(node).ok()?; + adapter.register_interfaces(&node, node.interfaces()).ok()?; } } - let app_state = Arc::new(RwLock::new(AppState::new( - app_name, - toolkit_name, - toolkit_version, - ))); - atspi_bus - .register_root_node(PlatformRootNode::new( - Arc::downgrade(&app_state), - Arc::downgrade(&tree), - )) - .ok()?; - Some(Self { - atspi_bus, - _app_state: app_state, - tree, - }) + Some(adapter) + } + + fn register_interfaces( + &self, + node: &ResolvedPlatformNode, + new_interfaces: Interfaces, + ) -> zbus::Result { + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, node.id().as_str()); + if new_interfaces.contains(Interface::Accessible) { + self.atspi_bus.register_interface( + &path, + AccessibleInterface::new(self.atspi_bus.unique_name().to_owned(), node.downgrade()), + )?; + } + if new_interfaces.contains(Interface::Value) { + self.atspi_bus + .register_interface(&path, ValueInterface::new(node.downgrade()))?; + } + Ok(true) + } + + fn unregister_interfaces( + &self, + node: &ResolvedPlatformNode, + old_interfaces: Interfaces, + ) -> zbus::Result { + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, node.id().as_str()); + if old_interfaces.contains(Interface::Value) { + self.atspi_bus + .unregister_interface::(&path)?; + } + Ok(true) } pub fn update(&self, update: TreeUpdate) -> QueuedEvents { @@ -98,6 +134,13 @@ impl<'a> Adapter<'a> { TreeChange::NodeUpdated { old_node, new_node } => { let old_node = ResolvedPlatformNode::new(old_node); let new_node = ResolvedPlatformNode::new(new_node); + let old_interfaces = old_node.interfaces(); + let new_interfaces = new_node.interfaces(); + let kept_interfaces = old_interfaces & new_interfaces; + self.unregister_interfaces(&new_node, old_interfaces ^ kept_interfaces) + .unwrap(); + self.register_interfaces(&new_node, new_interfaces ^ kept_interfaces) + .unwrap(); new_node.enqueue_changes(&mut queue, &old_node); } // TODO: handle other events diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 8ee33a8b6..2d3209810 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -9,7 +9,7 @@ use crate::atspi::{ proxies::{BusProxy, SocketProxy}, ObjectId, ObjectRef, }; -use crate::{PlatformNode, PlatformRootNode, ResolvedPlatformNode}; +use crate::PlatformRootNode; use std::{ collections::HashMap, convert::{AsRef, TryInto}, @@ -21,7 +21,7 @@ use x11rb::{ }; use zbus::{ blocking::{Connection, ConnectionBuilder}, - names::{BusName, InterfaceName, MemberName}, + names::{BusName, InterfaceName, MemberName, OwnedUniqueName}, Address, Result, }; use zvariant::{OwnedValue, Str, Value}; @@ -39,28 +39,26 @@ impl<'a> Bus<'a> { Some(Bus { conn, socket_proxy }) } - pub fn register_node(&mut self, node: ResolvedPlatformNode) -> Result { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, node.id().as_str()); - if self.conn.object_server().at( - path.clone(), - AccessibleInterface::new( - self.conn.unique_name().unwrap().to_owned(), - node.downgrade(), - ), - )? { - let interfaces = node.interfaces(); - if interfaces.contains(Interface::Value) { - self.register_value(&path, node.downgrade()) - } else { - Ok(false) - } - } else { - Ok(false) - } + pub fn unique_name(&self) -> &OwnedUniqueName { + self.conn.unique_name().unwrap() + } + + pub fn register_interface(&self, path: &str, interface: T) -> Result + where + T: zbus::Interface, + { + self.conn.object_server().at(path, interface) + } + + pub fn unregister_interface(&self, path: &str) -> Result + where + T: zbus::Interface, + { + self.conn.object_server().remove::(path) } pub fn register_root_node(&mut self, node: PlatformRootNode) -> Result { - println!("Registering on {:?}", self.conn.unique_name().unwrap()); + println!("Registering on {:?}", self.unique_name()); let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, ObjectId::root().as_str()); let registered = self .conn @@ -68,12 +66,12 @@ impl<'a> Bus<'a> { .at(path.clone(), ApplicationInterface(node.clone()))? && self.conn.object_server().at( path, - AccessibleInterface::new(self.conn.unique_name().unwrap().to_owned(), node.clone()), + AccessibleInterface::new(self.unique_name().to_owned(), node.clone()), )?; if registered { - let desktop_address = self.socket_proxy.embed(ObjectAddress::root( - self.conn.unique_name().unwrap().as_ref(), - ))?; + let desktop_address = self + .socket_proxy + .embed(ObjectAddress::root(self.unique_name().as_ref()))?; node.state .upgrade() .map(|state| state.write().desktop_address = Some(desktop_address)); @@ -83,12 +81,6 @@ impl<'a> Bus<'a> { } } - fn register_value(&mut self, path: &str, node: PlatformNode) -> Result { - self.conn - .object_server() - .at(path, ValueInterface::new(node)) - } - pub fn emit_focus_event(&self, target: &ObjectId) -> Result<()> { self.emit_event( target, @@ -134,7 +126,7 @@ impl<'a> Bus<'a> { Property::Description(value) => Str::from(value).into(), Property::Parent(Some(ObjectRef::Managed(parent))) => { OwnedObjectAddress::from(ObjectAddress::accessible( - self.conn.unique_name().unwrap().into(), + self.unique_name().into(), parent, )) .into() @@ -142,10 +134,10 @@ impl<'a> Bus<'a> { Property::Parent(Some(ObjectRef::Unmanaged(parent))) => { parent.clone().into() } - Property::Parent(None) => OwnedObjectAddress::from(ObjectAddress::root( - self.conn.unique_name().unwrap().into(), - )) - .into(), + Property::Parent(None) => { + OwnedObjectAddress::from(ObjectAddress::root(self.unique_name().into())) + .into() + } Property::Role(value) => OwnedValue::from(*value as u32), } .into(), diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index 581ee6420..426be2f51 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -122,6 +122,14 @@ impl From for Interfaces { } } +impl std::ops::BitAnd for Interfaces { + type Output = Interfaces; + + fn bitand(self, other: Self) -> Self::Output { + Interfaces(self.0 & other.0) + } +} + impl std::ops::BitXor for Interfaces { type Output = Interfaces; diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs index 2c015e09a..12422a390 100644 --- a/platforms/linux/src/lib.rs +++ b/platforms/linux/src/lib.rs @@ -11,4 +11,4 @@ mod atspi; mod node; pub use adapter::Adapter; -pub(crate) use node::{PlatformNode, PlatformRootNode, ResolvedPlatformNode}; +pub(crate) use node::{PlatformNode, PlatformRootNode}; From 8daa16142faa0e525c5307deecbd06ca8dfd71c1 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 22 Aug 2022 19:09:03 +0200 Subject: [PATCH 28/69] First try at implementing org.a11y.atspi.Action interface --- platforms/linux/examples/hello_world.rs | 5 +- platforms/linux/src/adapter.rs | 12 ++++- .../linux/src/atspi/interfaces/action.rs | 51 ++++++++++++++++++ platforms/linux/src/atspi/interfaces/mod.rs | 4 ++ platforms/linux/src/node.rs | 53 ++++++++++++++++++- 5 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 platforms/linux/src/atspi/interfaces/action.rs diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index ada288068..0af9e7c19 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -3,7 +3,9 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; +use accesskit::{ + ActionHandler, ActionRequest, DefaultActionVerb, Node, NodeId, Role, Tree, TreeUpdate, +}; use accesskit_linux::Adapter; use std::num::NonZeroU128; use winit::{ @@ -32,6 +34,7 @@ fn get_tree() -> Tree { fn make_button(id: NodeId, name: &str) -> Node { Node { + default_action_verb: Some(DefaultActionVerb::Click), name: Some(name.into()), focusable: true, ..Node::new(id, Role::Button) diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index cad3e443a..9e8fbc16e 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -10,8 +10,8 @@ use accesskit_consumer::{Node, Tree, TreeChange}; use crate::atspi::{ interfaces::{ - AccessibleInterface, Interface, Interfaces, ObjectEvent, QueuedEvent, ValueInterface, - WindowEvent, + AccessibleInterface, ActionInterface, Interface, Interfaces, ObjectEvent, QueuedEvent, + ValueInterface, WindowEvent, }, Bus, State, ACCESSIBLE_PATH_PREFIX, }; @@ -82,6 +82,10 @@ impl<'a> Adapter<'a> { AccessibleInterface::new(self.atspi_bus.unique_name().to_owned(), node.downgrade()), )?; } + if new_interfaces.contains(Interface::Action) { + self.atspi_bus + .register_interface(&path, ActionInterface::new(node.downgrade()))?; + } if new_interfaces.contains(Interface::Value) { self.atspi_bus .register_interface(&path, ValueInterface::new(node.downgrade()))?; @@ -95,6 +99,10 @@ impl<'a> Adapter<'a> { old_interfaces: Interfaces, ) -> zbus::Result { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, node.id().as_str()); + if old_interfaces.contains(Interface::Action) { + self.atspi_bus + .unregister_interface::(&path)?; + } if old_interfaces.contains(Interface::Value) { self.atspi_bus .unregister_interface::(&path)?; diff --git a/platforms/linux/src/atspi/interfaces/action.rs b/platforms/linux/src/atspi/interfaces/action.rs new file mode 100644 index 000000000..ef2d3c1a1 --- /dev/null +++ b/platforms/linux/src/atspi/interfaces/action.rs @@ -0,0 +1,51 @@ +use crate::PlatformNode; +use serde::{Deserialize, Serialize}; +use zbus::{dbus_interface, fdo}; +use zvariant::Type; + +#[derive(Deserialize, Serialize, Type)] +pub(crate) struct Action { + pub localized_name: String, + pub description: String, + pub key_binding: String, +} + +pub(crate) struct ActionInterface(PlatformNode); + +impl ActionInterface { + pub fn new(node: PlatformNode) -> Self { + Self(node) + } +} + +#[dbus_interface(name = "org.a11y.atspi.Action")] +impl ActionInterface { + #[dbus_interface(property)] + fn n_actions(&self) -> i32 { + self.0.resolve(|resolved| resolved.n_actions()).unwrap_or(0) + } + + fn get_description(&self, index: i32) -> &str { + "" + } + + fn get_name(&self, index: i32) -> fdo::Result { + self.0.resolve(|resolved| resolved.get_action_name(index)) + } + + fn get_localized_name(&self, index: i32) -> fdo::Result { + self.0.resolve(|resolved| resolved.get_action_name(index)) + } + + fn get_key_binding(&self, index: i32) -> &str { + "" + } + + fn get_actions(&self) -> fdo::Result> { + self.0.resolve(|resolved| resolved.get_actions()) + } + + fn do_action(&self, index: i32) -> fdo::Result { + self.0.resolve(|resolved| resolved.do_action(index)) + } +} diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index 426be2f51..fb356f361 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -4,6 +4,7 @@ // the LICENSE-MIT file), at your option. mod accessible; +mod action; mod application; mod events; mod value; @@ -22,6 +23,7 @@ use zvariant::{Signature, Type}; pub(crate) enum Interface { Accessible, Application, + Action, Value, } @@ -53,6 +55,7 @@ impl Interfaces { const INTERFACE_NAMES: &[&str] = &[ "org.a11y.atspi.Accessible", "org.a11y.atspi.Application", + "org.a11y.atspi.Action", "org.a11y.atspi.Value", ]; @@ -139,6 +142,7 @@ impl std::ops::BitXor for Interfaces { } pub(crate) use accessible::*; +pub(crate) use action::*; pub(crate) use application::*; pub(crate) use events::*; pub(crate) use value::*; diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 244bddb77..3b2880558 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -4,10 +4,10 @@ // the LICENSE-MIT file), at your option. use crate::atspi::{ - interfaces::{Interface, Interfaces, ObjectEvent, Property, QueuedEvent}, + interfaces::{Action, Interface, Interfaces, ObjectEvent, Property, QueuedEvent}, ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole, State, StateSet, }; -use accesskit::{AriaCurrent, CheckedState, InvalidState, Orientation, Role}; +use accesskit::{AriaCurrent, CheckedState, DefaultActionVerb, InvalidState, Orientation, Role}; use accesskit_consumer::{Node, Tree, WeakNode}; use parking_lot::RwLock; use std::sync::Weak; @@ -432,12 +432,61 @@ impl ResolvedPlatformNode<'_> { pub fn interfaces(&self) -> Interfaces { let mut interfaces = Interfaces::new(Interface::Accessible); + if self.node.default_action_verb().is_some() { + interfaces.insert(Interface::Action); + } if self.node.numeric_value().is_some() { interfaces.insert(Interface::Value); } interfaces } + pub fn n_actions(&self) -> i32 { + match self.node.default_action_verb() { + Some(_) => 1, + None => 0, + } + } + + pub fn get_action_name(&self, index: i32) -> String { + if index != 0 { + return String::new(); + } + String::from(match self.node.default_action_verb() { + Some(DefaultActionVerb::Click) => "click", + Some(DefaultActionVerb::Focus) => "focus", + Some(DefaultActionVerb::Check) => "check", + Some(DefaultActionVerb::Uncheck) => "uncheck", + Some(DefaultActionVerb::ClickAncestor) => "clickAncestor", + Some(DefaultActionVerb::Jump) => "jump", + Some(DefaultActionVerb::Open) => "open", + Some(DefaultActionVerb::Press) => "press", + Some(DefaultActionVerb::Select) => "select", + None => "", + }) + } + + pub fn get_actions(&self) -> Vec { + let n_actions = self.n_actions() as usize; + let mut actions = Vec::with_capacity(n_actions); + for i in 0..n_actions { + actions.push(Action { + localized_name: self.get_action_name(i as i32), + description: "".into(), + key_binding: "".into(), + }); + } + actions + } + + pub fn do_action(&self, index: i32) -> bool { + if index != 0 { + return false; + } + self.node.do_default_action(); + true + } + pub fn minimum_value(&self) -> f64 { self.node.min_numeric_value().unwrap_or(std::f64::MIN) } From 95c59533802fbb1ed979bf6c5a800e71cdd37b53 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 22 Aug 2022 22:58:03 +0200 Subject: [PATCH 29/69] Fix regressions in org.a11y.atspi.Accessible, add GetApplication while at it --- .../linux/src/atspi/interfaces/accessible.rs | 95 +++++++++++-------- 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 30448c75f..1fc7d6b20 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -27,65 +27,69 @@ impl AccessibleInterface { #[dbus_interface(property)] fn name(&self) -> String { self.node - .resolve(|node| node.name()) - .unwrap_or(String::new()) + .resolve(|resolved| resolved.name()) + .unwrap_or_else(|_| String::new()) } #[dbus_interface(property)] fn description(&self) -> String { self.node - .resolve(|node| node.description()) - .unwrap_or(String::new()) + .resolve(|resolved| resolved.description()) + .unwrap_or_else(|_| String::new()) } #[dbus_interface(property)] fn parent(&self) -> OwnedObjectAddress { - match self.node.resolve(|node| node.parent()).ok().flatten() { - Some(ObjectRef::Managed(id)) => { - ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() - } - Some(ObjectRef::Unmanaged(address)) => address, - None => ObjectAddress::null(self.bus_name.as_ref()).into(), - } + self.node + .resolve(|resolved| match resolved.parent() { + Some(ObjectRef::Managed(id)) => { + ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() + } + Some(ObjectRef::Unmanaged(address)) => address, + _ => ObjectAddress::root(self.bus_name.as_ref()).into(), + }) + .unwrap_or_else(|_| ObjectAddress::null(self.bus_name.as_ref()).into()) } #[dbus_interface(property)] fn child_count(&self) -> i32 { self.node - .resolve(|node| node.child_count()) + .resolve(|resolved| resolved.child_count()) .map_or(0, |count| count.try_into().unwrap_or(0)) } #[dbus_interface(property)] fn locale(&self) -> String { self.node - .resolve(|node| node.locale()) - .unwrap_or(String::new()) + .resolve(|resolved| resolved.locale()) + .unwrap_or_else(|_| String::new()) } #[dbus_interface(property)] fn accessible_id(&self) -> ObjectId { self.node - .resolve(|node| node.id()) - .unwrap_or(unsafe { ObjectId::from_str_unchecked("") }) + .resolve(|resolved| resolved.id()) + .unwrap_or_else(|_| unsafe { ObjectId::from_str_unchecked("") }) } - fn get_child_at_index(&self, index: i32) -> fdo::Result { + fn get_child_at_index(&self, index: i32) -> fdo::Result<(OwnedObjectAddress,)> { let index = index .try_into() .map_err(|_| fdo::Error::InvalidArgs("Index can't be negative.".into()))?; - self.node.resolve(|node| match node.child_at_index(index) { - Some(ObjectRef::Managed(id)) => { - ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() - } - Some(ObjectRef::Unmanaged(address)) => address, - _ => ObjectAddress::null(self.bus_name.as_ref()).into(), - }) + self.node + .resolve(|resolved| match resolved.child_at_index(index) { + Some(ObjectRef::Managed(id)) => { + (ObjectAddress::accessible(self.bus_name.as_ref(), &id).into(),) + } + Some(ObjectRef::Unmanaged(address)) => (address,), + _ => (ObjectAddress::null(self.bus_name.as_ref()).into(),), + }) } fn get_children(&self) -> fdo::Result> { - self.node.resolve(|node| { - node.children() + self.node.resolve(|resolved| { + resolved + .children() .into_iter() .map(|child| match child { ObjectRef::Managed(id) => { @@ -98,7 +102,7 @@ impl AccessibleInterface { } fn get_index_in_parent(&self) -> fdo::Result { - let index = self.node.resolve(|node| node.index_in_parent())?; + let index = self.node.resolve(|resolved| resolved.index_in_parent())?; if let Some(index) = index { index .try_into() @@ -109,15 +113,19 @@ impl AccessibleInterface { } fn get_role(&self) -> fdo::Result { - self.node.resolve(|node| node.role()) + self.node.resolve(|resolved| resolved.role()) } fn get_state(&self) -> fdo::Result { - self.node.resolve(|node| node.state()) + self.node.resolve(|resolved| resolved.state()) + } + + fn get_application(&self) -> (OwnedObjectAddress,) { + (ObjectAddress::root(self.bus_name.as_ref()).into(),) } fn get_interfaces(&self) -> fdo::Result { - self.node.resolve(|node| node.interfaces()) + self.node.resolve(|resolved| resolved.interfaces()) } } @@ -129,7 +137,7 @@ impl AccessibleInterface { .state .upgrade() .map(|state| state.read().name.clone()) - .unwrap_or(String::new()) + .unwrap_or_else(|| String::new()) } #[dbus_interface(property)] @@ -152,8 +160,8 @@ impl AccessibleInterface { } #[dbus_interface(property)] - fn locale(&self) -> String { - String::new() + fn locale(&self) -> &str { + "" } #[dbus_interface(property)] @@ -161,18 +169,23 @@ impl AccessibleInterface { ObjectId::root() } - fn get_child_at_index(&self, index: i32) -> fdo::Result { + fn get_child_at_index(&self, index: i32) -> fdo::Result<(OwnedObjectAddress,)> { if index != 0 { - return Ok(ObjectAddress::null(self.bus_name.as_ref()).into()); + return Ok((ObjectAddress::null(self.bus_name.as_ref()).into(),)); } self.node .tree .upgrade() .map(|tree| { - ObjectAddress::accessible(self.bus_name.as_ref(), &tree.read().root().id().into()) - .into() + ( + ObjectAddress::accessible( + self.bus_name.as_ref(), + &tree.read().root().id().into(), + ) + .into(), + ) }) - .ok_or(fdo::Error::UnknownObject("".into())) + .ok_or_else(|| fdo::Error::UnknownObject("".into())) } fn get_children(&self) -> fdo::Result> { @@ -186,7 +199,7 @@ impl AccessibleInterface { ) .into()] }) - .ok_or(fdo::Error::UnknownObject("".into())) + .ok_or_else(|| fdo::Error::UnknownObject("".into())) } fn get_index_in_parent(&self) -> i32 { @@ -201,6 +214,10 @@ impl AccessibleInterface { StateSet::empty() } + fn get_application(&self) -> (OwnedObjectAddress,) { + (ObjectAddress::root(self.bus_name.as_ref()).into(),) + } + fn get_interfaces(&self) -> Interfaces { Interfaces::new(Interface::Accessible | Interface::Application) } From 44c7511b81620c3ea3b266116ab742a1e3098460 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 22 Aug 2022 23:00:02 +0200 Subject: [PATCH 30/69] Get rid of Drop for Bus to avoid accidentally unembedding, AT-SPI seems to not need it anyway --- platforms/linux/src/atspi/bus.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 2d3209810..0e4926a57 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -185,14 +185,6 @@ impl<'a> Bus<'a> { } } -impl Drop for Bus<'_> { - fn drop(&mut self) { - let _ = self.socket_proxy.unembed(ObjectAddress::root( - self.conn.unique_name().unwrap().as_ref(), - )); - } -} - fn spi_display_name() -> Option { var("AT_SPI_DISPLAY").ok().or(match var("DISPLAY") { Ok(mut display_env) if display_env.len() > 0 => { From 4a54c2f85f3203eab63c115e1c97f9c51001af2a Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 28 Aug 2022 10:54:38 +0200 Subject: [PATCH 31/69] Actually implement Deserialize for StateSet, test the whole module --- Cargo.lock | 2 +- platforms/linux/Cargo.toml | 1 + platforms/linux/src/atspi/state.rs | 122 +++++++++++++++++++++++++---- 3 files changed, 110 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dce6e95ee..feaf841e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1481,4 +1481,4 @@ dependencies = [ name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" \ No newline at end of file diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index f25b6c984..92be61e9d 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -19,4 +19,5 @@ zbus = "2.3.2" zvariant = "3.3.1" [dev-dependencies] +byteorder = "1.4.3" winit = "0.26.1" diff --git a/platforms/linux/src/atspi/state.rs b/platforms/linux/src/atspi/state.rs index 7b14189ae..8766ef52e 100644 --- a/platforms/linux/src/atspi/state.rs +++ b/platforms/linux/src/atspi/state.rs @@ -5,7 +5,7 @@ use enumflags2::{bitflags, BitFlag, BitFlags, FromBitsError}; use serde::{ - de::{self, Deserialize, Deserializer, SeqAccess, Visitor}, + de::{self, Deserialize, Deserializer, Visitor}, ser::{Serialize, SerializeSeq, Serializer}, }; use std::fmt; @@ -224,10 +224,14 @@ pub(crate) enum State { LastDefined, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub(crate) struct StateSet(BitFlags); impl StateSet { + pub fn new>>(value: B) -> Self { + Self(value.into()) + } + pub fn from_bits(bits: u64) -> Result> { Ok(StateSet(BitFlags::from_bits(bits)?)) } @@ -268,24 +272,23 @@ impl<'de> Deserialize<'de> for StateSet { .write_str("a sequence comprised of two u32 that represents a valid StateSet") } - fn visit_seq(self, mut seq: A) -> Result + fn visit_newtype_struct(self, deserializer: D) -> Result where - A: SeqAccess<'de>, + D: Deserializer<'de>, { - match SeqAccess::next_element::>(&mut seq)? { - Some(vec) => { - let len = vec.len(); - if len != 2 { - return Err(de::Error::invalid_length(len, &"Vec with two elements")); - } - Ok(StateSet::from_bits(0).unwrap()) + match as Deserialize>::deserialize(deserializer) { + Ok(states) if states.len() == 2 => { + let mut bits = states[0] as u64; + bits |= (states[1] as u64) << 32; + StateSet::from_bits(bits).map_err(|_| de::Error::custom("invalid state")) } - None => Err(de::Error::custom("Vec with two elements")), + Ok(states) => Err(de::Error::invalid_length(states.len(), &"array of size 2")), + Err(e) => Err(e), } } } - deserializer.deserialize_seq(StateSetVisitor) + deserializer.deserialize_newtype_struct("StateSet", StateSetVisitor) } } @@ -304,7 +307,7 @@ impl Serialize for StateSet { impl Type for StateSet { fn signature() -> Signature<'static> { - Signature::from_str_unchecked("au") + as Type>::signature() } } @@ -321,3 +324,94 @@ impl std::ops::BitXor for StateSet { StateSet(self.0 ^ other.0) } } + +#[cfg(test)] +mod tests { + use super::*; + use byteorder::LE; + use zvariant::{from_slice, to_bytes, EncodingContext as Context}; + + #[test] + fn serialize_empty_state_set() { + let ctxt = Context::::new_dbus(0); + let encoded = to_bytes(ctxt, &StateSet::empty()).unwrap(); + assert_eq!(encoded, &[8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + } + + #[test] + fn deserialize_empty_state_set() { + let ctxt = Context::::new_dbus(0); + let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ctxt).unwrap(); + assert_eq!(decoded, StateSet::empty()); + } + + #[test] + fn serialize_state_set_invalid() { + let ctxt = Context::::new_dbus(0); + let encoded = to_bytes(ctxt, &StateSet::new(State::Invalid)).unwrap(); + assert_eq!(encoded, &[8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]); + } + + #[test] + fn deserialize_state_set_invalid() { + let ctxt = Context::::new_dbus(0); + let decoded: StateSet = from_slice(&[8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], ctxt).unwrap(); + assert_eq!(decoded, StateSet::new(State::Invalid)); + } + + #[test] + fn serialize_state_set_manages_descendants() { + let ctxt = Context::::new_dbus(0); + let encoded = to_bytes(ctxt, &StateSet::new(State::ManagesDescendants)).unwrap(); + assert_eq!(encoded, &[8, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0]); + } + + #[test] + fn deserialize_state_set_manages_descendants() { + let ctxt = Context::::new_dbus(0); + let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0], ctxt).unwrap(); + assert_eq!(decoded, StateSet::new(State::ManagesDescendants)); + } + + #[test] + fn serialize_state_set_indeterminate() { + let ctxt = Context::::new_dbus(0); + let encoded = to_bytes(ctxt, &StateSet::new(State::Indeterminate)).unwrap(); + assert_eq!(encoded, &[8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]); + } + + #[test] + fn deserialize_state_set_indeterminate() { + let ctxt = Context::::new_dbus(0); + let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], ctxt).unwrap(); + assert_eq!(decoded, StateSet::new(State::Indeterminate)); + } + + #[test] + fn serialize_state_set_focusable_focused() { + let ctxt = Context::::new_dbus(0); + let encoded = to_bytes(ctxt, &StateSet::new(State::Focusable | State::Focused)).unwrap(); + assert_eq!(encoded, &[8, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0]); + } + + #[test] + fn deserialize_state_set_focusable_focused() { + let ctxt = Context::::new_dbus(0); + let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0], ctxt).unwrap(); + assert_eq!(decoded, StateSet::new(State::Focusable | State::Focused)); + } + + #[test] + fn cannot_deserialize_state_set_invalid_length() { + let ctxt = Context::::new_dbus(0); + let decoded = from_slice::<_, StateSet>(&[4, 0, 0, 0, 0, 0, 0, 0], ctxt); + assert!(decoded.is_err()); + } + + #[test] + fn cannot_deserialize_state_set_invalid_flag() { + let ctxt = Context::::new_dbus(0); + let decoded = from_slice::<_, StateSet>(&[8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32], ctxt); + assert!(decoded.is_err()); + } +} From 0966b96abf811519d0a2fba7d0e1dc15da11cbfc Mon Sep 17 00:00:00 2001 From: DataTriny Date: Tue, 30 Aug 2022 22:40:03 +0200 Subject: [PATCH 32/69] Remove officially deprecated org.a11y.atspi.Event.Focus --- platforms/linux/src/adapter.rs | 2 -- platforms/linux/src/atspi/bus.rs | 15 --------------- platforms/linux/src/atspi/interfaces/events.rs | 1 - 3 files changed, 18 deletions(-) diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 9e8fbc16e..3cef94f63 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -130,7 +130,6 @@ impl<'a> Adapter<'a> { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, true), }); - queue.push(QueuedEvent::Focus(node.id())); } if let Some(node) = old_node.map(|node| ResolvedPlatformNode::new(node)) { queue.push(QueuedEvent::Object { @@ -216,7 +215,6 @@ impl<'a> QueuedEvents<'a> { pub fn raise(&self) { for event in &self.queue { let _ = match &event { - QueuedEvent::Focus(target) => self.bus.emit_focus_event(target), QueuedEvent::Object { target, event } => self.bus.emit_object_event(target, event), QueuedEvent::Window { target, diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index 0e4926a57..ec7175c0f 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -81,21 +81,6 @@ impl<'a> Bus<'a> { } } - pub fn emit_focus_event(&self, target: &ObjectId) -> Result<()> { - self.emit_event( - target, - "org.a11y.atspi.Event.Focus", - "Focus", - EventData { - minor: "", - detail1: 0, - detail2: 0, - any_data: 0i32.into(), - properties: HashMap::new(), - }, - ) - } - pub fn emit_object_event(&self, target: &ObjectId, event: &ObjectEvent) -> Result<()> { let interface = "org.a11y.atspi.Event.Object"; let signal = event.as_ref(); diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index c754aed5e..9b3262f09 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -10,7 +10,6 @@ use strum::AsRefStr; use zvariant::{OwnedValue, Type}; pub(crate) enum QueuedEvent { - Focus(ObjectId<'static>), Object { target: ObjectId<'static>, event: ObjectEvent, From 90923b450f4fb14eef21416aeea1015f053e6bcf Mon Sep 17 00:00:00 2001 From: DataTriny Date: Tue, 30 Aug 2022 23:09:31 +0200 Subject: [PATCH 33/69] Simplify how we obtain AT-SPI bus address, drop reference to x11rb --- platforms/linux/Cargo.toml | 1 - platforms/linux/src/atspi/bus.rs | 61 ++++---------------------------- 2 files changed, 7 insertions(+), 55 deletions(-) diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index 92be61e9d..0318e2ba3 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -14,7 +14,6 @@ lazy_static = "1.4.0" parking_lot = "0.11.1" serde = "1.0" strum = { version = "0.23.0", features = ["derive"] } -x11rb = "0.8.1" zbus = "2.3.2" zvariant = "3.3.1" diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index ec7175c0f..e551edb23 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -15,10 +15,6 @@ use std::{ convert::{AsRef, TryInto}, env::var, }; -use x11rb::{ - connection::Connection as _, - protocol::xproto::{AtomEnum, ConnectionExt}, -}; use zbus::{ blocking::{Connection, ConnectionBuilder}, names::{BusName, InterfaceName, MemberName, OwnedUniqueName}, @@ -170,57 +166,14 @@ impl<'a> Bus<'a> { } } -fn spi_display_name() -> Option { - var("AT_SPI_DISPLAY").ok().or(match var("DISPLAY") { - Ok(mut display_env) if display_env.len() > 0 => { - match (display_env.rfind(':'), display_env.rfind('.')) { - (Some(i), Some(j)) if j > i => { - display_env.truncate(j); - Some(display_env) - } - _ => Some(display_env), - } - } - _ => None, - }) -} - -fn a11y_bus_address_from_x11() -> Option { - let (bridge_display, screen_num) = x11rb::connect(Some(&spi_display_name()?)).ok()?; - let root_window = &bridge_display.setup().roots[screen_num].root; - let reply = bridge_display.intern_atom(false, b"AT_SPI_BUS").ok()?; - let at_spi_bus = reply.reply().ok()?; - let address = bridge_display - .get_property( - false, - *root_window, - at_spi_bus.atom, - AtomEnum::STRING, - 0, - 1024, - ) - .ok()? - .reply() - .map_or(None, |data| String::from_utf8(data.value).ok()); - address -} - -fn a11y_bus_address_from_dbus() -> Option { - let session_bus = Connection::session().ok()?; - BusProxy::new(&session_bus).ok()?.get_address().ok() -} - fn a11y_bus() -> Option { - let mut address = match var("AT_SPI_BUS_ADDRESS") { - Ok(address) if address.len() > 0 => Some(address), - _ => None, + let address = match var("AT_SPI_BUS_ADDRESS") { + Ok(address) if address.len() > 0 => address, + _ => { + let session_bus = Connection::session().ok()?; + BusProxy::new(&session_bus).ok()?.get_address().ok()? + } }; - if address.is_none() && var("WAYLAND_DISPLAY").is_err() { - address = a11y_bus_address_from_x11(); - } - if address.is_none() { - address = a11y_bus_address_from_dbus(); - } - let address: Address = address?.as_str().try_into().ok()?; + let address: Address = address.as_str().try_into().ok()?; ConnectionBuilder::address(address).ok()?.build().ok() } From aec46ab9794725ba18c31cd3066db7f69b964d06 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Tue, 30 Aug 2022 23:33:46 +0200 Subject: [PATCH 34/69] Update deps --- platforms/linux/Cargo.toml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index 0318e2ba3..b0a783245 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -8,15 +8,13 @@ edition = "2018" [dependencies] accesskit = { version = "0.4.0", path = "../../common" } accesskit_consumer = { version = "0.4.0", path = "../../consumer" } -async-io = "1.4.1" enumflags2 = "0.7.1" -lazy_static = "1.4.0" parking_lot = "0.11.1" serde = "1.0" strum = { version = "0.23.0", features = ["derive"] } -zbus = "2.3.2" -zvariant = "3.3.1" +zbus = "3.0.0" +zvariant = "3.6.0" [dev-dependencies] byteorder = "1.4.3" -winit = "0.26.1" +winit = "0.27.2" From 19551a447d6fecc4b31f82a2dbad1a2f660d1ed3 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 13 Nov 2022 10:29:37 +0100 Subject: [PATCH 35/69] Rebase --- common/src/lib.rs | 33 -- platforms/linux/Cargo.toml | 6 +- platforms/linux/examples/hello_world.rs | 36 +- platforms/linux/src/adapter.rs | 160 +++--- .../linux/src/atspi/interfaces/accessible.rs | 88 ++-- .../linux/src/atspi/interfaces/action.rs | 10 +- platforms/linux/src/atspi/interfaces/value.rs | 20 +- platforms/linux/src/node.rs | 456 +++++++++++------- 8 files changed, 453 insertions(+), 356 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index ce52205fa..1a9da492b 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -250,39 +250,6 @@ impl Default for Role { } } -impl Role { - pub fn is_read_only_supported(&self) -> bool { - // https://www.w3.org/TR/wai-aria-1.1/#aria-readonly - // Roles that support aria-readonly - match self { - Role::CheckBox | Role::ColorWell | Role::ComboBoxGrouping | Role::ComboBoxMenuButton | Role::Date | Role::DateTime | Role::Grid | Role::InputTime | Role::ListBox | Role::MenuItemCheckBox | Role::MenuItemRadio | Role::MenuListPopup | Role::PopupButton | Role::RadioButton | Role::RadioGroup | Role::SearchBox | Role::Slider | Role::SpinButton | Role::Switch | Role::TextField | Role::TextFieldWithComboBox | Role::ToggleButton | Role::TreeGrid => true, - // https://www.w3.org/TR/wai-aria-1.1/#aria-readonly - // ARIA-1.1+ 'gridcell', supports aria-readonly, but 'cell' does not. - // - // https://www.w3.org/TR/wai-aria-1.1/#columnheader - // https://www.w3.org/TR/wai-aria-1.1/#rowheader - // While the [columnheader|rowheader] role can be used in both interactive - // grids and non-interactive tables, the use of aria-readonly and - // aria-required is only applicable to interactive elements. - // Therefore, [...] user agents SHOULD NOT expose either property to - // assistive technologies unless the columnheader descends from a grid. - Role::Cell | Role::RowHeader | Role::ColumnHeader => false, - _ => false - } - } - - pub fn should_have_read_only_state_by_default(&self) -> bool { - match self { - Role::Article | Role::Definition | Role::DescriptionList | Role::DescriptionListTerm | Role::Directory | Role::Document | Role::GraphicsDocument | Role::Image | Role::List | Role::ListItem | Role::PdfRoot | Role::ProgressIndicator | Role::RootWebArea | Role::Term | Role::Timer | Role::Toolbar | Role::Tooltip => true, - // TODO(aleventhal) this changed between ARIA 1.0 and 1.1. - // We need to determine whether grids/treegrids should really be readonly - // or editable by default. - Role::Grid => false, - _ => false - } - } -} - /// An action to be taken on an accessibility node. /// /// In contrast to [`DefaultActionVerb`], these describe what happens to the diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index b0a783245..33c00104d 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -3,11 +3,9 @@ name = "accesskit_linux" version = "0.1.0" edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -accesskit = { version = "0.4.0", path = "../../common" } -accesskit_consumer = { version = "0.4.0", path = "../../consumer" } +accesskit = { version = "0.7.0", path = "../../common" } +accesskit_consumer = { version = "0.7.1", path = "../../consumer" } enumflags2 = "0.7.1" parking_lot = "0.11.1" serde = "1.0" diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs index 0af9e7c19..d0f111bbc 100644 --- a/platforms/linux/examples/hello_world.rs +++ b/platforms/linux/examples/hello_world.rs @@ -7,7 +7,7 @@ use accesskit::{ ActionHandler, ActionRequest, DefaultActionVerb, Node, NodeId, Role, Tree, TreeUpdate, }; use accesskit_linux::Adapter; -use std::num::NonZeroU128; +use std::{num::NonZeroU128, sync::Arc}; use winit::{ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -32,25 +32,31 @@ fn get_tree() -> Tree { } } -fn make_button(id: NodeId, name: &str) -> Node { - Node { - default_action_verb: Some(DefaultActionVerb::Click), +fn make_button(name: &str) -> Arc { + Arc::new(Node { + role: Role::Button, name: Some(name.into()), focusable: true, - ..Node::new(id, Role::Button) - } + default_action_verb: Some(DefaultActionVerb::Click), + ..Default::default() + }) } fn get_initial_state() -> TreeUpdate { - let root = Node { + let root = Arc::new(Node { + role: Role::Window, children: vec![BUTTON_1_ID, BUTTON_2_ID], name: Some(WINDOW_TITLE.into()), - ..Node::new(WINDOW_ID, Role::Window) - }; - let button_1 = make_button(BUTTON_1_ID, "Button 1"); - let button_2 = make_button(BUTTON_2_ID, "Button 2"); + ..Default::default() + }); + let button_1 = make_button("Button 1"); + let button_2 = make_button("Button 2"); TreeUpdate { - nodes: vec![root, button_1, button_2], + nodes: vec![ + (WINDOW_ID, root), + (BUTTON_1_ID, button_1), + (BUTTON_2_ID, button_2), + ], tree: Some(get_tree()), focus: None, } @@ -114,16 +120,16 @@ fn main() { WindowEvent::KeyboardInput { input: KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Return), + virtual_keycode: Some(VirtualKeyCode::Space), state: ElementState::Released, .. }, .. } => unsafe { let updated_node = if FOCUS == BUTTON_1_ID { - make_button(BUTTON_1_ID, "You pressed button 1") + (BUTTON_1_ID, make_button("You pressed button 1")) } else { - make_button(BUTTON_2_ID, "You pressed button 2") + (BUTTON_2_ID, make_button("You pressed button 2")) }; adapter .update(TreeUpdate { diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 3cef94f63..a7d5d75f2 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -5,17 +5,17 @@ use std::sync::Arc; -use accesskit::{ActionHandler, Role, TreeUpdate}; -use accesskit_consumer::{Node, Tree, TreeChange}; +use accesskit::{ActionHandler, NodeId, Role, TreeUpdate}; +use accesskit_consumer::{Node, Tree, TreeChangeHandler}; use crate::atspi::{ interfaces::{ AccessibleInterface, ActionInterface, Interface, Interfaces, ObjectEvent, QueuedEvent, ValueInterface, WindowEvent, }, - Bus, State, ACCESSIBLE_PATH_PREFIX, + Bus, ObjectId, State, ACCESSIBLE_PATH_PREFIX, }; -use crate::node::{AppState, PlatformNode, PlatformRootNode, ResolvedPlatformNode}; +use crate::node::{filter, AppState, NodeWrapper, PlatformNode, PlatformRootNode}; use parking_lot::RwLock; pub struct Adapter<'a> { @@ -33,7 +33,7 @@ impl<'a> Adapter<'a> { action_handler: Box, ) -> Option { let mut atspi_bus = Bus::a11y_bus()?; - let tree = Tree::new(initial_state, action_handler); + let tree = Arc::new(Tree::new(initial_state, action_handler)); let app_state = Arc::new(RwLock::new(AppState::new( app_name, toolkit_name, @@ -54,17 +54,20 @@ impl<'a> Adapter<'a> { let reader = adapter.tree.read(); let mut objects_to_add = Vec::new(); - fn add_children<'b>(node: Node<'b>, to_add: &mut Vec>) { - for child in node.unignored_children() { - to_add.push(ResolvedPlatformNode::new(child)); + fn add_children<'b>(node: Node<'b>, to_add: &mut Vec) { + for child in node.filtered_children(&filter) { + to_add.push(child.id()); add_children(child, to_add); } } - objects_to_add.push(ResolvedPlatformNode::new(reader.root())); + objects_to_add.push(reader.root().id()); add_children(reader.root(), &mut objects_to_add); - for node in objects_to_add { - adapter.register_interfaces(&node, node.interfaces()).ok()?; + for id in objects_to_add { + let interfaces = NodeWrapper::new(&reader.node_by_id(id).unwrap()).interfaces(); + adapter + .register_interfaces(&adapter.tree, id, interfaces) + .ok()?; } } Some(adapter) @@ -72,33 +75,42 @@ impl<'a> Adapter<'a> { fn register_interfaces( &self, - node: &ResolvedPlatformNode, + tree: &Arc, + id: NodeId, new_interfaces: Interfaces, ) -> zbus::Result { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, node.id().as_str()); + let atspi_id = ObjectId::from(id); + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, atspi_id.as_str()); if new_interfaces.contains(Interface::Accessible) { self.atspi_bus.register_interface( &path, - AccessibleInterface::new(self.atspi_bus.unique_name().to_owned(), node.downgrade()), + AccessibleInterface::new( + self.atspi_bus.unique_name().to_owned(), + PlatformNode::new(tree, id), + ), )?; } if new_interfaces.contains(Interface::Action) { self.atspi_bus - .register_interface(&path, ActionInterface::new(node.downgrade()))?; + .register_interface(&path, ActionInterface::new(PlatformNode::new(tree, id)))?; } if new_interfaces.contains(Interface::Value) { self.atspi_bus - .register_interface(&path, ValueInterface::new(node.downgrade()))?; + .register_interface(&path, ValueInterface::new(PlatformNode::new(tree, id)))?; } Ok(true) } fn unregister_interfaces( &self, - node: &ResolvedPlatformNode, + id: &ObjectId, old_interfaces: Interfaces, ) -> zbus::Result { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, node.id().as_str()); + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str()); + if old_interfaces.contains(Interface::Accessible) { + self.atspi_bus + .unregister_interface::>(&path)?; + } if old_interfaces.contains(Interface::Action) { self.atspi_bus .unregister_interface::(&path)?; @@ -111,56 +123,78 @@ impl<'a> Adapter<'a> { } pub fn update(&self, update: TreeUpdate) -> QueuedEvents { - let mut queue = Vec::new(); - self.tree.update_and_process_changes(update, |change| { - match change { - TreeChange::FocusMoved { old_node, new_node } => { - let old_window = old_node.and_then(|node| containing_window(node)); - let new_window = new_node.and_then(|node| containing_window(node)); - if old_window.map(|n| n.id()) != new_window.map(|n| n.id()) { - if let Some(window) = old_window { - self.window_deactivated(&ResolvedPlatformNode::new(window), &mut queue); - } - if let Some(window) = new_window { - self.window_activated(&ResolvedPlatformNode::new(window), &mut queue); - } - } - if let Some(node) = new_node.map(|node| ResolvedPlatformNode::new(node)) { - queue.push(QueuedEvent::Object { - target: node.id(), - event: ObjectEvent::StateChanged(State::Focused, true), - }); + struct Handler<'a> { + adapter: &'a Adapter<'a>, + tree: &'a Arc, + queue: Vec, + } + impl TreeChangeHandler for Handler<'_> { + fn node_added(&mut self, node: &Node) { + let interfaces = NodeWrapper::new(node).interfaces(); + self.adapter + .register_interfaces(self.tree, node.id(), interfaces) + .unwrap(); + } + fn node_updated(&mut self, old_node: &Node, new_node: &Node) { + let old_wrapper = NodeWrapper::new(old_node); + let new_wrapper = NodeWrapper::new(new_node); + let old_interfaces = old_wrapper.interfaces(); + let new_interfaces = new_wrapper.interfaces(); + let kept_interfaces = old_interfaces & new_interfaces; + self.adapter + .unregister_interfaces(&new_wrapper.id(), old_interfaces ^ kept_interfaces) + .unwrap(); + self.adapter + .register_interfaces(self.tree, new_node.id(), new_interfaces ^ kept_interfaces) + .unwrap(); + new_wrapper.enqueue_changes(&mut self.queue, &old_wrapper); + } + fn focus_moved(&mut self, old_node: Option<&Node>, new_node: Option<&Node>) { + let old_window = old_node.and_then(|node| containing_window(*node)); + let new_window = new_node.and_then(|node| containing_window(*node)); + if old_window.map(|n| n.id()) != new_window.map(|n| n.id()) { + if let Some(window) = old_window { + self.adapter + .window_deactivated(&NodeWrapper::new(&window), &mut self.queue); } - if let Some(node) = old_node.map(|node| ResolvedPlatformNode::new(node)) { - queue.push(QueuedEvent::Object { - target: node.id(), - event: ObjectEvent::StateChanged(State::Focused, false), - }); + if let Some(window) = new_window { + self.adapter + .window_activated(&NodeWrapper::new(&window), &mut self.queue); } } - TreeChange::NodeUpdated { old_node, new_node } => { - let old_node = ResolvedPlatformNode::new(old_node); - let new_node = ResolvedPlatformNode::new(new_node); - let old_interfaces = old_node.interfaces(); - let new_interfaces = new_node.interfaces(); - let kept_interfaces = old_interfaces & new_interfaces; - self.unregister_interfaces(&new_node, old_interfaces ^ kept_interfaces) - .unwrap(); - self.register_interfaces(&new_node, new_interfaces ^ kept_interfaces) - .unwrap(); - new_node.enqueue_changes(&mut queue, &old_node); + if let Some(node) = new_node.map(|node| NodeWrapper::new(node)) { + self.queue.push(QueuedEvent::Object { + target: node.id(), + event: ObjectEvent::StateChanged(State::Focused, true), + }); } - // TODO: handle other events - _ => (), - }; - }); + if let Some(node) = old_node.map(|node| NodeWrapper::new(node)) { + self.queue.push(QueuedEvent::Object { + target: node.id(), + event: ObjectEvent::StateChanged(State::Focused, false), + }); + } + } + fn node_removed(&mut self, node: &Node) { + let node = NodeWrapper::new(node); + self.adapter + .unregister_interfaces(&node.id(), node.interfaces()) + .unwrap(); + } + } + let mut handler = Handler { + adapter: self, + tree: &self.tree, + queue: Vec::new(), + }; + self.tree.update_and_process_changes(update, &mut handler); QueuedEvents { bus: self.atspi_bus.clone(), - queue, + queue: handler.queue, } } - fn window_activated(&self, window: &ResolvedPlatformNode, queue: &mut Vec) { + fn window_activated(&self, window: &NodeWrapper, queue: &mut Vec) { queue.push(QueuedEvent::Window { target: window.id(), name: window.name(), @@ -172,7 +206,7 @@ impl<'a> Adapter<'a> { }); } - fn window_deactivated(&self, window: &ResolvedPlatformNode, queue: &mut Vec) { + fn window_deactivated(&self, window: &NodeWrapper, queue: &mut Vec) { queue.push(QueuedEvent::Window { target: window.id(), name: window.name(), @@ -183,12 +217,6 @@ impl<'a> Adapter<'a> { event: ObjectEvent::StateChanged(State::Active, false), }); } - - fn root_platform_node(&self) -> PlatformNode { - let reader = self.tree.read(); - let node = reader.root(); - PlatformNode::new(&node) - } } fn containing_window(node: Node) -> Option { diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 1fc7d6b20..5f2b72c8e 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. @@ -26,49 +26,39 @@ impl AccessibleInterface { impl AccessibleInterface { #[dbus_interface(property)] fn name(&self) -> String { - self.node - .resolve(|resolved| resolved.name()) - .unwrap_or_else(|_| String::new()) + self.node.name().unwrap_or_else(|_| String::new()) } #[dbus_interface(property)] fn description(&self) -> String { - self.node - .resolve(|resolved| resolved.description()) - .unwrap_or_else(|_| String::new()) + self.node.description().unwrap_or_else(|_| String::new()) } #[dbus_interface(property)] fn parent(&self) -> OwnedObjectAddress { - self.node - .resolve(|resolved| match resolved.parent() { - Some(ObjectRef::Managed(id)) => { - ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() - } - Some(ObjectRef::Unmanaged(address)) => address, - _ => ObjectAddress::root(self.bus_name.as_ref()).into(), - }) - .unwrap_or_else(|_| ObjectAddress::null(self.bus_name.as_ref()).into()) + match self.node.parent() { + Ok(ObjectRef::Managed(id)) => { + ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() + } + Ok(ObjectRef::Unmanaged(address)) => address, + _ => ObjectAddress::null(self.bus_name.as_ref()).into(), + } } #[dbus_interface(property)] fn child_count(&self) -> i32 { - self.node - .resolve(|resolved| resolved.child_count()) - .map_or(0, |count| count.try_into().unwrap_or(0)) + self.node.child_count().unwrap_or(0) } #[dbus_interface(property)] fn locale(&self) -> String { - self.node - .resolve(|resolved| resolved.locale()) - .unwrap_or_else(|_| String::new()) + self.node.locale().unwrap_or_else(|_| String::new()) } #[dbus_interface(property)] fn accessible_id(&self) -> ObjectId { self.node - .resolve(|resolved| resolved.id()) + .accessible_id() .unwrap_or_else(|_| unsafe { ObjectId::from_str_unchecked("") }) } @@ -76,48 +66,38 @@ impl AccessibleInterface { let index = index .try_into() .map_err(|_| fdo::Error::InvalidArgs("Index can't be negative.".into()))?; - self.node - .resolve(|resolved| match resolved.child_at_index(index) { - Some(ObjectRef::Managed(id)) => { - (ObjectAddress::accessible(self.bus_name.as_ref(), &id).into(),) - } - Some(ObjectRef::Unmanaged(address)) => (address,), - _ => (ObjectAddress::null(self.bus_name.as_ref()).into(),), - }) + Ok(match self.node.child_at_index(index)? { + ObjectRef::Managed(id) => { + (ObjectAddress::accessible(self.bus_name.as_ref(), &id).into(),) + } + ObjectRef::Unmanaged(address) => (address,), + }) } fn get_children(&self) -> fdo::Result> { - self.node.resolve(|resolved| { - resolved - .children() - .into_iter() - .map(|child| match child { - ObjectRef::Managed(id) => { - ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() - } - ObjectRef::Unmanaged(address) => address, - }) - .collect() - }) + Ok(self + .node + .children()? + .into_iter() + .map(|child| match child { + ObjectRef::Managed(id) => { + ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() + } + ObjectRef::Unmanaged(address) => address, + }) + .collect()) } fn get_index_in_parent(&self) -> fdo::Result { - let index = self.node.resolve(|resolved| resolved.index_in_parent())?; - if let Some(index) = index { - index - .try_into() - .map_err(|_| fdo::Error::Failed("Index is too big.".into())) - } else { - Ok(-1) - } + self.node.index_in_parent() } fn get_role(&self) -> fdo::Result { - self.node.resolve(|resolved| resolved.role()) + self.node.role() } fn get_state(&self) -> fdo::Result { - self.node.resolve(|resolved| resolved.state()) + self.node.state() } fn get_application(&self) -> (OwnedObjectAddress,) { @@ -125,7 +105,7 @@ impl AccessibleInterface { } fn get_interfaces(&self) -> fdo::Result { - self.node.resolve(|resolved| resolved.interfaces()) + self.node.interfaces() } } diff --git a/platforms/linux/src/atspi/interfaces/action.rs b/platforms/linux/src/atspi/interfaces/action.rs index ef2d3c1a1..e69335bc4 100644 --- a/platforms/linux/src/atspi/interfaces/action.rs +++ b/platforms/linux/src/atspi/interfaces/action.rs @@ -22,7 +22,7 @@ impl ActionInterface { impl ActionInterface { #[dbus_interface(property)] fn n_actions(&self) -> i32 { - self.0.resolve(|resolved| resolved.n_actions()).unwrap_or(0) + self.0.n_actions().unwrap_or(0) } fn get_description(&self, index: i32) -> &str { @@ -30,11 +30,11 @@ impl ActionInterface { } fn get_name(&self, index: i32) -> fdo::Result { - self.0.resolve(|resolved| resolved.get_action_name(index)) + self.0.get_action_name(index) } fn get_localized_name(&self, index: i32) -> fdo::Result { - self.0.resolve(|resolved| resolved.get_action_name(index)) + self.0.get_action_name(index) } fn get_key_binding(&self, index: i32) -> &str { @@ -42,10 +42,10 @@ impl ActionInterface { } fn get_actions(&self) -> fdo::Result> { - self.0.resolve(|resolved| resolved.get_actions()) + self.0.get_actions() } fn do_action(&self, index: i32) -> fdo::Result { - self.0.resolve(|resolved| resolved.do_action(index)) + self.0.do_action(index) } } diff --git a/platforms/linux/src/atspi/interfaces/value.rs b/platforms/linux/src/atspi/interfaces/value.rs index 46a14f4d0..b949154f6 100644 --- a/platforms/linux/src/atspi/interfaces/value.rs +++ b/platforms/linux/src/atspi/interfaces/value.rs @@ -19,36 +19,26 @@ impl ValueInterface { impl ValueInterface { #[dbus_interface(property)] fn minimum_value(&self) -> f64 { - self.node - .resolve(|resolved| resolved.minimum_value()) - .unwrap() + self.node.minimum_value().unwrap() } #[dbus_interface(property)] fn maximum_value(&self) -> f64 { - self.node - .resolve(|resolved| resolved.maximum_value()) - .unwrap() + self.node.maximum_value().unwrap() } #[dbus_interface(property)] fn minimum_increment(&self) -> f64 { - self.node - .resolve(|resolved| resolved.minimum_increment()) - .unwrap() + self.node.minimum_increment().unwrap() } #[dbus_interface(property)] fn current_value(&self) -> f64 { - self.node - .resolve(|resolved| resolved.current_value()) - .unwrap() + self.node.current_value().unwrap() } #[dbus_interface(property)] fn set_current_value(&self, value: f64) { - self.node - .resolve(|resolved| resolved.set_current_value(value)) - .unwrap(); + self.node.set_current_value(value).unwrap(); } } diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 3b2880558..d5cb9ea92 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. @@ -7,23 +7,46 @@ use crate::atspi::{ interfaces::{Action, Interface, Interfaces, ObjectEvent, Property, QueuedEvent}, ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole, State, StateSet, }; -use accesskit::{AriaCurrent, CheckedState, DefaultActionVerb, InvalidState, Orientation, Role}; -use accesskit_consumer::{Node, Tree, WeakNode}; +use accesskit::{CheckedState, DefaultActionVerb, NodeId, Role}; +use accesskit_consumer::{FilterResult, Node, Tree, TreeState}; use parking_lot::RwLock; -use std::sync::Weak; +use std::{ + convert::TryFrom, + sync::{Arc, Weak}, +}; use zbus::fdo; -pub(crate) struct ResolvedPlatformNode<'a> { - node: Node<'a>, +pub(crate) fn filter(node: &Node) -> FilterResult { + if node.is_focused() { + return FilterResult::Include; + } + + if node.is_hidden() { + return FilterResult::ExcludeSubtree; + } + + let role = node.role(); + if role == Role::Presentation || role == Role::GenericContainer || role == Role::InlineTextBox { + return FilterResult::ExcludeNode; + } + + FilterResult::Include } -impl ResolvedPlatformNode<'_> { - pub fn new(node: Node) -> ResolvedPlatformNode { - ResolvedPlatformNode { node } +fn filter_with_root_exception(node: &Node) -> FilterResult { + if node.is_root() { + return FilterResult::Include; } + filter(node) +} - pub fn downgrade(&self) -> PlatformNode { - PlatformNode::new(&self.node) +pub(crate) struct NodeWrapper<'a> { + node: &'a Node<'a>, +} + +impl<'a> NodeWrapper<'a> { + pub(crate) fn new(node: &'a Node<'a>) -> Self { + NodeWrapper { node } } pub fn name(&self) -> String { @@ -42,7 +65,7 @@ impl ResolvedPlatformNode<'_> { } pub fn child_count(&self) -> usize { - self.node.children().count() + self.node.child_ids().count() } pub fn locale(&self) -> String { @@ -53,24 +76,6 @@ impl ResolvedPlatformNode<'_> { self.node.id().into() } - pub fn child_at_index(&self, index: usize) -> Option { - self.node - .children() - .nth(index) - .map(|child| child.id().into()) - } - - pub fn children(&self) -> Vec { - self.node - .children() - .map(|child| child.id().into()) - .collect() - } - - pub fn index_in_parent(&self) -> Option { - self.node.parent_and_index().map(|(_, index)| index) - } - pub fn role(&self) -> AtspiRole { match self.node.role() { Role::Alert => AtspiRole::Notification, @@ -162,7 +167,7 @@ impl ResolvedPlatformNode<'_> { Role::Heading => AtspiRole::Heading, Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame, Role::Image => { - if self.node.unignored_children().next().is_some() { + if self.node.filtered_children(&filter).next().is_some() { AtspiRole::ImageMap } else { AtspiRole::Image @@ -195,7 +200,7 @@ impl ResolvedPlatformNode<'_> { // - The list marker itself is ignored but the descendants are not // - Or the list marker contains images Role::ListMarker => { - if self.node.unignored_children().next().is_none() { + if self.node.filtered_children(&filter).next().is_none() { AtspiRole::Static } else { AtspiRole::Panel @@ -220,17 +225,18 @@ impl ResolvedPlatformNode<'_> { Role::PdfRoot => AtspiRole::DocumentFrame, Role::PluginObject => AtspiRole::Embedded, Role::PopupButton => { - if self - .node - .data() - .html_tag - .as_ref() - .map_or(false, |tag| tag.as_ref() == "select") - { - AtspiRole::ComboBox - } else { - AtspiRole::PushButton - } + // TODO: Add a getter for html_tag + //if self + // .node + // .data() + // .html_tag + // .as_ref() + // .map_or(false, |tag| tag.as_ref() == "select") + //{ + // AtspiRole::ComboBox + //} else { + AtspiRole::PushButton + //} } Role::Portal => AtspiRole::PushButton, Role::Pre => AtspiRole::Section, @@ -276,11 +282,12 @@ impl ResolvedPlatformNode<'_> { Role::Term => AtspiRole::DescriptionTerm, Role::TitleBar => AtspiRole::TitleBar, Role::TextField | Role::SearchBox => { - if self.node.data().protected { - AtspiRole::PasswordText - } else { - AtspiRole::Entry - } + // TODO: Add a getter for protected + //if self.node.data().protected { + // AtspiRole::PasswordText + //} else { + AtspiRole::Entry + //} } Role::TextFieldWithComboBox => AtspiRole::ComboBox, Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => { @@ -308,66 +315,66 @@ impl ResolvedPlatformNode<'_> { pub fn state(&self) -> StateSet { let platform_role = self.role(); - let data = self.node.data(); + //let data = self.node.data(); let mut state = StateSet::empty(); if self.node.role() == Role::Window && self.node.parent().is_none() { state.insert(State::Active); } - if let Some(expanded) = data.expanded { - state.insert(State::Expandable); - if expanded { - state.insert(State::Expanded); - } - } - if data.default { - state.insert(State::IsDefault); - } - if data.editable && !data.read_only { - state.insert(State::Editable); - } + //if let Some(expanded) = data.expanded { + // state.insert(State::Expandable); + // if expanded { + // state.insert(State::Expanded); + // } + //} + //if data.default { + // state.insert(State::IsDefault); + //} + //if data.editable && !data.read_only { + // state.insert(State::Editable); + //} // TODO: Focus and selection. - if data.focusable { + if self.node.is_focusable() { state.insert(State::Focusable); } - match data.orientation { - Some(Orientation::Horizontal) => state.insert(State::Horizontal), - Some(Orientation::Vertical) => state.insert(State::Vertical), - _ => {} - } - if !self.node.is_invisible_or_ignored() { + //match data.orientation { + // Some(Orientation::Horizontal) => state.insert(State::Horizontal), + // Some(Orientation::Vertical) => state.insert(State::Vertical), + // _ => {} + //} + if filter(self.node) == FilterResult::Include { state.insert(State::Visible); // if (!delegate_->IsOffscreen() && !is_minimized) state.insert(State::Showing); } - if data.multiselectable { - state.insert(State::Multiselectable); - } - if data.required { - state.insert(State::Required); - } - if data.visited { - state.insert(State::Visited); - } - if let Some(InvalidState::True | InvalidState::Other(_)) = data.invalid_state { - state.insert(State::InvalidEntry); - } - match data.aria_current { - None | Some(AriaCurrent::False) => {} - _ => state.insert(State::Active), - } - if platform_role != AtspiRole::ToggleButton && data.checked_state.is_some() { + //if data.multiselectable { + // state.insert(State::Multiselectable); + //} + //if data.required { + // state.insert(State::Required); + //} + //if data.visited { + // state.insert(State::Visited); + //} + //if let Some(InvalidState::True | InvalidState::Other(_)) = data.invalid_state { + // state.insert(State::InvalidEntry); + //} + //match data.aria_current { + // None | Some(AriaCurrent::False) => {} + // _ => state.insert(State::Active), + //} + if platform_role != AtspiRole::ToggleButton && self.node.checked_state().is_some() { state.insert(State::Checkable); } - if data.has_popup.is_some() { - state.insert(State::HasPopup); - } - if data.busy { - state.insert(State::Busy); - } - if data.modal { - state.insert(State::Modal); - } - if let Some(selected) = data.selected { + //if data.has_popup.is_some() { + // state.insert(State::HasPopup); + //} + //if data.busy { + // state.insert(State::Busy); + //} + //if data.modal { + // state.insert(State::Modal); + //} + if let Some(selected) = self.node.is_selected() { if !self.node.is_disabled() { state.insert(State::Selectable); } @@ -377,27 +384,27 @@ impl ResolvedPlatformNode<'_> { } if self.node.is_text_field() { state.insert(State::SelectableText); - match self.node.data().multiline { - true => state.insert(State::MultiLine), - false => state.insert(State::SingleLine), - } + //match self.node.data().multiline { + // true => state.insert(State::MultiLine), + // false => state.insert(State::SingleLine), + //} } // Special case for indeterminate progressbar. - if self.node.role() == Role::ProgressIndicator && data.numeric_value.is_none() { + if self.node.role() == Role::ProgressIndicator && self.node.numeric_value().is_none() { state.insert(State::Indeterminate); } - let has_suggestion = data - .auto_complete - .as_ref() - .map_or(false, |a| !a.as_ref().is_empty()); - if has_suggestion || data.autofill_available { - state.insert(State::SupportsAutocompletion); - } + //let has_suggestion = data + // .auto_complete + // .as_ref() + // .map_or(false, |a| !a.as_ref().is_empty()); + //if has_suggestion || data.autofill_available { + // state.insert(State::SupportsAutocompletion); + //} // Checked state - match data.checked_state { + match self.node.checked_state() { Some(CheckedState::Mixed) => state.insert(State::Indeterminate), Some(CheckedState::True) => { if platform_role == AtspiRole::ToggleButton { @@ -442,10 +449,7 @@ impl ResolvedPlatformNode<'_> { } pub fn n_actions(&self) -> i32 { - match self.node.default_action_verb() { - Some(_) => 1, - None => 0, - } + self.node.default_action_verb().map_or(0, |_| 1) } pub fn get_action_name(&self, index: i32) -> String { @@ -466,53 +470,12 @@ impl ResolvedPlatformNode<'_> { }) } - pub fn get_actions(&self) -> Vec { - let n_actions = self.n_actions() as usize; - let mut actions = Vec::with_capacity(n_actions); - for i in 0..n_actions { - actions.push(Action { - localized_name: self.get_action_name(i as i32), - description: "".into(), - key_binding: "".into(), - }); - } - actions - } - - pub fn do_action(&self, index: i32) -> bool { - if index != 0 { - return false; - } - self.node.do_default_action(); - true - } - - pub fn minimum_value(&self) -> f64 { - self.node.min_numeric_value().unwrap_or(std::f64::MIN) - } - - pub fn maximum_value(&self) -> f64 { - self.node.max_numeric_value().unwrap_or(std::f64::MAX) - } - - pub fn minimum_increment(&self) -> f64 { - self.node.numeric_value_step().unwrap_or(0.0) - } - - pub fn current_value(&self) -> f64 { - self.node.numeric_value().unwrap_or(0.0) - } - - pub fn set_current_value(&self, value: f64) { - self.node.set_numeric_value(value) - } - - pub fn enqueue_changes(&self, queue: &mut Vec, old: &ResolvedPlatformNode) { + pub fn enqueue_changes(&self, queue: &mut Vec, old: &NodeWrapper) { self.enqueue_state_changes(queue, old); self.enqueue_property_changes(queue, old); } - fn enqueue_state_changes(&self, queue: &mut Vec, old: &ResolvedPlatformNode) { + fn enqueue_state_changes(&self, queue: &mut Vec, old: &NodeWrapper) { let old_state = old.state(); let new_state = self.state(); let changed_states = old_state ^ new_state; @@ -524,7 +487,7 @@ impl ResolvedPlatformNode<'_> { } } - fn enqueue_property_changes(&self, queue: &mut Vec, old: &ResolvedPlatformNode) { + fn enqueue_property_changes(&self, queue: &mut Vec, old: &NodeWrapper) { let name = self.name(); if name != old.name() { queue.push(QueuedEvent::Object { @@ -556,21 +519,186 @@ impl ResolvedPlatformNode<'_> { } } +fn unknown_object() -> fdo::Error { + fdo::Error::UnknownObject("".into()) +} + #[derive(Clone)] -pub(crate) struct PlatformNode(WeakNode); +pub(crate) struct PlatformNode { + tree: Weak, + node_id: NodeId, +} impl PlatformNode { - pub fn new(node: &Node) -> Self { - Self(node.downgrade()) + pub(crate) fn new(tree: &Arc, node_id: NodeId) -> Self { + Self { + tree: Arc::downgrade(tree), + node_id, + } + } + + fn upgrade_tree(&self) -> fdo::Result> { + if let Some(tree) = self.tree.upgrade() { + Ok(tree) + } else { + Err(unknown_object()) + } + } + + fn with_tree_state(&self, f: F) -> fdo::Result + where + F: FnOnce(&TreeState) -> fdo::Result, + { + let tree = self.upgrade_tree()?; + let state = tree.read(); + f(&state) } - pub fn resolve(&self, f: F) -> fdo::Result + fn resolve(&self, f: F) -> fdo::Result where - for<'a> F: FnOnce(ResolvedPlatformNode<'a>) -> T, + for<'a> F: FnOnce(NodeWrapper<'a>) -> fdo::Result, { - self.0 - .map(|node| f(ResolvedPlatformNode::new(node))) - .ok_or(fdo::Error::UnknownObject("".into())) + self.with_tree_state(|state| { + if let Some(node) = state.node_by_id(self.node_id) { + f(NodeWrapper::new(&node)) + } else { + Err(unknown_object()) + } + }) + } + + fn validate_for_action(&self) -> fdo::Result> { + let tree = self.upgrade_tree()?; + let state = tree.read(); + if state.has_node(self.node_id) { + drop(state); + Ok(tree) + } else { + Err(unknown_object()) + } + } + + pub fn name(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.name())) + } + + pub fn description(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.description())) + } + + pub fn parent(&self) -> fdo::Result { + self.resolve(|resolved| { + Ok(resolved + .parent() + .unwrap_or_else(|| ObjectRef::Managed(ObjectId::root()))) + }) + } + + pub fn child_count(&self) -> fdo::Result { + self.resolve(|resolved| { + i32::try_from(resolved.child_count()) + .map_err(|_| fdo::Error::Failed("Too many children.".into())) + }) + } + + pub fn locale(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.locale())) + } + + pub fn accessible_id(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.id())) + } + + pub fn child_at_index(&self, index: usize) -> fdo::Result { + self.resolve(|resolved| { + resolved + .node + .child_ids() + .nth(index) + .map(ObjectRef::from) + .ok_or_else(unknown_object) + }) + } + + pub fn children(&self) -> fdo::Result> { + self.resolve(|resolved| Ok(resolved.node.child_ids().map(ObjectRef::from).collect())) + } + + pub fn index_in_parent(&self) -> fdo::Result { + self.resolve(|resolved| { + resolved + .node + .parent_and_index() + .map_or(Ok(-1), |(_, index)| { + i32::try_from(index).map_err(|_| fdo::Error::Failed("Index is too big.".into())) + }) + }) + } + + pub fn role(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.role())) + } + + pub fn state(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.state())) + } + + pub fn interfaces(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.interfaces())) + } + + pub fn n_actions(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.n_actions())) + } + + pub fn get_action_name(&self, index: i32) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.get_action_name(index))) + } + + pub fn get_actions(&self) -> fdo::Result> { + self.resolve(|resolved| { + let n_actions = resolved.n_actions() as usize; + let mut actions = Vec::with_capacity(n_actions); + for i in 0..n_actions { + actions.push(Action { + localized_name: resolved.get_action_name(i as i32), + description: "".into(), + key_binding: "".into(), + }); + } + Ok(actions) + }) + } + + pub fn do_action(&self, index: i32) -> fdo::Result { + if index != 0 { + return Ok(false); + } + let tree = self.validate_for_action()?; + tree.do_default_action(self.node_id); + Ok(true) + } + + pub fn minimum_value(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.node.min_numeric_value().unwrap_or(std::f64::MIN))) + } + + pub fn maximum_value(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.node.max_numeric_value().unwrap_or(std::f64::MAX))) + } + + pub fn minimum_increment(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.node.numeric_value_step().unwrap_or(0.0))) + } + + pub fn current_value(&self) -> fdo::Result { + self.resolve(|resolved| Ok(resolved.node.numeric_value().unwrap_or(0.0))) + } + + pub fn set_current_value(&self, value: f64) -> fdo::Result<()> { + let tree = self.validate_for_action()?; + tree.set_numeric_value(self.node_id, value); + Ok(()) } } From 0dae57ad581655e9727f33d031b947fef0727884 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 13 Nov 2022 12:22:32 +0100 Subject: [PATCH 36/69] Add Linux support to accesskit_winit --- Cargo.lock | 2 +- platforms/linux/examples/hello_world.rs | 150 --------------------- platforms/linux/src/adapter.rs | 122 ++++++++--------- platforms/linux/src/atspi/bus.rs | 6 +- platforms/winit/Cargo.toml | 3 + platforms/winit/examples/simple.rs | 2 + platforms/winit/src/platform_impl/linux.rs | 37 +++++ platforms/winit/src/platform_impl/mod.rs | 7 +- 8 files changed, 114 insertions(+), 215 deletions(-) delete mode 100644 platforms/linux/examples/hello_world.rs create mode 100755 platforms/winit/src/platform_impl/linux.rs diff --git a/Cargo.lock b/Cargo.lock index feaf841e2..dce6e95ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1481,4 +1481,4 @@ dependencies = [ name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" \ No newline at end of file +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/platforms/linux/examples/hello_world.rs b/platforms/linux/examples/hello_world.rs deleted file mode 100644 index d0f111bbc..000000000 --- a/platforms/linux/examples/hello_world.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. -// Licensed under the Apache License, Version 2.0 (found in -// the LICENSE-APACHE file) or the MIT license (found in -// the LICENSE-MIT file), at your option. - -use accesskit::{ - ActionHandler, ActionRequest, DefaultActionVerb, Node, NodeId, Role, Tree, TreeUpdate, -}; -use accesskit_linux::Adapter; -use std::{num::NonZeroU128, sync::Arc}; -use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - -const WINDOW_TITLE: &str = "Hello world"; - -const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(1) }); -const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); -const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); - -struct NullActionHandler; - -impl ActionHandler for NullActionHandler { - fn do_action(&self, _request: ActionRequest) {} -} - -fn get_tree() -> Tree { - Tree { - ..Tree::new(WINDOW_ID) - } -} - -fn make_button(name: &str) -> Arc { - Arc::new(Node { - role: Role::Button, - name: Some(name.into()), - focusable: true, - default_action_verb: Some(DefaultActionVerb::Click), - ..Default::default() - }) -} - -fn get_initial_state() -> TreeUpdate { - let root = Arc::new(Node { - role: Role::Window, - children: vec![BUTTON_1_ID, BUTTON_2_ID], - name: Some(WINDOW_TITLE.into()), - ..Default::default() - }); - let button_1 = make_button("Button 1"); - let button_2 = make_button("Button 2"); - TreeUpdate { - nodes: vec![ - (WINDOW_ID, root), - (BUTTON_1_ID, button_1), - (BUTTON_2_ID, button_2), - ], - tree: Some(get_tree()), - focus: None, - } -} - -static mut FOCUS: NodeId = BUTTON_1_ID; - -fn main() { - let adapter = Adapter::new( - String::from("hello_world"), - String::from("ExampleUI"), - String::from("0.1.0"), - get_initial_state(), - Box::new(NullActionHandler {}), - ) - .unwrap(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title(WINDOW_TITLE) - .build(&event_loop) - .unwrap(); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, window_id } if window_id == window.id() => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::Focused(window_has_focus) => { - adapter - .update(TreeUpdate { - nodes: vec![], - focus: window_has_focus.then(|| unsafe { FOCUS }), - tree: None, - }) - .raise(); - } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Tab), - state: ElementState::Pressed, - .. - }, - .. - } => unsafe { - FOCUS = if FOCUS == BUTTON_1_ID { - BUTTON_2_ID - } else { - BUTTON_1_ID - }; - adapter - .update(TreeUpdate { - nodes: vec![], - focus: Some(FOCUS), - tree: None, - }) - .raise(); - }, - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Space), - state: ElementState::Released, - .. - }, - .. - } => unsafe { - let updated_node = if FOCUS == BUTTON_1_ID { - (BUTTON_1_ID, make_button("You pressed button 1")) - } else { - (BUTTON_2_ID, make_button("You pressed button 2")) - }; - adapter - .update(TreeUpdate { - nodes: vec![updated_node], - focus: Some(FOCUS), - tree: None, - }) - .raise(); - }, - _ => (), - }, - Event::MainEventsCleared => { - window.request_redraw(); - } - _ => (), - } - }); -} diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index a7d5d75f2..f88650af1 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -18,34 +18,35 @@ use crate::atspi::{ use crate::node::{filter, AppState, NodeWrapper, PlatformNode, PlatformRootNode}; use parking_lot::RwLock; -pub struct Adapter<'a> { - atspi_bus: Bus<'a>, +pub struct Adapter { + atspi_bus: Option, _app_state: Arc>, tree: Arc, } -impl<'a> Adapter<'a> { +impl Adapter { pub fn new( app_name: String, toolkit_name: String, toolkit_version: String, - initial_state: TreeUpdate, + initial_state: Box TreeUpdate>, action_handler: Box, - ) -> Option { - let mut atspi_bus = Bus::a11y_bus()?; - let tree = Arc::new(Tree::new(initial_state, action_handler)); + ) -> Self { + let mut atspi_bus = Bus::a11y_bus(); + let tree = Arc::new(Tree::new(initial_state(), action_handler)); let app_state = Arc::new(RwLock::new(AppState::new( app_name, toolkit_name, toolkit_version, ))); - atspi_bus - .register_root_node(PlatformRootNode::new( + atspi_bus.as_mut().and_then(|bus| { + bus.register_root_node(PlatformRootNode::new( Arc::downgrade(&app_state), Arc::downgrade(&tree), )) - .ok()?; - let adapter = Adapter { + .ok() + }); + let mut adapter = Adapter { atspi_bus, _app_state: app_state, tree, @@ -67,10 +68,10 @@ impl<'a> Adapter<'a> { let interfaces = NodeWrapper::new(&reader.node_by_id(id).unwrap()).interfaces(); adapter .register_interfaces(&adapter.tree, id, interfaces) - .ok()?; + .unwrap(); } } - Some(adapter) + adapter } fn register_interfaces( @@ -79,26 +80,26 @@ impl<'a> Adapter<'a> { id: NodeId, new_interfaces: Interfaces, ) -> zbus::Result { - let atspi_id = ObjectId::from(id); - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, atspi_id.as_str()); - if new_interfaces.contains(Interface::Accessible) { - self.atspi_bus.register_interface( - &path, - AccessibleInterface::new( - self.atspi_bus.unique_name().to_owned(), - PlatformNode::new(tree, id), - ), - )?; - } - if new_interfaces.contains(Interface::Action) { - self.atspi_bus - .register_interface(&path, ActionInterface::new(PlatformNode::new(tree, id)))?; - } - if new_interfaces.contains(Interface::Value) { - self.atspi_bus - .register_interface(&path, ValueInterface::new(PlatformNode::new(tree, id)))?; - } - Ok(true) + self.atspi_bus.as_ref().map_or(Ok(false), |bus| { + let atspi_id = ObjectId::from(id); + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, atspi_id.as_str()); + if new_interfaces.contains(Interface::Accessible) { + bus.register_interface( + &path, + AccessibleInterface::new( + bus.unique_name().to_owned(), + PlatformNode::new(tree, id), + ), + )?; + } + if new_interfaces.contains(Interface::Action) { + bus.register_interface(&path, ActionInterface::new(PlatformNode::new(tree, id)))?; + } + if new_interfaces.contains(Interface::Value) { + bus.register_interface(&path, ValueInterface::new(PlatformNode::new(tree, id)))?; + } + Ok(true) + }) } fn unregister_interfaces( @@ -106,25 +107,24 @@ impl<'a> Adapter<'a> { id: &ObjectId, old_interfaces: Interfaces, ) -> zbus::Result { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str()); - if old_interfaces.contains(Interface::Accessible) { - self.atspi_bus - .unregister_interface::>(&path)?; - } - if old_interfaces.contains(Interface::Action) { - self.atspi_bus - .unregister_interface::(&path)?; - } - if old_interfaces.contains(Interface::Value) { - self.atspi_bus - .unregister_interface::(&path)?; - } - Ok(true) + self.atspi_bus.as_ref().map_or(Ok(false), |bus| { + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str()); + if old_interfaces.contains(Interface::Accessible) { + bus.unregister_interface::>(&path)?; + } + if old_interfaces.contains(Interface::Action) { + bus.unregister_interface::(&path)?; + } + if old_interfaces.contains(Interface::Value) { + bus.unregister_interface::(&path)?; + } + Ok(true) + }) } pub fn update(&self, update: TreeUpdate) -> QueuedEvents { struct Handler<'a> { - adapter: &'a Adapter<'a>, + adapter: &'a Adapter, tree: &'a Arc, queue: Vec, } @@ -234,22 +234,24 @@ fn containing_window(node: Node) -> Option { } #[must_use = "events must be explicitly raised"] -pub struct QueuedEvents<'a> { - bus: Bus<'a>, +pub struct QueuedEvents { + bus: Option, queue: Vec, } -impl<'a> QueuedEvents<'a> { +impl QueuedEvents { pub fn raise(&self) { - for event in &self.queue { - let _ = match &event { - QueuedEvent::Object { target, event } => self.bus.emit_object_event(target, event), - QueuedEvent::Window { - target, - name, - event, - } => self.bus.emit_window_event(target, name, event), - }; + if let Some(bus) = &self.bus { + for event in &self.queue { + let _ = match &event { + QueuedEvent::Object { target, event } => bus.emit_object_event(target, event), + QueuedEvent::Window { + target, + name, + event, + } => bus.emit_window_event(target, name, event), + }; + } } } } diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index e551edb23..b90213135 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -23,12 +23,12 @@ use zbus::{ use zvariant::{OwnedValue, Str, Value}; #[derive(Clone)] -pub(crate) struct Bus<'a> { +pub(crate) struct Bus { conn: Connection, - socket_proxy: SocketProxy<'a>, + socket_proxy: SocketProxy<'static>, } -impl<'a> Bus<'a> { +impl Bus { pub fn a11y_bus() -> Option { let conn = a11y_bus()?; let socket_proxy = SocketProxy::new(&conn).ok()?; diff --git a/platforms/winit/Cargo.toml b/platforms/winit/Cargo.toml index a643d93f4..dea1db96a 100644 --- a/platforms/winit/Cargo.toml +++ b/platforms/winit/Cargo.toml @@ -15,6 +15,9 @@ accesskit = { version = "0.8.1", path = "../../common" } parking_lot = "0.12.1" winit = { version = "0.27.2", default-features = false, features = ["x11", "wayland", "wayland-dlopen"] } +[target.'cfg(target_os = "linux")'.dependencies] +accesskit_linux = { version = "0.1.0", path = "../linux" } + [target.'cfg(target_os = "windows")'.dependencies] accesskit_windows = { version = "0.10.2", path = "../windows" } diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index 747525d01..8cee04cc7 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -135,6 +135,8 @@ fn main() { println!("This example has no visible GUI, and a keyboard interface:"); println!("- [Tab] switches focus between two logical buttons."); println!("- [Space] 'presses' the button, adding static text in a live region announcing that it was pressed."); + #[cfg(target_os = "linux")] + println!("Enable Orca with [Super]+[Alt]+[S]."); #[cfg(target_os = "windows")] println!("Enable Narrator with [Win]+[Ctrl]+[Enter] (or [Win]+[Enter] on older versions of Windows)."); diff --git a/platforms/winit/src/platform_impl/linux.rs b/platforms/winit/src/platform_impl/linux.rs new file mode 100755 index 000000000..2851b9078 --- /dev/null +++ b/platforms/winit/src/platform_impl/linux.rs @@ -0,0 +1,37 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file). + +use accesskit::kurbo::Rect; +use accesskit::{ActionHandler, TreeUpdate}; +use accesskit_linux::Adapter as LinuxAdapter; +use winit::window::Window; + +pub struct Adapter { + adapter: LinuxAdapter, +} + +impl Adapter { + pub fn new( + window: &Window, + source: Box TreeUpdate>, + action_handler: Box, + ) -> Self { + let adapter = LinuxAdapter::new( + String::new(), + String::new(), + String::new(), + source, + action_handler, + ); + Self { adapter } + } + + pub fn update(&self, update: TreeUpdate) { + self.adapter.update(update).raise(); + } + + pub fn update_if_active(&self, updater: impl FnOnce() -> TreeUpdate) { + self.adapter.update(updater()).raise(); + } +} diff --git a/platforms/winit/src/platform_impl/mod.rs b/platforms/winit/src/platform_impl/mod.rs index 56e0040ce..0663146df 100644 --- a/platforms/winit/src/platform_impl/mod.rs +++ b/platforms/winit/src/platform_impl/mod.rs @@ -14,6 +14,11 @@ mod platform; #[path = "macos.rs"] mod platform; -#[cfg(all(not(target_os = "windows"), not(target_os = "macos"),))] +#[cfg(target_os = "linux")] +#[path = "linux.rs"] +mod platform; + +#[cfg(all(not(target_os = "windows"), not(target_os = "macos"), not(target_os = "linux)))] +#[cfg(all(not(target_os = "windows"), not(target_os = "linux"),))] #[path = "null.rs"] mod platform; From a7e601e1a3ba9e091d4451c7bc8351c090d9d887 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 13 Nov 2022 18:23:09 +0100 Subject: [PATCH 37/69] Various cleanups --- platforms/linux/src/adapter.rs | 8 ++++---- platforms/linux/src/atspi/bus.rs | 13 ++++++------ .../linux/src/atspi/interfaces/accessible.rs | 8 ++++---- .../linux/src/atspi/interfaces/action.rs | 4 ++-- .../linux/src/atspi/interfaces/application.rs | 8 ++++---- .../linux/src/atspi/interfaces/events.rs | 6 ------ platforms/linux/src/atspi/object_address.rs | 12 +++++------ platforms/linux/src/atspi/object_id.rs | 4 ++-- platforms/linux/src/atspi/state.rs | 20 ++++++++----------- platforms/linux/src/node.rs | 12 +---------- platforms/winit/src/platform_impl/linux.rs | 3 +-- 11 files changed, 38 insertions(+), 60 deletions(-) diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index f88650af1..746226836 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -46,7 +46,7 @@ impl Adapter { )) .ok() }); - let mut adapter = Adapter { + let adapter = Adapter { atspi_bus, _app_state: app_state, tree, @@ -55,7 +55,7 @@ impl Adapter { let reader = adapter.tree.read(); let mut objects_to_add = Vec::new(); - fn add_children<'b>(node: Node<'b>, to_add: &mut Vec) { + fn add_children(node: Node<'_>, to_add: &mut Vec) { for child in node.filtered_children(&filter) { to_add.push(child.id()); add_children(child, to_add); @@ -162,13 +162,13 @@ impl Adapter { .window_activated(&NodeWrapper::new(&window), &mut self.queue); } } - if let Some(node) = new_node.map(|node| NodeWrapper::new(node)) { + if let Some(node) = new_node.map(NodeWrapper::new) { self.queue.push(QueuedEvent::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, true), }); } - if let Some(node) = old_node.map(|node| NodeWrapper::new(node)) { + if let Some(node) = old_node.map(NodeWrapper::new) { self.queue.push(QueuedEvent::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, false), diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index b90213135..ca8791201 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. @@ -68,9 +68,9 @@ impl Bus { let desktop_address = self .socket_proxy .embed(ObjectAddress::root(self.unique_name().as_ref()))?; - node.state - .upgrade() - .map(|state| state.write().desktop_address = Some(desktop_address)); + if let Some(state) = node.state.upgrade() { + state.write().desktop_address = Some(desktop_address); + } Ok(true) } else { Ok(false) @@ -120,8 +120,7 @@ impl Bus { .into() } Property::Role(value) => OwnedValue::from(*value as u32), - } - .into(), + }, properties, }, ), @@ -168,7 +167,7 @@ impl Bus { fn a11y_bus() -> Option { let address = match var("AT_SPI_BUS_ADDRESS") { - Ok(address) if address.len() > 0 => address, + Ok(address) if !address.is_empty() => address, _ => { let session_bus = Connection::session().ok()?; BusProxy::new(&session_bus).ok()?.get_address().ok()? diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 5f2b72c8e..1b1ea994a 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -26,12 +26,12 @@ impl AccessibleInterface { impl AccessibleInterface { #[dbus_interface(property)] fn name(&self) -> String { - self.node.name().unwrap_or_else(|_| String::new()) + self.node.name().unwrap_or_default() } #[dbus_interface(property)] fn description(&self) -> String { - self.node.description().unwrap_or_else(|_| String::new()) + self.node.description().unwrap_or_default() } #[dbus_interface(property)] @@ -52,7 +52,7 @@ impl AccessibleInterface { #[dbus_interface(property)] fn locale(&self) -> String { - self.node.locale().unwrap_or_else(|_| String::new()) + self.node.locale().unwrap_or_default() } #[dbus_interface(property)] @@ -117,7 +117,7 @@ impl AccessibleInterface { .state .upgrade() .map(|state| state.read().name.clone()) - .unwrap_or_else(|| String::new()) + .unwrap_or_default() } #[dbus_interface(property)] diff --git a/platforms/linux/src/atspi/interfaces/action.rs b/platforms/linux/src/atspi/interfaces/action.rs index e69335bc4..5911dc6cb 100644 --- a/platforms/linux/src/atspi/interfaces/action.rs +++ b/platforms/linux/src/atspi/interfaces/action.rs @@ -25,7 +25,7 @@ impl ActionInterface { self.0.n_actions().unwrap_or(0) } - fn get_description(&self, index: i32) -> &str { + fn get_description(&self, _index: i32) -> &str { "" } @@ -37,7 +37,7 @@ impl ActionInterface { self.0.get_action_name(index) } - fn get_key_binding(&self, index: i32) -> &str { + fn get_key_binding(&self, _index: i32) -> &str { "" } diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/linux/src/atspi/interfaces/application.rs index 31c9ea34e..55c7a8bf7 100644 --- a/platforms/linux/src/atspi/interfaces/application.rs +++ b/platforms/linux/src/atspi/interfaces/application.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. @@ -16,7 +16,7 @@ impl ApplicationInterface { .state .upgrade() .map(|state| state.read().toolkit_name.clone()) - .unwrap_or(String::new()) + .unwrap_or_default() } #[dbus_interface(property)] @@ -25,7 +25,7 @@ impl ApplicationInterface { .state .upgrade() .map(|state| state.read().toolkit_version.clone()) - .unwrap_or(String::new()) + .unwrap_or_default() } #[dbus_interface(property)] @@ -48,6 +48,6 @@ impl ApplicationInterface { .state .upgrade() .map(|state| state.write().id = Some(id)) - .ok_or(fdo::Error::UnknownObject("".into())) + .ok_or_else(|| fdo::Error::UnknownObject("".into())) } } diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index 9b3262f09..d36ce5d70 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -44,14 +44,8 @@ pub(crate) enum ObjectEvent { pub(crate) enum WindowEvent { #[strum(serialize = "Activate")] Activated, - #[strum(serialize = "Close")] - Closed, - #[strum(serialize = "Create")] - Created, #[strum(serialize = "Deactivate")] Deactivated, - #[strum(serialize = "Destroy")] - Destroyed, } #[derive(Deserialize, Serialize, Type)] diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs index 6ddbd3d0c..5c07f192e 100644 --- a/platforms/linux/src/atspi/object_address.rs +++ b/platforms/linux/src/atspi/object_address.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. @@ -8,11 +8,11 @@ use serde::{Deserialize, Serialize}; use zbus::names::{OwnedUniqueName, UniqueName}; use zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type, Value}; -pub(crate) const ACCESSIBLE_PATH_PREFIX: &'static str = "/org/a11y/atspi/accessible/"; -pub(crate) const NULL_PATH: &'static str = "/org/a11y/atspi/null"; -pub(crate) const ROOT_PATH: &'static str = "/org/a11y/atspi/accessible/root"; +pub(crate) const ACCESSIBLE_PATH_PREFIX: &str = "/org/a11y/atspi/accessible/"; +pub(crate) const NULL_PATH: &str = "/org/a11y/atspi/null"; +pub(crate) const ROOT_PATH: &str = "/org/a11y/atspi/accessible/root"; -#[derive(Clone, Debug, Deserialize, Serialize, Type, Value)] +#[derive(Clone, Debug, Serialize, Deserialize, Type, Value)] pub struct ObjectAddress<'a> { #[serde(borrow)] bus_name: UniqueName<'a>, @@ -51,7 +51,7 @@ impl<'a> ObjectAddress<'a> { } } -#[derive(Clone, Debug, Deserialize, OwnedValue, PartialEq, Serialize, Type, Value)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, OwnedValue, Type, Value)] pub struct OwnedObjectAddress { bus_name: OwnedUniqueName, path: OwnedObjectPath, diff --git a/platforms/linux/src/atspi/object_id.rs b/platforms/linux/src/atspi/object_id.rs index 037cf97d8..651aa3f12 100644 --- a/platforms/linux/src/atspi/object_id.rs +++ b/platforms/linux/src/atspi/object_id.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. @@ -7,7 +7,7 @@ use accesskit::NodeId; use serde::{Deserialize, Serialize}; use zvariant::{Str, Type, Value}; -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Type, Value)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Type, Value)] pub struct ObjectId<'a>(#[serde(borrow)] Str<'a>); impl<'a> ObjectId<'a> { diff --git a/platforms/linux/src/atspi/state.rs b/platforms/linux/src/atspi/state.rs index 8766ef52e..c308454a5 100644 --- a/platforms/linux/src/atspi/state.rs +++ b/platforms/linux/src/atspi/state.rs @@ -228,10 +228,6 @@ pub(crate) enum State { pub(crate) struct StateSet(BitFlags); impl StateSet { - pub fn new>>(value: B) -> Self { - Self(value.into()) - } - pub fn from_bits(bits: u64) -> Result> { Ok(StateSet(BitFlags::from_bits(bits)?)) } @@ -348,7 +344,7 @@ mod tests { #[test] fn serialize_state_set_invalid() { let ctxt = Context::::new_dbus(0); - let encoded = to_bytes(ctxt, &StateSet::new(State::Invalid)).unwrap(); + let encoded = to_bytes(ctxt, &StateSet(State::Invalid.into())).unwrap(); assert_eq!(encoded, &[8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]); } @@ -356,13 +352,13 @@ mod tests { fn deserialize_state_set_invalid() { let ctxt = Context::::new_dbus(0); let decoded: StateSet = from_slice(&[8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], ctxt).unwrap(); - assert_eq!(decoded, StateSet::new(State::Invalid)); + assert_eq!(decoded, StateSet(State::Invalid.into())); } #[test] fn serialize_state_set_manages_descendants() { let ctxt = Context::::new_dbus(0); - let encoded = to_bytes(ctxt, &StateSet::new(State::ManagesDescendants)).unwrap(); + let encoded = to_bytes(ctxt, &StateSet(State::ManagesDescendants.into())).unwrap(); assert_eq!(encoded, &[8, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0]); } @@ -370,13 +366,13 @@ mod tests { fn deserialize_state_set_manages_descendants() { let ctxt = Context::::new_dbus(0); let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0], ctxt).unwrap(); - assert_eq!(decoded, StateSet::new(State::ManagesDescendants)); + assert_eq!(decoded, StateSet(State::ManagesDescendants.into())); } #[test] fn serialize_state_set_indeterminate() { let ctxt = Context::::new_dbus(0); - let encoded = to_bytes(ctxt, &StateSet::new(State::Indeterminate)).unwrap(); + let encoded = to_bytes(ctxt, &StateSet(State::Indeterminate.into())).unwrap(); assert_eq!(encoded, &[8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]); } @@ -384,13 +380,13 @@ mod tests { fn deserialize_state_set_indeterminate() { let ctxt = Context::::new_dbus(0); let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], ctxt).unwrap(); - assert_eq!(decoded, StateSet::new(State::Indeterminate)); + assert_eq!(decoded, StateSet(State::Indeterminate.into())); } #[test] fn serialize_state_set_focusable_focused() { let ctxt = Context::::new_dbus(0); - let encoded = to_bytes(ctxt, &StateSet::new(State::Focusable | State::Focused)).unwrap(); + let encoded = to_bytes(ctxt, &StateSet(State::Focusable | State::Focused)).unwrap(); assert_eq!(encoded, &[8, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0]); } @@ -398,7 +394,7 @@ mod tests { fn deserialize_state_set_focusable_focused() { let ctxt = Context::::new_dbus(0); let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0], ctxt).unwrap(); - assert_eq!(decoded, StateSet::new(State::Focusable | State::Focused)); + assert_eq!(decoded, StateSet(State::Focusable | State::Focused)); } #[test] diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index d5cb9ea92..343c38713 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -33,13 +33,6 @@ pub(crate) fn filter(node: &Node) -> FilterResult { FilterResult::Include } -fn filter_with_root_exception(node: &Node) -> FilterResult { - if node.is_root() { - return FilterResult::Include; - } - filter(node) -} - pub(crate) struct NodeWrapper<'a> { node: &'a Node<'a>, } @@ -50,10 +43,7 @@ impl<'a> NodeWrapper<'a> { } pub fn name(&self) -> String { - self.node - .name() - .map(|name| name.to_string()) - .unwrap_or(String::new()) + self.node.name().unwrap_or_default() } pub fn description(&self) -> String { diff --git a/platforms/winit/src/platform_impl/linux.rs b/platforms/winit/src/platform_impl/linux.rs index 2851b9078..b476b175b 100755 --- a/platforms/winit/src/platform_impl/linux.rs +++ b/platforms/winit/src/platform_impl/linux.rs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file). -use accesskit::kurbo::Rect; use accesskit::{ActionHandler, TreeUpdate}; use accesskit_linux::Adapter as LinuxAdapter; use winit::window::Window; @@ -13,7 +12,7 @@ pub struct Adapter { impl Adapter { pub fn new( - window: &Window, + _: &Window, source: Box TreeUpdate>, action_handler: Box, ) -> Self { From e204ef958d0117815c8028fead202ec324591920 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sun, 13 Nov 2022 23:43:51 +0100 Subject: [PATCH 38/69] Start using the atspi crate --- platforms/linux/Cargo.toml | 6 +- platforms/linux/src/adapter.rs | 20 +- platforms/linux/src/atspi/bus.rs | 54 ++- .../linux/src/atspi/interfaces/accessible.rs | 12 +- .../linux/src/atspi/interfaces/events.rs | 17 +- platforms/linux/src/atspi/interfaces/mod.rs | 134 +---- platforms/linux/src/atspi/mod.rs | 459 +----------------- platforms/linux/src/atspi/object_address.rs | 9 + platforms/linux/src/atspi/proxies.rs | 47 -- platforms/linux/src/atspi/state.rs | 413 ---------------- platforms/linux/src/node.rs | 11 +- 11 files changed, 65 insertions(+), 1117 deletions(-) delete mode 100644 platforms/linux/src/atspi/proxies.rs delete mode 100644 platforms/linux/src/atspi/state.rs diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index 33c00104d..dbb96d2b9 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -6,13 +6,9 @@ edition = "2018" [dependencies] accesskit = { version = "0.7.0", path = "../../common" } accesskit_consumer = { version = "0.7.1", path = "../../consumer" } -enumflags2 = "0.7.1" +atspi = { git = "https://github.com/DataTriny/odilia", branch = "atspi_events" } parking_lot = "0.11.1" serde = "1.0" strum = { version = "0.23.0", features = ["derive"] } zbus = "3.0.0" zvariant = "3.6.0" - -[dev-dependencies] -byteorder = "1.4.3" -winit = "0.27.2" diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 746226836..17166feff 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -1,22 +1,20 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use std::sync::Arc; - -use accesskit::{ActionHandler, NodeId, Role, TreeUpdate}; -use accesskit_consumer::{Node, Tree, TreeChangeHandler}; - use crate::atspi::{ interfaces::{ - AccessibleInterface, ActionInterface, Interface, Interfaces, ObjectEvent, QueuedEvent, - ValueInterface, WindowEvent, + AccessibleInterface, ActionInterface, ObjectEvent, QueuedEvent, ValueInterface, WindowEvent, }, - Bus, ObjectId, State, ACCESSIBLE_PATH_PREFIX, + Bus, ObjectId, ACCESSIBLE_PATH_PREFIX, }; use crate::node::{filter, AppState, NodeWrapper, PlatformNode, PlatformRootNode}; +use accesskit::{ActionHandler, NodeId, Role, TreeUpdate}; +use accesskit_consumer::{Node, Tree, TreeChangeHandler}; +use atspi::{Interface, InterfaceSet, State}; use parking_lot::RwLock; +use std::sync::Arc; pub struct Adapter { atspi_bus: Option, @@ -78,7 +76,7 @@ impl Adapter { &self, tree: &Arc, id: NodeId, - new_interfaces: Interfaces, + new_interfaces: InterfaceSet, ) -> zbus::Result { self.atspi_bus.as_ref().map_or(Ok(false), |bus| { let atspi_id = ObjectId::from(id); @@ -105,7 +103,7 @@ impl Adapter { fn unregister_interfaces( &self, id: &ObjectId, - old_interfaces: Interfaces, + old_interfaces: InterfaceSet, ) -> zbus::Result { self.atspi_bus.as_ref().map_or(Ok(false), |bus| { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str()); diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index ca8791201..a3a0760e4 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -3,13 +3,10 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{ - interfaces::*, - object_address::*, - proxies::{BusProxy, SocketProxy}, - ObjectId, ObjectRef, -}; +use crate::atspi::{interfaces::*, object_address::*, ObjectId, ObjectRef}; use crate::PlatformRootNode; +use atspi::{bus::BusProxyBlocking, socket::SocketProxyBlocking, EventBody}; +use serde::Serialize; use std::{ collections::HashMap, convert::{AsRef, TryInto}, @@ -20,18 +17,18 @@ use zbus::{ names::{BusName, InterfaceName, MemberName, OwnedUniqueName}, Address, Result, }; -use zvariant::{OwnedValue, Str, Value}; +use zvariant::{ObjectPath, Str, Value}; #[derive(Clone)] pub(crate) struct Bus { conn: Connection, - socket_proxy: SocketProxy<'static>, + socket_proxy: SocketProxyBlocking<'static>, } impl Bus { pub fn a11y_bus() -> Option { let conn = a11y_bus()?; - let socket_proxy = SocketProxy::new(&conn).ok()?; + let socket_proxy = SocketProxyBlocking::new(&conn).ok()?; Some(Bus { conn, socket_proxy }) } @@ -65,11 +62,12 @@ impl Bus { AccessibleInterface::new(self.unique_name().to_owned(), node.clone()), )?; if registered { - let desktop_address = self - .socket_proxy - .embed(ObjectAddress::root(self.unique_name().as_ref()))?; + let desktop = self.socket_proxy.embed(&( + self.unique_name().as_str(), + ObjectPath::from_str_unchecked(ROOT_PATH), + ))?; if let Some(state) = node.state.upgrade() { - state.write().desktop_address = Some(desktop_address); + state.write().desktop_address = Some(desktop.into()); } Ok(true) } else { @@ -86,8 +84,8 @@ impl Bus { target, interface, signal, - EventData { - minor: state.as_ref(), + EventBody { + kind: state, detail1: *value as i32, detail2: 0, any_data: 0i32.into(), @@ -98,8 +96,13 @@ impl Bus { target, interface, signal, - EventData { - minor: property.as_ref(), + EventBody { + kind: match property { + Property::Name(_) => "accessible-name", + Property::Description(_) => "accessible-description", + Property::Parent(_) => "accessible-parent", + Property::Role(_) => "accessible-role", + }, detail1: 0, detail2: 0, any_data: match property { @@ -119,7 +122,7 @@ impl Bus { OwnedObjectAddress::from(ObjectAddress::root(self.unique_name().into())) .into() } - Property::Role(value) => OwnedValue::from(*value as u32), + Property::Role(value) => Value::U32(*value as u32), }, properties, }, @@ -137,22 +140,22 @@ impl Bus { target, "org.a11y.atspi.Event.Window", event.as_ref(), - EventData { - minor: "", + EventBody { + kind: "", detail1: 0, detail2: 0, - any_data: Value::from(window_name).into(), + any_data: window_name.into(), properties: HashMap::new(), }, ) } - fn emit_event( + fn emit_event( &self, id: &ObjectId, interface: &str, signal_name: &str, - body: EventData, + body: EventBody, ) -> Result<()> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str()); self.conn.emit_signal( @@ -170,7 +173,10 @@ fn a11y_bus() -> Option { Ok(address) if !address.is_empty() => address, _ => { let session_bus = Connection::session().ok()?; - BusProxy::new(&session_bus).ok()?.get_address().ok()? + BusProxyBlocking::new(&session_bus) + .ok()? + .get_address() + .ok()? } }; let address: Address = address.as_str().try_into().ok()?; diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 1b1ea994a..4c614c77f 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -3,11 +3,9 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{ - interfaces::{Interface, Interfaces}, - ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Role, StateSet, -}; +use crate::atspi::{ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress}; use crate::{PlatformNode, PlatformRootNode}; +use atspi::{accessible::Role, Interface, InterfaceSet, StateSet}; use std::convert::TryInto; use zbus::{fdo, names::OwnedUniqueName}; @@ -104,7 +102,7 @@ impl AccessibleInterface { (ObjectAddress::root(self.bus_name.as_ref()).into(),) } - fn get_interfaces(&self) -> fdo::Result { + fn get_interfaces(&self) -> fdo::Result { self.node.interfaces() } } @@ -198,7 +196,7 @@ impl AccessibleInterface { (ObjectAddress::root(self.bus_name.as_ref()).into(),) } - fn get_interfaces(&self) -> Interfaces { - Interfaces::new(Interface::Accessible | Interface::Application) + fn get_interfaces(&self) -> InterfaceSet { + InterfaceSet::new(Interface::Accessible | Interface::Application) } } diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/linux/src/atspi/interfaces/events.rs index d36ce5d70..f5a89f3b7 100644 --- a/platforms/linux/src/atspi/interfaces/events.rs +++ b/platforms/linux/src/atspi/interfaces/events.rs @@ -1,13 +1,11 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{ObjectId, ObjectRef, Role, State}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use crate::atspi::{ObjectId, ObjectRef}; +use atspi::{accessible::Role, State}; use strum::AsRefStr; -use zvariant::{OwnedValue, Type}; pub(crate) enum QueuedEvent { Object { @@ -47,12 +45,3 @@ pub(crate) enum WindowEvent { #[strum(serialize = "Deactivate")] Deactivated, } - -#[derive(Deserialize, Serialize, Type)] -pub(crate) struct EventData<'a> { - pub minor: &'a str, - pub detail1: i32, - pub detail2: i32, - pub any_data: OwnedValue, - pub properties: HashMap, -} diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index fb356f361..cab510a13 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. @@ -9,138 +9,6 @@ mod application; mod events; mod value; -use enumflags2::{bitflags, BitFlags, FromBitsError}; -use serde::{ - de::{self, Deserialize, Deserializer, SeqAccess, Visitor}, - ser::{Serialize, SerializeSeq, Serializer}, -}; -use std::fmt; -use zvariant::{Signature, Type}; - -#[bitflags] -#[repr(u32)] -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] -pub(crate) enum Interface { - Accessible, - Application, - Action, - Value, -} - -#[derive(Clone, Copy, Debug)] -pub(crate) struct Interfaces(BitFlags); - -impl Interfaces { - pub fn new>>(value: B) -> Self { - Self(value.into()) - } - - pub fn from_bits(bits: u32) -> Result> { - Ok(Interfaces(BitFlags::from_bits(bits)?)) - } - - pub fn contains>>(self, other: B) -> bool { - self.0.contains(other) - } - - pub fn insert>>(&mut self, other: B) { - self.0.insert(other); - } - - pub fn iter(self) -> impl Iterator { - self.0.iter() - } -} - -const INTERFACE_NAMES: &[&str] = &[ - "org.a11y.atspi.Accessible", - "org.a11y.atspi.Application", - "org.a11y.atspi.Action", - "org.a11y.atspi.Value", -]; - -impl<'de> Deserialize<'de> for Interfaces { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct InterfacesVisitor; - - impl<'de> Visitor<'de> for InterfacesVisitor { - type Value = Interfaces; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a sequence comprised of D-Bus interface names") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - match SeqAccess::next_element::>(&mut seq)? { - Some(names) => { - let mut bits = 0; - for name in names { - if let Ok(index) = INTERFACE_NAMES.binary_search(&name.as_str()) { - bits &= 2u32.pow(index as u32); - } - } - Ok(Interfaces::from_bits(bits).unwrap()) - } - None => Err(de::Error::custom("Vec containing D-Bus interface names")), - } - } - } - - deserializer.deserialize_seq(InterfacesVisitor) - } -} - -impl Serialize for Interfaces { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut interfaces = Vec::with_capacity(INTERFACE_NAMES.len()); - for interface in self.iter() { - interfaces.push(INTERFACE_NAMES[(interface as u32).trailing_zeros() as usize]); - } - let mut seq = serializer.serialize_seq(Some(interfaces.len()))?; - for interface in interfaces { - seq.serialize_element(interface)?; - } - seq.end() - } -} - -impl Type for Interfaces { - fn signature() -> Signature<'static> { - Signature::from_str_unchecked("as") - } -} - -impl From for Interfaces { - fn from(value: Interface) -> Self { - Self(value.into()) - } -} - -impl std::ops::BitAnd for Interfaces { - type Output = Interfaces; - - fn bitand(self, other: Self) -> Self::Output { - Interfaces(self.0 & other.0) - } -} - -impl std::ops::BitXor for Interfaces { - type Output = Interfaces; - - fn bitxor(self, other: Self) -> Self::Output { - Interfaces(self.0 ^ other.0) - } -} - pub(crate) use accessible::*; pub(crate) use action::*; pub(crate) use application::*; diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs index a7bf02975..5f0fd4241 100644 --- a/platforms/linux/src/atspi/mod.rs +++ b/platforms/linux/src/atspi/mod.rs @@ -1,472 +1,15 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use serde::{Deserialize, Serialize}; -use zvariant::Type; - mod bus; pub(crate) mod interfaces; mod object_address; mod object_id; mod object_ref; -pub(crate) mod proxies; -mod state; - -/// Enumeration used by interface #AtspiAccessible to specify the role -/// of an #AtspiAccessible object. -#[repr(u32)] -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, Type)] -pub(crate) enum Role { - /// A role indicating an error condition, such as - /// uninitialized Role data. - Invalid, - /// Object is a label indicating the keyboard - /// accelerators for the parent. - AcceleratorLabel, - /// Object is used to alert the user about something. - Alert, - /// Object contains a dynamic or moving image of some kind. - Animation, - /// Object is a 2d directional indicator. - Arrow, - /// Object contains one or more dates, usually arranged - /// into a 2d list. - Calendar, - /// Object that can be drawn into and is used to trap events. - Canvas, - /// A choice that can be checked or unchecked and - /// provides a separate indicator for the current state. - CheckBox, - /// A menu item that behaves like a check box. See @CHECK_BOX. - CheckMenuItem, - /// A specialized dialog that lets the user choose a color. - ColorChooser, - /// The header for a column of data. - ColumnHeader, - /// A list of choices the user can select from. - ComboBox, - /// An object which allows entry of a date. - DateEditor, - /// An inconifed internal frame within a DESKTOP_PANE. - DesktopIcon, - /// A pane that supports internal frames and - /// iconified versions of those internal frames. - DesktopFrame, - /// An object that allows a value to be changed via rotating a - /// visual element, or which displays a value via such a rotating element. - Dial, - /// A top level window with title bar and a border. - Dialog, - /// A pane that allows the user to navigate through - /// and select the contents of a directory. - DirectoryPane, - /// A specialized dialog that displays the files in - /// the directory and lets the user select a file, browse a different - /// directory, or specify a filename. - DrawingArea, - /// An object used for drawing custom user interface elements. - FileChooser, - /// A object that fills up space in a user interface. - Filler, - /// Don't use, reserved for future use. - FocusTraversable, - /// Allows selection of a display font. - FontChooser, - /// A top level window with a title bar, border, menubar, etc. - Frame, - /// A pane that is guaranteed to be painted on top of - /// all panes beneath it. - GlassPane, - /// A document container for HTML, whose children - /// represent the document content. - HtmlContainer, - /// A small fixed size picture, typically used to decorate - /// components. - Icon, - /// An image, typically static. - Image, - /// A frame-like object that is clipped by a desktop pane. - InternalFrame, - /// An object used to present an icon or short string in an interface. - Label, - /// A specialized pane that allows its children to be - /// drawn in layers, providing a form of stacking order. - LayeredPane, - /// An object that presents a list of objects to the user and - /// allows the user to select one or more of them. - List, - /// An object that represents an element of a list. - ListItem, - /// An object usually found inside a menu bar that contains a - /// list of actions the user can choose from. - Menu, - /// An object usually drawn at the top of the primary - /// dialog box of an application that contains a list of menus the user can - /// choose from. - MenuBar, - /// An object usually contained in a menu that presents - /// an action the user can choose. - MenuItem, - /// A specialized pane whose primary use is inside a dialog. - OptionPane, - /// An object that is a child of a page tab list. - PageTab, - /// An object that presents a series of panels (or page tabs), - /// one at a time,through some mechanism provided by the object. - PageTabList, - /// A generic container that is often used to group objects. - Panel, - /// A text object uses for passwords, or other places - /// where the text content is not shown visibly to the user. - PasswordText, - /// A temporary window that is usually used to offer the - /// user a list of choices, and then hides when the user selects one of those - /// choices. - PopupMenu, - /// An object used to indicate how much of a task has been completed. - ProgressBar, - /// An object the user can manipulate to tell the - /// application to do something. - PushButton, - /// A specialized check box that will cause other radio buttons - /// in the same group to become unchecked when this one is checked. - RadioButton, - /// Object is both a menu item and a 'radio button' - /// See @RADIO_BUTTON. - RadioMenuItem, - /// A specialized pane that has a glass pane and a - /// layered pane as its children. - RootPane, - /// The header for a row of data. - RowHeader, - /// An object usually used to allow a user to - /// incrementally view a large amount of data by moving the bounds of a - /// viewport along a one-dimensional axis. - ScrollBar, - /// An object that allows a user to incrementally view - /// a large amount of information. @SCROLL_PANE objects are usually - /// accompanied by @SCROLL_BAR controllers, on which the - /// @RELATION_CONTROLLER_FOR and @RELATION_CONTROLLED_BY - /// reciprocal relations are set. See #get_relation_set. - ScrollPane, - /// An object usually contained in a menu to provide a - /// visible and logical separation of the contents in a menu. - Separator, - /// An object that allows the user to select from a bounded range. - Slider, - /// An object which allows one of a set of choices to - /// be selected, and which displays the current choice. Unlike - /// @SCROLL_BAR, @SLIDER objects need not control - /// 'viewport'-like objects. - SpinButton, - /// A specialized panel that presents two other panels - /// at the same time. - SplitPane, - /// Object displays non-quantitative status information - /// (c.f. @PROGRESS_BAR) - StatusBar, - /// An object used to repesent information in terms of rows and columns. - Table, - /// A 'cell' or discrete child within a Table. Note: - /// Table cells need not have @TABLE_CELL, other - /// #AtspiRoleType values are valid as well. - TableCell, - /// An object which labels a particular column - /// in an #AtspiTable. - TableColumnHeader, - /// An object which labels a particular row in a - /// #AtspiTable. #AtspiTable rows and columns may also be labelled via the - /// @RELATION_LABEL_FOR/@RELATION_LABELLED_BY relationships. - /// See #get_relation_set. - TableRowHeader, - /// Object allows menu to be removed from menubar - /// and shown in its own window. - TearoffMenuItem, - /// An object that emulates a terminal. - Terminal, - /// An interactive widget that supports multiple lines of text - /// and optionally accepts user input, but whose purpose is not to solicit user - /// input. Thus @TEXT is appropriate for the text view in a plain text - /// editor but inappropriate for an input field in a dialog box or web form. For - /// widgets whose purpose is to solicit input from the user, see @ENTRY - /// and @PASSWORD_TEXT. For generic objects which display a brief amount - /// of textual information, see @STATIC. - Text, - /// A specialized push button that can be checked or - /// unchecked, but does not procide a separate indicator for the current - /// state. - ToggleButton, - /// A bar or palette usually composed of push buttons or - /// toggle buttons. - ToolBar, - /// An object that provides information about another object. - ToolTip, - /// An object used to repsent hierarchical information to the user. - Tree, - /// An object that presents both tabular and - /// hierarchical info to the user. - TreeTable, - /// The object contains some #AtspiAccessible information, - /// but its role is not known. - Unknown, - /// An object usually used in a scroll pane, or to - /// otherwise clip a larger object or content renderer to a specific - /// onscreen viewport. - Viewport, - /// A top level window with no title or border. - Window, - /// Means that the role for this item is known, but not - /// included in the core enumeration. Deprecated since 2.24. - Extended, - /// An object that serves as a document header. - Header, - /// An object that serves as a document footer. - Footer, - /// An object which is contains a single paragraph of - /// text content. See also @TEXT. - Paragraph, - /// An object which describes margins and tab stops, etc. - /// for text objects which it controls (should have - /// @RELATION_CONTROLLER_FOR relation to such). - Ruler, - /// An object corresponding to the toplevel accessible - /// of an application, which may contain @FRAME objects or other - /// accessible objects. Children of #AccessibleDesktop objects are generally - /// @APPLICATION objects. - Application, - /// The object is a dialog or list containing items - /// for insertion into an entry widget, for instance a list of words for - /// completion of a text entry. - Autocomplete, - /// The object is an editable text object in a toolbar. - Editbar, - /// The object is an embedded component container. This - /// role is a "grouping" hint that the contained objects share a context - /// which is different from the container in which this accessible is - /// embedded. In particular, it is used for some kinds of document embedding, - /// and for embedding of out-of-process component, "panel applets", etc. - Embedded, - /// The object is a component whose textual content may be - /// entered or modified by the user, provided @STATE_EDITABLE is present. - /// A readonly @ENTRY object (i.e. where @STATE_EDITABLE is - /// not present) implies a read-only 'text field' in a form, as opposed to a - /// title, label, or caption. - Entry, - /// The object is a graphical depiction of quantitative data. - /// It may contain multiple subelements whose attributes and/or description - /// may be queried to obtain both the quantitative data and information about - /// how the data is being presented. The @LABELLED_BY relation is - /// particularly important in interpreting objects of this type, as is the - /// accessible description property. See @CAPTION. - Chart, - /// The object contains descriptive information, usually - /// textual, about another user interface element such as a table, chart, or - /// image. - Caption, - /// The object is a visual frame or container which - /// contains a view of document content. #AtspiDocument frames may occur within - /// another #AtspiDocument instance, in which case the second document may be - /// said to be embedded in the containing instance. HTML frames are often - /// DOCUMENT_FRAME: Either this object, or a singleton descendant, - /// should implement the #AtspiDocument interface. - DocumentFrame, - /// The object serves as a heading for content which - /// follows it in a document. The 'heading level' of the heading, if - /// availabe, may be obtained by querying the object's attributes. - Heading, - /// The object is a containing instance which encapsulates a - /// page of information. @PAGE is used in documents and content which - /// support a paginated navigation model. - Page, - /// The object is a containing instance of document content - /// which constitutes a particular 'logical' section of the document. The - /// type of content within a section, and the nature of the section division - /// itself, may be obtained by querying the object's attributes. Sections - /// may be nested. - Section, - /// The object is redundant with another object in - /// the hierarchy, and is exposed for purely technical reasons. Objects of - /// this role should be ignored by clients, if they are encountered at all. - RedundantObject, - /// The object is a containing instance of document content - /// which has within it components with which the user can interact in order - /// to input information; i.e. the object is a container for pushbuttons, - /// comboboxes, text input fields, and other 'GUI' components. @FORM - /// should not, in general, be used for toplevel GUI containers or dialogs, - /// but should be reserved for 'GUI' containers which occur within document - /// content, for instance within Web documents, presentations, or text - /// documents. Unlike other GUI containers and dialogs which occur inside - /// application instances, @FORM containers' components are - /// associated with the current document, rather than the current foreground - /// application or viewer instance. - Form, - /// The object is a hypertext anchor, i.e. a "link" in a - /// hypertext document. Such objects are distinct from 'inline' content - /// which may also use the #AtspiHypertext/#AtspiHyperlink interfacesto indicate - /// the range/location within a text object where an inline or embedded object - /// lies. - Link, - /// The object is a window or similar viewport - /// which is used to allow composition or input of a 'complex character', - /// in other words it is an "input method window". - InputMethodWindow, - /// A row in a table. - TableRow, - /// An object that represents an element of a tree. - TreeItem, - /// A document frame which contains a spreadsheet. - DocumentSpreadsheet, - /// A document frame which contains a - /// presentation or slide content. - DocumentPresentation, - /// A document frame which contains textual content, - /// such as found in a word processing application. - DocumentText, - /// A document frame which contains HTML or other - /// markup suitable for display in a web browser. - DocumentWeb, - /// A document frame which contains email content - /// to be displayed or composed either in plain text or HTML. - DocumentEmail, - /// An object found within a document and designed to - /// present a comment, note, or other annotation. In some cases, this object - /// might not be visible until activated. - Comment, - /// A non-collapsible list of choices the user can select from. - ListBox, - /// A group of related widgets. This group typically has a label. - Grouping, - /// An image map object. Usually a graphic with multiple - /// hotspots, where each hotspot can be activated resulting in the loading of - /// another document or section of a document. - ImageMap, - /// A transitory object designed to present a - /// message to the user, typically at the desktop level rather than inside a - /// particular application. - Notification, - /// An object designed to present a message to the user - /// within an existing window. - InfoBar, - /// A bar that serves as a level indicator to, for - /// instance, show the strength of a password or the state of a battery. @Since: 2.8 - LevelBar, - /// A bar that serves as the title of a window or a - /// dialog. @Since: 2.12 - TitleBar, - /// An object which contains a text section - /// that is quoted from another source. @Since: 2.12 - BlockQuote, - /// An object which represents an audio - /// element. @Since: 2.12 - Audio, - /// An object which represents a video element. @Since: 2.12 - Video, - /// A definition of a term or concept. @Since: 2.12 - Definition, - /// A section of a page that consists of a - /// composition that forms an independent part of a document, page, or - /// site. Examples: A blog entry, a news story, a forum post. - /// @Since: 2.12 - Article, - /// A region of a web page intended as a - /// navigational landmark. This is designed to allow Assistive - /// Technologies to provide quick navigation among key regions within a - /// document. @Since: 2.12 - Landmark, - /// A text widget or container holding log content, such - /// as chat history and error logs. In this role there is a - /// relationship between the arrival of new items in the log and the - /// reading order. The log contains a meaningful sequence and new - /// information is added only to the end of the log, not at arbitrary - /// points. @Since: 2.12 - Log, - /// A container where non-essential information - /// changes frequently. Common usages of marquee include stock tickers - /// and ad banners. The primary difference between a marquee and a log - /// is that logs usually have a meaningful order or sequence of - /// important content changes. @Since: 2.12 - Marquee, - /// A text widget or container that holds a mathematical - /// expression. @Since: 2.12 - Math, - /// A widget whose purpose is to display a rating, - /// such as the number of stars associated with a song in a media - /// player. Objects of this role should also implement - /// AtspiValue. @Since: 2.12 - Rating, - /// An object containing a numerical counter which - /// indicates an amount of elapsed time from a start point, or the time - /// remaining until an end point. @Since: 2.12 - Timer, - /// A generic non-container object whose purpose is to display - /// a brief amount of information to the user and whose role is known by the - /// implementor but lacks semantic value for the user. Examples in which - /// @STATIC is appropriate include the message displayed in a message - /// box and an image used as an alternative means to display text. - /// @STATIC should not be applied to widgets which are traditionally - /// interactive, objects which display a significant amount of content, or any - /// object which has an accessible relation pointing to another object. The - /// displayed information, as a general rule, should be exposed through the - /// accessible name of the object. For labels which describe another widget, see - /// @LABEL. For text views, see @TEXT. For generic - /// containers, see @PANEL. For objects whose role is not known by the - /// implementor, see @UNKNOWN. @Since: 2.16. - Static, - /// An object that represents a mathematical fraction. @Since: 2.16. - MathFraction, - /// An object that represents a mathematical expression - /// displayed with a radical. @Since: 2.16. - MathRoot, - /// An object that contains text that is displayed as a - /// subscript. @Since: 2.16. - Subscript, - /// An object that contains text that is displayed as a - /// superscript. @Since: 2.16. - Superscript, - /// An object that represents a list of term-value - /// groups. A term-value group represents an individual description and consist - /// of one or more names (@DESCRIPTION_TERM) followed by one or more - /// values (@DESCRIPTION_VALUE). For each list, there should not be - /// more than one group with the same term name. @Since: 2.26. - DescriptionList, - /// An object that represents a term or phrase - /// with a corresponding definition. @Since: 2.26. - DescriptionTerm, - /// An object that represents the description, - /// definition, or value of a term. @Since: 2.26. - DescriptionValue, - /// An object that contains the text of a footnote. @Since: 2.26. - Footnote, - /// Content previously deleted or proposed to be - /// deleted, e.g. in revision history or a content view providing suggestions - /// from reviewers. @Since: 2.34. - ContentDeletion, - /// Content previously inserted or proposed to be - /// inserted, e.g. in revision history or a content view providing suggestions - /// from reviewers. @Since: 2.34. - ContentInsertion, - /// A run of content that is marked or highlighted, such as for - /// reference purposes, or to call it out as having a special purpose. If the - /// marked content has an associated section in the document elaborating on the - /// reason for the mark, then %RELATION_DETAILS should be used on the mark - /// to point to that associated section. In addition, the reciprocal relation - /// %RELATION_DETAILS_FOR should be used on the associated content section - /// to point back to the mark. @Since: 2.36. - Mark, - /// A container for content that is called out as a proposed - /// change from the current version of the document, such as by a reviewer of the - /// content. This role should include either %CONTENT_DELETION and/or - /// %CONTENT_INSERTION children, in any order, to indicate what the - /// actual change is. @Since: 2.36 - Suggestion, - /// Not a valid role, used for finding end of enumeration. - LastDefined, -} pub(crate) use bus::Bus; pub(crate) use object_address::*; pub(crate) use object_id::*; pub(crate) use object_ref::*; -pub(crate) use state::*; diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs index 5c07f192e..374e00a4e 100644 --- a/platforms/linux/src/atspi/object_address.rs +++ b/platforms/linux/src/atspi/object_address.rs @@ -65,3 +65,12 @@ impl From> for OwnedObjectAddress { } } } + +impl From<(String, OwnedObjectPath)> for OwnedObjectAddress { + fn from(value: (String, OwnedObjectPath)) -> Self { + Self { + bus_name: OwnedUniqueName::from(UniqueName::from_string_unchecked(value.0)), + path: value.1, + } + } +} diff --git a/platforms/linux/src/atspi/proxies.rs b/platforms/linux/src/atspi/proxies.rs deleted file mode 100644 index 61f7abf2f..000000000 --- a/platforms/linux/src/atspi/proxies.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. -// Licensed under the Apache License, Version 2.0 (found in -// the LICENSE-APACHE file) or the MIT license (found in -// the LICENSE-MIT file), at your option. - -use crate::atspi::{ObjectAddress, OwnedObjectAddress}; -use zbus::{dbus_proxy, Result}; - -#[dbus_proxy( - default_service = "org.a11y.Bus", - default_path = "/org/a11y/bus", - interface = "org.a11y.Bus", - gen_async = false -)] -pub(crate) trait Bus { - fn get_address(&self) -> Result; -} - -#[dbus_proxy( - default_path = "/org/a11y/atspi/accessible/root", - default_service = "org.a11y.atspi.Registry", - gen_async = false, - interface = "org.a11y.atspi.Socket" -)] -pub(crate) trait Socket { - fn embed<'a>(&self, plug: ObjectAddress<'a>) -> Result; - - fn unembed<'a>(&self, plug: ObjectAddress<'a>) -> Result<()>; - - #[dbus_proxy(signal)] - fn available(&self, socket: ObjectAddress<'_>) -> Result<()>; -} - -#[dbus_proxy(interface = "org.a11y.Status")] -pub(crate) trait Status { - #[dbus_proxy(property)] - fn is_enabled(&self) -> Result; - - #[DBusProxy(property)] - fn set_is_enabled(&self, value: bool) -> Result<()>; - - #[dbus_proxy(property)] - fn screen_reader_enabled(&self) -> Result; - - #[DBusProxy(property)] - fn set_screen_reader_enabled(&self, value: bool) -> Result<()>; -} diff --git a/platforms/linux/src/atspi/state.rs b/platforms/linux/src/atspi/state.rs deleted file mode 100644 index c308454a5..000000000 --- a/platforms/linux/src/atspi/state.rs +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. -// Licensed under the Apache License, Version 2.0 (found in -// the LICENSE-APACHE file) or the MIT license (found in -// the LICENSE-MIT file), at your option. - -use enumflags2::{bitflags, BitFlag, BitFlags, FromBitsError}; -use serde::{ - de::{self, Deserialize, Deserializer, Visitor}, - ser::{Serialize, SerializeSeq, Serializer}, -}; -use std::fmt; -use strum::AsRefStr; -use zvariant::{Signature, Type}; - -/// Enumeration used by various interfaces indicating every possible state -/// an #AtspiAccessible object can assume. -#[bitflags] -#[repr(u64)] -#[derive(AsRefStr, Clone, Copy, Debug)] -#[strum(serialize_all = "kebab-case")] -pub(crate) enum State { - /// Indicates an invalid state - probably an error condition. - Invalid, - /// Indicates a window is currently the active window, or - /// an object is the active subelement within a container or table. - /// @ACTIVE should not be used for objects which have - /// #FOCUSABLE or #SELECTABLE: Those objects should use - /// @FOCUSED and @SELECTED respectively. - /// @ACTIVE is a means to indicate that an object which is not - /// focusable and not selectable is the currently-active item within its - /// parent container. - Active, - /// Indicates that the object is armed. - Armed, - /// Indicates the current object is busy, i.e. onscreen - /// representation is in the process of changing, or the object is - /// temporarily unavailable for interaction due to activity already in progress. - Busy, - /// Indicates this object is currently checked. - Checked, - /// Indicates this object is collapsed. - Collapsed, - /// Indicates that this object no longer has a valid - /// backing widget (for instance, if its peer object has been destroyed). - Defunct, - /// Indicates the user can change the contents of this object. - Editable, - /// Indicates that this object is enabled, i.e. that it - /// currently reflects some application state. Objects that are "greyed out" - /// may lack this state, and may lack the @SENSITIVE if direct - /// user interaction cannot cause them to acquire @ENABLED. - /// See @SENSITIVE. - Enabled, - /// Indicates this object allows progressive - /// disclosure of its children. - Expandable, - /// Indicates this object is expanded. - Expanded, - /// Indicates this object can accept keyboard focus, - /// which means all events resulting from typing on the keyboard will - /// normally be passed to it when it has focus. - Focusable, - /// Indicates this object currently has the keyboard focus. - Focused, - /// Indicates that the object has an associated tooltip. - HasTooltip, - /// Indicates the orientation of this object is horizontal. - Horizontal, - /// Indicates this object is minimized and is - /// represented only by an icon. - Iconified, - /// Indicates something must be done with this object - /// before the user can interact with an object in a different window. - Modal, - /// Indicates this (text) object can contain multiple - /// lines of text. - MultiLine, - /// Indicates this object allows more than one of - /// its children to be selected at the same time, or in the case of text - /// objects, that the object supports non-contiguous text selections. - Multiselectable, - /// Indicates this object paints every pixel within its - /// rectangular region. It also indicates an alpha value of unity, if it - /// supports alpha blending. - Opaque, - /// Indicates this object is currently pressed. - Pressed, - /// Indicates the size of this object's size is not fixed. - Resizable, - /// Indicates this object is the child of an object - /// that allows its children to be selected and that this child is one of - /// those children that can be selected. - Selectable, - /// Indicates this object is the child of an object that - /// allows its children to be selected and that this child is one of those - /// children that has been selected. - Selected, - /// Indicates this object is sensitive, e.g. to user - /// interaction. @SENSITIVE usually accompanies. - /// @ENABLED for user-actionable controls, but may be found in the - /// absence of @ENABLED if the current visible state of the control - /// is "disconnected" from the application state. In such cases, direct user - /// interaction can often result in the object gaining @SENSITIVE, - /// for instance if a user makes an explicit selection using an object whose - /// current state is ambiguous or undefined. See @ENABLED, - /// @INDETERMINATE. - Sensitive, - /// Indicates this object, the object's parent, the - /// object's parent's parent, and so on, are all 'shown' to the end-user, - /// i.e. subject to "exposure" if blocking or obscuring objects do not - /// interpose between this object and the top of the window stack. - Showing, - /// Indicates this (text) object can contain only a - /// single line of text. - SingleLine, - /// Indicates that the information returned for this object - /// may no longer be synchronized with the application state. This can occur - /// if the object has @TRANSIENT, and can also occur towards the - /// end of the object peer's lifecycle. - Stale, - /// Indicates this object is transient. - Transient, - /// Indicates the orientation of this object is vertical; - /// for example this state may appear on such objects as scrollbars, text - /// objects (with vertical text flow), separators, etc. - Vertical, - /// Indicates this object is visible, e.g. has been - /// explicitly marked for exposure to the user. @VISIBLE is no - /// guarantee that the object is actually unobscured on the screen, only that - /// it is 'potentially' visible, barring obstruction, being scrolled or clipped - /// out of the field of view, or having an ancestor container that has not yet - /// made visible. A widget is potentially onscreen if it has both - /// @VISIBLE and @SHOWING. The absence of - /// @VISIBLE and @SHOWING is - /// semantically equivalent to saying that an object is 'hidden'. - Visible, - /// Indicates that "active-descendant-changed" - /// event is sent when children become 'active' (i.e. are selected or - /// navigated to onscreen). Used to prevent need to enumerate all children - /// in very large containers, like tables. The presence of - /// @MANAGES_DESCENDANTS is an indication to the client that the - /// children should not, and need not, be enumerated by the client. - /// Objects implementing this state are expected to provide relevant state - /// notifications to listening clients, for instance notifications of - /// visibility changes and activation of their contained child objects, without - /// the client having previously requested references to those children. - ManagesDescendants, - /// Indicates that a check box or other boolean - /// indicator is in a state other than checked or not checked. This - /// usually means that the boolean value reflected or controlled by the - /// object does not apply consistently to the entire current context. - /// For example, a checkbox for the "Bold" attribute of text may have - /// @INDETERMINATE if the currently selected text contains a mixture - /// of weight attributes. In many cases interacting with a - /// @INDETERMINATE object will cause the context's corresponding - /// boolean attribute to be homogenized, whereupon the object will lose - /// @INDETERMINATE and a corresponding state-changed event will be - /// fired. - Indeterminate, - /// Indicates that user interaction with this object is - /// 'required' from the user, for instance before completing the - /// processing of a form. - Required, - /// Indicates that an object's onscreen content - /// is truncated, e.g. a text value in a spreadsheet cell. - Truncated, - /// Indicates this object's visual representation is - /// dynamic, not static. This state may be applied to an object during an - /// animated 'effect' and be removed from the object once its visual - /// representation becomes static. Some applications, notably content viewers, - /// may not be able to detect all kinds of animated content. Therefore the - /// absence of this state should not be taken as - /// definitive evidence that the object's visual representation is - /// static; this state is advisory. - Animated, - /// This object has indicated an error condition - /// due to failure of input validation. For instance, a form control may - /// acquire this state in response to invalid or malformed user input. - InvalidEntry, - /// This state indicates that the object - /// in question implements some form of typeahead or - /// pre-selection behavior whereby entering the first character of one or more - /// sub-elements causes those elements to scroll into view or become - /// selected. Subsequent character input may narrow the selection further as - /// long as one or more sub-elements match the string. This state is normally - /// only useful and encountered on objects that implement #AtspiSelection. - /// In some cases the typeahead behavior may result in full or partial - /// completion of the data in the input field, in which case - /// these input events may trigger text-changed events from the source. - SupportsAutocompletion, - /// This state indicates that the object in - /// question supports text selection. It should only be exposed on objects - /// which implement the #AtspiText interface, in order to distinguish this state - /// from @SELECTABLE, which infers that the object in question is a - /// selectable child of an object which implements #AtspiSelection. While - /// similar, text selection and subelement selection are distinct operations. - SelectableText, - /// This state indicates that the object in question is - /// the 'default' interaction object in a dialog, i.e. the one that gets - /// activated if the user presses "Enter" when the dialog is initially - /// posted. - IsDefault, - /// This state indicates that the object (typically a - /// hyperlink) has already been activated or invoked, with the result that - /// some backing data has been downloaded or rendered. - Visited, - /// Indicates this object has the potential to - /// be checked, such as a checkbox or toggle-able table cell. @Since: - /// 2.12 - Checkable, - /// Indicates that the object has a popup - /// context menu or sub-level menu which may or may not be - /// showing. This means that activation renders conditional content. - /// Note that ordinary tooltips are not considered popups in this - /// context. @Since: 2.12 - HasPopup, - /// Indicates that an object which is ENABLED and - /// SENSITIVE has a value which can be read, but not modified, by the - /// user. @Since: 2.16 - ReadOnly, - /// This value of the enumeration should not be used - /// as a parameter, it indicates the number of items in the #AtspiStateType - /// enumeration. - LastDefined, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub(crate) struct StateSet(BitFlags); - -impl StateSet { - pub fn from_bits(bits: u64) -> Result> { - Ok(StateSet(BitFlags::from_bits(bits)?)) - } - - pub fn empty() -> StateSet { - StateSet(State::empty()) - } - - pub fn bits(&self) -> u64 { - self.0.bits() - } - - pub fn contains>>(self, other: B) -> bool { - self.0.contains(other) - } - - pub fn insert>>(&mut self, other: B) { - self.0.insert(other); - } - - pub fn iter(self) -> impl Iterator { - self.0.iter() - } -} - -impl<'de> Deserialize<'de> for StateSet { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct StateSetVisitor; - - impl<'de> Visitor<'de> for StateSetVisitor { - type Value = StateSet; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter - .write_str("a sequence comprised of two u32 that represents a valid StateSet") - } - - fn visit_newtype_struct(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - match as Deserialize>::deserialize(deserializer) { - Ok(states) if states.len() == 2 => { - let mut bits = states[0] as u64; - bits |= (states[1] as u64) << 32; - StateSet::from_bits(bits).map_err(|_| de::Error::custom("invalid state")) - } - Ok(states) => Err(de::Error::invalid_length(states.len(), &"array of size 2")), - Err(e) => Err(e), - } - } - } - - deserializer.deserialize_newtype_struct("StateSet", StateSetVisitor) - } -} - -impl Serialize for StateSet { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(Some(2))?; - let bits = self.bits(); - seq.serialize_element(&(bits as u32))?; - seq.serialize_element(&((bits >> 32) as u32))?; - seq.end() - } -} - -impl Type for StateSet { - fn signature() -> Signature<'static> { - as Type>::signature() - } -} - -impl From for StateSet { - fn from(value: State) -> Self { - Self(value.into()) - } -} - -impl std::ops::BitXor for StateSet { - type Output = StateSet; - - fn bitxor(self, other: Self) -> Self::Output { - StateSet(self.0 ^ other.0) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use byteorder::LE; - use zvariant::{from_slice, to_bytes, EncodingContext as Context}; - - #[test] - fn serialize_empty_state_set() { - let ctxt = Context::::new_dbus(0); - let encoded = to_bytes(ctxt, &StateSet::empty()).unwrap(); - assert_eq!(encoded, &[8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - } - - #[test] - fn deserialize_empty_state_set() { - let ctxt = Context::::new_dbus(0); - let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ctxt).unwrap(); - assert_eq!(decoded, StateSet::empty()); - } - - #[test] - fn serialize_state_set_invalid() { - let ctxt = Context::::new_dbus(0); - let encoded = to_bytes(ctxt, &StateSet(State::Invalid.into())).unwrap(); - assert_eq!(encoded, &[8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]); - } - - #[test] - fn deserialize_state_set_invalid() { - let ctxt = Context::::new_dbus(0); - let decoded: StateSet = from_slice(&[8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], ctxt).unwrap(); - assert_eq!(decoded, StateSet(State::Invalid.into())); - } - - #[test] - fn serialize_state_set_manages_descendants() { - let ctxt = Context::::new_dbus(0); - let encoded = to_bytes(ctxt, &StateSet(State::ManagesDescendants.into())).unwrap(); - assert_eq!(encoded, &[8, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0]); - } - - #[test] - fn deserialize_state_set_manages_descendants() { - let ctxt = Context::::new_dbus(0); - let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0], ctxt).unwrap(); - assert_eq!(decoded, StateSet(State::ManagesDescendants.into())); - } - - #[test] - fn serialize_state_set_indeterminate() { - let ctxt = Context::::new_dbus(0); - let encoded = to_bytes(ctxt, &StateSet(State::Indeterminate.into())).unwrap(); - assert_eq!(encoded, &[8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]); - } - - #[test] - fn deserialize_state_set_indeterminate() { - let ctxt = Context::::new_dbus(0); - let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], ctxt).unwrap(); - assert_eq!(decoded, StateSet(State::Indeterminate.into())); - } - - #[test] - fn serialize_state_set_focusable_focused() { - let ctxt = Context::::new_dbus(0); - let encoded = to_bytes(ctxt, &StateSet(State::Focusable | State::Focused)).unwrap(); - assert_eq!(encoded, &[8, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0]); - } - - #[test] - fn deserialize_state_set_focusable_focused() { - let ctxt = Context::::new_dbus(0); - let decoded: StateSet = from_slice(&[8, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0], ctxt).unwrap(); - assert_eq!(decoded, StateSet(State::Focusable | State::Focused)); - } - - #[test] - fn cannot_deserialize_state_set_invalid_length() { - let ctxt = Context::::new_dbus(0); - let decoded = from_slice::<_, StateSet>(&[4, 0, 0, 0, 0, 0, 0, 0], ctxt); - assert!(decoded.is_err()); - } - - #[test] - fn cannot_deserialize_state_set_invalid_flag() { - let ctxt = Context::::new_dbus(0); - let decoded = from_slice::<_, StateSet>(&[8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32], ctxt); - assert!(decoded.is_err()); - } -} diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 343c38713..87073b941 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -4,11 +4,12 @@ // the LICENSE-MIT file), at your option. use crate::atspi::{ - interfaces::{Action, Interface, Interfaces, ObjectEvent, Property, QueuedEvent}, - ObjectId, ObjectRef, OwnedObjectAddress, Role as AtspiRole, State, StateSet, + interfaces::{Action, ObjectEvent, Property, QueuedEvent}, + ObjectId, ObjectRef, OwnedObjectAddress, }; use accesskit::{CheckedState, DefaultActionVerb, NodeId, Role}; use accesskit_consumer::{FilterResult, Node, Tree, TreeState}; +use atspi::{accessible::Role as AtspiRole, Interface, InterfaceSet, State, StateSet}; use parking_lot::RwLock; use std::{ convert::TryFrom, @@ -427,8 +428,8 @@ impl<'a> NodeWrapper<'a> { state } - pub fn interfaces(&self) -> Interfaces { - let mut interfaces = Interfaces::new(Interface::Accessible); + pub fn interfaces(&self) -> InterfaceSet { + let mut interfaces = InterfaceSet::new(Interface::Accessible); if self.node.default_action_verb().is_some() { interfaces.insert(Interface::Action); } @@ -633,7 +634,7 @@ impl PlatformNode { self.resolve(|resolved| Ok(resolved.state())) } - pub fn interfaces(&self) -> fdo::Result { + pub fn interfaces(&self) -> fdo::Result { self.resolve(|resolved| Ok(resolved.interfaces())) } From 56d26ef857afdb53010d4ec79f170b2045b93b37 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 21 Nov 2022 22:10:55 +0100 Subject: [PATCH 39/69] Add basic support for hit-testing --- platforms/linux/Cargo.toml | 2 +- platforms/linux/src/adapter.rs | 69 +++++-- .../linux/src/atspi/interfaces/accessible.rs | 6 +- .../linux/src/atspi/interfaces/component.rs | 45 +++++ platforms/linux/src/atspi/interfaces/mod.rs | 2 + platforms/linux/src/atspi/mod.rs | 22 +++ platforms/linux/src/atspi/object_address.rs | 16 +- platforms/linux/src/node.rs | 173 ++++++++++++++++-- platforms/winit/examples/simple.rs | 114 ++++++------ platforms/winit/src/lib.rs | 62 ++++++- platforms/winit/src/platform_impl/linux.rs | 6 +- 11 files changed, 416 insertions(+), 101 deletions(-) create mode 100644 platforms/linux/src/atspi/interfaces/component.rs diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index dbb96d2b9..a5929e81a 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" [dependencies] accesskit = { version = "0.7.0", path = "../../common" } accesskit_consumer = { version = "0.7.1", path = "../../consumer" } -atspi = { git = "https://github.com/DataTriny/odilia", branch = "atspi_events" } +atspi = { git = "https://github.com/odilia-app/odilia" } parking_lot = "0.11.1" serde = "1.0" strum = { version = "0.23.0", features = ["derive"] } diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 17166feff..606571642 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -5,12 +5,13 @@ use crate::atspi::{ interfaces::{ - AccessibleInterface, ActionInterface, ObjectEvent, QueuedEvent, ValueInterface, WindowEvent, + AccessibleInterface, ActionInterface, ComponentInterface, ObjectEvent, QueuedEvent, + ValueInterface, WindowEvent, }, Bus, ObjectId, ACCESSIBLE_PATH_PREFIX, }; use crate::node::{filter, AppState, NodeWrapper, PlatformNode, PlatformRootNode}; -use accesskit::{ActionHandler, NodeId, Role, TreeUpdate}; +use accesskit::{kurbo::Rect, ActionHandler, NodeId, Role, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChangeHandler}; use atspi::{Interface, InterfaceSet, State}; use parking_lot::RwLock; @@ -18,7 +19,7 @@ use std::sync::Arc; pub struct Adapter { atspi_bus: Option, - _app_state: Arc>, + app_state: Arc>, tree: Arc, } @@ -36,6 +37,7 @@ impl Adapter { app_name, toolkit_name, toolkit_version, + atspi_bus.as_ref().map(|bus| bus.unique_name().clone()), ))); atspi_bus.as_mut().and_then(|bus| { bus.register_root_node(PlatformRootNode::new( @@ -46,7 +48,7 @@ impl Adapter { }); let adapter = Adapter { atspi_bus, - _app_state: app_state, + app_state, tree, }; { @@ -63,7 +65,9 @@ impl Adapter { objects_to_add.push(reader.root().id()); add_children(reader.root(), &mut objects_to_add); for id in objects_to_add { - let interfaces = NodeWrapper::new(&reader.node_by_id(id).unwrap()).interfaces(); + let interfaces = + NodeWrapper::new(&reader.node_by_id(id).unwrap(), &adapter.app_state) + .interfaces(); adapter .register_interfaces(&adapter.tree, id, interfaces) .unwrap(); @@ -86,15 +90,27 @@ impl Adapter { &path, AccessibleInterface::new( bus.unique_name().to_owned(), - PlatformNode::new(tree, id), + PlatformNode::new(tree, id, &self.app_state), ), )?; } if new_interfaces.contains(Interface::Action) { - bus.register_interface(&path, ActionInterface::new(PlatformNode::new(tree, id)))?; + bus.register_interface( + &path, + ActionInterface::new(PlatformNode::new(tree, id, &self.app_state)), + )?; + } + if new_interfaces.contains(Interface::Component) { + bus.register_interface( + &path, + ComponentInterface::new(PlatformNode::new(tree, id, &self.app_state)), + )?; } if new_interfaces.contains(Interface::Value) { - bus.register_interface(&path, ValueInterface::new(PlatformNode::new(tree, id)))?; + bus.register_interface( + &path, + ValueInterface::new(PlatformNode::new(tree, id, &self.app_state)), + )?; } Ok(true) }) @@ -113,6 +129,9 @@ impl Adapter { if old_interfaces.contains(Interface::Action) { bus.unregister_interface::(&path)?; } + if old_interfaces.contains(Interface::Component) { + bus.unregister_interface::(&path)?; + } if old_interfaces.contains(Interface::Value) { bus.unregister_interface::(&path)?; } @@ -120,6 +139,12 @@ impl Adapter { }) } + pub fn set_root_window_bounds(&self, outer: Rect, inner: Rect) { + let mut app_state = self.app_state.write(); + app_state.outer_bounds = outer; + app_state.inner_bounds = inner; + } + pub fn update(&self, update: TreeUpdate) -> QueuedEvents { struct Handler<'a> { adapter: &'a Adapter, @@ -128,14 +153,14 @@ impl Adapter { } impl TreeChangeHandler for Handler<'_> { fn node_added(&mut self, node: &Node) { - let interfaces = NodeWrapper::new(node).interfaces(); + let interfaces = NodeWrapper::new(node, &self.adapter.app_state).interfaces(); self.adapter .register_interfaces(self.tree, node.id(), interfaces) .unwrap(); } fn node_updated(&mut self, old_node: &Node, new_node: &Node) { - let old_wrapper = NodeWrapper::new(old_node); - let new_wrapper = NodeWrapper::new(new_node); + let old_wrapper = NodeWrapper::new(old_node, &self.adapter.app_state); + let new_wrapper = NodeWrapper::new(new_node, &self.adapter.app_state); let old_interfaces = old_wrapper.interfaces(); let new_interfaces = new_wrapper.interfaces(); let kept_interfaces = old_interfaces & new_interfaces; @@ -152,21 +177,29 @@ impl Adapter { let new_window = new_node.and_then(|node| containing_window(*node)); if old_window.map(|n| n.id()) != new_window.map(|n| n.id()) { if let Some(window) = old_window { - self.adapter - .window_deactivated(&NodeWrapper::new(&window), &mut self.queue); + self.adapter.window_deactivated( + &NodeWrapper::new(&window, &self.adapter.app_state), + &mut self.queue, + ); } if let Some(window) = new_window { - self.adapter - .window_activated(&NodeWrapper::new(&window), &mut self.queue); + self.adapter.window_activated( + &NodeWrapper::new(&window, &self.adapter.app_state), + &mut self.queue, + ); } } - if let Some(node) = new_node.map(NodeWrapper::new) { + if let Some(node) = + new_node.map(|node| NodeWrapper::new(node, &self.adapter.app_state)) + { self.queue.push(QueuedEvent::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, true), }); } - if let Some(node) = old_node.map(NodeWrapper::new) { + if let Some(node) = + old_node.map(|node| NodeWrapper::new(node, &self.adapter.app_state)) + { self.queue.push(QueuedEvent::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, false), @@ -174,7 +207,7 @@ impl Adapter { } } fn node_removed(&mut self, node: &Node) { - let node = NodeWrapper::new(node); + let node = NodeWrapper::new(node, &self.adapter.app_state); self.adapter .unregister_interfaces(&node.id(), node.interfaces()) .unwrap(); diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 4c614c77f..7c292fc90 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -39,7 +39,7 @@ impl AccessibleInterface { ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() } Ok(ObjectRef::Unmanaged(address)) => address, - _ => ObjectAddress::null(self.bus_name.as_ref()).into(), + _ => OwnedObjectAddress::null(self.bus_name.clone()), } } @@ -129,7 +129,7 @@ impl AccessibleInterface { .state .upgrade() .and_then(|state| state.read().desktop_address.clone()) - .unwrap_or_else(|| ObjectAddress::null(self.bus_name.as_ref()).into()) + .unwrap_or_else(|| OwnedObjectAddress::null(self.bus_name.clone())) } #[dbus_interface(property)] @@ -149,7 +149,7 @@ impl AccessibleInterface { fn get_child_at_index(&self, index: i32) -> fdo::Result<(OwnedObjectAddress,)> { if index != 0 { - return Ok((ObjectAddress::null(self.bus_name.as_ref()).into(),)); + return Ok((OwnedObjectAddress::null(self.bus_name.clone()),)); } self.node .tree diff --git a/platforms/linux/src/atspi/interfaces/component.rs b/platforms/linux/src/atspi/interfaces/component.rs new file mode 100644 index 000000000..fad38204d --- /dev/null +++ b/platforms/linux/src/atspi/interfaces/component.rs @@ -0,0 +1,45 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use crate::{ + atspi::{object_address::OwnedObjectAddress, Rect}, + PlatformNode, +}; +use atspi::CoordType; +use zbus::fdo; + +pub(crate) struct ComponentInterface { + node: PlatformNode, +} + +impl ComponentInterface { + pub fn new(node: PlatformNode) -> Self { + Self { node } + } +} + +#[dbus_interface(name = "org.a11y.atspi.Component")] +impl ComponentInterface { + fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> fdo::Result { + self.node.contains(x, y, coord_type) + } + + fn get_accessible_at_point( + &self, + x: i32, + y: i32, + coord_type: CoordType, + ) -> fdo::Result<(OwnedObjectAddress,)> { + self.node.get_accessible_at_point(x, y, coord_type) + } + + fn get_extents(&self, coord_type: CoordType) -> fdo::Result<(Rect,)> { + self.node.get_extents(coord_type) + } + + fn grab_focus(&self) -> fdo::Result { + self.node.grab_focus() + } +} diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index cab510a13..b1ff59631 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -6,11 +6,13 @@ mod accessible; mod action; mod application; +mod component; mod events; mod value; pub(crate) use accessible::*; pub(crate) use action::*; pub(crate) use application::*; +pub(crate) use component::*; pub(crate) use events::*; pub(crate) use value::*; diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs index 5f0fd4241..1845fe98e 100644 --- a/platforms/linux/src/atspi/mod.rs +++ b/platforms/linux/src/atspi/mod.rs @@ -3,12 +3,34 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. +use serde::{Deserialize, Serialize}; +use zvariant::Type; + mod bus; pub(crate) mod interfaces; mod object_address; mod object_id; mod object_ref; +#[derive(Debug, Default, Serialize, Deserialize, Type)] +pub(crate) struct Rect { + x: i32, + y: i32, + width: i32, + height: i32, +} + +impl From for Rect { + fn from(value: accesskit::kurbo::Rect) -> Rect { + Rect { + x: value.x0 as i32, + y: value.y0 as i32, + width: value.width() as i32, + height: value.height() as i32, + } + } +} + pub(crate) use bus::Bus; pub(crate) use object_address::*; pub(crate) use object_id::*; diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs index 374e00a4e..f121444bf 100644 --- a/platforms/linux/src/atspi/object_address.rs +++ b/platforms/linux/src/atspi/object_address.rs @@ -36,13 +36,6 @@ impl<'a> ObjectAddress<'a> { } } - pub fn null(bus_name: UniqueName<'a>) -> ObjectAddress<'a> { - Self { - bus_name, - path: ObjectPath::from_str_unchecked(NULL_PATH), - } - } - pub fn root(bus_name: UniqueName<'a>) -> ObjectAddress<'a> { Self { bus_name, @@ -57,6 +50,15 @@ pub struct OwnedObjectAddress { path: OwnedObjectPath, } +impl OwnedObjectAddress { + pub fn null(bus_name: OwnedUniqueName) -> Self { + Self { + bus_name, + path: ObjectPath::from_str_unchecked(NULL_PATH).into(), + } + } +} + impl From> for OwnedObjectAddress { fn from(value: ObjectAddress) -> OwnedObjectAddress { OwnedObjectAddress { diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 87073b941..4a8aa381a 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -5,17 +5,20 @@ use crate::atspi::{ interfaces::{Action, ObjectEvent, Property, QueuedEvent}, - ObjectId, ObjectRef, OwnedObjectAddress, + ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Rect as AtspiRect, +}; +use accesskit::{ + kurbo::{Point, Rect}, + CheckedState, DefaultActionVerb, NodeId, Role, }; -use accesskit::{CheckedState, DefaultActionVerb, NodeId, Role}; use accesskit_consumer::{FilterResult, Node, Tree, TreeState}; -use atspi::{accessible::Role as AtspiRole, Interface, InterfaceSet, State, StateSet}; +use atspi::{accessible::Role as AtspiRole, CoordType, Interface, InterfaceSet, State, StateSet}; use parking_lot::RwLock; use std::{ convert::TryFrom, sync::{Arc, Weak}, }; -use zbus::fdo; +use zbus::{fdo, names::OwnedUniqueName}; pub(crate) fn filter(node: &Node) -> FilterResult { if node.is_focused() { @@ -36,11 +39,15 @@ pub(crate) fn filter(node: &Node) -> FilterResult { pub(crate) struct NodeWrapper<'a> { node: &'a Node<'a>, + app_state: Arc>, } impl<'a> NodeWrapper<'a> { - pub(crate) fn new(node: &'a Node<'a>) -> Self { - NodeWrapper { node } + pub(crate) fn new(node: &'a Node<'a>, app_state: &Arc>) -> Self { + NodeWrapper { + node, + app_state: app_state.clone(), + } } pub fn name(&self) -> String { @@ -433,6 +440,9 @@ impl<'a> NodeWrapper<'a> { if self.node.default_action_verb().is_some() { interfaces.insert(Interface::Action); } + if self.node.bounding_box().is_some() || self.node.is_root() { + interfaces.insert(Interface::Component); + } if self.node.numeric_value().is_some() { interfaces.insert(Interface::Value); } @@ -518,13 +528,19 @@ fn unknown_object() -> fdo::Error { pub(crate) struct PlatformNode { tree: Weak, node_id: NodeId, + app_state: Weak>, } impl PlatformNode { - pub(crate) fn new(tree: &Arc, node_id: NodeId) -> Self { + pub(crate) fn new( + tree: &Arc, + node_id: NodeId, + app_state: &Arc>, + ) -> Self { Self { tree: Arc::downgrade(tree), node_id, + app_state: Arc::downgrade(app_state), } } @@ -536,22 +552,31 @@ impl PlatformNode { } } - fn with_tree_state(&self, f: F) -> fdo::Result + fn upgrade_app_state(&self) -> fdo::Result>> { + if let Some(state) = self.app_state.upgrade() { + Ok(state) + } else { + Err(unknown_object()) + } + } + + fn with_state(&self, f: F) -> fdo::Result where - F: FnOnce(&TreeState) -> fdo::Result, + F: FnOnce((&TreeState, &Arc>)) -> fdo::Result, { let tree = self.upgrade_tree()?; + let app_state = self.upgrade_app_state()?; let state = tree.read(); - f(&state) + f((&state, &app_state)) } fn resolve(&self, f: F) -> fdo::Result where for<'a> F: FnOnce(NodeWrapper<'a>) -> fdo::Result, { - self.with_tree_state(|state| { - if let Some(node) = state.node_by_id(self.node_id) { - f(NodeWrapper::new(&node)) + self.with_state(|(tree, app)| { + if let Some(node) = tree.node_by_id(self.node_id) { + f(NodeWrapper::new(&node, app)) } else { Err(unknown_object()) } @@ -670,6 +695,115 @@ impl PlatformNode { Ok(true) } + pub fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> fdo::Result { + self.resolve(|wrapper| { + let app_state = wrapper.app_state.read(); + let bounds = match wrapper.node.bounding_box() { + Some(node_bounds) => { + let top_left = match coord_type { + CoordType::Screen => app_state.inner_bounds.origin(), + CoordType::Window => { + let outer_position = app_state.outer_bounds.origin(); + let inner_position = app_state.inner_bounds.origin(); + Point::new( + inner_position.x - outer_position.x, + inner_position.y - outer_position.y, + ) + } + _ => unimplemented!(), + }; + let new_origin = + Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0); + node_bounds.with_origin(new_origin) + } + None if wrapper.node.is_root() => { + let window_bounds = app_state.outer_bounds; + match coord_type { + CoordType::Screen => window_bounds, + CoordType::Window => window_bounds.with_origin(Point::ZERO), + _ => unimplemented!(), + } + } + _ => return Err(unknown_object()), + }; + Ok(bounds.contains(Point::new(x.into(), y.into()))) + }) + } + + pub fn get_accessible_at_point( + &self, + x: i32, + y: i32, + coord_type: CoordType, + ) -> fdo::Result<(OwnedObjectAddress,)> { + self.resolve(|wrapper| { + let app_state = wrapper.app_state.read(); + let is_root = wrapper.node.is_root(); + let top_left = match coord_type { + CoordType::Screen if is_root => app_state.outer_bounds.origin(), + CoordType::Screen => app_state.inner_bounds.origin(), + CoordType::Window if is_root => Point::ZERO, + CoordType::Window => app_state.inner_bounds.origin(), + _ => unimplemented!(), + }; + let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y); + Ok(wrapper.node.node_at_point(point, &filter).map_or_else( + || { + (OwnedObjectAddress::null( + app_state.bus_name.clone().unwrap(), + ),) + }, + |node| { + (ObjectAddress::accessible( + app_state.bus_name.as_ref().unwrap().as_ref(), + &node.id().into(), + ) + .into(),) + }, + )) + }) + } + + pub fn get_extents(&self, coord_type: CoordType) -> fdo::Result<(AtspiRect,)> { + self.resolve(|wrapper| { + let app_state = wrapper.app_state.read(); + match wrapper.node.bounding_box() { + Some(node_bounds) => { + let top_left = match coord_type { + CoordType::Screen => app_state.inner_bounds.origin(), + CoordType::Window => { + let outer_position = app_state.outer_bounds.origin(); + let inner_position = app_state.inner_bounds.origin(); + Point::new( + inner_position.x - outer_position.x, + inner_position.y - outer_position.y, + ) + } + _ => unimplemented!(), + }; + let new_origin = + Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0); + Ok((node_bounds.with_origin(new_origin).into(),)) + } + None if wrapper.node.is_root() => { + let window_bounds = app_state.outer_bounds; + Ok((match coord_type { + CoordType::Screen => window_bounds.into(), + CoordType::Window => window_bounds.with_origin(Point::ZERO).into(), + _ => unimplemented!(), + },)) + } + _ => Err(unknown_object()), + } + }) + } + + pub fn grab_focus(&self) -> fdo::Result { + let tree = self.validate_for_action()?; + tree.set_focus(self.node_id); + Ok(true) + } + pub fn minimum_value(&self) -> fdo::Result { self.resolve(|resolved| Ok(resolved.node.min_numeric_value().unwrap_or(std::f64::MIN))) } @@ -697,18 +831,29 @@ pub(crate) struct AppState { pub name: String, pub toolkit_name: String, pub toolkit_version: String, + pub bus_name: Option, pub id: Option, pub desktop_address: Option, + pub outer_bounds: Rect, + pub inner_bounds: Rect, } impl AppState { - pub fn new(name: String, toolkit_name: String, toolkit_version: String) -> Self { + pub fn new( + name: String, + toolkit_name: String, + toolkit_version: String, + bus_name: Option, + ) -> Self { Self { name, toolkit_name, toolkit_version, + bus_name, id: None, desktop_address: None, + outer_bounds: Rect::default(), + inner_bounds: Rect::default(), } } } diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index 8cee04cc7..7313e48a9 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -164,67 +164,71 @@ fn main() { window.set_visible(true); - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::ExitWithCode(0); - } - WindowEvent::Focused(is_window_focused) => { - let mut state = state.lock().unwrap(); - state.is_window_focused = is_window_focused; - state.update_focus(&adapter); - } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state: ElementState::Pressed, - .. - }, - .. - } => match virtual_code { - VirtualKeyCode::Tab => { + adapter.run( + window, + event_loop, + move |adapter, event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => { + *control_flow = ControlFlow::ExitWithCode(0); + } + WindowEvent::Focused(is_window_focused) => { let mut state = state.lock().unwrap(); - state.focus = if state.focus == BUTTON_1_ID { - BUTTON_2_ID - } else { - BUTTON_1_ID - }; + state.is_window_focused = is_window_focused; state.update_focus(&adapter); } - VirtualKeyCode::Space => { - let state = state.lock().unwrap(); - state.press_button(&adapter, state.focus); - } + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(virtual_code), + state: ElementState::Pressed, + .. + }, + .. + } => match virtual_code { + VirtualKeyCode::Tab => { + let mut state = state.lock().unwrap(); + state.focus = if state.focus == BUTTON_1_ID { + BUTTON_2_ID + } else { + BUTTON_1_ID + }; + state.update_focus(&adapter); + } + VirtualKeyCode::Space => { + let state = state.lock().unwrap(); + state.press_button(&adapter, state.focus); + } + _ => (), + }, _ => (), }, - _ => (), - }, - Event::UserEvent(ActionRequestEvent { - request: - ActionRequest { - action, - target, - data: None, - }, - .. - }) if target == BUTTON_1_ID || target == BUTTON_2_ID => { - let mut state = state.lock().unwrap(); - match action { - Action::Focus => { - state.focus = target; - state.update_focus(&adapter); - } - Action::Default => { - state.press_button(&adapter, target); + Event::UserEvent(ActionRequestEvent { + request: + ActionRequest { + action, + target, + data: None, + }, + .. + }) if target == BUTTON_1_ID || target == BUTTON_2_ID => { + let mut state = state.lock().unwrap(); + match action { + Action::Focus => { + state.focus = target; + state.update_focus(&adapter); + } + Action::Default => { + state.press_button(&adapter, target); + } + _ => (), } - _ => (), } + _ => (), } - _ => (), - } - }); + }, + ); } diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index eb54ff1d8..e14253a49 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -2,10 +2,11 @@ // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file). -use accesskit::{ActionHandler, ActionRequest, TreeUpdate}; +use accesskit::{kurbo::Rect, ActionHandler, ActionRequest, TreeUpdate}; use parking_lot::Mutex; use winit::{ - event_loop::EventLoopProxy, + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget}, window::{Window, WindowId}, }; @@ -63,6 +64,63 @@ impl Adapter { Self { adapter } } + #[cfg(target_os = "linux")] + pub fn run(self, window: Window, event_loop: EventLoop, mut event_handler: F) -> ! + where + F: 'static + FnMut(&Self, Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), + { + event_loop.run(move |event, window_target, control_flow| { + if let Event::WindowEvent { ref event, .. } = event { + match event { + WindowEvent::Moved(outer_position) => { + let outer_position: (_, _) = outer_position.cast::().into(); + let outer_size: (_, _) = window.outer_size().cast::().into(); + let inner_position: (_, _) = window + .inner_position() + .unwrap_or_default() + .cast::() + .into(); + let inner_size: (_, _) = window.inner_size().cast::().into(); + self.adapter.set_root_window_bounds( + Rect::from_origin_size(outer_position, outer_size), + Rect::from_origin_size(inner_position, inner_size), + ) + } + WindowEvent::Resized(outer_size) => { + let outer_position: (_, _) = window + .outer_position() + .unwrap_or_default() + .cast::() + .into(); + let outer_size: (_, _) = outer_size.cast::().into(); + let inner_position: (_, _) = window + .inner_position() + .unwrap_or_default() + .cast::() + .into(); + let inner_size: (_, _) = window.inner_size().cast::().into(); + self.adapter.set_root_window_bounds( + Rect::from_origin_size(outer_position, outer_size), + Rect::from_origin_size(inner_position, inner_size), + ) + } + _ => (), + } + } + event_handler(&self, event, window_target, control_flow) + }) + } + + #[cfg(not(target_os = "linux"))] + pub fn run(&self, window: Window, event_loop: EventLoop, mut event_handler: F) -> ! + where + F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), + { + event_loop.run(move |event, window_target, control_flow| { + event_handler(event, window_target, control_flow) + }) + } + pub fn update(&self, update: TreeUpdate) { self.adapter.update(update) } diff --git a/platforms/winit/src/platform_impl/linux.rs b/platforms/winit/src/platform_impl/linux.rs index b476b175b..62d91861a 100755 --- a/platforms/winit/src/platform_impl/linux.rs +++ b/platforms/winit/src/platform_impl/linux.rs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file). -use accesskit::{ActionHandler, TreeUpdate}; +use accesskit::{kurbo::Rect, ActionHandler, TreeUpdate}; use accesskit_linux::Adapter as LinuxAdapter; use winit::window::Window; @@ -26,6 +26,10 @@ impl Adapter { Self { adapter } } + pub fn set_root_window_bounds(&self, outer: Rect, inner: Rect) { + self.adapter.set_root_window_bounds(outer, inner); + } + pub fn update(&self, update: TreeUpdate) { self.adapter.update(update).raise(); } From 92cf00fde39c080b8d6e3221e32d4b9e6c91802b Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 21 Nov 2022 22:26:54 +0100 Subject: [PATCH 40/69] Bump Rust edition, fix some deps, populate Cargo.toml --- platforms/linux/Cargo.toml | 14 ++++++++++---- platforms/linux/src/atspi/bus.rs | 2 +- platforms/linux/src/atspi/interfaces/action.rs | 3 +-- platforms/linux/src/atspi/mod.rs | 2 +- platforms/linux/src/atspi/object_address.rs | 6 ++++-- platforms/linux/src/atspi/object_id.rs | 2 +- 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index a5929e81a..bc0c5b781 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -1,14 +1,20 @@ [package] name = "accesskit_linux" version = "0.1.0" -edition = "2018" +authors = ["Arnold Loubriat "] +license = "Apache-2.0" +description = "AccessKit UI accessibility infrastructure: Linux adapter" +categories = ["gui"] +keywords = ["gui", "ui", "accessibility"] +repository = "https://github.com/AccessKit/accesskit" +readme = "README.md" +edition = "2021" [dependencies] accesskit = { version = "0.7.0", path = "../../common" } accesskit_consumer = { version = "0.7.1", path = "../../consumer" } atspi = { git = "https://github.com/odilia-app/odilia" } -parking_lot = "0.11.1" +parking_lot = "0.12.1" serde = "1.0" strum = { version = "0.23.0", features = ["derive"] } -zbus = "3.0.0" -zvariant = "3.6.0" +zbus = "3.0" diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index a3a0760e4..ce9a6d80d 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -15,9 +15,9 @@ use std::{ use zbus::{ blocking::{Connection, ConnectionBuilder}, names::{BusName, InterfaceName, MemberName, OwnedUniqueName}, + zvariant::{ObjectPath, Str, Value}, Address, Result, }; -use zvariant::{ObjectPath, Str, Value}; #[derive(Clone)] pub(crate) struct Bus { diff --git a/platforms/linux/src/atspi/interfaces/action.rs b/platforms/linux/src/atspi/interfaces/action.rs index 5911dc6cb..1e969795e 100644 --- a/platforms/linux/src/atspi/interfaces/action.rs +++ b/platforms/linux/src/atspi/interfaces/action.rs @@ -1,7 +1,6 @@ use crate::PlatformNode; use serde::{Deserialize, Serialize}; -use zbus::{dbus_interface, fdo}; -use zvariant::Type; +use zbus::{dbus_interface, fdo, zvariant::Type}; #[derive(Deserialize, Serialize, Type)] pub(crate) struct Action { diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/linux/src/atspi/mod.rs index 1845fe98e..4291f4a91 100644 --- a/platforms/linux/src/atspi/mod.rs +++ b/platforms/linux/src/atspi/mod.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use serde::{Deserialize, Serialize}; -use zvariant::Type; +use zbus::zvariant::Type; mod bus; pub(crate) mod interfaces; diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs index f121444bf..62c79c727 100644 --- a/platforms/linux/src/atspi/object_address.rs +++ b/platforms/linux/src/atspi/object_address.rs @@ -5,8 +5,10 @@ use crate::atspi::ObjectId; use serde::{Deserialize, Serialize}; -use zbus::names::{OwnedUniqueName, UniqueName}; -use zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type, Value}; +use zbus::{ + names::{OwnedUniqueName, UniqueName}, + zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type, Value}, +}; pub(crate) const ACCESSIBLE_PATH_PREFIX: &str = "/org/a11y/atspi/accessible/"; pub(crate) const NULL_PATH: &str = "/org/a11y/atspi/null"; diff --git a/platforms/linux/src/atspi/object_id.rs b/platforms/linux/src/atspi/object_id.rs index 651aa3f12..6db76576b 100644 --- a/platforms/linux/src/atspi/object_id.rs +++ b/platforms/linux/src/atspi/object_id.rs @@ -5,7 +5,7 @@ use accesskit::NodeId; use serde::{Deserialize, Serialize}; -use zvariant::{Str, Type, Value}; +use zbus::zvariant::{Str, Type, Value}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Type, Value)] pub struct ObjectId<'a>(#[serde(borrow)] Str<'a>); From 6f45b19a748daa5830806a1b5c144b2976bc69a8 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Tue, 22 Nov 2022 19:54:01 +0100 Subject: [PATCH 41/69] accesskit_winit: Rework event handling --- platforms/winit/examples/simple.rs | 114 ++++++++++++++--------------- platforms/winit/src/lib.rs | 99 +++++++++++-------------- 2 files changed, 99 insertions(+), 114 deletions(-) diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index 7313e48a9..ec53b9466 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -164,71 +164,67 @@ fn main() { window.set_visible(true); - adapter.run( - window, - event_loop, - move |adapter, event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::ExitWithCode(0); - } - WindowEvent::Focused(is_window_focused) => { + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } if adapter.on_event(&window, &event) => match event { + WindowEvent::CloseRequested => { + *control_flow = ControlFlow::ExitWithCode(0); + } + WindowEvent::Focused(is_window_focused) => { + let mut state = state.lock().unwrap(); + state.is_window_focused = is_window_focused; + state.update_focus(&adapter); + } + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(virtual_code), + state: ElementState::Pressed, + .. + }, + .. + } => match virtual_code { + VirtualKeyCode::Tab => { let mut state = state.lock().unwrap(); - state.is_window_focused = is_window_focused; + state.focus = if state.focus == BUTTON_1_ID { + BUTTON_2_ID + } else { + BUTTON_1_ID + }; state.update_focus(&adapter); } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state: ElementState::Pressed, - .. - }, - .. - } => match virtual_code { - VirtualKeyCode::Tab => { - let mut state = state.lock().unwrap(); - state.focus = if state.focus == BUTTON_1_ID { - BUTTON_2_ID - } else { - BUTTON_1_ID - }; - state.update_focus(&adapter); - } - VirtualKeyCode::Space => { - let state = state.lock().unwrap(); - state.press_button(&adapter, state.focus); - } - _ => (), - }, + VirtualKeyCode::Space => { + let state = state.lock().unwrap(); + state.press_button(&adapter, state.focus); + } _ => (), }, - Event::UserEvent(ActionRequestEvent { - request: - ActionRequest { - action, - target, - data: None, - }, - .. - }) if target == BUTTON_1_ID || target == BUTTON_2_ID => { - let mut state = state.lock().unwrap(); - match action { - Action::Focus => { - state.focus = target; - state.update_focus(&adapter); - } - Action::Default => { - state.press_button(&adapter, target); - } - _ => (), + _ => (), + }, + Event::UserEvent(ActionRequestEvent { + request: + ActionRequest { + action, + target, + data: None, + }, + .. + }) if target == BUTTON_1_ID || target == BUTTON_2_ID => { + let mut state = state.lock().unwrap(); + match action { + Action::Focus => { + state.focus = target; + state.update_focus(&adapter); + } + Action::Default => { + state.press_button(&adapter, target); } + _ => (), } - _ => (), } - }, - ); + _ => (), + } + }); } diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index e14253a49..a90273f8a 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -5,8 +5,8 @@ use accesskit::{kurbo::Rect, ActionHandler, ActionRequest, TreeUpdate}; use parking_lot::Mutex; use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget}, + event::WindowEvent, + event_loop::EventLoopProxy, window::{Window, WindowId}, }; @@ -64,61 +64,50 @@ impl Adapter { Self { adapter } } + #[cfg(not(target_os = "linux"))] + #[must_use] + pub fn on_event(&self, window: &Window, event: &WindowEvent) -> bool { + true + } #[cfg(target_os = "linux")] - pub fn run(self, window: Window, event_loop: EventLoop, mut event_handler: F) -> ! - where - F: 'static + FnMut(&Self, Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), - { - event_loop.run(move |event, window_target, control_flow| { - if let Event::WindowEvent { ref event, .. } = event { - match event { - WindowEvent::Moved(outer_position) => { - let outer_position: (_, _) = outer_position.cast::().into(); - let outer_size: (_, _) = window.outer_size().cast::().into(); - let inner_position: (_, _) = window - .inner_position() - .unwrap_or_default() - .cast::() - .into(); - let inner_size: (_, _) = window.inner_size().cast::().into(); - self.adapter.set_root_window_bounds( - Rect::from_origin_size(outer_position, outer_size), - Rect::from_origin_size(inner_position, inner_size), - ) - } - WindowEvent::Resized(outer_size) => { - let outer_position: (_, _) = window - .outer_position() - .unwrap_or_default() - .cast::() - .into(); - let outer_size: (_, _) = outer_size.cast::().into(); - let inner_position: (_, _) = window - .inner_position() - .unwrap_or_default() - .cast::() - .into(); - let inner_size: (_, _) = window.inner_size().cast::().into(); - self.adapter.set_root_window_bounds( - Rect::from_origin_size(outer_position, outer_size), - Rect::from_origin_size(inner_position, inner_size), - ) - } - _ => (), - } + #[must_use] + pub fn on_event(&self, window: &Window, event: &WindowEvent) -> bool { + match event { + WindowEvent::Moved(outer_position) => { + let outer_position: (_, _) = outer_position.cast::().into(); + let outer_size: (_, _) = window.outer_size().cast::().into(); + let inner_position: (_, _) = window + .inner_position() + .unwrap_or_default() + .cast::() + .into(); + let inner_size: (_, _) = window.inner_size().cast::().into(); + self.adapter.set_root_window_bounds( + Rect::from_origin_size(outer_position, outer_size), + Rect::from_origin_size(inner_position, inner_size), + ) } - event_handler(&self, event, window_target, control_flow) - }) - } - - #[cfg(not(target_os = "linux"))] - pub fn run(&self, window: Window, event_loop: EventLoop, mut event_handler: F) -> ! - where - F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), - { - event_loop.run(move |event, window_target, control_flow| { - event_handler(event, window_target, control_flow) - }) + WindowEvent::Resized(outer_size) => { + let outer_position: (_, _) = window + .outer_position() + .unwrap_or_default() + .cast::() + .into(); + let outer_size: (_, _) = outer_size.cast::().into(); + let inner_position: (_, _) = window + .inner_position() + .unwrap_or_default() + .cast::() + .into(); + let inner_size: (_, _) = window.inner_size().cast::().into(); + self.adapter.set_root_window_bounds( + Rect::from_origin_size(outer_position, outer_size), + Rect::from_origin_size(inner_position, inner_size), + ) + } + _ => (), + } + true } pub fn update(&self, update: TreeUpdate) { From 366bbc2e00fe0ac7395f63eabd19fb2f37c49247 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Fri, 25 Nov 2022 18:46:22 +0100 Subject: [PATCH 42/69] Improve object address management --- platforms/linux/Cargo.toml | 2 +- platforms/linux/src/adapter.rs | 1 - .../linux/src/atspi/interfaces/accessible.rs | 62 ++++++++++--------- .../linux/src/atspi/interfaces/component.rs | 8 ++- platforms/linux/src/atspi/interfaces/mod.rs | 24 +++++++ platforms/linux/src/atspi/object_address.rs | 16 +++++ platforms/linux/src/node.rs | 44 +++---------- 7 files changed, 91 insertions(+), 66 deletions(-) diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index bc0c5b781..d4adecd11 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" [dependencies] accesskit = { version = "0.7.0", path = "../../common" } accesskit_consumer = { version = "0.7.1", path = "../../consumer" } -atspi = { git = "https://github.com/odilia-app/odilia" } +atspi = "0.3.5" parking_lot = "0.12.1" serde = "1.0" strum = { version = "0.23.0", features = ["derive"] } diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 606571642..4d79fe75c 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -37,7 +37,6 @@ impl Adapter { app_name, toolkit_name, toolkit_version, - atspi_bus.as_ref().map(|bus| bus.unique_name().clone()), ))); atspi_bus.as_mut().and_then(|bus| { bus.register_root_node(PlatformRootNode::new( diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 7c292fc90..95bf517a3 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -7,7 +7,7 @@ use crate::atspi::{ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress}; use crate::{PlatformNode, PlatformRootNode}; use atspi::{accessible::Role, Interface, InterfaceSet, StateSet}; use std::convert::TryInto; -use zbus::{fdo, names::OwnedUniqueName}; +use zbus::{fdo, names::OwnedUniqueName, MessageHeader}; pub(crate) struct AccessibleInterface { bus_name: OwnedUniqueName, @@ -35,9 +35,7 @@ impl AccessibleInterface { #[dbus_interface(property)] fn parent(&self) -> OwnedObjectAddress { match self.node.parent() { - Ok(ObjectRef::Managed(id)) => { - ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() - } + Ok(ObjectRef::Managed(id)) => OwnedObjectAddress::accessible(self.bus_name.clone(), id), Ok(ObjectRef::Unmanaged(address)) => address, _ => OwnedObjectAddress::null(self.bus_name.clone()), } @@ -60,16 +58,15 @@ impl AccessibleInterface { .unwrap_or_else(|_| unsafe { ObjectId::from_str_unchecked("") }) } - fn get_child_at_index(&self, index: i32) -> fdo::Result<(OwnedObjectAddress,)> { + fn get_child_at_index( + &self, + #[zbus(header)] hdr: MessageHeader<'_>, + index: i32, + ) -> fdo::Result<(OwnedObjectAddress,)> { let index = index .try_into() .map_err(|_| fdo::Error::InvalidArgs("Index can't be negative.".into()))?; - Ok(match self.node.child_at_index(index)? { - ObjectRef::Managed(id) => { - (ObjectAddress::accessible(self.bus_name.as_ref(), &id).into(),) - } - ObjectRef::Unmanaged(address) => (address,), - }) + super::object_address(hdr.destination()?, self.node.child_at_index(index)?) } fn get_children(&self) -> fdo::Result> { @@ -98,8 +95,14 @@ impl AccessibleInterface { self.node.state() } - fn get_application(&self) -> (OwnedObjectAddress,) { - (ObjectAddress::root(self.bus_name.as_ref()).into(),) + fn get_application( + &self, + #[zbus(header)] hdr: MessageHeader<'_>, + ) -> fdo::Result<(OwnedObjectAddress,)> { + super::object_address( + hdr.destination()?, + Some(ObjectRef::Managed(ObjectId::root())), + ) } fn get_interfaces(&self) -> fdo::Result { @@ -147,23 +150,20 @@ impl AccessibleInterface { ObjectId::root() } - fn get_child_at_index(&self, index: i32) -> fdo::Result<(OwnedObjectAddress,)> { + fn get_child_at_index( + &self, + #[zbus(header)] hdr: MessageHeader<'_>, + index: i32, + ) -> fdo::Result<(OwnedObjectAddress,)> { if index != 0 { - return Ok((OwnedObjectAddress::null(self.bus_name.clone()),)); + return super::object_address(hdr.destination()?, None); } - self.node + let child = self + .node .tree .upgrade() - .map(|tree| { - ( - ObjectAddress::accessible( - self.bus_name.as_ref(), - &tree.read().root().id().into(), - ) - .into(), - ) - }) - .ok_or_else(|| fdo::Error::UnknownObject("".into())) + .map(|tree| ObjectRef::Managed(tree.read().root().id().into())); + super::object_address(hdr.destination()?, child) } fn get_children(&self) -> fdo::Result> { @@ -192,8 +192,14 @@ impl AccessibleInterface { StateSet::empty() } - fn get_application(&self) -> (OwnedObjectAddress,) { - (ObjectAddress::root(self.bus_name.as_ref()).into(),) + fn get_application( + &self, + #[zbus(header)] hdr: MessageHeader<'_>, + ) -> fdo::Result<(OwnedObjectAddress,)> { + super::object_address( + hdr.destination()?, + Some(ObjectRef::Managed(ObjectId::root())), + ) } fn get_interfaces(&self) -> InterfaceSet { diff --git a/platforms/linux/src/atspi/interfaces/component.rs b/platforms/linux/src/atspi/interfaces/component.rs index fad38204d..ee178e0dc 100644 --- a/platforms/linux/src/atspi/interfaces/component.rs +++ b/platforms/linux/src/atspi/interfaces/component.rs @@ -8,7 +8,7 @@ use crate::{ PlatformNode, }; use atspi::CoordType; -use zbus::fdo; +use zbus::{fdo, MessageHeader}; pub(crate) struct ComponentInterface { node: PlatformNode, @@ -28,11 +28,15 @@ impl ComponentInterface { fn get_accessible_at_point( &self, + #[zbus(header)] hdr: MessageHeader<'_>, x: i32, y: i32, coord_type: CoordType, ) -> fdo::Result<(OwnedObjectAddress,)> { - self.node.get_accessible_at_point(x, y, coord_type) + super::object_address( + hdr.destination()?, + self.node.get_accessible_at_point(x, y, coord_type)?, + ) } fn get_extents(&self, coord_type: CoordType) -> fdo::Result<(Rect,)> { diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/linux/src/atspi/interfaces/mod.rs index b1ff59631..2b0c54940 100644 --- a/platforms/linux/src/atspi/interfaces/mod.rs +++ b/platforms/linux/src/atspi/interfaces/mod.rs @@ -10,6 +10,30 @@ mod component; mod events; mod value; +use crate::atspi::{ObjectRef, OwnedObjectAddress}; +use zbus::{ + fdo, + names::{BusName, OwnedUniqueName, UniqueName}, +}; + +fn object_address( + destination: Option<&BusName>, + object_ref: Option, +) -> fdo::Result<(OwnedObjectAddress,)> { + match object_ref { + Some(ObjectRef::Managed(id)) => { + Ok((OwnedObjectAddress::accessible(app_name(destination)?, id),)) + } + Some(ObjectRef::Unmanaged(address)) => Ok((address,)), + None => Ok((OwnedObjectAddress::null(app_name(destination)?),)), + } +} + +fn app_name(destination: Option<&BusName>) -> fdo::Result { + let destination = destination.ok_or(fdo::Error::ZBus(zbus::Error::MissingField))?; + Ok(UniqueName::from_str_unchecked(destination.as_str()).into()) +} + pub(crate) use accessible::*; pub(crate) use action::*; pub(crate) use application::*; diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/linux/src/atspi/object_address.rs index 62c79c727..5f6171bdd 100644 --- a/platforms/linux/src/atspi/object_address.rs +++ b/platforms/linux/src/atspi/object_address.rs @@ -53,6 +53,22 @@ pub struct OwnedObjectAddress { } impl OwnedObjectAddress { + pub fn new(bus_name: OwnedUniqueName, path: OwnedObjectPath) -> Self { + Self { bus_name, path } + } + + pub fn accessible(bus_name: OwnedUniqueName, id: ObjectId) -> Self { + Self { + bus_name, + path: ObjectPath::from_string_unchecked(format!( + "{}{}", + ACCESSIBLE_PATH_PREFIX, + id.as_str() + )) + .into(), + } + } + pub fn null(bus_name: OwnedUniqueName) -> Self { Self { bus_name, diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 4a8aa381a..cbf003630 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -5,7 +5,7 @@ use crate::atspi::{ interfaces::{Action, ObjectEvent, Property, QueuedEvent}, - ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress, Rect as AtspiRect, + ObjectId, ObjectRef, OwnedObjectAddress, Rect as AtspiRect, }; use accesskit::{ kurbo::{Point, Rect}, @@ -18,7 +18,7 @@ use std::{ convert::TryFrom, sync::{Arc, Weak}, }; -use zbus::{fdo, names::OwnedUniqueName}; +use zbus::fdo; pub(crate) fn filter(node: &Node) -> FilterResult { if node.is_focused() { @@ -625,15 +625,8 @@ impl PlatformNode { self.resolve(|resolved| Ok(resolved.id())) } - pub fn child_at_index(&self, index: usize) -> fdo::Result { - self.resolve(|resolved| { - resolved - .node - .child_ids() - .nth(index) - .map(ObjectRef::from) - .ok_or_else(unknown_object) - }) + pub fn child_at_index(&self, index: usize) -> fdo::Result> { + self.resolve(|resolved| Ok(resolved.node.child_ids().nth(index).map(ObjectRef::from))) } pub fn children(&self) -> fdo::Result> { @@ -735,7 +728,7 @@ impl PlatformNode { x: i32, y: i32, coord_type: CoordType, - ) -> fdo::Result<(OwnedObjectAddress,)> { + ) -> fdo::Result> { self.resolve(|wrapper| { let app_state = wrapper.app_state.read(); let is_root = wrapper.node.is_root(); @@ -747,20 +740,10 @@ impl PlatformNode { _ => unimplemented!(), }; let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y); - Ok(wrapper.node.node_at_point(point, &filter).map_or_else( - || { - (OwnedObjectAddress::null( - app_state.bus_name.clone().unwrap(), - ),) - }, - |node| { - (ObjectAddress::accessible( - app_state.bus_name.as_ref().unwrap().as_ref(), - &node.id().into(), - ) - .into(),) - }, - )) + Ok(wrapper + .node + .node_at_point(point, &filter) + .map(|node| ObjectRef::Managed(node.id().into()))) }) } @@ -831,7 +814,6 @@ pub(crate) struct AppState { pub name: String, pub toolkit_name: String, pub toolkit_version: String, - pub bus_name: Option, pub id: Option, pub desktop_address: Option, pub outer_bounds: Rect, @@ -839,17 +821,11 @@ pub(crate) struct AppState { } impl AppState { - pub fn new( - name: String, - toolkit_name: String, - toolkit_version: String, - bus_name: Option, - ) -> Self { + pub fn new(name: String, toolkit_name: String, toolkit_version: String) -> Self { Self { name, toolkit_name, toolkit_version, - bus_name, id: None, desktop_address: None, outer_bounds: Rect::default(), From aa589ec3b570af7f4110ecd49e5db104724ac360 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 19 Dec 2022 11:02:43 +0100 Subject: [PATCH 43/69] Rebase --- platforms/linux/Cargo.toml | 8 ++++---- platforms/linux/src/node.rs | 5 +++++ platforms/winit/src/platform_impl/mod.rs | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/platforms/linux/Cargo.toml b/platforms/linux/Cargo.toml index d4adecd11..0fa73761f 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/linux/Cargo.toml @@ -11,10 +11,10 @@ readme = "README.md" edition = "2021" [dependencies] -accesskit = { version = "0.7.0", path = "../../common" } -accesskit_consumer = { version = "0.7.1", path = "../../consumer" } -atspi = "0.3.5" +accesskit = { version = "0.7.0" } +accesskit_consumer = { version = "0.7.1" } +atspi = "0.3.10" parking_lot = "0.12.1" serde = "1.0" strum = { version = "0.23.0", features = ["derive"] } -zbus = "3.0" +zbus = "3.6" diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index cbf003630..507089e60 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -3,6 +3,11 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. +// Derived from Chromium's accessibility abstraction. +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + use crate::atspi::{ interfaces::{Action, ObjectEvent, Property, QueuedEvent}, ObjectId, ObjectRef, OwnedObjectAddress, Rect as AtspiRect, diff --git a/platforms/winit/src/platform_impl/mod.rs b/platforms/winit/src/platform_impl/mod.rs index 0663146df..4bfcff829 100644 --- a/platforms/winit/src/platform_impl/mod.rs +++ b/platforms/winit/src/platform_impl/mod.rs @@ -18,7 +18,7 @@ mod platform; #[path = "linux.rs"] mod platform; -#[cfg(all(not(target_os = "windows"), not(target_os = "macos"), not(target_os = "linux)))] +#[cfg(all(not(target_os = "windows"), not(target_os = "macos"), not(target_os = "linux")))] #[cfg(all(not(target_os = "windows"), not(target_os = "linux"),))] #[path = "null.rs"] mod platform; From d8acda13f14ab48281aba0e905b581ed8fb7ac03 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Tue, 20 Dec 2022 12:17:49 +0100 Subject: [PATCH 44/69] Refactor node positioning --- platforms/linux/src/adapter.rs | 71 ++++---- platforms/linux/src/atspi/bus.rs | 4 +- .../linux/src/atspi/interfaces/accessible.rs | 20 ++- .../linux/src/atspi/interfaces/application.rs | 20 +-- .../linux/src/atspi/interfaces/component.rs | 37 ++++- platforms/linux/src/lib.rs | 5 +- platforms/linux/src/node.rs | 157 +++++------------- platforms/linux/src/util.rs | 57 +++++++ 8 files changed, 190 insertions(+), 181 deletions(-) create mode 100644 platforms/linux/src/util.rs diff --git a/platforms/linux/src/adapter.rs b/platforms/linux/src/adapter.rs index 4d79fe75c..77b22a09b 100644 --- a/platforms/linux/src/adapter.rs +++ b/platforms/linux/src/adapter.rs @@ -3,23 +3,27 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{ - interfaces::{ - AccessibleInterface, ActionInterface, ComponentInterface, ObjectEvent, QueuedEvent, - ValueInterface, WindowEvent, - }, - Bus, ObjectId, ACCESSIBLE_PATH_PREFIX, -}; -use crate::node::{filter, AppState, NodeWrapper, PlatformNode, PlatformRootNode}; use accesskit::{kurbo::Rect, ActionHandler, NodeId, Role, TreeUpdate}; use accesskit_consumer::{Node, Tree, TreeChangeHandler}; use atspi::{Interface, InterfaceSet, State}; +use crate::{ + atspi::{ + interfaces::{ + AccessibleInterface, ActionInterface, ComponentInterface, + ObjectEvent, QueuedEvent, ValueInterface, WindowEvent, + }, + Bus, ObjectId, ACCESSIBLE_PATH_PREFIX, + }, + node::{filter, NodeWrapper, PlatformNode, PlatformRootNode}, + util::{AppContext, WindowBounds}, +}; use parking_lot::RwLock; use std::sync::Arc; pub struct Adapter { atspi_bus: Option, - app_state: Arc>, + _app_context: Arc>, + root_window_bounds: Arc>, tree: Arc, } @@ -33,21 +37,19 @@ impl Adapter { ) -> Self { let mut atspi_bus = Bus::a11y_bus(); let tree = Arc::new(Tree::new(initial_state(), action_handler)); - let app_state = Arc::new(RwLock::new(AppState::new( + let app_context = Arc::new(RwLock::new(AppContext::new( app_name, toolkit_name, toolkit_version, ))); atspi_bus.as_mut().and_then(|bus| { - bus.register_root_node(PlatformRootNode::new( - Arc::downgrade(&app_state), - Arc::downgrade(&tree), - )) - .ok() + bus.register_root_node(PlatformRootNode::new(&app_context, &tree)) + .ok() }); let adapter = Adapter { atspi_bus, - app_state, + _app_context: app_context, + root_window_bounds: Arc::new(RwLock::new(WindowBounds::default())), tree, }; { @@ -64,8 +66,7 @@ impl Adapter { objects_to_add.push(reader.root().id()); add_children(reader.root(), &mut objects_to_add); for id in objects_to_add { - let interfaces = - NodeWrapper::new(&reader.node_by_id(id).unwrap(), &adapter.app_state) + let interfaces = NodeWrapper::new(&reader.node_by_id(id).unwrap()) .interfaces(); adapter .register_interfaces(&adapter.tree, id, interfaces) @@ -89,26 +90,26 @@ impl Adapter { &path, AccessibleInterface::new( bus.unique_name().to_owned(), - PlatformNode::new(tree, id, &self.app_state), + PlatformNode::new(tree, id), ), )?; } if new_interfaces.contains(Interface::Action) { bus.register_interface( &path, - ActionInterface::new(PlatformNode::new(tree, id, &self.app_state)), + ActionInterface::new(PlatformNode::new(tree, id)), )?; } if new_interfaces.contains(Interface::Component) { bus.register_interface( &path, - ComponentInterface::new(PlatformNode::new(tree, id, &self.app_state)), + ComponentInterface::new(PlatformNode::new(tree, id), &self.root_window_bounds), )?; } if new_interfaces.contains(Interface::Value) { bus.register_interface( &path, - ValueInterface::new(PlatformNode::new(tree, id, &self.app_state)), + ValueInterface::new(PlatformNode::new(tree, id)), )?; } Ok(true) @@ -139,9 +140,9 @@ impl Adapter { } pub fn set_root_window_bounds(&self, outer: Rect, inner: Rect) { - let mut app_state = self.app_state.write(); - app_state.outer_bounds = outer; - app_state.inner_bounds = inner; + let mut bounds = self.root_window_bounds.write(); + bounds.outer = outer; + bounds.inner = inner; } pub fn update(&self, update: TreeUpdate) -> QueuedEvents { @@ -152,14 +153,14 @@ impl Adapter { } impl TreeChangeHandler for Handler<'_> { fn node_added(&mut self, node: &Node) { - let interfaces = NodeWrapper::new(node, &self.adapter.app_state).interfaces(); + let interfaces = NodeWrapper::new(node).interfaces(); self.adapter .register_interfaces(self.tree, node.id(), interfaces) .unwrap(); } fn node_updated(&mut self, old_node: &Node, new_node: &Node) { - let old_wrapper = NodeWrapper::new(old_node, &self.adapter.app_state); - let new_wrapper = NodeWrapper::new(new_node, &self.adapter.app_state); + let old_wrapper = NodeWrapper::new(old_node); + let new_wrapper = NodeWrapper::new(new_node); let old_interfaces = old_wrapper.interfaces(); let new_interfaces = new_wrapper.interfaces(); let kept_interfaces = old_interfaces & new_interfaces; @@ -177,28 +178,24 @@ impl Adapter { if old_window.map(|n| n.id()) != new_window.map(|n| n.id()) { if let Some(window) = old_window { self.adapter.window_deactivated( - &NodeWrapper::new(&window, &self.adapter.app_state), + &NodeWrapper::new(&window), &mut self.queue, ); } if let Some(window) = new_window { self.adapter.window_activated( - &NodeWrapper::new(&window, &self.adapter.app_state), + &NodeWrapper::new(&window), &mut self.queue, ); } } - if let Some(node) = - new_node.map(|node| NodeWrapper::new(node, &self.adapter.app_state)) - { + if let Some(node) = new_node.map(NodeWrapper::new) { self.queue.push(QueuedEvent::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, true), }); } - if let Some(node) = - old_node.map(|node| NodeWrapper::new(node, &self.adapter.app_state)) - { + if let Some(node) = old_node.map(NodeWrapper::new) { self.queue.push(QueuedEvent::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, false), @@ -206,7 +203,7 @@ impl Adapter { } } fn node_removed(&mut self, node: &Node) { - let node = NodeWrapper::new(node, &self.adapter.app_state); + let node = NodeWrapper::new(node); self.adapter .unregister_interfaces(&node.id(), node.interfaces()) .unwrap(); diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/linux/src/atspi/bus.rs index ce9a6d80d..94b45daee 100644 --- a/platforms/linux/src/atspi/bus.rs +++ b/platforms/linux/src/atspi/bus.rs @@ -66,8 +66,8 @@ impl Bus { self.unique_name().as_str(), ObjectPath::from_str_unchecked(ROOT_PATH), ))?; - if let Some(state) = node.state.upgrade() { - state.write().desktop_address = Some(desktop.into()); + if let Some(context) = node.context.upgrade() { + context.write().desktop_address = Some(desktop.into()); } Ok(true) } else { diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/linux/src/atspi/interfaces/accessible.rs index 95bf517a3..953a87e62 100644 --- a/platforms/linux/src/atspi/interfaces/accessible.rs +++ b/platforms/linux/src/atspi/interfaces/accessible.rs @@ -3,9 +3,11 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress}; -use crate::{PlatformNode, PlatformRootNode}; use atspi::{accessible::Role, Interface, InterfaceSet, StateSet}; +use crate::{ + atspi::{ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress}, + PlatformNode, PlatformRootNode, unknown_object, +}; use std::convert::TryInto; use zbus::{fdo, names::OwnedUniqueName, MessageHeader}; @@ -55,7 +57,6 @@ impl AccessibleInterface { fn accessible_id(&self) -> ObjectId { self.node .accessible_id() - .unwrap_or_else(|_| unsafe { ObjectId::from_str_unchecked("") }) } fn get_child_at_index( @@ -115,9 +116,9 @@ impl AccessibleInterface { #[dbus_interface(property)] fn name(&self) -> String { self.node - .state + .context .upgrade() - .map(|state| state.read().name.clone()) + .map(|context| context.read().name.clone()) .unwrap_or_default() } @@ -129,14 +130,15 @@ impl AccessibleInterface { #[dbus_interface(property)] fn parent(&self) -> OwnedObjectAddress { self.node - .state + .context .upgrade() - .and_then(|state| state.read().desktop_address.clone()) + .and_then(|context| context.read().desktop_address.clone()) .unwrap_or_else(|| OwnedObjectAddress::null(self.bus_name.clone())) } #[dbus_interface(property)] fn child_count(&self) -> i32 { + // TODO: Handle multiple top-level windows. 1 } @@ -155,6 +157,7 @@ impl AccessibleInterface { #[zbus(header)] hdr: MessageHeader<'_>, index: i32, ) -> fdo::Result<(OwnedObjectAddress,)> { + // TODO: Handle multiple top-level windows. if index != 0 { return super::object_address(hdr.destination()?, None); } @@ -167,6 +170,7 @@ impl AccessibleInterface { } fn get_children(&self) -> fdo::Result> { + // TODO: Handle multiple top-level windows. self.node .tree .upgrade() @@ -177,7 +181,7 @@ impl AccessibleInterface { ) .into()] }) - .ok_or_else(|| fdo::Error::UnknownObject("".into())) + .ok_or_else(|| unknown_object(&ObjectId::root())) } fn get_index_in_parent(&self) -> i32 { diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/linux/src/atspi/interfaces/application.rs index 55c7a8bf7..8f08ccaae 100644 --- a/platforms/linux/src/atspi/interfaces/application.rs +++ b/platforms/linux/src/atspi/interfaces/application.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::PlatformRootNode; +use crate::{atspi::ObjectId, PlatformRootNode, unknown_object}; use zbus::fdo; pub(crate) struct ApplicationInterface(pub PlatformRootNode); @@ -13,18 +13,18 @@ impl ApplicationInterface { #[dbus_interface(property)] fn toolkit_name(&self) -> String { self.0 - .state + .context .upgrade() - .map(|state| state.read().toolkit_name.clone()) + .map(|context| context.read().toolkit_name.clone()) .unwrap_or_default() } #[dbus_interface(property)] fn version(&self) -> String { self.0 - .state + .context .upgrade() - .map(|state| state.read().toolkit_version.clone()) + .map(|context| context.read().toolkit_version.clone()) .unwrap_or_default() } @@ -36,18 +36,18 @@ impl ApplicationInterface { #[dbus_interface(property)] fn id(&self) -> i32 { self.0 - .state + .context .upgrade() - .and_then(|state| state.read().id) + .and_then(|context| context.read().id) .unwrap_or(-1) } #[dbus_interface(property)] fn set_id(&mut self, id: i32) -> fdo::Result<()> { self.0 - .state + .context .upgrade() - .map(|state| state.write().id = Some(id)) - .ok_or_else(|| fdo::Error::UnknownObject("".into())) + .map(|context| context.write().id = Some(id)) + .ok_or_else(|| unknown_object(&ObjectId::root())) } } diff --git a/platforms/linux/src/atspi/interfaces/component.rs b/platforms/linux/src/atspi/interfaces/component.rs index ee178e0dc..392a447cb 100644 --- a/platforms/linux/src/atspi/interfaces/component.rs +++ b/platforms/linux/src/atspi/interfaces/component.rs @@ -5,25 +5,42 @@ use crate::{ atspi::{object_address::OwnedObjectAddress, Rect}, - PlatformNode, + util::WindowBounds, + PlatformNode, unknown_object, }; use atspi::CoordType; +use parking_lot::RwLock; +use std::sync::{Arc, Weak}; use zbus::{fdo, MessageHeader}; pub(crate) struct ComponentInterface { node: PlatformNode, + root_window_bounds: Weak>, } impl ComponentInterface { - pub fn new(node: PlatformNode) -> Self { - Self { node } + pub(crate) fn new(node: PlatformNode, root_window_bounds: &Arc>) -> Self { + Self { + node, + root_window_bounds: Arc::downgrade(root_window_bounds), + } + } + + fn upgrade_bounds(&self) -> fdo::Result>> { + if let Some(bounds) = self.root_window_bounds.upgrade() { + Ok(bounds) + } else { + Err(unknown_object(&self.node.accessible_id())) + } } } #[dbus_interface(name = "org.a11y.atspi.Component")] impl ComponentInterface { fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> fdo::Result { - self.node.contains(x, y, coord_type) + let window_bounds = self.upgrade_bounds()?; + let contains = self.node.contains(&window_bounds.read(), x, y, coord_type); + contains } fn get_accessible_at_point( @@ -33,14 +50,18 @@ impl ComponentInterface { y: i32, coord_type: CoordType, ) -> fdo::Result<(OwnedObjectAddress,)> { - super::object_address( + let window_bounds = self.upgrade_bounds()?; + let accessible = super::object_address( hdr.destination()?, - self.node.get_accessible_at_point(x, y, coord_type)?, - ) + self.node.get_accessible_at_point(&window_bounds.read(), x, y, coord_type)?, + ); + accessible } fn get_extents(&self, coord_type: CoordType) -> fdo::Result<(Rect,)> { - self.node.get_extents(coord_type) + let window_bounds = self.upgrade_bounds()?; + let extents = self.node.get_extents(&window_bounds.read(), coord_type); + extents } fn grab_focus(&self) -> fdo::Result { diff --git a/platforms/linux/src/lib.rs b/platforms/linux/src/lib.rs index 12422a390..b2fe64e91 100644 --- a/platforms/linux/src/lib.rs +++ b/platforms/linux/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. @@ -9,6 +9,7 @@ extern crate zbus; mod adapter; mod atspi; mod node; +mod util; pub use adapter::Adapter; -pub(crate) use node::{PlatformNode, PlatformRootNode}; +pub(crate) use node::{PlatformNode, PlatformRootNode, unknown_object}; diff --git a/platforms/linux/src/node.rs b/platforms/linux/src/node.rs index 507089e60..98e73ef7f 100644 --- a/platforms/linux/src/node.rs +++ b/platforms/linux/src/node.rs @@ -8,16 +8,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE.chromium file. -use crate::atspi::{ - interfaces::{Action, ObjectEvent, Property, QueuedEvent}, - ObjectId, ObjectRef, OwnedObjectAddress, Rect as AtspiRect, -}; -use accesskit::{ - kurbo::{Point, Rect}, - CheckedState, DefaultActionVerb, NodeId, Role, -}; +use accesskit::{kurbo::Point, CheckedState, DefaultActionVerb, NodeId, Role}; use accesskit_consumer::{FilterResult, Node, Tree, TreeState}; use atspi::{accessible::Role as AtspiRole, CoordType, Interface, InterfaceSet, State, StateSet}; +use crate::{ + atspi::{ + interfaces::{Action, ObjectEvent, Property, QueuedEvent}, + ACCESSIBLE_PATH_PREFIX, ObjectId, ObjectRef, Rect as AtspiRect, + }, + util::{AppContext, WindowBounds}, +}; use parking_lot::RwLock; use std::{ convert::TryFrom, @@ -44,15 +44,11 @@ pub(crate) fn filter(node: &Node) -> FilterResult { pub(crate) struct NodeWrapper<'a> { node: &'a Node<'a>, - app_state: Arc>, } impl<'a> NodeWrapper<'a> { - pub(crate) fn new(node: &'a Node<'a>, app_state: &Arc>) -> Self { - NodeWrapper { - node, - app_state: app_state.clone(), - } + pub(crate) fn new(node: &'a Node<'a>) -> Self { + NodeWrapper { node } } pub fn name(&self) -> String { @@ -525,27 +521,21 @@ impl<'a> NodeWrapper<'a> { } } -fn unknown_object() -> fdo::Error { - fdo::Error::UnknownObject("".into()) +pub(crate) fn unknown_object(id: &ObjectId) -> fdo::Error { + fdo::Error::UnknownObject(format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str())) } #[derive(Clone)] pub(crate) struct PlatformNode { tree: Weak, node_id: NodeId, - app_state: Weak>, } impl PlatformNode { - pub(crate) fn new( - tree: &Arc, - node_id: NodeId, - app_state: &Arc>, - ) -> Self { + pub(crate) fn new(tree: &Arc, node_id: NodeId) -> Self { Self { tree: Arc::downgrade(tree), node_id, - app_state: Arc::downgrade(app_state), } } @@ -553,37 +543,28 @@ impl PlatformNode { if let Some(tree) = self.tree.upgrade() { Ok(tree) } else { - Err(unknown_object()) - } - } - - fn upgrade_app_state(&self) -> fdo::Result>> { - if let Some(state) = self.app_state.upgrade() { - Ok(state) - } else { - Err(unknown_object()) + Err(unknown_object(&self.accessible_id())) } } fn with_state(&self, f: F) -> fdo::Result where - F: FnOnce((&TreeState, &Arc>)) -> fdo::Result, + F: FnOnce(&TreeState) -> fdo::Result, { let tree = self.upgrade_tree()?; - let app_state = self.upgrade_app_state()?; let state = tree.read(); - f((&state, &app_state)) + f(&state) } fn resolve(&self, f: F) -> fdo::Result where for<'a> F: FnOnce(NodeWrapper<'a>) -> fdo::Result, { - self.with_state(|(tree, app)| { + self.with_state(|tree| { if let Some(node) = tree.node_by_id(self.node_id) { - f(NodeWrapper::new(&node, app)) + f(NodeWrapper::new(&node)) } else { - Err(unknown_object()) + Err(unknown_object(&self.accessible_id())) } }) } @@ -595,7 +576,7 @@ impl PlatformNode { drop(state); Ok(tree) } else { - Err(unknown_object()) + Err(unknown_object(&self.accessible_id())) } } @@ -626,8 +607,8 @@ impl PlatformNode { self.resolve(|resolved| Ok(resolved.locale())) } - pub fn accessible_id(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.id())) + pub fn accessible_id(&self) -> ObjectId<'static> { + self.node_id.into() } pub fn child_at_index(&self, index: usize) -> fdo::Result> { @@ -693,36 +674,24 @@ impl PlatformNode { Ok(true) } - pub fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> fdo::Result { + pub fn contains(&self, window_bounds: &WindowBounds, x: i32, y: i32, coord_type: CoordType) -> fdo::Result { self.resolve(|wrapper| { - let app_state = wrapper.app_state.read(); let bounds = match wrapper.node.bounding_box() { Some(node_bounds) => { - let top_left = match coord_type { - CoordType::Screen => app_state.inner_bounds.origin(), - CoordType::Window => { - let outer_position = app_state.outer_bounds.origin(); - let inner_position = app_state.inner_bounds.origin(); - Point::new( - inner_position.x - outer_position.x, - inner_position.y - outer_position.y, - ) - } - _ => unimplemented!(), - }; + let top_left = window_bounds.top_left(coord_type, wrapper.node.is_root()); let new_origin = Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0); node_bounds.with_origin(new_origin) } None if wrapper.node.is_root() => { - let window_bounds = app_state.outer_bounds; + let bounds = window_bounds.outer; match coord_type { - CoordType::Screen => window_bounds, - CoordType::Window => window_bounds.with_origin(Point::ZERO), + CoordType::Screen => bounds, + CoordType::Window => bounds.with_origin(Point::ZERO), _ => unimplemented!(), } } - _ => return Err(unknown_object()), + _ => return Err(unknown_object(&wrapper.id())), }; Ok(bounds.contains(Point::new(x.into(), y.into()))) }) @@ -730,20 +699,13 @@ impl PlatformNode { pub fn get_accessible_at_point( &self, + window_bounds: &WindowBounds, x: i32, y: i32, coord_type: CoordType, ) -> fdo::Result> { self.resolve(|wrapper| { - let app_state = wrapper.app_state.read(); - let is_root = wrapper.node.is_root(); - let top_left = match coord_type { - CoordType::Screen if is_root => app_state.outer_bounds.origin(), - CoordType::Screen => app_state.inner_bounds.origin(), - CoordType::Window if is_root => Point::ZERO, - CoordType::Window => app_state.inner_bounds.origin(), - _ => unimplemented!(), - }; + let top_left = window_bounds.top_left(coord_type, wrapper.node.is_root()); let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y); Ok(wrapper .node @@ -752,36 +714,24 @@ impl PlatformNode { }) } - pub fn get_extents(&self, coord_type: CoordType) -> fdo::Result<(AtspiRect,)> { + pub fn get_extents(&self, window_bounds: &WindowBounds, coord_type: CoordType) -> fdo::Result<(AtspiRect,)> { self.resolve(|wrapper| { - let app_state = wrapper.app_state.read(); match wrapper.node.bounding_box() { Some(node_bounds) => { - let top_left = match coord_type { - CoordType::Screen => app_state.inner_bounds.origin(), - CoordType::Window => { - let outer_position = app_state.outer_bounds.origin(); - let inner_position = app_state.inner_bounds.origin(); - Point::new( - inner_position.x - outer_position.x, - inner_position.y - outer_position.y, - ) - } - _ => unimplemented!(), - }; + let top_left = window_bounds.top_left(coord_type, wrapper.node.is_root()); let new_origin = Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0); Ok((node_bounds.with_origin(new_origin).into(),)) } None if wrapper.node.is_root() => { - let window_bounds = app_state.outer_bounds; + let bounds = window_bounds.outer; Ok((match coord_type { - CoordType::Screen => window_bounds.into(), - CoordType::Window => window_bounds.with_origin(Point::ZERO).into(), + CoordType::Screen => bounds.into(), + CoordType::Window => bounds.with_origin(Point::ZERO).into(), _ => unimplemented!(), },)) } - _ => Err(unknown_object()), + _ => Err(unknown_object(&wrapper.id())), } }) } @@ -815,38 +765,17 @@ impl PlatformNode { } } -pub(crate) struct AppState { - pub name: String, - pub toolkit_name: String, - pub toolkit_version: String, - pub id: Option, - pub desktop_address: Option, - pub outer_bounds: Rect, - pub inner_bounds: Rect, -} - -impl AppState { - pub fn new(name: String, toolkit_name: String, toolkit_version: String) -> Self { - Self { - name, - toolkit_name, - toolkit_version, - id: None, - desktop_address: None, - outer_bounds: Rect::default(), - inner_bounds: Rect::default(), - } - } -} - #[derive(Clone)] pub(crate) struct PlatformRootNode { - pub state: Weak>, - pub tree: Weak, + pub(crate) context: Weak>, + pub(crate) tree: Weak, } impl PlatformRootNode { - pub fn new(state: Weak>, tree: Weak) -> Self { - Self { state, tree } + pub fn new(context: &Arc>, tree: &Arc) -> Self { + Self { + context: Arc::downgrade(context), + tree: Arc::downgrade(tree), + } } } diff --git a/platforms/linux/src/util.rs b/platforms/linux/src/util.rs new file mode 100644 index 000000000..990eff8c4 --- /dev/null +++ b/platforms/linux/src/util.rs @@ -0,0 +1,57 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use accesskit::kurbo::{Point, Rect}; +use atspi::CoordType; +use crate::atspi::OwnedObjectAddress; + +pub(crate) struct AppContext { + pub(crate) name: String, + pub(crate) toolkit_name: String, + pub(crate) toolkit_version: String, + pub(crate) id: Option, + pub(crate) desktop_address: Option, +} + +impl AppContext { + pub(crate) fn new( + name: String, + toolkit_name: String, + toolkit_version: String + ) -> Self { + Self { + name, + toolkit_name, + toolkit_version, + id: None, + desktop_address: None, + } + } +} + +#[derive(Default)] +pub(crate) struct WindowBounds { + pub(crate) outer: Rect, + pub(crate) inner: Rect, +} + +impl WindowBounds { + pub(crate) fn top_left(&self, coord_type: CoordType, is_root: bool) -> Point { + match coord_type { + CoordType::Screen if is_root => self.outer.origin(), + CoordType::Screen => self.inner.origin(), + CoordType::Window if is_root => Point::ZERO, + CoordType::Window => { + let outer_position = self.outer.origin(); + let inner_position = self.inner.origin(); + Point::new( + inner_position.x - outer_position.x, + inner_position.y - outer_position.y, + ) + } + _ => unimplemented!(), + } + } +} From 0acef5fea66bbe161c7bce3fe12dfff0b68e0a95 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Tue, 20 Dec 2022 15:12:31 +0100 Subject: [PATCH 45/69] Rename the Linux adapter, improve conditional compilation inside accesskit_winit --- Cargo.toml | 2 +- platforms/{linux => unix}/Cargo.toml | 2 +- platforms/{linux => unix}/src/adapter.rs | 0 platforms/{linux => unix}/src/atspi/bus.rs | 0 .../src/atspi/interfaces/accessible.rs | 0 .../src/atspi/interfaces/action.rs | 0 .../src/atspi/interfaces/application.rs | 0 .../src/atspi/interfaces/component.rs | 0 .../src/atspi/interfaces/events.rs | 0 .../src/atspi/interfaces/mod.rs | 0 .../src/atspi/interfaces/value.rs | 0 platforms/{linux => unix}/src/atspi/mod.rs | 0 .../src/atspi/object_address.rs | 0 .../{linux => unix}/src/atspi/object_id.rs | 0 .../{linux => unix}/src/atspi/object_ref.rs | 0 platforms/{linux => unix}/src/lib.rs | 0 platforms/{linux => unix}/src/node.rs | 0 platforms/{linux => unix}/src/util.rs | 0 platforms/winit/Cargo.toml | 6 +++--- platforms/winit/src/platform_impl/mod.rs | 21 +++++++++++++++---- .../src/platform_impl/{linux.rs => unix.rs} | 6 +++--- 21 files changed, 25 insertions(+), 12 deletions(-) rename platforms/{linux => unix}/Cargo.toml (95%) rename platforms/{linux => unix}/src/adapter.rs (100%) rename platforms/{linux => unix}/src/atspi/bus.rs (100%) rename platforms/{linux => unix}/src/atspi/interfaces/accessible.rs (100%) rename platforms/{linux => unix}/src/atspi/interfaces/action.rs (100%) rename platforms/{linux => unix}/src/atspi/interfaces/application.rs (100%) rename platforms/{linux => unix}/src/atspi/interfaces/component.rs (100%) rename platforms/{linux => unix}/src/atspi/interfaces/events.rs (100%) rename platforms/{linux => unix}/src/atspi/interfaces/mod.rs (100%) rename platforms/{linux => unix}/src/atspi/interfaces/value.rs (100%) rename platforms/{linux => unix}/src/atspi/mod.rs (100%) rename platforms/{linux => unix}/src/atspi/object_address.rs (100%) rename platforms/{linux => unix}/src/atspi/object_id.rs (100%) rename platforms/{linux => unix}/src/atspi/object_ref.rs (100%) rename platforms/{linux => unix}/src/lib.rs (100%) rename platforms/{linux => unix}/src/node.rs (100%) rename platforms/{linux => unix}/src/util.rs (100%) rename platforms/winit/src/platform_impl/{linux.rs => unix.rs} (89%) mode change 100755 => 100644 diff --git a/Cargo.toml b/Cargo.toml index 8c0be21af..35433b258 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,8 @@ members = [ "common", "consumer", - "platforms/linux", "platforms/macos", + "platforms/unix", "platforms/windows", "platforms/winit", ] diff --git a/platforms/linux/Cargo.toml b/platforms/unix/Cargo.toml similarity index 95% rename from platforms/linux/Cargo.toml rename to platforms/unix/Cargo.toml index 0fa73761f..44827c848 100644 --- a/platforms/linux/Cargo.toml +++ b/platforms/unix/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "accesskit_linux" +name = "accesskit_unix" version = "0.1.0" authors = ["Arnold Loubriat "] license = "Apache-2.0" diff --git a/platforms/linux/src/adapter.rs b/platforms/unix/src/adapter.rs similarity index 100% rename from platforms/linux/src/adapter.rs rename to platforms/unix/src/adapter.rs diff --git a/platforms/linux/src/atspi/bus.rs b/platforms/unix/src/atspi/bus.rs similarity index 100% rename from platforms/linux/src/atspi/bus.rs rename to platforms/unix/src/atspi/bus.rs diff --git a/platforms/linux/src/atspi/interfaces/accessible.rs b/platforms/unix/src/atspi/interfaces/accessible.rs similarity index 100% rename from platforms/linux/src/atspi/interfaces/accessible.rs rename to platforms/unix/src/atspi/interfaces/accessible.rs diff --git a/platforms/linux/src/atspi/interfaces/action.rs b/platforms/unix/src/atspi/interfaces/action.rs similarity index 100% rename from platforms/linux/src/atspi/interfaces/action.rs rename to platforms/unix/src/atspi/interfaces/action.rs diff --git a/platforms/linux/src/atspi/interfaces/application.rs b/platforms/unix/src/atspi/interfaces/application.rs similarity index 100% rename from platforms/linux/src/atspi/interfaces/application.rs rename to platforms/unix/src/atspi/interfaces/application.rs diff --git a/platforms/linux/src/atspi/interfaces/component.rs b/platforms/unix/src/atspi/interfaces/component.rs similarity index 100% rename from platforms/linux/src/atspi/interfaces/component.rs rename to platforms/unix/src/atspi/interfaces/component.rs diff --git a/platforms/linux/src/atspi/interfaces/events.rs b/platforms/unix/src/atspi/interfaces/events.rs similarity index 100% rename from platforms/linux/src/atspi/interfaces/events.rs rename to platforms/unix/src/atspi/interfaces/events.rs diff --git a/platforms/linux/src/atspi/interfaces/mod.rs b/platforms/unix/src/atspi/interfaces/mod.rs similarity index 100% rename from platforms/linux/src/atspi/interfaces/mod.rs rename to platforms/unix/src/atspi/interfaces/mod.rs diff --git a/platforms/linux/src/atspi/interfaces/value.rs b/platforms/unix/src/atspi/interfaces/value.rs similarity index 100% rename from platforms/linux/src/atspi/interfaces/value.rs rename to platforms/unix/src/atspi/interfaces/value.rs diff --git a/platforms/linux/src/atspi/mod.rs b/platforms/unix/src/atspi/mod.rs similarity index 100% rename from platforms/linux/src/atspi/mod.rs rename to platforms/unix/src/atspi/mod.rs diff --git a/platforms/linux/src/atspi/object_address.rs b/platforms/unix/src/atspi/object_address.rs similarity index 100% rename from platforms/linux/src/atspi/object_address.rs rename to platforms/unix/src/atspi/object_address.rs diff --git a/platforms/linux/src/atspi/object_id.rs b/platforms/unix/src/atspi/object_id.rs similarity index 100% rename from platforms/linux/src/atspi/object_id.rs rename to platforms/unix/src/atspi/object_id.rs diff --git a/platforms/linux/src/atspi/object_ref.rs b/platforms/unix/src/atspi/object_ref.rs similarity index 100% rename from platforms/linux/src/atspi/object_ref.rs rename to platforms/unix/src/atspi/object_ref.rs diff --git a/platforms/linux/src/lib.rs b/platforms/unix/src/lib.rs similarity index 100% rename from platforms/linux/src/lib.rs rename to platforms/unix/src/lib.rs diff --git a/platforms/linux/src/node.rs b/platforms/unix/src/node.rs similarity index 100% rename from platforms/linux/src/node.rs rename to platforms/unix/src/node.rs diff --git a/platforms/linux/src/util.rs b/platforms/unix/src/util.rs similarity index 100% rename from platforms/linux/src/util.rs rename to platforms/unix/src/util.rs diff --git a/platforms/winit/Cargo.toml b/platforms/winit/Cargo.toml index dea1db96a..91734b7d5 100644 --- a/platforms/winit/Cargo.toml +++ b/platforms/winit/Cargo.toml @@ -15,14 +15,14 @@ accesskit = { version = "0.8.1", path = "../../common" } parking_lot = "0.12.1" winit = { version = "0.27.2", default-features = false, features = ["x11", "wayland", "wayland-dlopen"] } -[target.'cfg(target_os = "linux")'.dependencies] -accesskit_linux = { version = "0.1.0", path = "../linux" } - [target.'cfg(target_os = "windows")'.dependencies] accesskit_windows = { version = "0.10.2", path = "../windows" } [target.'cfg(target_os = "macos")'.dependencies] accesskit_macos = { version = "0.4.0", path = "../macos" } +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] +accesskit_unix = { version = "0.1.0", path = "../unix" } + [dev-dependencies] winit = "0.27.2" diff --git a/platforms/winit/src/platform_impl/mod.rs b/platforms/winit/src/platform_impl/mod.rs index 4bfcff829..1d0956c36 100644 --- a/platforms/winit/src/platform_impl/mod.rs +++ b/platforms/winit/src/platform_impl/mod.rs @@ -14,11 +14,24 @@ mod platform; #[path = "macos.rs"] mod platform; -#[cfg(target_os = "linux")] -#[path = "linux.rs"] +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +#[path = "unix.rs"] mod platform; -#[cfg(all(not(target_os = "windows"), not(target_os = "macos"), not(target_os = "linux")))] -#[cfg(all(not(target_os = "windows"), not(target_os = "linux"),))] +#[cfg(all( + not(target_os = "windows"), + not(target_os = "macos"), + not(target_os = "linux"), + not(target_os = "dragonfly"), + not(target_os = "freebsd"), + not(target_os = "netbsd"), + not(target_os = "openbsd") +))] #[path = "null.rs"] mod platform; diff --git a/platforms/winit/src/platform_impl/linux.rs b/platforms/winit/src/platform_impl/unix.rs old mode 100755 new mode 100644 similarity index 89% rename from platforms/winit/src/platform_impl/linux.rs rename to platforms/winit/src/platform_impl/unix.rs index 62d91861a..c6ec4db68 --- a/platforms/winit/src/platform_impl/linux.rs +++ b/platforms/winit/src/platform_impl/unix.rs @@ -3,11 +3,11 @@ // the LICENSE-APACHE file). use accesskit::{kurbo::Rect, ActionHandler, TreeUpdate}; -use accesskit_linux::Adapter as LinuxAdapter; +use accesskit_unix::Adapter as UnixAdapter; use winit::window::Window; pub struct Adapter { - adapter: LinuxAdapter, + adapter: UnixAdapter, } impl Adapter { @@ -16,7 +16,7 @@ impl Adapter { source: Box TreeUpdate>, action_handler: Box, ) -> Self { - let adapter = LinuxAdapter::new( + let adapter = UnixAdapter::new( String::new(), String::new(), String::new(), From 2b85e230cb98a7bb1d7ef3ad983f750468b4bf15 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Tue, 20 Dec 2022 16:36:25 +0100 Subject: [PATCH 46/69] Slightly improve lazy initialization process --- platforms/unix/src/adapter.rs | 128 ++++++++++------------ platforms/winit/src/platform_impl/unix.rs | 14 ++- 2 files changed, 70 insertions(+), 72 deletions(-) diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index 77b22a09b..7e9e2a4d0 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -21,7 +21,7 @@ use parking_lot::RwLock; use std::sync::Arc; pub struct Adapter { - atspi_bus: Option, + atspi_bus: Bus, _app_context: Arc>, root_window_bounds: Arc>, tree: Arc, @@ -34,18 +34,16 @@ impl Adapter { toolkit_version: String, initial_state: Box TreeUpdate>, action_handler: Box, - ) -> Self { - let mut atspi_bus = Bus::a11y_bus(); + ) -> Option { + let mut atspi_bus = Bus::a11y_bus()?; let tree = Arc::new(Tree::new(initial_state(), action_handler)); let app_context = Arc::new(RwLock::new(AppContext::new( app_name, toolkit_name, toolkit_version, ))); - atspi_bus.as_mut().and_then(|bus| { - bus.register_root_node(PlatformRootNode::new(&app_context, &tree)) - .ok() - }); + atspi_bus.register_root_node(PlatformRootNode::new(&app_context, &tree)) + .ok()?; let adapter = Adapter { atspi_bus, _app_context: app_context, @@ -73,7 +71,7 @@ impl Adapter { .unwrap(); } } - adapter + Some(adapter) } fn register_interfaces( @@ -82,38 +80,35 @@ impl Adapter { id: NodeId, new_interfaces: InterfaceSet, ) -> zbus::Result { - self.atspi_bus.as_ref().map_or(Ok(false), |bus| { - let atspi_id = ObjectId::from(id); - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, atspi_id.as_str()); - if new_interfaces.contains(Interface::Accessible) { - bus.register_interface( - &path, - AccessibleInterface::new( - bus.unique_name().to_owned(), - PlatformNode::new(tree, id), - ), - )?; - } - if new_interfaces.contains(Interface::Action) { - bus.register_interface( - &path, - ActionInterface::new(PlatformNode::new(tree, id)), - )?; - } - if new_interfaces.contains(Interface::Component) { - bus.register_interface( - &path, - ComponentInterface::new(PlatformNode::new(tree, id), &self.root_window_bounds), - )?; - } - if new_interfaces.contains(Interface::Value) { - bus.register_interface( - &path, - ValueInterface::new(PlatformNode::new(tree, id)), - )?; - } - Ok(true) - }) + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, ObjectId::from(id).as_str()); + if new_interfaces.contains(Interface::Accessible) { + self.atspi_bus.register_interface( + &path, + AccessibleInterface::new( + self.atspi_bus.unique_name().to_owned(), + PlatformNode::new(tree, id), + ), + )?; + } + if new_interfaces.contains(Interface::Action) { + self.atspi_bus.register_interface( + &path, + ActionInterface::new(PlatformNode::new(tree, id)), + )?; + } + if new_interfaces.contains(Interface::Component) { + self.atspi_bus.register_interface( + &path, + ComponentInterface::new(PlatformNode::new(tree, id), &self.root_window_bounds), + )?; + } + if new_interfaces.contains(Interface::Value) { + self.atspi_bus.register_interface( + &path, + ValueInterface::new(PlatformNode::new(tree, id)), + )?; + } + Ok(true) } fn unregister_interfaces( @@ -121,22 +116,20 @@ impl Adapter { id: &ObjectId, old_interfaces: InterfaceSet, ) -> zbus::Result { - self.atspi_bus.as_ref().map_or(Ok(false), |bus| { - let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str()); - if old_interfaces.contains(Interface::Accessible) { - bus.unregister_interface::>(&path)?; - } - if old_interfaces.contains(Interface::Action) { - bus.unregister_interface::(&path)?; - } - if old_interfaces.contains(Interface::Component) { - bus.unregister_interface::(&path)?; - } - if old_interfaces.contains(Interface::Value) { - bus.unregister_interface::(&path)?; - } - Ok(true) - }) + let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str()); + if old_interfaces.contains(Interface::Accessible) { + self.atspi_bus.unregister_interface::>(&path)?; + } + if old_interfaces.contains(Interface::Action) { + self.atspi_bus.unregister_interface::(&path)?; + } + if old_interfaces.contains(Interface::Component) { + self.atspi_bus.unregister_interface::(&path)?; + } + if old_interfaces.contains(Interface::Value) { + self.atspi_bus.unregister_interface::(&path)?; + } + Ok(true) } pub fn set_root_window_bounds(&self, outer: Rect, inner: Rect) { @@ -262,23 +255,22 @@ fn containing_window(node: Node) -> Option { #[must_use = "events must be explicitly raised"] pub struct QueuedEvents { - bus: Option, + bus: Bus, queue: Vec, } impl QueuedEvents { pub fn raise(&self) { - if let Some(bus) = &self.bus { - for event in &self.queue { - let _ = match &event { - QueuedEvent::Object { target, event } => bus.emit_object_event(target, event), - QueuedEvent::Window { - target, - name, - event, - } => bus.emit_window_event(target, name, event), - }; - } + for event in &self.queue { + let _ = match &event { + QueuedEvent::Object { target, event } => + self.bus.emit_object_event(target, event), + QueuedEvent::Window { + target, + name, + event, + } => self.bus.emit_window_event(target, name, event), + }; } } } diff --git a/platforms/winit/src/platform_impl/unix.rs b/platforms/winit/src/platform_impl/unix.rs index c6ec4db68..1c7b77500 100644 --- a/platforms/winit/src/platform_impl/unix.rs +++ b/platforms/winit/src/platform_impl/unix.rs @@ -7,7 +7,7 @@ use accesskit_unix::Adapter as UnixAdapter; use winit::window::Window; pub struct Adapter { - adapter: UnixAdapter, + adapter: Option, } impl Adapter { @@ -27,14 +27,20 @@ impl Adapter { } pub fn set_root_window_bounds(&self, outer: Rect, inner: Rect) { - self.adapter.set_root_window_bounds(outer, inner); + if let Some(adapter) = self.adapter { + adapter.set_root_window_bounds(outer, inner); + } } pub fn update(&self, update: TreeUpdate) { - self.adapter.update(update).raise(); + if let Some(adapter) = self.adapter { + adapter.update(update).raise(); + } } pub fn update_if_active(&self, updater: impl FnOnce() -> TreeUpdate) { - self.adapter.update(updater()).raise(); + if let Some(adapter) = self.adapter { + adapter.update(updater()).raise(); + } } } From ae7ae165347c3fc8a80991a52b509ef8b8283ac7 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Tue, 20 Dec 2022 20:20:58 +0100 Subject: [PATCH 47/69] Raise proper event when removing a node --- platforms/unix/src/adapter.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index 7e9e2a4d0..c9220d64b 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -197,6 +197,10 @@ impl Adapter { } fn node_removed(&mut self, node: &Node) { let node = NodeWrapper::new(node); + self.queue.push(QueuedEvent::Object { + target: node.id(), + event: ObjectEvent::StateChanged(State::Defunct, true), + }); self.adapter .unregister_interfaces(&node.id(), node.interfaces()) .unwrap(); From 8e1de32e41e92a6fcc6601814b73ae213ca8c5b6 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 21 Dec 2022 21:53:06 +0100 Subject: [PATCH 48/69] Update to latest accesskit --- consumer/src/node.rs | 34 +- platforms/unix/Cargo.toml | 4 +- platforms/unix/src/adapter.rs | 53 ++- .../unix/src/atspi/interfaces/accessible.rs | 4 +- platforms/unix/src/node.rs | 372 ++++++++++-------- platforms/winit/src/lib.rs | 24 +- platforms/winit/src/platform_impl/unix.rs | 8 +- 7 files changed, 279 insertions(+), 220 deletions(-) diff --git a/consumer/src/node.rs b/consumer/src/node.rs index 4e8133701..876d69532 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -103,17 +103,21 @@ impl<'a> Node<'a> { (self.tree_state.node_by_id(*parent).unwrap(), *index) }) } +} +impl NodeState { pub fn child_ids( &self, ) -> impl DoubleEndedIterator + ExactSizeIterator + FusedIterator - + 'a { - let data = &self.state.data; + + '_ { + let data = &self.data; data.children.iter().copied() } +} +impl<'a> Node<'a> { pub fn children( &self, ) -> impl DoubleEndedIterator> @@ -121,7 +125,7 @@ impl<'a> Node<'a> { + FusedIterator> + 'a { let state = self.tree_state; - self.child_ids() + self.state.child_ids() .map(move |id| state.node_by_id(id).unwrap()) } @@ -267,23 +271,31 @@ impl<'a> Node<'a> { }; parent_transform * self.direct_transform() } +} +impl NodeState { + pub fn raw_bounds(&self) -> Option { + self.data().bounds + } +} + +impl<'a> Node<'a> { pub fn has_bounds(&self) -> bool { - self.data().bounds.is_some() + self.state.raw_bounds().is_some() } /// Returns the node's transformed bounding box relative to the tree's /// container (e.g. window). pub fn bounding_box(&self) -> Option { - self.data() - .bounds + self.state + .raw_bounds() .as_ref() .map(|rect| self.transform().transform_rect_bbox(*rect)) } pub(crate) fn bounding_box_in_coordinate_space(&self, other: &Node) -> Option { - self.data() - .bounds + self.state + .raw_bounds() .as_ref() .map(|rect| self.relative_transform(other).transform_rect_bbox(*rect)) } @@ -307,7 +319,7 @@ impl<'a> Node<'a> { } if filter_result == FilterResult::Include { - if let Some(rect) = &self.data().bounds { + if let Some(rect) = &self.state.raw_bounds() { if rect.contains(point) { return Some((*self, point)); } @@ -413,6 +425,10 @@ impl NodeState { self.data().multiline } + pub fn is_protected(&self) -> bool { + self.data().protected + } + pub fn default_action_verb(&self) -> Option { self.data().default_action_verb } diff --git a/platforms/unix/Cargo.toml b/platforms/unix/Cargo.toml index 44827c848..23e24b782 100644 --- a/platforms/unix/Cargo.toml +++ b/platforms/unix/Cargo.toml @@ -11,8 +11,8 @@ readme = "README.md" edition = "2021" [dependencies] -accesskit = { version = "0.7.0" } -accesskit_consumer = { version = "0.7.1" } +accesskit = { version = "0.8.1", path = "../../common" } +accesskit_consumer = { version = "0.11.0", path = "../../consumer" } atspi = "0.3.10" parking_lot = "0.12.1" serde = "1.0" diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index c9220d64b..49b908e9b 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use accesskit::{kurbo::Rect, ActionHandler, NodeId, Role, TreeUpdate}; -use accesskit_consumer::{Node, Tree, TreeChangeHandler}; +use accesskit_consumer::{DetachedNode, Node, Tree, TreeChangeHandler, TreeState}; use atspi::{Interface, InterfaceSet, State}; use crate::{ atspi::{ @@ -32,7 +32,7 @@ impl Adapter { app_name: String, toolkit_name: String, toolkit_version: String, - initial_state: Box TreeUpdate>, + initial_state: impl 'static + FnOnce() -> TreeUpdate, action_handler: Box, ) -> Option { let mut atspi_bus = Bus::a11y_bus()?; @@ -64,7 +64,7 @@ impl Adapter { objects_to_add.push(reader.root().id()); add_children(reader.root(), &mut objects_to_add); for id in objects_to_add { - let interfaces = NodeWrapper::new(&reader.node_by_id(id).unwrap()) + let interfaces = NodeWrapper::Node(&reader.node_by_id(id).unwrap()) .interfaces(); adapter .register_interfaces(&adapter.tree, id, interfaces) @@ -146,14 +146,14 @@ impl Adapter { } impl TreeChangeHandler for Handler<'_> { fn node_added(&mut self, node: &Node) { - let interfaces = NodeWrapper::new(node).interfaces(); + let interfaces = NodeWrapper::Node(node).interfaces(); self.adapter .register_interfaces(self.tree, node.id(), interfaces) .unwrap(); } - fn node_updated(&mut self, old_node: &Node, new_node: &Node) { - let old_wrapper = NodeWrapper::new(old_node); - let new_wrapper = NodeWrapper::new(new_node); + fn node_updated(&mut self, old_node: &DetachedNode, new_node: &Node) { + let old_wrapper = NodeWrapper::DetachedNode(old_node); + let new_wrapper = NodeWrapper::Node(new_node); let old_interfaces = old_wrapper.interfaces(); let new_interfaces = new_wrapper.interfaces(); let kept_interfaces = old_interfaces & new_interfaces; @@ -165,38 +165,35 @@ impl Adapter { .unwrap(); new_wrapper.enqueue_changes(&mut self.queue, &old_wrapper); } - fn focus_moved(&mut self, old_node: Option<&Node>, new_node: Option<&Node>) { - let old_window = old_node.and_then(|node| containing_window(*node)); - let new_window = new_node.and_then(|node| containing_window(*node)); - if old_window.map(|n| n.id()) != new_window.map(|n| n.id()) { - if let Some(window) = old_window { - self.adapter.window_deactivated( - &NodeWrapper::new(&window), + fn focus_moved(&mut self, old_node: Option<&DetachedNode>, new_node: Option<&Node>) { + if let Some(root_window) = root_window(&self.tree.read()) { + if old_node.is_none() && new_node.is_some() { + self.adapter.window_activated( + &NodeWrapper::Node(&root_window), &mut self.queue, ); - } - if let Some(window) = new_window { - self.adapter.window_activated( - &NodeWrapper::new(&window), + } else if old_node.is_some() && new_node.is_none() { + self.adapter.window_deactivated( + &NodeWrapper::Node(&root_window), &mut self.queue, ); } } - if let Some(node) = new_node.map(NodeWrapper::new) { + if let Some(node) = new_node.map(NodeWrapper::Node) { self.queue.push(QueuedEvent::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, true), }); } - if let Some(node) = old_node.map(NodeWrapper::new) { + if let Some(node) = old_node.map(NodeWrapper::DetachedNode) { self.queue.push(QueuedEvent::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, false), }); } } - fn node_removed(&mut self, node: &Node) { - let node = NodeWrapper::new(node); + fn node_removed(&mut self, node: &DetachedNode, _: &TreeState) { + let node = NodeWrapper::DetachedNode(node); self.queue.push(QueuedEvent::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Defunct, true), @@ -243,16 +240,12 @@ impl Adapter { } } -fn containing_window(node: Node) -> Option { +fn root_window(current_state: &TreeState) -> Option { const WINDOW_ROLES: &[Role] = &[Role::AlertDialog, Role::Dialog, Role::Window]; - if WINDOW_ROLES.contains(&node.role()) { - Some(node) + let root = current_state.root(); + if WINDOW_ROLES.contains(&root.role()) { + Some(root) } else { - while let Some(node) = node.parent() { - if WINDOW_ROLES.contains(&node.role()) { - return Some(node); - } - } None } } diff --git a/platforms/unix/src/atspi/interfaces/accessible.rs b/platforms/unix/src/atspi/interfaces/accessible.rs index 953a87e62..ea136eb0c 100644 --- a/platforms/unix/src/atspi/interfaces/accessible.rs +++ b/platforms/unix/src/atspi/interfaces/accessible.rs @@ -49,8 +49,8 @@ impl AccessibleInterface { } #[dbus_interface(property)] - fn locale(&self) -> String { - self.node.locale().unwrap_or_default() + fn locale(&self) -> &str { + "" } #[dbus_interface(property)] diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 98e73ef7f..e0ae6b5ea 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -9,7 +9,9 @@ // found in the LICENSE.chromium file. use accesskit::{kurbo::Point, CheckedState, DefaultActionVerb, NodeId, Role}; -use accesskit_consumer::{FilterResult, Node, Tree, TreeState}; +use accesskit_consumer::{ + DetachedNode, FilterResult, Node, NodeState, Tree, TreeState, +}; use atspi::{accessible::Role as AtspiRole, CoordType, Interface, InterfaceSet, State, StateSet}; use crate::{ atspi::{ @@ -19,17 +21,10 @@ use crate::{ util::{AppContext, WindowBounds}, }; use parking_lot::RwLock; -use std::{ - convert::TryFrom, - sync::{Arc, Weak}, -}; +use std::{iter::FusedIterator, sync::{Arc, Weak}}; use zbus::fdo; -pub(crate) fn filter(node: &Node) -> FilterResult { - if node.is_focused() { - return FilterResult::Include; - } - +fn filter_common(node: &NodeState) -> FilterResult { if node.is_hidden() { return FilterResult::ExcludeSubtree; } @@ -42,17 +37,40 @@ pub(crate) fn filter(node: &Node) -> FilterResult { FilterResult::Include } -pub(crate) struct NodeWrapper<'a> { - node: &'a Node<'a>, +pub(crate) fn filter(node: &Node) -> FilterResult { + if node.is_focused() { + return FilterResult::Include; + } + + filter_common(node.state()) +} + +pub(crate) fn filter_detached(node: &DetachedNode) -> FilterResult { + if node.is_focused() { + return FilterResult::Include; + } + + filter_common(node.state()) +} + +pub(crate) enum NodeWrapper<'a> { + Node(&'a Node<'a>), + DetachedNode(&'a DetachedNode), } impl<'a> NodeWrapper<'a> { - pub(crate) fn new(node: &'a Node<'a>) -> Self { - NodeWrapper { node } + fn node_state(&self) -> &NodeState { + match self { + Self::Node(node) => node.state(), + Self::DetachedNode(node) => node.state(), + } } pub fn name(&self) -> String { - self.node.name().unwrap_or_default() + match self { + Self::Node(node) => node.name(), + Self::DetachedNode(node) => node.name(), + }.unwrap_or_default() } pub fn description(&self) -> String { @@ -60,23 +78,24 @@ impl<'a> NodeWrapper<'a> { } pub fn parent(&self) -> Option { - self.node.parent().map(|parent| parent.id().into()) - } - - pub fn child_count(&self) -> usize { - self.node.child_ids().count() + self.node_state().parent_id().map(Into::into) } - pub fn locale(&self) -> String { - String::new() + pub fn id(&self) -> ObjectId<'static> { + self.node_state().id().into() } - pub fn id(&self) -> ObjectId<'static> { - self.node.id().into() + pub fn child_ids( + &self + ) -> impl DoubleEndedIterator + + ExactSizeIterator + + FusedIterator + + '_ { + self.node_state().child_ids() } pub fn role(&self) -> AtspiRole { - match self.node.role() { + match self.node_state().role() { Role::Alert => AtspiRole::Notification, Role::AlertDialog => AtspiRole::Alert, Role::Comment | Role::Suggestion => AtspiRole::Section, @@ -150,7 +169,7 @@ impl<'a> NodeWrapper<'a> { Role::Document => AtspiRole::DocumentFrame, Role::EmbeddedObject => AtspiRole::Embedded, // TODO: Forms which lack an accessible name are no longer - // exposed as forms. http://crbug.com/874384. Forms which have accessible + // exposed as forms. Forms which have accessible // names should be exposed as `AtspiRole::Landmark` according to Core AAM. Role::Form => AtspiRole::Form, Role::Figure | Role::Feed => AtspiRole::Panel, @@ -165,13 +184,8 @@ impl<'a> NodeWrapper<'a> { Role::Group => AtspiRole::Panel, Role::Heading => AtspiRole::Heading, Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame, - Role::Image => { - if self.node.filtered_children(&filter).next().is_some() { - AtspiRole::ImageMap - } else { - AtspiRole::Image - } - } + // TODO: If there are unignored children, then it should be AtspiRole::ImageMap. + Role::Image => AtspiRole::Image, Role::InlineTextBox => AtspiRole::Static, Role::InputTime => AtspiRole::DateEditor, Role::LabelText | Role::Legend => AtspiRole::Label, @@ -180,13 +194,12 @@ impl<'a> NodeWrapper<'a> { Role::LayoutTableCell => AtspiRole::Section, Role::LayoutTableRow => AtspiRole::Section, // TODO: Having a separate accessible object for line breaks - // is inconsistent with other implementations. http://crbug.com/873144#c1. + // is inconsistent with other implementations. Role::LineBreak => AtspiRole::Static, Role::Link => AtspiRole::Link, Role::List => AtspiRole::List, Role::ListBox => AtspiRole::ListBox, - // TODO: Use `AtspiRole::MenuItem' inside a combo box, see how - // ax_platform_node_win.cc code does this. + // TODO: Use `AtspiRole::MenuItem' inside a combo box. Role::ListBoxOption => AtspiRole::ListItem, Role::ListGrid => AtspiRole::Table, Role::ListItem => AtspiRole::ListItem, @@ -198,13 +211,8 @@ impl<'a> NodeWrapper<'a> { // only if it still has non-ignored descendants, which happens only when => // - The list marker itself is ignored but the descendants are not // - Or the list marker contains images - Role::ListMarker => { - if self.node.filtered_children(&filter).next().is_none() { - AtspiRole::Static - } else { - AtspiRole::Panel - } - } + // TODO: How to check for unignored children when the node is detached? + Role::ListMarker => AtspiRole::Static, Role::Log => AtspiRole::Log, Role::Main => AtspiRole::Landmark, Role::Mark => AtspiRole::Static, @@ -223,20 +231,7 @@ impl<'a> NodeWrapper<'a> { Role::PdfActionableHighlight => AtspiRole::PushButton, Role::PdfRoot => AtspiRole::DocumentFrame, Role::PluginObject => AtspiRole::Embedded, - Role::PopupButton => { - // TODO: Add a getter for html_tag - //if self - // .node - // .data() - // .html_tag - // .as_ref() - // .map_or(false, |tag| tag.as_ref() == "select") - //{ - // AtspiRole::ComboBox - //} else { - AtspiRole::PushButton - //} - } + Role::PopupButton => AtspiRole::PushButton, Role::Portal => AtspiRole::PushButton, Role::Pre => AtspiRole::Section, Role::ProgressIndicator => AtspiRole::ProgressBar, @@ -250,9 +245,9 @@ impl<'a> NodeWrapper<'a> { // TODO: Generally exposed as description on (`Role::Ruby`) element, not // as its own object in the tree. // However, it's possible to make a `Role::RubyAnnotation` element show up in the - // AX tree, for example by adding tabindex="0" to the source or + // tree, for example by adding tabindex="0" to the source or // element or making the source element the target of an aria-owns. - // Therefore, browser side needs to gracefully handle it if it actually + // Therefore, we need to gracefully handle it if it actually // shows up in the tree. Role::RubyAnnotation => AtspiRole::Static, Role::Section => AtspiRole::Section, @@ -263,30 +258,25 @@ impl<'a> NodeWrapper<'a> { Role::Splitter => AtspiRole::Separator, Role::StaticText => AtspiRole::Static, Role::Status => AtspiRole::StatusBar, - // ax::mojom::Role::kSubscript => - // AtspiRole::Subscript, - // ax::mojom::Role::kSuperscript => - // AtspiRole::Superscript, Role::SvgRoot => AtspiRole::DocumentFrame, Role::Tab => AtspiRole::PageTab, Role::Table => AtspiRole::Table, // TODO: This mapping is correct, but it doesn't seem to be // used. We don't necessarily want to always expose these containers, but - // we must do so if they are focusable. http://crbug.com/874043 + // we must do so if they are focusable. Role::TableHeaderContainer => AtspiRole::Panel, Role::TabList => AtspiRole::PageTabList, Role::TabPanel => AtspiRole::ScrollPane, // TODO: This mapping should also be applied to the dfn - // element. http://crbug.com/874411 + // element. Role::Term => AtspiRole::DescriptionTerm, Role::TitleBar => AtspiRole::TitleBar, Role::TextField | Role::SearchBox => { - // TODO: Add a getter for protected - //if self.node.data().protected { - // AtspiRole::PasswordText - //} else { - AtspiRole::Entry - //} + if self.node_state().is_protected() { + AtspiRole::PasswordText + } else { + AtspiRole::Entry + } } Role::TextFieldWithComboBox => AtspiRole::ComboBox, Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => { @@ -312,12 +302,19 @@ impl<'a> NodeWrapper<'a> { } } + fn is_focused(&self) -> bool { + match self { + Self::Node(node) => node.is_focused(), + Self::DetachedNode(node) => node.is_focused(), + } + } + pub fn state(&self) -> StateSet { - let platform_role = self.role(); - //let data = self.node.data(); - let mut state = StateSet::empty(); - if self.node.role() == Role::Window && self.node.parent().is_none() { - state.insert(State::Active); + let state = self.node_state(); + let atspi_role = self.role(); + let mut atspi_state = StateSet::empty(); + if state.role() == Role::Window && state.parent_id().is_none() { + atspi_state.insert(State::Active); } //if let Some(expanded) = data.expanded { // state.insert(State::Expandable); @@ -332,18 +329,20 @@ impl<'a> NodeWrapper<'a> { // state.insert(State::Editable); //} // TODO: Focus and selection. - if self.node.is_focusable() { - state.insert(State::Focusable); + if state.is_focusable() { + atspi_state.insert(State::Focusable); } //match data.orientation { // Some(Orientation::Horizontal) => state.insert(State::Horizontal), // Some(Orientation::Vertical) => state.insert(State::Vertical), // _ => {} //} - if filter(self.node) == FilterResult::Include { - state.insert(State::Visible); - // if (!delegate_->IsOffscreen() && !is_minimized) - state.insert(State::Showing); + let filter_result = match self { + Self::Node(node) => filter(node), + Self::DetachedNode(node) => filter_detached(node), + }; + if filter_result == FilterResult::Include { + atspi_state.insert(State::Visible | State::Showing); } //if data.multiselectable { // state.insert(State::Multiselectable); @@ -361,8 +360,8 @@ impl<'a> NodeWrapper<'a> { // None | Some(AriaCurrent::False) => {} // _ => state.insert(State::Active), //} - if platform_role != AtspiRole::ToggleButton && self.node.checked_state().is_some() { - state.insert(State::Checkable); + if atspi_role != AtspiRole::ToggleButton && state.checked_state().is_some() { + atspi_state.insert(State::Checkable); } //if data.has_popup.is_some() { // state.insert(State::HasPopup); @@ -373,25 +372,25 @@ impl<'a> NodeWrapper<'a> { //if data.modal { // state.insert(State::Modal); //} - if let Some(selected) = self.node.is_selected() { - if !self.node.is_disabled() { - state.insert(State::Selectable); + if let Some(selected) = state.is_selected() { + if !state.is_disabled() { + atspi_state.insert(State::Selectable); } if selected { - state.insert(State::Selected); + atspi_state.insert(State::Selected); } } - if self.node.is_text_field() { - state.insert(State::SelectableText); - //match self.node.data().multiline { - // true => state.insert(State::MultiLine), - // false => state.insert(State::SingleLine), - //} + if state.is_text_field() { + atspi_state.insert(State::SelectableText); + atspi_state.insert(match state.is_multiline() { + true => State::MultiLine, + false => State::SingleLine, + }); } // Special case for indeterminate progressbar. - if self.node.role() == Role::ProgressIndicator && self.node.numeric_value().is_none() { - state.insert(State::Indeterminate); + if state.role() == Role::ProgressIndicator && state.numeric_value().is_none() { + atspi_state.insert(State::Indeterminate); } //let has_suggestion = data @@ -403,27 +402,24 @@ impl<'a> NodeWrapper<'a> { //} // Checked state - match self.node.checked_state() { - Some(CheckedState::Mixed) => state.insert(State::Indeterminate), - Some(CheckedState::True) => { - if platform_role == AtspiRole::ToggleButton { - state.insert(State::Pressed); - } else { - state.insert(State::Checked); - } - } + match state.checked_state() { + Some(CheckedState::Mixed) => + atspi_state.insert(State::Indeterminate), + Some(CheckedState::True) if atspi_role == AtspiRole::ToggleButton => + atspi_state.insert(State::Pressed), + Some(CheckedState::True) => + atspi_state.insert(State::Checked), _ => {} } - if self.node.is_read_only_supported() && self.node.is_read_only_or_disabled() { - state.insert(State::ReadOnly); + if state.is_read_only_supported() && state.is_read_only_or_disabled() { + atspi_state.insert(State::ReadOnly); } else { - state.insert(State::Enabled); - state.insert(State::Sensitive); + atspi_state.insert(State::Enabled | State::Sensitive); } - if self.node.is_focused() { - state.insert(State::Focused); + if self.is_focused() { + atspi_state.insert(State::Focused); } // It is insufficient to compare with g_current_activedescendant due to both @@ -433,32 +429,43 @@ impl<'a> NodeWrapper<'a> { // the activedescendant change. // if (GetActiveDescendantOfCurrentFocused() == atk_object) // state.insert(State::Focused); - state + atspi_state + } + + fn is_root(&self) -> bool { + match self { + Self::Node(node) => node.is_root(), + Self::DetachedNode(node) => node.is_root(), + } } pub fn interfaces(&self) -> InterfaceSet { + let state = self.node_state(); let mut interfaces = InterfaceSet::new(Interface::Accessible); - if self.node.default_action_verb().is_some() { + if state.default_action_verb().is_some() { interfaces.insert(Interface::Action); } - if self.node.bounding_box().is_some() || self.node.is_root() { + if state.raw_bounds().is_some() || self.is_root() { interfaces.insert(Interface::Component); } - if self.node.numeric_value().is_some() { + if self.current_value().is_some() { interfaces.insert(Interface::Value); } interfaces } - pub fn n_actions(&self) -> i32 { - self.node.default_action_verb().map_or(0, |_| 1) + fn n_actions(&self) -> i32 { + match self.node_state().default_action_verb() { + Some(_) => 1, + None => 0, + } } - pub fn get_action_name(&self, index: i32) -> String { + fn get_action_name(&self, index: i32) -> String { if index != 0 { return String::new(); } - String::from(match self.node.default_action_verb() { + String::from(match self.node_state().default_action_verb() { Some(DefaultActionVerb::Click) => "click", Some(DefaultActionVerb::Focus) => "focus", Some(DefaultActionVerb::Check) => "check", @@ -472,6 +479,10 @@ impl<'a> NodeWrapper<'a> { }) } + fn current_value(&self) -> Option { + self.node_state().numeric_value() + } + pub fn enqueue_changes(&self, queue: &mut Vec, old: &NodeWrapper) { self.enqueue_state_changes(queue, old); self.enqueue_property_changes(queue, old); @@ -547,7 +558,7 @@ impl PlatformNode { } } - fn with_state(&self, f: F) -> fdo::Result + fn with_tree_state(&self, f: F) -> fdo::Result where F: FnOnce(&TreeState) -> fdo::Result, { @@ -558,11 +569,11 @@ impl PlatformNode { fn resolve(&self, f: F) -> fdo::Result where - for<'a> F: FnOnce(NodeWrapper<'a>) -> fdo::Result, + for<'a> F: FnOnce(Node<'a>) -> fdo::Result, { - self.with_state(|tree| { - if let Some(node) = tree.node_by_id(self.node_id) { - f(NodeWrapper::new(&node)) + self.with_tree_state(|state| { + if let Some(node) = state.node_by_id(self.node_id) { + f(node) } else { Err(unknown_object(&self.accessible_id())) } @@ -581,48 +592,55 @@ impl PlatformNode { } pub fn name(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.name())) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + Ok(wrapper.name()) + }) } pub fn description(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.description())) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + Ok(wrapper.description()) + }) } pub fn parent(&self) -> fdo::Result { - self.resolve(|resolved| { - Ok(resolved - .parent() - .unwrap_or_else(|| ObjectRef::Managed(ObjectId::root()))) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + Ok(wrapper.parent().unwrap_or_else(|| ObjectRef::Managed(ObjectId::root()))) }) } pub fn child_count(&self) -> fdo::Result { - self.resolve(|resolved| { - i32::try_from(resolved.child_count()) + self.resolve(|node| { + i32::try_from(node.state().child_ids().count()) .map_err(|_| fdo::Error::Failed("Too many children.".into())) }) } - pub fn locale(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.locale())) - } - pub fn accessible_id(&self) -> ObjectId<'static> { self.node_id.into() } pub fn child_at_index(&self, index: usize) -> fdo::Result> { - self.resolve(|resolved| Ok(resolved.node.child_ids().nth(index).map(ObjectRef::from))) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + let child = wrapper.child_ids().nth(index).map(ObjectRef::from); + Ok(child) + }) } pub fn children(&self) -> fdo::Result> { - self.resolve(|resolved| Ok(resolved.node.child_ids().map(ObjectRef::from).collect())) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + Ok(wrapper.child_ids().map(ObjectRef::from).collect()) + }) } pub fn index_in_parent(&self) -> fdo::Result { - self.resolve(|resolved| { - resolved - .node + self.resolve(|node| { + node .parent_and_index() .map_or(Ok(-1), |(_, index)| { i32::try_from(index).map_err(|_| fdo::Error::Failed("Index is too big.".into())) @@ -631,32 +649,48 @@ impl PlatformNode { } pub fn role(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.role())) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + Ok(wrapper.role()) + }) } pub fn state(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.state())) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + Ok(wrapper.state()) + }) } pub fn interfaces(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.interfaces())) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + Ok(wrapper.interfaces()) + }) } pub fn n_actions(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.n_actions())) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + Ok(wrapper.n_actions()) + }) } pub fn get_action_name(&self, index: i32) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.get_action_name(index))) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + Ok(wrapper.get_action_name(index)) + }) } pub fn get_actions(&self) -> fdo::Result> { - self.resolve(|resolved| { - let n_actions = resolved.n_actions() as usize; + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + let n_actions = wrapper.n_actions() as usize; let mut actions = Vec::with_capacity(n_actions); for i in 0..n_actions { actions.push(Action { - localized_name: resolved.get_action_name(i as i32), + localized_name: wrapper.get_action_name(i as i32), description: "".into(), key_binding: "".into(), }); @@ -675,15 +709,15 @@ impl PlatformNode { } pub fn contains(&self, window_bounds: &WindowBounds, x: i32, y: i32, coord_type: CoordType) -> fdo::Result { - self.resolve(|wrapper| { - let bounds = match wrapper.node.bounding_box() { + self.resolve(|node| { + let bounds = match node.bounding_box() { Some(node_bounds) => { - let top_left = window_bounds.top_left(coord_type, wrapper.node.is_root()); + let top_left = window_bounds.top_left(coord_type, node.is_root()); let new_origin = Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0); node_bounds.with_origin(new_origin) } - None if wrapper.node.is_root() => { + None if node.is_root() => { let bounds = window_bounds.outer; match coord_type { CoordType::Screen => bounds, @@ -691,7 +725,7 @@ impl PlatformNode { _ => unimplemented!(), } } - _ => return Err(unknown_object(&wrapper.id())), + _ => return Err(unknown_object(&self.accessible_id())), }; Ok(bounds.contains(Point::new(x.into(), y.into()))) }) @@ -704,26 +738,25 @@ impl PlatformNode { y: i32, coord_type: CoordType, ) -> fdo::Result> { - self.resolve(|wrapper| { - let top_left = window_bounds.top_left(coord_type, wrapper.node.is_root()); + self.resolve(|node| { + let top_left = window_bounds.top_left(coord_type, node.is_root()); let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y); - Ok(wrapper - .node + Ok(node .node_at_point(point, &filter) - .map(|node| ObjectRef::Managed(node.id().into()))) + .map(|node| ObjectRef::Managed(NodeWrapper::Node(&node).id().into()))) }) } pub fn get_extents(&self, window_bounds: &WindowBounds, coord_type: CoordType) -> fdo::Result<(AtspiRect,)> { - self.resolve(|wrapper| { - match wrapper.node.bounding_box() { + self.resolve(|node| { + match node.bounding_box() { Some(node_bounds) => { - let top_left = window_bounds.top_left(coord_type, wrapper.node.is_root()); + let top_left = window_bounds.top_left(coord_type, node.is_root()); let new_origin = Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0); Ok((node_bounds.with_origin(new_origin).into(),)) } - None if wrapper.node.is_root() => { + None if node.is_root() => { let bounds = window_bounds.outer; Ok((match coord_type { CoordType::Screen => bounds.into(), @@ -731,7 +764,7 @@ impl PlatformNode { _ => unimplemented!(), },)) } - _ => Err(unknown_object(&wrapper.id())), + _ => Err(unknown_object(&self.accessible_id())), } }) } @@ -743,19 +776,22 @@ impl PlatformNode { } pub fn minimum_value(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.node.min_numeric_value().unwrap_or(std::f64::MIN))) + self.resolve(|node| Ok(node.state().min_numeric_value().unwrap_or(std::f64::MIN))) } pub fn maximum_value(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.node.max_numeric_value().unwrap_or(std::f64::MAX))) + self.resolve(|node| Ok(node.state().max_numeric_value().unwrap_or(std::f64::MAX))) } pub fn minimum_increment(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.node.numeric_value_step().unwrap_or(0.0))) + self.resolve(|node| Ok(node.state().numeric_value_step().unwrap_or(0.0))) } pub fn current_value(&self) -> fdo::Result { - self.resolve(|resolved| Ok(resolved.node.numeric_value().unwrap_or(0.0))) + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + Ok(wrapper.current_value().unwrap_or(0.0)) + }) } pub fn set_current_value(&self, value: f64) -> fdo::Result<()> { diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index a90273f8a..01fa44bb7 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file). -use accesskit::{kurbo::Rect, ActionHandler, ActionRequest, TreeUpdate}; +use accesskit::{ActionHandler, ActionRequest, TreeUpdate}; use parking_lot::Mutex; use winit::{ event::WindowEvent, @@ -64,14 +64,28 @@ impl Adapter { Self { adapter } } - #[cfg(not(target_os = "linux"))] + #[cfg(all( + not(target_os = "linux"), + not(target_os = "dragonfly"), + not(target_os = "freebsd"), + not(target_os = "netbsd"), + not(target_os = "openbsd") + ))] #[must_use] - pub fn on_event(&self, window: &Window, event: &WindowEvent) -> bool { + pub fn on_event(&self, _window: &Window, _event: &WindowEvent) -> bool { true } - #[cfg(target_os = "linux")] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] #[must_use] pub fn on_event(&self, window: &Window, event: &WindowEvent) -> bool { + use accesskit::kurbo::Rect; + match event { WindowEvent::Moved(outer_position) => { let outer_position: (_, _) = outer_position.cast::().into(); @@ -117,4 +131,4 @@ impl Adapter { pub fn update_if_active(&self, updater: impl FnOnce() -> TreeUpdate) { self.adapter.update_if_active(updater) } -} +} \ No newline at end of file diff --git a/platforms/winit/src/platform_impl/unix.rs b/platforms/winit/src/platform_impl/unix.rs index 1c7b77500..a589178ff 100644 --- a/platforms/winit/src/platform_impl/unix.rs +++ b/platforms/winit/src/platform_impl/unix.rs @@ -13,7 +13,7 @@ pub struct Adapter { impl Adapter { pub fn new( _: &Window, - source: Box TreeUpdate>, + source: impl 'static + FnOnce() -> TreeUpdate, action_handler: Box, ) -> Self { let adapter = UnixAdapter::new( @@ -27,19 +27,19 @@ impl Adapter { } pub fn set_root_window_bounds(&self, outer: Rect, inner: Rect) { - if let Some(adapter) = self.adapter { + if let Some(adapter) = &self.adapter { adapter.set_root_window_bounds(outer, inner); } } pub fn update(&self, update: TreeUpdate) { - if let Some(adapter) = self.adapter { + if let Some(adapter) = &self.adapter { adapter.update(update).raise(); } } pub fn update_if_active(&self, updater: impl FnOnce() -> TreeUpdate) { - if let Some(adapter) = self.adapter { + if let Some(adapter) = &self.adapter { adapter.update(updater()).raise(); } } From c223cb5acf7066a569b86a323324bcf8fdae7e3b Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 21 Dec 2022 22:08:24 +0100 Subject: [PATCH 49/69] Add a README --- platforms/unix/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 platforms/unix/README.md diff --git a/platforms/unix/README.md b/platforms/unix/README.md new file mode 100644 index 000000000..116f59780 --- /dev/null +++ b/platforms/unix/README.md @@ -0,0 +1,3 @@ +# AccessKit Unix adapter + +This is the Unix adapter for [AccessKit](https://accesskit.dev/). It exposes an AccessKit accessibility tree through the AT-SPI protocol. From c83b3c46559717ec18c0d197f7516094217e03d6 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 21 Dec 2022 22:17:00 +0100 Subject: [PATCH 50/69] cargo fmt --- consumer/src/node.rs | 3 +- platforms/unix/src/adapter.rs | 55 ++++----- .../unix/src/atspi/interfaces/accessible.rs | 7 +- .../unix/src/atspi/interfaces/application.rs | 2 +- .../unix/src/atspi/interfaces/component.rs | 6 +- platforms/unix/src/lib.rs | 2 +- platforms/unix/src/node.rs | 89 +++++++------- platforms/unix/src/util.rs | 110 +++++++++--------- platforms/winit/src/lib.rs | 2 +- 9 files changed, 139 insertions(+), 137 deletions(-) diff --git a/consumer/src/node.rs b/consumer/src/node.rs index 876d69532..c371a2011 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -125,7 +125,8 @@ impl<'a> Node<'a> { + FusedIterator> + 'a { let state = self.tree_state; - self.state.child_ids() + self.state + .child_ids() .map(move |id| state.node_by_id(id).unwrap()) } diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index 49b908e9b..831f6b19c 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -3,20 +3,20 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{kurbo::Rect, ActionHandler, NodeId, Role, TreeUpdate}; -use accesskit_consumer::{DetachedNode, Node, Tree, TreeChangeHandler, TreeState}; -use atspi::{Interface, InterfaceSet, State}; use crate::{ atspi::{ interfaces::{ - AccessibleInterface, ActionInterface, ComponentInterface, - ObjectEvent, QueuedEvent, ValueInterface, WindowEvent, + AccessibleInterface, ActionInterface, ComponentInterface, ObjectEvent, QueuedEvent, + ValueInterface, WindowEvent, }, Bus, ObjectId, ACCESSIBLE_PATH_PREFIX, }, node::{filter, NodeWrapper, PlatformNode, PlatformRootNode}, util::{AppContext, WindowBounds}, }; +use accesskit::{kurbo::Rect, ActionHandler, NodeId, Role, TreeUpdate}; +use accesskit_consumer::{DetachedNode, Node, Tree, TreeChangeHandler, TreeState}; +use atspi::{Interface, InterfaceSet, State}; use parking_lot::RwLock; use std::sync::Arc; @@ -42,7 +42,8 @@ impl Adapter { toolkit_name, toolkit_version, ))); - atspi_bus.register_root_node(PlatformRootNode::new(&app_context, &tree)) + atspi_bus + .register_root_node(PlatformRootNode::new(&app_context, &tree)) .ok()?; let adapter = Adapter { atspi_bus, @@ -64,8 +65,7 @@ impl Adapter { objects_to_add.push(reader.root().id()); add_children(reader.root(), &mut objects_to_add); for id in objects_to_add { - let interfaces = NodeWrapper::Node(&reader.node_by_id(id).unwrap()) - .interfaces(); + let interfaces = NodeWrapper::Node(&reader.node_by_id(id).unwrap()).interfaces(); adapter .register_interfaces(&adapter.tree, id, interfaces) .unwrap(); @@ -91,10 +91,8 @@ impl Adapter { )?; } if new_interfaces.contains(Interface::Action) { - self.atspi_bus.register_interface( - &path, - ActionInterface::new(PlatformNode::new(tree, id)), - )?; + self.atspi_bus + .register_interface(&path, ActionInterface::new(PlatformNode::new(tree, id)))?; } if new_interfaces.contains(Interface::Component) { self.atspi_bus.register_interface( @@ -103,10 +101,8 @@ impl Adapter { )?; } if new_interfaces.contains(Interface::Value) { - self.atspi_bus.register_interface( - &path, - ValueInterface::new(PlatformNode::new(tree, id)), - )?; + self.atspi_bus + .register_interface(&path, ValueInterface::new(PlatformNode::new(tree, id)))?; } Ok(true) } @@ -118,16 +114,20 @@ impl Adapter { ) -> zbus::Result { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str()); if old_interfaces.contains(Interface::Accessible) { - self.atspi_bus.unregister_interface::>(&path)?; + self.atspi_bus + .unregister_interface::>(&path)?; } if old_interfaces.contains(Interface::Action) { - self.atspi_bus.unregister_interface::(&path)?; + self.atspi_bus + .unregister_interface::(&path)?; } if old_interfaces.contains(Interface::Component) { - self.atspi_bus.unregister_interface::(&path)?; + self.atspi_bus + .unregister_interface::(&path)?; } if old_interfaces.contains(Interface::Value) { - self.atspi_bus.unregister_interface::(&path)?; + self.atspi_bus + .unregister_interface::(&path)?; } Ok(true) } @@ -168,15 +168,11 @@ impl Adapter { fn focus_moved(&mut self, old_node: Option<&DetachedNode>, new_node: Option<&Node>) { if let Some(root_window) = root_window(&self.tree.read()) { if old_node.is_none() && new_node.is_some() { - self.adapter.window_activated( - &NodeWrapper::Node(&root_window), - &mut self.queue, - ); + self.adapter + .window_activated(&NodeWrapper::Node(&root_window), &mut self.queue); } else if old_node.is_some() && new_node.is_none() { - self.adapter.window_deactivated( - &NodeWrapper::Node(&root_window), - &mut self.queue, - ); + self.adapter + .window_deactivated(&NodeWrapper::Node(&root_window), &mut self.queue); } } if let Some(node) = new_node.map(NodeWrapper::Node) { @@ -260,8 +256,7 @@ impl QueuedEvents { pub fn raise(&self) { for event in &self.queue { let _ = match &event { - QueuedEvent::Object { target, event } => - self.bus.emit_object_event(target, event), + QueuedEvent::Object { target, event } => self.bus.emit_object_event(target, event), QueuedEvent::Window { target, name, diff --git a/platforms/unix/src/atspi/interfaces/accessible.rs b/platforms/unix/src/atspi/interfaces/accessible.rs index ea136eb0c..e4bf7c0f1 100644 --- a/platforms/unix/src/atspi/interfaces/accessible.rs +++ b/platforms/unix/src/atspi/interfaces/accessible.rs @@ -3,11 +3,11 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use atspi::{accessible::Role, Interface, InterfaceSet, StateSet}; use crate::{ atspi::{ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress}, - PlatformNode, PlatformRootNode, unknown_object, + unknown_object, PlatformNode, PlatformRootNode, }; +use atspi::{accessible::Role, Interface, InterfaceSet, StateSet}; use std::convert::TryInto; use zbus::{fdo, names::OwnedUniqueName, MessageHeader}; @@ -55,8 +55,7 @@ impl AccessibleInterface { #[dbus_interface(property)] fn accessible_id(&self) -> ObjectId { - self.node - .accessible_id() + self.node.accessible_id() } fn get_child_at_index( diff --git a/platforms/unix/src/atspi/interfaces/application.rs b/platforms/unix/src/atspi/interfaces/application.rs index 8f08ccaae..ccd9a9790 100644 --- a/platforms/unix/src/atspi/interfaces/application.rs +++ b/platforms/unix/src/atspi/interfaces/application.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::{atspi::ObjectId, PlatformRootNode, unknown_object}; +use crate::{atspi::ObjectId, unknown_object, PlatformRootNode}; use zbus::fdo; pub(crate) struct ApplicationInterface(pub PlatformRootNode); diff --git a/platforms/unix/src/atspi/interfaces/component.rs b/platforms/unix/src/atspi/interfaces/component.rs index 392a447cb..4051d4082 100644 --- a/platforms/unix/src/atspi/interfaces/component.rs +++ b/platforms/unix/src/atspi/interfaces/component.rs @@ -5,8 +5,9 @@ use crate::{ atspi::{object_address::OwnedObjectAddress, Rect}, + unknown_object, util::WindowBounds, - PlatformNode, unknown_object, + PlatformNode, }; use atspi::CoordType; use parking_lot::RwLock; @@ -53,7 +54,8 @@ impl ComponentInterface { let window_bounds = self.upgrade_bounds()?; let accessible = super::object_address( hdr.destination()?, - self.node.get_accessible_at_point(&window_bounds.read(), x, y, coord_type)?, + self.node + .get_accessible_at_point(&window_bounds.read(), x, y, coord_type)?, ); accessible } diff --git a/platforms/unix/src/lib.rs b/platforms/unix/src/lib.rs index b2fe64e91..63cb61bf2 100644 --- a/platforms/unix/src/lib.rs +++ b/platforms/unix/src/lib.rs @@ -12,4 +12,4 @@ mod node; mod util; pub use adapter::Adapter; -pub(crate) use node::{PlatformNode, PlatformRootNode, unknown_object}; +pub(crate) use node::{unknown_object, PlatformNode, PlatformRootNode}; diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index e0ae6b5ea..8ca784dfd 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -8,20 +8,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE.chromium file. -use accesskit::{kurbo::Point, CheckedState, DefaultActionVerb, NodeId, Role}; -use accesskit_consumer::{ - DetachedNode, FilterResult, Node, NodeState, Tree, TreeState, -}; -use atspi::{accessible::Role as AtspiRole, CoordType, Interface, InterfaceSet, State, StateSet}; use crate::{ atspi::{ interfaces::{Action, ObjectEvent, Property, QueuedEvent}, - ACCESSIBLE_PATH_PREFIX, ObjectId, ObjectRef, Rect as AtspiRect, + ObjectId, ObjectRef, Rect as AtspiRect, ACCESSIBLE_PATH_PREFIX, }, util::{AppContext, WindowBounds}, }; +use accesskit::{kurbo::Point, CheckedState, DefaultActionVerb, NodeId, Role}; +use accesskit_consumer::{DetachedNode, FilterResult, Node, NodeState, Tree, TreeState}; +use atspi::{accessible::Role as AtspiRole, CoordType, Interface, InterfaceSet, State, StateSet}; use parking_lot::RwLock; -use std::{iter::FusedIterator, sync::{Arc, Weak}}; +use std::{ + iter::FusedIterator, + sync::{Arc, Weak}, +}; use zbus::fdo; fn filter_common(node: &NodeState) -> FilterResult { @@ -70,7 +71,8 @@ impl<'a> NodeWrapper<'a> { match self { Self::Node(node) => node.name(), Self::DetachedNode(node) => node.name(), - }.unwrap_or_default() + } + .unwrap_or_default() } pub fn description(&self) -> String { @@ -86,7 +88,7 @@ impl<'a> NodeWrapper<'a> { } pub fn child_ids( - &self + &self, ) -> impl DoubleEndedIterator + ExactSizeIterator + FusedIterator @@ -403,12 +405,11 @@ impl<'a> NodeWrapper<'a> { // Checked state match state.checked_state() { - Some(CheckedState::Mixed) => - atspi_state.insert(State::Indeterminate), - Some(CheckedState::True) if atspi_role == AtspiRole::ToggleButton => - atspi_state.insert(State::Pressed), - Some(CheckedState::True) => - atspi_state.insert(State::Checked), + Some(CheckedState::Mixed) => atspi_state.insert(State::Indeterminate), + Some(CheckedState::True) if atspi_role == AtspiRole::ToggleButton => { + atspi_state.insert(State::Pressed) + } + Some(CheckedState::True) => atspi_state.insert(State::Checked), _ => {} } @@ -608,7 +609,9 @@ impl PlatformNode { pub fn parent(&self) -> fdo::Result { self.resolve(|node| { let wrapper = NodeWrapper::Node(&node); - Ok(wrapper.parent().unwrap_or_else(|| ObjectRef::Managed(ObjectId::root()))) + Ok(wrapper + .parent() + .unwrap_or_else(|| ObjectRef::Managed(ObjectId::root()))) }) } @@ -640,11 +643,9 @@ impl PlatformNode { pub fn index_in_parent(&self) -> fdo::Result { self.resolve(|node| { - node - .parent_and_index() - .map_or(Ok(-1), |(_, index)| { - i32::try_from(index).map_err(|_| fdo::Error::Failed("Index is too big.".into())) - }) + node.parent_and_index().map_or(Ok(-1), |(_, index)| { + i32::try_from(index).map_err(|_| fdo::Error::Failed("Index is too big.".into())) + }) }) } @@ -708,7 +709,13 @@ impl PlatformNode { Ok(true) } - pub fn contains(&self, window_bounds: &WindowBounds, x: i32, y: i32, coord_type: CoordType) -> fdo::Result { + pub fn contains( + &self, + window_bounds: &WindowBounds, + x: i32, + y: i32, + coord_type: CoordType, + ) -> fdo::Result { self.resolve(|node| { let bounds = match node.bounding_box() { Some(node_bounds) => { @@ -747,25 +754,27 @@ impl PlatformNode { }) } - pub fn get_extents(&self, window_bounds: &WindowBounds, coord_type: CoordType) -> fdo::Result<(AtspiRect,)> { - self.resolve(|node| { - match node.bounding_box() { - Some(node_bounds) => { - let top_left = window_bounds.top_left(coord_type, node.is_root()); - let new_origin = - Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0); - Ok((node_bounds.with_origin(new_origin).into(),)) - } - None if node.is_root() => { - let bounds = window_bounds.outer; - Ok((match coord_type { - CoordType::Screen => bounds.into(), - CoordType::Window => bounds.with_origin(Point::ZERO).into(), - _ => unimplemented!(), - },)) - } - _ => Err(unknown_object(&self.accessible_id())), + pub fn get_extents( + &self, + window_bounds: &WindowBounds, + coord_type: CoordType, + ) -> fdo::Result<(AtspiRect,)> { + self.resolve(|node| match node.bounding_box() { + Some(node_bounds) => { + let top_left = window_bounds.top_left(coord_type, node.is_root()); + let new_origin = + Point::new(top_left.x + node_bounds.x0, top_left.y + node_bounds.y0); + Ok((node_bounds.with_origin(new_origin).into(),)) + } + None if node.is_root() => { + let bounds = window_bounds.outer; + Ok((match coord_type { + CoordType::Screen => bounds.into(), + CoordType::Window => bounds.with_origin(Point::ZERO).into(), + _ => unimplemented!(), + },)) } + _ => Err(unknown_object(&self.accessible_id())), }) } diff --git a/platforms/unix/src/util.rs b/platforms/unix/src/util.rs index 990eff8c4..35c0fed5b 100644 --- a/platforms/unix/src/util.rs +++ b/platforms/unix/src/util.rs @@ -1,57 +1,53 @@ -// Copyright 2022 The AccessKit Authors. All rights reserved. -// Licensed under the Apache License, Version 2.0 (found in -// the LICENSE-APACHE file) or the MIT license (found in -// the LICENSE-MIT file), at your option. - -use accesskit::kurbo::{Point, Rect}; -use atspi::CoordType; -use crate::atspi::OwnedObjectAddress; - -pub(crate) struct AppContext { - pub(crate) name: String, - pub(crate) toolkit_name: String, - pub(crate) toolkit_version: String, - pub(crate) id: Option, - pub(crate) desktop_address: Option, -} - -impl AppContext { - pub(crate) fn new( - name: String, - toolkit_name: String, - toolkit_version: String - ) -> Self { - Self { - name, - toolkit_name, - toolkit_version, - id: None, - desktop_address: None, - } - } -} - -#[derive(Default)] -pub(crate) struct WindowBounds { - pub(crate) outer: Rect, - pub(crate) inner: Rect, -} - -impl WindowBounds { - pub(crate) fn top_left(&self, coord_type: CoordType, is_root: bool) -> Point { - match coord_type { - CoordType::Screen if is_root => self.outer.origin(), - CoordType::Screen => self.inner.origin(), - CoordType::Window if is_root => Point::ZERO, - CoordType::Window => { - let outer_position = self.outer.origin(); - let inner_position = self.inner.origin(); - Point::new( - inner_position.x - outer_position.x, - inner_position.y - outer_position.y, - ) - } - _ => unimplemented!(), - } - } -} +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use crate::atspi::OwnedObjectAddress; +use accesskit::kurbo::{Point, Rect}; +use atspi::CoordType; + +pub(crate) struct AppContext { + pub(crate) name: String, + pub(crate) toolkit_name: String, + pub(crate) toolkit_version: String, + pub(crate) id: Option, + pub(crate) desktop_address: Option, +} + +impl AppContext { + pub(crate) fn new(name: String, toolkit_name: String, toolkit_version: String) -> Self { + Self { + name, + toolkit_name, + toolkit_version, + id: None, + desktop_address: None, + } + } +} + +#[derive(Default)] +pub(crate) struct WindowBounds { + pub(crate) outer: Rect, + pub(crate) inner: Rect, +} + +impl WindowBounds { + pub(crate) fn top_left(&self, coord_type: CoordType, is_root: bool) -> Point { + match coord_type { + CoordType::Screen if is_root => self.outer.origin(), + CoordType::Screen => self.inner.origin(), + CoordType::Window if is_root => Point::ZERO, + CoordType::Window => { + let outer_position = self.outer.origin(); + let inner_position = self.inner.origin(); + Point::new( + inner_position.x - outer_position.x, + inner_position.y - outer_position.y, + ) + } + _ => unimplemented!(), + } + } +} diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index 01fa44bb7..2fdcb80b7 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -131,4 +131,4 @@ impl Adapter { pub fn update_if_active(&self, updater: impl FnOnce() -> TreeUpdate) { self.adapter.update_if_active(updater) } -} \ No newline at end of file +} From e2f23502189a281d3084af8c9fcafaa5beeefd7f Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 21 Dec 2022 22:23:58 +0100 Subject: [PATCH 51/69] Please clippy --- platforms/unix/src/atspi/interfaces/component.rs | 1 + platforms/unix/src/node.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/platforms/unix/src/atspi/interfaces/component.rs b/platforms/unix/src/atspi/interfaces/component.rs index 4051d4082..007b4199c 100644 --- a/platforms/unix/src/atspi/interfaces/component.rs +++ b/platforms/unix/src/atspi/interfaces/component.rs @@ -57,6 +57,7 @@ impl ComponentInterface { self.node .get_accessible_at_point(&window_bounds.read(), x, y, coord_type)?, ); + drop(window_bounds); accessible } diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 8ca784dfd..357c6da7c 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -750,7 +750,7 @@ impl PlatformNode { let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y); Ok(node .node_at_point(point, &filter) - .map(|node| ObjectRef::Managed(NodeWrapper::Node(&node).id().into()))) + .map(|node| ObjectRef::Managed(NodeWrapper::Node(&node).id()))) }) } From 5528004b7085d35b6b5dbf9589c44cacfb3f089f Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 21 Dec 2022 23:45:12 +0100 Subject: [PATCH 52/69] Fix winit example message about how to launch Orca --- platforms/winit/examples/simple.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index ec53b9466..d5070dd16 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -135,10 +135,16 @@ fn main() { println!("This example has no visible GUI, and a keyboard interface:"); println!("- [Tab] switches focus between two logical buttons."); println!("- [Space] 'presses' the button, adding static text in a live region announcing that it was pressed."); - #[cfg(target_os = "linux")] - println!("Enable Orca with [Super]+[Alt]+[S]."); #[cfg(target_os = "windows")] println!("Enable Narrator with [Win]+[Ctrl]+[Enter] (or [Win]+[Enter] on older versions of Windows)."); + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + println!("Enable Orca with [Super]+[Alt]+[S]."); let event_loop = EventLoopBuilder::with_user_event().build(); From 36c442c2eef10fd67c5c6a8711da862d42804886 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 21 Dec 2022 23:59:53 +0100 Subject: [PATCH 53/69] Remove commented out code and traces --- platforms/unix/src/atspi/bus.rs | 1 - platforms/unix/src/node.rs | 57 --------------------------------- 2 files changed, 58 deletions(-) diff --git a/platforms/unix/src/atspi/bus.rs b/platforms/unix/src/atspi/bus.rs index 94b45daee..b50adfaf3 100644 --- a/platforms/unix/src/atspi/bus.rs +++ b/platforms/unix/src/atspi/bus.rs @@ -51,7 +51,6 @@ impl Bus { } pub fn register_root_node(&mut self, node: PlatformRootNode) -> Result { - println!("Registering on {:?}", self.unique_name()); let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, ObjectId::root().as_str()); let registered = self .conn diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 357c6da7c..24b37262b 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -318,27 +318,10 @@ impl<'a> NodeWrapper<'a> { if state.role() == Role::Window && state.parent_id().is_none() { atspi_state.insert(State::Active); } - //if let Some(expanded) = data.expanded { - // state.insert(State::Expandable); - // if expanded { - // state.insert(State::Expanded); - // } - //} - //if data.default { - // state.insert(State::IsDefault); - //} - //if data.editable && !data.read_only { - // state.insert(State::Editable); - //} // TODO: Focus and selection. if state.is_focusable() { atspi_state.insert(State::Focusable); } - //match data.orientation { - // Some(Orientation::Horizontal) => state.insert(State::Horizontal), - // Some(Orientation::Vertical) => state.insert(State::Vertical), - // _ => {} - //} let filter_result = match self { Self::Node(node) => filter(node), Self::DetachedNode(node) => filter_detached(node), @@ -346,34 +329,9 @@ impl<'a> NodeWrapper<'a> { if filter_result == FilterResult::Include { atspi_state.insert(State::Visible | State::Showing); } - //if data.multiselectable { - // state.insert(State::Multiselectable); - //} - //if data.required { - // state.insert(State::Required); - //} - //if data.visited { - // state.insert(State::Visited); - //} - //if let Some(InvalidState::True | InvalidState::Other(_)) = data.invalid_state { - // state.insert(State::InvalidEntry); - //} - //match data.aria_current { - // None | Some(AriaCurrent::False) => {} - // _ => state.insert(State::Active), - //} if atspi_role != AtspiRole::ToggleButton && state.checked_state().is_some() { atspi_state.insert(State::Checkable); } - //if data.has_popup.is_some() { - // state.insert(State::HasPopup); - //} - //if data.busy { - // state.insert(State::Busy); - //} - //if data.modal { - // state.insert(State::Modal); - //} if let Some(selected) = state.is_selected() { if !state.is_disabled() { atspi_state.insert(State::Selectable); @@ -395,14 +353,6 @@ impl<'a> NodeWrapper<'a> { atspi_state.insert(State::Indeterminate); } - //let has_suggestion = data - // .auto_complete - // .as_ref() - // .map_or(false, |a| !a.as_ref().is_empty()); - //if has_suggestion || data.autofill_available { - // state.insert(State::SupportsAutocompletion); - //} - // Checked state match state.checked_state() { Some(CheckedState::Mixed) => atspi_state.insert(State::Indeterminate), @@ -423,13 +373,6 @@ impl<'a> NodeWrapper<'a> { atspi_state.insert(State::Focused); } - // It is insufficient to compare with g_current_activedescendant due to both - // timing and event ordering for objects which implement AtkSelection and also - // have an active descendant. For instance, if we check the state set of a - // selectable child, it will only have ATK_STATE_FOCUSED if we've processed - // the activedescendant change. - // if (GetActiveDescendantOfCurrentFocused() == atk_object) - // state.insert(State::Focused); atspi_state } From cb4ef83191321c9b795f16fa066a85764c6585f9 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Thu, 22 Dec 2022 17:43:09 +0100 Subject: [PATCH 54/69] Fix licensing and copyright notices --- platforms/unix/Cargo.toml | 2 +- platforms/unix/src/atspi/interfaces/action.rs | 5 +++++ platforms/unix/src/atspi/object_ref.rs | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/platforms/unix/Cargo.toml b/platforms/unix/Cargo.toml index 23e24b782..1ce366f3b 100644 --- a/platforms/unix/Cargo.toml +++ b/platforms/unix/Cargo.toml @@ -2,7 +2,7 @@ name = "accesskit_unix" version = "0.1.0" authors = ["Arnold Loubriat "] -license = "Apache-2.0" +license = "MIT/Apache-2.0" description = "AccessKit UI accessibility infrastructure: Linux adapter" categories = ["gui"] keywords = ["gui", "ui", "accessibility"] diff --git a/platforms/unix/src/atspi/interfaces/action.rs b/platforms/unix/src/atspi/interfaces/action.rs index 1e969795e..9c8bbbb36 100644 --- a/platforms/unix/src/atspi/interfaces/action.rs +++ b/platforms/unix/src/atspi/interfaces/action.rs @@ -1,3 +1,8 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + use crate::PlatformNode; use serde::{Deserialize, Serialize}; use zbus::{dbus_interface, fdo, zvariant::Type}; diff --git a/platforms/unix/src/atspi/object_ref.rs b/platforms/unix/src/atspi/object_ref.rs index 8fbb9228f..ecf16faf6 100644 --- a/platforms/unix/src/atspi/object_ref.rs +++ b/platforms/unix/src/atspi/object_ref.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The AccessKit Authors. All rights reserved. +// Copyright 2022 The AccessKit Authors. All rights reserved. // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. From 806e1aa7ff99392ba12c572cd3fa86d12d78a310 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Thu, 22 Dec 2022 17:45:29 +0100 Subject: [PATCH 55/69] Tell release-please about the Unix adapter --- release-please-config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/release-please-config.json b/release-please-config.json index a345b03a5..2a636a3e6 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -7,6 +7,7 @@ "common": {}, "consumer": {}, "platforms/macos": {}, + "platforms/unix": {}, "platforms/windows": {}, "platforms/winit": {} } From 30ba2cad624704a427a15c298bbba0a3e0f6899c Mon Sep 17 00:00:00 2001 From: DataTriny Date: Thu, 22 Dec 2022 18:57:37 +0100 Subject: [PATCH 56/69] Drop unused AT-SPI types and methods --- platforms/unix/src/atspi/bus.rs | 11 ++-- .../unix/src/atspi/interfaces/accessible.rs | 15 +++--- .../unix/src/atspi/interfaces/component.rs | 2 +- platforms/unix/src/atspi/object_address.rs | 52 +++---------------- platforms/unix/src/atspi/object_id.rs | 18 ++----- 5 files changed, 22 insertions(+), 76 deletions(-) diff --git a/platforms/unix/src/atspi/bus.rs b/platforms/unix/src/atspi/bus.rs index b50adfaf3..05be737fd 100644 --- a/platforms/unix/src/atspi/bus.rs +++ b/platforms/unix/src/atspi/bus.rs @@ -108,18 +108,17 @@ impl Bus { Property::Name(value) => Str::from(value).into(), Property::Description(value) => Str::from(value).into(), Property::Parent(Some(ObjectRef::Managed(parent))) => { - OwnedObjectAddress::from(ObjectAddress::accessible( - self.unique_name().into(), - parent, - )) + OwnedObjectAddress::accessible( + self.unique_name().clone(), + parent.clone(), + ) .into() } Property::Parent(Some(ObjectRef::Unmanaged(parent))) => { parent.clone().into() } Property::Parent(None) => { - OwnedObjectAddress::from(ObjectAddress::root(self.unique_name().into())) - .into() + OwnedObjectAddress::root(self.unique_name().clone()).into() } Property::Role(value) => Value::U32(*value as u32), }, diff --git a/platforms/unix/src/atspi/interfaces/accessible.rs b/platforms/unix/src/atspi/interfaces/accessible.rs index e4bf7c0f1..2d96ff091 100644 --- a/platforms/unix/src/atspi/interfaces/accessible.rs +++ b/platforms/unix/src/atspi/interfaces/accessible.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use crate::{ - atspi::{ObjectAddress, ObjectId, ObjectRef, OwnedObjectAddress}, + atspi::{ObjectId, ObjectRef, OwnedObjectAddress}, unknown_object, PlatformNode, PlatformRootNode, }; use atspi::{accessible::Role, Interface, InterfaceSet, StateSet}; @@ -75,9 +75,7 @@ impl AccessibleInterface { .children()? .into_iter() .map(|child| match child { - ObjectRef::Managed(id) => { - ObjectAddress::accessible(self.bus_name.as_ref(), &id).into() - } + ObjectRef::Managed(id) => OwnedObjectAddress::accessible(self.bus_name.clone(), id), ObjectRef::Unmanaged(address) => address, }) .collect()) @@ -174,11 +172,10 @@ impl AccessibleInterface { .tree .upgrade() .map(|tree| { - vec![ObjectAddress::accessible( - self.bus_name.as_ref(), - &tree.read().root().id().into(), - ) - .into()] + vec![OwnedObjectAddress::accessible( + self.bus_name.clone(), + tree.read().root().id().into(), + )] }) .ok_or_else(|| unknown_object(&ObjectId::root())) } diff --git a/platforms/unix/src/atspi/interfaces/component.rs b/platforms/unix/src/atspi/interfaces/component.rs index 007b4199c..047f1c4e5 100644 --- a/platforms/unix/src/atspi/interfaces/component.rs +++ b/platforms/unix/src/atspi/interfaces/component.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use crate::{ - atspi::{object_address::OwnedObjectAddress, Rect}, + atspi::{OwnedObjectAddress, Rect}, unknown_object, util::WindowBounds, PlatformNode, diff --git a/platforms/unix/src/atspi/object_address.rs b/platforms/unix/src/atspi/object_address.rs index 5f6171bdd..2e498909d 100644 --- a/platforms/unix/src/atspi/object_address.rs +++ b/platforms/unix/src/atspi/object_address.rs @@ -14,50 +14,14 @@ pub(crate) const ACCESSIBLE_PATH_PREFIX: &str = "/org/a11y/atspi/accessible/"; pub(crate) const NULL_PATH: &str = "/org/a11y/atspi/null"; pub(crate) const ROOT_PATH: &str = "/org/a11y/atspi/accessible/root"; -#[derive(Clone, Debug, Serialize, Deserialize, Type, Value)] -pub struct ObjectAddress<'a> { - #[serde(borrow)] - bus_name: UniqueName<'a>, - #[serde(borrow)] - path: ObjectPath<'a>, -} - -impl<'a> ObjectAddress<'a> { - pub fn new(bus_name: UniqueName<'a>, path: ObjectPath<'a>) -> ObjectAddress<'a> { - Self { bus_name, path } - } - - pub fn accessible(bus_name: UniqueName<'a>, id: &ObjectId) -> ObjectAddress<'a> { - Self { - bus_name, - path: ObjectPath::from_string_unchecked(format!( - "{}{}", - ACCESSIBLE_PATH_PREFIX, - id.as_str() - )), - } - } - - pub fn root(bus_name: UniqueName<'a>) -> ObjectAddress<'a> { - Self { - bus_name, - path: ObjectPath::from_str_unchecked(ROOT_PATH), - } - } -} - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, OwnedValue, Type, Value)] -pub struct OwnedObjectAddress { +pub(crate) struct OwnedObjectAddress { bus_name: OwnedUniqueName, path: OwnedObjectPath, } impl OwnedObjectAddress { - pub fn new(bus_name: OwnedUniqueName, path: OwnedObjectPath) -> Self { - Self { bus_name, path } - } - - pub fn accessible(bus_name: OwnedUniqueName, id: ObjectId) -> Self { + pub(crate) fn accessible(bus_name: OwnedUniqueName, id: ObjectId) -> Self { Self { bus_name, path: ObjectPath::from_string_unchecked(format!( @@ -69,19 +33,17 @@ impl OwnedObjectAddress { } } - pub fn null(bus_name: OwnedUniqueName) -> Self { + pub(crate) fn null(bus_name: OwnedUniqueName) -> Self { Self { bus_name, path: ObjectPath::from_str_unchecked(NULL_PATH).into(), } } -} -impl From> for OwnedObjectAddress { - fn from(value: ObjectAddress) -> OwnedObjectAddress { - OwnedObjectAddress { - bus_name: value.bus_name.into(), - path: value.path.into(), + pub(crate) fn root(bus_name: OwnedUniqueName) -> Self { + Self { + bus_name, + path: ObjectPath::from_str_unchecked(ROOT_PATH).into(), } } } diff --git a/platforms/unix/src/atspi/object_id.rs b/platforms/unix/src/atspi/object_id.rs index 6db76576b..f85b819de 100644 --- a/platforms/unix/src/atspi/object_id.rs +++ b/platforms/unix/src/atspi/object_id.rs @@ -8,28 +8,16 @@ use serde::{Deserialize, Serialize}; use zbus::zvariant::{Str, Type, Value}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Type, Value)] -pub struct ObjectId<'a>(#[serde(borrow)] Str<'a>); +pub(crate) struct ObjectId<'a>(#[serde(borrow)] Str<'a>); impl<'a> ObjectId<'a> { - pub unsafe fn from_str_unchecked(id: &'a str) -> ObjectId<'a> { - Self(Str::from(id)) - } - - pub fn root() -> ObjectId<'static> { + pub(crate) fn root() -> ObjectId<'static> { ObjectId(Str::from("root")) } - pub fn as_bytes(&self) -> &[u8] { - self.0.as_bytes() - } - - pub fn as_str(&self) -> &str { + pub(crate) fn as_str(&self) -> &str { self.0.as_str() } - - pub fn to_owned(&self) -> ObjectId<'static> { - ObjectId(self.0.to_owned()) - } } impl From for ObjectId<'static> { From c762bbba4e4dc097a2936157f261b69a4710b4cb Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sat, 24 Dec 2022 14:35:06 +0100 Subject: [PATCH 57/69] Apply inverse transform before hit-testing --- platforms/unix/src/node.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 24b37262b..47a86aef8 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -691,6 +691,7 @@ impl PlatformNode { self.resolve(|node| { let top_left = window_bounds.top_left(coord_type, node.is_root()); let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y); + let point = node.transform().inverse() * point; Ok(node .node_at_point(point, &filter) .map(|node| ObjectRef::Managed(NodeWrapper::Node(&node).id()))) From 480932faeaca9643fc7bff39d017123b4927ca90 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sat, 24 Dec 2022 15:18:50 +0100 Subject: [PATCH 58/69] Improve ComponentInterface::get_accessible_at_point --- platforms/unix/src/atspi/interfaces/component.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/platforms/unix/src/atspi/interfaces/component.rs b/platforms/unix/src/atspi/interfaces/component.rs index 047f1c4e5..50762b8a9 100644 --- a/platforms/unix/src/atspi/interfaces/component.rs +++ b/platforms/unix/src/atspi/interfaces/component.rs @@ -52,13 +52,10 @@ impl ComponentInterface { coord_type: CoordType, ) -> fdo::Result<(OwnedObjectAddress,)> { let window_bounds = self.upgrade_bounds()?; - let accessible = super::object_address( - hdr.destination()?, + let accessible = self.node - .get_accessible_at_point(&window_bounds.read(), x, y, coord_type)?, - ); - drop(window_bounds); - accessible + .get_accessible_at_point(&window_bounds.read(), x, y, coord_type)?; + super::object_address(hdr.destination()?, accessible) } fn get_extents(&self, coord_type: CoordType) -> fdo::Result<(Rect,)> { From 010512293853170c10f814e020b32d27eff90a33 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sat, 24 Dec 2022 17:17:10 +0100 Subject: [PATCH 59/69] Only expose filtered parent and children --- platforms/unix/src/node.rs | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 47a86aef8..cab746b9f 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -79,8 +79,17 @@ impl<'a> NodeWrapper<'a> { String::new() } - pub fn parent(&self) -> Option { - self.node_state().parent_id().map(Into::into) + pub fn parent_id(&self) -> Option { + self.node_state().parent_id() + } + + pub fn filtered_parent(&self) -> Option { + match self { + Self::Node(node) => node + .filtered_parent(&filter) + .map(|parent| parent.id().into()), + _ => unreachable!(), + } } pub fn id(&self) -> ObjectId<'static> { @@ -459,11 +468,11 @@ impl<'a> NodeWrapper<'a> { event: ObjectEvent::PropertyChanged(Property::Description(description)), }); } - let parent = self.parent(); - if parent != old.parent() { + let parent_id = self.parent_id(); + if parent_id != old.parent_id() { queue.push(QueuedEvent::Object { target: self.id(), - event: ObjectEvent::PropertyChanged(Property::Parent(parent)), + event: ObjectEvent::PropertyChanged(Property::Parent(self.filtered_parent())), }); } let role = self.role(); @@ -551,9 +560,9 @@ impl PlatformNode { pub fn parent(&self) -> fdo::Result { self.resolve(|node| { - let wrapper = NodeWrapper::Node(&node); - Ok(wrapper - .parent() + Ok(node + .filtered_parent(&filter) + .map(|parent| parent.id().into()) .unwrap_or_else(|| ObjectRef::Managed(ObjectId::root()))) }) } @@ -579,16 +588,17 @@ impl PlatformNode { pub fn children(&self) -> fdo::Result> { self.resolve(|node| { - let wrapper = NodeWrapper::Node(&node); - Ok(wrapper.child_ids().map(ObjectRef::from).collect()) + Ok(node + .filtered_children(&filter) + .map(|child| child.id().into()) + .collect()) }) } pub fn index_in_parent(&self) -> fdo::Result { self.resolve(|node| { - node.parent_and_index().map_or(Ok(-1), |(_, index)| { - i32::try_from(index).map_err(|_| fdo::Error::Failed("Index is too big.".into())) - }) + i32::try_from(node.preceding_filtered_siblings(&filter).count()) + .map_err(|_| fdo::Error::Failed("Index is too big.".into())) }) } From 0a299940fbf28cd6d1846859a3259fb791908583 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sat, 24 Dec 2022 17:31:06 +0100 Subject: [PATCH 60/69] Raise event on value property change --- platforms/unix/src/atspi/bus.rs | 2 ++ platforms/unix/src/atspi/interfaces/events.rs | 2 ++ platforms/unix/src/node.rs | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/platforms/unix/src/atspi/bus.rs b/platforms/unix/src/atspi/bus.rs index 05be737fd..390c0794e 100644 --- a/platforms/unix/src/atspi/bus.rs +++ b/platforms/unix/src/atspi/bus.rs @@ -101,6 +101,7 @@ impl Bus { Property::Description(_) => "accessible-description", Property::Parent(_) => "accessible-parent", Property::Role(_) => "accessible-role", + Property::Value(_) => "accessible-value", }, detail1: 0, detail2: 0, @@ -121,6 +122,7 @@ impl Bus { OwnedObjectAddress::root(self.unique_name().clone()).into() } Property::Role(value) => Value::U32(*value as u32), + Property::Value(value) => Value::F64(*value), }, properties, }, diff --git a/platforms/unix/src/atspi/interfaces/events.rs b/platforms/unix/src/atspi/interfaces/events.rs index f5a89f3b7..d57e5dbf7 100644 --- a/platforms/unix/src/atspi/interfaces/events.rs +++ b/platforms/unix/src/atspi/interfaces/events.rs @@ -29,6 +29,8 @@ pub(crate) enum Property { Parent(Option), #[strum(serialize = "accessible-role")] Role(Role), + #[strum(serialize = "accessible-value")] + Value(f64), } #[derive(AsRefStr)] diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index cab746b9f..38846a90f 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -482,6 +482,14 @@ impl<'a> NodeWrapper<'a> { event: ObjectEvent::PropertyChanged(Property::Role(role)), }); } + if let Some(value) = self.current_value() { + if Some(value) != old.current_value() { + queue.push(QueuedEvent::Object { + target: self.id(), + event: ObjectEvent::PropertyChanged(Property::Value(value)), + }); + } + } } } From c2253ffb6b72634f6fdbd3250ae11da92ee7ce7a Mon Sep 17 00:00:00 2001 From: DataTriny Date: Sat, 24 Dec 2022 17:44:09 +0100 Subject: [PATCH 61/69] Drop dependency on strum --- platforms/unix/Cargo.toml | 1 - platforms/unix/src/atspi/bus.rs | 23 +++++++++++-------- platforms/unix/src/atspi/interfaces/events.rs | 12 ---------- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/platforms/unix/Cargo.toml b/platforms/unix/Cargo.toml index 1ce366f3b..3fc4f05fa 100644 --- a/platforms/unix/Cargo.toml +++ b/platforms/unix/Cargo.toml @@ -16,5 +16,4 @@ accesskit_consumer = { version = "0.11.0", path = "../../consumer" } atspi = "0.3.10" parking_lot = "0.12.1" serde = "1.0" -strum = { version = "0.23.0", features = ["derive"] } zbus = "3.6" diff --git a/platforms/unix/src/atspi/bus.rs b/platforms/unix/src/atspi/bus.rs index 390c0794e..cceb3dd25 100644 --- a/platforms/unix/src/atspi/bus.rs +++ b/platforms/unix/src/atspi/bus.rs @@ -3,15 +3,13 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{interfaces::*, object_address::*, ObjectId, ObjectRef}; -use crate::PlatformRootNode; +use crate::{ + atspi::{interfaces::*, object_address::*, ObjectId, ObjectRef}, + PlatformRootNode, +}; use atspi::{bus::BusProxyBlocking, socket::SocketProxyBlocking, EventBody}; use serde::Serialize; -use std::{ - collections::HashMap, - convert::{AsRef, TryInto}, - env::var, -}; +use std::{collections::HashMap, env::var}; use zbus::{ blocking::{Connection, ConnectionBuilder}, names::{BusName, InterfaceName, MemberName, OwnedUniqueName}, @@ -76,7 +74,10 @@ impl Bus { pub fn emit_object_event(&self, target: &ObjectId, event: &ObjectEvent) -> Result<()> { let interface = "org.a11y.atspi.Event.Object"; - let signal = event.as_ref(); + let signal = match event { + ObjectEvent::StateChanged(_, _) => "StateChanged", + ObjectEvent::PropertyChanged(_) => "PropertyChange", + }; let properties = HashMap::new(); match event { ObjectEvent::StateChanged(state, value) => self.emit_event( @@ -136,10 +137,14 @@ impl Bus { window_name: &str, event: &WindowEvent, ) -> Result<()> { + let signal = match event { + WindowEvent::Activated => "Activate", + WindowEvent::Deactivated => "Deactivate", + }; self.emit_event( target, "org.a11y.atspi.Event.Window", - event.as_ref(), + signal, EventBody { kind: "", detail1: 0, diff --git a/platforms/unix/src/atspi/interfaces/events.rs b/platforms/unix/src/atspi/interfaces/events.rs index d57e5dbf7..fd3017f5b 100644 --- a/platforms/unix/src/atspi/interfaces/events.rs +++ b/platforms/unix/src/atspi/interfaces/events.rs @@ -5,7 +5,6 @@ use crate::atspi::{ObjectId, ObjectRef}; use atspi::{accessible::Role, State}; -use strum::AsRefStr; pub(crate) enum QueuedEvent { Object { @@ -19,31 +18,20 @@ pub(crate) enum QueuedEvent { }, } -#[derive(AsRefStr)] pub(crate) enum Property { - #[strum(serialize = "accessible-name")] Name(String), - #[strum(serialize = "accessible-description")] Description(String), - #[strum(serialize = "accessible-parent")] Parent(Option), - #[strum(serialize = "accessible-role")] Role(Role), - #[strum(serialize = "accessible-value")] Value(f64), } -#[derive(AsRefStr)] pub(crate) enum ObjectEvent { StateChanged(State, bool), - #[strum(serialize = "PropertyChange")] PropertyChanged(Property), } -#[derive(AsRefStr)] pub(crate) enum WindowEvent { - #[strum(serialize = "Activate")] Activated, - #[strum(serialize = "Deactivate")] Deactivated, } From 300018717b6eb2a1472a0982a0631aee947bed3e Mon Sep 17 00:00:00 2001 From: DataTriny Date: Mon, 26 Dec 2022 23:37:32 +0100 Subject: [PATCH 62/69] More Component interface methods and events --- consumer/src/tree.rs | 9 ++ platforms/unix/Cargo.toml | 2 +- platforms/unix/src/adapter.rs | 6 +- platforms/unix/src/atspi/bus.rs | 23 +++-- .../unix/src/atspi/interfaces/component.rs | 14 +++- platforms/unix/src/atspi/interfaces/events.rs | 6 +- platforms/unix/src/atspi/mod.rs | 13 ++- platforms/unix/src/node.rs | 84 ++++++++++++++++++- 8 files changed, 142 insertions(+), 15 deletions(-) diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index 95c9a69a6..197ed116f 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -3,6 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. +use accesskit::kurbo::Point; use accesskit::{ Action, ActionData, ActionHandler, ActionRequest, Live, Node as NodeData, NodeId, TextSelection, Tree as TreeData, TreeUpdate, @@ -386,6 +387,14 @@ impl Tree { }) } + pub fn scroll_to_point(&self, target: NodeId, point: Point) { + self.action_handler.do_action(ActionRequest { + action: Action::ScrollToPoint, + target, + data: Some(ActionData::ScrollToPoint(point)), + }) + } + pub fn select_text_range(&self, range: &TextRange) { let selection = TextSelection { anchor: range.start.downgrade(), diff --git a/platforms/unix/Cargo.toml b/platforms/unix/Cargo.toml index 3fc4f05fa..5c8556bbd 100644 --- a/platforms/unix/Cargo.toml +++ b/platforms/unix/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" [dependencies] accesskit = { version = "0.8.1", path = "../../common" } accesskit_consumer = { version = "0.11.0", path = "../../consumer" } -atspi = "0.3.10" +atspi = { git = "https://github.com/odilia-app/atspi" } parking_lot = "0.12.1" serde = "1.0" zbus = "3.6" diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index 831f6b19c..74bf4353c 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -163,7 +163,11 @@ impl Adapter { self.adapter .register_interfaces(self.tree, new_node.id(), new_interfaces ^ kept_interfaces) .unwrap(); - new_wrapper.enqueue_changes(&mut self.queue, &old_wrapper); + new_wrapper.enqueue_changes( + &self.adapter.root_window_bounds.read(), + &mut self.queue, + &old_wrapper, + ); } fn focus_moved(&mut self, old_node: Option<&DetachedNode>, new_node: Option<&Node>) { if let Some(root_window) = root_window(&self.tree.read()) { diff --git a/platforms/unix/src/atspi/bus.rs b/platforms/unix/src/atspi/bus.rs index cceb3dd25..69540f08c 100644 --- a/platforms/unix/src/atspi/bus.rs +++ b/platforms/unix/src/atspi/bus.rs @@ -75,20 +75,21 @@ impl Bus { pub fn emit_object_event(&self, target: &ObjectId, event: &ObjectEvent) -> Result<()> { let interface = "org.a11y.atspi.Event.Object"; let signal = match event { - ObjectEvent::StateChanged(_, _) => "StateChanged", + ObjectEvent::BoundsChanged(_) => "BoundsChanged", ObjectEvent::PropertyChanged(_) => "PropertyChange", + ObjectEvent::StateChanged(_, _) => "StateChanged", }; let properties = HashMap::new(); match event { - ObjectEvent::StateChanged(state, value) => self.emit_event( + ObjectEvent::BoundsChanged(bounds) => self.emit_event( target, interface, signal, EventBody { - kind: state, - detail1: *value as i32, + kind: "", + detail1: 0, detail2: 0, - any_data: 0i32.into(), + any_data: Value::from(*bounds), properties, }, ), @@ -128,6 +129,18 @@ impl Bus { properties, }, ), + ObjectEvent::StateChanged(state, value) => self.emit_event( + target, + interface, + signal, + EventBody { + kind: state, + detail1: *value as i32, + detail2: 0, + any_data: 0i32.into(), + properties, + }, + ), } } diff --git a/platforms/unix/src/atspi/interfaces/component.rs b/platforms/unix/src/atspi/interfaces/component.rs index 50762b8a9..42b638c34 100644 --- a/platforms/unix/src/atspi/interfaces/component.rs +++ b/platforms/unix/src/atspi/interfaces/component.rs @@ -9,7 +9,7 @@ use crate::{ util::WindowBounds, PlatformNode, }; -use atspi::CoordType; +use atspi::{component::Layer, CoordType}; use parking_lot::RwLock; use std::sync::{Arc, Weak}; use zbus::{fdo, MessageHeader}; @@ -64,7 +64,19 @@ impl ComponentInterface { extents } + fn get_layer(&self) -> fdo::Result { + self.node.get_layer() + } + fn grab_focus(&self) -> fdo::Result { self.node.grab_focus() } + + fn scroll_to_point(&self, coord_type: CoordType, x: i32, y: i32) -> fdo::Result { + let window_bounds = self.upgrade_bounds()?; + let scrolled = self + .node + .scroll_to_point(&window_bounds.read(), coord_type, x, y); + scrolled + } } diff --git a/platforms/unix/src/atspi/interfaces/events.rs b/platforms/unix/src/atspi/interfaces/events.rs index fd3017f5b..25054d145 100644 --- a/platforms/unix/src/atspi/interfaces/events.rs +++ b/platforms/unix/src/atspi/interfaces/events.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use crate::atspi::{ObjectId, ObjectRef}; +use crate::atspi::{ObjectId, ObjectRef, Rect}; use atspi::{accessible::Role, State}; pub(crate) enum QueuedEvent { @@ -26,9 +26,11 @@ pub(crate) enum Property { Value(f64), } +#[allow(clippy::enum_variant_names)] pub(crate) enum ObjectEvent { - StateChanged(State, bool), + BoundsChanged(Rect), PropertyChanged(Property), + StateChanged(State, bool), } pub(crate) enum WindowEvent { diff --git a/platforms/unix/src/atspi/mod.rs b/platforms/unix/src/atspi/mod.rs index 4291f4a91..fc85461d7 100644 --- a/platforms/unix/src/atspi/mod.rs +++ b/platforms/unix/src/atspi/mod.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use serde::{Deserialize, Serialize}; -use zbus::zvariant::Type; +use zbus::zvariant::{OwnedValue, Type, Value}; mod bus; pub(crate) mod interfaces; @@ -12,7 +12,7 @@ mod object_address; mod object_id; mod object_ref; -#[derive(Debug, Default, Serialize, Deserialize, Type)] +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, OwnedValue, Type, Value)] pub(crate) struct Rect { x: i32, y: i32, @@ -20,6 +20,15 @@ pub(crate) struct Rect { height: i32, } +impl Rect { + pub const INVALID: Rect = Rect { + x: -1, + y: -1, + width: -1, + height: -1, + }; +} + impl From for Rect { fn from(value: accesskit::kurbo::Rect) -> Rect { Rect { diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 38846a90f..0ebf1b981 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -15,9 +15,15 @@ use crate::{ }, util::{AppContext, WindowBounds}, }; -use accesskit::{kurbo::Point, CheckedState, DefaultActionVerb, NodeId, Role}; +use accesskit::{ + kurbo::{Affine, Point, Rect}, + CheckedState, DefaultActionVerb, NodeId, Role, +}; use accesskit_consumer::{DetachedNode, FilterResult, Node, NodeState, Tree, TreeState}; -use atspi::{accessible::Role as AtspiRole, CoordType, Interface, InterfaceSet, State, StateSet}; +use atspi::{ + accessible::Role as AtspiRole, component::Layer, CoordType, Interface, InterfaceSet, State, + StateSet, +}; use parking_lot::RwLock; use std::{ iter::FusedIterator, @@ -432,13 +438,45 @@ impl<'a> NodeWrapper<'a> { }) } + fn raw_bounds_and_transform(&self) -> (Option, Affine) { + let state = self.node_state(); + (state.raw_bounds(), state.direct_transform()) + } + + fn extents(&self, window_bounds: &WindowBounds) -> AtspiRect { + if self.is_root() { + return window_bounds.outer.into(); + } + match self { + Self::Node(node) => node.bounding_box().map_or_else( + || AtspiRect::INVALID, + |bounds| { + let window_top_left = window_bounds.inner.origin(); + let node_origin = bounds.origin(); + let new_origin = Point::new( + window_top_left.x + node_origin.x, + window_top_left.y + node_origin.y, + ); + bounds.with_origin(new_origin).into() + }, + ), + _ => unreachable!(), + } + } + fn current_value(&self) -> Option { self.node_state().numeric_value() } - pub fn enqueue_changes(&self, queue: &mut Vec, old: &NodeWrapper) { + pub fn enqueue_changes( + &self, + window_bounds: &WindowBounds, + queue: &mut Vec, + old: &NodeWrapper, + ) { self.enqueue_state_changes(queue, old); self.enqueue_property_changes(queue, old); + self.enqueue_bounds_changes(window_bounds, queue, old); } fn enqueue_state_changes(&self, queue: &mut Vec, old: &NodeWrapper) { @@ -491,6 +529,20 @@ impl<'a> NodeWrapper<'a> { } } } + + fn enqueue_bounds_changes( + &self, + window_bounds: &WindowBounds, + queue: &mut Vec, + old: &NodeWrapper, + ) { + if self.raw_bounds_and_transform() != old.raw_bounds_and_transform() { + queue.push(QueuedEvent::Object { + target: self.id(), + event: ObjectEvent::BoundsChanged(self.extents(window_bounds)), + }); + } + } } pub(crate) fn unknown_object(id: &ObjectId) -> fdo::Error { @@ -740,12 +792,38 @@ impl PlatformNode { }) } + pub fn get_layer(&self) -> fdo::Result { + self.resolve(|node| { + let wrapper = NodeWrapper::Node(&node); + if wrapper.role() == AtspiRole::Window { + Ok(Layer::Window) + } else { + Ok(Layer::Widget) + } + }) + } + pub fn grab_focus(&self) -> fdo::Result { let tree = self.validate_for_action()?; tree.set_focus(self.node_id); Ok(true) } + pub fn scroll_to_point( + &self, + window_bounds: &WindowBounds, + coord_type: CoordType, + x: i32, + y: i32, + ) -> fdo::Result { + let tree = self.validate_for_action()?; + let is_root = self.node_id == tree.read().root_id(); + let top_left = window_bounds.top_left(coord_type, is_root); + let point = Point::new(f64::from(x) - top_left.x, f64::from(y) - top_left.y); + tree.scroll_to_point(self.node_id, point); + Ok(true) + } + pub fn minimum_value(&self) -> fdo::Result { self.resolve(|node| Ok(node.state().min_numeric_value().unwrap_or(std::f64::MIN))) } From c077ed928ba4f34e294bd0021d1236cfe8c262a3 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Tue, 27 Dec 2022 20:18:06 +0100 Subject: [PATCH 63/69] Use filtered nodes everywhere --- platforms/unix/src/adapter.rs | 80 +++++++++++++++++++++++------------ platforms/unix/src/node.rs | 27 ++++-------- 2 files changed, 62 insertions(+), 45 deletions(-) diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index 74bf4353c..2245bdb1c 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -11,11 +11,11 @@ use crate::{ }, Bus, ObjectId, ACCESSIBLE_PATH_PREFIX, }, - node::{filter, NodeWrapper, PlatformNode, PlatformRootNode}, + node::{filter, filter_detached, NodeWrapper, PlatformNode, PlatformRootNode}, util::{AppContext, WindowBounds}, }; use accesskit::{kurbo::Rect, ActionHandler, NodeId, Role, TreeUpdate}; -use accesskit_consumer::{DetachedNode, Node, Tree, TreeChangeHandler, TreeState}; +use accesskit_consumer::{DetachedNode, FilterResult, Node, Tree, TreeChangeHandler, TreeState}; use atspi::{Interface, InterfaceSet, State}; use parking_lot::RwLock; use std::sync::Arc; @@ -144,30 +144,61 @@ impl Adapter { tree: &'a Arc, queue: Vec, } - impl TreeChangeHandler for Handler<'_> { - fn node_added(&mut self, node: &Node) { + impl Handler<'_> { + fn add_node(&mut self, node: &Node) { let interfaces = NodeWrapper::Node(node).interfaces(); self.adapter .register_interfaces(self.tree, node.id(), interfaces) .unwrap(); } - fn node_updated(&mut self, old_node: &DetachedNode, new_node: &Node) { - let old_wrapper = NodeWrapper::DetachedNode(old_node); - let new_wrapper = NodeWrapper::Node(new_node); - let old_interfaces = old_wrapper.interfaces(); - let new_interfaces = new_wrapper.interfaces(); - let kept_interfaces = old_interfaces & new_interfaces; - self.adapter - .unregister_interfaces(&new_wrapper.id(), old_interfaces ^ kept_interfaces) - .unwrap(); + fn remove_node(&mut self, node: &DetachedNode) { + let node = NodeWrapper::DetachedNode(node); + self.queue.push(QueuedEvent::Object { + target: node.id(), + event: ObjectEvent::StateChanged(State::Defunct, true), + }); self.adapter - .register_interfaces(self.tree, new_node.id(), new_interfaces ^ kept_interfaces) + .unregister_interfaces(&node.id(), node.interfaces()) .unwrap(); - new_wrapper.enqueue_changes( - &self.adapter.root_window_bounds.read(), - &mut self.queue, - &old_wrapper, - ); + } + } + impl TreeChangeHandler for Handler<'_> { + fn node_added(&mut self, node: &Node) { + if filter(node) == FilterResult::Include { + self.add_node(node); + } + } + fn node_updated(&mut self, old_node: &DetachedNode, new_node: &Node) { + let filter_old = filter_detached(old_node); + let filter_new = filter(new_node); + if filter_new != filter_old { + if filter_new == FilterResult::Include { + self.add_node(new_node); + } else if filter_old == FilterResult::Include { + self.remove_node(old_node); + } + } else if filter_new == FilterResult::Include { + let old_wrapper = NodeWrapper::DetachedNode(old_node); + let new_wrapper = NodeWrapper::Node(new_node); + let old_interfaces = old_wrapper.interfaces(); + let new_interfaces = new_wrapper.interfaces(); + let kept_interfaces = old_interfaces & new_interfaces; + self.adapter + .unregister_interfaces(&new_wrapper.id(), old_interfaces ^ kept_interfaces) + .unwrap(); + self.adapter + .register_interfaces( + self.tree, + new_node.id(), + new_interfaces ^ kept_interfaces, + ) + .unwrap(); + new_wrapper.enqueue_changes( + &self.adapter.root_window_bounds.read(), + &mut self.queue, + &old_wrapper, + ); + } } fn focus_moved(&mut self, old_node: Option<&DetachedNode>, new_node: Option<&Node>) { if let Some(root_window) = root_window(&self.tree.read()) { @@ -193,14 +224,9 @@ impl Adapter { } } fn node_removed(&mut self, node: &DetachedNode, _: &TreeState) { - let node = NodeWrapper::DetachedNode(node); - self.queue.push(QueuedEvent::Object { - target: node.id(), - event: ObjectEvent::StateChanged(State::Defunct, true), - }); - self.adapter - .unregister_interfaces(&node.id(), node.interfaces()) - .unwrap(); + if filter_detached(node) == FilterResult::Include { + self.remove_node(node); + } } } let mut handler = Handler { diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 0ebf1b981..6d6eb6a0f 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -25,10 +25,7 @@ use atspi::{ StateSet, }; use parking_lot::RwLock; -use std::{ - iter::FusedIterator, - sync::{Arc, Weak}, -}; +use std::sync::{Arc, Weak}; use zbus::fdo; fn filter_common(node: &NodeState) -> FilterResult { @@ -102,15 +99,6 @@ impl<'a> NodeWrapper<'a> { self.node_state().id().into() } - pub fn child_ids( - &self, - ) -> impl DoubleEndedIterator - + ExactSizeIterator - + FusedIterator - + '_ { - self.node_state().child_ids() - } - pub fn role(&self) -> AtspiRole { match self.node_state().role() { Role::Alert => AtspiRole::Notification, @@ -629,7 +617,7 @@ impl PlatformNode { pub fn child_count(&self) -> fdo::Result { self.resolve(|node| { - i32::try_from(node.state().child_ids().count()) + i32::try_from(node.filtered_children(&filter).count()) .map_err(|_| fdo::Error::Failed("Too many children.".into())) }) } @@ -640,18 +628,21 @@ impl PlatformNode { pub fn child_at_index(&self, index: usize) -> fdo::Result> { self.resolve(|node| { - let wrapper = NodeWrapper::Node(&node); - let child = wrapper.child_ids().nth(index).map(ObjectRef::from); + let child = node + .filtered_children(&filter) + .nth(index) + .map(|child| child.id().into()); Ok(child) }) } pub fn children(&self) -> fdo::Result> { self.resolve(|node| { - Ok(node + let children = node .filtered_children(&filter) .map(|child| child.id().into()) - .collect()) + .collect(); + Ok(children) }) } From afcdf42c48239c90d043024051a514dc4c1f6ab5 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Tue, 27 Dec 2022 20:18:53 +0100 Subject: [PATCH 64/69] Switch to newly published atspi version --- platforms/unix/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/unix/Cargo.toml b/platforms/unix/Cargo.toml index 5c8556bbd..be95d72ff 100644 --- a/platforms/unix/Cargo.toml +++ b/platforms/unix/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" [dependencies] accesskit = { version = "0.8.1", path = "../../common" } accesskit_consumer = { version = "0.11.0", path = "../../consumer" } -atspi = { git = "https://github.com/odilia-app/atspi" } +atspi = "0.6.0" parking_lot = "0.12.1" serde = "1.0" zbus = "3.6" From 8f12d9152c0d926ced91972a5fc4e909dc86802e Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 28 Dec 2022 23:34:58 +0100 Subject: [PATCH 65/69] Raise events on child addition and removal --- Cargo.lock | 762 +++++++++++++++++- platforms/unix/src/adapter.rs | 6 +- platforms/unix/src/atspi/bus.rs | 56 +- platforms/unix/src/atspi/interfaces/events.rs | 2 + platforms/unix/src/atspi/object_ref.rs | 11 + platforms/unix/src/node.rs | 45 +- 6 files changed, 839 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dce6e95ee..cb4f772f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,6 +31,18 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "accesskit_unix" +version = "0.1.0" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi", + "parking_lot", + "serde", + "zbus", +] + [[package]] name = "accesskit_windows" version = "0.10.2" @@ -52,6 +64,7 @@ version = "0.7.3" dependencies = [ "accesskit", "accesskit_macos", + "accesskit_unix", "accesskit_windows", "parking_lot", "winit", @@ -63,6 +76,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -81,6 +103,115 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" +[[package]] +name = "async-broadcast" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61" +dependencies = [ + "event-listener", + "futures-core", + "parking_lot", +] + +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +dependencies = [ + "async-lock", + "autocfg", + "concurrent-queue", + "futures-lite", + "libc", + "log", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "windows-sys 0.42.0", +] + +[[package]] +name = "async-lock" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +dependencies = [ + "event-listener", + "futures-lite", +] + +[[package]] +name = "async-recursion" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" + +[[package]] +name = "async-trait" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atspi" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49d470388c8f9366e00ba7baa5c943ee3b27d56b4c8e12a1dabc4e7449e3bb5b" +dependencies = [ + "async-recursion", + "async-trait", + "enumflags2", + "futures", + "serde", + "tracing", + "zbus", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -99,6 +230,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + [[package]] name = "block-sys" version = "0.1.0-beta.1" @@ -130,6 +270,12 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "calloop" version = "0.10.1" @@ -195,6 +341,15 @@ dependencies = [ "objc", ] +[[package]] +name = "concurrent-queue" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -248,6 +403,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -257,6 +421,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossfont" version = "0.5.0" @@ -280,6 +453,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cty" version = "0.2.2" @@ -321,6 +504,47 @@ dependencies = [ "syn", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -362,6 +586,27 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" +[[package]] +name = "enumflags2" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "enumset" version = "1.0.8" @@ -384,6 +629,12 @@ dependencies = [ "syn", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "expat-sys" version = "2.1.6" @@ -394,6 +645,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "flate2" version = "1.0.24" @@ -474,6 +734,110 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "ident_case" version = "1.0.1" @@ -532,9 +896,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libloading" @@ -622,7 +986,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -708,6 +1072,20 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + [[package]] name = "nom" version = "7.1.1" @@ -780,6 +1158,22 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +[[package]] +name = "ordered-stream" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ca8c99d73c6e92ac1358f9f692c22c0bfd9c4701fa086f5d365c0d4ea818ea" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.12.1" @@ -800,7 +1194,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -815,6 +1209,18 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.25" @@ -833,34 +1239,85 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "windows-sys 0.42.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ + "once_cell", "thiserror", "toml", ] [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "raw-window-handle" version = "0.4.3" @@ -881,13 +1338,50 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "ryu" version = "1.0.5" @@ -994,6 +1488,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "servo-fontconfig" version = "0.5.1" @@ -1015,6 +1520,26 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + [[package]] name = "slotmap" version = "1.0.6" @@ -1049,6 +1574,22 @@ dependencies = [ "wayland-protocols", ] +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -1057,13 +1598,27 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.73" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", ] [[package]] @@ -1121,10 +1676,58 @@ dependencies = [ ] [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "tracing" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "vec_map" @@ -1138,6 +1741,12 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1281,6 +1890,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1343,6 +1961,21 @@ dependencies = [ "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" @@ -1444,7 +2077,7 @@ dependencies = [ "wayland-client", "wayland-protocols", "web-sys", - "windows-sys", + "windows-sys 0.36.1", "x11-dl", ] @@ -1482,3 +2115,92 @@ name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "zbus" +version = "3.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938ea6da98c75c2c37a86007bd17fd8e208cbec24e086108c87ece98e9edec0d" +dependencies = [ + "async-broadcast", + "async-channel", + "async-executor", + "async-io", + "async-lock", + "async-recursion", + "async-task", + "async-trait", + "byteorder", + "derivative", + "dirs", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.25.1", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45066039ebf3330820e495e854f8b312abb68f0a39e97972d092bd72e8bb3e8e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "zbus_names" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c737644108627748a660d038974160e0cbb62605536091bdfa28fd7f64d43c8" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zvariant" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8c89c183461e11867ded456db252eae90874bc6769b7adbea464caa777e51" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155247a5d1ab55e335421c104ccd95d64f17cebbd02f50cdbc1c33385f9c4d81" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index 2245bdb1c..02c611fca 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -283,9 +283,9 @@ pub struct QueuedEvents { } impl QueuedEvents { - pub fn raise(&self) { - for event in &self.queue { - let _ = match &event { + pub fn raise(self) { + for event in self.queue { + let _ = match event { QueuedEvent::Object { target, event } => self.bus.emit_object_event(target, event), QueuedEvent::Window { target, diff --git a/platforms/unix/src/atspi/bus.rs b/platforms/unix/src/atspi/bus.rs index 69540f08c..5774ad77b 100644 --- a/platforms/unix/src/atspi/bus.rs +++ b/platforms/unix/src/atspi/bus.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use crate::{ - atspi::{interfaces::*, object_address::*, ObjectId, ObjectRef}, + atspi::{interfaces::*, object_address::*, ObjectId}, PlatformRootNode, }; use atspi::{bus::BusProxyBlocking, socket::SocketProxyBlocking, EventBody}; @@ -72,10 +72,11 @@ impl Bus { } } - pub fn emit_object_event(&self, target: &ObjectId, event: &ObjectEvent) -> Result<()> { + pub fn emit_object_event(&self, target: ObjectId, event: ObjectEvent) -> Result<()> { let interface = "org.a11y.atspi.Event.Object"; let signal = match event { ObjectEvent::BoundsChanged(_) => "BoundsChanged", + ObjectEvent::ChildAdded(_, _) | ObjectEvent::ChildRemoved(_) => "ChildrenChanged", ObjectEvent::PropertyChanged(_) => "PropertyChange", ObjectEvent::StateChanged(_, _) => "StateChanged", }; @@ -89,7 +90,31 @@ impl Bus { kind: "", detail1: 0, detail2: 0, - any_data: Value::from(*bounds), + any_data: Value::from(bounds), + properties, + }, + ), + ObjectEvent::ChildAdded(index, child) => self.emit_event( + target, + interface, + signal, + EventBody { + kind: "add", + detail1: index as i32, + detail2: 0, + any_data: child.into_value(self.unique_name().clone()), + properties, + }, + ), + ObjectEvent::ChildRemoved(child) => self.emit_event( + target, + interface, + signal, + EventBody { + kind: "remove", + detail1: -1, + detail2: 0, + any_data: child.into_value(self.unique_name().clone()), properties, }, ), @@ -110,21 +135,14 @@ impl Bus { any_data: match property { Property::Name(value) => Str::from(value).into(), Property::Description(value) => Str::from(value).into(), - Property::Parent(Some(ObjectRef::Managed(parent))) => { - OwnedObjectAddress::accessible( - self.unique_name().clone(), - parent.clone(), - ) - .into() - } - Property::Parent(Some(ObjectRef::Unmanaged(parent))) => { - parent.clone().into() + Property::Parent(Some(parent)) => { + parent.into_value(self.unique_name().clone()) } Property::Parent(None) => { OwnedObjectAddress::root(self.unique_name().clone()).into() } - Property::Role(value) => Value::U32(*value as u32), - Property::Value(value) => Value::F64(*value), + Property::Role(value) => Value::U32(value as u32), + Property::Value(value) => Value::F64(value), }, properties, }, @@ -135,7 +153,7 @@ impl Bus { signal, EventBody { kind: state, - detail1: *value as i32, + detail1: value as i32, detail2: 0, any_data: 0i32.into(), properties, @@ -146,9 +164,9 @@ impl Bus { pub fn emit_window_event( &self, - target: &ObjectId, - window_name: &str, - event: &WindowEvent, + target: ObjectId, + window_name: String, + event: WindowEvent, ) -> Result<()> { let signal = match event { WindowEvent::Activated => "Activate", @@ -170,7 +188,7 @@ impl Bus { fn emit_event( &self, - id: &ObjectId, + id: ObjectId, interface: &str, signal_name: &str, body: EventBody, diff --git a/platforms/unix/src/atspi/interfaces/events.rs b/platforms/unix/src/atspi/interfaces/events.rs index 25054d145..c5109bd84 100644 --- a/platforms/unix/src/atspi/interfaces/events.rs +++ b/platforms/unix/src/atspi/interfaces/events.rs @@ -29,6 +29,8 @@ pub(crate) enum Property { #[allow(clippy::enum_variant_names)] pub(crate) enum ObjectEvent { BoundsChanged(Rect), + ChildAdded(usize, ObjectRef), + ChildRemoved(ObjectRef), PropertyChanged(Property), StateChanged(State, bool), } diff --git a/platforms/unix/src/atspi/object_ref.rs b/platforms/unix/src/atspi/object_ref.rs index ecf16faf6..f7be5f64e 100644 --- a/platforms/unix/src/atspi/object_ref.rs +++ b/platforms/unix/src/atspi/object_ref.rs @@ -5,6 +5,7 @@ use crate::atspi::{ObjectId, OwnedObjectAddress}; use accesskit::NodeId; +use zbus::{names::OwnedUniqueName, zvariant::Value}; #[derive(Debug, PartialEq)] pub(crate) enum ObjectRef { @@ -12,6 +13,16 @@ pub(crate) enum ObjectRef { Unmanaged(OwnedObjectAddress), } +impl<'a> ObjectRef { + pub(crate) fn into_value(self, name: OwnedUniqueName) -> Value<'a> { + match self { + Self::Managed(object) => OwnedObjectAddress::accessible(name, object), + Self::Unmanaged(object) => object, + } + .into() + } +} + impl From for ObjectRef { fn from(value: NodeId) -> ObjectRef { ObjectRef::Managed(value.into()) diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 6d6eb6a0f..6b2dc0cd5 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -25,7 +25,10 @@ use atspi::{ StateSet, }; use parking_lot::RwLock; -use std::sync::{Arc, Weak}; +use std::{ + iter::FusedIterator, + sync::{Arc, Weak}, +}; use zbus::fdo; fn filter_common(node: &NodeState) -> FilterResult { @@ -99,6 +102,24 @@ impl<'a> NodeWrapper<'a> { self.node_state().id().into() } + fn child_ids( + &self, + ) -> impl DoubleEndedIterator + + ExactSizeIterator + + FusedIterator + + '_ { + self.node_state().child_ids() + } + + fn filtered_child_ids( + &self, + ) -> impl DoubleEndedIterator + FusedIterator + '_ { + match self { + Self::Node(node) => node.filtered_children(&filter).map(|child| child.id()), + Self::DetachedNode(_) => unreachable!(), + } + } + pub fn role(&self) -> AtspiRole { match self.node_state().role() { Role::Alert => AtspiRole::Notification, @@ -465,6 +486,7 @@ impl<'a> NodeWrapper<'a> { self.enqueue_state_changes(queue, old); self.enqueue_property_changes(queue, old); self.enqueue_bounds_changes(window_bounds, queue, old); + self.enqueue_children_changes(queue, old); } fn enqueue_state_changes(&self, queue: &mut Vec, old: &NodeWrapper) { @@ -531,6 +553,27 @@ impl<'a> NodeWrapper<'a> { }); } } + + fn enqueue_children_changes(&self, queue: &mut Vec, old: &NodeWrapper) { + let old_children = old.child_ids().collect::>(); + let filtered_children = self.filtered_child_ids().collect::>(); + for (index, child) in filtered_children.iter().enumerate() { + if !old_children.contains(child) { + queue.push(QueuedEvent::Object { + target: self.id(), + event: ObjectEvent::ChildAdded(index, ObjectRef::from(*child)), + }); + } + } + for child in old_children.into_iter() { + if !filtered_children.contains(&child) { + queue.push(QueuedEvent::Object { + target: self.id(), + event: ObjectEvent::ChildRemoved(child.into()), + }); + } + } + } } pub(crate) fn unknown_object(id: &ObjectId) -> fdo::Error { From 988ccfd7e5444298e402e74bde02fa5b20bce953 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Thu, 29 Dec 2022 18:00:18 +0100 Subject: [PATCH 66/69] Raise events asynchronously --- Cargo.lock | 28 +++ platforms/unix/Cargo.toml | 1 + platforms/unix/src/adapter.rs | 152 +++++++------ platforms/unix/src/atspi/bus.rs | 203 ++++++++++-------- platforms/unix/src/atspi/interfaces/events.rs | 2 +- platforms/unix/src/node.rs | 115 +++++----- platforms/winit/src/platform_impl/unix.rs | 4 +- 7 files changed, 302 insertions(+), 203 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb4f772f6..38c3ea83b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,7 @@ dependencies = [ "accesskit", "accesskit_consumer", "atspi", + "futures", "parking_lot", "serde", "zbus", @@ -742,6 +743,7 @@ checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -764,6 +766,17 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +[[package]] +name = "futures-executor" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.25" @@ -785,6 +798,17 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.25" @@ -803,9 +827,13 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", diff --git a/platforms/unix/Cargo.toml b/platforms/unix/Cargo.toml index be95d72ff..2cdfd739c 100644 --- a/platforms/unix/Cargo.toml +++ b/platforms/unix/Cargo.toml @@ -14,6 +14,7 @@ edition = "2021" accesskit = { version = "0.8.1", path = "../../common" } accesskit_consumer = { version = "0.11.0", path = "../../consumer" } atspi = "0.6.0" +futures = "0.3" parking_lot = "0.12.1" serde = "1.0" zbus = "3.6" diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index 02c611fca..f8026c924 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -6,7 +6,7 @@ use crate::{ atspi::{ interfaces::{ - AccessibleInterface, ActionInterface, ComponentInterface, ObjectEvent, QueuedEvent, + AccessibleInterface, ActionInterface, ComponentInterface, Event, ObjectEvent, ValueInterface, WindowEvent, }, Bus, ObjectId, ACCESSIBLE_PATH_PREFIX, @@ -17,11 +17,18 @@ use crate::{ use accesskit::{kurbo::Rect, ActionHandler, NodeId, Role, TreeUpdate}; use accesskit_consumer::{DetachedNode, FilterResult, Node, Tree, TreeChangeHandler, TreeState}; use atspi::{Interface, InterfaceSet, State}; +use futures::{ + channel::mpsc::{self, UnboundedReceiver, UnboundedSender}, + StreamExt, +}; use parking_lot::RwLock; use std::sync::Arc; +use zbus::Task; pub struct Adapter { atspi_bus: Bus, + _event_task: Task<()>, + events: UnboundedSender, _app_context: Arc>, root_window_bounds: Arc>, tree: Arc, @@ -36,6 +43,14 @@ impl Adapter { action_handler: Box, ) -> Option { let mut atspi_bus = Bus::a11y_bus()?; + let (event_sender, event_receiver) = mpsc::unbounded(); + let atspi_bus_copy = atspi_bus.clone(); + let event_task = atspi_bus.connection().inner().executor().spawn( + async move { + handle_events(atspi_bus_copy, event_receiver).await; + }, + "accesskit_event_task", + ); let tree = Arc::new(Tree::new(initial_state(), action_handler)); let app_context = Arc::new(RwLock::new(AppContext::new( app_name, @@ -47,6 +62,8 @@ impl Adapter { .ok()?; let adapter = Adapter { atspi_bus, + _event_task: event_task, + events: event_sender, _app_context: app_context, root_window_bounds: Arc::new(RwLock::new(WindowBounds::default())), tree, @@ -138,11 +155,10 @@ impl Adapter { bounds.inner = inner; } - pub fn update(&self, update: TreeUpdate) -> QueuedEvents { + pub fn update(&self, update: TreeUpdate) { struct Handler<'a> { adapter: &'a Adapter, tree: &'a Arc, - queue: Vec, } impl Handler<'_> { fn add_node(&mut self, node: &Node) { @@ -153,10 +169,13 @@ impl Adapter { } fn remove_node(&mut self, node: &DetachedNode) { let node = NodeWrapper::DetachedNode(node); - self.queue.push(QueuedEvent::Object { - target: node.id(), - event: ObjectEvent::StateChanged(State::Defunct, true), - }); + self.adapter + .events + .unbounded_send(Event::Object { + target: node.id(), + event: ObjectEvent::StateChanged(State::Defunct, true), + }) + .unwrap(); self.adapter .unregister_interfaces(&node.id(), node.interfaces()) .unwrap(); @@ -193,9 +212,9 @@ impl Adapter { new_interfaces ^ kept_interfaces, ) .unwrap(); - new_wrapper.enqueue_changes( + new_wrapper.notify_changes( &self.adapter.root_window_bounds.read(), - &mut self.queue, + &self.adapter.events, &old_wrapper, ); } @@ -203,24 +222,34 @@ impl Adapter { fn focus_moved(&mut self, old_node: Option<&DetachedNode>, new_node: Option<&Node>) { if let Some(root_window) = root_window(&self.tree.read()) { if old_node.is_none() && new_node.is_some() { - self.adapter - .window_activated(&NodeWrapper::Node(&root_window), &mut self.queue); + self.adapter.window_activated( + &NodeWrapper::Node(&root_window), + &self.adapter.events, + ); } else if old_node.is_some() && new_node.is_none() { - self.adapter - .window_deactivated(&NodeWrapper::Node(&root_window), &mut self.queue); + self.adapter.window_deactivated( + &NodeWrapper::Node(&root_window), + &self.adapter.events, + ); } } if let Some(node) = new_node.map(NodeWrapper::Node) { - self.queue.push(QueuedEvent::Object { - target: node.id(), - event: ObjectEvent::StateChanged(State::Focused, true), - }); + self.adapter + .events + .unbounded_send(Event::Object { + target: node.id(), + event: ObjectEvent::StateChanged(State::Focused, true), + }) + .unwrap(); } if let Some(node) = old_node.map(NodeWrapper::DetachedNode) { - self.queue.push(QueuedEvent::Object { - target: node.id(), - event: ObjectEvent::StateChanged(State::Focused, false), - }); + self.adapter + .events + .unbounded_send(Event::Object { + target: node.id(), + event: ObjectEvent::StateChanged(State::Focused, false), + }) + .unwrap(); } } fn node_removed(&mut self, node: &DetachedNode, _: &TreeState) { @@ -232,37 +261,40 @@ impl Adapter { let mut handler = Handler { adapter: self, tree: &self.tree, - queue: Vec::new(), }; self.tree.update_and_process_changes(update, &mut handler); - QueuedEvents { - bus: self.atspi_bus.clone(), - queue: handler.queue, - } } - fn window_activated(&self, window: &NodeWrapper, queue: &mut Vec) { - queue.push(QueuedEvent::Window { - target: window.id(), - name: window.name(), - event: WindowEvent::Activated, - }); - queue.push(QueuedEvent::Object { - target: window.id(), - event: ObjectEvent::StateChanged(State::Active, true), - }); + fn window_activated(&self, window: &NodeWrapper, events: &UnboundedSender) { + events + .unbounded_send(Event::Window { + target: window.id(), + name: window.name(), + event: WindowEvent::Activated, + }) + .unwrap(); + events + .unbounded_send(Event::Object { + target: window.id(), + event: ObjectEvent::StateChanged(State::Active, true), + }) + .unwrap(); } - fn window_deactivated(&self, window: &NodeWrapper, queue: &mut Vec) { - queue.push(QueuedEvent::Window { - target: window.id(), - name: window.name(), - event: WindowEvent::Deactivated, - }); - queue.push(QueuedEvent::Object { - target: window.id(), - event: ObjectEvent::StateChanged(State::Active, false), - }); + fn window_deactivated(&self, window: &NodeWrapper, events: &UnboundedSender) { + events + .unbounded_send(Event::Window { + target: window.id(), + name: window.name(), + event: WindowEvent::Deactivated, + }) + .unwrap(); + events + .unbounded_send(Event::Object { + target: window.id(), + event: ObjectEvent::StateChanged(State::Active, false), + }) + .unwrap(); } } @@ -276,23 +308,15 @@ fn root_window(current_state: &TreeState) -> Option { } } -#[must_use = "events must be explicitly raised"] -pub struct QueuedEvents { - bus: Bus, - queue: Vec, -} - -impl QueuedEvents { - pub fn raise(self) { - for event in self.queue { - let _ = match event { - QueuedEvent::Object { target, event } => self.bus.emit_object_event(target, event), - QueuedEvent::Window { - target, - name, - event, - } => self.bus.emit_window_event(target, name, event), - }; - } +async fn handle_events(bus: Bus, mut events: UnboundedReceiver) { + while let Some(event) = events.next().await { + let _ = match event { + Event::Object { target, event } => bus.emit_object_event(target, event).await, + Event::Window { + target, + name, + event, + } => bus.emit_window_event(target, name, event).await, + }; } } diff --git a/platforms/unix/src/atspi/bus.rs b/platforms/unix/src/atspi/bus.rs index 5774ad77b..ceac25a14 100644 --- a/platforms/unix/src/atspi/bus.rs +++ b/platforms/unix/src/atspi/bus.rs @@ -30,6 +30,10 @@ impl Bus { Some(Bus { conn, socket_proxy }) } + pub(crate) fn connection(&self) -> &Connection { + &self.conn + } + pub fn unique_name(&self) -> &OwnedUniqueName { self.conn.unique_name().unwrap() } @@ -72,7 +76,11 @@ impl Bus { } } - pub fn emit_object_event(&self, target: ObjectId, event: ObjectEvent) -> Result<()> { + pub(crate) async fn emit_object_event( + &self, + target: ObjectId<'_>, + event: ObjectEvent, + ) -> Result<()> { let interface = "org.a11y.atspi.Event.Object"; let signal = match event { ObjectEvent::BoundsChanged(_) => "BoundsChanged", @@ -82,89 +90,104 @@ impl Bus { }; let properties = HashMap::new(); match event { - ObjectEvent::BoundsChanged(bounds) => self.emit_event( - target, - interface, - signal, - EventBody { - kind: "", - detail1: 0, - detail2: 0, - any_data: Value::from(bounds), - properties, - }, - ), - ObjectEvent::ChildAdded(index, child) => self.emit_event( - target, - interface, - signal, - EventBody { - kind: "add", - detail1: index as i32, - detail2: 0, - any_data: child.into_value(self.unique_name().clone()), - properties, - }, - ), - ObjectEvent::ChildRemoved(child) => self.emit_event( - target, - interface, - signal, - EventBody { - kind: "remove", - detail1: -1, - detail2: 0, - any_data: child.into_value(self.unique_name().clone()), - properties, - }, - ), - ObjectEvent::PropertyChanged(property) => self.emit_event( - target, - interface, - signal, - EventBody { - kind: match property { - Property::Name(_) => "accessible-name", - Property::Description(_) => "accessible-description", - Property::Parent(_) => "accessible-parent", - Property::Role(_) => "accessible-role", - Property::Value(_) => "accessible-value", + ObjectEvent::BoundsChanged(bounds) => { + self.emit_event( + target, + interface, + signal, + EventBody { + kind: "", + detail1: 0, + detail2: 0, + any_data: Value::from(bounds), + properties, + }, + ) + .await + } + ObjectEvent::ChildAdded(index, child) => { + self.emit_event( + target, + interface, + signal, + EventBody { + kind: "add", + detail1: index as i32, + detail2: 0, + any_data: child.into_value(self.unique_name().clone()), + properties, + }, + ) + .await + } + ObjectEvent::ChildRemoved(child) => { + self.emit_event( + target, + interface, + signal, + EventBody { + kind: "remove", + detail1: -1, + detail2: 0, + any_data: child.into_value(self.unique_name().clone()), + properties, + }, + ) + .await + } + ObjectEvent::PropertyChanged(property) => { + self.emit_event( + target, + interface, + signal, + EventBody { + kind: match property { + Property::Name(_) => "accessible-name", + Property::Description(_) => "accessible-description", + Property::Parent(_) => "accessible-parent", + Property::Role(_) => "accessible-role", + Property::Value(_) => "accessible-value", + }, + detail1: 0, + detail2: 0, + any_data: match property { + Property::Name(value) => Str::from(value).into(), + Property::Description(value) => Str::from(value).into(), + Property::Parent(Some(parent)) => { + parent.into_value(self.unique_name().clone()) + } + Property::Parent(None) => { + OwnedObjectAddress::root(self.unique_name().clone()).into() + } + Property::Role(value) => Value::U32(value as u32), + Property::Value(value) => Value::F64(value), + }, + properties, }, - detail1: 0, - detail2: 0, - any_data: match property { - Property::Name(value) => Str::from(value).into(), - Property::Description(value) => Str::from(value).into(), - Property::Parent(Some(parent)) => { - parent.into_value(self.unique_name().clone()) - } - Property::Parent(None) => { - OwnedObjectAddress::root(self.unique_name().clone()).into() - } - Property::Role(value) => Value::U32(value as u32), - Property::Value(value) => Value::F64(value), + ) + .await + } + ObjectEvent::StateChanged(state, value) => { + self.emit_event( + target, + interface, + signal, + EventBody { + kind: state, + detail1: value as i32, + detail2: 0, + any_data: 0i32.into(), + properties, }, - properties, - }, - ), - ObjectEvent::StateChanged(state, value) => self.emit_event( - target, - interface, - signal, - EventBody { - kind: state, - detail1: value as i32, - detail2: 0, - any_data: 0i32.into(), - properties, - }, - ), + ) + .await + } } } - pub fn emit_window_event( + pub(crate) async fn emit_window_event( &self, - target: ObjectId, + target: ObjectId<'_>, window_name: String, event: WindowEvent, ) -> Result<()> { @@ -184,23 +207,27 @@ impl Bus { properties: HashMap::new(), }, ) + .await } - fn emit_event( + async fn emit_event( &self, - id: ObjectId, + id: ObjectId<'_>, interface: &str, signal_name: &str, - body: EventBody, + body: EventBody<'_, T>, ) -> Result<()> { let path = format!("{}{}", ACCESSIBLE_PATH_PREFIX, id.as_str()); - self.conn.emit_signal( - Option::::None, - path, - InterfaceName::from_str_unchecked(interface), - MemberName::from_str_unchecked(signal_name), - &body, - ) + self.conn + .inner() + .emit_signal( + Option::::None, + path, + InterfaceName::from_str_unchecked(interface), + MemberName::from_str_unchecked(signal_name), + &body, + ) + .await } } diff --git a/platforms/unix/src/atspi/interfaces/events.rs b/platforms/unix/src/atspi/interfaces/events.rs index c5109bd84..5824a84e4 100644 --- a/platforms/unix/src/atspi/interfaces/events.rs +++ b/platforms/unix/src/atspi/interfaces/events.rs @@ -6,7 +6,7 @@ use crate::atspi::{ObjectId, ObjectRef, Rect}; use atspi::{accessible::Role, State}; -pub(crate) enum QueuedEvent { +pub(crate) enum Event { Object { target: ObjectId<'static>, event: ObjectEvent, diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 6b2dc0cd5..90919a76d 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -10,7 +10,7 @@ use crate::{ atspi::{ - interfaces::{Action, ObjectEvent, Property, QueuedEvent}, + interfaces::{Action, Event, ObjectEvent, Property}, ObjectId, ObjectRef, Rect as AtspiRect, ACCESSIBLE_PATH_PREFIX, }, util::{AppContext, WindowBounds}, @@ -24,6 +24,7 @@ use atspi::{ accessible::Role as AtspiRole, component::Layer, CoordType, Interface, InterfaceSet, State, StateSet, }; +use futures::channel::mpsc::UnboundedSender; use parking_lot::RwLock; use std::{ iter::FusedIterator, @@ -477,100 +478,118 @@ impl<'a> NodeWrapper<'a> { self.node_state().numeric_value() } - pub fn enqueue_changes( + pub fn notify_changes( &self, window_bounds: &WindowBounds, - queue: &mut Vec, + events: &UnboundedSender, old: &NodeWrapper, ) { - self.enqueue_state_changes(queue, old); - self.enqueue_property_changes(queue, old); - self.enqueue_bounds_changes(window_bounds, queue, old); - self.enqueue_children_changes(queue, old); + self.notify_state_changes(events, old); + self.notify_property_changes(events, old); + self.notify_bounds_changes(window_bounds, events, old); + self.notify_children_changes(events, old); } - fn enqueue_state_changes(&self, queue: &mut Vec, old: &NodeWrapper) { + fn notify_state_changes(&self, events: &UnboundedSender, old: &NodeWrapper) { let old_state = old.state(); let new_state = self.state(); let changed_states = old_state ^ new_state; for state in changed_states.iter() { - queue.push(QueuedEvent::Object { - target: self.id(), - event: ObjectEvent::StateChanged(state, new_state.contains(state)), - }); + events + .unbounded_send(Event::Object { + target: self.id(), + event: ObjectEvent::StateChanged(state, new_state.contains(state)), + }) + .unwrap(); } } - fn enqueue_property_changes(&self, queue: &mut Vec, old: &NodeWrapper) { + fn notify_property_changes(&self, events: &UnboundedSender, old: &NodeWrapper) { let name = self.name(); if name != old.name() { - queue.push(QueuedEvent::Object { - target: self.id(), - event: ObjectEvent::PropertyChanged(Property::Name(name)), - }); + events + .unbounded_send(Event::Object { + target: self.id(), + event: ObjectEvent::PropertyChanged(Property::Name(name)), + }) + .unwrap(); } let description = self.description(); if description != old.description() { - queue.push(QueuedEvent::Object { - target: self.id(), - event: ObjectEvent::PropertyChanged(Property::Description(description)), - }); + events + .unbounded_send(Event::Object { + target: self.id(), + event: ObjectEvent::PropertyChanged(Property::Description(description)), + }) + .unwrap(); } let parent_id = self.parent_id(); if parent_id != old.parent_id() { - queue.push(QueuedEvent::Object { - target: self.id(), - event: ObjectEvent::PropertyChanged(Property::Parent(self.filtered_parent())), - }); + events + .unbounded_send(Event::Object { + target: self.id(), + event: ObjectEvent::PropertyChanged(Property::Parent(self.filtered_parent())), + }) + .unwrap(); } let role = self.role(); if role != old.role() { - queue.push(QueuedEvent::Object { - target: self.id(), - event: ObjectEvent::PropertyChanged(Property::Role(role)), - }); + events + .unbounded_send(Event::Object { + target: self.id(), + event: ObjectEvent::PropertyChanged(Property::Role(role)), + }) + .unwrap(); } if let Some(value) = self.current_value() { if Some(value) != old.current_value() { - queue.push(QueuedEvent::Object { - target: self.id(), - event: ObjectEvent::PropertyChanged(Property::Value(value)), - }); + events + .unbounded_send(Event::Object { + target: self.id(), + event: ObjectEvent::PropertyChanged(Property::Value(value)), + }) + .unwrap(); } } } - fn enqueue_bounds_changes( + fn notify_bounds_changes( &self, window_bounds: &WindowBounds, - queue: &mut Vec, + events: &UnboundedSender, old: &NodeWrapper, ) { if self.raw_bounds_and_transform() != old.raw_bounds_and_transform() { - queue.push(QueuedEvent::Object { - target: self.id(), - event: ObjectEvent::BoundsChanged(self.extents(window_bounds)), - }); + events + .unbounded_send(Event::Object { + target: self.id(), + event: ObjectEvent::BoundsChanged(self.extents(window_bounds)), + }) + .unwrap(); } } - fn enqueue_children_changes(&self, queue: &mut Vec, old: &NodeWrapper) { + fn notify_children_changes(&self, events: &UnboundedSender, old: &NodeWrapper) { let old_children = old.child_ids().collect::>(); let filtered_children = self.filtered_child_ids().collect::>(); for (index, child) in filtered_children.iter().enumerate() { if !old_children.contains(child) { - queue.push(QueuedEvent::Object { - target: self.id(), - event: ObjectEvent::ChildAdded(index, ObjectRef::from(*child)), - }); + events + .unbounded_send(Event::Object { + target: self.id(), + event: ObjectEvent::ChildAdded(index, ObjectRef::from(*child)), + }) + .unwrap(); } } for child in old_children.into_iter() { if !filtered_children.contains(&child) { - queue.push(QueuedEvent::Object { - target: self.id(), - event: ObjectEvent::ChildRemoved(child.into()), - }); + events + .unbounded_send(Event::Object { + target: self.id(), + event: ObjectEvent::ChildRemoved(child.into()), + }) + .unwrap(); } } } diff --git a/platforms/winit/src/platform_impl/unix.rs b/platforms/winit/src/platform_impl/unix.rs index a589178ff..3a0d1c0d8 100644 --- a/platforms/winit/src/platform_impl/unix.rs +++ b/platforms/winit/src/platform_impl/unix.rs @@ -34,13 +34,13 @@ impl Adapter { pub fn update(&self, update: TreeUpdate) { if let Some(adapter) = &self.adapter { - adapter.update(update).raise(); + adapter.update(update); } } pub fn update_if_active(&self, updater: impl FnOnce() -> TreeUpdate) { if let Some(adapter) = &self.adapter { - adapter.update(updater()).raise(); + adapter.update(updater()); } } } From 79776dcd5e9d940cb46e410ba23467cbd3c2ea38 Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 4 Jan 2023 22:39:03 +0100 Subject: [PATCH 67/69] Chop the dependency tree a bit --- Cargo.lock | 90 ++++++++++++++--------------------- platforms/unix/Cargo.toml | 5 +- platforms/unix/src/adapter.rs | 30 ++++++------ platforms/unix/src/node.rs | 30 ++++++------ 4 files changed, 67 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38c3ea83b..71cc73336 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,8 +37,9 @@ version = "0.1.0" dependencies = [ "accesskit", "accesskit_consumer", + "async-channel", "atspi", - "futures", + "futures-lite", "parking_lot", "serde", "zbus", @@ -200,17 +201,34 @@ dependencies = [ [[package]] name = "atspi" -version = "0.6.0" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49d470388c8f9366e00ba7baa5c943ee3b27d56b4c8e12a1dabc4e7449e3bb5b" +checksum = "ab84c09a770065868da0d713f1f4b35af85d96530a868f1c1a6c249178379187" dependencies = [ "async-recursion", "async-trait", + "atspi-macros", "enumflags2", - "futures", + "futures-lite", "serde", "tracing", "zbus", + "zbus_names", +] + +[[package]] +name = "atspi-macros" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3ebc5a6f61f6996eca56a4cece7b3fe7da3b86f0473c7b71ab44e229f3acce4" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn", + "zbus", + "zbus_names", + "zvariant", ] [[package]] @@ -735,48 +753,12 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "futures" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" -dependencies = [ - "futures-core", - "futures-sink", -] - [[package]] name = "futures-core" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" -[[package]] -name = "futures-executor" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.25" @@ -798,17 +780,6 @@ dependencies = [ "waker-fn", ] -[[package]] -name = "futures-macro" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "futures-sink" version = "0.3.25" @@ -827,13 +798,9 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ - "futures-channel", "futures-core", - "futures-io", - "futures-macro", "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1483,6 +1450,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-xml-rs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0bf1ba0696ccf0872866277143ff1fd14d22eec235d2b23702f95e6660f7dfa" +dependencies = [ + "log", + "serde", + "thiserror", + "xml-rs", +] + [[package]] name = "serde_derive" version = "1.0.126" @@ -2172,6 +2151,7 @@ dependencies = [ "ordered-stream", "rand", "serde", + "serde-xml-rs", "serde_repr", "sha1", "static_assertions", diff --git a/platforms/unix/Cargo.toml b/platforms/unix/Cargo.toml index 2cdfd739c..839af5671 100644 --- a/platforms/unix/Cargo.toml +++ b/platforms/unix/Cargo.toml @@ -13,8 +13,9 @@ edition = "2021" [dependencies] accesskit = { version = "0.8.1", path = "../../common" } accesskit_consumer = { version = "0.11.0", path = "../../consumer" } -atspi = "0.6.0" -futures = "0.3" +async-channel = "1.8.0" +atspi = "0.8.7" +futures-lite = "1.12.0" parking_lot = "0.12.1" serde = "1.0" zbus = "3.6" diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index f8026c924..4ea93e6a4 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -16,11 +16,9 @@ use crate::{ }; use accesskit::{kurbo::Rect, ActionHandler, NodeId, Role, TreeUpdate}; use accesskit_consumer::{DetachedNode, FilterResult, Node, Tree, TreeChangeHandler, TreeState}; +use async_channel::{Receiver, Sender}; use atspi::{Interface, InterfaceSet, State}; -use futures::{ - channel::mpsc::{self, UnboundedReceiver, UnboundedSender}, - StreamExt, -}; +use futures_lite::StreamExt; use parking_lot::RwLock; use std::sync::Arc; use zbus::Task; @@ -28,7 +26,7 @@ use zbus::Task; pub struct Adapter { atspi_bus: Bus, _event_task: Task<()>, - events: UnboundedSender, + events: Sender, _app_context: Arc>, root_window_bounds: Arc>, tree: Arc, @@ -43,7 +41,7 @@ impl Adapter { action_handler: Box, ) -> Option { let mut atspi_bus = Bus::a11y_bus()?; - let (event_sender, event_receiver) = mpsc::unbounded(); + let (event_sender, event_receiver) = async_channel::unbounded(); let atspi_bus_copy = atspi_bus.clone(); let event_task = atspi_bus.connection().inner().executor().spawn( async move { @@ -171,7 +169,7 @@ impl Adapter { let node = NodeWrapper::DetachedNode(node); self.adapter .events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Defunct, true), }) @@ -236,7 +234,7 @@ impl Adapter { if let Some(node) = new_node.map(NodeWrapper::Node) { self.adapter .events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, true), }) @@ -245,7 +243,7 @@ impl Adapter { if let Some(node) = old_node.map(NodeWrapper::DetachedNode) { self.adapter .events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: node.id(), event: ObjectEvent::StateChanged(State::Focused, false), }) @@ -265,32 +263,32 @@ impl Adapter { self.tree.update_and_process_changes(update, &mut handler); } - fn window_activated(&self, window: &NodeWrapper, events: &UnboundedSender) { + fn window_activated(&self, window: &NodeWrapper, events: &Sender) { events - .unbounded_send(Event::Window { + .send_blocking(Event::Window { target: window.id(), name: window.name(), event: WindowEvent::Activated, }) .unwrap(); events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: window.id(), event: ObjectEvent::StateChanged(State::Active, true), }) .unwrap(); } - fn window_deactivated(&self, window: &NodeWrapper, events: &UnboundedSender) { + fn window_deactivated(&self, window: &NodeWrapper, events: &Sender) { events - .unbounded_send(Event::Window { + .send_blocking(Event::Window { target: window.id(), name: window.name(), event: WindowEvent::Deactivated, }) .unwrap(); events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: window.id(), event: ObjectEvent::StateChanged(State::Active, false), }) @@ -308,7 +306,7 @@ fn root_window(current_state: &TreeState) -> Option { } } -async fn handle_events(bus: Bus, mut events: UnboundedReceiver) { +async fn handle_events(bus: Bus, mut events: Receiver) { while let Some(event) = events.next().await { let _ = match event { Event::Object { target, event } => bus.emit_object_event(target, event).await, diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 90919a76d..3baad926b 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -20,11 +20,11 @@ use accesskit::{ CheckedState, DefaultActionVerb, NodeId, Role, }; use accesskit_consumer::{DetachedNode, FilterResult, Node, NodeState, Tree, TreeState}; +use async_channel::Sender; use atspi::{ accessible::Role as AtspiRole, component::Layer, CoordType, Interface, InterfaceSet, State, StateSet, }; -use futures::channel::mpsc::UnboundedSender; use parking_lot::RwLock; use std::{ iter::FusedIterator, @@ -481,7 +481,7 @@ impl<'a> NodeWrapper<'a> { pub fn notify_changes( &self, window_bounds: &WindowBounds, - events: &UnboundedSender, + events: &Sender, old: &NodeWrapper, ) { self.notify_state_changes(events, old); @@ -490,13 +490,13 @@ impl<'a> NodeWrapper<'a> { self.notify_children_changes(events, old); } - fn notify_state_changes(&self, events: &UnboundedSender, old: &NodeWrapper) { + fn notify_state_changes(&self, events: &Sender, old: &NodeWrapper) { let old_state = old.state(); let new_state = self.state(); let changed_states = old_state ^ new_state; for state in changed_states.iter() { events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: self.id(), event: ObjectEvent::StateChanged(state, new_state.contains(state)), }) @@ -504,11 +504,11 @@ impl<'a> NodeWrapper<'a> { } } - fn notify_property_changes(&self, events: &UnboundedSender, old: &NodeWrapper) { + fn notify_property_changes(&self, events: &Sender, old: &NodeWrapper) { let name = self.name(); if name != old.name() { events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: self.id(), event: ObjectEvent::PropertyChanged(Property::Name(name)), }) @@ -517,7 +517,7 @@ impl<'a> NodeWrapper<'a> { let description = self.description(); if description != old.description() { events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: self.id(), event: ObjectEvent::PropertyChanged(Property::Description(description)), }) @@ -526,7 +526,7 @@ impl<'a> NodeWrapper<'a> { let parent_id = self.parent_id(); if parent_id != old.parent_id() { events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: self.id(), event: ObjectEvent::PropertyChanged(Property::Parent(self.filtered_parent())), }) @@ -535,7 +535,7 @@ impl<'a> NodeWrapper<'a> { let role = self.role(); if role != old.role() { events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: self.id(), event: ObjectEvent::PropertyChanged(Property::Role(role)), }) @@ -544,7 +544,7 @@ impl<'a> NodeWrapper<'a> { if let Some(value) = self.current_value() { if Some(value) != old.current_value() { events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: self.id(), event: ObjectEvent::PropertyChanged(Property::Value(value)), }) @@ -556,12 +556,12 @@ impl<'a> NodeWrapper<'a> { fn notify_bounds_changes( &self, window_bounds: &WindowBounds, - events: &UnboundedSender, + events: &Sender, old: &NodeWrapper, ) { if self.raw_bounds_and_transform() != old.raw_bounds_and_transform() { events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: self.id(), event: ObjectEvent::BoundsChanged(self.extents(window_bounds)), }) @@ -569,13 +569,13 @@ impl<'a> NodeWrapper<'a> { } } - fn notify_children_changes(&self, events: &UnboundedSender, old: &NodeWrapper) { + fn notify_children_changes(&self, events: &Sender, old: &NodeWrapper) { let old_children = old.child_ids().collect::>(); let filtered_children = self.filtered_child_ids().collect::>(); for (index, child) in filtered_children.iter().enumerate() { if !old_children.contains(child) { events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: self.id(), event: ObjectEvent::ChildAdded(index, ObjectRef::from(*child)), }) @@ -585,7 +585,7 @@ impl<'a> NodeWrapper<'a> { for child in old_children.into_iter() { if !filtered_children.contains(&child) { events - .unbounded_send(Event::Object { + .send_blocking(Event::Object { target: self.id(), event: ObjectEvent::ChildRemoved(child.into()), }) From 01b48aade2963d263db46b8b0b8302638404342c Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 4 Jan 2023 23:10:50 +0100 Subject: [PATCH 68/69] Split adapter initialization method --- platforms/unix/src/adapter.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index 4ea93e6a4..ff209d41e 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -66,8 +66,12 @@ impl Adapter { root_window_bounds: Arc::new(RwLock::new(WindowBounds::default())), tree, }; - { - let reader = adapter.tree.read(); + adapter.register_tree(); + Some(adapter) + } + + fn register_tree(&self) { + let reader = self.tree.read(); let mut objects_to_add = Vec::new(); fn add_children(node: Node<'_>, to_add: &mut Vec) { @@ -81,12 +85,10 @@ impl Adapter { add_children(reader.root(), &mut objects_to_add); for id in objects_to_add { let interfaces = NodeWrapper::Node(&reader.node_by_id(id).unwrap()).interfaces(); - adapter - .register_interfaces(&adapter.tree, id, interfaces) + self + .register_interfaces(&self.tree, id, interfaces) .unwrap(); } - } - Some(adapter) } fn register_interfaces( From 55ea3c9b643b7b26febfaae0cb26eec6e65f0a9d Mon Sep 17 00:00:00 2001 From: DataTriny Date: Wed, 4 Jan 2023 23:14:57 +0100 Subject: [PATCH 69/69] Add some documentation on public items --- platforms/unix/src/adapter.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index ff209d41e..d9fca322d 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -33,6 +33,7 @@ pub struct Adapter { } impl Adapter { + /// Create a new Unix adapter. pub fn new( app_name: String, toolkit_name: String, @@ -71,24 +72,23 @@ impl Adapter { } fn register_tree(&self) { - let reader = self.tree.read(); - let mut objects_to_add = Vec::new(); + let reader = self.tree.read(); + let mut objects_to_add = Vec::new(); - fn add_children(node: Node<'_>, to_add: &mut Vec) { - for child in node.filtered_children(&filter) { - to_add.push(child.id()); - add_children(child, to_add); - } + fn add_children(node: Node<'_>, to_add: &mut Vec) { + for child in node.filtered_children(&filter) { + to_add.push(child.id()); + add_children(child, to_add); } + } - objects_to_add.push(reader.root().id()); - add_children(reader.root(), &mut objects_to_add); - for id in objects_to_add { - let interfaces = NodeWrapper::Node(&reader.node_by_id(id).unwrap()).interfaces(); - self - .register_interfaces(&self.tree, id, interfaces) - .unwrap(); - } + objects_to_add.push(reader.root().id()); + add_children(reader.root(), &mut objects_to_add); + for id in objects_to_add { + let interfaces = NodeWrapper::Node(&reader.node_by_id(id).unwrap()).interfaces(); + self.register_interfaces(&self.tree, id, interfaces) + .unwrap(); + } } fn register_interfaces( @@ -155,6 +155,7 @@ impl Adapter { bounds.inner = inner; } + /// Apply the provided update to the tree. pub fn update(&self, update: TreeUpdate) { struct Handler<'a> { adapter: &'a Adapter,