Skip to content

Commit 6b026ea

Browse files
authored
Merge pull request #23 from nut-tree/feature/17/retrieve_window_dimensions
Retrieve window dimensions
2 parents 366626e + 0021b37 commit 6b026ea

19 files changed

+6050
-19
lines changed

.travis.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ script:
3838
- npm run patch && npm i
3939
- if [[ $TRAVIS_OS_NAME = "windows" ]]; then npm run build:release:win; else npm run build:release; fi
4040
- cd test
41-
- npm ci
42-
- npm test
43-
- cd ..
41+
- npm cit
42+
- cd window-integration-tests
43+
- npm cit
44+
- cd ../..
4445

4546
before_deploy:
4647
- echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" >> $HOME/.npmrc 2> /dev/null

CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ project(libnut)
66
# Source
77
set(SOURCE_FILES "src/main.cc" "src/deadbeef_rand.c" "src/keycode.c" "src/keypress.c" "src/MMBitmap.c" "src/mouse.c" "src/screen.c" "src/screengrab.c")
88
if (UNIX AND NOT APPLE)
9-
set(SOURCE_FILES "${SOURCE_FILES}" "src/linux/xdisplay.c" "src/linux/highlightwindow.c")
9+
set(SOURCE_FILES "${SOURCE_FILES}" "src/linux/xdisplay.c" "src/linux/highlightwindow.c" "src/linux/window_manager.cc")
1010
elseif (UNIX AND APPLE)
11-
set(SOURCE_FILES "${SOURCE_FILES}" "src/macos/highlightwindow.m")
11+
set(SOURCE_FILES "${SOURCE_FILES}" "src/macos/highlightwindow.m" "src/macos/window_manager.mm")
1212
elseif (WIN32)
13-
set(SOURCE_FILES "${SOURCE_FILES}" "src/win32/highlightwindow.c")
13+
set(SOURCE_FILES "${SOURCE_FILES}" "src/win32/highlightwindow.c" "src/win32/window_manager.cc")
1414
endif()
1515
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC})
1616

appveyor.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ test_script:
3333
- npm --version
3434
# run tests
3535
- cd test
36-
- npm ci
37-
- npm test test
38-
- npm test run test:it
39-
- cd ..
36+
- npm cit
37+
- npm run test:it
38+
- cd window-integration-tests
39+
- npm cit
40+
- cd ../..
4041

4142
on_success:
4243
- IF defined APPVEYOR_REPO_TAG_NAME npm publish

index.d.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,23 @@ export interface Screen {
1212
highlight(x: number, y: number, width: number, height: number, duration: number, opacity: number): void;
1313
}
1414

