Linux 下 Qt 嵌入的 CEF 浏览器如何调整位置和大小?
结论
代码混用了两种 CEF 窗口模式,而且 X11 调整代码最终操作的是桌面根窗口。应先选定一种模式,再按对应方式调整尺寸:
- 原生子窗口模式:使用
SetAsChild,调整browser->GetHost()->GetWindowHandle()对应的 CEF 子窗口,或通过 Qt 布局管理承载它的控件。 - windowless(OSR)模式:使用
SetAsWindowless,由 Qt 控件提供绘制区域。尺寸变化后调用WasResized(),并在OnPaint中绘制内容。
settings.windowless_rendering_enabled = true 只会启用 windowless 渲染支持,不会自动把通过 SetAsChild 创建的浏览器改为 windowless 模式。
当前代码中的问题
在 resizeEvent 中,代码先取得 CEF 浏览器的窗口句柄:
CefWindowHandle hwnd_ = browser->GetHost()->GetWindowHandle();
随后又执行了:
Window parent_xwindow = DefaultRootWindow(disp);
hwnd_ = parent_xwindow;
这会覆盖 CEF 浏览器的窗口句柄,把操作目标改成 X11 根窗口。因此,后续的 XMoveResizeWindow 不会调整嵌入 Qt 控件中的 CEF 子窗口。不要用 DefaultRootWindow(disp) 代替浏览器窗口句柄。
此外,SetAsChild 用于创建原生子窗口,而示例同时设置了 windowless_rendering_enabled,并实现了 GetViewRect 和 OnPaint。这些配置对应不同的渲染路径,应根据目标行为选择一种。
方案一:继续使用原生子窗口
保留 SetAsChild。在 Linux/X11 分支中,使用 CEF 返回的窗口句柄,并在调用 X11 函数前检查 disp 是否有效。不要覆盖 CEF 窗口句柄:
Display* disp = cef_get_xdisplay();
if (!disp) {
return;
}
CefWindowHandle cefHandle = browser->GetHost()->GetWindowHandle();
if (!cefHandle) {
return;
}
Window cefWindow = static_cast<Window>(cefHandle);
QSize size = e->size();
XMoveResizeWindow(disp, cefWindow, 0, 0,
static_cast<unsigned int>(size.width()),
static_cast<unsigned int>(size.height()));
XFlush(disp);
这里的 x、y 是相对于该 X11 窗口父窗口的坐标。如果浏览器应铺满 CefWidget,通常使用 (0, 0) 和控件宽高,而不是控件相对于主窗口的 geometry() 坐标。确认目标是 CEF 子窗口后,可以按需将这段逻辑封装为 linuxMoveWindow。
对于 Qt,通常可以让 CefWidget 加入 Qt 布局,并在布局或控件尺寸变化时同步调整 CEF 原生子窗口的大小。调整逻辑应在浏览器创建且窗口句柄可用后执行。由于控件可能先收到 resizeEvent,浏览器之后才创建,因此也可以在浏览器创建完成后,根据控件当时的尺寸进行调整。
方案二:使用 windowless 渲染
如果要通过 OnPaint 将 CEF 内容绘制到 Qt 控件,应将创建方式改为 windowless,而不是使用 SetAsChild:
CefWindowInfo window_info;
window_info.SetAsWindowless(static_cast<CefWindowHandle>(winId()));
初始化时需要启用 settings.windowless_rendering_enabled,并由 GetViewRect 返回有效的视图尺寸。Qt 控件尺寸变化时,通知 CEF:
void resizeEvent(QResizeEvent* e) override
{
QWidget::resizeEvent(e);
if (browser_) {
browser_->GetHost()->WasResized();
}
}
WasResized() 用于通知 windowless 浏览器视图区域发生变化,不能用来调整 SetAsChild 创建的原生窗口。OnPaint 收到新尺寸的缓冲区后,再将内容绘制到控件上。
检查事项
- 移除将 CEF 窗口句柄替换为根窗口句柄的代码。
cef_get_xdisplay()可能返回空指针。调用DefaultRootWindow或其他 X11 函数前,应先检查指针。- X11 窗口句柄应与所用的
Display*属于同一个 X11 会话。如果调整没有效果,检查两者是否匹配。 - 原生子窗口模式不应依赖
OnPaint绘制浏览器内容;windowless 模式则通过GetViewRect、WasResized()和OnPaint配合 Qt 控件完成绘制。
备注:内容仅供参考。
