Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add window shadow for frameless window on Wayland and WCO on Linux #41840

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions docs/api/structures/base-window-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@
* `followWindow` - The backdrop should automatically appear active when the window is active, and inactive when it is not. This is the default.
* `active` - The backdrop should always appear active.
* `inactive` - The backdrop should always appear inactive.
* `titleBarStyle` string (optional) _macOS_ _Windows_ - The style of window title bar.
* `titleBarStyle` string (optional) - The style of window title bar.
Default is `default`. Possible values are:
* `default` - Results in the standard title bar for macOS or Windows respectively.
* `hidden` - Results in a hidden title bar and a full size content window. On macOS, the window still has the standard window controls (“traffic lights”) in the top left. On Windows, when combined with `titleBarOverlay: true` it will activate the Window Controls Overlay (see `titleBarOverlay` for more information), otherwise no window controls will be shown.
* `hidden` - Results in a hidden title bar and a full size content window. On macOS, the window still has the standard window controls (“traffic lights”) in the top left. On Windows and Linux, when combined with `titleBarOverlay: true` it will activate the Window Controls Overlay (see `titleBarOverlay` for more information), otherwise no window controls will be shown.
* `hiddenInset` _macOS_ - Only on macOS, results in a hidden title bar
with an alternative look where the traffic light buttons are slightly
more inset from the window edge.
Expand All @@ -96,9 +96,9 @@
* `roundedCorners` boolean (optional) _macOS_ - Whether frameless window
should have rounded corners on macOS. Default is `true`. Setting this property
to `false` will prevent the window from being fullscreenable.
* `thickFrame` boolean (optional) - Use `WS_THICKFRAME` style for frameless windows on
Windows, which adds standard window frame. Setting it to `false` will remove
window shadow and window animations. Default is `true`.
* `thickFrame` boolean (optional) - Add standard window frame for frameless windows on
Windows and Wayland. Setting it to `false` will remove
window shadow and (on Windows) window animations. Default is `true`.
* `vibrancy` string (optional) _macOS_ - Add a type of vibrancy effect to
the window, only on macOS. Can be `appearance-based`, `titlebar`, `selection`,
`menu`, `popover`, `sidebar`, `header`, `sheet`, `window`, `hud`, `fullscreen-ui`,
Expand Down
1 change: 1 addition & 0 deletions patches/chromium/.patches
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,4 @@ fix_getcursorscreenpoint_wrongly_returns_0_0.patch
fix_add_support_for_skipping_first_2_no-op_refreshes_in_thumb_cap.patch
refactor_expose_file_system_access_blocklist.patch
revert_power_update_trace_counter_in_power_monitor.patch
fix_add_support_for_square_frameless_csd_shadow_with_gtk.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Misaki Kasumi <[email protected]>
Date: Sat, 13 Apr 2024 11:15:00 +0000
Subject: fix: add support for square frameless CSD shadow with GTK

This commit adds support for
1) making GTK window frame square
2) hiding GTK titlebar

diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc
index ed3213e73959079e5bc119987da7c31cc07f2f27..048a72a7a90d2635824a55bb5d150f60ec9f6523 100644
--- a/ui/gtk/gtk_ui.cc
+++ b/ui/gtk/gtk_ui.cc
@@ -563,11 +563,12 @@ std::unique_ptr<ui::NavButtonProvider> GtkUi::CreateNavButtonProvider() {

ui::WindowFrameProvider* GtkUi::GetWindowFrameProvider(bool solid_frame,
bool tiled,
- bool maximized) {
- auto& provider = frame_providers_[solid_frame][tiled][maximized];
+ bool maximized,
+ bool square) {
+ auto& provider = frame_providers_[solid_frame][tiled][maximized][square];
if (!provider) {
provider =
- std::make_unique<gtk::WindowFrameProviderGtk>(solid_frame, tiled, maximized);
+ std::make_unique<gtk::WindowFrameProviderGtk>(solid_frame, tiled, maximized, square);
}
return provider.get();
}
diff --git a/ui/gtk/gtk_ui.h b/ui/gtk/gtk_ui.h
index e3cbfa3a1ac8c169c429e29c1262d0dd2a05a4b6..13b10cfb2cbcbae9ea8cacf2454c53aa1dcd14e6 100644
--- a/ui/gtk/gtk_ui.h
+++ b/ui/gtk/gtk_ui.h
@@ -108,7 +108,8 @@ class GtkUi : public ui::LinuxUiAndTheme {
std::unique_ptr<ui::NavButtonProvider> CreateNavButtonProvider() override;
ui::WindowFrameProvider* GetWindowFrameProvider(bool solid_frame,
bool tiled,
- bool maximized) override;
+ bool maximized,
+ bool square) override;

private:
using TintMap = std::map<int, color_utils::HSL>;
@@ -192,7 +193,7 @@ class GtkUi : public ui::LinuxUiAndTheme {
// while Chrome is running. This 2D array is indexed first by whether the
// frame is translucent (0) or solid(1), then by whether the frame is normal
// (0) or tiled (1).
- std::unique_ptr<ui::WindowFrameProvider> frame_providers_[2][2][2];
+ std::unique_ptr<ui::WindowFrameProvider> frame_providers_[2][2][2][2];

// Objects to notify when the window frame button order changes.
base::ObserverList<ui::WindowButtonOrderObserver>::Unchecked
diff --git a/ui/gtk/window_frame_provider_gtk.cc b/ui/gtk/window_frame_provider_gtk.cc
index 52615c5c119c58f17850454e98d1240b141e8a09..53ce8a998adda304f3f4888eff568f708543ee28 100644
--- a/ui/gtk/window_frame_provider_gtk.cc
+++ b/ui/gtk/window_frame_provider_gtk.cc
@@ -45,7 +45,7 @@ GtkCssContext WindowContext(bool solid_frame, bool tiled, bool maximized, bool f
return AppendCssNodeToStyleContext({}, selector);
}

-GtkCssContext DecorationContext(bool solid_frame, bool tiled, bool maximized, bool focused) {
+GtkCssContext DecorationContext(bool solid_frame, bool tiled, bool maximized, bool focused, bool square) {
auto context = WindowContext(solid_frame, tiled, maximized, focused);
// GTK4 renders the decoration directly on the window.
if (!GtkCheckVersion(4)) {
@@ -62,6 +62,13 @@ GtkCssContext DecorationContext(bool solid_frame, bool tiled, bool maximized, bo
border-bottom-right-radius: 0;
})");

+ if (square) {
+ ApplyCssToContext(context, R"(* {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ })");
+ }
+
return context;
}

@@ -123,7 +130,7 @@ int ComputeTopCornerRadius() {
// need to experimentally determine the corner radius by rendering a sample.
// Additionally, in GTK4, the headerbar corners get clipped by the window
// rather than the headerbar having its own rounded corners.
- auto context = GtkCheckVersion(4) ? DecorationContext(false, false, false, false)
+ auto context = GtkCheckVersion(4) ? DecorationContext(false, false, false, false, false)
: HeaderContext(false, false, false, false);
ApplyCssToContext(context, R"(window, headerbar {
background-image: none;
@@ -192,8 +199,8 @@ WindowFrameProviderGtk::Asset& WindowFrameProviderGtk::Asset::operator=(

WindowFrameProviderGtk::Asset::~Asset() = default;

-WindowFrameProviderGtk::WindowFrameProviderGtk(bool solid_frame, bool tiled, bool maximized)
- : solid_frame_(solid_frame), tiled_(tiled), maximized_(maximized) {
+WindowFrameProviderGtk::WindowFrameProviderGtk(bool solid_frame, bool tiled, bool maximized, bool square)
+ : solid_frame_(solid_frame), tiled_(tiled), maximized_(maximized), square_(square) {
GtkSettings* settings = gtk_settings_get_default();
// Unretained() is safe since WindowFrameProviderGtk will own the signals.
auto callback = base::BindRepeating(&WindowFrameProviderGtk::OnThemeChanged,
@@ -337,6 +344,10 @@ void WindowFrameProviderGtk::PaintWindowFrame(gfx::Canvas* canvas,
effective_frame_thickness_px.right(), 1, client_bounds_px.right(),
corner_insets.top(), effective_frame_thickness_px.right(), edge_h);

+ if (top_area_height_dip == 0) {
+ return;
+ }
+
const int top_area_bottom_dip = rect_dip.y() + top_area_height_dip;
const int top_area_bottom_px = base::ClampCeil(top_area_bottom_dip * scale);
const int top_area_height_px = top_area_bottom_px - client_bounds_px.y();
@@ -372,7 +383,7 @@ WindowFrameProviderGtk::Asset& WindowFrameProviderGtk::GetOrCreateAsset(

gfx::Rect frame_bounds_dip(kMaxFrameSizeDip, kMaxFrameSizeDip,
2 * kMaxFrameSizeDip, 2 * kMaxFrameSizeDip);
- auto focused_context = DecorationContext(solid_frame_, tiled_, maximized_, true);
+ auto focused_context = DecorationContext(solid_frame_, tiled_, maximized_, true, square_);
frame_bounds_dip.Inset(-GtkStyleContextGetPadding(focused_context));
frame_bounds_dip.Inset(-GtkStyleContextGetBorder(focused_context));
gfx::Size bitmap_size(BitmapSizePx(asset), BitmapSizePx(asset));
@@ -380,7 +391,7 @@ WindowFrameProviderGtk::Asset& WindowFrameProviderGtk::GetOrCreateAsset(
focused_context, scale);
asset.unfocused_bitmap =
PaintBitmap(bitmap_size, gfx::RectF(frame_bounds_dip),
- DecorationContext(solid_frame_, tiled_, maximized_, false), scale);
+ DecorationContext(solid_frame_, tiled_, maximized_, false, square_), scale);

return asset;
}
diff --git a/ui/gtk/window_frame_provider_gtk.h b/ui/gtk/window_frame_provider_gtk.h
index 94050a0136b78730f607f42991742e0434948d0e..14095c4058e01fb02397b7788f7a0e9f139b6a2e 100644
--- a/ui/gtk/window_frame_provider_gtk.h
+++ b/ui/gtk/window_frame_provider_gtk.h
@@ -20,7 +20,7 @@ namespace gtk {

class WindowFrameProviderGtk : public ui::WindowFrameProvider {
public:
- WindowFrameProviderGtk(bool solid_frame, bool tiled, bool maximized);
+ WindowFrameProviderGtk(bool solid_frame, bool tiled, bool maximized, bool square);

WindowFrameProviderGtk(const WindowFrameProviderGtk&) = delete;
WindowFrameProviderGtk& operator=(const WindowFrameProviderGtk&) = delete;
@@ -67,6 +67,8 @@ class WindowFrameProviderGtk : public ui::WindowFrameProvider {
const bool tiled_;
// Whether to draw the window decorations as maximized.
const bool maximized_;
+ // Whether to draw the top-left and top-right at square corners.
+ const bool square_;

// Scale-independent metric calculated based on the bitmaps.
std::optional<gfx::Insets> frame_thickness_dip_;
diff --git a/ui/linux/fallback_linux_ui.cc b/ui/linux/fallback_linux_ui.cc
index 7d13381eb1d16193bad0be1318e8ed199c6fb845..fbe1aa77eec35b484b57b8a034f445712d97617d 100644
--- a/ui/linux/fallback_linux_ui.cc
+++ b/ui/linux/fallback_linux_ui.cc
@@ -142,7 +142,8 @@ FallbackLinuxUi::CreateNavButtonProvider() {
ui::WindowFrameProvider* FallbackLinuxUi::GetWindowFrameProvider(
bool solid_frame,
bool tiled,
- bool maximized) {
+ bool maximized,
+ bool square) {
return nullptr;
}

diff --git a/ui/linux/fallback_linux_ui.h b/ui/linux/fallback_linux_ui.h
index 282b48038f83d4a6dafe734f639d994c245c67ac..da6dc74a9dc729cdaccb1fc61eefb0163cbbba89 100644
--- a/ui/linux/fallback_linux_ui.h
+++ b/ui/linux/fallback_linux_ui.h
@@ -67,7 +67,8 @@ class FallbackLinuxUi : public LinuxUiAndTheme {
std::unique_ptr<ui::NavButtonProvider> CreateNavButtonProvider() override;
ui::WindowFrameProvider* GetWindowFrameProvider(bool solid_frame,
bool tiled,
- bool maximized) override;
+ bool maximized,
+ bool square) override;

private:
std::optional<gfx::FontRenderParams> default_font_render_params_;
diff --git a/ui/linux/linux_ui.h b/ui/linux/linux_ui.h
index 4477a1012fef5ad6dd365d70d28eadd3db136ceb..bead34acf6023d3d8a427a5d38c5186a156a47c2 100644
--- a/ui/linux/linux_ui.h
+++ b/ui/linux/linux_ui.h
@@ -305,7 +305,8 @@ class COMPONENT_EXPORT(LINUX_UI) LinuxUiTheme {
// the process ends.
virtual WindowFrameProvider* GetWindowFrameProvider(bool solid_frame,
bool tiled,
- bool maximized) = 0;
+ bool maximized,
+ bool square) = 0;

protected:
LinuxUiTheme();
13 changes: 5 additions & 8 deletions shell/browser/api/electron_api_browser_window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "content/public/common/color_parser.h"
#include "shell/browser/api/electron_api_web_contents_view.h"
#include "shell/browser/browser.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "shell/browser/web_contents_preferences.h"
#include "shell/browser/window_list.h"
#include "shell/common/color_util.h"
Expand All @@ -27,10 +28,6 @@
#include "shell/browser/native_window_views.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "shell/browser/ui/views/win_frame_view.h"
#endif

namespace electron::api {

BrowserWindow::BrowserWindow(gin::Arguments* args,
Expand Down Expand Up @@ -275,11 +272,11 @@ v8::Local<v8::Value> BrowserWindow::GetWebContents(v8::Isolate* isolate) {
return v8::Local<v8::Value>::New(isolate, web_contents_);
}

#if BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_OZONE)
void BrowserWindow::SetTitleBarOverlay(const gin_helper::Dictionary& options,
gin_helper::Arguments* args) {
// Ensure WCO is already enabled on this window
if (!window_->titlebar_overlay_enabled()) {
if (!window_->IsWindowControlsOverlayEnabled()) {
args->ThrowError("Titlebar overlay is not enabled");
return;
}
Expand Down Expand Up @@ -327,7 +324,7 @@ void BrowserWindow::SetTitleBarOverlay(const gin_helper::Dictionary& options,
// If anything was updated, invalidate the layout and schedule a paint of the
// window's frame view
if (updated) {
auto* frame_view = static_cast<WinFrameView*>(
auto* frame_view = static_cast<FramelessView*>(
window->widget()->non_client_view()->frame_view());
frame_view->InvalidateCaptionButtons();
}
Expand Down Expand Up @@ -373,7 +370,7 @@ void BrowserWindow::BuildPrototype(v8::Isolate* isolate,
.SetMethod("focusOnWebView", &BrowserWindow::FocusOnWebView)
.SetMethod("blurWebView", &BrowserWindow::BlurWebView)
.SetMethod("isWebViewFocused", &BrowserWindow::IsWebViewFocused)
#if BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_OZONE)
.SetMethod("setTitleBarOverlay", &BrowserWindow::SetTitleBarOverlay)
#endif
.SetProperty("webContents", &BrowserWindow::GetWebContents);
Expand Down
2 changes: 1 addition & 1 deletion shell/browser/api/electron_api_browser_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class BrowserWindow : public BaseWindow,
void BlurWebView();
bool IsWebViewFocused();
v8::Local<v8::Value> GetWebContents(v8::Isolate* isolate);
#if BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_OZONE)
void SetTitleBarOverlay(const gin_helper::Dictionary& options,
gin_helper::Arguments* args);
#endif
Expand Down
18 changes: 11 additions & 7 deletions shell/browser/native_window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -122,23 +122,27 @@ NativeWindow::NativeWindow(const gin_helper::Dictionary& options,
int height;
if (titlebar_overlay_dict.Get(options::kOverlayHeight, &height))
titlebar_overlay_height_ = height;

#if !(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC))
DCHECK(false);
#endif
}
}

if (parent)
options.Get("modal", &is_modal_);

#if defined(USE_OZONE)
bool thick_frame = true;
// Only honor thickFrame for frameless window.
if (!has_frame()) {
options.Get("thickFrame", &thick_frame);
}

// Ozone X11 likes to prefer custom frames, but we don't need them unless
// on Wayland.
if (base::FeatureList::IsEnabled(features::kWaylandWindowDecorations) &&
!ui::OzonePlatform::GetInstance()
->GetPlatformRuntimeProperties()
.supports_server_side_window_decorations) {
(!ui::OzonePlatform::GetInstance()
->GetPlatformRuntimeProperties()
.supports_server_side_window_decorations ||
title_bar_style_ != TitleBarStyle::kNormal) &&
(has_frame() || !transparent()) && thick_frame) {
has_client_frame_ = true;
}
#endif
Expand Down
12 changes: 11 additions & 1 deletion shell/browser/native_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -373,11 +373,21 @@ class NativeWindow : public base::SupportsUserData,
kCustomButtonsOnHover,
};
TitleBarStyle title_bar_style() const { return title_bar_style_; }

bool IsWindowControlsOverlayEnabled() const {
bool valid_titlebar_style = title_bar_style() == TitleBarStyle::kHidden
#if BUILDFLAG(IS_MAC)
||
title_bar_style() == TitleBarStyle::kHiddenInset
#endif
;
return valid_titlebar_style && titlebar_overlay_;
}

int titlebar_overlay_height() const { return titlebar_overlay_height_; }
void set_titlebar_overlay_height(int height) {
titlebar_overlay_height_ = height;
}
bool titlebar_overlay_enabled() const { return titlebar_overlay_; }

bool has_frame() const { return has_frame_; }
void set_has_frame(bool has_frame) { has_frame_ = has_frame; }
Expand Down
10 changes: 6 additions & 4 deletions shell/browser/native_window_views.cc
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,16 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
}
}

if (title_bar_style_ != TitleBarStyle::kNormal)
set_has_frame(false);

// If the taskbar is re-created after we start up, we have to rebuild all of
// our buttons.
taskbar_created_message_ = RegisterWindowMessage(TEXT("TaskbarCreated"));
#endif

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_OZONE)
if (title_bar_style_ != TitleBarStyle::kNormal)
set_has_frame(false);
#endif

if (enable_larger_than_screen())
// We need to set a default maximum window size here otherwise Windows
// will not allow us to resize the window larger than scree.
Expand Down Expand Up @@ -1687,7 +1689,7 @@ NativeWindowViews::CreateNonClientFrameView(views::Widget* widget) {
if (has_frame() && !has_client_frame()) {
return std::make_unique<NativeFrameView>(this, widget);
} else {
auto frame_view = has_frame() && has_client_frame()
auto frame_view = has_client_frame()
? std::make_unique<ClientFrameViewLinux>()
: std::make_unique<FramelessView>();
frame_view->Init(this, widget);
Expand Down