15+
export interface Point {
16+
x: number;
17+
y: number
18+
}
19+
20+
export interface Size {
21+
width: number;
22+
height: number;
23+
}
24+
25+
export interface Rect {
26+
x: number;
27+
y: number;
28+
width: number;
29+
height: number;
30+
}
31+
1532
export function setKeyboardDelay(ms: number): void;
1633
export function keyTap(key: string, modifier?: string | string[]): void;
1734
export function keyToggle(
@@ -28,7 +45,11 @@ export function mouseClick(button?: string, double?: boolean): void;
2845
export function mouseToggle(down?: string, button?: string): void;
2946
export function dragMouse(x: number, y: number): void;
3047
export function scrollMouse(x: number, y: number): void;
31-
export function getMousePos(): { x: number; y: number };
32-
export function getScreenSize(): { width: number; height: number };
48+
export function getMousePos(): Point;
49+
export function getScreenSize(): Size;
50+
export function getWindows(): number[];
51+
export function getActiveWindow(): number;
52+
export function getWindowRect(handle: number): Rect;
53+
export function getWindowTitle(handle: number): string;
3354

3455
export const screen: Screen;

src/linux/window_manager.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include <X11/Xlib.h>
2+
#include <X11/Xutil.h>
3+
#include "../window_manager.h"
4+
extern "C" {
5+
#include "../xdisplay.h"
6+
}
7+
8+
WindowHandle getActiveWindow() {
9+
Display* xServer = XGetMainDisplay();
10+
Window window;
11+
if (xServer != NULL) {
12+
int32_t revertToWindow;
13+
XGetInputFocus(xServer, &window, &revertToWindow);
14+
return window;
15+
}
16+
return -1;
17+
}
18+
19+
std::vector<WindowHandle> getWindows() {
20+
Display* xServer = XGetMainDisplay();
21+
std::vector<WindowHandle> windowHandles;
22+
if (xServer != NULL) {
23+
Window defaultRootWindow = DefaultRootWindow(xServer);
24+
Window rootWindow;
25+
Window parentWindow;
26+
Window* windowList;
27+
uint32_t windowCount;
28+
29+
Status queryTreeResult = XQueryTree(xServer, defaultRootWindow, &rootWindow, &parentWindow, &windowList, &windowCount);
30+
if (queryTreeResult > 0) {
31+
for (size_t idx = 0; idx < windowCount; ++idx) {
32+
windowHandles.push_back(windowList[idx]);
33+
}
34+
}
35+
}
36+
return windowHandles;
37+
}
38+
39+
std::string getWindowTitle(const WindowHandle windowHandle) {
40+
Display* xServer = XGetMainDisplay();
41+
std::string windowName = "";
42+
if (xServer != NULL && windowHandle >= 0) {
43+
/*
44+
* While there's also `XFetchName` to retrieve a window's `WM_NAME` property, in my tests `XFetchName` always failed and return 0
45+
* `XGetWMName` on the other hand just worked as expected.
46+
* Keep that in mind in case you're considering changing this implementation
47+
*/
48+
XTextProperty windowTextProperty;
49+
Status getWMNameResult = XGetWMName(xServer, windowHandle, &windowTextProperty);
50+
if (getWMNameResult > 0) {
51+
windowName = std::string(reinterpret_cast<const char*>(windowTextProperty.value));
52+
}
53+
}
54+
return windowName;
55+
}
56+
57+
MMRect getWindowRect(const WindowHandle windowHandle) {
58+
Display* xServer = XGetMainDisplay();
59+
MMRect windowRect = MMRectMake(0, 0, 0, 0);
60+
if (xServer != NULL && windowHandle >= 0) {
61+
Window rootWindow;
62+
int32_t x, y;
63+
uint32_t width, height, border_width, border_height;
64+
Status getXGeometryResult = XGetGeometry(xServer, windowHandle, &rootWindow, &x, &y, &width, &height, &border_width, &border_height);
65+
if (getXGeometryResult > 0) {
66+
windowRect = MMRectMake(x, y, width, height);
67+
}
68+
}
69+
return windowRect;
70+
}

src/macos/window_manager.mm

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#import <Foundation/Foundation.h>
2+
#import <AppKit/AppKit.h>
3+
#import <ApplicationServices/ApplicationServices.h>
4+
#include "../window_manager.h"
5+
6+
NSDictionary* getWindowInfo(int64_t windowHandle) {
7+
CGWindowListOption listOptions = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
8+
CFArrayRef windowList = CGWindowListCopyWindowInfo(listOptions, kCGNullWindowID);
9+
10+
for (NSDictionary *info in (NSArray *)windowList) {
11+
NSNumber *windowNumber = info[(id)kCGWindowNumber];
12+
13+
if (windowHandle == [windowNumber intValue]) {
14+
CFRetain(info);
15+
CFRelease(windowList);
16+
return info;
17+
}
18+
}
19+
20+
if (windowList) {
21+
CFRelease(windowList);
22+
}
23+
24+
return nullptr;
25+
}
26+
27+
WindowHandle getActiveWindow() {
28+
CGWindowListOption listOptions = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
29+
CFArrayRef windowList = CGWindowListCopyWindowInfo(listOptions, kCGNullWindowID);
30+
31+
for (NSDictionary *info in (NSArray *)windowList) {
32+
NSNumber *ownerPid = info[(id)kCGWindowOwnerPID];
33+
NSNumber *windowNumber = info[(id)kCGWindowNumber];
34+
35+
auto app = [NSRunningApplication runningApplicationWithProcessIdentifier: [ownerPid intValue]];
36+
37+
if (![app isActive]) {
38+
continue;
39+
}
40+
41+
CFRelease(windowList);
42+
return [windowNumber intValue];
43+
}
44+
45+
if (windowList) {
46+
CFRelease(windowList);
47+
}
48+
return -1;
49+
}
50+
51+
std::vector<WindowHandle> getWindows() {
52+
CGWindowListOption listOptions = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
53+
CFArrayRef windowList = CGWindowListCopyWindowInfo(listOptions, kCGNullWindowID);
54+
55+
std::vector<WindowHandle> windowHandles;
56+
57+
for (NSDictionary *info in (NSArray *)windowList) {
58+
NSNumber *ownerPid = info[(id)kCGWindowOwnerPID];
59+
NSNumber *windowNumber = info[(id)kCGWindowNumber];
60+
61+
auto app = [NSRunningApplication runningApplicationWithProcessIdentifier: [ownerPid intValue]];
62+
auto path = app ? [app.bundleURL.path UTF8String] : "";
63+
64+
if (app && path != "") {
65+
windowHandles.push_back([windowNumber intValue]);
66+
}
67+
}
68+
69+
if (windowList) {
70+
CFRelease(windowList);
71+
}
72+
73+
return windowHandles;
74+
}
75+
76+
MMRect getWindowRect(const WindowHandle windowHandle) {
77+
auto windowInfo = getWindowInfo(windowHandle);
78+
if (windowInfo != nullptr && windowHandle >= 0) {
79+
CGRect windowRect;
80+
if (CGRectMakeWithDictionaryRepresentation((CFDictionaryRef)windowInfo[(id)kCGWindowBounds], &windowRect)) {
81+
return MMRectMake(windowRect.origin.x, windowRect.origin.y, windowRect.size.width, windowRect.size.height);
82+
}
83+
}
84+
return MMRectMake(0, 0, 0, 0);
85+
}
86+
87+
std::string getWindowTitle(const WindowHandle windowHandle) {
88+
auto windowInfo = getWindowInfo(windowHandle);
89+
if (windowInfo != nullptr && windowHandle >= 0) {
90+
NSString *windowName = windowInfo[(id)kCGWindowName];
91+
return [windowName UTF8String];
92+
}
93+
return "";
94+
}

src/main.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "mouse.h"
88
#include "screen.h"
99
#include "screengrab.h"
10+
#include "window_manager.h"
1011

1112
int mouseDelay = 10;
1213
int keyboardDelay = 10;
@@ -668,6 +669,48 @@ Napi::Number _highlight(const Napi::CallbackInfo &info)
668669
return Napi::Number::New(env, 1);
669670
}
670671

