NPAPI – a plugin to retrieve device information

Some times ago I wrote a barcode scanner NPAPI plugin for the HTML5 browser by zetakey. It is based on code supplied as MySensor in the motorola knowledge base. The plugin is for Windows Mobile 6.x based devices by Intermec and another one for M3 devices.

The MySensor plugin based on npruntime was the only sample I could get to compile and work correctly. The barcode scanner fork I wrote does also work very well. The new MyDevinfo plugin retrieves model code, battery level and WLAN RSSI value of the Intermec device and allows to present this information to a HTML5 browser user when needed.

npapi_simple

The above shows the web page of MyDevinfo_simple.htm.

A NPAPI plugin for Windows Mobile html5 browsers like Zetakey Browser, Intermec Browser, Honeywell Enterprise Browser, Honeywell HTML5 Browser or Motorola/Zebra RhoMobile Browser is a DLL. The DLL has to be placed in the browser’s plugin directory and then can be used from HTML5 javascript code. The code defines which properties, methods and callbacks can be used from javascript. When the browser loads the plugin it queries all known properties of the plugin object. These properties are accessed via NPIdentifier types:

...
static NPIdentifier sCurrentRSSIValue_id;
static NPIdentifier sCurrentBattery_id;
static NPIdentifier sModelCode_id;
...
//  Constructor for the Plugin, called when the embedded mime type is found on a web page (see npp_new).
//  <embed id="embed1" type="application/x-itc-devinfo" hidden=true> </embed> 
CMyDevinfoPlugin::CMyDevinfoPlugin(NPP pNPInstance) :
  m_pNPInstance(pNPInstance),
  m_pNPStream(NULL),
  m_bInitialized(FALSE),
  m_pScriptableObject(NULL)
{
	DEBUGMSG(1, (L"CMyDevinfoPlugin()...\n"));
  	// Must initialise this before getting NPNVPluginElementNPObject, as it'll
	// call back into our GetValue method and require a valid plugin.
	pNPInstance->pdata = this;

    // Say that we're a windowless plugin.
    NPN_SetValue(m_pNPInstance, NPPVpluginWindowBool, false);

	//  Instantiate the values of the methods / properties we possess
	sMonitor_id = NPN_GetStringIdentifier("monitor");
	sPollInterval_id = NPN_GetStringIdentifier("pollInterval");
	sCurrentValue_id = NPN_GetStringIdentifier("currentValue");
	
	sCurrentRSSIValue_id = NPN_GetStringIdentifier("currentRSSIValue");
	sCurrentBattery_id = NPN_GetStringIdentifier("currentBatteryLevel");
	sModelCode_id = NPN_GetStringIdentifier("modelCode");

	//  Export onto the webpage the JS object 'MyDevinfo'.  This enables us
	//  to say var myObj = new MyDevinfo();
	NPObject *sWindowObj;
	NPN_GetValue(m_pNPInstance, NPNVWindowNPObject, &sWindowObj);
	NPObject *mySensorObject =NPN_CreateObject(m_pNPInstance,GET_NPOBJECT_CLASS(MyDevinfoPluginObject));
	NPVariant v;
	OBJECT_TO_NPVARIANT(mySensorObject, v);
	NPIdentifier n = NPN_GetStringIdentifier("MyDevinfo");
	NPN_SetProperty(m_pNPInstance, sWindowObj, n, &v);
	NPN_ReleaseObject(mySensorObject);
	NPN_ReleaseObject(sWindowObj);

}

In the above you can see the global initialisation of the property and method names for the Devinfo object. When a property of the javascript object is queried, the following code is executed:

bool MyDevinfoPluginObject::GetProperty(NPIdentifier name, NPVariant *result)
{
	//  Retrieve the value of a property.  *result is an out parameter
	//  into which we should store the value
	bool bReturnVal = false;
	VOID_TO_NPVARIANT(*result);

        if (name == sCurrentRSSIValue_id)
	{
		//  Called by: var sensorVal = myDevinfo.currentRSSIValue;
		//  Return the current value to the web page.
		this->m_iCurrentRSSIValue=my_getRSSI();
		INT32_TO_NPVARIANT(this->m_iCurrentRSSIValue , *result);// (((float)this->m_iCurrentValue) / 100.0, *result);
		bReturnVal = true;
	}
	else if (name == sCurrentBattery_id)
	{
		//  Called by: var sensorVal = myDevinfo.currentBatteryLevel;
		//  Return the current value to the web page.
		this->m_iCurrentBatteryLevel=getBatteryPercent();
		INT32_TO_NPVARIANT( this->m_iCurrentBatteryLevel, *result);// (((float)this->m_iCurrentValue) / 100.0, *result);
		bReturnVal = true;
	}
	else if (name == sModelCode_id)
	{
		//  Called by: var sensorVal = myDevinfo.modelCode;
		//  Return the value to the web page.
		char* npOutString = (char *)NPN_MemAlloc(MAX_BUFF);
		sprintf(npOutString, "%s", getModelCode());
		STRINGZ_TO_NPVARIANT( npOutString , *result);// (((float)this->m_iCurrentValue) / 100.0, *result);
		bReturnVal = true;
	}
	if (!bReturnVal)
		VOID_TO_NPVARIANT(*result);
	return bReturnVal;
}

The return types have to be put in a NPVARIANT structure which is then returned to the browser. There are different macros to convert int, float or null terminated strings to a NPVARIANT.

In the code I used separate header and code files to retrieve the information like the battery or RSSI level. This is easy to manage.

If you want to extend the number of properties you need to add a new global NPIdentifier variable, add code to register the new property and code to return the new property value.

The code for a object method is similar. There is also a global variable of type NPIdentifier. Then, when the object method is called from javascript, the following code is executed:

...
bool MyDevinfoPluginObject::Invoke(NPIdentifier name, const NPVariant *args,
                               uint32_t argCount, NPVariant *result)
{
	//  Called when a method is called on an object
	bool bReturnVal = false;
	VOID_TO_NPVARIANT(*result);
	//  Convert to lower case to make our methods case insensitive
	char* szNameCmp = _strlwr(NPN_UTF8FromIdentifier(name));
	NPIdentifier methodName =  NPN_GetStringIdentifier(szNameCmp);
	NPN_MemFree(szNameCmp);
	//  mySensor.monitor(bool)
	if (methodName == sMonitor_id)
	{
		//  Expect one argument which is a boolean (start / stop)
		if (argCount == 1 && NPVARIANT_IS_BOOLEAN(args[0]))
		{
			if (NPVARIANT_TO_BOOLEAN(args[0]))
			{
				//  Create a thread to monitor the sensor			
				CloseHandle(CreateThread(NULL, 0,
				(LPTHREAD_START_ROUTINE)SensorMonitorThread, this, 0, NULL));
			}
			else
			{
				//  Stop monitoring the sensor
				SetEvent(m_hStopSensorMonitor);
			}
			//  Monitor has no return value
			VOID_TO_NPVARIANT(*result);
			bReturnVal = true;
		}
	}
	if (!bReturnVal)
		VOID_TO_NPVARIANT(*result);
	return bReturnVal;
}

As the monitor method is to be called with a boolean to start or stop the monitor, the code will start or stop a background thread. First the argument passed to the method is tested to be a boolean, then it is converted from the provided NPVARIANT to a C boolean.

The background thread uses a window handle to the browser to send update messages. These WM_USER messages are received by an unvisible window and it’s window procedure. That will then invoke a callback method inside the browser context, if available.

Source code and description at github.

Update 15.5.2018: added localIP property to enable HTML to retrieve local IP address.

 

Leave a Reply