实操:如何将安卓 App与Linux系统应用级融合(附代码)

Android App与Linux系统的应用融合适配,是指通过将Android系统已渲染完毕的图形层以X11 Client角色,提交至X11 Server进行合成显示,并同时接收来自X11 Server的鼠标、键盘及输入法等外部输入事件,将其转发至Android Framework处理的一种系统集成方式。融合后能实现android的应用在Linux 的X11 桌面上以X11 窗口的形式显示,同时能接受X11 的输入法字符,鼠标点击事件、键盘事件。Android应用的窗口也间接接受linux的窗口管理器的管理。

1. 技术背景

1.1 Android 图像合成与送显关键步骤

andorid的图像合成送显流程中最后面的两个方法是hwc_prepare和hwc_set。

  • hwc_prepare的作用是surfaceflinger询问hwc,哪些层需要使用client(gpu)合成,哪些层hwc自己可以合成(device 合成)。hwc本身不是无限能力的,所以总会存在还需要client合成的情况。

  • hwc_set方法的主要作用是送显。

1.2 Waydroid往Linux Wayland Server 合成与送显的两种模式

waydroid实现了基于wayland协议的应用融合适配,合成与显示有两种方式:

单窗口模式: Android所有画面显示在一个linux窗口,在hwc_prepare中设置所有的layer都为 HWC_FRAMEBUFFER,由gpu合成所有layer,最后送显。单这不是单窗口的唯一合成方式,也可以采用多窗口模式的合成方式。

多窗口模式: 以应用为单位,分别创建Wayland窗口进行显示,在hwc_prepare中设置所有layer(除HWC_FRAMEBUFFER_TARGET)都为HWC_OVERLAY,这样所有的layer在hwc_set 中可以按应用为单位进行合成后再送显。

1.3 waydroid合成与送显核心流程

以多窗口模式举例,介绍waydroid的合成与送显流程。

  1. 在hwc_prepare方法里将所有layer的合成方式都改为HWC_OVERLAYS,这意味着所有layer由hwcomposer在hwc_set方法里进行合成,下述所有步骤都在hwc_set方法里进行。

  2. 在hwc_set方法里遍历所有layers,将layers按照其app_title进行分类,检查这个分类名(app名称)在全局windows map中检查是否已经创建对应的wayland 窗口了。如果没有,就创建一个window(自定义数据结构)对象,并初始化window中的wayland surface,并将window放入windows map中。

  3. 创建主窗口后,再以layer为单位创建对应的wl_subsurface,绑定到主surface上。

  4. 最后将主surface提交,提交的同时,就会将wl_subsurface合成。

  5. 当app关闭后,则hwc_set中不再接收到该app的layers,那么遍历全局windows窗口时,在layers遍历查找,如果没发现该window对应的app,那就会销毁该window,同时从map中删除这个对象。

通过以上步骤,实现了以android app 为单位在linux wayland协议上创建窗口,进行多layers合成与显示的过程,也介绍了关闭app后,如何销毁对应wayland 窗口的逻辑。

2. 基于X11 协议的合成与送显

基于X11 协议合成与送显的核心流程与waydroid流程是一样的。主要区别在于图像合成和送显方法,layer格式转换处理(x11 server支持的格式较少)。如下介绍详细的流程。

2.1 为应用创建主窗口

主窗口的作用是建立一个和Android屏幕一样大小的坐标系,每一个android应用都需要创建一个主窗口,合成图像时就可以直接使用android layer的坐标值,主窗口必须设置为透明,因为android应用在小窗模式下要和其他linux、andorid应用同时显示在桌面上。透明区域还必须设置为不接收鼠标事件和键盘事件,后续在app的所有layer并联区域上再设置允许接收事件。

bash 复制代码
//使用xcb api创建窗口,这里一个比较重要的参数是第2个参数32,32表示argb,带有alpha通道
xcb_create_window(display->xcbconnection,
                    32,
                    window->xcbwindow,
                    display->xcbscreen->root,
                    0, 0, display->width, display->height, 0,
                    XCB_WINDOW_CLASS_INPUT_OUTPUT,
                    display->visualid,
                    value_mask, value_list);
 
