Skip to content

NodeJS library using native modules to get the active window and some metadata (including the application icon) on Windows, MacOS and Linux.

License

Notifications You must be signed in to change notification settings

paymoapp/node-active-window

Repository files navigation

Node Active Window

NPM Typescript N-API License

NodeJS library using native modules to get the active window and some metadata (including the application icon) on Windows, MacOS and Linux.

Table of Contents

Getting started

Installation

npm install --save @paymoapp/active-window

Native addon

This project uses NodeJS Native Addons to function, so you can use this library in any NodeJS or Electron project, there won't be any problem with bundling and code signing.

The project uses prebuild to supply prebuilt libraries.

The project uses Node-API version 6, you can check this table to see which node versions are supported.

If there's a compliant prebuilt binary, it will be downloaded during installation, or it will be built. You can also rebuild it anytime by running npm run build:gyp.

The library has native addons for all the three major operating systems: Windows, MacOS and Linux. For Linux, only the X11 windowing system is supported.

Usage

You need to import the library and call the initialize function. For MacOS see the method's documentation for additional required steps and caveats.

On MacOS you need to check for the screen recording permission (using requestPermissions), otherwise you won't be able to fetch the window titles.

Then you can use the getActiveWindow or subscribe methods to fetch the current active window or watch for window changes.

Example

You can run a demo application by calling npm run demo. You can browse it's source code for a detailed example using the watch API in demo/index.ts.

import ActiveWindow from '@paymoapp/active-window';

ActiveWindow.initialize();

if (!ActiveWindow.requestPermissions()) {
	console.log('Error: You need to grant screen recording permission in System Preferences > Security & Privacy > Privacy > Screen Recording');
	process.exit(0);
}

const activeWin = ActiveWindow.getActiveWindow();

console.log('Window title:', activeWin.title);
console.log('Application:', activeWin.application);
console.log('Application path:', activeWin.path);
console.log('Application PID:', activeWin.pid);
console.log('Application icon:', activeWin.icon);

API

Data structures

πŸ—ƒ Β Β  WindowInfo
interface WindowInfo {
	title: string;
	application: string;
	path: string;
	pid: number;
	icon: string;
	windows?: {
		isUWPApp: boolean;
		uwpPackage: string;
	};
}

This is the only object you will receive when interacting with this library. It contains information about the currently active window:

  • title - The title of the current window
  • application - The name of the application. On Windows you should use the uwpPackage parameter instead if isUWPApp is set to true
  • path - The path to the application's executable
  • pid - Process identifier of the application
  • icon - Base64 encoded string representing the application icon
  • windows - Object containing Windows platform specific information, undefined on other platforms
  • windows.isUWPApp - Set to true if the active window is owned by an Universal Windows Platform application
  • windows.uwpPackage - Contains the package family name of the UWP application

None of the parameters are nullable, even if their value couldn't be fetched, they will be either be set to an empty string (for the string values), -1 (for the numeric values) or false (for the boolean values).

Functions

𝑓 Β Β  getActiveWindow
interface IActiveWindow {
	getActiveWindow(): WindowInfo
	// ...
}

Requests the current foreground window in a synchronous way. It will throw an error if the current window couldn't be fetched (for example there're no focused windows at the moment).

𝑓 Β Β  subscribe
interface IActiveWindow {
	subscribe(callback: (windowInfo: WindowInfo | null) => void): number;
	// ...
}

Subscribe to changes of the active window. The supplied callback will be called with null if there're no focused windows at the moment.

The function returns a number representing the ID of the watch. You should store this value to remove the event listener later on.

𝑓 Β Β  unsubscribe
interface IActiveWindow {
	unsubscribe(watchId: number): void;
	// ...
}

Remove the event listener associated with the supplied watch ID. Use this to unsubscribe from the active window changed events.

𝑓 Β Β  initialize
interface IActiveWindow {
	initialize(opts?: { osxRunLoop: false | 'get' | 'all' }): void;
	// ...
}

On some platforms (Linux) the library needs some initialization to be done. You must call this function before doing anything with the library regardless of the current platform.

If you're not using this library in a GUI application there might be no run loop running for the main thread. In this case you MUST set the osxRunLoop property to get results. On Electron you have a working run loop in the main process and you should use IPCs to access the lib from the renderer.

Possible values for osxRunLoop:

  • false or not set (default): Don't run the run loop manually.
  • get: Run the run loop only for the getActiveWindow calls. Subscriptions will not work with this mode.
  • all: Run the run loop both for getActiveWindow calls and for subscriptions.
𝑓 Β Β  requestPermissions
interface IActiveWindow {
	requestPermissions(): boolean;
	// ...
}

