open-alo-core 0.1.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.
- open_alo_core-0.1.0/API_REFERENCE.md +1735 -0
- open_alo_core-0.1.0/LICENSE +21 -0
- open_alo_core-0.1.0/MANIFEST.in +14 -0
- open_alo_core-0.1.0/PKG-INFO +175 -0
- open_alo_core-0.1.0/PYPI_README.md +142 -0
- open_alo_core-0.1.0/README.md +271 -0
- open_alo_core-0.1.0/pyproject.toml +60 -0
- open_alo_core-0.1.0/setup.cfg +4 -0
- open_alo_core-0.1.0/src/open_alo_core/__init__.py +91 -0
- open_alo_core-0.1.0/src/open_alo_core/exceptions.py +35 -0
- open_alo_core-0.1.0/src/open_alo_core/types.py +112 -0
- open_alo_core-0.1.0/src/open_alo_core/utils/__init__.py +104 -0
- open_alo_core-0.1.0/src/open_alo_core/wayland/__init__.py +0 -0
- open_alo_core-0.1.0/src/open_alo_core/wayland/capture.py +345 -0
- open_alo_core-0.1.0/src/open_alo_core/wayland/input.py +524 -0
- open_alo_core-0.1.0/src/open_alo_core/wayland/unified.py +861 -0
- open_alo_core-0.1.0/src/open_alo_core/window.py +93 -0
- open_alo_core-0.1.0/src/open_alo_core/window_manager.py +491 -0
- open_alo_core-0.1.0/src/open_alo_core.egg-info/PKG-INFO +175 -0
- open_alo_core-0.1.0/src/open_alo_core.egg-info/SOURCES.txt +21 -0
- open_alo_core-0.1.0/src/open_alo_core.egg-info/dependency_links.txt +1 -0
- open_alo_core-0.1.0/src/open_alo_core.egg-info/requires.txt +1 -0
- open_alo_core-0.1.0/src/open_alo_core.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,1735 @@
|
|
|
1
|
+
# open_alo_core API Reference
|
|
2
|
+
|
|
3
|
+
**Version:** 0.1.0
|
|
4
|
+
**Platform:** Linux (Wayland/X11)
|
|
5
|
+
**License:** MIT
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Overview](#overview)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Core Classes](#core-classes)
|
|
13
|
+
- [UnifiedRemoteDesktop](#unifiedremotedesktop) ⭐ **RECOMMENDED for AI Agents**
|
|
14
|
+
- [WaylandInput](#waylandinput) (Legacy)
|
|
15
|
+
- [WaylandCapture](#waylandcapture) (Legacy)
|
|
16
|
+
- [WindowManager](#windowmanager)
|
|
17
|
+
- [Types](#types)
|
|
18
|
+
- [Point](#point)
|
|
19
|
+
- [Size](#size)
|
|
20
|
+
- [Rect](#rect)
|
|
21
|
+
- [WindowInfo](#windowinfo)
|
|
22
|
+
- [WindowType](#windowtype)
|
|
23
|
+
- [FrameType](#frametype)
|
|
24
|
+
- [Exceptions](#exceptions)
|
|
25
|
+
- [Utilities](#utilities)
|
|
26
|
+
- [Constants](#constants)
|
|
27
|
+
- [Complete API Index](#complete-api-index)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Overview
|
|
32
|
+
|
|
33
|
+
`open_alo_core` is a standalone desktop automation SDK for Linux that provides:
|
|
34
|
+
|
|
35
|
+
- **Input Control**: Mouse and keyboard control via XDG RemoteDesktop Portal
|
|
36
|
+
- **Screen Capture**: Native Wayland screenshot via PipeWire and XDG ScreenCast Portal
|
|
37
|
+
- **Window Management**: Comprehensive window control via GNOME Shell D-Bus (requires Window Calls extension)
|
|
38
|
+
- **Zero Dependencies on AI/ML**: Pure hardware abstraction layer
|
|
39
|
+
- **Wayland Native**: No X11 fallback, works on modern Wayland compositors
|
|
40
|
+
|
|
41
|
+
### Architecture
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
open_alo_core/
|
|
45
|
+
├── wayland/
|
|
46
|
+
│ ├── input.py # WaylandInput - Mouse & keyboard control
|
|
47
|
+
│ └── capture.py # WaylandCapture - Screen capture
|
|
48
|
+
├── window_manager.py # WindowManager - Window management
|
|
49
|
+
├── types.py # Data types (Point, Size, Rect)
|
|
50
|
+
├── exceptions.py # Exception hierarchy
|
|
51
|
+
└── utils/ # Utility functions
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### System Requirements
|
|
55
|
+
|
|
56
|
+
- **OS**: Linux with Wayland compositor (GNOME Shell, KDE Plasma, Sway, etc.)
|
|
57
|
+
- **Python**: 3.8+
|
|
58
|
+
- **Dependencies**:
|
|
59
|
+
- PyGObject (python3-gi)
|
|
60
|
+
- GStreamer 1.0 (for capture)
|
|
61
|
+
- PipeWire (for capture)
|
|
62
|
+
- **Window Management** (GNOME only):
|
|
63
|
+
- [Window Calls Extension](https://extensions.gnome.org/extension/4724/window-calls/)
|
|
64
|
+
- Install and enable: `gnome-extensions enable window-calls@domandoman.github.com`
|
|
65
|
+
|
|
66
|
+
**Tested Environment:**
|
|
67
|
+
- Ubuntu 25.10 (Questing), Wayland + GNOME/Unity
|
|
68
|
+
- Window Calls extension v13+
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Install from source
|
|
76
|
+
cd open_alo_core
|
|
77
|
+
pip install -e .
|
|
78
|
+
|
|
79
|
+
# Or install dependencies manually
|
|
80
|
+
sudo apt install python3-gi gstreamer1.0-tools pipewire
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Quick Start
|
|
86
|
+
|
|
87
|
+
### Unified Approach (Recommended for AI Agents) ⭐
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from open_alo_core import UnifiedRemoteDesktop, Point
|
|
91
|
+
|
|
92
|
+
# ONE permission dialog for both input and capture
|
|
93
|
+
with UnifiedRemoteDesktop() as remote:
|
|
94
|
+
remote.initialize(persist_mode=2, enable_capture=True)
|
|
95
|
+
|
|
96
|
+
# Screen capture
|
|
97
|
+
screenshot = remote.capture_screenshot() # PNG bytes
|
|
98
|
+
frame = remote.get_frame() # Real-time stream
|
|
99
|
+
width, height = remote.get_screen_size()
|
|
100
|
+
|
|
101
|
+
# Input control
|
|
102
|
+
remote.click(Point(500, 500))
|
|
103
|
+
remote.type_text("Hello World!")
|
|
104
|
+
remote.key_combo(["ctrl", "c"])
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Legacy Approach (Separate Input/Capture)
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from open_alo_core import WaylandInput, Point
|
|
111
|
+
|
|
112
|
+
# Context manager automatically cleans up
|
|
113
|
+
with WaylandInput() as ctrl:
|
|
114
|
+
# Initialize with persistent permissions (approve once)
|
|
115
|
+
ctrl.initialize(persist_mode=2)
|
|
116
|
+
|
|
117
|
+
# Click at coordinates
|
|
118
|
+
ctrl.click(Point(500, 500))
|
|
119
|
+
|
|
120
|
+
# Type text
|
|
121
|
+
ctrl.type_text("Hello World!")
|
|
122
|
+
|
|
123
|
+
# Press keys
|
|
124
|
+
ctrl.press_key("Return")
|
|
125
|
+
|
|
126
|
+
# Keyboard shortcuts
|
|
127
|
+
ctrl.key_combo(["Control", "a"]) # Select all
|
|
128
|
+
ctrl.key_combo(["Control", "c"]) # Copy
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Screen Capture
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from open_alo_core import WaylandCapture
|
|
135
|
+
from pathlib import Path
|
|
136
|
+
|
|
137
|
+
with WaylandCapture() as capture:
|
|
138
|
+
# User will be prompted to select screen/window
|
|
139
|
+
result = capture.capture_screen()
|
|
140
|
+
|
|
141
|
+
# Save as PNG
|
|
142
|
+
Path("/tmp/screenshot.png").write_bytes(result.data)
|
|
143
|
+
|
|
144
|
+
print(f"Captured {result.source_type}: {result.size}")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Window Management
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from open_alo_core import WindowManager, activate_window
|
|
151
|
+
|
|
152
|
+
# Simple activation
|
|
153
|
+
activate_window("Text Editor")
|
|
154
|
+
|
|
155
|
+
# Full API
|
|
156
|
+
wm = WindowManager()
|
|
157
|
+
windows = wm.list_windows()
|
|
158
|
+
editor = wm.find_window("Text Editor")
|
|
159
|
+
|
|
160
|
+
if editor:
|
|
161
|
+
wm.activate(editor.id)
|
|
162
|
+
wm.maximize(editor.id)
|
|
163
|
+
wm.move_resize(editor.id, 0, 0, 1920, 1080)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Core Classes
|
|
169
|
+
|
|
170
|
+
### UnifiedRemoteDesktop ⭐
|
|
171
|
+
|
|
172
|
+
**Full path**: `open_alo_core.wayland.unified.UnifiedRemoteDesktop`
|
|
173
|
+
|
|
174
|
+
**RECOMMENDED for AI Agents** - Provides both input control and screen capture with a single permission dialog.
|
|
175
|
+
|
|
176
|
+
#### Why Use UnifiedRemoteDesktop?
|
|
177
|
+
|
|
178
|
+
- ✅ **ONE permission dialog** (vs two separate dialogs)
|
|
179
|
+
- ✅ **Simpler code** (single class vs WaylandInput + WaylandCapture)
|
|
180
|
+
- ✅ **Better UX** (same approach as RustDesk, Chrome Remote Desktop)
|
|
181
|
+
- ✅ **AI-ready** (real-time screen streaming + input control)
|
|
182
|
+
|
|
183
|
+
#### Constructor
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
UnifiedRemoteDesktop(token_path: Optional[Path] = None)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Parameters:**
|
|
190
|
+
- `token_path` (Optional[Path]): Custom path for storing permission tokens. Defaults to `~/.config/open_alo_core/unified_token.json`
|
|
191
|
+
|
|
192
|
+
#### Methods
|
|
193
|
+
|
|
194
|
+
##### `initialize(persist_mode: int = 2, enable_capture: bool = True) -> bool`
|
|
195
|
+
|
|
196
|
+
Initialize unified remote desktop session. Shows ONE permission dialog for both input and capture.
|
|
197
|
+
|
|
198
|
+
**Parameters:**
|
|
199
|
+
- `persist_mode` (int): Permission persistence
|
|
200
|
+
- `0` = Ask every time (testing)
|
|
201
|
+
- `1` = Persist during app session
|
|
202
|
+
- `2` = Persist until revoked (recommended)
|
|
203
|
+
- `enable_capture` (bool): Enable screen capture capabilities. Set `True` for AI agents.
|
|
204
|
+
|
|
205
|
+
**Returns:**
|
|
206
|
+
- `bool`: True if initialization succeeded
|
|
207
|
+
|
|
208
|
+
**Raises:**
|
|
209
|
+
- `PermissionDenied`: User denied permission
|
|
210
|
+
- `SessionError`: Portal communication failed
|
|
211
|
+
|
|
212
|
+
**Example:**
|
|
213
|
+
```python
|
|
214
|
+
with UnifiedRemoteDesktop() as remote:
|
|
215
|
+
remote.initialize(persist_mode=2, enable_capture=True)
|
|
216
|
+
# Ready to use!
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
##### `capture_screenshot() -> bytes`
|
|
220
|
+
|
|
221
|
+
Capture current screen as PNG image.
|
|
222
|
+
|
|
223
|
+
**Returns:**
|
|
224
|
+
- `bytes`: PNG image data
|
|
225
|
+
|
|
226
|
+
**Example:**
|
|
227
|
+
```python
|
|
228
|
+
screenshot = remote.capture_screenshot()
|
|
229
|
+
Path("/tmp/screenshot.png").write_bytes(screenshot)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
##### `get_frame() -> bytes`
|
|
233
|
+
|
|
234
|
+
Get real-time frame from video stream (for continuous monitoring).
|
|
235
|
+
|
|
236
|
+
**Returns:**
|
|
237
|
+
- `bytes`: PNG image data
|
|
238
|
+
|
|
239
|
+
**Example:**
|
|
240
|
+
```python
|
|
241
|
+
while agent_running:
|
|
242
|
+
frame = remote.get_frame()
|
|
243
|
+
action = ai_model.decide(frame)
|
|
244
|
+
# Execute action...
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
##### `get_screen_size() -> Tuple[int, int]`
|
|
248
|
+
|
|
249
|
+
Get screen resolution.
|
|
250
|
+
|
|
251
|
+
**Returns:**
|
|
252
|
+
- `Tuple[int, int]`: (width, height) in pixels
|
|
253
|
+
|
|
254
|
+
**Example:**
|
|
255
|
+
```python
|
|
256
|
+
width, height = remote.get_screen_size()
|
|
257
|
+
center = Point(width // 2, height // 2)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
##### `click(point: Point, button: int = 1) -> None`
|
|
261
|
+
|
|
262
|
+
Click mouse at specific coordinates.
|
|
263
|
+
|
|
264
|
+
**Parameters:**
|
|
265
|
+
- `point` (Point): Click coordinates
|
|
266
|
+
- `button` (int): Mouse button (1=left, 2=middle, 3=right)
|
|
267
|
+
|
|
268
|
+
**Example:**
|
|
269
|
+
```python
|
|
270
|
+
remote.click(Point(100, 200)) # Left click
|
|
271
|
+
remote.click(Point(100, 200), button=3) # Right click
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
##### `move_mouse(point: Point) -> None`
|
|
275
|
+
|
|
276
|
+
Move mouse cursor to coordinates.
|
|
277
|
+
|
|
278
|
+
**Parameters:**
|
|
279
|
+
- `point` (Point): Target coordinates
|
|
280
|
+
|
|
281
|
+
**Example:**
|
|
282
|
+
```python
|
|
283
|
+
remote.move_mouse(Point(500, 500))
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
##### `type_text(text: str, interval: float = 0.05) -> None`
|
|
287
|
+
|
|
288
|
+
Type text with optional delay between characters.
|
|
289
|
+
|
|
290
|
+
**Parameters:**
|
|
291
|
+
- `text` (str): Text to type
|
|
292
|
+
- `interval` (float): Delay between characters in seconds
|
|
293
|
+
|
|
294
|
+
**Example:**
|
|
295
|
+
```python
|
|
296
|
+
remote.type_text("Hello World!\n")
|
|
297
|
+
remote.type_text("fast typing", interval=0.01)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
##### `press_key(key: Union[str, int]) -> None`
|
|
301
|
+
|
|
302
|
+
Press and release a single key.
|
|
303
|
+
|
|
304
|
+
**Parameters:**
|
|
305
|
+
- `key` (Union[str, int]): Key name or code
|
|
306
|
+
|
|
307
|
+
**Example:**
|
|
308
|
+
```python
|
|
309
|
+
remote.press_key("enter")
|
|
310
|
+
remote.press_key("escape")
|
|
311
|
+
remote.press_key("f5")
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
##### `key_combo(keys: List[Union[str, int]]) -> None`
|
|
315
|
+
|
|
316
|
+
Press keyboard shortcut (hold all keys, then release).
|
|
317
|
+
|
|
318
|
+
**Parameters:**
|
|
319
|
+
- `keys` (List[Union[str, int]]): List of keys to press together
|
|
320
|
+
|
|
321
|
+
**Example:**
|
|
322
|
+
```python
|
|
323
|
+
remote.key_combo(["ctrl", "c"]) # Copy
|
|
324
|
+
remote.key_combo(["ctrl", "shift", "t"]) # New terminal tab
|
|
325
|
+
remote.key_combo(["alt", "f4"]) # Close window
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
##### `close() -> None`
|
|
329
|
+
|
|
330
|
+
Release resources and close session. Called automatically when using context manager.
|
|
331
|
+
|
|
332
|
+
**Example:**
|
|
333
|
+
```python
|
|
334
|
+
# Manual cleanup
|
|
335
|
+
remote = UnifiedRemoteDesktop()
|
|
336
|
+
remote.initialize()
|
|
337
|
+
# ... use remote ...
|
|
338
|
+
remote.close()
|
|
339
|
+
|
|
340
|
+
# Auto cleanup (recommended)
|
|
341
|
+
with UnifiedRemoteDesktop() as remote:
|
|
342
|
+
remote.initialize()
|
|
343
|
+
# ... use remote ...
|
|
344
|
+
# Automatically closed
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### Complete Example
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
from open_alo_core import UnifiedRemoteDesktop, WindowManager, Point
|
|
351
|
+
from pathlib import Path
|
|
352
|
+
|
|
353
|
+
# Setup window
|
|
354
|
+
wm = WindowManager()
|
|
355
|
+
app = wm.find_window("TextEditor")
|
|
356
|
+
wm.activate(app.id)
|
|
357
|
+
|
|
358
|
+
# AI agent loop
|
|
359
|
+
with UnifiedRemoteDesktop() as remote:
|
|
360
|
+
remote.initialize(persist_mode=2, enable_capture=True)
|
|
361
|
+
|
|
362
|
+
while True:
|
|
363
|
+
# 1. Capture screen
|
|
364
|
+
frame = remote.get_frame()
|
|
365
|
+
|
|
366
|
+
# 2. AI decides action
|
|
367
|
+
action = ai_model.process(frame)
|
|
368
|
+
|
|
369
|
+
# 3. Execute
|
|
370
|
+
if action['type'] == 'click':
|
|
371
|
+
remote.click(Point(action['x'], action['y']))
|
|
372
|
+
elif action['type'] == 'type':
|
|
373
|
+
remote.type_text(action['text'])
|
|
374
|
+
elif action['type'] == 'screenshot':
|
|
375
|
+
screenshot = remote.capture_screenshot()
|
|
376
|
+
Path(f"capture_{time.time()}.png").write_bytes(screenshot)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
### WaylandInput (Legacy)
|
|
382
|
+
|
|
383
|
+
**Full path**: `open_alo_core.wayland.input.WaylandInput`
|
|
384
|
+
|
|
385
|
+
> **Note:** For new projects, prefer `UnifiedRemoteDesktop` which combines input and capture in one permission dialog.
|
|
386
|
+
|
|
387
|
+
Provides mouse and keyboard control on Wayland using XDG RemoteDesktop Portal.
|
|
388
|
+
|
|
389
|
+
#### Constructor
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
WaylandInput(token_path: Optional[Path] = None)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Parameters:**
|
|
396
|
+
- `token_path` (Optional[Path]): Custom path for storing permission tokens. If `None`, uses `~/.config/open_alo_core/tokens.json`
|
|
397
|
+
|
|
398
|
+
**Example:**
|
|
399
|
+
```python
|
|
400
|
+
# Default token location
|
|
401
|
+
ctrl = WaylandInput()
|
|
402
|
+
|
|
403
|
+
# Custom token location
|
|
404
|
+
ctrl = WaylandInput(token_path=Path("/tmp/tokens.json"))
|
|
405
|
+
|
|
406
|
+
# Ephemeral session (no persistence)
|
|
407
|
+
ctrl = WaylandInput()
|
|
408
|
+
ctrl.initialize(persist_mode=0)
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### Methods
|
|
412
|
+
|
|
413
|
+
##### `initialize(persist_mode: int = 0) -> None`
|
|
414
|
+
|
|
415
|
+
Initialize portal session and request permissions.
|
|
416
|
+
|
|
417
|
+
**Parameters:**
|
|
418
|
+
- `persist_mode` (int):
|
|
419
|
+
- `0`: Never persist (dialog every time)
|
|
420
|
+
- `1`: Persist while app running
|
|
421
|
+
- `2`: Persist until revoked (recommended)
|
|
422
|
+
|
|
423
|
+
**Raises:**
|
|
424
|
+
- `PermissionDenied`: User denied permission
|
|
425
|
+
- `SessionError`: Session creation failed
|
|
426
|
+
- `RuntimeError`: Not running on Wayland
|
|
427
|
+
|
|
428
|
+
**Example:**
|
|
429
|
+
```python
|
|
430
|
+
ctrl = WaylandInput()
|
|
431
|
+
ctrl.initialize(persist_mode=2) # Approve once, persist forever
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
##### `click(point: Point, button: int = 1) -> None`
|
|
435
|
+
|
|
436
|
+
Click at screen coordinates.
|
|
437
|
+
|
|
438
|
+
**Parameters:**
|
|
439
|
+
- `point` (Point): Screen coordinates (x, y)
|
|
440
|
+
- `button` (int): Mouse button
|
|
441
|
+
- `1`: Left button (default)
|
|
442
|
+
- `2`: Middle button
|
|
443
|
+
- `3`: Right button
|
|
444
|
+
|
|
445
|
+
**Raises:**
|
|
446
|
+
- `RuntimeError`: Not initialized
|
|
447
|
+
- `InputError`: Click failed
|
|
448
|
+
|
|
449
|
+
**Example:**
|
|
450
|
+
```python
|
|
451
|
+
# Left click
|
|
452
|
+
ctrl.click(Point(500, 500))
|
|
453
|
+
|
|
454
|
+
# Right click
|
|
455
|
+
ctrl.click(Point(100, 100), button=3)
|
|
456
|
+
|
|
457
|
+
# Middle click
|
|
458
|
+
ctrl.click(Point(200, 200), button=2)
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
##### `move_mouse(point: Point) -> None`
|
|
462
|
+
|
|
463
|
+
Move mouse cursor to coordinates.
|
|
464
|
+
|
|
465
|
+
**Parameters:**
|
|
466
|
+
- `point` (Point): Target coordinates
|
|
467
|
+
|
|
468
|
+
**Raises:**
|
|
469
|
+
- `RuntimeError`: Not initialized
|
|
470
|
+
- `InputError`: Move failed
|
|
471
|
+
|
|
472
|
+
**Example:**
|
|
473
|
+
```python
|
|
474
|
+
ctrl.move_mouse(Point(800, 600))
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
##### `type_text(text: str, interval: float = 0.01) -> None`
|
|
478
|
+
|
|
479
|
+
Type a text string character by character.
|
|
480
|
+
|
|
481
|
+
**Parameters:**
|
|
482
|
+
- `text` (str): Unicode text to type
|
|
483
|
+
- `interval` (float): Delay between characters in seconds (default: 0.01)
|
|
484
|
+
|
|
485
|
+
**Raises:**
|
|
486
|
+
- `RuntimeError`: Not initialized
|
|
487
|
+
- `InputError`: Typing failed
|
|
488
|
+
|
|
489
|
+
**Example:**
|
|
490
|
+
```python
|
|
491
|
+
# Normal typing
|
|
492
|
+
ctrl.type_text("Hello World!")
|
|
493
|
+
|
|
494
|
+
# Fast typing
|
|
495
|
+
ctrl.type_text("Speed typing", interval=0.001)
|
|
496
|
+
|
|
497
|
+
# Unicode support
|
|
498
|
+
ctrl.type_text("你好世界 🌍")
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
##### `press_key(key: str) -> None`
|
|
502
|
+
|
|
503
|
+
Press and release a single key.
|
|
504
|
+
|
|
505
|
+
**Parameters:**
|
|
506
|
+
- `key` (str): Key name (e.g., "Return", "Escape", "a"). See [Key Names](#key-names) for full list.
|
|
507
|
+
|
|
508
|
+
**Raises:**
|
|
509
|
+
- `RuntimeError`: Not initialized
|
|
510
|
+
- `InputError`: Key press failed
|
|
511
|
+
|
|
512
|
+
**Example:**
|
|
513
|
+
```python
|
|
514
|
+
ctrl.press_key("Return") # Enter
|
|
515
|
+
ctrl.press_key("Escape") # Esc
|
|
516
|
+
ctrl.press_key("Tab") # Tab
|
|
517
|
+
ctrl.press_key("space") # Spacebar
|
|
518
|
+
ctrl.press_key("a") # Letter 'a'
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
##### `key_combo(keys: List[str]) -> None`
|
|
522
|
+
|
|
523
|
+
Press multiple keys together (keyboard shortcut).
|
|
524
|
+
|
|
525
|
+
**Parameters:**
|
|
526
|
+
- `keys` (List[str]): List of keys to press together
|
|
527
|
+
|
|
528
|
+
**Raises:**
|
|
529
|
+
- `RuntimeError`: Not initialized
|
|
530
|
+
- `InputError`: Key combo failed
|
|
531
|
+
|
|
532
|
+
**Example:**
|
|
533
|
+
```python
|
|
534
|
+
# Common shortcuts
|
|
535
|
+
ctrl.key_combo(["Control", "a"]) # Select all
|
|
536
|
+
ctrl.key_combo(["Control", "c"]) # Copy
|
|
537
|
+
ctrl.key_combo(["Control", "v"]) # Paste
|
|
538
|
+
ctrl.key_combo(["Control", "Shift", "t"]) # Reopen tab
|
|
539
|
+
ctrl.key_combo(["Alt", "Tab"]) # Switch window
|
|
540
|
+
ctrl.key_combo(["Super", "d"]) # Show desktop
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
##### `close() -> None`
|
|
544
|
+
|
|
545
|
+
Release resources and close portal session.
|
|
546
|
+
|
|
547
|
+
**Example:**
|
|
548
|
+
```python
|
|
549
|
+
ctrl = WaylandInput()
|
|
550
|
+
ctrl.initialize(persist_mode=2)
|
|
551
|
+
# ... use controller ...
|
|
552
|
+
ctrl.close() # Clean up
|
|
553
|
+
|
|
554
|
+
# Or use context manager (auto cleanup)
|
|
555
|
+
with WaylandInput() as ctrl:
|
|
556
|
+
ctrl.initialize(persist_mode=2)
|
|
557
|
+
# ... automatically closed
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### Key Names
|
|
561
|
+
|
|
562
|
+
The following key names are supported (case-insensitive aliases normalized automatically):
|
|
563
|
+
|
|
564
|
+
**Special Keys:**
|
|
565
|
+
- `Return`, `Enter` → Enter key
|
|
566
|
+
- `Escape`, `Esc` → Escape
|
|
567
|
+
- `Tab` → Tab
|
|
568
|
+
- `space` → Spacebar
|
|
569
|
+
- `BackSpace` → Backspace
|
|
570
|
+
- `Delete`, `Del` → Delete
|
|
571
|
+
|
|
572
|
+
**Navigation:**
|
|
573
|
+
- `Home`, `End`
|
|
574
|
+
- `Page_Up`, `PageUp`
|
|
575
|
+
- `Page_Down`, `PageDown`
|
|
576
|
+
- `Left`, `Right`, `Up`, `Down` → Arrow keys
|
|
577
|
+
|
|
578
|
+
**Modifiers:**
|
|
579
|
+
- `Control`, `Ctrl` → Control key
|
|
580
|
+
- `Alt` → Alt key
|
|
581
|
+
- `Shift` → Shift key
|
|
582
|
+
- `Super`, `Win`, `Cmd`, `Command` → Super/Windows/Command key
|
|
583
|
+
|
|
584
|
+
**Function Keys:**
|
|
585
|
+
- `F1` through `F12`
|
|
586
|
+
|
|
587
|
+
**Characters:**
|
|
588
|
+
- Any letter: `a`, `b`, `c`, ..., `z`
|
|
589
|
+
- Any number: `0`, `1`, `2`, ..., `9`
|
|
590
|
+
- Symbols: `-`, `=`, `[`, `]`, `;`, `'`, etc.
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
### WaylandCapture (Legacy)
|
|
595
|
+
|
|
596
|
+
**Full path**: `open_alo_core.wayland.capture.WaylandCapture`
|
|
597
|
+
|
|
598
|
+
> **Note:** For new projects, prefer `UnifiedRemoteDesktop` which combines input and capture in one permission dialog.
|
|
599
|
+
|
|
600
|
+
Provides screen capture on Wayland using PipeWire and XDG ScreenCast Portal.
|
|
601
|
+
|
|
602
|
+
#### Constructor
|
|
603
|
+
|
|
604
|
+
```python
|
|
605
|
+
WaylandCapture()
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
No parameters needed.
|
|
609
|
+
|
|
610
|
+
**Example:**
|
|
611
|
+
```python
|
|
612
|
+
capture = WaylandCapture()
|
|
613
|
+
|
|
614
|
+
# Or use context manager
|
|
615
|
+
with WaylandCapture() as capture:
|
|
616
|
+
result = capture.capture_screen()
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
#### Methods
|
|
620
|
+
|
|
621
|
+
##### `capture_screen() -> CaptureResult`
|
|
622
|
+
|
|
623
|
+
Capture the screen with user selection.
|
|
624
|
+
|
|
625
|
+
Shows a permission dialog asking the user which screen/window to capture. Returns PNG image data.
|
|
626
|
+
|
|
627
|
+
**Returns:**
|
|
628
|
+
- `CaptureResult`: Object containing:
|
|
629
|
+
- `data` (bytes): PNG image data
|
|
630
|
+
- `source_type` (str): Source type ("monitor", "window", "camera")
|
|
631
|
+
- `size` (Tuple[int, int]): Image dimensions (width, height)
|
|
632
|
+
|
|
633
|
+
**Raises:**
|
|
634
|
+
- `CaptureError`: Capture failed
|
|
635
|
+
- `PermissionDenied`: User denied permission
|
|
636
|
+
|
|
637
|
+
**Example:**
|
|
638
|
+
```python
|
|
639
|
+
with WaylandCapture() as capture:
|
|
640
|
+
# User selects screen/window
|
|
641
|
+
result = capture.capture_screen()
|
|
642
|
+
|
|
643
|
+
# Save to file
|
|
644
|
+
from pathlib import Path
|
|
645
|
+
Path("/tmp/screenshot.png").write_bytes(result.data)
|
|
646
|
+
|
|
647
|
+
# Get info
|
|
648
|
+
print(f"Captured {result.source_type}")
|
|
649
|
+
print(f"Size: {result.size[0]}x{result.size[1]}")
|
|
650
|
+
print(f"Data: {len(result.data)} bytes")
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
##### `close() -> None`
|
|
654
|
+
|
|
655
|
+
Release resources and close session.
|
|
656
|
+
|
|
657
|
+
**Example:**
|
|
658
|
+
```python
|
|
659
|
+
capture = WaylandCapture()
|
|
660
|
+
result = capture.capture_screen()
|
|
661
|
+
capture.close()
|
|
662
|
+
|
|
663
|
+
# Or use context manager
|
|
664
|
+
with WaylandCapture() as capture:
|
|
665
|
+
result = capture.capture_screen()
|
|
666
|
+
# Automatically closed
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
#### CaptureResult
|
|
670
|
+
|
|
671
|
+
Result object returned by `capture_screen()`.
|
|
672
|
+
|
|
673
|
+
**Attributes:**
|
|
674
|
+
- `data` (bytes): PNG image data (ready to save or process)
|
|
675
|
+
- `source_type` (str): Type of source captured
|
|
676
|
+
- `"monitor"`: Full screen capture
|
|
677
|
+
- `"window"`: Single window capture
|
|
678
|
+
- `"camera"`: Camera capture (rare)
|
|
679
|
+
- `size` (Tuple[int, int]): Image dimensions as (width, height)
|
|
680
|
+
|
|
681
|
+
**Example:**
|
|
682
|
+
```python
|
|
683
|
+
result = capture.capture_screen()
|
|
684
|
+
|
|
685
|
+
# Save to file
|
|
686
|
+
with open("/tmp/shot.png", "wb") as f:
|
|
687
|
+
f.write(result.data)
|
|
688
|
+
|
|
689
|
+
# Load with PIL
|
|
690
|
+
from PIL import Image
|
|
691
|
+
from io import BytesIO
|
|
692
|
+
img = Image.open(BytesIO(result.data))
|
|
693
|
+
|
|
694
|
+
# Get dimensions
|
|
695
|
+
width, height = result.size
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
### WindowManager
|
|
701
|
+
|
|
702
|
+
**Full path**: `open_alo_core.window_manager.WindowManager`
|
|
703
|
+
|
|
704
|
+
Comprehensive window management for GNOME Shell via D-Bus.
|
|
705
|
+
|
|
706
|
+
**Requirements:**
|
|
707
|
+
- GNOME Shell with Wayland or X11
|
|
708
|
+
- [Window Calls Extension](https://extensions.gnome.org/extension/4724/window-calls/) installed and enabled
|
|
709
|
+
|
|
710
|
+
#### Constructor
|
|
711
|
+
|
|
712
|
+
```python
|
|
713
|
+
WindowManager(timeout: int = 5)
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
**Parameters:**
|
|
717
|
+
- `timeout` (int): Default timeout for D-Bus calls in seconds (default: 5)
|
|
718
|
+
|
|
719
|
+
**Raises:**
|
|
720
|
+
- `RuntimeError`: Window Calls extension not available
|
|
721
|
+
|
|
722
|
+
**Example:**
|
|
723
|
+
```python
|
|
724
|
+
wm = WindowManager() # Default 5s timeout
|
|
725
|
+
wm = WindowManager(timeout=10) # Custom timeout
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
#### Window Listing & Search Methods
|
|
729
|
+
|
|
730
|
+
##### `list_windows(current_workspace_only: bool = False) -> List[WindowInfo]`
|
|
731
|
+
|
|
732
|
+
List all open windows.
|
|
733
|
+
|
|
734
|
+
**Parameters:**
|
|
735
|
+
- `current_workspace_only` (bool): Only return windows in current workspace (default: False)
|
|
736
|
+
|
|
737
|
+
**Returns:**
|
|
738
|
+
- `List[WindowInfo]`: List of window information objects
|
|
739
|
+
|
|
740
|
+
**Example:**
|
|
741
|
+
```python
|
|
742
|
+
# All windows
|
|
743
|
+
all_windows = wm.list_windows()
|
|
744
|
+
for win in all_windows:
|
|
745
|
+
print(f"{win.wm_class}: {win.title}")
|
|
746
|
+
|
|
747
|
+
# Current workspace only
|
|
748
|
+
current = wm.list_windows(current_workspace_only=True)
|
|
749
|
+
print(f"Windows on current workspace: {len(current)}")
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
##### `find_window(query: str, match_title: bool = True) -> Optional[WindowInfo]`
|
|
753
|
+
|
|
754
|
+
Find first window matching query.
|
|
755
|
+
|
|
756
|
+
**Parameters:**
|
|
757
|
+
- `query` (str): Search string (case-insensitive)
|
|
758
|
+
- `match_title` (bool): Also search in window titles (default: True)
|
|
759
|
+
|
|
760
|
+
**Returns:**
|
|
761
|
+
- `WindowInfo`: First matching window, or `None` if not found
|
|
762
|
+
|
|
763
|
+
**Example:**
|
|
764
|
+
```python
|
|
765
|
+
# Find by wm_class (fast)
|
|
766
|
+
editor = wm.find_window("gedit")
|
|
767
|
+
|
|
768
|
+
# Find by title
|
|
769
|
+
browser = wm.find_window("Google Chrome", match_title=True)
|
|
770
|
+
|
|
771
|
+
# wm_class only (faster, skips title search)
|
|
772
|
+
terminal = wm.find_window("gnome-terminal", match_title=False)
|
|
773
|
+
|
|
774
|
+
# Case-insensitive
|
|
775
|
+
vscode = wm.find_window("CODE") # Finds "code"
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
##### `find_all_windows(query: str, match_title: bool = True) -> List[WindowInfo]`
|
|
779
|
+
|
|
780
|
+
Find all windows matching query.
|
|
781
|
+
|
|
782
|
+
**Parameters:**
|
|
783
|
+
- `query` (str): Search string (case-insensitive)
|
|
784
|
+
- `match_title` (bool): Also search in window titles (default: True)
|
|
785
|
+
|
|
786
|
+
**Returns:**
|
|
787
|
+
- `List[WindowInfo]`: All matching windows
|
|
788
|
+
|
|
789
|
+
**Example:**
|
|
790
|
+
```python
|
|
791
|
+
# Find all terminal windows
|
|
792
|
+
terminals = wm.find_all_windows("terminal")
|
|
793
|
+
print(f"Found {len(terminals)} terminal windows")
|
|
794
|
+
|
|
795
|
+
# Find all browser windows
|
|
796
|
+
browsers = wm.find_all_windows("browser")
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
##### `get_focused_window() -> Optional[WindowInfo]`
|
|
800
|
+
|
|
801
|
+
Get currently focused window.
|
|
802
|
+
|
|
803
|
+
**Returns:**
|
|
804
|
+
- `WindowInfo`: Focused window, or `None` if none
|
|
805
|
+
|
|
806
|
+
**Example:**
|
|
807
|
+
```python
|
|
808
|
+
focused = wm.get_focused_window()
|
|
809
|
+
if focused:
|
|
810
|
+
print(f"Currently focused: {focused.wm_class}")
|
|
811
|
+
print(f"Title: {focused.title}")
|
|
812
|
+
else:
|
|
813
|
+
print("No window focused")
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
##### `get_details(window_id: int) -> Optional[Dict]`
|
|
817
|
+
|
|
818
|
+
Get detailed information about a window.
|
|
819
|
+
|
|
820
|
+
**Parameters:**
|
|
821
|
+
- `window_id` (int): Window ID
|
|
822
|
+
|
|
823
|
+
**Returns:**
|
|
824
|
+
- `Dict`: Dictionary with detailed properties, or `None` if failed
|
|
825
|
+
|
|
826
|
+
**Properties returned:**
|
|
827
|
+
- `wm_class`, `wm_class_instance`, `pid`, `id`
|
|
828
|
+
- `x`, `y`, `width`, `height`
|
|
829
|
+
- `maximized`, `focus`, `in_current_workspace`
|
|
830
|
+
- `moveable`, `resizeable`, `canclose`, `canmaximize`, `canminimize`, `canshade`
|
|
831
|
+
- `frame_type`, `window_type`, `layer`, `monitor`
|
|
832
|
+
- `role`, `display`, `area`, `area_all`, `area_cust`
|
|
833
|
+
|
|
834
|
+
**Example:**
|
|
835
|
+
```python
|
|
836
|
+
details = wm.get_details(window_id)
|
|
837
|
+
if details:
|
|
838
|
+
print(f"Maximized: {details['maximized']}")
|
|
839
|
+
print(f"Can maximize: {details['canmaximize']}")
|
|
840
|
+
print(f"Moveable: {details['moveable']}")
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
##### `get_title(window_id: int) -> Optional[str]`
|
|
844
|
+
|
|
845
|
+
Get window title by ID.
|
|
846
|
+
|
|
847
|
+
**Parameters:**
|
|
848
|
+
- `window_id` (int): Window ID
|
|
849
|
+
|
|
850
|
+
**Returns:**
|
|
851
|
+
- `str`: Window title, or `None` if failed
|
|
852
|
+
|
|
853
|
+
**Example:**
|
|
854
|
+
```python
|
|
855
|
+
title = wm.get_title(window_id)
|
|
856
|
+
print(f"Window title: {title}")
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
#### Window State Management Methods
|
|
860
|
+
|
|
861
|
+
##### `activate(window_id: int) -> bool`
|
|
862
|
+
|
|
863
|
+
Activate (focus) a window.
|
|
864
|
+
|
|
865
|
+
**Parameters:**
|
|
866
|
+
- `window_id` (int): Window ID
|
|
867
|
+
|
|
868
|
+
**Returns:**
|
|
869
|
+
- `bool`: True if successful
|
|
870
|
+
|
|
871
|
+
**Example:**
|
|
872
|
+
```python
|
|
873
|
+
editor = wm.find_window("Text Editor")
|
|
874
|
+
if editor:
|
|
875
|
+
wm.activate(editor.id)
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
##### `maximize(window_id: int) -> bool`
|
|
879
|
+
|
|
880
|
+
Maximize a window.
|
|
881
|
+
|
|
882
|
+
**Parameters:**
|
|
883
|
+
- `window_id` (int): Window ID
|
|
884
|
+
|
|
885
|
+
**Returns:**
|
|
886
|
+
- `bool`: True if successful
|
|
887
|
+
|
|
888
|
+
**Example:**
|
|
889
|
+
```python
|
|
890
|
+
wm.maximize(window_id)
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
##### `unmaximize(window_id: int) -> bool`
|
|
894
|
+
|
|
895
|
+
Unmaximize (restore) a window.
|
|
896
|
+
|
|
897
|
+
**Parameters:**
|
|
898
|
+
- `window_id` (int): Window ID
|
|
899
|
+
|
|
900
|
+
**Returns:**
|
|
901
|
+
- `bool`: True if successful
|
|
902
|
+
|
|
903
|
+
**Example:**
|
|
904
|
+
```python
|
|
905
|
+
wm.unmaximize(window_id)
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
##### `minimize(window_id: int) -> bool`
|
|
909
|
+
|
|
910
|
+
Minimize a window.
|
|
911
|
+
|
|
912
|
+
**Parameters:**
|
|
913
|
+
- `window_id` (int): Window ID
|
|
914
|
+
|
|
915
|
+
**Returns:**
|
|
916
|
+
- `bool`: True if successful
|
|
917
|
+
|
|
918
|
+
**Example:**
|
|
919
|
+
```python
|
|
920
|
+
wm.minimize(window_id)
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
##### `unminimize(window_id: int) -> bool`
|
|
924
|
+
|
|
925
|
+
Unminimize (restore) a window.
|
|
926
|
+
|
|
927
|
+
**Parameters:**
|
|
928
|
+
- `window_id` (int): Window ID
|
|
929
|
+
|
|
930
|
+
**Returns:**
|
|
931
|
+
- `bool`: True if successful
|
|
932
|
+
|
|
933
|
+
**Example:**
|
|
934
|
+
```python
|
|
935
|
+
wm.unminimize(window_id)
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
##### `close(window_id: int) -> bool`
|
|
939
|
+
|
|
940
|
+
Close a window.
|
|
941
|
+
|
|
942
|
+
**Parameters:**
|
|
943
|
+
- `window_id` (int): Window ID
|
|
944
|
+
|
|
945
|
+
**Returns:**
|
|
946
|
+
- `bool`: True if successful
|
|
947
|
+
|
|
948
|
+
**Example:**
|
|
949
|
+
```python
|
|
950
|
+
# Close a window
|
|
951
|
+
if wm.close(window_id):
|
|
952
|
+
print("Window closed")
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
#### Window Positioning Methods
|
|
956
|
+
|
|
957
|
+
##### `move(window_id: int, x: int, y: int) -> bool`
|
|
958
|
+
|
|
959
|
+
Move window to position.
|
|
960
|
+
|
|
961
|
+
**Parameters:**
|
|
962
|
+
- `window_id` (int): Window ID
|
|
963
|
+
- `x` (int): X coordinate (can be negative)
|
|
964
|
+
- `y` (int): Y coordinate (can be negative)
|
|
965
|
+
|
|
966
|
+
**Returns:**
|
|
967
|
+
- `bool`: True if successful
|
|
968
|
+
|
|
969
|
+
**Example:**
|
|
970
|
+
```python
|
|
971
|
+
# Move to top-left
|
|
972
|
+
wm.move(window_id, 0, 0)
|
|
973
|
+
|
|
974
|
+
# Negative coordinates supported (off-screen)
|
|
975
|
+
wm.move(window_id, -100, -50)
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
##### `resize(window_id: int, width: int, height: int) -> bool`
|
|
979
|
+
|
|
980
|
+
Resize window.
|
|
981
|
+
|
|
982
|
+
**Parameters:**
|
|
983
|
+
- `window_id` (int): Window ID
|
|
984
|
+
- `width` (int): New width in pixels
|
|
985
|
+
- `height` (int): New height in pixels
|
|
986
|
+
|
|
987
|
+
**Returns:**
|
|
988
|
+
- `bool`: True if successful
|
|
989
|
+
|
|
990
|
+
**Example:**
|
|
991
|
+
```python
|
|
992
|
+
# Standard HD resolution
|
|
993
|
+
wm.resize(window_id, 1920, 1080)
|
|
994
|
+
|
|
995
|
+
# Custom size
|
|
996
|
+
wm.resize(window_id, 800, 600)
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
##### `move_resize(window_id: int, x: int, y: int, width: int, height: int) -> bool`
|
|
1000
|
+
|
|
1001
|
+
Move and resize window in one operation (more efficient).
|
|
1002
|
+
|
|
1003
|
+
**Parameters:**
|
|
1004
|
+
- `window_id` (int): Window ID
|
|
1005
|
+
- `x` (int): X coordinate
|
|
1006
|
+
- `y` (int): Y coordinate
|
|
1007
|
+
- `width` (int): Width in pixels
|
|
1008
|
+
- `height` (int): Height in pixels
|
|
1009
|
+
|
|
1010
|
+
**Returns:**
|
|
1011
|
+
- `bool`: True if successful
|
|
1012
|
+
|
|
1013
|
+
**Example:**
|
|
1014
|
+
```python
|
|
1015
|
+
# Position at (100, 100) with size 1920x1080
|
|
1016
|
+
wm.move_resize(window_id, 100, 100, 1920, 1080)
|
|
1017
|
+
|
|
1018
|
+
# Tile left half of 1920x1080 screen
|
|
1019
|
+
wm.move_resize(window_id, 0, 0, 960, 1080)
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
##### `get_frame_rect(window_id: int) -> Optional[Dict]`
|
|
1023
|
+
|
|
1024
|
+
Get window frame rectangle.
|
|
1025
|
+
|
|
1026
|
+
**Parameters:**
|
|
1027
|
+
- `window_id` (int): Window ID
|
|
1028
|
+
|
|
1029
|
+
**Returns:**
|
|
1030
|
+
- `Dict`: Dictionary with `x`, `y`, `width`, `height`, or `None` if failed
|
|
1031
|
+
|
|
1032
|
+
**Example:**
|
|
1033
|
+
```python
|
|
1034
|
+
frame = wm.get_frame_rect(window_id)
|
|
1035
|
+
if frame:
|
|
1036
|
+
print(f"Position: ({frame['x']}, {frame['y']})")
|
|
1037
|
+
print(f"Size: {frame['width']}x{frame['height']}")
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
##### `get_frame_bounds(window_id: int) -> Optional[Dict]`
|
|
1041
|
+
|
|
1042
|
+
Get window frame bounds (may not work in GNOME 43+).
|
|
1043
|
+
|
|
1044
|
+
**Parameters:**
|
|
1045
|
+
- `window_id` (int): Window ID
|
|
1046
|
+
|
|
1047
|
+
**Returns:**
|
|
1048
|
+
- `Dict`: Frame bounds dictionary, or `None` if failed
|
|
1049
|
+
|
|
1050
|
+
**Example:**
|
|
1051
|
+
```python
|
|
1052
|
+
bounds = wm.get_frame_bounds(window_id)
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
#### Workspace Management Methods
|
|
1056
|
+
|
|
1057
|
+
##### `move_to_workspace(window_id: int, workspace_num: int) -> bool`
|
|
1058
|
+
|
|
1059
|
+
Move window to different workspace.
|
|
1060
|
+
|
|
1061
|
+
**Parameters:**
|
|
1062
|
+
- `window_id` (int): Window ID
|
|
1063
|
+
- `workspace_num` (int): Target workspace number (0-indexed)
|
|
1064
|
+
|
|
1065
|
+
**Returns:**
|
|
1066
|
+
- `bool`: True if successful
|
|
1067
|
+
|
|
1068
|
+
**Example:**
|
|
1069
|
+
```python
|
|
1070
|
+
# Move to workspace 0 (first workspace)
|
|
1071
|
+
wm.move_to_workspace(window_id, 0)
|
|
1072
|
+
|
|
1073
|
+
# Move to workspace 2 (third workspace)
|
|
1074
|
+
wm.move_to_workspace(window_id, 2)
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
---
|
|
1078
|
+
|
|
1079
|
+
## Types
|
|
1080
|
+
|
|
1081
|
+
### Point
|
|
1082
|
+
|
|
1083
|
+
**Full path**: `open_alo_core.types.Point`
|
|
1084
|
+
|
|
1085
|
+
2D screen coordinates.
|
|
1086
|
+
|
|
1087
|
+
**Definition:**
|
|
1088
|
+
```python
|
|
1089
|
+
class Point(NamedTuple):
|
|
1090
|
+
x: int
|
|
1091
|
+
y: int
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
**Example:**
|
|
1095
|
+
```python
|
|
1096
|
+
from open_alo_core import Point
|
|
1097
|
+
|
|
1098
|
+
# Create point
|
|
1099
|
+
p = Point(500, 500)
|
|
1100
|
+
print(p.x, p.y) # 500 500
|
|
1101
|
+
|
|
1102
|
+
# Use in click
|
|
1103
|
+
ctrl.click(Point(100, 200))
|
|
1104
|
+
|
|
1105
|
+
# Immutable
|
|
1106
|
+
p = Point(10, 20)
|
|
1107
|
+
# p.x = 30 # Error: cannot modify NamedTuple
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
---
|
|
1111
|
+
|
|
1112
|
+
### Size
|
|
1113
|
+
|
|
1114
|
+
**Full path**: `open_alo_core.types.Size`
|
|
1115
|
+
|
|
1116
|
+
Width and height dimensions.
|
|
1117
|
+
|
|
1118
|
+
**Definition:**
|
|
1119
|
+
```python
|
|
1120
|
+
class Size(NamedTuple):
|
|
1121
|
+
width: int
|
|
1122
|
+
height: int
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
**Example:**
|
|
1126
|
+
```python
|
|
1127
|
+
from open_alo_core import Size
|
|
1128
|
+
|
|
1129
|
+
size = Size(1920, 1080)
|
|
1130
|
+
print(f"{size.width}x{size.height}") # 1920x1080
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
---
|
|
1134
|
+
|
|
1135
|
+
### Rect
|
|
1136
|
+
|
|
1137
|
+
**Full path**: `open_alo_core.types.Rect`
|
|
1138
|
+
|
|
1139
|
+
Rectangle with position and size.
|
|
1140
|
+
|
|
1141
|
+
**Definition:**
|
|
1142
|
+
```python
|
|
1143
|
+
class Rect(NamedTuple):
|
|
1144
|
+
x: int
|
|
1145
|
+
y: int
|
|
1146
|
+
width: int
|
|
1147
|
+
height: int
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
**Properties:**
|
|
1151
|
+
- `center` → Point: Center point of rectangle
|
|
1152
|
+
- `top_left` → Point: Top-left corner
|
|
1153
|
+
- `bottom_right` → Point: Bottom-right corner
|
|
1154
|
+
|
|
1155
|
+
**Methods:**
|
|
1156
|
+
- `contains(point: Point) -> bool`: Check if point is inside rectangle
|
|
1157
|
+
|
|
1158
|
+
**Example:**
|
|
1159
|
+
```python
|
|
1160
|
+
from open_alo_core import Rect, Point
|
|
1161
|
+
|
|
1162
|
+
rect = Rect(100, 100, 800, 600)
|
|
1163
|
+
|
|
1164
|
+
# Get center
|
|
1165
|
+
center = rect.center # Point(500, 400)
|
|
1166
|
+
|
|
1167
|
+
# Get corners
|
|
1168
|
+
top_left = rect.top_left # Point(100, 100)
|
|
1169
|
+
bottom_right = rect.bottom_right # Point(900, 700)
|
|
1170
|
+
|
|
1171
|
+
# Check if point inside
|
|
1172
|
+
point = Point(500, 400)
|
|
1173
|
+
if rect.contains(point):
|
|
1174
|
+
print("Point is inside rectangle")
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
---
|
|
1178
|
+
|
|
1179
|
+
### WindowInfo
|
|
1180
|
+
|
|
1181
|
+
**Full path**: `open_alo_core.window_manager.WindowInfo`
|
|
1182
|
+
|
|
1183
|
+
Window information container (dataclass).
|
|
1184
|
+
|
|
1185
|
+
**Attributes:**
|
|
1186
|
+
- `id` (int): Window ID (unique during window lifetime)
|
|
1187
|
+
- `wm_class` (str): Application class (e.g., "org.gnome.Nautilus")
|
|
1188
|
+
- `wm_class_instance` (str): Class instance
|
|
1189
|
+
- `title` (str): Window title
|
|
1190
|
+
- `pid` (int): Process ID
|
|
1191
|
+
- `x` (int): X position
|
|
1192
|
+
- `y` (int): Y position
|
|
1193
|
+
- `width` (int): Width in pixels
|
|
1194
|
+
- `height` (int): Height in pixels
|
|
1195
|
+
- `workspace` (int): Workspace number (0-indexed)
|
|
1196
|
+
- `monitor` (int): Monitor number
|
|
1197
|
+
- `frame_type` (int): Frame type (0=normal, 1=frameless)
|
|
1198
|
+
- `window_type` (int): Window type (0=normal, 1=desktop, 2=dock, etc.)
|
|
1199
|
+
- `focus` (bool): Currently focused
|
|
1200
|
+
- `in_current_workspace` (bool): In current workspace
|
|
1201
|
+
- `maximized` (int): Maximized state
|
|
1202
|
+
|
|
1203
|
+
**Example:**
|
|
1204
|
+
```python
|
|
1205
|
+
windows = wm.list_windows()
|
|
1206
|
+
for win in windows:
|
|
1207
|
+
print(f"ID: {win.id}")
|
|
1208
|
+
print(f"Class: {win.wm_class}")
|
|
1209
|
+
print(f"Title: {win.title}")
|
|
1210
|
+
print(f"Position: ({win.x}, {win.y})")
|
|
1211
|
+
print(f"Size: {win.width}x{win.height}")
|
|
1212
|
+
print(f"Focused: {win.focus}")
|
|
1213
|
+
print(f"Workspace: {win.workspace}")
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
---
|
|
1217
|
+
|
|
1218
|
+
### WindowType
|
|
1219
|
+
|
|
1220
|
+
**Full path**: `open_alo_core.window_manager.WindowType`
|
|
1221
|
+
|
|
1222
|
+
Window type enumeration.
|
|
1223
|
+
|
|
1224
|
+
**Values:**
|
|
1225
|
+
- `NORMAL = 0`: Normal application window
|
|
1226
|
+
- `DESKTOP = 1`: Desktop window
|
|
1227
|
+
- `DOCK = 2`: Dock/panel window
|
|
1228
|
+
- `DIALOG = 3`: Dialog window
|
|
1229
|
+
- `MODAL_DIALOG = 4`: Modal dialog
|
|
1230
|
+
- `TOOLBAR = 5`: Toolbar window
|
|
1231
|
+
- `MENU = 6`: Menu window
|
|
1232
|
+
- `UTILITY = 7`: Utility window
|
|
1233
|
+
- `SPLASH = 8`: Splash screen
|
|
1234
|
+
|
|
1235
|
+
**Example:**
|
|
1236
|
+
```python
|
|
1237
|
+
from open_alo_core import WindowType
|
|
1238
|
+
|
|
1239
|
+
windows = wm.list_windows()
|
|
1240
|
+
normal_windows = [w for w in windows if w.window_type == WindowType.NORMAL]
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
---
|
|
1244
|
+
|
|
1245
|
+
### FrameType
|
|
1246
|
+
|
|
1247
|
+
**Full path**: `open_alo_core.window_manager.FrameType`
|
|
1248
|
+
|
|
1249
|
+
Window frame type enumeration.
|
|
1250
|
+
|
|
1251
|
+
**Values:**
|
|
1252
|
+
- `NORMAL = 0`: Normal window with decorations
|
|
1253
|
+
- `FRAMELESS = 1`: Frameless window (no decorations)
|
|
1254
|
+
|
|
1255
|
+
**Example:**
|
|
1256
|
+
```python
|
|
1257
|
+
from open_alo_core import FrameType
|
|
1258
|
+
|
|
1259
|
+
windows = wm.list_windows()
|
|
1260
|
+
framed = [w for w in windows if w.frame_type == FrameType.NORMAL]
|
|
1261
|
+
```
|
|
1262
|
+
|
|
1263
|
+
---
|
|
1264
|
+
|
|
1265
|
+
## Exceptions
|
|
1266
|
+
|
|
1267
|
+
All exceptions inherit from `CoreError` for easy catching.
|
|
1268
|
+
|
|
1269
|
+
### CoreError
|
|
1270
|
+
|
|
1271
|
+
**Full path**: `open_alo_core.exceptions.CoreError`
|
|
1272
|
+
|
|
1273
|
+
Base exception for all core errors.
|
|
1274
|
+
|
|
1275
|
+
**Example:**
|
|
1276
|
+
```python
|
|
1277
|
+
from open_alo_core import CoreError
|
|
1278
|
+
|
|
1279
|
+
try:
|
|
1280
|
+
# ... automation code ...
|
|
1281
|
+
pass
|
|
1282
|
+
except CoreError as e:
|
|
1283
|
+
print(f"Core error: {e}")
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
### PermissionDenied
|
|
1287
|
+
|
|
1288
|
+
**Full path**: `open_alo_core.exceptions.PermissionDenied`
|
|
1289
|
+
|
|
1290
|
+
User denied portal permission or insufficient privileges.
|
|
1291
|
+
|
|
1292
|
+
**Raised by:**
|
|
1293
|
+
- `WaylandInput.initialize()`
|
|
1294
|
+
- `WaylandCapture.capture_screen()`
|
|
1295
|
+
|
|
1296
|
+
**Example:**
|
|
1297
|
+
```python
|
|
1298
|
+
from open_alo_core import PermissionDenied
|
|
1299
|
+
|
|
1300
|
+
try:
|
|
1301
|
+
ctrl.initialize(persist_mode=2)
|
|
1302
|
+
except PermissionDenied:
|
|
1303
|
+
print("User denied permission")
|
|
1304
|
+
```
|
|
1305
|
+
|
|
1306
|
+
### CaptureError
|
|
1307
|
+
|
|
1308
|
+
**Full path**: `open_alo_core.exceptions.CaptureError`
|
|
1309
|
+
|
|
1310
|
+
Screen capture failed.
|
|
1311
|
+
|
|
1312
|
+
**Raised by:**
|
|
1313
|
+
- `WaylandCapture.capture_screen()`
|
|
1314
|
+
|
|
1315
|
+
**Example:**
|
|
1316
|
+
```python
|
|
1317
|
+
from open_alo_core import CaptureError
|
|
1318
|
+
|
|
1319
|
+
try:
|
|
1320
|
+
result = capture.capture_screen()
|
|
1321
|
+
except CaptureError as e:
|
|
1322
|
+
print(f"Capture failed: {e}")
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
### InputError
|
|
1326
|
+
|
|
1327
|
+
**Full path**: `open_alo_core.exceptions.InputError`
|
|
1328
|
+
|
|
1329
|
+
Input injection failed.
|
|
1330
|
+
|
|
1331
|
+
**Raised by:**
|
|
1332
|
+
- `WaylandInput.click()`
|
|
1333
|
+
- `WaylandInput.move_mouse()`
|
|
1334
|
+
- `WaylandInput.type_text()`
|
|
1335
|
+
- `WaylandInput.press_key()`
|
|
1336
|
+
- `WaylandInput.key_combo()`
|
|
1337
|
+
|
|
1338
|
+
**Example:**
|
|
1339
|
+
```python
|
|
1340
|
+
from open_alo_core import InputError
|
|
1341
|
+
|
|
1342
|
+
try:
|
|
1343
|
+
ctrl.click(Point(500, 500))
|
|
1344
|
+
except InputError as e:
|
|
1345
|
+
print(f"Click failed: {e}")
|
|
1346
|
+
```
|
|
1347
|
+
|
|
1348
|
+
### SessionError
|
|
1349
|
+
|
|
1350
|
+
**Full path**: `open_alo_core.exceptions.SessionError`
|
|
1351
|
+
|
|
1352
|
+
Portal session creation/management failed.
|
|
1353
|
+
|
|
1354
|
+
**Raised by:**
|
|
1355
|
+
- `WaylandInput.initialize()`
|
|
1356
|
+
|
|
1357
|
+
**Example:**
|
|
1358
|
+
```python
|
|
1359
|
+
from open_alo_core import SessionError
|
|
1360
|
+
|
|
1361
|
+
try:
|
|
1362
|
+
ctrl.initialize(persist_mode=2)
|
|
1363
|
+
except SessionError as e:
|
|
1364
|
+
print(f"Session error: {e}")
|
|
1365
|
+
```
|
|
1366
|
+
|
|
1367
|
+
### BackendNotAvailable
|
|
1368
|
+
|
|
1369
|
+
**Full path**: `open_alo_core.exceptions.BackendNotAvailable`
|
|
1370
|
+
|
|
1371
|
+
Requested backend not available on this system.
|
|
1372
|
+
|
|
1373
|
+
**Example:**
|
|
1374
|
+
```python
|
|
1375
|
+
from open_alo_core import BackendNotAvailable
|
|
1376
|
+
|
|
1377
|
+
try:
|
|
1378
|
+
# ... initialization ...
|
|
1379
|
+
pass
|
|
1380
|
+
except BackendNotAvailable:
|
|
1381
|
+
print("Backend not available on this system")
|
|
1382
|
+
```
|
|
1383
|
+
|
|
1384
|
+
---
|
|
1385
|
+
|
|
1386
|
+
## Utilities
|
|
1387
|
+
|
|
1388
|
+
### detect_session_type()
|
|
1389
|
+
|
|
1390
|
+
**Full path**: `open_alo_core.utils.detect_session_type`
|
|
1391
|
+
|
|
1392
|
+
Detect if running in Wayland or X11 session.
|
|
1393
|
+
|
|
1394
|
+
**Returns:**
|
|
1395
|
+
- `"wayland"`: Running on Wayland
|
|
1396
|
+
- `"x11"`: Running on X11
|
|
1397
|
+
- `"unknown"`: Cannot determine
|
|
1398
|
+
|
|
1399
|
+
**Example:**
|
|
1400
|
+
```python
|
|
1401
|
+
from open_alo_core import detect_session_type
|
|
1402
|
+
|
|
1403
|
+
session = detect_session_type()
|
|
1404
|
+
if session == "wayland":
|
|
1405
|
+
print("Using Wayland backend")
|
|
1406
|
+
elif session == "x11":
|
|
1407
|
+
print("Using X11 backend")
|
|
1408
|
+
else:
|
|
1409
|
+
print("Unknown session type")
|
|
1410
|
+
```
|
|
1411
|
+
|
|
1412
|
+
---
|
|
1413
|
+
|
|
1414
|
+
### is_wayland()
|
|
1415
|
+
|
|
1416
|
+
**Full path**: `open_alo_core.utils.is_wayland`
|
|
1417
|
+
|
|
1418
|
+
Check if running on Wayland.
|
|
1419
|
+
|
|
1420
|
+
**Returns:**
|
|
1421
|
+
- `bool`: True if WAYLAND_DISPLAY environment variable is set
|
|
1422
|
+
|
|
1423
|
+
**Example:**
|
|
1424
|
+
```python
|
|
1425
|
+
from open_alo_core import is_wayland
|
|
1426
|
+
|
|
1427
|
+
if is_wayland():
|
|
1428
|
+
ctrl = WaylandInput()
|
|
1429
|
+
else:
|
|
1430
|
+
raise RuntimeError("Wayland required")
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
---
|
|
1434
|
+
|
|
1435
|
+
### is_portal_available()
|
|
1436
|
+
|
|
1437
|
+
**Full path**: `open_alo_core.utils.is_portal_available`
|
|
1438
|
+
|
|
1439
|
+
Check if XDG Desktop Portal is available.
|
|
1440
|
+
|
|
1441
|
+
**Returns:**
|
|
1442
|
+
- `bool`: True if portal service is running
|
|
1443
|
+
|
|
1444
|
+
**Example:**
|
|
1445
|
+
```python
|
|
1446
|
+
from open_alo_core import is_portal_available
|
|
1447
|
+
|
|
1448
|
+
if not is_portal_available():
|
|
1449
|
+
print("Warning: Portal not available")
|
|
1450
|
+
print("Cannot initialize input/capture")
|
|
1451
|
+
```
|
|
1452
|
+
|
|
1453
|
+
---
|
|
1454
|
+
|
|
1455
|
+
### is_pipewire_available()
|
|
1456
|
+
|
|
1457
|
+
**Full path**: `open_alo_core.utils.is_pipewire_available`
|
|
1458
|
+
|
|
1459
|
+
Check if PipeWire is available for screen capture.
|
|
1460
|
+
|
|
1461
|
+
**Returns:**
|
|
1462
|
+
- `bool`: True if PipeWire is running
|
|
1463
|
+
|
|
1464
|
+
**Example:**
|
|
1465
|
+
```python
|
|
1466
|
+
from open_alo_core import is_pipewire_available
|
|
1467
|
+
|
|
1468
|
+
if is_pipewire_available():
|
|
1469
|
+
print("PipeWire available for capture")
|
|
1470
|
+
else:
|
|
1471
|
+
print("Warning: PipeWire not available")
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
---
|
|
1475
|
+
|
|
1476
|
+
### normalize_key()
|
|
1477
|
+
|
|
1478
|
+
**Full path**: `open_alo_core.types.normalize_key`
|
|
1479
|
+
|
|
1480
|
+
Normalize key name to standard form.
|
|
1481
|
+
|
|
1482
|
+
Converts common key aliases to GTK/GDK standard names.
|
|
1483
|
+
|
|
1484
|
+
**Parameters:**
|
|
1485
|
+
- `key` (str): Key name to normalize
|
|
1486
|
+
|
|
1487
|
+
**Returns:**
|
|
1488
|
+
- `str`: Normalized key name
|
|
1489
|
+
|
|
1490
|
+
**Example:**
|
|
1491
|
+
```python
|
|
1492
|
+
from open_alo_core import normalize_key
|
|
1493
|
+
|
|
1494
|
+
# Aliases normalized
|
|
1495
|
+
normalize_key("enter") # → "Return"
|
|
1496
|
+
normalize_key("ctrl") # → "Control"
|
|
1497
|
+
normalize_key("esc") # → "Escape"
|
|
1498
|
+
normalize_key("win") # → "Super"
|
|
1499
|
+
|
|
1500
|
+
# Already normalized
|
|
1501
|
+
normalize_key("Return") # → "Return"
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
---
|
|
1505
|
+
|
|
1506
|
+
## Constants
|
|
1507
|
+
|
|
1508
|
+
### Mouse Buttons
|
|
1509
|
+
|
|
1510
|
+
**Module**: `open_alo_core.types`
|
|
1511
|
+
|
|
1512
|
+
```python
|
|
1513
|
+
BUTTON_LEFT = 1 # Left mouse button
|
|
1514
|
+
BUTTON_MIDDLE = 2 # Middle mouse button
|
|
1515
|
+
BUTTON_RIGHT = 3 # Right mouse button
|
|
1516
|
+
```
|
|
1517
|
+
|
|
1518
|
+
**Example:**
|
|
1519
|
+
```python
|
|
1520
|
+
from open_alo_core import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT
|
|
1521
|
+
|
|
1522
|
+
ctrl.click(Point(500, 500), button=BUTTON_LEFT) # Left click
|
|
1523
|
+
ctrl.click(Point(500, 500), button=BUTTON_MIDDLE) # Middle click
|
|
1524
|
+
ctrl.click(Point(500, 500), button=BUTTON_RIGHT) # Right click
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
---
|
|
1528
|
+
|
|
1529
|
+
## Complete API Index
|
|
1530
|
+
|
|
1531
|
+
### Classes
|
|
1532
|
+
- `UnifiedRemoteDesktop` ⭐ **RECOMMENDED** - Single permission for input + capture
|
|
1533
|
+
- `WaylandInput` (Legacy) - Mouse and keyboard control
|
|
1534
|
+
- `WaylandCapture` (Legacy) - Screen capture
|
|
1535
|
+
- `WindowManager` - Window management
|
|
1536
|
+
- `Point` - 2D coordinates
|
|
1537
|
+
- `Size` - Dimensions
|
|
1538
|
+
- `Rect` - Rectangle with position and size
|
|
1539
|
+
- `WindowInfo` - Window information
|
|
1540
|
+
- `WindowType` - Window type enumeration
|
|
1541
|
+
- `FrameType` - Frame type enumeration
|
|
1542
|
+
- `CaptureResult` - Capture result container
|
|
1543
|
+
|
|
1544
|
+
### Exceptions
|
|
1545
|
+
- `CoreError` - Base exception
|
|
1546
|
+
- `PermissionDenied` - Permission denied
|
|
1547
|
+
- `CaptureError` - Capture failed
|
|
1548
|
+
- `InputError` - Input failed
|
|
1549
|
+
- `SessionError` - Session failed
|
|
1550
|
+
- `BackendNotAvailable` - Backend unavailable
|
|
1551
|
+
|
|
1552
|
+
### Functions
|
|
1553
|
+
- `detect_session_type()` - Detect Wayland/X11
|
|
1554
|
+
- `is_wayland()` - Check if Wayland
|
|
1555
|
+
- `is_portal_available()` - Check portal availability
|
|
1556
|
+
- `is_pipewire_available()` - Check PipeWire availability
|
|
1557
|
+
- `normalize_key()` - Normalize key name
|
|
1558
|
+
- `list_windows()` - List windows (convenience)
|
|
1559
|
+
- `find_window()` - Find window (convenience)
|
|
1560
|
+
- `activate_window()` - Activate window (convenience)
|
|
1561
|
+
- `get_focused_window()` - Get focused window (convenience)
|
|
1562
|
+
|
|
1563
|
+
### Constants
|
|
1564
|
+
- `BUTTON_LEFT` - Left mouse button (1)
|
|
1565
|
+
- `BUTTON_MIDDLE` - Middle mouse button (2)
|
|
1566
|
+
- `BUTTON_RIGHT` - Right mouse button (3)
|
|
1567
|
+
|
|
1568
|
+
### UnifiedRemoteDesktop Methods (Recommended) ⭐
|
|
1569
|
+
- `initialize(persist_mode, enable_capture)` → bool - Initialize with one dialog
|
|
1570
|
+
- `capture_screenshot()` → bytes - Take PNG screenshot
|
|
1571
|
+
- `get_frame()` → bytes - Get real-time frame
|
|
1572
|
+
- `get_screen_size()` → (int, int) - Get resolution
|
|
1573
|
+
- `click(point, button)` - Click at coordinates
|
|
1574
|
+
- `move_mouse(point)` - Move cursor
|
|
1575
|
+
- `type_text(text, interval)` - Type text
|
|
1576
|
+
- `press_key(key)` - Press single key
|
|
1577
|
+
- `key_combo(keys)` - Press key combination
|
|
1578
|
+
- `close()` - Release resources
|
|
1579
|
+
|
|
1580
|
+
### WaylandInput Methods (Legacy)
|
|
1581
|
+
- `initialize(persist_mode)` - Initialize session
|
|
1582
|
+
- `click(point, button)` - Click at coordinates
|
|
1583
|
+
- `move_mouse(point)` - Move cursor
|
|
1584
|
+
- `type_text(text, interval)` - Type text
|
|
1585
|
+
- `press_key(key)` - Press single key
|
|
1586
|
+
- `key_combo(keys)` - Press key combination
|
|
1587
|
+
- `close()` - Release resources
|
|
1588
|
+
|
|
1589
|
+
### WaylandCapture Methods (Legacy)
|
|
1590
|
+
- `capture_screen()` - Capture screen
|
|
1591
|
+
- `close()` - Release resources
|
|
1592
|
+
|
|
1593
|
+
### WindowManager Methods
|
|
1594
|
+
|
|
1595
|
+
**Listing & Search:**
|
|
1596
|
+
- `list_windows(current_workspace_only)` - List windows
|
|
1597
|
+
- `find_window(query, match_title)` - Find window
|
|
1598
|
+
- `find_all_windows(query, match_title)` - Find all matching
|
|
1599
|
+
- `get_focused_window()` - Get focused window
|
|
1600
|
+
- `get_details(window_id)` - Get detailed info
|
|
1601
|
+
- `get_title(window_id)` - Get window title
|
|
1602
|
+
|
|
1603
|
+
**State Management:**
|
|
1604
|
+
- `activate(window_id)` - Activate window
|
|
1605
|
+
- `maximize(window_id)` - Maximize
|
|
1606
|
+
- `unmaximize(window_id)` - Unmaximize
|
|
1607
|
+
- `minimize(window_id)` - Minimize
|
|
1608
|
+
- `unminimize(window_id)` - Unminimize
|
|
1609
|
+
- `close(window_id)` - Close window
|
|
1610
|
+
|
|
1611
|
+
**Positioning:**
|
|
1612
|
+
- `move(window_id, x, y)` - Move window
|
|
1613
|
+
- `resize(window_id, width, height)` - Resize window
|
|
1614
|
+
- `move_resize(window_id, x, y, width, height)` - Move and resize
|
|
1615
|
+
- `get_frame_rect(window_id)` - Get frame rectangle
|
|
1616
|
+
- `get_frame_bounds(window_id)` - Get frame bounds
|
|
1617
|
+
|
|
1618
|
+
**Workspace:**
|
|
1619
|
+
- `move_to_workspace(window_id, workspace_num)` - Move to workspace
|
|
1620
|
+
|
|
1621
|
+
---
|
|
1622
|
+
|
|
1623
|
+
## Advanced Usage Examples
|
|
1624
|
+
|
|
1625
|
+
### Complete Workflow
|
|
1626
|
+
|
|
1627
|
+
```python
|
|
1628
|
+
from open_alo_core import (
|
|
1629
|
+
WaylandInput, WaylandCapture, WindowManager,
|
|
1630
|
+
Point, activate_window
|
|
1631
|
+
)
|
|
1632
|
+
|
|
1633
|
+
# 1. Activate target application
|
|
1634
|
+
activate_window("Text Editor")
|
|
1635
|
+
|
|
1636
|
+
# 2. Control input
|
|
1637
|
+
with WaylandInput() as ctrl:
|
|
1638
|
+
ctrl.initialize(persist_mode=2)
|
|
1639
|
+
|
|
1640
|
+
# Click and type
|
|
1641
|
+
ctrl.click(Point(500, 500))
|
|
1642
|
+
ctrl.type_text("Hello World!")
|
|
1643
|
+
|
|
1644
|
+
# Save with Ctrl+S
|
|
1645
|
+
ctrl.key_combo(["Control", "s"])
|
|
1646
|
+
|
|
1647
|
+
# 3. Capture screen
|
|
1648
|
+
with WaylandCapture() as capture:
|
|
1649
|
+
result = capture.capture_screen()
|
|
1650
|
+
Path("/tmp/screenshot.png").write_bytes(result.data)
|
|
1651
|
+
|
|
1652
|
+
print("Workflow complete!")
|
|
1653
|
+
```
|
|
1654
|
+
|
|
1655
|
+
### Window Tiling
|
|
1656
|
+
|
|
1657
|
+
```python
|
|
1658
|
+
from open_alo_core import WindowManager
|
|
1659
|
+
|
|
1660
|
+
wm = WindowManager()
|
|
1661
|
+
|
|
1662
|
+
# Get windows
|
|
1663
|
+
editor = wm.find_window("Text Editor")
|
|
1664
|
+
browser = wm.find_window("Brave")
|
|
1665
|
+
|
|
1666
|
+
if editor and browser:
|
|
1667
|
+
# Tile left half (1920x1080 screen)
|
|
1668
|
+
wm.move_resize(editor.id, 0, 0, 960, 1080)
|
|
1669
|
+
|
|
1670
|
+
# Tile right half
|
|
1671
|
+
wm.move_resize(browser.id, 960, 0, 960, 1080)
|
|
1672
|
+
```
|
|
1673
|
+
|
|
1674
|
+
### Automated Testing
|
|
1675
|
+
|
|
1676
|
+
```python
|
|
1677
|
+
from open_alo_core import WaylandInput, WaylandCapture, Point
|
|
1678
|
+
import time
|
|
1679
|
+
|
|
1680
|
+
with WaylandInput() as ctrl:
|
|
1681
|
+
ctrl.initialize(persist_mode=2)
|
|
1682
|
+
|
|
1683
|
+
# Test workflow
|
|
1684
|
+
steps = [
|
|
1685
|
+
(Point(100, 100), "Click button"),
|
|
1686
|
+
(Point(200, 200), "Fill form"),
|
|
1687
|
+
]
|
|
1688
|
+
|
|
1689
|
+
for point, description in steps:
|
|
1690
|
+
print(f"Step: {description}")
|
|
1691
|
+
ctrl.click(point)
|
|
1692
|
+
time.sleep(1)
|
|
1693
|
+
|
|
1694
|
+
# Capture result
|
|
1695
|
+
with WaylandCapture() as cap:
|
|
1696
|
+
result = cap.capture_screen()
|
|
1697
|
+
Path(f"/tmp/{description}.png").write_bytes(result.data)
|
|
1698
|
+
```
|
|
1699
|
+
|
|
1700
|
+
---
|
|
1701
|
+
|
|
1702
|
+
## Version History
|
|
1703
|
+
|
|
1704
|
+
### 0.1.0 (Current)
|
|
1705
|
+
- Initial release
|
|
1706
|
+
- Wayland MVP
|
|
1707
|
+
- Input control via RemoteDesktop Portal
|
|
1708
|
+
- Screen capture via ScreenCast Portal
|
|
1709
|
+
- Window management via GNOME Shell D-Bus
|
|
1710
|
+
- Persistent permission tokens
|
|
1711
|
+
- Type-safe API with NamedTuples and dataclasses
|
|
1712
|
+
|
|
1713
|
+
---
|
|
1714
|
+
|
|
1715
|
+
## License
|
|
1716
|
+
|
|
1717
|
+
MIT License - See LICENSE file for details
|
|
1718
|
+
|
|
1719
|
+
---
|
|
1720
|
+
|
|
1721
|
+
## Contributing
|
|
1722
|
+
|
|
1723
|
+
Contributions welcome! Please ensure:
|
|
1724
|
+
- Type hints for all public APIs
|
|
1725
|
+
- Docstrings with examples
|
|
1726
|
+
- Error handling with appropriate exceptions
|
|
1727
|
+
- Tests for new functionality
|
|
1728
|
+
|
|
1729
|
+
---
|
|
1730
|
+
|
|
1731
|
+
## Support
|
|
1732
|
+
|
|
1733
|
+
- **Issues**: GitHub Issues
|
|
1734
|
+
- **Documentation**: This file
|
|
1735
|
+
- **Examples**: `examples/` directory
|