Step 4: Drawing and handling event

4.1  Overview for windowed plugins and windowless plugins.

NPRuntime graphical plug-in supports two models for drawing:

KreaTV's platform-independent npruntime graphical plug-in support is based on windowless plugins, as it doesn't have a general conceptual windowing system. Full details of windowless plugins can be found at:

Gecko_Plugin_API_Reference:Drawing_and_Event_Handling

Note: There are a few variations in the Windowless API depending on the platform. The kreatv implementation most closely resembles the Unix/X11 case described in the Mozilla documentation; in particular, the destination "drawable" is passed in the paint event, not in the NPP_SetWindow call.

4.2  Major aspects of windowless plug-in support in NPRuntime API

4.2.1  Plug-in specifies that it is windowless during initialisation.

To specify that a plug-in is windowless, use NPN_SetValue with NPPVpluginWindowBool as the value of variable and false as the value of value. The plug-in will make this call from its NPP_New method.


NPError NPP_New(NPMIMEType pluginType,
                NPP instance,
                uint16 mode,
                int16 argc,
                char* argn[],
                char* argv[],
                NPSavedData* saved)
{   
  ...

  TPlugin * pPlugin = new TPlugin(instance);

  ...

  instance->pdata = (void *)pPlugin;
  
  ...
}

TPlugin::TPlugin(NPP pNPInstance):
...
{
  ...
  
  //Specify that we're a windowless plug-in.
  NPN_SetValue(pNPInstance, NPPVpluginWindowBool, false);
  
  ...
}

NPError NPN_SetValue(NPP instance, NPPVariable variable, void *value);

Below is the related data type.


namespace ekioh {
    class EKPluginPixMap;
};

typedef struct _YourPluginInstance
{
    ...
    ekioh::EKPluginPixMap* pixmap;

    uint32_t x, y;
    uint32_t width, height;
    ...

} YourPluginInstance;

class TPlugin
{
private:
  ...

  YourPluginInstance pluginInstance;
 
  ...

public:
  TPlugin(NPP pNPInstance);
  ~TPlugin();

  ...
  
  NPError SetWindow(NPWindow* pNPWindow);
  int16 HandleEvent(NPEvent * event);

  ...
};

4.2.2  Browser indicates we're using a "drawable".

Browser indicates we're using a "drawable" instead of a window in NPP_SetWindow(), and defines the plug-in bounds on the drawable. The actual drawable is passed in the paint event.


NPError NPP_SetWindow (NPP instance, NPWindow* pNPWindow)
{
  ...

  TPlugin * pPlugin = (TPlugin *)instance->pdata;

  ...

  //Please call this when a window is created, moved, sized, or destroyed.
  pPlugin->SetWindow(pNPWindow);

  ...
}

//Save the window information.
NPError TPlugin::SetWindow(NPWindow* pNPWindow)
{
  ...

  pluginInstance.x = pNPWindow->x;
  pluginInstance.y = pNPWindow->y;
  pluginInstance.width = pNPWindow->width;
  pluginInstance.height = pNPWindow->height;

  ...

  return TRUE;
}

The drawable is a simple structure describing a pixmap:


enum EKPixelFormat {
  EKPixelFormat_ARGB32
};

struct EKPluginPixMap {
  uint32_t width; ///< width in pixels
  uint32_t height; ///< height in pixels
  uint32_t stride; ///< stride in bytes
  uint32_t bitsPerPixel; ///< bits per pixel
  EKPixelFormat pixelFormat; ///< format of pixel data
  uint8_t* buf; ///< pointer to pixel data
};

4.2.3  Init the pixmap you want to draw on screen.