On the MacOS platform you need to request screen recording permission to fetch the title of the current window.

The function will return true if the permission is granted and false if the permission is denied. This is a non-blocking function, so you will only get the momentary status.

You can call this function regardless of the current platform. On unsupported platforms it will simply return true.

When the function is called, the user will be presented with a system modal with instructions to grant the permission. You should include these instructions in your application as well, since this is a one-time modal. After the user grants the permission, it is required to relaunch the application for the changes to take effect.

If the user fails to grant the required permissions, the title property of the returned WindowInfo will be an empty string.

Native libraries

You can import the each platform dependent library as a standalone C++ / Objective-C++ library. You can find the library itself in the module's src directory. The napi directory contains the Node-API bindings and the demo directory contains a small demo program that can be built using a Makefile.

You can build the demo by navigating to the module/<platform>/demo folder and executing make. You can run the demo using make run and clean the build artifacts using make clean.

The demo has 4 running modes:

  • default: make run - in this mode the library is used to fetch the current window details, then there's a 3 second delay after which the current window is fetched again
  • loop: make run MODE=loop - in this mode the library is used the poll the current window in every 3 seconds until SIGINT (Ctrl+C) is received
  • watch: make run MODE=watch - in this mode the library is used to watch the current window and it's title. There's no polling involved in this mode
  • benchmark: make run MODE=benchmark - in this mode the library will fetch the current window details 100.000 times (or 10.000 times on windows) and it will print the total of the consumed CPU seconds while doing so

Linux (module/linux)

Data structures

πŸ—ƒ Β Β  WindowInfo
struct WindowInfo {
	std::string title; // UTF8 encoded string of window title. Empty string if couldn't fetch
	std::string application; // UTF8 encoded string of application name. Empty string if couldn't fetch
	std::string path; // UTF8 encoded string of application path. Empty string if couldn't fetch
	int pid; // PID of process. -1 if couldn't fetch
	std::string icon; // base64 encoded PNG icon. Empty string if couldn't fetch
}
πŸ—ƒ Β Β  watch_t
typedef unsigned int watch_t;
πŸ—ƒ Β Β  watch_callback
typedef std::function<void(WindowInfo*)> watch_callback;

Public functions

𝑓 Β Β  Constructor
ActiveWindow(unsigned int iconCacheSize = 0);

If you pass iconCacheSize > 0, then an LRU (least recently used) cache will be instantiated which will cache the fetched icons. This results in about 90% less CPU seconds consumed. You can use the benchmark mode of the demo to test it yourself.

You should pass a cache size suitable for your application. A bigger cache results in a greater memory consumption, but it also reduces the CPU utilization if your user switches across a large set of applications.

𝑓 Β Β  getActiveWindow
WindowInfo* getActiveWindow();

Returns pointer to WindowInfo containing the gathered information about the current window. The pointer can be NULL in the case of an error or if there is no active window (ex: all the windows are minified). You should free up the allocated WindowInfo object using delete.

𝑓 Β Β  buildAppCache
void buildAppCache();

Gathers all the .desktop entries available on the system (starting from ~/.local/share/applications through each $XDG_DATA_DIRS/applications) and resolves the icon path for them. This cache is used to get the icon for a given window. If this function is not called, then no icons will be resolved.

𝑓 Β Β  watchActiveWindow
watch_t watchActiveWindow(watch_callback cb);

Sets up a watch for the active window. If there's a change in the current active window, or the title of the active window, the callback will be fired with the current active window. You don't need to call getActiveWindow() in the callback, you can use the supplied parameter.

This method will also start a background watch thread if it's not already running. Please note that the callbacks will be executed on this thread, so you should assure thread safety.

You MUST NOT free up the WindowInfo object received in the parameter. If you need to store the active window you SHOULD make a copy of it, since the WindowInfo object will be freed after calling all the callbacks.

You should save the returned watch ID to unsubscribe later.

𝑓 Β Β  unwatchActiveWindow
void unwatchActiveWindow(watch_t watch);

Removes the watch associated with the supplied watch ID.

The background watch thread will not be closed, even if there're no more watches left. It will only be closed when the class's destructor is called.

Example

See module/linux/demo/main.cpp for an example.

#include <iostream>
#include "ActiveWindow.h"

using namespace std;
using namespace PaymoActiveWindow;

