在PPAPI插件中创建本地窗口

实验了一个比较奇特的东西,在PPAPI插件里创建一个本地窗口,叠加在插件在网页的位置上。

CEF3默认是多进程架构,PPAPI插件在一个单独进程里跑,这个进程没启动Windows的消息循环,所以,要创建插件的话,得自己搞一个消息循环。另外浏览器窗口属于别的进程,怎么把创建出来的窗口成为浏览器窗口的子窗口,也是个问题。这个第一个要解决的问题。

CEF3还支持单进程运行,Browser、Render、Plugin合一,此时创建本地窗口又和多进程不同。这是第二个问题。

第三个问题是,如何把窗口定位到网页的插件区域。

琢磨了下,都解决了。

foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。

效果

先看看效果图。先是跨进程的:

在PPAPI插件中创建本地窗口_第1张图片

灰色区域那里是我创建的本地窗口,上面有一行字。

再看同一进程的:

在PPAPI插件中创建本地窗口_第2张图片

注意文字的变化。

代码

Talk is cheap,show me the code:

/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.

* 2016-1-13, edited by foruok.
* 如需转载,请关注微信订阅号“程序视界”,回复foruok获取其联系方式
* 
*/
#include 
#include 
#include 
#include 
#include 

#include "ppapi/c/pp_completion_callback.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/pp_instance.h"
#include "ppapi/c/pp_module.h"
#include "ppapi/c/pp_rect.h"
#include "ppapi/c/pp_var.h"
#include "ppapi/c/ppb.h"
#include "ppapi/c/ppb_core.h"
#include "ppapi/c/ppb_graphics_2d.h"
#include "ppapi/c/ppb_image_data.h"
#include "ppapi/c/ppb_instance.h"
#include "ppapi/c/ppb_view.h"
#include "ppapi/c/ppp.h"
#include "ppapi/c/ppp_instance.h"
#include "ppapi/c/ppb_input_event.h"
#include "ppapi/c/ppp_input_event.h"

PPB_GetInterface g_get_browser_interface = NULL;

const PPB_Core* g_core_interface;
const PPB_Graphics2D* g_graphics_2d_interface;
const PPB_ImageData* g_image_data_interface;
const PPB_Instance* g_instance_interface;
const PPB_View* g_view_interface;
const PPB_InputEvent *g_input_interface;
const PPB_MouseInputEvent *g_mouse_interface;


/******foruok: create native window begin******/

static HWND g_child_window = NULL;
struct CreateChildWinParam {
    struct PP_Rect r;
    HWND hWndParent;
};
HANDLE g_hThread = NULL;
DWORD g_dwThreadId = 0;
BOOL g_bInProcess = 0;