//设置主窗口完全不响应事件
if (use_subsurfaces) {
        xcb_shape_rectangles(display->xcbconnection,
        XCB_SHAPE_SO_SET,        // 设置操作(替换现有形状)
        XCB_SHAPE_SK_INPUT,
        XCB_CLIP_ORDERING_UNSORTED,
        window->xcbwindow,
        0, 0, 0, NULL);          // 0个矩形,NULL数组
    }
//backpixmap用于双缓冲显示,减少显示撕裂
window->backpixmap = XCreatePixmap(display->x11display, window->xcbwindow, display->width, display->height, 32);
if (window->backpixmap == None) {
    return NULL;
}
window->backxpicture = XRenderCreatePicture(display->x11display, window->backpixmap,display->argb_format, CPRepeat, &pa);
// 用透明填充backpixmap
XRenderColor transparent_color = {0, 0, 0, 0};  // RGBA all zero for transparent
XRenderFillRectangle(display->x11display, PictOpSrc, window->backxpicture, &transparent_color, 0, 0, display->width, display->height);

2.2 转换layer格式为x11 server支持的rgba8888

经实测x11 server在32位的合成模式下只支持rgba8888,比如rgb565不支持。所以需要使用egl转换layer buffer格式。

bash 复制代码
//通过egl image转存buffer到一个新建的buffer,opengles驱动程序会自动执行格式转换
 
void egl_convert_buffer_to_BGRA_8888(struct display* display, android::sp<android::GraphicBuffer> src_buffer, android::sp<android::GraphicBuffer> dst_buffer) {
    static GLuint shader_program = 0;
    static GLuint VAO = 0, VBO = 0, EBO = 0;
    static int gl_initialized = 0;
 
    if (!gl_initialized) {
        // 创建着色器程序
        shader_program = create_shader_program();
        if (!shader_program) {
            return;
        }
 
        // 设置顶点数据(全屏四边形)
        float vertices[] = {
            // 位置      // 纹理坐标
            -1.0f, -1.0f, 0.0f, 0.0f,
             1.0f, -1.0f, 1.0f, 0.0f,
             1.0f,  1.0f, 1.0f, 1.0f,
            -1.0f,  1.0f, 0.0f, 1.0f
        };
        unsigned int indices[] = {
            0, 1, 2,
            2, 3, 0
        };
 
        glGenVertexArraysOES(1, &VAO);
        glGenBuffers(1, &VBO);
        glGenBuffers(1, &EBO);
 
        glBindVertexArrayOES(VAO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
 
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
 
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
        glEnableVertexAttribArray(1);
 
        gl_initialized = 1;
    }
 
    EGLint image_attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
 
    // Create EGLImage for source buffer
    auto src_image = eglCreateImageKHR(display->egl_dpy, EGL_NO_CONTEXT,
                                    EGL_NATIVE_BUFFER_ANDROID, (EGLClientBuffer) src_buffer->getNativeBuffer(),
                                    image_attrs);
    if (src_image == EGL_NO_IMAGE_KHR) {
        ALOGE("Failed to create EGLImage from source buffer: 0x%x", eglGetError());
        return;
    }
 
    // Create EGLImage for destination buffer
    auto dst_image = eglCreateImageKHR(display->egl_dpy, EGL_NO_CONTEXT,
                                    EGL_NATIVE_BUFFER_ANDROID, (EGLClientBuffer) dst_buffer->getNativeBuffer(),
                                    image_attrs);
    if (dst_image == EGL_NO_IMAGE_KHR) {
        ALOGE("Failed to create EGLImage from destination buffer: 0x%x", eglGetError());
        eglDestroyImageKHR(display->egl_dpy, src_image);
        return;
    }
 
    // Create texture for source buffer
    GLuint src_texture;
    glGenTextures(1, &src_texture);
    glBindTexture(GL_TEXTURE_2D, src_texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, src_image);
 
    // Create texture for destination buffer
    GLuint dst_texture;
    glGenTextures(1, &dst_texture);
    glBindTexture(GL_TEXTURE_2D, dst_texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, dst_image);
 
    // Create framebuffer and bind destination texture
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_texture, 0);
 
    // Check framebuffer completeness
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        ALOGE("Framebuffer not complete");
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glDeleteFramebuffers(1, &framebuffer);
        glDeleteTextures(1, &src_texture);
        glDeleteTextures(1, &dst_texture);
        eglDestroyImageKHR(display->egl_dpy, src_image);
        eglDestroyImageKHR(display->egl_dpy, dst_image);
    return ;
    }
 
    // Set viewport and use shader program
    glViewport(0, 0, src_buffer->getWidth(), src_buffer->getHeight());
    glUseProgram(shader_program);
 
    // Set uniform variable
    GLint texture_location = glGetUniformLocation(shader_program, "ourTexture");
    glUniform1i(texture_location, 0);
 
    // Bind source texture and render
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, src_texture);
 
    glBindVertexArrayOES(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    glFinish();
 
    // Cleanup
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glDeleteFramebuffers(1, &framebuffer);
    glDeleteTextures(1, &src_texture);
    glDeleteTextures(1, &dst_texture);
    eglDestroyImageKHR(display->egl_dpy, src_image);
    eglDestroyImageKHR(display->egl_dpy, dst_image);
}
 
