agentmux: forward-port drag + right-click HTCAPTION + transparency cascade onto CEF 148 (7778)#3
Conversation
WindowEventFilterLinux::HandleMouseEventWithHitTest unconditionally consumes all mouse presses on HTCAPTION (caption / -webkit-app-region: drag areas), calling OnClickedCaption which on right-click resolves to a kMenu action that requires view->context_menu_controller(). CEF does not set a controller, so the kMenu branch hits 'break' without SetHandled, and the event is consumed without any menu being shown. This patch lets right-clicks fall through (return false) so CEF embedders can show their own context menu via the standard renderer-side contextmenu event. Left-click drag is unaffected: OnClickedCaption still records begin_drag_location_ and motion-gated MaybeDispatchHostWindowDragMovement still fires on subsequent drag. For: AgentMux drag/right-click coexistence on Linux/Wayland. See docs/PLAN-CEF-FORK-AND-PATCHES.md in the agentmux repo.
Adds a public CefWindow API to initiate a native interactive window move without requiring -webkit-app-region: drag on the source element. Useful when an embedder wants both window-drag AND contextmenu (or other renderer mouse events) on the same title-bar element. Why: Chromium suppresses ALL renderer events (mousedown, contextmenu, click, etc.) on -webkit-app-region: drag elements before they reach the renderer process. That makes "drag the window from here" and "right-click shows the app context menu here" mutually exclusive on the same element. The standard workaround — JS-driven drag — needs a CEF API to actually trigger the compositor-driven move; this is that API. Implementation (Linux/Ozone): looks up the underlying ui::PlatformWindow via aura::WindowTreeHostPlatform::platform_window() and calls ui::WmMoveResizeHandler::DispatchHostWindowDragMovement(HTCAPTION, ...). On Wayland this dispatches xdg_toplevel.move using the most recent input serial; on X11 it dispatches _NET_WM_MOVERESIZE. Both are non-blocking. Notes: - Method is appended at the END of CefWindow so the generated C struct (_cef_window_t) extends rather than disturbs existing field offsets, preserving ABI compatibility with downstream Rust bindings (cef-dll-sys). - Pointer location is passed as gfx::Point() — unused by the Wayland HTCAPTION path; X11 uses the recent button-press serial which the caller already established by virtue of the in-flight mousedown event. - views::Widget::RunMoveLoop() is the more obvious public API but it's X11-flavored (synchronous nested message loop) and behaves unpredictably on Wayland; the WmMoveResizeHandler path is the Wayland-native equivalent. For: AgentMux drag/right-click coexistence on Linux/Wayland. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes from PR #1 review: 1. P1: pass actual cursor screen point to DispatchHostWindowDragMovement instead of gfx::Point(). The Wayland HTCAPTION path ignores it (just issues xdg_toplevel.move with the recent input serial), but X11Window's path uses it as the _NET_WM_MOVERESIZE drag anchor — passing gfx::Point() gave a wrong anchor offset and caused the function to return true on X11 despite a broken drag origin. Use display::Screen::Get()->GetCursorScreenPoint() which returns the coordinate space the X server's root-window events live in. 2. P1: clarify the rationale for the unchecked static_cast< aura::WindowTreeHostPlatform*>. chromium builds with -fno-rtti so dynamic_cast isn't available; the BUILDFLAG(IS_OZONE) gate is the static guarantee that the host is a PlatformWindow-backed tree host. Comment now explains the invariant + flags the future-Ozone- backend rework concern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per CEF's API versioning convention (e.g. cef_browser_view_delegate.h's AllowMoveForPictureInPicture has `added=13601`), new public methods should declare the API version they were introduced in via the `/*--cef(added=NNNNN)--*/` annotation. This lets the API version manager track availability and lets clients gate use behind `CEF_API_ADDED(14800)` macros where needed. CEF 148 forward-port note: this commit was originally added=14600 on the agentmux/7680 branch. The annotation moves to the latest API version of the host CEF branch on each forward-port; `cef_api_versions.json` on 7778 lists 14800 as the latest. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reagent P2 fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to b921ffe ("Support transparent window in Views framework"). The original patch correctly colored the Views/Aura side: - CefBrowserViewImpl::SetBackgroundColor(SK_ColorTRANSPARENT) → ParentClass::SetBackgroundColor + web_view->SetResizeBackgroundColor - CefWindow::SetBackgroundColor(SK_ColorTRANSPARENT) → widget_->GetCompositor()->SetBackgroundColor Both stop at the Aura compositor's clear color, never reaching the renderer's cc::LayerTreeHost. As a result `has_transparent_background_` stays at its default `false`, the compositor clamps every fragment's alpha to 1.0, and the wl_buffer receives opaque pixels even when: - the Views Widget is kTranslucent, - the wl_buffer is allocated as WL_SHM_FORMAT_ARGB8888, and - wl_surface_set_opaque_region is never called. Empirically verified on Linux/Wayland: WAYLAND_DEBUG=1 traces show zero set_opaque_region calls and ARGB8888 buffers, yet the rendered pixels are opaque white. The renderer is the missing link. Fix: cache the resolved CefBrowserSettings background color at SetDefaults() time, then call WebContents::SetPageBaseBackgroundColor in WebContentsCreated() when alpha == 0. That broadcasts to every RenderViewHost via the ExecutePageBroadcastMethod path, surviving navigations and renderer process swaps, and flips the renderer's has_transparent_background_ to true so cc emits true ARGB pixels. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to 68e0dc6. SetPageBaseBackgroundColor sets the blink::Page base color (used as the "behind body" base when body bg has alpha < 1) but does NOT trigger SetBackgroundOpaque(false) → renderer flips cc::LayerTreeHost::has_transparent_background_. With only the page-base call, body alpha=0 reveals the renderer's default OPAQUE white clear color, not the desktop. The Chromium-side function that flips has_transparent_background_ is RenderWidgetHostViewBase::SetBackgroundColor — it inspects the alpha component, and on the opaque-to-transparent transition emits the IPC that the renderer translates into cc::LayerTreeHost.has_transparent_ background_=true. Two calls together cover both layers: 1. SetPageBaseBackgroundColor → blink page-base color = transparent 2. RenderWidgetHostView::SetBackgroundColor → cc::LayerTreeHost.has_ transparent_background_ = true RWHView may be null at WebContentsCreated time if the renderer hasn't spawned yet — guarded accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to 2f69aab. Direct call to `web_contents->GetRenderWidgetHostView()->SetBackgroundColor()` in WebContentsCreated almost never runs because the RWHView doesn't exist yet — the renderer process spawns asynchronously, after WebContentsCreated returns. Pixel sampling on AgentMux 0.33.789 confirmed: body's CSS bg rgba(34,34,34,0) was emitting opaque (34,34,34,255) pixels, meaning cc::LayerTreeHost::has_transparent_background_ stayed false. Install a one-shot TransparencyApplyOnRenderReady WebContentsObserver in WebContentsCreated. When RenderFrameCreated fires for the primary main frame (renderer process now exists), apply RenderWidgetHostView::SetBackgroundColor(SK_ColorTRANSPARENT) — which triggers SetBackgroundOpaque(false) IPC, flips has_transparent_background_, and the renderer emits true ARGB pixels. Self-deletes after success or on WebContents destruction. Keep the immediate-try path in WebContentsCreated for cases where RWHView happens to already exist (single-process mode, etc.) — and the SetPageBaseBackgroundColor call still serves the blink page-base color that body bg's alpha < 1 composes against. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up tweak to the TransparencyApplyOnRenderReady observer added in 6e0e93e. Originally self-deleted on the first primary-main RenderFrameCreated, which works for the initial page load but loses transparency on any cross-process navigation that swaps the renderer (the new RWHView defaults back to opaque-white clear). Keep the observer alive for the lifetime of the WebContents and apply on every RenderFrameCreated for the primary main frame, plus RenderViewReady as a fallback. Self-deletes in WebContentsDestroyed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three transparency fixes for top-level (non-modal) translucent CEF windows on Linux/Wayland under software compositing: 1. CefWindowImpl::CreateWidget — after widget_ is assigned, push SK_ColorTRANSPARENT through SetBackgroundColor() so the browser-side ui::Compositor's cc::LayerTreeHost background_color is actually transparent. The existing window_view.cc:738 call lives inside `if (host_widget)` (modals/dialogs only), and even there it fires inside OnNativeWidgetCreated where widget_ is still null and widget_->GetCompositor() returns null — silently dropped. For top-level windows the call was never made, so the browser-side compositor stayed at its default opaque white clear color and the wl_surface framebuffer was filled with opaque white regardless of CefSettings.background_color. 2. window_view.cc — mirror the modal `is_translucent` branch in an `else if` so top-level windows also call SetBackgroundColor at the same lifecycle point (kept for parity even though widget_ is null there; the CreateWidget call above is what actually takes effect). 3. browser_view_impl.cc TransparencyApplyOnRenderReady — cleanup of the renderer-side cascade: keep the observer alive across renderer process swaps and add a direct host_->owner_delegate()->SetBackgroundOpaque(false) call in addition to view->SetBackgroundColor(SK_ColorTRANSPARENT), because RWHView::SetBackgroundColor early-returns when the cached color matches the new one (after SetDefaults already set default_background_color_ to transparent) and skips the IPC. Verified via diagnostic LOG(WARNING) walkthrough of the full call chain: CefWindowImpl::SetBackgroundColor now fires with widget=non-null, compositor=non-null, and ui::Compositor's LayerTreeHost shows bg=(0,0,0,0) has_transparent_background=1 will_fill_screen=0 in CalculateRenderPasses. Browser-side compositor is now correctly transparent; the renderer-side compositor pipeline still needs further work for multi-layer pane interiors but body-only regions now bleed the wallpaper through. Refs: agentmux PR chromiumembedded#797.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c87bca497c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Whether transparent painting is used with windowless rendering or Views. | ||
| const bool use_transparent_painting = | ||
| use_windowless_rendering_ && | ||
| (use_windowless_rendering_ || use_views_) && |
There was a problem hiding this comment.
Initialize use_views_ before computing transparency
When cefclient is launched with --enable-views --transparent-painting-enabled, this expression reads use_views_ before the constructor assigns it below. That makes the transparent-painting decision depend on an indeterminate bool, so the new Views transparency sample can still copy background_color_ into browser_background_color_ and run opaque even though the switch was supplied. Move the use_views_ initialization ahead of this calculation.
Useful? React with 👍 / 👎.
| auto color = CefContext::Get()->GetBackgroundColor(nullptr, STATE_ENABLED); | ||
| bool is_translucent = color == SK_ColorTRANSPARENT; |
There was a problem hiding this comment.
Honor browser settings when making the widget translucent
For a Views browser that sets only CefBrowserSettings.background_color to transparent, this uses nullptr and therefore looks only at the global CefSettings.background_color. The code later honors the per-browser setting in CefBrowserViewImpl::SetDefaults, but the top-level widget remains kOpaque and skips the compositor transparent background path, so the newly documented per-browser transparent Views case cannot actually show alpha unless the global setting is also transparent.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
ReAgent Diagnostics
| Field | Value |
|---|---|
| ReAgent Version | 5.12.63 |
| Trigger | PR opened |
| Project Context | No CLAUDE.md |
| Model | claude-opus-4-8 |
| Effort | xhigh |
| Ref Repos | Enabled (dev-tools, shared-infrastructure) |
| Merge Safety | No recent-code deletions |
| Review Time | 6m 17s |
| Timestamp | 2026-06-04T10:15:11Z |
| Repository | agentmuxai/cef |
| PR | #3 |
Issues:
- [P2] libcef/browser/views/browser_view_impl.cc:37 - Class comment "Self-deletes after success or on WebContents destruction. Single-use." is stale/contradictory: the observer is not single-use and does not self-delete after success — it persists across renderer swaps (re-applies on every RenderFrameCreated/RenderViewReady) and only deletes on WebContentsDestroyed, exactly as the Attach() comment two lines down states.
- [P2] libcef/browser/context.h:74 - Comment "If |transparent_state| is STATE_DISABLED then SK_AlphaTRANSPARENT will always be returned" is now wrong after the is_windowless→transparent_state rename: STATE_DISABLED forces is_transparent=false, so GetColor only accepts opaque colors and SK_AlphaOPAQUE (not transparent) is returned.
- [P2] libcef/browser/context.cc:78 - Garbled comment "transparent unsupported browser colors must be fully opaque" from the is_windowless→is_transparent rename (originally "Windowed browser colors must be fully opaque"); reads as a non-sentence.
- [P2] tests/ceftests/views/window_unittest.cc:589 - Comment is grammatically broken ("...background_color by set in CefTestSuite::GetSettings()") and inaccurate: GetSettings() never assigns settings.background_color; the test only passes because the field's zero-default is alpha=0, which the comment does not state.
There was a problem hiding this comment.
ReAgent Diagnostics
| Field | Value |
|---|---|
| ReAgent Version | 5.12.63 |
| Trigger | PR opened |
| Project Context | No CLAUDE.md |
| Model | claude-opus-4-8 |
| Effort | xhigh |
| Ref Repos | Enabled (dev-tools, shared-infrastructure) |
| Merge Safety | No recent-code deletions |
| Review Time | 6m 43s |
| Timestamp | 2026-06-04T10:15:37Z |
| Repository | agentmuxai/cef |
| PR | #3 |
Issues:
- [P1] libcef/browser/views/window_view.cc:526 - is_translucent is derived solely from the global CefSettings.background_color alpha (GetBackgroundColor(nullptr, STATE_ENABLED)) with no IsFrameless() check, and SetDefaults likewise switches to STATE_ENABLED (browser_view_impl.cc:536); since background_color defaults to 0 (transparent), any default-configured Views browser/window now becomes kTranslucent with a transparent renderer background instead of the prior opaque white, contradicting the documented "frameless window using Views framework" gating in include/internal/cef_types.h
- [P2] cef_paths2.gypi:360 - transparent_views.html added to resource.h/cefclient.rc/resource_util_win_idmap.cc but not to the cefclient resources list here, so it is not bundled into cefclient_files on Linux/macOS
- [P2] tests/ceftests/views/window_unittest.cc:590 - comment is malformed ("by set in") and incorrect: CefTestSuite::GetSettings() (test_suite.cc:146) never sets settings.background_color; the assertion passes only via the zero default, not an explicit transparent setting
- [P2] libcef/browser/context.cc:78 - parameter rename left garbled comments ("transparent unsupported browser colors must be fully opaque." and line 83 "transparent supported browser colors may be fully transparent.")
…-sys fork patch (#1272) * feat(linux): patch cef-dll-sys to AgentU-asaf/cef-rs fork to restore --features patched-libcef on CEF 148 PR #1221 bumped the workspace from CEF 146 to CEF 148. cef-dll-sys 146.7.0+146.0.12 (where we landed our binding patch) has a sibling 148.3.0+148.0.9 on crates.io regenerated from upstream CEF 148 headers, which do NOT include the begin_window_drag slot on _cef_window_t. The result: any build with --features patched-libcef fails to compile against CEF 148 with `error[E0609]: no field begin_window_drag on type _cef_window_t` at agentmux-cef/src/ui_tasks.rs:215. That made --features patched-libcef impossible to enable on the workspace since the 148 bump, silently downgrading Linux native window-drag to the no-op fallback (see #1260 retro and the audit in docs/specs/SPEC_CEF_148_LINUX_FORWARD_PORT_2026_06_04.md §5). The fork at AgentU-asaf/cef-rs@agentmux/148-begin-window-drag appends the begin_window_drag field to the linux_x86_64 binding (cef-dll-sys 148.3.0+148.0.9, struct _cef_window_t grows 888 → 896 bytes, last slot — same mechanical edit that's been published in cef-dll-sys 146.7.0+146.0.12 since CEF 146). This PR adds a workspace [patch.crates-io] entry redirecting cef-dll-sys at that branch. Verification: cargo build --release -p agentmux-cef --features patched-libcef → compiles clean against CEF 148 (3m 28s) → binary contains "[start_window_drag] BeginWindowDrag returned" string, confirming the patched code path is now compiled in instead of the no-op stub The override is transparent: same crate name, same crates.io-resolved version (148.3.0+148.0.9), so it affects only the binding source. macOS and Windows builds (which don't enable --features patched-libcef) link unaffected — they still link the public cef-dll-sys behavior under the hood, just sourced from the fork's identical-on-non-Linux files. Lifecycle: this [patch.crates-io] is temporary — when the BeginWindowDrag patch is upstreamed to chromiumembedded/cef + tauri-apps/cef-rs (or deferred indefinitely by either upstream), remove this block and the workspace uses the public binding directly. Spec: docs/specs/SPEC_CEF_148_LINUX_FORWARD_PORT_2026_06_04.md §5 + §8.5 Companion PRs: - agentmuxai/cef#3 (CEF source forward-port, branch agentmux/7778-drag-rightclick-and-transparency) - AgentU-asaf/cef-rs#1 (cef-dll-sys binding fork, branch agentmux/148-begin-window-drag) Out of scope (follow-ups): - §8.6 bundle script updates to deploy matching CEF 148 runtime files (paks/locales/icudtl) — requires AppImage repack + smoke test, not a Cargo.toml edit - §8.7 re-shipping the X11 ozone default end-to-end — blocked on §8.6 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(cef): address reagent P2s on #1272 — SHA pin, minimal Cargo.lock, patch-shaped changeset Per reagent review on #1272: 1. Cargo.toml [patch.crates-io] now pins by `rev = "515b3ac…"` not `branch = "agentmux/148-begin-window-drag"`. The AgentU-asaf fork is force-pushable; the SHA-pin makes the build reproducible and matches the inline comment that already promised SHA pinning. 2. Cargo.lock is minimal: only the cef-dll-sys + download-cef source lines change (registry+checksum → git+rev). Resolver-driven windows-sys/anstream/clap/getrandom/etc. downgrades against main are reverted — those were incidental to a cargo update sweep, not required by the patch, and contradicted the PR's "macOS & Windows unaffected" claim. Verified `cargo build --release -p agentmux-cef --features patched-libcef` still completes against the surgical lockfile. 3. Changeset is `type: patch` (one-line body) per release.sh schema — matches the format every other changeset in .changesets/ uses, no non-schema scope/title fields that would land in VERSION_HISTORY as stray bullets. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: snowbark <snowbark@agentmux.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Forward-ports the 11 commits living on
agentmuxai/cef@agentmux/7680-drag-rightclick-and-transparency(CEF 146) onto upstream7778(CEF 148.0.7778.180). All cherry-picks applied cleanly — zero conflicts. The patches form a self-contained stack: native window drag + transparent Views support + HTCAPTION right-click pass-through.This is PR #1 of the sequence described in
agentmuxai/agentmux@docs/specs/SPEC_CEF_148_LINUX_FORWARD_PORT_2026_06_04.md§8.Why now
agentmuxai/agentmuxchromiumembedded#1221 (CEF 146 → 148 bump) silently broke title-bar drag on Linux because the binding lost the_cef_window_t::begin_window_dragfield that AgentMux's host calls via FFI. The fix is to bring those patches forward to 148, regenerate the cef-dll-sys binding, rebuild the Linux libcef.so, and unblock the workspace from staying oncef = "148"end-to-end. Spec details the full sequence; this PR is the source-side foundation.Commits (in cherry-pick order, all clean)
ebba46f1afd2bb9afe7d1d5dCefWindow::BeginWindowDrag()for native HTCLIENT-region drag084c26ebBeginWindowDrag(P1 X11 cursor-screen-point fix)d1b78a32CefWindow::BeginWindowDragwithadded=14800(re-annotated from 14600 to match CEF 148's latest API percef_api_versions.json)4c1676b4transparent_views.htmldfe6f93374039df6RWHView::SetBackgroundColorfor renderer alpha cascade6a227369WebContentsObserverfor late-binding transparency cascadea83da219WebContentsObserveralive across renderer process swapsc87bca49What changed during the forward-port
Only one — the API version annotation on
BeginWindowDragwas moved fromadded=14600toadded=14800to match CEF 148's current latest API version percef_api_versions.json. This follows CEF's convention of annotating with the first API version the symbol is available in for the host branch.Everything else applied with no source-level adjustments. The Chromium 146 → 148 delta did not invalidate any of these patches — the
WmMoveResizeHandler::DispatchHostWindowDragMovementsignature, the Aura Views lifecycle, and the WebContentsObserver / RWHView APIs are all stable across that range. Confirmed bygit cherry-pickreporting auto-merge oninclude/internal/cef_types.handpatch/patch.cfg(both header-level merges with the upstream 7778 additions, no semantic overlap), and clean application on every other commit.What this PR does NOT include
BeginWindowDragslot at the end of_cef_window_tchanges the struct layout, which means the per-platform hashes incef_api_versions.json[hashes][14800]should be regenerated via CEF's translator tool. I did not run the tool locally; the existing hashes for 14800 still match upstream 7778. This is acceptable for forks of the binding (which is the path AgentMux uses — see PR views: Annotate CefWindow::BeginWindowDrag with added=14600 #2 of the spec for the cef-dll-sys 148 fork that appendsbegin_window_dragmanually), but should be addressed if these patches are ever upstreamed.Test plan
git cherry-pickreports clean apply for all 11 commits (no<<<<<<<markers, nogit statusmodifications)git diff agentmuxai/7778 --shortstat→ 20 files changed, +410 / -33include/views/cef_window.hBeginWindowDragannotation isadded=14800(re-annotated for 148)window_impl.cc::BeginWindowDrag()retains thedisplay::Screen::Get()->GetCursorScreenPoint()X11 fix from010f616f/084c26eb5c9a1b08(process_requirement) for macOS-26 renderer crash fixcefclientsmoke test with--enable-views+ transparent windowwindow_unittest.cc(the cherry-picks bring in test additions too)Follow-up PRs
Per
SPEC_CEF_148_LINUX_FORWARD_PORT_2026_06_04.md§8:a5af/cef-dll-sys: new branchagentmux/148that appendsbegin_window_dragto the_cef_window_tbinding.agentmuxai/agentmux: workspace[patch.crates-io]pointing at the cef-dll-sys fork. Restores--features patched-libcefworking withcef = "148".🤖 Generated with Claude Code