use-computer-cli 0.1.1__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/PKG-INFO +47 -13
  2. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/README.md +42 -12
  3. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/pyproject.toml +24 -2
  4. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/__init__.py +51 -0
  5. use_computer_cli-0.2.0/src/use_computer/accessibility/__init__.py +51 -0
  6. use_computer_cli-0.2.0/src/use_computer/accessibility/atspi.py +293 -0
  7. use_computer_cli-0.2.0/src/use_computer/accessibility/ax.py +225 -0
  8. use_computer_cli-0.2.0/src/use_computer/accessibility/base.py +76 -0
  9. use_computer_cli-0.2.0/src/use_computer/accessibility/roles.py +248 -0
  10. use_computer_cli-0.2.0/src/use_computer/accessibility/uia.py +210 -0
  11. use_computer_cli-0.2.0/src/use_computer/actions.py +345 -0
  12. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/cli.py +311 -14
  13. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/config.py +14 -0
  14. use_computer_cli-0.2.0/src/use_computer/errors.py +124 -0
  15. use_computer_cli-0.2.0/src/use_computer/render.py +84 -0
  16. use_computer_cli-0.2.0/src/use_computer/runner.py +623 -0
  17. use_computer_cli-0.2.0/src/use_computer/selectors.py +304 -0
  18. use_computer_cli-0.2.0/src/use_computer/skill/SKILL.md +290 -0
  19. use_computer_cli-0.2.0/src/use_computer/tree.py +287 -0
  20. use_computer_cli-0.2.0/tests/fake_provider.py +140 -0
  21. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/test_cli.py +130 -0
  22. use_computer_cli-0.2.0/tests/test_elements.py +450 -0
  23. use_computer_cli-0.2.0/tests/test_render.py +54 -0
  24. use_computer_cli-0.2.0/tests/test_selectors.py +153 -0
  25. use_computer_cli-0.1.1/src/use_computer/actions.py +0 -185
  26. use_computer_cli-0.1.1/src/use_computer/errors.py +0 -53
  27. use_computer_cli-0.1.1/src/use_computer/runner.py +0 -298
  28. use_computer_cli-0.1.1/src/use_computer/skill/SKILL.md +0 -135
  29. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/.gitignore +0 -0
  30. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/LICENSE +0 -0
  31. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/backends/__init__.py +0 -0
  32. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/backends/base.py +0 -0
  33. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/backends/local.py +0 -0
  34. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/backends/vnc.py +0 -0
  35. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/compare.py +0 -0
  36. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/coordinates.py +0 -0
  37. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/keys.py +0 -0
  38. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/src/use_computer/skill/__init__.py +0 -0
  39. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/__init__.py +0 -0
  40. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/conftest.py +0 -0
  41. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/fake_backend.py +0 -0
  42. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/test_backends.py +0 -0
  43. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/test_compare.py +0 -0
  44. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/test_config.py +0 -0
  45. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/test_config_init.py +0 -0
  46. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/test_coordinates.py +0 -0
  47. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/test_keys.py +0 -0
  48. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/test_runner.py +0 -0
  49. {use_computer_cli-0.1.1 → use_computer_cli-0.2.0}/tests/test_skill.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: use-computer-cli
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Execute input on a screen for computer-use agents: move, click, drag, scroll, type, key, screenshot.
5
5
  Project-URL: Homepage, https://github.com/applica-software-guru/use-computer
6
6
  Author: Bruno Fortunato
@@ -50,19 +50,33 @@ Requires-Dist: ruff>=0.6; extra == 'dev'
50
50
  Provides-Extra: local
51
51
  Requires-Dist: mss>=10; extra == 'local'
52
52
  Requires-Dist: pynput>=1.8; extra == 'local'
53
+ Provides-Extra: tree
54
+ Requires-Dist: pygobject>=3.46; (sys_platform == 'linux') and extra == 'tree'
55
+ Requires-Dist: pyobjc-framework-applicationservices>=10; (sys_platform == 'darwin') and extra == 'tree'
56
+ Requires-Dist: uiautomation>=2.0; (sys_platform == 'win32') and extra == 'tree'
53
57
  Provides-Extra: vnc