//使用一个egl work线程执行egl格式转换
static void * produce_BGRA_8888(struct waydroid_hwc_composer_device_1 *pdev, sp<android::GraphicBuffer> src_gb, sp<android::GraphicBuffer> dst_gb) {
    pdev->display->egl_work_queue.push_back(std::bind(egl_convert_buffer_to_BGRA_8888, pdev->display, src_gb,dst_gb));
    sem_post(&pdev->display->egl_go);
    sem_wait(&pdev->display->egl_done);
    return (void *)dst_gb->getNativeBuffer()->handle;
}
 
//创建一个bgra8888的目标缓存,用于接收其他格式的纹理
 sp<android::GraphicBuffer> dst_gb = new android::GraphicBuffer(
       width, height, HAL_PIXEL_FORMAT_BGRA_8888,
       GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER);
 if (dst_gb->initCheck() != android::NO_ERROR) {
    ALOGE("Failed to create destination GraphicBuffer");
    return ;
}
//如果不是x11 server支持的bgra8888格式,就要新建一个dest graphic buffer
if (format != HAL_PIXEL_FORMAT_BGRA_8888) {
        sp<android::GraphicBuffer> gb_for_stride = new android::GraphicBuffer(width, height,
        format, 1, GRALLOC_USAGE_HW_COMPOSER |
        GRALLOC_USAGE_HW_TEXTURE, std::string("gb_for_stride") + std::to_string(getpid()));
        if (gb_for_stride->initCheck() != android::NO_ERROR) {
        ALOGE("Failed to create gb_for_stride");
        return;
        }
 
        int stride_for_src_gb;
        switch (format) {
        case HAL_PIXEL_FORMAT_RGBA_8888:
        case HAL_PIXEL_FORMAT_RGBX_8888:
        case HAL_PIXEL_FORMAT_BGRA_8888:
            stride_for_src_gb = gb_for_stride->getStride() * 4;
            break;
        case HAL_PIXEL_FORMAT_RGB_888:
            stride_for_src_gb = gb_for_stride->getStride() * 3;
            break;
        case HAL_PIXEL_FORMAT_RGB_565:
            stride_for_src_gb = gb_for_stride->getStride() * 2;
            break;
        default: //other formats need to test!!!
            stride_for_src_gb = gb_for_stride->getStride();
        }
 
        // Create source GraphicBuffer from existing handle
        sp<android::GraphicBuffer> src_gb = new android::GraphicBuffer(
        (native_handle_t*)layer->handle, android::GraphicBuffer::WRAP_HANDLE,
        width, height, format, 1, uint64_t(usage), stride_for_src_gb);
        if (src_gb->initCheck() != android::NO_ERROR) {
        ALOGE("Failed to create source GraphicBuffer from handle");
        return ;
        }
            if (!produce_BGRA_8888(pdev, src_gb, dst_gb)) {
                ALOGE("produce_BGRA_8888 failed");
                return ;
            }
        const native_handle_t* native_handle = dst_gb->getNativeBuffer()->handle;
        struct gralloc_handle_t * drm_handle = (struct gralloc_handle_t*)native_handle;
        prime_fd = drm_handle->prime_fd;
        size = dst_gb->getStride() * height * 4;
        stride = dst_gb->getStride() * 4 ;
    }