int main() {
	ActiveWindow* activeWindow = new ActiveWindow(10);

	WindowInfo* windowInfo = activeWindow->getActiveWindow();

	if (windowInfo == NULL) {
		cout<<"Could not get active window\n";
	}
	else {
		cout<<"Title: "<<windowInfo->title<<"\n";
		cout<<"Application: "<<windowInfo->application<<"\n";
		cout<<"Executable path: "<<windowInfo->path<<"\n";
		cout<<"PID: "<<windowInfo->pid<<"\n";
		cout<<"Icon: "<<windowInfo->icon<<"\n";
	}

	delete windowInfo;
	delete activeWindow;

	return 0;
}

Building

See module/linux/demo/Makefile for a sample makefile. You need to check the lib target.

You should use C++17 for building and you need to link the following libraries:

  • X11 (-lX11) - libx11-dev
  • PThread (-lpthread) - libpthread-stubs0-dev

Windows (module/windows)

Data structures

πŸ—ƒ Β Β  WindowInfo
struct WindowInfo {
	std::wstring title = L""; // UTF16 encoded string of window title. Empty string if couldn't fetch
	std::wstring application = L""; // UTF16 encoded string of application. Empty string if couldn't fetch
	std::wstring path = L""; // UTF16 encoded string of executable path. Empty string if couldn't fetch
	unsigned int pid = 0; // Process PID
	bool isUWPApp = false; // if application is detected to be an Universal Windows Platform application
	std::wstring uwpPackage = L""; // UTF16 encoded string of the UWP package name. Empty string if couldn't fetch
	std::string icon = ""; // base64 encoded icon. Empty string if couldn't fetch
};
πŸ—ƒ Β Β  watch_t
typedef unsigned int watch_t;
πŸ—ƒ Β Β  watch_callback
typedef std::function<void(WindowInfo*)> watch_callback;

Public functions

𝑓 Β Β  Constructor
ActiveWindow(unsigned int iconCacheSize = 0);

If you pass iconCacheSize > 0, then an LRU (least recently used) cache will be instantiated which will cache the fetched icons. This results in about 90% less CPU seconds consumed. You can use the benchmark mode of the demo to test it yourself.

You should pass a cache size suitable for your application. A bigger cache results in a greater memory consumption, but it also reduces the CPU utilization if your user switches across a large set of applications.

𝑓 Β Β  getActiveWindow
WindowInfo* getActiveWindow();

Returns pointer to WindowInfo containing the gathered information about the current window. The pointer can be NULL in the case of an error or if there is no active window (ex: all the windows are minified or the desktop is selected). You should free up the allocated WindowInfo object using delete.

𝑓 Β Β  watchActiveWindow
watch_t watchActiveWindow(watch_callback cb);

Sets up a watch for the active window. If there's a change in the current active window, or the title of the active window, the callback will be fired with the current active window. You don't need to call getActiveWindow() in the callback, you can use the supplied parameter.

This method will also start a background watch thread if it's not already running. Please note that the callbacks will be executed on this thread, so you should assure thread safety.

You MUST NOT free up the WindowInfo object received in the parameter. If you need to store the active window you SHOULD make a copy of it, since the WindowInfo object will be freed after calling all the callbacks.

You should save the returned watch ID to unsubscribe later.

𝑓 Β Β  unwatchActiveWindow
void unwatchActiveWindow(watch_t watch);

Removes the watch associated with the supplied watch ID.

The background watch thread will not be closed, even if there're no more watches left. It will only be closed when the class's destructor is called.

Example

See module/windows/demo/main.cpp for an example.

#include <iostream>
#include "ActiveWindow.h"

using namespace std;
using namespace PaymoActiveWindow;

int main() {
	ActiveWindow* activeWindow = new ActiveWindow(10);

	WindowInfo* windowInfo = activeWindow->getActiveWindow();

	if (windowInfo == NULL) {
		cout<<"Could not get active window\n";
	}
	else {
		wcout<<L"Title: "<<windowInfo->title<<"\n";
		wcout<<L"Application: "<<windowInfo->application<<"\n";
		wcout<<L"Executable path: "<<windowInfo->path<<"\n";
		cout<<"PID: "<<windowInfo->pid<<"\n";
		cout<<"Is UWP application: "<<(windowInfo->isUWPApp ? "Yes" : "No")<<"\n";
		if (windowInfo->isUWPApp) {
			wcout<<"UWP package name: "<<windowInfo->uwpPackage<<"\n";
		}
		cout<<"Icon: "<<windowInfo->icon<<"\n";
	}

	delete windowInfo;
	delete activeWindow;

	return 0;
}

Building

See module/windows/demo/Makefile for a sample makefile. You need to check the lib target. This is not a GNU makefile, it should be used with Microsoft's NMAKE.

