src/backend/
src/backend/mod.rs
#![allow(unused)]
fn main() {
pub mod atspi;
pub mod imageproc;
#[cfg(feature = "ocr")]
pub mod ocrs;
}
src/backend/atspi.rs
Async D-Bus accessibility tree walker.
#![allow(unused)]
fn main() {
pub struct AtspiBackend {
conn: Connection,
rule: ApplicationRule,
window_info: WindowInfo,
scale_factor: f64,
}
impl AtspiBackend {
pub async fn new(
window_info: WindowInfo,
rule: ApplicationRule,
) -> Result<Self, Box<dyn std::error::Error>>
pub async fn get_children(
&self,
) -> Result<Vec<Child>, Box<dyn std::error::Error>>
}
}
get_children() flow
find_active_window()— iterate D-Bus tree for app → window matching PID + Active statewalk_children(proxy, &mut children, 0)— recursive, max depth 20, max 500/level- Batch-fetch children via
futures::join_all - Query role, state, extents via
tokio::join! - Filter: ALL of (Sensitive, Showing, Visible) AND NONE of EXCLUDED_ROLES
- Role →
ChildKind::Textifis_text_role(), elseElement - Recurse via
Box::pin(self.walk_children(...))
- Batch-fetch children via
Private methods
#![allow(unused)]
fn main() {
async fn find_active_window(
&self,
) -> Result<Option<AccessibleProxy<'_>>, zbus::Error>
async fn walk_children(
&self,
proxy: &AccessibleProxy<'_>,
children: &mut Vec<Child>,
depth: usize,
) -> Result<(), Box<dyn std::error::Error>>
fn is_text_role(role: i32) -> bool
}
is_text_role() matches
| Role ID | Name |
|---|---|
| 36 | Label |
| 74 | Text |
| 87 | DocumentText |
| 116 | Static |
| 73 | Paragraph |
| 83 | Heading |
src/backend/imageproc.rs
Computer vision: screenshot → edge detection → BFS → text lines.
Statics
#![allow(unused)]
fn main() {
pub static DEBUG_BFS_COMPONENTS: Mutex<Vec<Child>>
pub static SAVE_DEBUG_IMAGES: AtomicBool
}
Public functions
#![allow(unused)]
fn main() {
pub fn get_children(
window_info: &WindowInfo,
rule: &ApplicationRule,
) -> Result<Vec<Child>, Box<dyn std::error::Error>>
}
Pipeline
- Screenshot: X11
GetImage(Z_PIXMAP, root, x, y, w, h)→ BGRA buffer - Dual grayscale (single pass over BGRA):
luma:0.299R + 0.587G + 0.114B— text detectionprocess_img:max(R, G, B)— preserves color edge contrast
- Canny edge detection on
process_img:- Resize by
detection_scale(Nearest neighbor) imageproc::edges::canny(src, canny_min_val, canny_max_val)
- Resize by
- Text word detection (
detect_text_words):- Resize
lumaby same scale - Horizontal projection → text line bands (threshold 0.5% width, min 8px, max gap 2% height)
- Vertical projection per line → word segments (gap threshold 25% line-height, min 3px gap, min 4px word)
- Scale back by
inv_scale
- Resize
- Dilation:
imageproc::morphology::dilate(edges, LInf, kernel_size/2) - BFS: 4-direction flood fill → connected components as
ChildKind::Element - Filter: Remove components >50% of window
- Overlap analysis:
- BFS overlapping text words >95% →
ChildKind::Text - Words overlapping 2+ BFS → added as separate
Text
- BFS overlapping text words >95% →
- Debug images: Saved to
/tmp/qhints_debug/ifSAVE_DEBUG_IMAGES
Private functions
#![allow(unused)]
fn main() {
fn detect_text_words(
edges: &GrayImage,
_luma: &GrayImage,
img_w: u32,
img_h: u32,
win_x: u32,
win_y: u32,
) -> Vec<Child>
fn draw_boxes(
luma: &GrayImage,
words: &[Child],
all_bfs: &[Child],
kept: &[Child],
path: &Path,
) -> Result<(), Box<dyn std::error::Error>>
}
src/backend/ocrs.rs (feature-gated)
OCR-based detection via ocrs + rten.
Constants
#![allow(unused)]
fn main() {
const DETECTION_MODEL: &str = "https://ocrs-models.s3-accelerate.amazonaws.com/text-detection.rten";
const RECOGNITION_MODEL: &str = "https://ocrs-models.s3-accelerate.amazonaws.com/text-recognition.rten";
}
Public functions
#![allow(unused)]
fn main() {
pub fn get_children(
window_info: &WindowInfo,
rule: &ApplicationRule,
) -> Result<Vec<Child>, Box<dyn std::error::Error>>
}
Pipeline
- Screenshot: X11
GetImage(same as imageproc) - RGB conversion: BGRA → flat RGB array; also build luma for BFS
- Model download: Download models to
~/.cache/qhints/ocrs/- Skips if already cached
- OCR:
ocrs::OcrEngine::newwith detection + recognition modelsengine.prepare_input(ImageSource)→ocr_inputengine.detect_words(&ocr_input)→ word bounding boxes asChildKind::Text
- BFS gap-filling: Canny + dilation + BFS on luma →
ChildKind::Element - Filter: Remove BFS components overlapping OCR words >30%
- Merge: BFS first, then word boxes appended
Private functions
#![allow(unused)]
fn main() {
fn cache_dir() -> Result<PathBuf, Box<dyn std::error::Error>>
fn download_model(
url: &str,
path: &Path,
) -> Result<(), Box<dyn std::error::Error>>
}