2.3 将图形buffer导出为dri3_pixmap,封装成picture

以gralloc_gbm创建的graphic buffer为例,其包含的prime_fd是一种dma buf的fd, 该fd可被用于零拷贝传递数据。这里通过xcb_dri3_pixmap_from_buffer,即可将其封装成一个xcbpixmap。继而基于xcbpixmap(pixmap是x11中最基础的资源对象)创建RenderPicture。后续通过Render Picture可以实现一些高级功能,在这里我们最需要的是透明合成的能力。

bash 复制代码
if (pdev->display->gtype == GRALLOC_GBM) {
        struct gralloc_handle_t *drm_handle = (struct gralloc_handle_t *)layer->handle;
        width = drm_handle->width;
        height = drm_handle->height;
        stride = drm_handle->stride;
        format = drm_handle->format;
        usage = drm_handle->usage;
        prime_fd = drm_handle->prime_fd;
}
//clone prime fd
int x11_fd = dup(prime_fd);
if (x11_fd >= 0) {
    fcntl(x11_fd, F_SETFD, FD_CLOEXEC);
}else {
    ALOGE("dup fd failed");
    return ;
}
buf->xcbpixmap = xcb_generate_id(pdev->display->xcbconnection);
XRenderPictureAttributes pa;
pa.repeat = False;
//通过dma buf fd(x11_fd)创建一个dri3 pixmap
xcb_void_cookie_t pixmap_cookie = xcb_dri3_pixmap_from_buffer(pdev->display->xcbconnection,
            buf->xcbpixmap, xcbwindow, size, width, height, stride, 32, 32, x11_fd);
xcb_generic_error_t *pixmap_error = xcb_request_check(pdev->display->xcbconnection, pixmap_cookie);
if (pixmap_error) {
      ALOGE("XCB error in xcb_dri3_pixmap_from_buffer: %d", pixmap_error->error_code);
      free(pixmap_error);
      close(x11_fd);
      return ;
}
buf->xpicture = XRenderCreatePicture(pdev->display->x11display, buf->xcbpixmap,pdev->display->argb_format, CPRepeat, &pa);
close(x11_fd);

2.4 按指定坐标合成图层到backxpicture

layer中包含裁减的坐标和当前layer的真实坐标和高度、宽度。计算好这些数据后,就可以将xpicture按照指定坐标(这里依赖前文创建的主窗口)合成到window→backxpicture。 合成时选用PictOpOver表示支持透明合成的方式将当前picutre合成到backxpicture。

