Widget detection and interaction techniques: Bubble Cursor and Semantic Pointing
Project description
TargetFinder Toolkit
This toolkit accompanies the work presented in the article TargetFinder: Detecting Widgets from Pixels on Desktop Interfaces.
It provides a real-time detection system using the YOLOv8 model to predict the bounding boxes of GUI widgets from desktop screenshots — without requiring access to application internals or accessibility APIs.
The system is lightweight and easy to integrate, enabling the implementation of advanced interaction techniques.
As proof of concept, we include two interaction techniques built on top of TargetFinder:
Compatibility Note
TargetFinder uses themsslibrary for fast screen capture, and the detection engine is theoretically cross-platform. However, the system has been validated only on Windows 10/11 and Linux (Ubuntu X11). Operation is not guaranteed on macOS, where additional adaptations are required.
Other Linux setups (e.g., distributions other than Ubuntu, or Wayland instead of X11) may also require adjustments.
Installation
pip install .
Linux prerequisites (click to expand)
During installation on Linux, you may need to install some system packages to avoid common errors:
-
evdev build tools
If installation fails due to missingevdevheaders:sudo apt install build-essential python3-dev
-
X11 vs Wayland screen capture
mssrelies on X11 (does not work with Wayland sinceXGetImageis not available).
If you see the following error:mss.exception.ScreenShotError: XGetImage() failedSwitch to an X11 session at login.
-
Qt X11 plugin (
xcb)
If you encounter errors like:qt.qpa.plugin: Could not load the Qt platform plugin "xcb" ...Install the required libraries:
sudo apt install libxcb-cursor0 libxkbcommon-x11-0 libxcb-xinerama0
-
tk (MouseInfo) support
pyautoguiorpynputmay fail iftkis missing:sudo apt install python3-tk python3-dev
Python API & Examples
Minimal examples:
Print detection changes (callback)
import time
from target_finder_toolkit.targetfinder import TargetFinder
from target_finder_toolkit import postprocess as pp
def on_change(detections, added, removed, frame):
"""
Called every time the detector refreshes.
- detections: [{id, x, y, width, height, score, class_id, class_name} ...]
- added: new detections since last callback
- removed: detections that disappeared since last callback
- frame: RGB numpy array (only if with_frame=True), else None
"""
pp.pretty_print_change(detections, added, removed)
if __name__ == "__main__":
det = TargetFinder()
det.set_callback(on_change, with_frame=False, diff_iou=0.5)
det.start()
print("TargetFinder started — press Ctrl+C to stop.")
try:
while True:
time.sleep(0.2)
except KeyboardInterrupt:
det.stop()
print("Stopped.")
Save one annotated frame + crops
import time
from pathlib import Path
from target_finder_toolkit.targetfinder import TargetFinder
from target_finder_toolkit import postprocess as pp
# global flag to ensure we only save once
_saved_once = False
def on_change(detections, added, removed, frame):
"""
Save a single annotated frame and crops (first time we get detections+frame).
"""
global _saved_once
if _saved_once:
return
if frame is None or not detections:
return
out_dir = Path("out")
out_dir.mkdir(parents=True, exist_ok=True)
# Save annotated frame
annot_path = out_dir / "annotated.png"
pp.save_annotated(annot_path, frame, detections)
# Extract and save crops
crops = pp.extract_crops(frame, detections)
crop_paths = pp.save_crops(out_dir / "crops", crops, prefix="crop", ext=".png")
print(f"Saved annotated frame: {annot_path}")
print(f"Saved {len(crop_paths)} crops under: {out_dir/'crops'}")
_saved_once = True
if __name__ == "__main__":
det = TargetFinder()
# with_frame=True is required to receive the RGB frame in the callback
det.set_callback(on_change, with_frame=True, diff_iou=0.5)
det.start()
print("TargetFinder started — will save once when detections are available. Ctrl+C to stop.")
try:
while True:
time.sleep(0.2)
except KeyboardInterrupt:
det.stop()
print("Stopped.")
Detect on a static image
from target_finder_toolkit.targetfinder import TargetFinder
if __name__ == "__main__":
image_path = "screenshot.png" # change this to your image
det = TargetFinder()
detections = det.detect_image(
image_path,
save_annotated=True, # creates e.g. screenshot_annotated.png
save_json=True # creates e.g. screenshot_detections.json
)
Documentation
For the full API reference and detailed explanations of all parameters,
visit the documentation site:
👉 Documentation (API & Developer Guide)
Demos using TargetFinder
TargetFinder GUI
After installation, targetfinder-gui: launches the main overlay GUI.
| Windows | Linux |
|---|---|
| Full video: (see in /demo/Videos/) | Full video: (see in /demo/Videos/) |
Bubble Cursor
After installation, bubblecursor runs the Bubble Cursor interaction technique.
| Windows | Linux |
|---|---|
| Full video: (see in /demo/Videos/) | Full video: (see in /demo/Videos/) |
Semantic Pointing
After installation, semanticpointing runs the Semantic Pointing interaction technique.
| Windows | Linux |
|---|---|
| Full video: (see in /demo/Videos/) | Full video: (see in /demo/Videos/) |
Available options:
| Option | Description |
|---|---|
--model-path |
By default, TargetFinder loads our trained model YOLOv8n packaged with the toolkit, but you can supply your own. |
--change-thresh |
Screen change detection threshold. A higher value makes detection less sensitive to small variations. (default = 100). |
--capture-interval |
Time between screen captures in seconds. Lower values = higher frequency but more CPU/GPU usage. (default = 1/30 ≈ 0.033s). |
--confidence |
Minimum YOLO confidence required to keep a detection. ([0.0–1.0], default = 0.28). |
--iou |
IoU threshold for non-max suppression (controls overlap merging). ([0.0–1.0], default = 0.3). |
--display (semanticpointing only) |
Show visual feedback (motor vs visual space). |
--disable-accel (semanticpointing only) |
Disable system mouse acceleration. |
Example:
bubblecursor \
--change-thresh 100 \
--capture-interval 0.033 \
--confidence 0.28 \
--iou 0.3
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file target_finder_toolkit-0.1.3.tar.gz.
File metadata
- Download URL: target_finder_toolkit-0.1.3.tar.gz
- Upload date:
- Size: 5.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ddf2b7db599da700121573914e10eeed72b9b021dd511dda68cb8565ca0825a
|
|
| MD5 |
ec1bbbb685052cd847396e32be0b840e
|
|
| BLAKE2b-256 |
4b21188027c0091d4d2aeba651354b2f3f66110c206c750953a17163349d4be7
|
File details
Details for the file target_finder_toolkit-0.1.3-py3-none-any.whl.
File metadata
- Download URL: target_finder_toolkit-0.1.3-py3-none-any.whl
- Upload date:
- Size: 5.7 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
992328f8f2dfe5e478117416a236adcf148c5c9464dd5fb2226b4d9866f0556f
|
|
| MD5 |
da2cbe222cc31a028dd95fd110bcf869
|
|
| BLAKE2b-256 |
22bf05baa2e8c067c60221811af978260e0238cbccf138cc93395b9ebe6546d3
|