Skip to content

Commit

Permalink
Add support for Windows UWP apps (#80)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
johnharden and sindresorhus authored Mar 10, 2021
1 parent 8ee48ec commit 066418f
Showing 1 changed file with 84 additions and 24 deletions.
108 changes: 84 additions & 24 deletions lib/windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const Rect = struct({
bottom: 'long'
});
const RectPointer = ref.refType(Rect);
const VoidPointer = ref.refType(ref.types.void);

// Required by QueryFullProcessImageName
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
Expand All @@ -31,7 +32,10 @@ const user32 = new ffi.Library('User32.dll', {
GetWindowThreadProcessId: ['uint32', ['pointer', 'uint32 *']],
// Get window bounds function
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowrect
GetWindowRect: ['bool', ['pointer', RectPointer]]
GetWindowRect: ['bool', ['pointer', RectPointer]],
// Iterate through child windows
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumchildwindows
EnumChildWindows: ['bool', ['pointer', VoidPointer, 'int32']]
});

const SIZE_T = 'uint64';
Expand Down Expand Up @@ -68,6 +72,74 @@ const kernel32 = new ffi.Library('kernel32', {
QueryFullProcessImageNameW: ['int', ['pointer', 'uint32', 'pointer', 'pointer']]
});

// Check parent window for SubWindows and find the unique process inside of it.
function getSubWindowRealProcessPath(activeWindowHandle, processPath) {
let newProcessPath;

const windowProcess = ffi.Callback('bool', ['pointer', 'int32'], hwnd => {
// Return the Process ID & Process Handle from the Active Window Handle
const [, processHandle] = getProcessIdAndHandle(hwnd);

if (ref.isNull(processHandle)) {
return false; // Failed to get process handle
}

const processPathChild = getProcessPath(processHandle);

if (processPathChild !== processPath) {
newProcessPath = processPathChild;
return false;
}

return true;
});

user32.EnumChildWindows(activeWindowHandle, windowProcess, 0);

if (newProcessPath !== undefined && newProcessPath !== processPath) {
return newProcessPath;
}

return processPath;
}

function getProcessPath(processHandle) {
if (ref.isNull(processHandle)) {
return undefined;
}

// Set the path length to more than the Windows extended-length MAX_PATH length
// The maximum path of 32,767 characters is approximate, because the "\\?\" prefix may be expanded to a longer string by the system at run time, and this expansion applies to the total length.
const pathLengthBytes = 66000;
// Path length in "characters"
const pathCharacterCount = Math.floor(pathLengthBytes / 2);
// Allocate a buffer to store the path of the process
const processFileNameBuffer = Buffer.alloc(pathLengthBytes);
// Create a buffer containing the allocated size for the path, as a buffer as it must be writable
const processFileNameSizeBuffer = ref.alloc('uint32', pathCharacterCount);
// Write process file path to buffer
kernel32.QueryFullProcessImageNameW(processHandle, 0, processFileNameBuffer, processFileNameSizeBuffer);
// Remove null characters from buffer
const processFileNameBufferClean = ref.reinterpretUntilZeros(processFileNameBuffer, wchar.size);
// Get process file path as a string
const processPath = wchar.toString(processFileNameBufferClean);

return processPath;
}

function getProcessIdAndHandle(windowHandle) {
// Allocate a buffer to store the process ID
const processIdBuffer = ref.alloc('uint32');
// Write the process ID creating the window to the buffer (it returns the thread ID, but it's not used here)
user32.GetWindowThreadProcessId(windowHandle, processIdBuffer);
// Get the process ID as a number from the buffer
const processId = ref.get(processIdBuffer);
// Get a "handle" of the process
const processHandle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, processId);

return [processId, processHandle];
}

function windows() {
// Windows C++ APIs' functions are declared with capitals, so this rule has to be turned off

Expand All @@ -93,35 +165,23 @@ function windows() {
// The text as a JavaScript string
const windowTitle = wchar.toString(windowTextBufferClean);

// Allocate a buffer to store the process ID
const processIdBuffer = ref.alloc('uint32');
// Write the process ID creating the window to the buffer (it returns the thread ID, but it's not used here)
user32.GetWindowThreadProcessId(activeWindowHandle, processIdBuffer);
// Get the process ID as a number from the buffer
const processId = ref.get(processIdBuffer);
// Get a "handle" of the process
const processHandle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, processId);
// Return the Process ID & Process Handle from the Active Window Handle
const [processId, processHandle] = getProcessIdAndHandle(activeWindowHandle);

if (ref.isNull(processHandle)) {
return undefined; // Failed to get process handle
}

// Set the path length to more than the Windows extended-length MAX_PATH length
const pathLengthBytes = 66000;
// Path length in "characters"
const pathLengthChars = Math.floor(pathLengthBytes / 2);
// Allocate a buffer to store the path of the process
const processFileNameBuffer = Buffer.alloc(pathLengthBytes);
// Create a buffer containing the allocated size for the path, as a buffer as it must be writable
const processFileNameSizeBuffer = ref.alloc('uint32', pathLengthChars);
// Write process file path to buffer
kernel32.QueryFullProcessImageNameW(processHandle, 0, processFileNameBuffer, processFileNameSizeBuffer);
// Remove null characters from buffer
const processFileNameBufferClean = ref.reinterpretUntilZeros(processFileNameBuffer, wchar.size);
// Get process file path as a string
const processPath = wchar.toString(processFileNameBufferClean);
// Return the Process Path & Process Name from a Process Handle
let processPath = getProcessPath(processHandle);
// Get process file name from path
const processName = path.basename(processPath);
let processName = path.basename(processPath);

// ApplicationFrameHost & Universal Windows Platform Support
if (processName === 'ApplicationFrameHost.exe') {
processPath = getSubWindowRealProcessPath(activeWindowHandle, processPath);
processName = path.basename(processPath);
}

// Get process memory counters
const memoryCounters = new ProcessMemoryCounters();
Expand Down

0 comments on commit 066418f

Please sign in to comment.