bash 复制代码
hwc_rect_t sourceCrop = layer->sourceCropi;
 
 if (layer->transform & HWC_TRANSFORM_ROT_90) {
     sourceCrop.left = layer->sourceCropi.top;
     sourceCrop.top = layer->sourceCropi.left;
     sourceCrop.right = layer->sourceCropi.bottom;
     sourceCrop.bottom = layer->sourceCropi.right;
 }
  
 xcb_configure_window_value_list_t values;
 values.x = floor(layer->displayFrame.left / pdev->display->scale);
 values.y = floor(layer->displayFrame.top / pdev->display->scale);
 // Calculate source crop dimensions
 int src_x = fmax(0, sourceCrop.left);
 int src_y = fmax(0, sourceCrop.top);
 int src_width = fmax(1, sourceCrop.right - sourceCrop.left);
 int src_height = fmax(1, sourceCrop.bottom - sourceCrop.top);
 
 // Calculate destination dimensions (scaled)
 int dst_width = fmax(1, ceil((layer->displayFrame.right - layer->displayFrame.left) / pdev->display->scale));
 int dst_height = fmax(1, ceil((layer->displayFrame.bottom - layer->displayFrame.top) / pdev->display->scale));
 
 
 if (use_subsurface) {
 if (window->lastLayer == 0) {
     window->rects.clear(); //清理上次收集的各图层的尺寸大小和坐标
     window->crops.clear(); //清理裁减区域数据
 }
     xcb_rectangle_t rect;
     rect = {static_cast<int16_t>(values.x), static_cast<int16_t>(values.y), static_cast<uint16_t>(dst_width),static_cast<uint16_t>(dst_height)};
     window->rects.push_back(rect);
     window->crops.push_back(sourceCrop);
 }
 //使用带透明的合成方式,将本layer合成到总的backxpicture PictOpOver是将本层以overlay的方式合并到之前的层。
 //这里还有一个关键就是values.x 和values.y 这个坐标就用于在第一节的主窗口进行坐标定位
 XRenderComposite(pdev->display->x11display, PictOpOver, buf->xpicture, None, window->backxpicture,
               src_x, src_y, 0, 0, values.x, values.y, dst_width, dst_height);

2.5 将back picture提交到前端显示

在前面的章节中,以应用为单位已将它所有的layers都合成到了window→backxpicture。只需要将backxpicture以覆盖的方式------PictOpSrc合成到xpicture(xpicture已和xcb窗口绑定了)即可。同时需要更新允许接收事件的窗口区域,因为有可能在这一帧中android窗口大小或者坐标可能都发生了变化。set_black_background是针对部分多layer的应用,多个layer合成的时候,如果没有黑色背景,那这个应用部分位置会保持透明,透过这些位置直接能看到背后的桌面。

bash 复制代码
if (it->second->rects.size() > 1)
 {
    set_black_background(pdev,it->second);
 }
XRenderComposite(pdev->display->x11display, PictOpSrc, it->second->backxpicture, None, it->second->xpicture,
            0, 0, 0, 0, 0,0, pdev->display->width, pdev->display->height);
 
//设置允许接收事件输入的窗口大小,实际上是android app
if (!it->second->rects.empty()) {
   xcb_shape_rectangles(pdev->display->xcbconnection,
            XCB_SHAPE_SO_SET,      // 设置操作
            XCB_SHAPE_SK_INPUT,    // 输入形状
            XCB_CLIP_ORDERING_UNSORTED,
           it->second->xcbwindow,
           0, 0,
           it->second->rects.size(),
           it->second->rects.data());
     }              
}
XFlush(pdev->display->x11display);

3 基于x11协议的 输入法 适配

X11 输入法系统基于客户端-服务器 模型 ,主要包含以下核心组件:

  • XIM (X Input Method) 协议:基础输入法框架,每个应用创建一个表示应用和输入法服务器之间的连接

  • XIC (X Input Context):每个输入窗口的上下文

3.1 创建x11 协议输入法核心对象

我们在hwc的初始化函数中创建这两个对象,创建代码如下所示:

bash 复制代码
//im为input method
display->im = xcb_xim_create(display->xcbconnection, display->screen_default_nbr, NULL);
 
// Open connection to XIM server.
bool result = xcb_xim_open(display->im, open_im_callback, true, display);
if(!result){
        return NULL;
}
 
void open_im_callback(xcb_xim_t *im, void *user_data) {
    ALOGE("open_callback called");
    //(void)user_data;
    struct display* display = (struct display*)user_data;
    if(!display){
        ALOGE("error display is NULL");
        return;
    }
    uint32_t input_style = XCB_IM_PreeditPosition | XCB_IM_StatusArea;
    xcb_point_t spot;
    spot.x = 800;
    spot.y = 500;
    xcb_xim_nested_list nested = xcb_xim_create_nested_list(im, XCB_XIM_XNSpotLocation, &spot, NULL);
    //ic 为输入上下文
    xcb_xim_create_ic(im, create_ic_callback, display, XCB_XIM_XNInputStyle,
                      &input_style, XCB_XIM_XNClientWindow, &display->xcbscreen->root,
                      XCB_XIM_XNFocusWindow, &display->xcbscreen->root, XCB_XIM_XNPreeditAttributes,
                      &nested, NULL);
    free(nested.data);
}
 
