Linux[X11]窗口预览--QML+XCB获取指定wid的窗口图像并通过qml窗口显示
1. 主要功能
实现X11环境下,在qml中显示指定winId窗口的窗口动态预览图像
2. 编译环境
系统:Linux系统,X11窗口环境
编译依赖:Qt5 XCB OpenGL
3. 实现部分
捕获窗口图像
其中m_winId代表X11窗口的winId ,winId怎么获取自行百度,本文不做阐述。
xcb_composite_query_version_cookie_t comp_ver_cookie = xcb_composite_query_version(c, 0, 2);
xcb_composite_query_version_reply_t *comp_ver_reply = xcb_composite_query_version_reply(c, comp_ver_cookie, NULL);
if (!comp_ver_reply)
{
// 返回窗口图标
return;
}
const xcb_setup_t *setup = xcb_get_setup(c);
xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
xcb_screen_t *screen = screen_iter.data;
// request redirection of window
xcb_void_cookie_t cookie = xcb_composite_redirect_window_checked(c, m_winId, XCB_COMPOSITE_REDIRECT_AUTOMATIC);
int win_h, win_w, win_d;
xcb_get_geometry_cookie_t gg_cookie = xcb_get_geometry(c, m_winId);
xcb_get_geometry_reply_t *gg_reply = xcb_get_geometry_reply(c, gg_cookie, NULL);
if (gg_reply) {
win_w = gg_reply->width;
win_h = gg_reply->height;
win_d = gg_reply->depth;
free(gg_reply);
} else {
return;
}
// create a pixmap
xcb_pixmap_t win_pixmap = xcb_generate_id(c);
xcb_composite_name_window_pixmap(c, m_winId, win_pixmap);
// get the image
xcb_get_image_cookie_t gi_cookie = xcb_get_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, win_pixmap, 0, 0, win_w, win_h, (uint32_t)(~0UL));
QSharedPointer<xcb_get_image_reply_t> gi_reply(xcb_get_image_reply(c, gi_cookie, NULL));
if (gi_reply) {
int data_len = xcb_get_image_data_length(gi_reply.data());
uint8_t *data = xcb_get_image_data(gi_reply.data());
if(win_d == 32) {
QImage img((uchar *)data, win_w, win_h, QImage::Format_ARGB32_Premultiplied);
image_data = img.copy();
} else if(win_d == 24) {
QImage img((uchar *)data, win_w, win_h, QImage::Format_RGB32);
image_data = img.copy();
}
}
借助qml显示QImage窗口预览图像
主要思路:
- 构建WindowPreview类继承自QQuickItem;
- 注册winId属性, 可以由qml前段赋值;
- 借助OpenGL实现图片渲染;
- 注册qml组件并使用。
完整代码
windowpreview.h
#pragma once
#define TEST
// Qt
#include <QX11Info>
#include <QVector>
#include <QMatrix4x4>
// Qt Quick
#include <QQuickItem>
#include <QQuickWindow>
// Qt OpenGL
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QOpenGLShaderProgram>
// xcb
#include <xcb/composite.h>
class OpenGLRender;
class WindowPreview : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(uint winId READ winId WRITE setWinId NOTIFY winIdChanged)
public:
explicit WindowPreview(QQuickItem* parent = nullptr);
uint32_t winId() const;
void setWinId(uint32_t winId);
Q_SIGNALS:
void winIdChanged();
protected slots:
void Render();
void OnWindowChanged(QQuickWindow* w);
void Release();
protected:
uint32_t m_winId;
xcb_connection_t* c;
QVector<GLfloat> data;
QOpenGLBuffer VBO;
QOpenGLShaderProgram program;
QOpenGLTexture *texture = nullptr;
QImage image_data;
QColor background;
bool InitGL();
void setImage(QImage &image);
void updateWindowPreview(); // 刷新窗口预览图像
// test only
void setImage(const QString &filename);
};
windowpreview.cpp
#include "windowpreview.h"
WindowPreview::WindowPreview(QQuickItem* parent)
: QQuickItem(parent),
background(QColor(255, 255, 255, 255)), // white background
c(QX11Info::connection())
{
data.resize(24);
data = {
-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,0.0f,
-1.0f,-1.0f,0.0f,1.0f,
1.0f,-1.0f,1.0f,1.0f
};
connect(this, &QQuickItem::windowChanged, this, &WindowPreview::OnWindowChanged);
// test only
setImage("/home/kylin/1.png");
}
uint32_t WindowPreview::winId() const
{
return m_winId;
}
void WindowPreview::setWinId(uint32_t winid)
{
m_winId = winid;
}
void WindowPreview::Render()
{
static bool initGL = InitGL();
Q_UNUSED(initGL);
updateWindowPreview();
QMatrix4x4 trans_;
float scale_im = (float)image_data.width()/(float)image_data.height();
float scale_form = (float)width()/(float)height();
if(scale_im>scale_form)
trans_.ortho(-1.0f,1.0f,-scale_im/scale_form,scale_im/scale_form,-1.0f,1.0f);
else
trans_.ortho(-scale_form/scale_im,scale_form/scale_im,-1.0f,1.0f,-1.0f,1.0f);
program.setUniformValue("transform",trans_);
if(!image_data.isNull() && texture) {
delete texture;
texture = new QOpenGLTexture(image_data);
texture->bind();
}
glClear(GL_COLOR_BUFFER_BIT);
glClearColor((GLfloat)background.red()/255.0,(GLfloat)background.green()/255.0,(GLfloat)background.blue()/255.0,(GLfloat)background.alpha()/255.0);
glDrawArrays(GL_TRIANGLES,0,data.count());
// only test
window()->update();
}
void WindowPreview::OnWindowChanged(QQuickWindow* w)
{
if (w == nullptr) return;
connect(w, &QQuickWindow::beforeRendering, this, &WindowPreview::Render, Qt::DirectConnection);
w->setClearBeforeRendering(false);
}
void WindowPreview::Release()
{
VBO.destroy();
}
bool WindowPreview::InitGL()
{
QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex,this);
const char *vertexShaderSource =
"#version 330 core\n"
"layout(location=0)in vec2 aPos;\n"
"layout(location=1)in vec2 aTexCoord;\n"
"out vec2 TexCoord;\n"
"uniform mat4 transform;\n"
"void main()\n"
"{\n"
"gl_Position = transform * vec4(aPos,0.0f,1.0f);\n"
"TexCoord = aTexCoord;\n"
"}";
vshader->compileSourceCode(vertexShaderSource);
QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
const char *fsrc =
"#version 330 core\n"
"out vec4 FragColor;\n"
"in vec2 TexCoord;\n"
"uniform sampler2D ourTexture;\n"
"void main()\n"
"{FragColor = texture(ourTexture, TexCoord);}";
fshader->compileSourceCode(fsrc);
program.addShader(vshader);
program.addShader(fshader);
program.bindAttributeLocation("ourTexture",0);
program.bindAttributeLocation("transform",1);
program.link();
program.bind();
VBO.create();
VBO.bind();
VBO.allocate(data.data(),data.count()*sizeof (GLfloat));
program.enableAttributeArray(0);
program.setAttributeBuffer(0,GL_FLOAT,0,2,4*sizeof (GLfloat));
program.enableAttributeArray(1);
program.setAttributeBuffer(1,GL_FLOAT,2*sizeof (GLfloat),2,4*sizeof (GLfloat));
// set texture filter
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
connect(window()->openglContext(), &QOpenGLContext::aboutToBeDestroyed, this, &WindowPreview::Release, Qt::DirectConnection);
return true;
}
void WindowPreview::setImage(QImage &image)
{
image_data = image.mirrored();
}
void WindowPreview::updateWindowPreview()
{
xcb_composite_query_version_cookie_t comp_ver_cookie = xcb_composite_query_version(c, 0, 2);
xcb_composite_query_version_reply_t *comp_ver_reply = xcb_composite_query_version_reply(c, comp_ver_cookie, NULL);
if (!comp_ver_reply)
{
// 返回窗口图标
return;
}
const xcb_setup_t *setup = xcb_get_setup(c);
xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
xcb_screen_t *screen = screen_iter.data;
// request redirection of window
xcb_void_cookie_t cookie = xcb_composite_redirect_window_checked(c, m_winId, XCB_COMPOSITE_REDIRECT_AUTOMATIC);
int win_h, win_w, win_d;
xcb_get_geometry_cookie_t gg_cookie = xcb_get_geometry(c, m_winId);
xcb_get_geometry_reply_t *gg_reply = xcb_get_geometry_reply(c, gg_cookie, NULL);
if (gg_reply) {
win_w = gg_reply->width;
win_h = gg_reply->height;
win_d = gg_reply->depth;
free(gg_reply);
} else {
return;
}
// create a pixmap
xcb_pixmap_t win_pixmap = xcb_generate_id(c);
xcb_composite_name_window_pixmap(c, m_winId, win_pixmap);
// get the image
xcb_get_image_cookie_t gi_cookie = xcb_get_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, win_pixmap, 0, 0, win_w, win_h, (uint32_t)(~0UL));
QSharedPointer<xcb_get_image_reply_t> gi_reply(xcb_get_image_reply(c, gi_cookie, NULL));
if (gi_reply) {
int data_len = xcb_get_image_data_length(gi_reply.data());
uint8_t *data = xcb_get_image_data(gi_reply.data());
if(win_d == 32) {
QImage img((uchar *)data, win_w, win_h, QImage::Format_ARGB32_Premultiplied);
image_data = img.copy();
} else if(win_d == 24) {
QImage img((uchar *)data, win_w, win_h, QImage::Format_RGB32);
image_data = img.copy();
}
}
window()->update();
}
// test only
void WindowPreview::setImage(const QString &filename)
{
if(texture) delete texture;
image_data = QImage(filename).mirrored();
texture = new QOpenGLTexture(image_data);
}
4. 使用方法
利用qmlRegisterType函数注册组件
qmlRegisterType<WindowPreview>("MyControls", 1, 0, "WindowPreview");
在qml文件中导入组件
import MyControls 1.0
使用组件
WindowPreview {
winId: 8388613
}