---
# System prepended metadata

title: Shadow on Linux Chrome
tags: [Chromium-Graphics, elkurin-daily-note]

---

# Shadow on Linux Chrome
Shadow, the visual effect around the window, can be drawn by the client or server.  
Linux supports client side shadow.  
The shadow stays outside of the window boundary, but it's currently not clipped.  
Let's see how it works.

On [UpdateFrameHints](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc;l=189;drc=3734137c6edeec1776c5731effbe5b80dd285d1f), it sets decoration insets.

[BrowserDesktopWindowTreeHostLinux](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h;l=32;drc=bb6dc55596dbc52541d3213cc5e36340bf16e41c) prepares the margins to draw shadow.
```cpp=
if (SupportsClientFrameShadow()) {
  // Set the frame decoration insets.
  const gfx::Insets insets_dip = view->MirroredFrameBorderInsets();
  const gfx::Insets insets_px = gfx::ScaleToCeiledInsets(insets_dip, scale);
  window->SetDecorationInsets(showing_frame ? &insets_px : nullptr);

  // Set the input region.
  gfx::Rect input_bounds(widget_size);
  input_bounds.Inset(insets_dip - view->GetInputInsets());
  input_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale);
  window->SetInputRegion(
      showing_frame ? std::optional<gfx::Rect>(input_bounds) : std::nullopt);
}
```

On linux, shadow is included inside frame, but it's not in UI window hierarchy and bounds, so it sets insets like this and create the space to draw.

## Decoration Insets
Frame insets is set from views::Widget.  
[DesktopWindowTreeHostPlatform::Init](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc;l=269;drc=c7da8fba0e20c71d61e5c78ecd6a3872c4c56e6c) converts params into PlatformWindowInitProperties, and  [`frame_insets`](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/widget.h;l=457;drc=c7da8fba0e20c71d61e5c78ecd6a3872c4c56e6c) will converted into `frame_insets_px`.


Let's see inside wayland.  

[WaylandWindow::SetDecorationInsets](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_window.cc;l=544;drc=c7da8fba0e20c71d61e5c78ecd6a3872c4c56e6c) sets `insets_px` to [`frame_insets_px_`](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_window.h;l=574;drc=c7da8fba0e20c71d61e5c78ecd6a3872c4c56e6c).  
Frame insets represent margines between edges of the surface and the window geometry, and the areas outside the geometry are used to draw client-side window decorations.  
Shadow is a client side decoration.

[WaylandWindow::GetWindowGeometryOffsetInDIP](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_window.cc;l=863;drc=c7da8fba0e20c71d61e5c78ecd6a3872c4c56e6c) returns the insets as
```cpp=
{static_cast<int>(frame_insets_px_->left() / scale),
 static_cast<int>(frame_insets_px_->top() / scale)}
```
but this is not utilized in WaylandToplevelWindow.

On showing window from [WaylandWindow::Show](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_window.cc;l=307;drc=c7da8fba0e20c71d61e5c78ecd6a3872c4c56e6c), [WaylandToplevelWindow::SetWindowGeometry](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_toplevel_window.cc;l=734;drc=c7da8fba0e20c71d61e5c78ecd6a3872c4c56e6c0) gets insets from GetDecorationInsetsInDIP and inset geometry_dip by its value.  


## Does this work on Exo + Wayland?
Insets changes the geometry position on the client side.  
How Exo handles this?

[WaylandToplevelWindow::HandleAuraToplevelConfigure](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_toplevel_window.cc;l=526;drc=c7da8fba0e20c71d61e5c78ecd6a3872c4c56e6c) receives the bounds change and window state change from Exo.  
On receiving configure event, Lacros computes bounds.  
```cpp=
gfx::Rect bounds_dip(
    pending_configure_state_.bounds_dip.value_or(gfx::Rect()));
if (width_dip > 1 && height_dip > 1) {
  bounds_dip.SetRect(x, y, width_dip, height_dip);
  const auto insets = GetDecorationInsetsInDIP();
  if (ShouldSetBounds(state_) && !insets.IsEmpty()) {
    bounds_dip.Inset(-insets);
    bounds_dip.set_origin({x, y});
  }
} else if (ShouldSetBounds(state_)) {
  bounds_dip = !restored_bounds_dip().IsEmpty() ? restored_bounds_dip()
                                                : GetBoundsInDIP();
}
```
`x`, `y`, `width_dip`, `height_dip` is returned and `insets` value will be outset.

However, just setting insets, Exo incorrectly handles geometry with insets area as a content area.  
TODO(elkurin): why?