Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node-webkit WinAPI

I’m developing simple node-webkit application for Windows (Vista, 7, …) and I need to use some WinAPI functions, specifically, RegisterHotKey and SendInput, to bind system-wide hotkeys and make keystrokes based on that. There’s no such API supplied by node-webkit, so I thought to use node-ffi to call that functions.

I’m new to WinAPI development, so I’ve read some MSDN manuals, but found that most of examples create window, message loop, message handling procedure and so on. So I don’t understand quite well, how to implement correctly calling to WinAPI from node-webkit, without creating separate window?

Node-ffi tutorial doesn’t cover that case, so I’ve found the node Windows library, but it seems that it just implements Windows application by means of node.

Is there a way to implement native calls without creating windows application? What’s the correct way to do that?

like image 515
dy_ Avatar asked Feb 10 '13 14:02

dy_


2 Answers

I wrote a node script that captures hotkeys on windows using the ffi, ref, and ref-struct modules. I've had some issues getting them to work in a packaged .exe since ffi and ref are native add-ons. For more information, see this github issue I opened awhile ago.

Anyways, here's the code:

var FFI = require('ffi'),
    ref = require('ref'),
    Struct = require('ref-struct');

/* First, create the necessary data structures that'll be used
   by our windows api calls. */

var pointStruct = Struct({
  'x': 'long',
  'y': 'long'
});

var msgStruct = Struct({
  'hwnd': 'int32',
  'message': 'int32', 
  'wParam': 'int32', 
  'lParam': 'int32', 
  'time': 'int32', 
  'pt': pointStruct
});

var msgStructPtr = ref.refType(msgStruct);

/* Second, register the functions we'd like to use by providing
   their method signatures. */

var user32 = new FFI.Library('user32', {

  'RegisterHotKey': [ 
    'bool', ['int32', 'int', 'int32', 'int32'] 
  ],

  'GetMessageA': [ 
    'bool', [msgStructPtr, 'int32', 'int32', 'int32'] 
  ]

  /* You may prefer to use PeekMessageA which has the same
     signature as GetMessageA, but is non-blocking. I haven't
     tested it, though.

});

/* Third, register your hotkeys. I wanted to control a media player,
   so these keys reflect that. */

var ALT = 0x0001,
    CTRL = 0x0002,
    SHIFT = 0x0004;

var MEDIA_NEXT = 0xB0,
    MEDIA_PREV = 0xB1,
    MEDIA_STOP = 0xB2,
    MEDIA_PLAY_PAUSE = 0xB3,
    MEDIA_LAUNCH = 0xB5;

var PERIOD = 0xBE,
    COMMA = 0xBC,
    EQUAL = 0xBB,
    DIVIDE = 0xBF,
    SQUOTE = 0xDE,
    PAGEUP = 0x21,
    PAGEDOWN = 0x22;

registrations = [];
registrations.push(user32.RegisterHotKey(0, 1, 0, MEDIA_NEXT));
registrations.push(user32.RegisterHotKey(0, 1, 0, MEDIA_PREV));
registrations.push(user32.RegisterHotKey(0, 1, 0, MEDIA_STOP));
registrations.push(user32.RegisterHotKey(0, 1, 0, MEDIA_PLAY_PAUSE));
registrations.push(user32.RegisterHotKey(0, 1, 0, MEDIA_LAUNCH));
registrations.push(user32.RegisterHotKey(0, 1, CTRL, PERIOD));
registrations.push(user32.RegisterHotKey(0, 1, CTRL, COMMA));
registrations.push(user32.RegisterHotKey(0, 1, CTRL, EQUAL));
registrations.push(user32.RegisterHotKey(0, 1, CTRL, DIVIDE));
registrations.push(user32.RegisterHotKey(0, 1, CTRL | ALT, PAGEUP));
registrations.push(user32.RegisterHotKey(0, 1, CTRL | ALT, PAGEDOWN));

// an array of booleans telling us which registrations failed/succeeded
console.log(registrations);

/* Fourth, wait for new hotkey events from the message queue. */

var myMsg = new msgStruct;
while (user32.GetMessageA(myMsg.ref(), 0, 0, 0)) {
    var key = myMsg.lParam >> 16;
    switch (key) {
        case MEDIA_NEXT: console.log('media next'); break;
        case MEDIA_PREV: console.log('media prev'); break;
        case MEDIA_STOP: console.log('media stop'); break;
        case MEDIA_PLAY_PAUSE: console.log('media play/pause'); break;
        case MEDIA_LAUNCH: console.log('media launch'); break;
        case PERIOD: console.log('next'); break;
        case COMMA: console.log('previous'); break;
        case EQUAL: console.log('play/pause'); break;
        case DIVIDE: console.log('info'); break;
        case PAGEUP: console.log('volume up'); break;
        case PAGEDOWN: console.log('volume down'); break;
        default: console.log('undefined hotkey', key, key.toString(16));
    }
}

If you want this to work with node-webkit, make sure you build all the native add-ons with nw-gyp with the --target set to your version of node-webkit (0.5.1 in my case):

# Make sure you run this command in the following directories (where the binding.gyp files are):
#  node_modules/ffi/
#  node_modules/ffi/node_modules/ref/
#  node_modules/ref/
$ nw-gyp clean configure --target=v0.5.1 build

Review the MSDN docs to understand the method signatures and structs used. Hope this helps!

like image 121
theabraham Avatar answered Nov 14 '22 16:11

theabraham


An alternative to node-ffi is to use the iohook npm module: https://github.com/wilix-team/iohook

Node.js global keyboard and mouse listener.

This module can handle keyboard and mouse events via native hooks inside and outside your JavaScript/TypeScript application.

Some other alternatives can be found here. (However, the others are not as good in my opinion; for example, most are no longer being maintained.)

like image 40
Venryx Avatar answered Nov 14 '22 15:11

Venryx