PAYATHON 2026

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 控件完成绘制。

备注:内容仅供参考。