static LRESULT CALLBACK VideoWindowProc(HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    PAINTSTRUCT ps;
    HDC hdc;
    RECT r;
    TCHAR textIn[] = _T("Child Window(in-process)");
    TCHAR text[] = _T("Child Window(out-process)");
    switch (uMsg)
    {
    case WM_PAINT:
        GetClientRect(hwnd, &r);
        hdc = BeginPaint(hwnd, &ps);
        SetTextColor(hdc, RGB(0, 200, 0));
        FillRect(hdc, &r, GetStockObject(GRAY_BRUSH));
        if (g_bInProcess)
        {
            TextOut(hdc, 10, 50, textIn, ARRAYSIZE(textIn) - 1);
        }
        else
        {
            TextOut(hdc, 10, 50, text, ARRAYSIZE(text) - 1);
        }
        EndPaint(hwnd, &ps);
        return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


static void RegisterVideoWindowClass()
{
    WNDCLASSEX wcex = {
        /* cbSize = */ sizeof(WNDCLASSEX),
        /* style = */ CS_HREDRAW | CS_VREDRAW,
        /* lpfnWndProc = */ VideoWindowProc,
        /* cbClsExtra = */ 0,
        /* cbWndExtra = */ 0,
        /* hInstance = */ GetModuleHandle(NULL),
        /* hIcon = */ NULL,
        /* hCursor = */ LoadCursor(NULL, IDC_ARROW),
        /* hbrBackground = */ 0,
        /* lpszMenuName = */ NULL,
        /* lpszClassName = */ _T("_ChildWindowClass"),
        /* hIconSm = */ NULL,
    };
    RegisterClassEx(&wcex);
}

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    MSG msg;

    struct CreateChildWinParam *para = (struct CreateChildWinParam *)lpParam;

    TCHAR szLog[256] = { 0 };
    g_child_window = CreateWindowEx(0, _T("_ChildWindowClass"), _T("ChildWindow"),
        para->hWndParent == NULL ? (WS_OVERLAPPEDWINDOW | WS_VISIBLE) : (WS_CHILD | WS_VISIBLE | WS_DISABLED),
        para->r.point.x, para->r.point.y, para->r.size.width, para->r.size.height,
        para->hWndParent, NULL, GetModuleHandle(NULL), NULL);
    _stprintf_s(szLog, 256, _T("create child window(standalone msg loop) at (%d, %d) child = 0x%08x\r\n"),
        para->r.point.x, para->r.point.y,
        g_child_window);
    OutputDebugString(szLog);

    BOOL fGotMessage;
    while ((fGotMessage = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0 && fGotMessage != -1)
    {
        TranslateMessage(&msg);
        if (msg.message == WM_USER && msg.hwnd == NULL)
        {
            OutputDebugString(_T("child window message loop quit\r\n"));
            g_dwThreadId = 0;
            g_hThread = NULL;
            g_child_window = NULL;
            return 0;
        }
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

void CreateChildWindowOnMainThread(void *param, int32_t result)
{
    struct CreateChildWinParam *p = (struct CreateChildWinParam *)param;
    g_child_window = CreateWindowEx(0, _T("_ChildWindowClass"), _T("ChildWindow"),
        WS_CHILD | WS_VISIBLE,
        p->r.point.x, p->r.point.y, p->r.size.width, p->r.size.height,
        p->hWndParent, NULL, GetModuleHandle(NULL), NULL);
    TCHAR szLog[256] = { 0 };
    _stprintf_s(szLog, 256, _T("create child window(in-process) at (%d, %d) child = 0x%08x\r\n"),
        p->r.point.x, p->r.point.y,
        g_child_window);
    OutputDebugString(szLog);
    ShowWindow(g_child_window, SW_SHOW);
    UpdateWindow(g_child_window);
}

void CreateChildWindow(struct PP_Rect *r)
{
    HWND hwnd = FindWindowEx(NULL, NULL, _T("CefBrowserWindow"), NULL);
    HWND hwndWeb = FindWindowEx(hwnd, NULL, _T("Chrome_WidgetWin_0"), NULL);;
    /*if (hwndWeb)
    {
        hwndWeb = FindWindowEx(hwndWeb, NULL, _T("Chrome_RenderWidgetHostHWND"), NULL); //web contents
    }*/
    if (hwndWeb != NULL)OutputDebugString(_T("Got Chrome_RenderWidgetHostHWND\r\n"));
    DWORD pluginPid = GetCurrentProcessId();
    DWORD browserPid = 0;
    GetWindowThreadProcessId(hwnd, &browserPid);
    TCHAR szLog[256] = { 0 };
    _stprintf_s(szLog, 256, _T("Browser pid - %d, plugin pid - %d, brower hwnd - 0x%08x, webpage hwnd - 0x%08x\r\n"), 
        browserPid, pluginPid, hwnd, hwndWeb);
    OutputDebugString(szLog);
    struct CreateChildWinParam *para = (struct PP_Rect *)malloc(sizeof(struct CreateChildWinParam));
    para->r = *r;
    para->hWndParent = hwndWeb;

    if (browserPid == pluginPid)
    {
        g_bInProcess = TRUE;
        g_core_interface->CallOnMainThread(0, PP_MakeCompletionCallback(CreateChildWindowOnMainThread, para), 0);
    }
    else
    {
        g_bInProcess = FALSE;
        g_hThread = CreateThread(NULL, 0, ThreadProc, para, 0, &g_dwThreadId);
        if (g_hThread != NULL)
        {
            OutputDebugString(_T("Launch child window thread.\r\n"));
        }
        else
        {
            OutputDebugString(_T("Launch child window thread FAILED!\r\n"));
        }
    }
}

/* PPP_Instance implementation -----------------------------------------------*/

struct InstanceInfo {
    PP_Instance pp_instance;
    struct PP_Size last_size;
    PP_Resource graphics;

    struct InstanceInfo* next;
};

/** Linked list of all live instances. */
struct InstanceInfo* all_instances = NULL;

/** Returns a refed resource corresponding to the created graphics 2d. */
PP_Resource MakeAndBindGraphics2D(PP_Instance instance,
    const struct PP_Size* size) {
    PP_Resource graphics;

    graphics = g_graphics_2d_interface->Create(instance, size, PP_FALSE);
    if (!graphics)
        return 0;

    if (!g_instance_interface->BindGraphics(instance, graphics)) {
        g_core_interface->ReleaseResource(graphics);
        return 0;
    }
    return graphics;
}

void FlushCompletionCallback(void* user_data, int32_t result) {
    /* Don't need to do anything here. */
}

unsigned int g_colors[4] = { 0xFF888888, 0xFFFF00FF, 0xFF00FFFF, 0xFFEA00FF };
unsigned int g_color_index = 0;

void Repaint(struct InstanceInfo* instance, const struct PP_Size* size) {
    PP_Resource image;
    struct PP_ImageDataDesc image_desc;
    uint32_t* image_data;
    int num_words, i;

    /* Ensure the graphics 2d is ready. */
    if (!instance->graphics) {
        instance->graphics = MakeAndBindGraphics2D(instance->pp_instance, size);
        if (!instance->graphics)
            return;
    }

    /* Create image data to paint into. */
    image = g_image_data_interface->Create(
        instance->pp_instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, PP_TRUE);
    if (!image)
        return;
    g_image_data_interface->Describe(image, &image_desc);

    /* Fill the image with blue. */
    image_data = (uint32_t*)g_image_data_interface->Map(image);
    if (!image_data) {
        g_core_interface->ReleaseResource(image);
        return;
    }
    num_words = image_desc.stride * size->height / 4;

    g_color_index++;
    if (g_color_index >= sizeof(g_colors) / sizeof(g_colors[0])) g_color_index = 0;

    for (i = 0; i < num_words; i++)
        image_data[i] = g_colors[g_color_index];

    /* Paint image to graphics 2d. */
    g_graphics_2d_interface->ReplaceContents(instance->graphics, image);
    g_graphics_2d_interface->Flush(instance->graphics,
        PP_MakeCompletionCallback(&FlushCompletionCallback, NULL));

    g_core_interface->ReleaseResource(image);
}

/** Returns the info for the given instance, or NULL if it's not found. */
struct InstanceInfo* FindInstance(PP_Instance instance) {
    struct InstanceInfo* cur = all_instances;
    while (cur) {
        if (cur->pp_instance == instance)
            return cur;
        cur = cur->next;
    }
    return NULL;
}

PP_Bool Instance_DidCreate(PP_Instance instance,
    uint32_t argc,
    const char* argn[],
    const char* argv[]) {
    struct InstanceInfo* info =
        (struct InstanceInfo*)malloc(sizeof(struct InstanceInfo));
    info->pp_instance = instance;
    info->last_size.width = 0;
    info->last_size.height = 0;
    info->graphics = 0;

    /* Insert into linked list of live instances. */
    info->next = all_instances;
    all_instances = info;

    g_input_interface->RequestInputEvents(instance, PP_INPUTEVENT_CLASS_MOUSE);
    g_input_interface->RequestFilteringInputEvents(instance, PP_INPUTEVENT_CLASS_MOUSE);

    OutputDebugString(_T("Instance_DidCreate\r\n"));

    return PP_TRUE;
}

void Instance_DidDestroy(PP_Instance instance) {
    /* Find the matching item in the linked list, delete it, and patch the
    * links.
    */
    struct InstanceInfo** prev_ptr = &all_instances;
    struct InstanceInfo* cur = all_instances;
    while (cur) {
        if (instance == cur->pp_instance) {
            *prev_ptr = cur->next;
            g_core_interface->ReleaseResource(cur->graphics);
            free(cur);
            return;
        }
        prev_ptr = &cur->next;
        cur = cur->next;
    }

    /**foruok: close native window**/
    if(g_child_window != NULL)SendMessage(g_child_window, WM_CLOSE, 0, 0);
    if (g_dwThreadId != 0)
    {
        OutputDebugString(_T("Plugin was destroyed, tell child window close and thread exit\r\n"));
        PostThreadMessage(g_dwThreadId, WM_USER, 0, 0);
    }
}

void Instance_DidChangeView(PP_Instance pp_instance,
    PP_Resource view) {
    struct PP_Rect position;
    struct InstanceInfo* info = FindInstance(pp_instance);
    if (!info)
        return;

    if (g_view_interface->GetRect(view, &position) == PP_FALSE)
        return;

    if (info->last_size.width != position.size.width ||
        info->last_size.height != position.size.height) {
        /* Got a resize, repaint the plugin. */
        Repaint(info, &position.size);
        info->last_size.width = position.size.width;
        info->last_size.height = position.size.height;

        /**foruok: call create window**/
        if (g_child_window == NULL)
        {
            CreateChildWindow(&position);
        }
    }

    OutputDebugString(_T("Instance_DidChangeView\r\n"));
}

void Instance_DidChangeFocus(PP_Instance pp_instance, PP_Bool has_focus) {
}

PP_Bool Instance_HandleDocumentLoad(PP_Instance pp_instance,
    PP_Resource pp_url_loader) {
    return PP_FALSE;
}

static PPP_Instance instance_interface = {
    &Instance_DidCreate,
    &Instance_DidDestroy,
    &Instance_DidChangeView,
    &Instance_DidChangeFocus,
    &Instance_HandleDocumentLoad
};

PP_Bool InputEvent_HandleInputEvent(PP_Instance instance, PP_Resource input_event)
{
    struct PP_Point pt;
    TCHAR szLog[512] = { 0 };
    switch (g_input_interface->GetType(input_event))
    {
    case PP_INPUTEVENT_TYPE_MOUSEDOWN:
        pt = g_mouse_interface->GetPosition(input_event);
        _stprintf_s(szLog, 512, _T("InputEvent_HandleInputEvent, mouse down at [%d, %d]\r\n"), pt.x, pt.y);
        OutputDebugString(szLog);
        break;
        /*
    case PP_INPUTEVENT_TYPE_MOUSEUP:
        OutputDebugString(_T("InputEvent_HandleInputEvent, mouse up\r\n"));
        break;

    case PP_INPUTEVENT_TYPE_MOUSEMOVE:
        OutputDebugString(_T("InputEvent_HandleInputEvent, mouse move\r\n"));
        break;
    case PP_INPUTEVENT_TYPE_MOUSEENTER:
        OutputDebugString(_T("InputEvent_HandleInputEvent, mouse enter\r\n"));
        break;
    case PP_INPUTEVENT_TYPE_MOUSELEAVE:
        OutputDebugString(_T("InputEvent_HandleInputEvent, mouse leave\r\n"));
        break;
        */
    default:
        return PP_FALSE;
    }
    struct InstanceInfo* info = FindInstance(instance);
    if (info && info->last_size.width > 0)
    {
        Repaint(info, &info->last_size);
    }
    return PP_TRUE;
}

static PPP_InputEvent input_interface = {
    &InputEvent_HandleInputEvent
};

/* Global entrypoints --------------------------------------------------------*/

PP_EXPORT int32_t PPP_InitializeModule(PP_Module module,
    PPB_GetInterface get_browser_interface) {

    /**foruok: register window class**/
    RegisterVideoWindowClass();

    g_get_browser_interface = get_browser_interface;

    g_core_interface = (const PPB_Core*)
        get_browser_interface(PPB_CORE_INTERFACE);
    g_instance_interface = (const PPB_Instance*)
        get_browser_interface(PPB_INSTANCE_INTERFACE);
    g_image_data_interface = (const PPB_ImageData*)
        get_browser_interface(PPB_IMAGEDATA_INTERFACE);
    g_graphics_2d_interface = (const PPB_Graphics2D*)
        get_browser_interface(PPB_GRAPHICS_2D_INTERFACE);
    g_view_interface = (const PPB_View*)
        get_browser_interface(PPB_VIEW_INTERFACE);
    g_input_interface = (const PPB_InputEvent*)get_browser_interface(PPB_INPUT_EVENT_INTERFACE);
    g_mouse_interface = (const PPB_MouseInputEvent*)get_browser_interface(PPB_MOUSE_INPUT_EVENT_INTERFACE);

    if (!g_core_interface || !g_instance_interface || !g_image_data_interface ||
        !g_graphics_2d_interface || !g_view_interface ||
        !g_input_interface || !g_mouse_interface)
        return -1;

    OutputDebugString(_T("PPP_InitializeModule\r\n"));
    return PP_OK;
}

PP_EXPORT void PPP_ShutdownModule() {
}

PP_EXPORT const void* PPP_GetInterface(const char* interface_name) {
    if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0)
    {
        OutputDebugString(_T("PPP_GetInterface, instance_interface\r\n"));
        return &instance_interface;
    }
    else if (strcmp(interface_name, PPP_INPUT_EVENT_INTERFACE) == 0)
    {
        OutputDebugString(_T("PPP_GetInterface, input_interface\r\n"));
        return &input_interface;
    }

    return NULL;
}

工程和原来差不多,不说了。

代码基于PPAPI插件的绘图与输入事件处理改造,添加的部分,在注释处加入了foruok标签,搜索即可查看。下面说明几点。

本地窗口的创建

CreateChildWindow()函数是创建本地窗口的入口函数,它利用进程id,判断了插件进程与浏览器进程是否同一进程。

如果是同一进程,通过调用PPB_Core的CallOnMainThread方法,在主线程执行CreateChildWindowOnMainThread来创建子窗口。

如果不是同一进程,就创建一个线程,在线程里创建窗口并启动了消息循环。注意,Windows下窗口的父子关注,支持跨进程,所以调用CreateWindowEx时只要指定父窗口句柄即可。

窗口定位

DidChangeView方法之前在理解PPAPI的设计里提到过,当插件和浏览器的视图关联起来或者视图发生变化时,DidChangeView方法会被调用。这次我们就在这里创建窗口。窗口的位置,可以通过PPB_View接口的GetRect()方法获取到。这个位置就是我们要创建的本地窗口的位置,传递给CreateWindowEx方法即可。

好啦,新的示例主要就这些内容了,处理输入事件的流程应该也清楚了。


这次实验的东西比较奇特,不走寻常路,仅仅是为了兼容老代码……

相关文章参考:

  • CEF Windows开发环境搭建
  • CEF加载PPAPI插件
  • VS2013编译最简单的PPAPI插件
  • 理解PPAPI的设计
  • PPAPI插件与浏览器的交互过程
  • Windows下从源码编译CEF
  • 编译PPAPI的media_stream_video示例
  • PPAPI插件的绘图与输入事件处理

你可能感兴趣的:(网络编程,CEF与PPAPI开发)