To prepare your environment, you need run the vcvarsall.bat batch script. If you checked the install windows build tools box during the installation of NodeJS, you should find this file in the following location: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat. So to gain access to the nmake, cl and link commands, execute this:

"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64

You should use C++17 for building and you need to link the following libraries:

  • User32.lib
  • Shell32.lib
  • Version.lib
  • Shlwapi.lib
  • Gdi32.lib
  • Gdiplus.lib
  • Windowsapp.lib

MacOS (module/macos)

Data structures

πŸ—ƒ Β Β  WindowInfo
struct WindowInfo {
	std::string title; // UTF8 encoded string of window title. Empty string if couldn't fetch
	std::string application; // UTF8 encoded string of application name
	std::string path; // UTF8 encoded string of application path
	int pid; // PID of process
	std::string icon; // base64 encoded PNG icon
}
πŸ—ƒ Β Β  watch_t
typedef unsigned int watch_t;
πŸ—ƒ Β Β  watch_callback
typedef std::function<void(WindowInfo*)> watch_callback;

Public functions

𝑓 Β Β  Constructor
ActiveWindow(unsigned int iconCacheSize = 0);

If you pass iconCacheSize > 0, then an LRU (least recently used) cache will be instantiated which will cache the fetched icons. This results in about 40% less CPU seconds consumed. You can use the benchmark mode of the demo to test it yourself.

You should pass a cache size suitable for your application. A bigger cache results in a greater memory consumption, but it also reduces the CPU utilization if your user switches across a large set of applications.

𝑓 Β Β  getActiveWindow
WindowInfo* getActiveWindow();

Returns pointer to WindowInfo containing the gathered information about the current window. The pointer can be NULL in the case of an error or if there is no active window (ex: all the windows are minified). You should free up the allocated WindowInfo object using delete.

𝑓 Β Β  requestScreenCaptureAccess
bool requestScreenCaptureAccess();

To access the title of the window the process requires the screen capture permission. To check it and request it you need to call this function. This function is non-blocking and will immediately return with the current status (false - permission denied, true - permission granted).

The first time this function is called there will be a system popup instructing the user how he can grant this permission, but you should also include this information in your application, since it's a one-time popup that closes when you click outside of it.

The application needs to be relaunched after granting the permission.

𝑓 Β Β  watchActiveWindow
watch_t watchActiveWindow(watch_callback cb);

Sets up a watch for the active window. If there's a change in the current active window, the callback will be fired with the current active window. You don't need to call getActiveWindow() in the callback, you can use the supplied parameter.

This method will use the observer set up on the main thread to listen to the events, so the main thread has to have a running NSRunLoop. If you integrate this library into a desktop application, then this should already be resolved. Otherwise (for example when using a console application), you have to manually run the RunLoop or call the runLoop() helper function which will block for 0.1ms.

If you want to use the watch function, you MUST use this library on the main thread, since the NSWorkspace notifications are only serviced on that thread.

The callbacks are also executed on the main thread, so you shouldn't do anything blocking in them.

You should save the returned watch ID to unsubscribe later.

𝑓 Β Β  unwatchActiveWindow
void unwatchActiveWindow(watch_t watch);

Removes the watch associated with the supplied watch ID.

𝑓 Β Β  runLoop
void runLoop();

A helper function which will run the thread's RunLoop for 0.1ms or until the first event is handled. This function should be called on the main thread.

You should only use this function only if you don't have a running RunLoop on your main thread.

Example

See module/macos/demo/main.mm for an example.

#include <iostream>
#include "ActiveWindow.h"

using namespace std;
using namespace PaymoActiveWindow;

int main() {
	ActiveWindow* activeWindow = new ActiveWindow();

	WindowInfo* windowInfo = activeWindow->getActiveWindow();

	if (windowInfo == NULL) {
		cout<<"Could not get active window\n";
	}
	else {
		cout<<"Title: "<<windowInfo->title<<"\n";
		cout<<"Application: "<<windowInfo->application<<"\n";
		cout<<"Executable path: "<<windowInfo->path<<"\n";
		cout<<"PID: "<<windowInfo->pid<<"\n";
		cout<<"Icon: "<<windowInfo->icon<<"\n";
	}

	delete windowInfo;
	delete activeWindow;

	return 0;
}

Building

See module/macos/demo/Makefile for a sample makefile. You need to check the lib target.

You should use C++17 for building and you need to link the following libraries:

  • -lc++
  • -framework Foundation
  • -framework AppKit
  • -framework ApplicationServices

About

NodeJS library using native modules to get the active window and some metadata (including the application icon) on Windows, MacOS and Linux.

Resources

License

Stars

Watchers

Forks

Packages

No packages published