33#import < ApplicationServices/ApplicationServices.h>
44#include < napi.h>
55#include < string>
6- #include < iostream>
76#include < map>
7+ #include < thread>
8+ #include < fstream>
89
910extern " C" AXError _AXUIElementGetWindow (AXUIElementRef, CGWindowID* out);
1011
12+ // CGWindowID to AXUIElementRef windows map
13+ std::map<int , AXUIElementRef> windowsMap;
14+
15+ bool _requestAccessibility (bool showDialog) {
16+ NSDictionary * opts = @{static_cast <id > (kAXTrustedCheckOptionPrompt ): showDialog ? @YES : @NO };
17+ return AXIsProcessTrustedWithOptions (static_cast <CFDictionaryRef> (opts));
18+ }
19+
1120Napi::Boolean requestAccessibility (const Napi::CallbackInfo &info) {
1221 Napi::Env env{info.Env ()};
13-
14- NSDictionary * opts = @{static_cast <id > (kAXTrustedCheckOptionPrompt ): @YES };
15- BOOL a = AXIsProcessTrustedWithOptions (static_cast <CFDictionaryRef> (opts));
16-
17- return Napi::Boolean::New (env, a);
22+ return Napi::Boolean::New (env, _requestAccessibility (true ));
1823}
1924
20- std::map<int , AXUIElementRef> m;
25+ NSDictionary * getWindowInfo (int handle) {
26+ CGWindowListOption listOptions = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements ;
27+ CFArrayRef windowList = CGWindowListCopyWindowInfo (listOptions, kCGNullWindowID );
28+
29+ for (NSDictionary *info in (NSArray *)windowList) {
30+ NSNumber *windowNumber = info[(id )kCGWindowNumber ];
31+
32+ if ([windowNumber intValue ] == handle) {
33+ return info;
34+ }
35+ }
36+
37+ return NULL ;
38+ }
2139
2240AXUIElementRef getAXWindow (int pid, int handle) {
2341 auto app = AXUIElementCreateApplication (pid);
@@ -39,6 +57,38 @@ AXUIElementRef getAXWindow(int pid, int handle) {
3957 return NULL ;
4058}
4159
60+ void cacheWindow (int handle, int pid) {
61+ if (_requestAccessibility (false )) {
62+ if (windowsMap.find (handle) == windowsMap.end ()) {
63+ windowsMap[handle] = getAXWindow (pid, handle);
64+ }
65+ }
66+ }
67+
68+ void cacheWindowByInfo (NSDictionary * info) {
69+ if (info) {
70+ NSNumber *ownerPid = info[(id )kCGWindowOwnerPID ];
71+ NSNumber *windowNumber = info[(id )kCGWindowNumber ];
72+
73+ cacheWindow ([windowNumber intValue ], [ownerPid intValue ]);
74+ }
75+ }
76+
77+ void findAndCacheWindow (int handle) {
78+ cacheWindowByInfo (getWindowInfo (handle));
79+ }
80+
81+ AXUIElementRef getAXWindowById (int handle) {
82+ auto win = windowsMap[handle];
83+
84+ if (!win) {
85+ findAndCacheWindow (handle);
86+ win = windowsMap[handle];
87+ }
88+
89+ return win;
90+ }
91+
4292Napi::Array getWindows (const Napi::CallbackInfo &info) {
4393 Napi::Env env{info.Env ()};
4494
@@ -74,57 +124,74 @@ AXUIElementRef getAXWindow(int pid, int handle) {
74124 CGWindowListOption listOptions = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements ;
75125 CFArrayRef windowList = CGWindowListCopyWindowInfo (listOptions, kCGNullWindowID );
76126
77- auto app = [NSWorkspace sharedWorkspace ].frontmostApplication ;
78-
79127 for (NSDictionary *info in (NSArray *)windowList) {
80128 NSNumber *ownerPid = info[(id )kCGWindowOwnerPID ];
81129 NSNumber *windowNumber = info[(id )kCGWindowNumber ];
82130
83- if ([ownerPid intValue ] != app.processIdentifier ) continue ;
131+ auto app = [NSRunningApplication runningApplicationWithProcessIdentifier: [ownerPid intValue ]];
132+
133+ if (![app isActive ]) continue ;
84134
85135 return Napi::Number::New (env, [windowNumber intValue ]);
86- }
136+ }
87137
88138 return Napi::Number::New (env, 0 );
89139}
90140
91- Napi::Object getWindowInfo (const Napi::CallbackInfo &info) {
141+ Napi::Object initWindow (const Napi::CallbackInfo &info) {
92142 Napi::Env env{info.Env ()};
93143
94- CGWindowListOption listOptions = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements ;
95- CFArrayRef windowList = CGWindowListCopyWindowInfo (listOptions, kCGNullWindowID );
96-
97144 int handle = info[0 ].As <Napi::Number>().Int32Value ();
98145
99- for (NSDictionary *info in (NSArray *)windowList) {
100- NSNumber *ownerPid = info[(id )kCGWindowOwnerPID ];
101- NSNumber *windowNumber = info[(id )kCGWindowNumber ];
102- NSString *windowName = info[(id )kCGWindowName ];
103-
104- if ([windowNumber intValue ] != handle) continue ;
146+ auto wInfo = getWindowInfo (handle);
105147
148+ if (wInfo) {
149+ NSNumber *ownerPid = wInfo[(id )kCGWindowOwnerPID ];
106150 auto app = [NSRunningApplication runningApplicationWithProcessIdentifier: [ownerPid intValue ]];
107151
108- CGRect bounds;
109- CGRectMakeWithDictionaryRepresentation ((CFDictionaryRef)info[(id )kCGWindowBounds ], &bounds);
110-
111152 auto obj = Napi::Object::New (env);
112- auto boundsObj = Napi::Object::New (env);
113-
114- boundsObj.Set (" x" , bounds.origin .x );
115- boundsObj.Set (" y" , bounds.origin .y );
116- boundsObj.Set (" width" , bounds.size .width );
117- boundsObj.Set (" height" , bounds.size .height );
118-
119- obj.Set (" id" , [windowNumber intValue ]);
120153 obj.Set (" processId" , [ownerPid intValue ]);
121154 obj.Set (" path" , [app.bundleURL.path UTF8String ]);
122- obj.Set (" bounds" , boundsObj);
123- obj.Set (" title" , [windowName UTF8String ]);
124155
125- if (m.find ([windowNumber intValue ]) == m.end ()) {
126- m[[windowNumber intValue ]] = getAXWindow ([ownerPid intValue ], [windowNumber intValue ]);
127- }
156+ cacheWindow (handle, [ownerPid intValue ]);
157+
158+ return obj;
159+ }
160+
161+ return Napi::Object::New (env);
162+ }
163+
164+ Napi::String getWindowTitle (const Napi::CallbackInfo &info) {
165+ Napi::Env env{info.Env ()};
166+
167+ int handle = info[0 ].As <Napi::Number>().Int32Value ();
168+
169+ auto wInfo = getWindowInfo (handle);
170+
171+ if (wInfo) {
172+ NSString *windowName = wInfo[(id )kCGWindowName ];
173+ return Napi::String::New (env, [windowName UTF8String ]);
174+ }
175+
176+ return Napi::String::New (env, " " );
177+ }
178+
179+ Napi::Object getWindowBounds (const Napi::CallbackInfo &info) {
180+ Napi::Env env{info.Env ()};
181+
182+ int handle = info[0 ].As <Napi::Number>().Int32Value ();
183+
184+ auto wInfo = getWindowInfo (handle);
185+
186+ if (wInfo) {
187+ CGRect bounds;
188+ CGRectMakeWithDictionaryRepresentation ((CFDictionaryRef)wInfo[(id )kCGWindowBounds ], &bounds);
189+
190+ auto obj = Napi::Object::New (env);
191+ obj.Set (" x" , bounds.origin .x );
192+ obj.Set (" y" , bounds.origin .y );
193+ obj.Set (" width" , bounds.size .width );
194+ obj.Set (" height" , bounds.size .height );
128195
129196 return obj;
130197 }
@@ -143,7 +210,7 @@ AXUIElementRef getAXWindow(int pid, int handle) {
143210 auto width = bounds.Get (" width" ).As <Napi::Number>().DoubleValue ();
144211 auto height = bounds.Get (" height" ).As <Napi::Number>().DoubleValue ();
145212
146- auto win = m[ handle] ;
213+ auto win = getAXWindowById ( handle) ;
147214
148215 if (win) {
149216 NSPoint point = NSMakePoint ((CGFloat) x, (CGFloat) y);
@@ -166,7 +233,7 @@ AXUIElementRef getAXWindow(int pid, int handle) {
166233 auto pid = info[1 ].As <Napi::Number>().Int32Value ();
167234
168235 auto app = AXUIElementCreateApplication (pid);
169- auto win = m[ handle] ;
236+ auto win = getAXWindowById ( handle) ;
170237
171238 AXUIElementSetAttributeValue (app, kAXFrontmostAttribute , kCFBooleanTrue );
172239 AXUIElementSetAttributeValue (win, kAXMainAttribute , kCFBooleanTrue );
@@ -180,7 +247,7 @@ AXUIElementRef getAXWindow(int pid, int handle) {
180247 auto handle = info[0 ].As <Napi::Number>().Int32Value ();
181248 auto toggle = info[1 ].As <Napi::Boolean>();
182249
183- auto win = m[ handle] ;
250+ auto win = getAXWindowById ( handle) ;
184251
185252 if (win) {
186253 AXUIElementSetAttributeValue (win, kAXMinimizedAttribute , toggle ? kCFBooleanTrue : kCFBooleanFalse );
@@ -192,7 +259,7 @@ AXUIElementRef getAXWindow(int pid, int handle) {
192259Napi::Boolean setWindowMaximized (const Napi::CallbackInfo &info) {
193260 Napi::Env env{info.Env ()};
194261 auto handle = info[0 ].As <Napi::Number>().Int32Value ();
195- auto win = m[ handle] ;
262+ auto win = getAXWindowById ( handle) ;
196263
197264 if (win) {
198265 NSRect screenSizeRect = [[NSScreen mainScreen ] frame ];
@@ -212,15 +279,20 @@ AXUIElementRef getAXWindow(int pid, int handle) {
212279 return Napi::Boolean::New (env, true );
213280}
214281
282+
215283Napi::Object Init (Napi::Env env, Napi::Object exports) {
216284 exports.Set (Napi::String::New (env, " getWindows" ),
217285 Napi::Function::New (env, getWindows));
218286 exports.Set (Napi::String::New (env, " getActiveWindow" ),
219287 Napi::Function::New (env, getActiveWindow));
220- exports.Set (Napi::String::New (env, " getWindowInfo" ),
221- Napi::Function::New (env, getWindowInfo));
222288 exports.Set (Napi::String::New (env, " setWindowBounds" ),
223289 Napi::Function::New (env, setWindowBounds));
290+ exports.Set (Napi::String::New (env, " getWindowBounds" ),
291+ Napi::Function::New (env, getWindowBounds));
292+ exports.Set (Napi::String::New (env, " getWindowTitle" ),
293+ Napi::Function::New (env, getWindowTitle));
294+ exports.Set (Napi::String::New (env, " initWindow" ),
295+ Napi::Function::New (env, initWindow));
224296 exports.Set (Napi::String::New (env, " bringWindowToTop" ),
225297 Napi::Function::New (env, bringWindowToTop));
226298 exports.Set (Napi::String::New (env, " setWindowMinimized" ),
@@ -229,6 +301,7 @@ AXUIElementRef getAXWindow(int pid, int handle) {
229301 Napi::Function::New (env, setWindowMaximized));
230302 exports.Set (Napi::String::New (env, " requestAccessibility" ),
231303 Napi::Function::New (env, requestAccessibility));
304+
232305 return exports;
233306}
234307
0 commit comments