You need to decide what you want to draw on the screen and convert it in the format of EKPixelFormat_ARGB32. Below is a very simple sample.


  //sample pixmap: a square area 
  const uint32_t pixmapData[5 x 5] = { 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, //a black line.
                                       0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, //a blue line.
                                       0xFF00FF00, 0xFF00FF00, 0xFF00FF00, 0xFF00FF00, 0xFF00FF00, //a green line.
				       0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, //a red line. 
		                       0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, //a black line.
  };	
  const int width=5, height = 5;

  pluginInstance.pixmap = new ekioh::EKPluginPixMap;
  pluginInstance.pixmap->width = width;
  pluginInstance.pixmap->height = height;
  pluginInstance.pixmap->bitsPerPixel = 32;
  pluginInstance.pixmap->stride = sizeof(uint32_t) * width;
  pluginInstance.pixmap->buffer = (uint8_t *) pixmapData;

4.2.4  Invalidating the drawing area.

Plug-in uses NPN_InvalidateRect() to invalidate an area prior to repainting or refreshing (asynchronous redraw). Then NPP_HandleEvent method will pass an update event or a paint message to the Plug-in.

4.2.5  Forcing a pcreatePixmapaint message.

Plug-in uses NPN_ForceRedraw() to force immediate update of any previously invalidated areas (synchronous redraw).

Note: A plug-in must not draw into its drawable unless it receives a paint message.

4.3  Event Handling for Windowless Plugins

On all platforms, platform-specific events(paint, input and so on) are passed to windowless plugins through the NPP_HandleEvent method. The plug-in must return true from NPP_HandleEvent if it has handled the event and false if it has not.


int16 NPP_HandleEvent(NPP instance, NPEvent *event);
{
  ...

  TPlugin * pPlugin = (TPlugin *)instance->pdata;
  if (pPlugin) {
    pPlugin->HandleEvent(event);
  }

  ...
}

int16 TPlugin::HandleEvent(NPEvent *event)
{
  ...

  ekioh::EKPluginEvent* ekEvent = static_cast(event);
  switch (ekEvent->type) {
  case ekioh::EKPluginEvent::Paint:
    paintPixmap(&pluginInstance, *ekEvent)//Here draw the pixmap in window.
    break;
  }
  ...
}

Events are also a simple structure. The only event currently supported is a paint event, that instructs the plug-in to redraw an area.


struct EKPluginEvent {
  {
    Paint
  };

  int version; ///< version of this structure
  EventType type;

  union {
    struct {
      // The plug-in must only draw to the area of this pixmap defined
      // by the NPWindow structure in the NPP_SetWindow call.
      EKPluginPixMap pixmap;

      // Dirty rectangle relative to top-left of pixmap.
      int x0;
      int y0;
      int x1;
      int y1;
    } paint;
  };
};

This structure may change in the future to support additional events or hardware-accelerated pixmaps. The version field (currently 0) will be used to indicate such changes.

The drawable passed to the plug-in is essentially the browser's offscreen buffer. The coordinates passed to the plug-in in NPP_SetWindow and paint events are relative to the origin of the browser's offscreen buffer, not the origin of the plug-in, so the plug-in needs to take care to draw in the correct area.

The kreatv plug-in drawable and events are defined in a new EKPluginEvent.h header file.

Below shows how to draw the previous sample pixmap(a square).


//Paint the sample square pixmap in window.
static void paintPixmap(PluginInstance *This, ekioh::EKPluginEvent& event)
{
  float scaleX = float(This->pixmap->width) / This->width;
  float scaleY = float(This->pixmap->height) / This->height;

  const uint32_t* src = (uint32_t *) This->pixmap->buffer;
  uint8_t* dst = event.paint.pixmap.buffer +
                 (event.paint.pixmap.stride * event.paint.y0) +
                 ((event.paint.pixmap.bitsPerPixel / 8) * event.paint.x0);

  for (int y = event.paint.y0; y < event.paint.y1; y++) {
    uint32_t* d = (uint32_t *) dst;
    int srcY = (y - This->y) * scaleY;
    for (int x = event.paint.x0; x < event.paint.x1; x++) {
      int srcX = (x - This->x) * scaleX;
      *d++ = *(src + ((This->pixmap->stride / 4) * srcY) + srcX);
    }
    dst += event.paint.pixmap.stride;
  }
}