54
58
  Requires-Dist: vncdotool>=1.3; extra == 'vnc'
55
59
  Description-Content-Type: text/markdown
56
60
 
57
61
  # use-computer
58
62
 
59
- Executes input on a screen for computer-use agents: move, click, double-click, right-click,
60
- drag, scroll, type text, press key combinations, and capture a screenshot.
63
+ Reads and acts on a screen for computer-use agents. It reads the **accessibility tree** the
64
+ operating system already maintains -- roles, names, states and clickable boxes -- and it moves the
65
+ mouse, clicks, drags, scrolls, types text, presses key combinations, and captures screenshots.
61
66
 
62
- `use-computer` is the **acting** half of a pair. [ui-locator](https://github.com/applica-software-guru/ui-locator)
63
- answers *where* the Invia button is and returns pixel coordinates; `use-computer` performs the
64
- click there. Both are driven by another AI agent through a CLI that emits JSON on stdout and
65
- diagnostics on stderr, with a Python API underneath.
67
+ Every interaction takes the highest rung it can reach:
68
+
69
+ 1. **Element, through the platform API** -- the OS presses the button itself. No coordinates, so
70
+ nothing to aim and no scale to get wrong.
71
+ 2. **Element, by coordinate** -- the tree can see the control but exposes no way to operate it, so
72
+ `use-computer` clicks its centre and tells you it did.
73
+ 3. **Pixel, from vision** -- the tree cannot see it. Screenshot,
74
+ [ui-locator](https://github.com/applica-software-guru/ui-locator), click those pixels.
75
+
76
+ Each rung is cheaper, faster and more accurate than the one below. Rung three is the floor the
77
+ whole ladder stands on and is not going anywhere; it is simply no longer the only rung. Everything
78
+ is driven by another AI agent through a CLI that emits JSON on stdout and diagnostics on stderr,
79
+ with a Python API underneath.
66
80
 
67
81
  ## Install
68
82
 
@@ -70,9 +84,16 @@ diagnostics on stderr, with a Python API underneath.
70
84
  pip install use-computer-cli # no backend
71
85
  pip install "use-computer-cli[local]" # drive this machine's display (pynput + mss)
72
86
  pip install "use-computer-cli[vnc]" # drive a remote framebuffer over RFB (vncdotool)
87
+ pip install "use-computer-cli[tree]" # read the accessibility tree
73
88
  ```
74
89
 
75
- Backends are optional extras, imported lazily, so the package installs without them.
90
+ Backends and the accessibility bindings are optional extras, imported lazily, so the package
91
+ installs without them.
92
+
93
+ The `tree` extra installs one binding per platform: `uiautomation` on Windows,
94
+ `pyobjc-framework-ApplicationServices` on macOS, PyGObject on Linux. **Linux needs a system package
95
+ as well** -- there is no `pyatspi` on PyPI, so the AT-SPI bindings come from `gir1.2-atspi-2.0`
96
+ (plus `python3-pyatspi` on Debian and Ubuntu). The error names both halves when one is missing.
76
97
 
77
98
  The distribution is `use-computer-cli` because `use-computer` is taken on PyPI by an unrelated
78
99
  project. The command it installs is `use-computer`, and the package it imports is `use_computer`.
@@ -80,16 +101,28 @@ project. The command it installs is `use-computer`, and the package it imports i
80
101
  ## Use
81
102
 
82
103
  ```bash
83
- use-computer click --x 120 --y 340 --use staging
104
+ use-computer tree --use laptop # what is on screen, structurally
105
+ use-computer click --role button --name "Invia" # act on it by name
106
+ use-computer set-value --role text --name "Email" --value "mario@example.com"
107
+ use-computer click --x 120 --y 340 --use staging # or by pixel, when the tree cannot see it
84
108
  use-computer type --text "hello" --use staging
85
109
  use-computer key ctrl+s --use staging
86
- use-computer screenshot --use laptop # writes a file, returns its path
110
+ use-computer screenshot --use laptop # writes a file, returns its path
87
111
 
88
112
  # a batch runs over one connection -- the default command, so `batch` may be omitted
89
- echo '[{"action":"click","x":120,"y":340},{"action":"key","combo":"enter"}]' \
90
- | use-computer - --use staging --verify
113
+ echo '[
114
+ {"action":"focus","role":"text","name":"Destinatario"},
115
+ {"action":"type","text":"mario@example.com"},
116
+ {"action":"click","role":"button","name":"Invia"},
117
+ {"action":"tree"}
118
+ ]' | use-computer - --use laptop
91
119
  ```
92
120
 
121
+ An action names its target by coordinate **or** by element, never both. A selector that matches
122
+ nothing comes back with a screenshot -- the signal to switch to vision. A selector that matches
123
+ several comes back with the candidates, because two buttons named "OK" in two dialogs is the
124
+ ordinary case and picking one silently fails a hundred runs later.
125
+
93
126
  stdout is one JSON object per run; every diagnostic goes to stderr. Exit codes: `0` success,
94
127
  `1` failure, `2` bad usage.
95
128
 
@@ -104,7 +137,8 @@ action and reports the path, so a verified action does not need a `screenshot` c
104
137
  - **Setup cost.** Opening a VNC connection dominates a single action, so one run performs a
105
138
  batch of actions over one connection.
106
139
  - **Blind actuation.** A click that lands on nothing looks exactly like a click that worked, so
107
- `--verify` compares the screen before and after and reports whether it changed.
140
+ `--verify` compares the screen before and after and reports whether it changed -- and an action
141
+ that went through the accessibility API reports the element it actually operated.
108
142
 
109
143
  ## Configure
110
144
 
@@ -1,12 +1,22 @@
1
1
  # use-computer
2
2
 
3
- Executes input on a screen for computer-use agents: move, click, double-click, right-click,
4
- drag, scroll, type text, press key combinations, and capture a screenshot.
3
+ Reads and acts on a screen for computer-use agents. It reads the **accessibility tree** the
4
+ operating system already maintains -- roles, names, states and clickable boxes -- and it moves the
5
+ mouse, clicks, drags, scrolls, types text, presses key combinations, and captures screenshots.
5
6
 
6
- `use-computer` is the **acting** half of a pair. [ui-locator](https://github.com/applica-software-guru/ui-locator)
7
- answers *where* the Invia button is and returns pixel coordinates; `use-computer` performs the
8
- click there. Both are driven by another AI agent through a CLI that emits JSON on stdout and
9
- diagnostics on stderr, with a Python API underneath.
7
+ Every interaction takes the highest rung it can reach:
8
+
9
+ 1. **Element, through the platform API** -- the OS presses the button itself. No coordinates, so
10
+ nothing to aim and no scale to get wrong.
11
+ 2. **Element, by coordinate** -- the tree can see the control but exposes no way to operate it, so
12
+ `use-computer` clicks its centre and tells you it did.
13
+ 3. **Pixel, from vision** -- the tree cannot see it. Screenshot,
14
+ [ui-locator](https://github.com/applica-software-guru/ui-locator), click those pixels.
15
+
16
+ Each rung is cheaper, faster and more accurate than the one below. Rung three is the floor the
17
+ whole ladder stands on and is not going anywhere; it is simply no longer the only rung. Everything
18
+ is driven by another AI agent through a CLI that emits JSON on stdout and diagnostics on stderr,
19
+ with a Python API underneath.
10
20
 
11
21
  ## Install
12
22
 
@@ -14,9 +24,16 @@ diagnostics on stderr, with a Python API underneath.
14
24
  pip install use-computer-cli # no backend
15
25
  pip install "use-computer-cli[local]" # drive this machine's display (pynput + mss)
16
26
  pip install "use-computer-cli[vnc]" # drive a remote framebuffer over RFB (vncdotool)
27
+ pip install "use-computer-cli[tree]" # read the accessibility tree
17
28
  ```
18
29
 
19
- Backends are optional extras, imported lazily, so the package installs without them.
30
+ Backends and the accessibility bindings are optional extras, imported lazily, so the package
31
+ installs without them.
32
+
33
+ The `tree` extra installs one binding per platform: `uiautomation` on Windows,
34
+ `pyobjc-framework-ApplicationServices` on macOS, PyGObject on Linux. **Linux needs a system package
35
+ as well** -- there is no `pyatspi` on PyPI, so the AT-SPI bindings come from `gir1.2-atspi-2.0`
36
+ (plus `python3-pyatspi` on Debian and Ubuntu). The error names both halves when one is missing.
20
37
 
21
38
  The distribution is `use-computer-cli` because `use-computer` is taken on PyPI by an unrelated
22
39
  project. The command it installs is `use-computer`, and the package it imports is `use_computer`.
@@ -24,16 +41,28 @@ project. The command it installs is `use-computer`, and the package it imports i
24
41
  ## Use
25
42
 
26
43
  ```bash
27
- use-computer click --x 120 --y 340 --use staging
44
+ use-computer tree --use laptop # what is on screen, structurally
45
+ use-computer click --role button --name "Invia" # act on it by name
46
+ use-computer set-value --role text --name "Email" --value "mario@example.com"
47
+ use-computer click --x 120 --y 340 --use staging # or by pixel, when the tree cannot see it
28
48
  use-computer type --text "hello" --use staging
29
49
  use-computer key ctrl+s --use staging
30
- use-computer screenshot --use laptop # writes a file, returns its path
50
+ use-computer screenshot --use laptop # writes a file, returns its path
31
51
 
32
52
  # a batch runs over one connection -- the default command, so `batch` may be omitted
33
- echo '[{"action":"click","x":120,"y":340},{"action":"key","combo":"enter"}]' \
34
- | use-computer - --use staging --verify
53
+ echo '[
54
+ {"action":"focus","role":"text","name":"Destinatario"},
55
+ {"action":"type","text":"mario@example.com"},
56
+ {"action":"click","role":"button","name":"Invia"},
57
+ {"action":"tree"}
58
+ ]' | use-computer - --use laptop
35
59
  ```
36
60
 
61
+ An action names its target by coordinate **or** by element, never both. A selector that matches
62
+ nothing comes back with a screenshot -- the signal to switch to vision. A selector that matches
63
+ several comes back with the candidates, because two buttons named "OK" in two dialogs is the
64
+ ordinary case and picking one silently fails a hundred runs later.
65
+
37
66
  stdout is one JSON object per run; every diagnostic goes to stderr. Exit codes: `0` success,
38
67
  `1` failure, `2` bad usage.
39
68
 
@@ -48,7 +77,8 @@ action and reports the path, so a verified action does not need a `screenshot` c
48
77
  - **Setup cost.** Opening a VNC connection dominates a single action, so one run performs a
49
78
  batch of actions over one connection.
50
79
  - **Blind actuation.** A click that lands on nothing looks exactly like a click that worked, so
51
- `--verify` compares the screen before and after and reports whether it changed.
80
+ `--verify` compares the screen before and after and reports whether it changed -- and an action
81
+ that went through the accessibility API reports the element it actually operated.
52
82
 
53
83
  ## Configure
54
84
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "use-computer-cli"
7
- version = "0.1.1"
7
+ version = "0.2.0"
8
8
  description = "Execute input on a screen for computer-use agents: move, click, drag, scroll, type, key, screenshot."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -38,6 +38,19 @@ dependencies = [
38
38
  # pyautogui is deliberately not used: its last release (0.9.54) dates from 2023.
39
39
  local = ["pynput>=1.8", "mss>=10"]
40
40
  vnc = ["vncdotool>=1.3"]
41
+ # Reading and operating the UI tree. One binding per platform, behind a marker.
42
+ #
43
+ # Linux needs a system package as well: there is no pyatspi on PyPI, so the AT-SPI bindings come
44
+ # from gir1.2-atspi-2.0 (plus python3-pyatspi on Debian/Ubuntu) and PyGObject reaches them through
45
+ # GObject Introspection. UITreeUnavailableError names both halves.
46
+ #
47
+ # Not dogtail (GPLv2 -- fine to run, wrong to import from a permissively licensed library) and not
48
+ # atomacos (last release 3.3.0, May 2021 -- the same staleness argument that ruled out pyautogui).
49
+ tree = [
50
+ "uiautomation>=2.0; sys_platform == 'win32'",
51
+ "pyobjc-framework-ApplicationServices>=10; sys_platform == 'darwin'",
52
+ "PyGObject>=3.46; sys_platform == 'linux'",
53
+ ]
41
54
  dev = [
42
55
  "pytest>=8",
43
56
  "pytest-mock>=3.12",
@@ -78,7 +91,16 @@ strict = true
78
91
  files = ["src", "tests"]
79
92
 
80
93
  [[tool.mypy.overrides]]
81
- module = ["pynput.*", "mss.*", "vncdotool.*", "ApplicationServices.*", "Quartz.*", "tomli"]
94
+ module = [
95
+ "pynput.*",
96
+ "mss.*",
97
+ "vncdotool.*",
98
+ "ApplicationServices.*",
99
+ "Quartz.*",
100
+ "gi.*",
101
+ "uiautomation.*",
102
+ "tomli",
103
+ ]
82
104
  ignore_missing_imports = true
83
105
 
84
106
  [tool.pytest.ini_options]
@@ -7,11 +7,15 @@ from __future__ import annotations
7
7
 
8
8
  from importlib.metadata import PackageNotFoundError, version
9
9
 
10
+ from use_computer.accessibility import AccessibilityProvider, create_provider
10
11
  from use_computer.actions import (
11
12
  Action,
12
13
  ClickAction,
14
+ CollapseAction,
13
15
  DoubleClickAction,
14
16
  DragAction,
17
+ ExpandAction,
18
+ FocusAction,
15
19
  KeyAction,
16
20
  MouseButton,
17
21
  MoveAction,
@@ -19,7 +23,13 @@ from use_computer.actions import (
19
23
  ScreenshotAction,
20
24
  ScrollAction,
21
25
  ScrollDirection,
26
+ SelectAction,
27
+ SetValueAction,
28
+ ShowMenuAction,
29
+ ToggleAction,
30
+ TreeAction,
22
31
  TypeAction,
32
+ WindowsAction,
23
33
  )
24
34
  from use_computer.backends import Backend, create_backend
25
35
  from use_computer.compare import ChangeReport, Screenshot, compare
@@ -28,15 +38,31 @@ from use_computer.config import load as load_config
28
38
  from use_computer.coordinates import Coordinate, CoordinateSpace, ScreenInfo, convert
29
39
  from use_computer.errors import (
30
40
  ActionFailedError,
41
+ ActionNotSupportedError,
42
+ AmbiguousNodeError,
31
43
  BackendNotAvailableError,
32
44
  ConfigError,
33
45
  CoordinateSpaceError,
34
46
  KeySyntaxError,
47
+ NodeNotFoundError,
35
48
  PermissionDeniedError,
49
+ UITreeUnavailableError,
36
50
  UseComputerError,
37
51
  )
38
52
  from use_computer.keys import KeyCombo, parse_combo
39
53
  from use_computer.runner import ActionResult, ErrorInfo, RunResult, Session, run_actions
54
+ from use_computer.tree import (
55
+ Box,
56
+ NodeSelector,
57
+ OutputFormat,
58
+ TreeReason,
59
+ TreeResult,
60
+ TreeScope,
61
+ UINode,
62
+ Via,
63
+ WindowInfo,
64
+ WindowsResult,
65
+ )
40
66
 
41
67
  #: The distribution name on PyPI, which differs from the import package: `use-computer` was
42
68
  #: already taken there. importlib.metadata is keyed by the distribution, so this is the name
@@ -50,14 +76,19 @@ except PackageNotFoundError: # pragma: no cover - source checkout without an in
50
76
 
51
77
  __all__ = [
52
78
  "DISTRIBUTION",
79
+ "AccessibilityProvider",
53
80
  "Action",
54
81
  "ActionFailedError",
82
+ "ActionNotSupportedError",
55
83
  "ActionResult",
84
+ "AmbiguousNodeError",
56
85
  "Backend",
57
86
  "BackendNotAvailableError",
58
87
  "BackendProfile",
88
+ "Box",
59
89
  "ChangeReport",
60
90
  "ClickAction",
91
+ "CollapseAction",
61
92
  "ConfigError",
62
93
  "Coordinate",
63
94
  "CoordinateSpace",
@@ -65,11 +96,16 @@ __all__ = [
65
96
  "DoubleClickAction",
66
97
  "DragAction",
67
98
  "ErrorInfo",
99
+ "ExpandAction",
100
+ "FocusAction",
68
101
  "KeyAction",
69
102
  "KeyCombo",
70
103
  "KeySyntaxError",
71
104
  "MouseButton",
72
105
  "MoveAction",
106
+ "NodeNotFoundError",
107
+ "NodeSelector",
108
+ "OutputFormat",
73
109
  "PermissionDeniedError",
74
110
  "ResolvedConfig",
75
111
  "RightClickAction",
@@ -79,14 +115,29 @@ __all__ = [
79
115
  "ScreenshotAction",
80
116
  "ScrollAction",
81
117
  "ScrollDirection",
118
+ "SelectAction",
82
119
  "Session",
120
+ "SetValueAction",
83
121
  "Settings",
122
+ "ShowMenuAction",
123
+ "ToggleAction",
124
+ "TreeAction",
125
+ "TreeReason",
126
+ "TreeResult",
127
+ "TreeScope",
84
128
  "TypeAction",
129
+ "UINode",
130
+ "UITreeUnavailableError",
85
131
  "UseComputerError",
132
+ "Via",
133
+ "WindowInfo",
134
+ "WindowsAction",
135
+ "WindowsResult",
86
136
  "__version__",
87
137
  "compare",
88
138
  "convert",
89
139
  "create_backend",
140
+ "create_provider",
90
141
  "load_config",
91
142
  "parse_combo",
92
143
  "run_actions",
@@ -0,0 +1,51 @@
1
+ """Choosing an accessibility provider.
2
+
3
+ By the *running platform*, never by configuration: a profile does not get to claim macOS
4
+ accessibility on Linux. And only for a backend that can have one at all -- RFB carries pixels,
5
+ so a vnc profile gets a clear error and the calling agent uses coordinates.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+
12
+ from use_computer.accessibility.base import AccessibilityProvider, require
13
+ from use_computer.errors import UITreeUnavailableError
14
+
15
+ #: Backends that drive a display this machine can also inspect. Everything else is remote
16
+ #: pixels, and no accessibility API reaches across RFB.
17
+ LOCAL_BACKENDS = frozenset({"local"})
18
+
19
+
20
+ def create_provider(backend: str) -> AccessibilityProvider:
21
+ """Build the provider for this platform, or say why there cannot be one.
22
+
23
+ Raises:
24
+ UITreeUnavailableError: wrong backend, unsupported platform, or a missing binding.
25
+ PermissionDeniedError: the OS refused the accessibility permission.
26
+ """
27
+ if backend not in LOCAL_BACKENDS:
28
+ raise UITreeUnavailableError(
29
+ f"the {backend!r} backend drives a remote framebuffer, which carries pixels and no "
30
+ "accessibility information. Use a screenshot and coordinates instead."
31
+ )
32
+
33
+ if sys.platform.startswith("linux"):
34
+ from use_computer.accessibility.atspi import AtspiProvider # noqa: PLC0415
35
+
36
+ return AtspiProvider()
37
+ if sys.platform == "win32":
38
+ from use_computer.accessibility.uia import UiaProvider # noqa: PLC0415
39
+
40
+ return UiaProvider()
41
+ if sys.platform == "darwin":
42
+ from use_computer.accessibility.ax import AxProvider # noqa: PLC0415
43
+
44
+ return AxProvider()
45
+
46
+ raise UITreeUnavailableError(
47
+ f"no accessibility provider for platform {sys.platform!r}."
48
+ )
49
+
50
+
51
+ __all__ = ["AccessibilityProvider", "UITreeUnavailableError", "create_provider", "require"]