11package accessibility
22
33import (
4- "context"
54 "fmt"
65 "image"
7- "strings"
8- "time"
96)
107
11- // Query represents a query for UI elements
12- type Query struct {
13- Role string
14- Title string
15- TitleContains string
16- Enabled * bool
17- Clickable * bool
18- Scrollable * bool
19- MinWidth int
20- MinHeight int
21- MaxResults int
22- Timeout time.Duration
23- }
24-
258func rectFromInfo (info * ElementInfo ) image.Rectangle {
269 return image .Rect (
2710 info .Position .X ,
@@ -40,179 +23,6 @@ func expandRectangle(rect image.Rectangle, padding int) image.Rectangle {
4023 )
4124}
4225
43- // QueryBuilder helps build queries
44- type QueryBuilder struct {
45- query Query
46- }
47-
48- // NewQuery creates a new query builder
49- func NewQuery () * QueryBuilder {
50- return & QueryBuilder {
51- query : Query {
52- MaxResults : 100 ,
53- Timeout : 5 * time .Second ,
54- },
55- }
56- }
57-
58- // WithRole sets the role filter
59- func (qb * QueryBuilder ) WithRole (role string ) * QueryBuilder {
60- qb .query .Role = role
61- return qb
62- }
63-
64- // WithTitle sets the exact title filter
65- func (qb * QueryBuilder ) WithTitle (title string ) * QueryBuilder {
66- qb .query .Title = title
67- return qb
68- }
69-
70- // WithTitleContains sets the title contains filter
71- func (qb * QueryBuilder ) WithTitleContains (text string ) * QueryBuilder {
72- qb .query .TitleContains = text
73- return qb
74- }
75-
76- // WithEnabled sets the enabled filter
77- func (qb * QueryBuilder ) WithEnabled (enabled bool ) * QueryBuilder {
78- qb .query .Enabled = & enabled
79- return qb
80- }
81-
82- // WithClickable sets the clickable filter
83- func (qb * QueryBuilder ) WithClickable (clickable bool ) * QueryBuilder {
84- qb .query .Clickable = & clickable
85- return qb
86- }
87-
88- // WithScrollable sets the scrollable filter
89- func (qb * QueryBuilder ) WithScrollable (scrollable bool ) * QueryBuilder {
90- qb .query .Scrollable = & scrollable
91- return qb
92- }
93-
94- // WithMinSize sets minimum size filter
95- func (qb * QueryBuilder ) WithMinSize (width , height int ) * QueryBuilder {
96- qb .query .MinWidth = width
97- qb .query .MinHeight = height
98- return qb
99- }
100-
101- // WithMaxResults sets the maximum number of results
102- func (qb * QueryBuilder ) WithMaxResults (max int ) * QueryBuilder {
103- qb .query .MaxResults = max
104- return qb
105- }
106-
107- // WithTimeout sets the query timeout
108- func (qb * QueryBuilder ) WithTimeout (timeout time.Duration ) * QueryBuilder {
109- qb .query .Timeout = timeout
110- return qb
111- }
112-
113- // Build returns the built query
114- func (qb * QueryBuilder ) Build () Query {
115- return qb .query
116- }
117-
118- // Execute executes the query on the given tree
119- func (q Query ) Execute (tree * TreeNode ) ([]* TreeNode , error ) {
120- ctx , cancel := context .WithTimeout (context .Background (), q .Timeout )
121- defer cancel ()
122-
123- results := make ([]* TreeNode , 0 )
124- resultsChan := make (chan * TreeNode , 100 )
125- done := make (chan struct {})
126-
127- // Start collection goroutine
128- go func () {
129- for node := range resultsChan {
130- results = append (results , node )
131- if q .MaxResults > 0 && len (results ) >= q .MaxResults {
132- break
133- }
134- }
135- close (done )
136- }()
137-
138- // Execute query
139- q .executeRecursive (ctx , tree , resultsChan )
140- close (resultsChan )
141-
142- // Wait for collection to finish
143- select {
144- case <- done :
145- case <- ctx .Done ():
146- return nil , fmt .Errorf ("query timeout" )
147- }
148-
149- return results , nil
150- }
151-
152- func (q Query ) executeRecursive (ctx context.Context , node * TreeNode , results chan <- * TreeNode ) {
153- select {
154- case <- ctx .Done ():
155- return
156- default :
157- }
158-
159- if q .matches (node ) {
160- select {
161- case results <- node :
162- case <- ctx .Done ():
163- return
164- }
165- }
166-
167- for _ , child := range node .Children {
168- q .executeRecursive (ctx , child , results )
169- }
170- }
171-
172- func (q Query ) matches (node * TreeNode ) bool {
173- info := node .Info
174-
175- // Role filter
176- if q .Role != "" && info .Role != q .Role {
177- return false
178- }
179-
180- // Title filter
181- if q .Title != "" && info .Title != q .Title {
182- return false
183- }
184-
185- // Title contains filter
186- if q .TitleContains != "" && ! strings .Contains (strings .ToLower (info .Title ), strings .ToLower (q .TitleContains )) {
187- return false
188- }
189-
190- // Enabled filter
191- if q .Enabled != nil && info .IsEnabled != * q .Enabled {
192- return false
193- }
194-
195- // Clickable filter
196- if q .Clickable != nil && node .Element .IsClickable () != * q .Clickable {
197- return false
198- }
199-
200- // Scrollable filter
201- if q .Scrollable != nil && node .Element .IsScrollable () != * q .Scrollable {
202- return false
203- }
204-
205- // Size filters
206- if q .MinWidth > 0 && info .Size .X < q .MinWidth {
207- return false
208- }
209- if q .MinHeight > 0 && info .Size .Y < q .MinHeight {
210- return false
211- }
212-
213- return true
214- }
215-
21626// GetClickableElements returns all clickable elements in the frontmost window
21727func GetClickableElements () ([]* TreeNode , error ) {
21828 window := GetFrontmostWindow ()
@@ -279,36 +89,6 @@ func GetScrollableElements() ([]*TreeNode, error) {
27989 return tree .FindScrollableElements (), nil
28090}
28191
282- // SearchElements searches for elements matching the query
283- func SearchElements (query Query ) ([]* TreeNode , error ) {
284- window := GetFrontmostWindow ()
285- if window == nil {
286- return nil , fmt .Errorf ("no frontmost window" )
287- }
288- defer window .Release ()
289-
290- opts := DefaultTreeOptions ()
291- opts .MaxDepth = 15
292-
293- tree , err := BuildTree (window , opts )
294- if err != nil {
295- return nil , err
296- }
297-
298- return query .Execute (tree )
299- }
300-
301- // FuzzySearch performs a fuzzy search for elements by title
302- func FuzzySearch (searchText string , maxResults int ) ([]* TreeNode , error ) {
303- query := NewQuery ().
304- WithTitleContains (searchText ).
305- WithEnabled (true ).
306- WithMaxResults (maxResults ).
307- Build ()
308-
309- return SearchElements (query )
310- }
311-
31292// GetMenuBarClickableElements returns clickable elements from the focused app's menu bar
31393func GetMenuBarClickableElements () ([]* TreeNode , error ) {
31494 app := GetFocusedApplication ()
0 commit comments