Skip to content
Portal Control Protocol

Element

An Element is a single UI component drawn from the AT-SPI2 accessibility tree. Buttons, text fields, menu items, sliders, tables, and every other widget all become Element primitives. For applications without AT-SPI2 support, the compositor generates fallback elements from surface metadata.

Elements form a tree. Each element has a parent (except roots) and zero or more children. The root of the tree for a given surface is referenced by Surface.atspi2_root. When System Intelligence needs to click a button or read text from a field, it addresses the target by element ID.

{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Element",
"type": "object",
"required": ["id", "surface_id", "app_id", "role", "states", "geometry", "actions"],
"properties": {
"id": {
"type": "string",
"description": "atspi:{bus}:{path} | surf:... | native:..."
},
"surface_id": { "type": "string" },
"app_id": { "type": "string" },
"role": {
"type": "string",
"description": "AtspiRole name. See the role registry below."
},
"name": {
"type": ["string", "null"],
"description": "Accessible name (label). Null for decorative elements."
},
"description": {
"type": ["string", "null"],
"description": "Accessible description, supplementary to name."
},
"states": {
"type": "array",
"items": { "type": "string" },
"description": "Current AT-SPI2 states."
},
"geometry": {
"type": "object",
"required": ["x", "y", "w", "h"],
"properties": {
"x": { "type": "integer" },
"y": { "type": "integer" },
"w": { "type": "integer", "minimum": 0 },
"h": { "type": "integer", "minimum": 0 }
}
},
"parent": {
"type": ["string", "null"],
"description": "Parent element ID. Null for root elements."
},
"children": {
"type": "array",
"items": { "type": "string" },
"description": "Child element IDs."
},
"actions": {
"type": "array",
"items": { "type": "string" },
"description": "AT-SPI2 supported actions."
},
"text": {
"type": ["string", "null"],
"description": "Current text content for text/entry/document roles."
},
"text_selection": {
"type": ["object", "null"],
"properties": {
"start": { "type": "integer" },
"end": { "type": "integer" },
"text": { "type": "string" }
},
"description": "Current text selection, if any."
},
"caret_position": {
"type": ["integer", "null"],
"description": "Current caret position in text content."
},
"value": {
"type": ["object", "null"],
"properties": {
"current": { "type": "number" },
"minimum": { "type": "number" },
"maximum": { "type": "number" },
"step": { "type": ["number", "null"] },
"text": { "type": ["string", "null"] }
},
"description": "Current value for slider/progress/spin_button/checkbox roles."
},
"table_info": {
"type": ["object", "null"],
"properties": {
"rows": { "type": "integer" },
"columns": { "type": "integer" },
"selected_rows": { "type": "array", "items": { "type": "integer" } },
"selected_columns": { "type": "array", "items": { "type": "integer" } }
},
"description": "Table/grid metadata for table roles."
},
"relationships": {
"type": "object",
"properties": {
"labelled_by": { "type": ["string", "null"] },
"label_for": { "type": ["string", "null"] },
"controller_of": { "type": ["string", "null"] },
"controlled_by": { "type": ["string", "null"] },
"member_of": { "type": ["array", "null"], "items": { "type": "string" } }
}
},
"fallback_note": {
"type": ["string", "null"],
"description": "Set on compositor-only fallback elements. Describes the limitation."
}
}
}

Element IDs carry a source prefix that indicates where the element came from:

Prefix Source Description
atspi:{bus}:{path} AT-SPI2 tree Standard toolkit applications (GTK, Qt). The daemon’s element auto-resolution splits on colons to recover the D-Bus path.
surf:... Compositor fallback Apps without accessibility support. The compositor generates these from surface metadata.
native:... Tier 1 app-defined Applications that define their own element IDs through the PcpServer trait.

The atspi: prefix is the most common. The bus and path components come from the D-Bus accessible object path in the AT-SPI2 registry.

Every element has exactly one role. The role determines what actions are available, what additional fields are populated, and what capability actions the Tier 2 adapter derives. The table below lists all 69 roles from core::element::role::AtspiRole along with the Tier 2 derived actions.

