14

I have a NodeJS application running on Windows that needs to display and switch the focus to a running Windows application when a user does a certain action. I have been using the node-ffi package to make windows API calls but have not been able to make it switch focus consistently. Here is the code I am using. It successfully gets the HWND of a running Calculator app, but then tries to switch focus to that HWND and it only works sometimes:

    var ffi = require('ffi');   
    var intPtr = ref.refType('long');
    var user32 = new ffi.Library('user32', {
        'FindWindowA': ['long', [ 'string', 'string']],
        'SetForegroundWindow': ['bool', ['long']],
        'BringWindowToTop': ['bool', ['long']],
    });

    var winToSetOnTop = user32.FindWindowA(null,"calculator")
    var res = user32.ShowWindow(winToSetOnTop, 9);
    res = user32.SetForegroundWindow(winToSetOnTop);
    res = user32.BringWindowToTop(winToSetOnTop);   

This combination of commands seems to work most consistently of the ones I have tried, but it does not work all the time. If the window I want to switch focus to is minimized it will always pop to the top. If the window is not minimized, but just behind another window, it will only be shown intermittently. I am not sure how to consistently to get a running windows application to always move to the top of the order, even if it is currently minimized.

KeithTheBiped
  • 832
  • 10
  • 21
  • You say both, that it always works for minimized windows as well as, that it doesn't work consistently. Which one is true? – IInspectable Sep 10 '16 at 00:18
  • You don't check for errors. Read the docs for SetForegroundWindow especially the list of conditions. – David Heffernan Sep 10 '16 at 07:21
  • @IInspectable It will always bring a minimized window to the top. If a window is not minimized, but is only behind another window, it will only bring it to the top intermittently. – KeithTheBiped Sep 12 '16 at 14:30

1 Answers1

19

I've worked out the following solution which works well in all circumstances to bring a window to the top. First it will get the window handle to a running instance of the Calculator application, then it will bring it topmost and focus it.

var ffi = require('ffi-napi')

var user32 = new ffi.Library('user32', {
    'GetTopWindow': ['long', ['long']],
    'FindWindowA': ['long', ['string', 'string']],
    'SetActiveWindow': ['long', ['long']],
    'SetForegroundWindow': ['bool', ['long']],
    'BringWindowToTop': ['bool', ['long']],
    'ShowWindow': ['bool', ['long', 'int']],
    'SwitchToThisWindow': ['void', ['long', 'bool']],
    'GetForegroundWindow': ['long', []],
    'AttachThreadInput': ['bool', ['int', 'long', 'bool']],
    'GetWindowThreadProcessId': ['int', ['long', 'int']],
    'SetWindowPos': ['bool', ['long', 'long', 'int', 'int', 'int', 'int', 'uint']],
    'SetFocus': ['long', ['long']]
});

var kernel32 = new ffi.Library('Kernel32.dll', {
    'GetCurrentThreadId': ['int', []]
});

var winToSetOnTop = user32.FindWindowA(null, "calculator")
var foregroundHWnd = user32.GetForegroundWindow()
var currentThreadId = kernel32.GetCurrentThreadId()
var windowThreadProcessId = user32.GetWindowThreadProcessId(foregroundHWnd, null)
var showWindow = user32.ShowWindow(winToSetOnTop, 9)
var setWindowPos1 = user32.SetWindowPos(winToSetOnTop, -1, 0, 0, 0, 0, 3)
var setWindowPos2 = user32.SetWindowPos(winToSetOnTop, -2, 0, 0, 0, 0, 3)
var setForegroundWindow = user32.SetForegroundWindow(winToSetOnTop)
var attachThreadInput = user32.AttachThreadInput(windowThreadProcessId, currentThreadId, 0)
var setFocus = user32.SetFocus(winToSetOnTop)
var setActiveWindow = user32.SetActiveWindow(winToSetOnTop)
yaya
  • 7,675
  • 1
  • 39
  • 38
KeithTheBiped
  • 832
  • 10
  • 21
  • 1
    Where could I find more about user32 api? – Paulo Henrique Oct 23 '17 at 21:01
  • 1
    @PauloHenrique The Wiki entry https://en.wikipedia.org/wiki/Windows_USER gives a little bit of high level information about user32.dll. For a deeper dive you can look at MSDN under Windows API. Documentation for most of the functions used in my solution can be found in the "Windows" section: https://msdn.microsoft.com/en-us/library/windows/desktop/ff468919(v=vs.85).aspx. Otherwise, WINAPI programming is a deep dark hole and for more information I would find an introductory guide, of which there are many, in order to get an overview of some of the Windows OS concepts, data structures, etc. – KeithTheBiped Oct 30 '17 at 19:17
  • The value to provide to `FindWindowA` is the TITLE of the process. – Luke Dec 11 '17 at 21:12
  • The call to `AttachThreadInput` is pretty much out of place. If anything, you'd have to call it *before* calling `SetForegroundWindow` (to establish the prerequisites for that call to succeed). Calling `AttachThreadInput` has [fairly severe consequences](https://blogs.msdn.microsoft.com/oldnewthing/20130619-00/?p=4043). This all appears to be based on guessing. And if you don't know why your code works, it doesn't. This is the case here, too. – IInspectable Aug 20 '18 at 22:37
  • @Luke: [FindWindow](https://msdn.microsoft.com/en-us/library/windows/desktop/ms633499.aspx) takes the window title and/or the window class name. None of these are related to the process name. – IInspectable Aug 20 '18 at 22:43