Skip to content

Commit 1b2aa11

Browse files
authored
Merge pull request #31 from sentialx/macos
refactor: remove `window.getInfo`
2 parents 67c619c + df1d597 commit 1b2aa11

File tree

7 files changed

+218
-124
lines changed

7 files changed

+218
-124
lines changed

example.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
const { windowManager } = require("./dist/index");
22

3-
windowManager.requestAccessibility(); // required on macOS
3+
console.log(windowManager.requestAccessibility()); // required on macOS
44

55
const window = windowManager.getActiveWindow();
66
console.log(window.getTitle());
77

88
const bounds = window.getBounds();
99
console.log(bounds);
1010

11-
// window.setBounds({ x: 0, y: 0 });
11+
window.setBounds({ x: 0, y: 0 });
1212
window.maximize();
1313

1414
setTimeout(() => {
@@ -17,10 +17,16 @@ setTimeout(() => {
1717

1818
console.log("Windows list");
1919
windowManager.getWindows().forEach(window => {
20-
console.log(window.getInfo());
20+
if (window.isVisible()) {
21+
console.log(window.path);
22+
}
23+
});
24+
25+
windowManager.on('window-activated', (window) => {
26+
console.log(window.path);
2127
});
2228

2329
console.log("Monitors list");
2430
windowManager.getMonitors().forEach(monitor => {
25-
console.log(monitor.getInfo());
31+
console.log(monitor.getWorkArea());
2632
});

lib/macos.mm

Lines changed: 116 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,39 @@
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

910
extern "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+
1120
Napi::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

2240
AXUIElementRef 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+
4292
Napi::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) {
192259
Napi::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+
215283
Napi::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

Comments
 (0)