Skip to content

Commit 47ed0b5

Browse files
authored
Merge pull request #30 from sentialx/monitors
feat: add monitors support
2 parents f34e560 + 9718f19 commit 47ed0b5

File tree

9 files changed

+220
-73
lines changed

9 files changed

+220
-73
lines changed

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
"functional": "cpp",
4646
"list": "cpp",
4747
"unordered_map": "cpp",
48-
"xhash": "cpp"
48+
"xhash": "cpp",
49+
"map": "cpp",
50+
"xtree": "cpp"
4951
}
5052
}

README.md

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,18 @@ window.setBounds({ x: 0, y: 0 });
5959

6060
- Returns [`Window`](#class-window)
6161

62-
#### windowManager.getScaleFactor(monitor: number) `Windows`
63-
64-
- Returns `number` - the monitor scale factor.
65-
6662
#### windowManager.getWindows() `Windows` `macOS`
6763

6864
- Returns [`Window[]`](#class-window)
6965

66+
#### windowManager.getMonitors() `Windows`
67+
68+
- Returns [`Monitor[]`](#class-monitor)
69+
70+
#### windowManager.getPrimaryMonitor() `Windows`
71+
72+
- Returns [`Monitor`](#class-monitor)
73+
7074
### Events
7175

7276
#### Event 'window-activated' `Windows` `macOS`
@@ -151,9 +155,9 @@ Returns `number` between 0 and 1.
151155

152156
#### win.getMonitor() `Windows`
153157

154-
Gets monitor by window.
158+
Gets monitor which the window belongs to.
155159

156-
Returns `number` - monitor handle.
160+
Returns [`Monitor`](#class-monitor)
157161

158162
#### win.isWindow() `Windows` `macOS`
159163

@@ -175,4 +179,43 @@ Returns [`Window`](#class-window)
175179

176180
- `size` number - can be only `16`, `32`, `64`, `256`. By default it's `64`.
177181

178-
Returns a png Buffer
182+
Returns a png Buffer
183+
184+
185+
## Class `Monitor` `Windows`
186+
187+
### new Monitor(id: number)
188+
189+
- `id` number - the monitor handle
190+
191+
### Instance properties
192+
193+
- `id` number
194+
195+
### Instance methods
196+
197+
#### monitor.getBounds() `Windows`
198+
199+
- Returns [`Rectangle`](#object-rectangle)
200+
201+
#### monitor.getWorkArea() `Windows`
202+
203+
Gets monitor working area bounds.
204+
205+
- Returns [`Rectangle`](#object-rectangle)
206+
207+
#### monitor.getInfo() `Windows`
208+
209+
Returns [`MonitorInfo`](#object-monitorinfo)
210+
211+
#### monitor.isPrimary() `Windows`
212+
213+
Whether the monitor is primary.
214+
215+
- Returns `boolean`
216+
217+
#### monitor.getScaleFactor() `Windows`
218+
219+
Gets monitor scale factor (DPI).
220+
221+
- Returns `number`

example.js

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

3+
windowManager.requestAccessibility(); // required on macOS
4+
35
const window = windowManager.getActiveWindow();
46
console.log(window.getTitle());
57

@@ -13,7 +15,12 @@ setTimeout(() => {
1315
window.setBounds(bounds);
1416
}, 1000);
1517

16-
console.log("Windows List");
18+
console.log("Windows list");
1719
windowManager.getWindows().forEach(window => {
1820
console.log(window.getInfo());
19-
});
21+
});
22+
23+
console.log("Monitors list");
24+
windowManager.getMonitors().forEach(monitor => {
25+
console.log(monitor.getInfo());
26+
});

lib/macos.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ AXUIElementRef getAXWindow(int pid, int handle) {
5050
for (NSDictionary *info in (NSArray *)windowList) {
5151
NSNumber *ownerPid = info[(id)kCGWindowOwnerPID];
5252
NSNumber *windowNumber = info[(id)kCGWindowNumber];
53-
auto app = [NSRunningApplication runningApplicationWithProcessIdentifier: [ownerPid intValue]];
5453

54+
auto app = [NSRunningApplication runningApplicationWithProcessIdentifier: [ownerPid intValue]];
5555
auto path = app ? [app.bundleURL.path UTF8String] : "";
5656

57-
if (path != "") {
57+
if (app && path != "") {
5858
vec.push_back(Napi::Number::New(env, [windowNumber intValue]));
5959
}
6060
}

lib/windows.cc

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@ struct Process {
1313
std::string path;
1414
};
1515

16-
struct Window {
17-
Process process;
18-
int64_t id;
19-
};
20-
2116
template <typename T>
2217
T getValueFromCallbackData (const Napi::CallbackInfo& info, unsigned handleIndex) {
2318
return reinterpret_cast<T> (info[handleIndex].As<Napi::Number> ().Int64Value ());
@@ -58,11 +53,10 @@ Napi::Number getActiveWindow (const Napi::CallbackInfo& info) {
5853
return Napi::Number::New (env, reinterpret_cast<int64_t> (handle));
5954
}
6055

61-
std::vector<Window> _windows;
56+
std::vector<int64_t> _windows;
6257

6358
BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lparam) {
64-
auto process = getWindowProcess (hwnd);
65-
_windows.push_back ({ process, reinterpret_cast<int64_t> (hwnd) });
59+
_windows.push_back (reinterpret_cast<int64_t> (hwnd));
6660
return TRUE;
6761
}
6862

@@ -75,13 +69,38 @@ Napi::Array getWindows (const Napi::CallbackInfo& info) {
7569
auto arr = Napi::Array::New (env);
7670
auto i = 0;
7771
for (auto _win : _windows) {
78-
if (_win.process.path.empty ()) continue;
79-
arr.Set (i++, Napi::Number::New (env, _win.id));
72+
arr.Set (i++, Napi::Number::New (env, _win));
8073
}
8174

8275
return arr;
8376
}
8477

78+
std::vector<int64_t> _monitors;
79+
80+
BOOL CALLBACK EnumMonitorsProc (HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
81+
_monitors.push_back (reinterpret_cast<int64_t> (hMonitor));
82+
return TRUE;
83+
}
84+
85+
Napi::Array getMonitors (const Napi::CallbackInfo& info) {
86+
Napi::Env env{ info.Env () };
87+
88+
_monitors.clear ();
89+
if (EnumDisplayMonitors (NULL, NULL, &EnumMonitorsProc, NULL)) {
90+
auto arr = Napi::Array::New (env);
91+
auto i = 0;
92+
93+
for (auto _mon : _monitors) {
94+
95+
arr.Set (i++, Napi::Number::New (env, _mon));
96+
}
97+
98+
return arr;
99+
}
100+
101+
return Napi::Array::New (env);
102+
}
103+
85104
Napi::Number getMonitorFromWindow (const Napi::CallbackInfo& info) {
86105
Napi::Env env{ info.Env () };
87106

@@ -247,6 +266,38 @@ Napi::Boolean isVisible (const Napi::CallbackInfo& info) {
247266
return Napi::Boolean::New (env, IsWindowVisible (handle));
248267
}
249268

269+
Napi::Object getMonitorInfo (const Napi::CallbackInfo& info) {
270+
Napi::Env env{ info.Env () };
271+
272+
auto handle{ getValueFromCallbackData<HMONITOR> (info, 0) };
273+
274+
MONITORINFO mInfo;
275+
mInfo.cbSize = sizeof (MONITORINFO);
276+
GetMonitorInfoA (handle, &mInfo);
277+
278+
Napi::Object bounds{ Napi::Object::New (env) };
279+
280+
bounds.Set ("x", mInfo.rcMonitor.left);
281+
bounds.Set ("y", mInfo.rcMonitor.top);
282+
bounds.Set ("width", mInfo.rcMonitor.right - mInfo.rcMonitor.left);
283+
bounds.Set ("height", mInfo.rcMonitor.bottom - mInfo.rcMonitor.top);
284+
285+
Napi::Object workArea{ Napi::Object::New (env) };
286+
287+
workArea.Set ("x", mInfo.rcWork.left);
288+
workArea.Set ("y", mInfo.rcWork.top);
289+
workArea.Set ("width", mInfo.rcWork.right - mInfo.rcWork.left);
290+
workArea.Set ("height", mInfo.rcWork.bottom - mInfo.rcWork.top);
291+
292+
Napi::Object obj{ Napi::Object::New (env) };
293+
294+
obj.Set ("bounds", bounds);
295+
obj.Set ("workArea", workArea);
296+
obj.Set ("isPrimary", (mInfo.dwFlags & MONITORINFOF_PRIMARY) != 0);
297+
298+
return obj;
299+
}
300+
250301
Napi::Object Init (Napi::Env env, Napi::Object exports) {
251302
exports.Set (Napi::String::New (env, "getActiveWindow"), Napi::Function::New (env, getActiveWindow));
252303
exports.Set (Napi::String::New (env, "getMonitorFromWindow"), Napi::Function::New (env, getMonitorFromWindow));
@@ -263,7 +314,9 @@ Napi::Object Init (Napi::Env env, Napi::Object exports) {
263314
Napi::Function::New (env, toggleWindowTransparency));
264315
exports.Set (Napi::String::New (env, "setWindowOwner"), Napi::Function::New (env, setWindowOwner));
265316
exports.Set (Napi::String::New (env, "getWindowInfo"), Napi::Function::New (env, getWindowInfo));
317+
exports.Set (Napi::String::New (env, "getMonitorInfo"), Napi::Function::New (env, getMonitorInfo));
266318
exports.Set (Napi::String::New (env, "getWindows"), Napi::Function::New (env, getWindows));
319+
exports.Set (Napi::String::New (env, "getMonitors"), Napi::Function::New (env, getMonitors));
267320

268321
return exports;
269322
}

src/classes/monitor.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { addon } from "..";
2+
import { IMonitorInfo, IRectangle } from "../interfaces";
3+
import { release } from "os";
4+
5+
export class Monitor {
6+
public id: number;
7+
8+
constructor(id: number) {
9+
if (process.platform !== 'win32' || !addon) return;
10+
11+
this.id = id;
12+
}
13+
14+
getInfo(): IMonitorInfo {
15+
if (process.platform !== 'win32' || !addon) return;
16+
return addon.getMonitorInfo(this.id);
17+
}
18+
19+
getBounds(): IRectangle {
20+
if (process.platform !== 'win32' || !addon) return;
21+
return this.getInfo().bounds;
22+
}
23+
24+
getWorkArea(): IRectangle {
25+
if (process.platform !== 'win32' || !addon) return;
26+
return this.getInfo().workArea;
27+
}
28+
29+
isPrimary(): boolean {
30+
if (process.platform !== 'win32' || !addon) return;
31+
return this.getInfo().isPrimary;
32+
}
33+
34+
getScaleFactor(): number {
35+
if (process.platform !== 'win32' || !addon) return;
36+
37+
const numbers = release()
38+
.split(".")
39+
.map(d => parseInt(d, 10));
40+
41+
if (numbers[0] > 8 || (numbers[0] === 8 && numbers[1] >= 1)) {
42+
return addon.getMonitorScaleFactor(this.id);
43+
}
44+
45+
return 1;
46+
};
47+
}

src/classes/window.ts

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,8 @@
11
import { platform } from "os";
2-
import { windowManager } from "..";
2+
import { addon } from "..";
33
import extractFileIcon from 'extract-file-icon';
4-
5-
let addon: any;
6-
7-
if (platform() === "win32" || platform() === "darwin") {
8-
let path_addon: string = (process.env.NODE_ENV != "dev") ? "Release" : "Debug";
9-
addon = require(`../../build/${path_addon}/addon.node`);
10-
}
11-
12-
interface Rectangle {
13-
x?: number;
14-
y?: number;
15-
width?: number;
16-
height?: number;
17-
}
18-
19-
interface WindowInfo {
20-
id: number;
21-
path: string;
22-
processId: number;
23-
title?: string;
24-
bounds?: Rectangle;
25-
opacity?: number;
26-
owner?: number;
27-
}
4+
import { Monitor } from "./monitor";
5+
import { IRectangle, IWindowInfo } from "../interfaces";
286

297
export class Window {
308
public id: number;
@@ -41,13 +19,13 @@ export class Window {
4119
this.path = path;
4220
}
4321

44-
getBounds(): Rectangle {
22+
getBounds(): IRectangle {
4523
if (!addon) return;
4624

4725
const { bounds } = this.getInfo();
4826

4927
if (platform() === "win32") {
50-
const sf = windowManager.getScaleFactor(this.getMonitor());
28+
const sf = this.getMonitor().getScaleFactor();
5129

5230
bounds.x = Math.floor(bounds.x / sf);
5331
bounds.y = Math.floor(bounds.y / sf);
@@ -58,13 +36,13 @@ export class Window {
5836
return bounds;
5937
}
6038

61-
setBounds(bounds: Rectangle) {
39+
setBounds(bounds: IRectangle) {
6240
if (!addon) return;
6341

6442
const newBounds = { ...this.getBounds(), ...bounds };
6543

6644
if (platform() === "win32") {
67-
const sf = windowManager.getScaleFactor(this.getMonitor());
45+
const sf = this.getMonitor().getScaleFactor();
6846

6947
newBounds.x = Math.floor(newBounds.x * sf);
7048
newBounds.y = Math.floor(newBounds.y * sf);
@@ -82,9 +60,9 @@ export class Window {
8260
return this.getInfo().title;
8361
}
8462

85-
getMonitor(): number {
63+
getMonitor(): Monitor {
8664
if (!addon || !addon.getMonitorFromWindow) return;
87-
return addon.getMonitorFromWindow(this.id);
65+
return new Monitor(addon.getMonitorFromWindow(this.id));
8866
}
8967

9068
show() {
@@ -190,11 +168,9 @@ export class Window {
190168
return new Window(this.getInfo().owner);
191169
}
192170

193-
getInfo(): WindowInfo {
171+
getInfo(): IWindowInfo {
194172
if (!addon) return;
195-
196173
const info = addon.getWindowInfo(this.id);
197-
198174
return info;
199175
}
200176
}

0 commit comments

Comments
 (0)