Read what an app shows
Take the rows of a list, the whole text of a document, the links and the images out of any app's window, and run a macro when an element changes.
Switchboard can already press buttons in another app. These steps point the same view of the app, its accessibility tree, at reading. They work on any Mac app, with no scripting support needed from it, and they only read: nothing in the app changes.
| To get | Use | You get |
|---|---|---|
| The rows of a list, table or outline | Read the rows of an app's list | A list of rows, each with named, typed fields |
| All the text of a document, note, field or terminal | Read all the text of an app's document | The text, however long |
| Every link in a window | Read the links of an app's window | Titles and addresses |
| The images in a window | Read or save the images of an app's window | Descriptions, and PNG files if you ask |
| A macro that runs when an element changes | An element in an app changes | The old and new text, the new rows, or the selection |
The first time, the macros need the Accessibility permission, and saving images needs Screen Recording. The steps tell you if one is missing.
Rows #
With Mail in front, this saves every row of the message list:
- Read the rows of the list in Mail into res
- Save rows to “Inbox.csv”
Each row is a dictionary: {from: "Ada", subject: "Lunch on Friday?", date: "2026-10-26"}. Columns are named from the table's header; a list without one gets c0, c1. A column whose every value reads as a date or a number is typed that way. fields shapes the columns by hand, and uses the same recipe as Scrape a page into rows: at, what, regex, as, all, default. So a recipe reads the same whether it came from a web page or from a window.
With no locator the step reads the biggest table, outline or list in the front window, and its note says which one. To read a particular one, give it a locator, or click Pick rows… on the step in the editor and click any row of the list: the preview shows the first three rows as a table, and renaming a column header renames the field.
Lists that only hold what is on screen #
Some lists (SwiftUI lazy lists, web tables) only have the visible rows. The result then says truncated and why. scroll: true reads a page, scrolls to the last row, reads again and so on until nothing new appears, then puts the scroll position back. The window moves while it runs. Give the rows a key so a row on two pages counts once.
Text #
- Read all the text of the front window into doc
- Copy “{{ doc }}” to the clipboard
Read field value stops at 200 characters; Read all the text of an app's document does not. range: visible reads what is on screen, range: selected the selection. A terminal's screen comes as the text. Password fields are refused.
Links and images #
Read the links of an app's window lists each address once, top to bottom. Read or save the images of an app's window lists images with their descriptions; with save it takes one screenshot of the window and crops each image that is fully in view, so what covers the window ends up in the picture too.
Watch an element #
- Processes text in ActivityMonitor changes
- Show “{{ old }} → {{ new }}” for 3 s
what: rows runs once a new row lands in a list (with the new rows as added), what: selection when the selection moves. A field you type into fires once you stop typing.
When there is nothing to read #
An app can describe nothing: a game, a drawing canvas, an Electron app before accessibility is on (Switchboard switches it on for Chromium and Electron apps by itself). The step then says the window has no table or text to read. For text drawn on screen, Read text on screen reads pixels instead. For a web page in your browser, Scrape a page into rows reads the page itself and is the better tool.
Examples that come with it #
The Read apps group holds five macros to copy from, all switched off until you turn them on: Inbox to CSV, Copy all links, Save images here, Copy all the text, and Watch this label.