I'm trying to use js-ctypes in Firefox to receive USB media/drive notifications, but I'm having a few issues and I can't tell if it's because I'm very inexperienced at Win32 API or awful at js-ctypes (or both!)
I've started by adapting an example I found on Alexandre Poirot's blog:
That example uses js-ctypes to create a "message-only" window, and then interacts with the shell service for the purpose of communicating with the Windows notification tray.
It seems simple enough, so after some research on the merits of RegisterDeviceNotification vs SHChangeNotifyRegister, I'm trying to adapt that (working!) example to register for device updates via SHChangeNotifyRegister
.
The code resides in a bootstrapped (restartless) Firefox extension (code below).
The implementation of the WindowProc
works well, as in the original example. My JavaScript callback logs the Window messages that come in (just numerically for this example).
Problems:
Firstly, it seems that calling DestroyWindow
crashes Firefox (almost always) on shutdown()
of the extension. Is there some Windows message I should handle on the "message-only" window to gracefully handle DestryWindow
?
Secondly, although it looks from the console output (below) that I'm getting meaningful values out of the calls to SHGetSpecialFolderLocation
and SHChangeNotifyRegister
(the return values aren't errors and the PIDLISTITEM
pointer is some real address) I'm not getting Device/Drive messages in the JavaScript callback.
Also, I tried to reproduce the PIDLISTITEM
structures to no avail (couldn't get js-ctypes
to recognise them in calls to SHChangeNotifyRegister
) and after studying some other non C++ examples, it seems that most folks are just using long*
instead -- I hope that's the source of my misunderstanding!
I've verified via similar C++ sample project from Microsoft that the messages themselves are received when the SHChangeNotifyRegistration
succeeds and I generate USB media events (ny inserting & removing USB flash media).
Minimal code to reproduce the issues follows:
install.rdf:
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>testwndproc@foo.com</em:id>
<em:type>2</em:type>
<em:name>TEST WNDPROC</em:name>
<em:version>1.0</em:version>
<em:bootstrap>true</em:bootstrap>
<em:unpack>true</em:unpack>
<em:description>Testing wndProc via JS-CTYPES on WIN32.</em:description>
<em:creator>David</em:creator>
<!-- Firefox Desktop -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>4.0.*</em:minVersion>
<em:maxVersion>29.0.*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>
bootstrap.js:
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Components.utils.import("resource://gre/modules/ctypes.jsm");
let consoleService = Cc["@mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService);
function LOG(msg) {
consoleService.logStringMessage("TEST-WNDPROC: "+msg);
}
var WindowProcType, DefWindowProc, RegisterClass, CreateWindowEx,
DestroyWindow, SHGetSpecialFolderLocation, WNDCLASS, wndclass,
messageWin, libs = {};
var windowProcJSCallback = function(hWnd, uMsg, wParam, lParam) {
LOG("windowProc: "+JSON.stringify([uMsg, wParam, lParam]));
//
// TODO: decode uMsg, wParam, lParam to interpret
// the incoming ShChangeNotifyEntry messages!
//
return DefWindowProc(hWnd, uMsg, wParam, lParam);
};
function startup(data, reason) {
try {
LOG("loading USER32.DLL ...");
libs.user32 = ctypes.open("user32.dll");
LOG("loading SHELL32.DLL ...");
libs.shell32 = ctypes.open("shell32.dll");
LOG("registering callback ctype WindowProc ...");
WindowProc = ctypes.FunctionType(
ctypes.stdcall_abi, ctypes.int,
[ctypes.voidptr_t, ctypes.int32_t,
ctypes.int32_t, ctypes.int32_t]).ptr;
LOG("registering API CreateWindowEx ...");
CreateWindowEx = libs.user32.declare("CreateWindowExA",
ctypes.winapi_abi, ctypes.voidptr_t, ctypes.long,
ctypes.char.ptr, ctypes.char.ptr, ctypes.int,
ctypes.int, ctypes.int, ctypes.int, ctypes.int,
ctypes.voidptr_t, ctypes.voidptr_t, ctypes.voidptr_t,
ctypes.voidptr_t);
LOG("registering API DestroyWindow ...");
DestroyWindow = libs.user32.declare("DestroyWindow",
ctypes.winapi_abi, ctypes.bool, ctypes.voidptr_t);
/*
// previously using....
LOG("registering ctype SHITEMID ...");
var ShItemId = ctypes.StructType("ShItemId", [
{ cb: ctypes.unsigned_short },
{ abID: ctypes.uint8_t.array(1) }
]);
LOG("registering ctype ITEMIDLIST ...");
var ItemIDList = ctypes.StructType("ItemIDList", [
{ mkid: ShItemId }
]);
*/
LOG("registering ctype SHChangeNotifyEntry ...");
var SHChangeNotifyEntry = ctypes.StructType(
"SHChangeNotifyEntry", [
{ pidl: ctypes.long.ptr }, /* ItemIDList.ptr ??? */
{ fRecursive: ctypes.bool }
]);
LOG("registering API SHChangeNotifyRegister ...");
SHChangeNotifyRegister = libs.shell32.declare(
"SHChangeNotifyRegister", ctypes.winapi_abi,
ctypes.unsigned_long,
ctypes.voidptr_t, ctypes.int, ctypes.long,
ctypes.unsigned_int, ctypes.int,
SHChangeNotifyEntry.array() /* SHChangeNotifyEntry.ptr ??? */
);
LOG("registering ctype WNDCLASS ...");
WNDCLASS = ctypes.StructType("WNDCLASS", [
{ style : ctypes.uint32_t },
{ lpfnWndProc : WindowProc },
{ cbClsExtra : ctypes.int32_t },
{ cbWndExtra : ctypes.int32_t },
{ hInstance : ctypes.voidptr_t },
{ hIcon : ctypes.voidptr_t },
{ hCursor : ctypes.voidptr_t },
{ hbrBackground : ctypes.voidptr_t },
{ lpszMenuName : ctypes.char.ptr },
{ lpszClassName : ctypes.char.ptr }
]);
LOG("registering API SHGetSpecialFolderLocation ...");
SHGetSpecialFolderLocation = libs.shell32.declare(
"SHGetSpecialFolderLocation", ctypes.winapi_abi,
ctypes.long, ctypes.voidptr_t, ctypes.int,
ctypes.long.ptr /* ItemIDList.ptr ??? */
);
LOG("registering API RegisterClass ...");
RegisterClass = libs.user32.declare("RegisterClassA",
ctypes.winapi_abi, ctypes.voidptr_t, WNDCLASS.ptr);
LOG("registering API DefWindowProc ...");
DefWindowProc = libs.user32.declare("DefWindowProcA",
ctypes.winapi_abi, ctypes.int, ctypes.voidptr_t,
ctypes.int32_t, ctypes.int32_t, ctypes.int32_t);
LOG("instatiating WNDCLASS (using windowProcJSCallback) ...");
var cName = "class-testingmessageonlywindow";
wndclass = WNDCLASS();
wndclass.lpszClassName = ctypes.char.array()(cName);
wndclass.lpfnWndProc = WindowProc(windowProcJSCallback);
LOG("calling API: RegisterClass ...");
RegisterClass(wndclass.address());
LOG("calling API: CreateWindowEx ...");
var HWND_MESSAGE = -3; // message-only window
messageWin = CreateWindowEx(
0, wndclass.lpszClassName,
ctypes.char.array()("my-testing-window"),
0, 0, 0, 0, 0,
ctypes.voidptr_t(HWND_MESSAGE),
null, null, null
);
LOG("instantiating pidl ...");
var pidl = ctypes.long();
LOG("Prior to call, pidl = "+pidl);
LOG("calling API: SHGetSpecialFolderLocation ...");
var CSIDL_DESKTOP = 0;
var hr = SHGetSpecialFolderLocation(
messageWin,
CSIDL_DESKTOP,
pidl.address()
);
LOG("got back: "+hr);
LOG("After the call, pidl = "+pidl);
LOG("instantiating pschcne ...");
var SHCNE = SHChangeNotifyEntry.array(1);
var shcne = SHCNE();
shcne[0].pidl = pidl.address();
shcne[0].fRecursive = false;
var WM_SHNOTIFY = 1025; // 0x401
var SHCNE_DISKEVENTS = 145439; // 0x2381F
var SHCNE_DRIVEADD = 256; // 256
var SHCNE_DRIVEREMOVED = 128; // 128
var SHCNE_MEDIAINSERTED = 32; // 32
var SHCNE_MEDIAREMOVED = 64; // 64
var SHCNRF_ShellLevel = 2; // 0x0002
var SHCNRF_InterruptLevel = 1; // 0x0001
var SHCNRF_NewDelivery = 32768; // 0x8000
var nSources = SHCNRF_ShellLevel |
SHCNRF_InterruptLevel |
SHCNRF_NewDelivery;
var lEvents = SHCNE_DISKEVENTS | SHCNE_DRIVEADD |
SHCNE_DRIVEREMOVED | SHCNE_MEDIAINSERTED |
SHCNE_MEDIAREMOVED;
var uMsg = WM_SHNOTIFY;
LOG("DEBUG: nSources="+nSources);
LOG("DEBUG: lEvents="+lEvents);
LOG("DEBUG: uMsg="+uMsg);
LOG("calling API: SHChangeNotifyRegister ...");
var reg_id = SHChangeNotifyRegister(
messageWin, nSources, lEvents, uMsg, 1, shcne
);
if (reg_id > 0) {
LOG("SUCCESS: Registered with ShellService for "+
"DRIVE/MEDIA notifications! reg-id: "+reg_id);
} else {
LOG("ERROR: Couldn't register for DRIVE/MEDIA "+
"notifications from ShellService!");
}
LOG("done!");
} catch (e) {
LOG("ERROR: "+e);
}
}
function shutdown(data, reason) {
if (reason == APP_SHUTDOWN) return;
try {
//LOG("destroying hidden window... ");
//DestroyWindow(messageWin); // crash!!!
LOG("unloading USER32.DLL ...");
libs.user32.close();
LOG("unloading SHELL32.DLL ...");
libs.shell32.close();
LOG("done!");
} catch (e) {
LOG("ERROR: "+e);
}
}
Console output:
17:08:25.518 TEST-WNDPROC: loading USER32.DLL ...
17:08:25.518 TEST-WNDPROC: loading SHELL32.DLL ...
17:08:25.518 TEST-WNDPROC: registering callback ctype WindowProc ...
17:08:25.518 TEST-WNDPROC: registering API CreateWindowEx ...
17:08:25.518 TEST-WNDPROC: registering API DestroyWindow ...
17:08:25.518 TEST-WNDPROC: registering ctype SHChangeNotifyEntry ...
17:08:25.518 TEST-WNDPROC: registering API SHChangeNotifyRegister ...
17:08:25.518 TEST-WNDPROC: registering ctype WNDCLASS ...
17:08:25.518 TEST-WNDPROC: registering API SHGetSpecialFolderLocation ...
17:08:25.518 TEST-WNDPROC: registering API RegisterClass ...
17:08:25.518 TEST-WNDPROC: registering API DefWindowProc ...
17:08:25.519 TEST-WNDPROC: instatiating WNDCLASS (using windowProcJSCallback) ...
17:08:25.519 TEST-WNDPROC: calling API: RegisterClass ...
17:08:25.519 TEST-WNDPROC: calling API: CreateWindowEx ...
17:08:25.519 TEST-WNDPROC: windowProc: [36,0,2973696]
17:08:25.519 TEST-WNDPROC: windowProc: [129,0,2973652]
17:08:25.519 TEST-WNDPROC: windowProc: [131,0,2973728]
17:08:25.519 TEST-WNDPROC: windowProc: [1,0,2973608]
17:08:25.519 TEST-WNDPROC: instantiating pidl ...
17:08:25.519 TEST-WNDPROC: Prior to call, pidl = ctypes.long(ctypes.Int64("0"))
17:08:25.519 TEST-WNDPROC: calling API: SHGetSpecialFolderLocation ...
17:08:25.519 TEST-WNDPROC: got back: 0
17:08:25.519 TEST-WNDPROC: After the call, pidl = ctypes.long(ctypes.Int64("224974424"))
17:08:25.519 TEST-WNDPROC: instantiating pschcne ...
17:08:25.519 TEST-WNDPROC: DEBUG: [nSources=32771][lEvents=145919][uMsg=1025]
17:08:25.519 TEST-WNDPROC: calling API: SHChangeNotifyRegister ...
17:08:25.520 TEST-WNDPROC: SUCCESS: Registered with ShellService for DRIVE/MEDIA
notifications! reg-id: 15
17:08:25.520 TEST-WNDPROC: done!
----- &< -------
17:09:31.391 TEST-WNDPROC: unloading USER32.DLL ...
17:09:31.391 TEST-WNDPROC: unloading SHELL32.DLL ...
17:09:31.391 TEST-WNDPROC: done!