672+
Napi::Number _getActiveWindow(const Napi::CallbackInfo &info) {
673+
Napi::Env env = info.Env();
674+
675+
WindowHandle windowHandle = getActiveWindow();
676+
return Napi::Number::New(env, windowHandle);
677+
}
678+
679+
Napi::Array _getWindows(const Napi::CallbackInfo &info) {
680+
Napi::Env env = info.Env();
681+
682+
std::vector<WindowHandle> windowHandles = getWindows();
683+
auto arr = Napi::Array::New(env, windowHandles.size());
684+
685+
for (size_t idx = 0; idx < windowHandles.size(); ++idx) {
686+
arr[idx] = windowHandles[idx];
687+
}
688+
689+
return arr;
690+
}
691+
692+
Napi::Object _getWindowRect(const Napi::CallbackInfo &info) {
693+
Napi::Env env = info.Env();
694+
695+
WindowHandle windowHandle = info[0].As<Napi::Number>().Int64Value();
696+
MMRect windowRect = getWindowRect(windowHandle);
697+
698+
Napi::Object obj = Napi::Object::New(env);
699+
obj.Set(Napi::String::New(env, "x"), Napi::Number::New(env, windowRect.origin.x));
700+
obj.Set(Napi::String::New(env, "y"), Napi::Number::New(env, windowRect.origin.y));
701+
obj.Set(Napi::String::New(env, "width"), Napi::Number::New(env, windowRect.size.width));
702+
obj.Set(Napi::String::New(env, "height"), Napi::Number::New(env, windowRect.size.height));
703+
704+
return obj;
705+
}
706+
707+
Napi::String _getWindowTitle(const Napi::CallbackInfo &info) {
708+
Napi::Env env = info.Env();
709+
710+
WindowHandle windowHandle = info[0].As<Napi::Number>().Int64Value();
711+
return Napi::String::New(env, getWindowTitle(windowHandle));
712+
}
713+
671714
Napi::Object _captureScreen(const Napi::CallbackInfo &info)
672715
{
673716
Napi::Env env = info.Env();
@@ -749,6 +792,10 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
749792

750793
exports.Set(Napi::String::New(env, "getScreenSize"), Napi::Function::New(env, _getScreenSize));
751794
exports.Set(Napi::String::New(env, "highlight"), Napi::Function::New(env, _highlight));
795+
exports.Set(Napi::String::New(env, "getWindows"), Napi::Function::New(env, _getWindows));
796+
exports.Set(Napi::String::New(env, "getActiveWindow"), Napi::Function::New(env, _getActiveWindow));
797+
exports.Set(Napi::String::New(env, "getWindowRect"), Napi::Function::New(env, _getWindowRect));
798+
exports.Set(Napi::String::New(env, "getWindowTitle"), Napi::Function::New(env, _getWindowTitle));
752799
exports.Set(Napi::String::New(env, "captureScreen"), Napi::Function::New(env, _captureScreen));
753800
exports.Set(Napi::String::New(env, "getXDisplayName"), Napi::Function::New(env, _getXDisplayName));
754801
exports.Set(Napi::String::New(env, "setXDisplayName"), Napi::Function::New(env, _setXDisplayName));

src/types.h

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@
55
#include "os.h"
66
#include "inline_keywords.h" /* For H_INLINE */
77
#include <stddef.h>
8+
#include <stdint.h>
89

910
/* Some generic, cross-platform types. */
1011

1112
struct _MMPoint {
12-
size_t x;
13-
size_t y;
13+
int64_t x;
14+
int64_t y;
1415
};
1516

1617
typedef struct _MMPoint MMPoint;
1718

1819
struct _MMSize {
19-
size_t width;
20-
size_t height;
20+
int64_t width;
21+
int64_t height;
2122
};
2223

2324
typedef struct _MMSize MMSize;
@@ -29,23 +30,23 @@ struct _MMRect {
2930

3031
typedef struct _MMRect MMRect;
3132

32-
H_INLINE MMPoint MMPointMake(size_t x, size_t y)
33+
H_INLINE MMPoint MMPointMake(int64_t x, int64_t y)
3334
{
3435
MMPoint point;
3536
point.x = x;
3637
point.y = y;
3738
return point;
3839
}
3940

40-
H_INLINE MMSize MMSizeMake(size_t width, size_t height)
41+
H_INLINE MMSize MMSizeMake(int64_t width, int64_t height)
4142
{
4243
MMSize size;
4344
size.width = width;
4445
size.height = height;
4546
return size;
4647
}
4748

48-
H_INLINE MMRect MMRectMake(size_t x, size_t y, size_t width, size_t height)
49+
H_INLINE MMRect MMRectMake(int64_t x, int64_t y, int64_t width, int64_t height)
4950
{
5051
MMRect rect;
5152
rect.origin = MMPointMake(x, y);
@@ -66,4 +67,6 @@ H_INLINE MMRect MMRectMake(size_t x, size_t y, size_t width, size_t height)
6667

6768
#endif
6869

70+
typedef int64_t WindowHandle;
71+
6972
#endif /* TYPES_H */

0 commit comments

Comments
 (0)