Techno Barje

Jsctypes + Win32api + Jetpack = Jetintray

JSCtypes! What a powerfull tool, that allows to call native libraries with our simple Javascript.
Jetpack! What a powerfull tool, that allows to build reliably javascript applications, with unittests, memory profiling, web IDE, …
And WinAPI … a giant C library still in production in 2010 that allows to do very various things on Windows platform.


Mix all that and you get:

JetInTray

A jetpack API for adding Tray icons on windows via jsctypes and on linux with a binary xpcom component (I didn’t had time to work on a jsctypes version).
You may checkout this jetpack package directly from github.
Or if you want to learn jsctypes I suggest you to look at files in lib directory and to read my two previous posts on jsctypes.

  • I explained on the first one how to start playing with jsctypes, how to create C-structures and call functions.
  • Then I showed in the second post, how to create a JS callback passed to the native library as a function pointer.


That said, I wanted to highlight some underground hacks around win32api! In WinAPI, there is no addEventListener/setEventCallback/addActionListener/… In fact, there is the well known WndProc messages function, that receives absolutely all event of the application!! (Yes for real!) We define this function as a static function named WndProc. But in Jsctypes case, that’s impossible to define a static function, we can only create function pointers. That’s where comes the not so known hack which allow to register dynamically such event listener.

  • First we have to define our listener function following the WinAPI datatypes
    Components.utils.import("resource://gre/modules/ctypes.jsm");
    var libs = {};
    libs.user32 = ctypes.open("user32.dll");
    
    // Define the function pointer type
    var WindowProcType = 
      ctypes.FunctionType(ctypes.stdcall_abi, ctypes.int,
        [ctypes.voidptr_t, ctypes.int32_t, ctypes.int32_t, ctypes.int32_t]).ptr;
    
    // Bind a usefull API function
    var DefWindowProc = libs.user32.declare("DefWindowProcA", ctypes.winapi_abi, ctypes.int,
        ctypes.voidptr_t, ctypes.int32_t, ctypes.int32_t, ctypes.int32_t);
    
    // Set our javascript callback
    function windowProcJSCallback(hWnd, uMsg, wParam, lParam) {
      
      // ... do something smart with this event!
      
      // You HAVE TO call this api function when you don't known how to handle an event
      // or your apply is going to crash or do nothing
      return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    
    // Retrieve a C function pointer for our Javascript callback
    var WindowProcPointer = WindowProcType(windowProcJSCallback);
    
  • Then we may fill a WNDCLASS structure with our fresh function pointer. This structure is used to create a new window class that use it own WndProc (not the default static function). See msdn doc for more information.
    var WNDCLASS = 
      ctypes.StructType("WNDCLASS",
        [
          { style  : ctypes.uint32_t },
          { lpfnWndProc  : WindowProcType }, // here is our function pointer!
          { 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 }
        ]);
    var wndclass = WNDCLASS();
    wndclass.lpszClassName = ctypes.char.array()("class-custom-wndproc");
    wndclass.lpfnWndProc = WindowProcType(windowProcCallback);   // <---- here it is!
    RegisterClass(wndclass.address());
    
  • After that we may create a hidden window that is created only to catch events.
    var 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
        );
    var HWND_MESSAGE = -3; // This is the code for message-only window
                          // http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only
    var win = CreateWindowEx(
        0, wndclass.lpszClassName,
        ctypes.char.array()("messages-only-window"),
        0, 0, 0, 0, 0,
        ctypes.voidptr_t(HWND_MESSAGE), null, null, null);
    
  • Finally, we only have to bind this window to any component which dispatch messages/events in order to receive them in our windowProcJSCallback callback. That’s it!