void create_ic_callback(xcb_xim_t *im, xcb_xic_t new_ic, void *user_data) {
    ALOGE("create_ic_callback called");
    //(void) user_data;
    struct display* display = (struct display*)user_data;
    if(!display){
        ALOGE("error display is NULL");
        return;
    }
    display->ic = new_ic;
    if (display->ic) {
        ALOGE("new ic id:%u ", display->ic);
        xcb_xim_set_ic_focus(im, display->ic);
    }
}

3.2 转发输入事件到android framework

输入事件从类别上分为键盘和鼠标事件,对于键盘事件要首先确认是否被输入法消费了。只有输入法不消费的键盘事件,才需要转发给应用(这里指android framework)。但我们只创建了一个input context,而且绑定的是xcb 的 screen root窗口,root窗口是无法获取焦点的,所以这种模式下,需要手动通过xcb_xim_forward_event 将键盘事件转发给这个input context。

bash 复制代码
void *event_loop_thread(void *arg) {
    ALOGE("input_loop_event start");
    struct display* display = (struct display*)arg;
    xcb_connection_t *connection = display->xcbconnection;
 
    xcb_generic_event_t *event;
    while ((event = xcb_wait_for_event(connection))) {
        if (!event) {
            if (xcb_connection_has_error(connection)) {
                ALOGE("XCB connection error: %d", xcb_connection_has_error(connection));
                break;
            }
            ALOGE("error event is null. ");
            continue;
        }
        uint8_t event_type = event->response_type & ~0x80;
        ALOGD("Processing event: type=%d", event_type);
        if(display->im){
            //先判断该事件是否应已被输入法处理,如果是直接free该事件;
            //如果不是,则将事件转发给ic。 通常第一次给到的事件,肯定是输入法未处理的,然后这里将事件转发给ic,im才会感知到有输入窗口,后续xcb_xim_filter_event才有可能为true
            if (!xcb_xim_filter_event(display->im, event)) {
                // Forward event to input method if IC is created.
                if (display->ic && (((event->response_type & ~0x80) == XCB_KEY_PRESS) ||
                           ((event->response_type & ~0x80) == XCB_KEY_RELEASE))) {
                    //这里将事件转发给input context
                    xcb_xim_forward_event(display->im, display->ic, (xcb_key_press_event_t *)event);
                    free(event);
                    continue;
                }
            }else{
                ALOGE("event has been consumed by input method. ");
                free(event);
                continue;
            }
        }else{
            ALOGE("error im is null. ");
        }
        //输入法不处理的事件才继续处理
        switch (event_type) {
            //处理窗口焦点获得事件
            case XCB_FOCUS_IN:{
                xcb_focus_in_event_t *focus = (xcb_focus_in_event_t *)event;
                xcb_window_t focused_win = focus->event;
                std::scoped_lock lock(display->windowsMutex);
                for (auto it = display->x11_windows->begin(); it != display->x11_windows->end(); it++) {
                    ALOGE("Task : %s", it->first.c_str());
                    if (it->second->xcbwindow == focused_win){
                        ALOGE("Task %s gained focus", it->first.c_str());
                        if (display->task != nullptr) {
                            if (it->first != "Openfde" && it->first != "none" && it->first != "0") {
                                if(isValidInteger(it->first)){
                                    //这个事件会发送给android系统里的android窗口
                                    display->task->setFocusedTask(stoi(it->first));
                                }
                            }
                        }
                    }
                }
                disable_auto_repeat(display->x11display);
                break;
            }
            case XCB_FOCUS_OUT:{
                enable_auto_repeat(display->x11display);
                ALOGE("Focus lost, releasing all keys");
                for (size_t i = 0; i < display->keysDown.size(); i++) {
                    if (display->keysDown[i] == WL_KEYBOARD_KEY_STATE_PRESSED) { //如果失去焦点,就将所有按下的按键都释放掉
                        send_key_event(display, i, WL_KEYBOARD_KEY_STATE_RELEASED);
                    }
                }
                break;
            }
            case XCB_CLIENT_MESSAGE: {
                xcb_client_message_event_t *cm = (xcb_client_message_event_t *)event;
                ALOGE("cm->type: %d, cm->data.data32[0]: %d", cm->type, cm->data.data32[0]);
                xcb_window_t focused_win = cm ->window;
                std::scoped_lock lock(display->windowsMutex);
                for (auto it = display->x11_windows->begin(); it != display->x11_windows->end(); it++) {
                    ALOGE("Task : %s", it->first.c_str());
                    if (it->second->xcbwindow == focused_win){
                        ALOGE("it->second->wm_protocols: %d, it->second->wm_delete_window: %d",
                            it->second->wm_protocols, it->second->wm_delete_window);
                        if (cm->type == it->second->wm_protocols && cm->data.data32[0] == it->second->wm_delete_window) {
                            if (display->task != nullptr) {
                                if (it->first != "Openfde" && it->first != "none" && it->first != "0") {
                                    ALOGE("remove task %s", it->first.c_str());
                                    if(isValidInteger(it->first)){
                                        //移除android任务,这会导致hwc 收不到该task的layer,从而会出发x11 client自己关闭x11窗口
                                        display->task->removeTask(stoi(it->first));
                                    }
                                }else{
                                    ALOGE("Received XCB_CLIENT_MESSAGE, ignoring\n");
                                }
                            }
                        }
                    }
                }
                break;
            }
            case XCB_BUTTON_PRESS:{
                    ALOGV("XCB_BUTTON_PRESS received");
                    if (dispatcher.button_press_cb) {
                        dispatcher.button_press_cb(arg, (xcb_button_press_event_t *)event);
                    }
                    ALOGE("im: %p", display->im);
                    xcb_button_press_event_t *xcb_button_event = (xcb_button_press_event_t *)event;
                    if(display->im){
                        xcb_point_t spot = {xcb_button_event->root_x, xcb_button_event->root_y};
                        ALOGE("on button press update_spot_location x: %d, y: %d", spot.x, spot.y);
                        update_spot_location(display->im, display->ic, spot);
                    }
                    break;
                }
            case XCB_BUTTON_RELEASE:
                ALOGV("XCB_BUTTON_RELEASE received");
                if (dispatcher.button_release_cb) {
                    dispatcher.button_release_cb(arg, (xcb_button_release_event_t *)event);
                }
                break;
            case XCB_MOTION_NOTIFY:
                if (dispatcher.motion_notify_cb) {
                    dispatcher.motion_notify_cb(arg, (xcb_motion_notify_event_t *)event);
                }
                break;
        }
        free(event);
    }

4 还存在的问题

目前还存在的问题是当一个应用最大化后,surfaceflinger便不会将起他应用送入hwc层了,这样hwc_set里便检测不到已打开应用的layers,这样除最大化的应用自身外,其他应用都会被关闭。

代码地址:https:// gitee .com/openfde/waydroid_android_hardware_waydroid/tree/dri3

相关推荐
xiangxiongfly91536 分钟前
Android VideoView总结
android·videoview
Nontee40 分钟前
Quartz 集群 + 策略模式:逾期预警推送系统设计实录
linux·服务器·策略模式
NJCloud1 小时前
Openstack(一)——基础环境配置与依赖服务安装
linux·架构·openstack
杉氧1 小时前
从 Modifier 到 Flexbox:React Native 布局与样式设计哲学
android·前端·react native
额额额对了1 小时前
Linux C 多线程编程基础:从内存模型到生命周期管理
linux·开发语言·算法
mengge.cloud1 小时前
Linux小白入门教程(基础命令篇)
linux·运维·服务器
Kapaseker1 小时前
我常用的 5 个 Kotlin 优化小技巧
android·kotlin
嵌入式小宁1 小时前
C11 原子操作 `atomic_int` 通俗指南
linux·c语言·嵌入式硬件