Role Tier 2 derives
Application structural root
Window, Frame, Dialog, Alert focus, close, minimize, maximize, activate
Panel, ScrollPane, Section, Embedded, Unknown structural
Role Tier 2 derives
PushButton, ToggleButton click
CheckBox, RadioButton click, set_value
ComboBox, SpinButton, Slider, Dial, ScrollBar set_value
PageTab, PageTabList click
Role Tier 2 derives
Entry, Text, Paragraph, DocumentText set_text, get_text
Heading, Label, Static, Icon, Image get_text (readable content)
DocumentWeb, DocumentSpreadsheet, DocumentPresentation, DocumentEmail get_text
Link click
Terminal get_text
Role Tier 2 derives
List, ListBox, ListItem, Tree, TreeTable, TreeItem structural (plus selection states)
Table, TableCell, TableColumnHeader, TableRowHeader, Grid get_text (cells)
Role Tier 2 derives
MenuBar, Menu, MenuItem, CheckMenuItem, RadioMenuItem, Separator click
Role Tier 2 derives
Canvas, Animation, Chart structural
ProgressBar get_value (via states)
StatusBar, ToolBar, ToolTip structural
Calendar, ColorChooser, FileChooser, FontChooser click, set_value (inputs)

Any role may additionally expose toolkit-declared actions, forwarded verbatim into Element.actions and invokable through the adapter’s method-aware execution path.

An element can have any combination of states. The states array reflects the current runtime state of the widget.

Common states include: enabled, disabled, visible, invisible, showing, hidden, focusable, focused, selected, selectable, checked, unchecked, indeterminate, editable, read_only, expandable, expanded, collapsed, multiselectable, required, invalid_entry, supports_autocompletion, transient, vertical, horizontal, modal, multiline, protected, stale, busy, resizable, movable.

States are strings in the wire format and enum variants in the Rust type. The two representations are equivalent.

pub struct ElementRelationships {
pub labelled_by: Option<ElementId>, // another element provides this label
pub label_for: Option<ElementId>, // this element labels another
pub controller_of: Option<ElementId>, // this element controls another's state
pub controlled_by: Option<ElementId>, // this element's state is controlled
pub member_of: Option<Vec<ElementId>>, // belongs to a named group
}

Relationships express semantic connections beyond the parent/child tree structure. A slider might have a label_for pointing to the text field it controls, for example.

pub struct TableInfo {
pub rows: usize,
pub columns: usize,
pub selected_rows: Vec<usize>,
pub selected_columns: Vec<usize>,
}

Populated only for Table, Grid, and TreeTable roles. Tracks dimensions and current selections.

pub struct TextSelection {
pub start: usize,
pub end: usize,
pub text: String,
}

Character offsets into the element’s text field. Only populated when a selection exists on text-entry and document roles.

pub struct ValueInfo {
pub current: f64,
pub minimum: f64,
pub maximum: f64,
pub step: Option<f64>,
pub text: Option<String>,
}

Populated for roles that represent a scalar value: Slider, ProgressBar, SpinButton, CheckBox.

{
"id": "atspi:::0.5:12/document/push_button",
"surface_id": "surf:12-w1",
"app_id": "com.example.present",
"role": "push_button",
"name": "Add Slide",
"description": "Insert a new slide at the end of the deck",
"states": ["enabled", "visible", "focusable"],
"geometry": { "x": 150, "y": 598, "w": 120, "h": 36 },
"parent": "atspi:::0.5:12/document/toolbar",
"children": [],
"actions": ["activate"],
"text": null,
"text_selection": null,
"caret_position": null,
"value": null,
"table_info": null,
"relationships": {
"labelled_by": null,
"label_for": null,
"controller_of": null,
"controlled_by": null,
"member_of": null
},
"fallback_note": null
}

This is a fully accessible button from the AT-SPI2 tree. It has one supported action (activate) and is currently enabled, visible, and focusable. The atspi: prefix in the ID means the daemon can split on colons to recover the D-Bus path for method-aware execution.

When an application has no AT-SPI2 support, the compositor generates fallback elements with the surf: prefix. These carry a fallback_note describing the limitation. Available actions are limited to basic window management (focus, close, minimize, maximize). Interacting with anything inside a fallback surface requires input simulation rather than AT-SPI2 action invocation.

Last updated: