Skip to content

Commit

Permalink
fix: Add support for Wayland window decorations (#29618)
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>

Co-authored-by: Jeremy Rose <nornagon@nornagon.net>
  • Loading branch information
refi64 and nornagon committed Jan 26, 2022
1 parent cabad35 commit 7caa88c
Show file tree
Hide file tree
Showing 12 changed files with 914 additions and 16 deletions.
5 changes: 5 additions & 0 deletions filenames.gni
Expand Up @@ -32,10 +32,13 @@ filenames = {
"shell/browser/notifications/linux/notification_presenter_linux.cc",
"shell/browser/notifications/linux/notification_presenter_linux.h",
"shell/browser/relauncher_linux.cc",
"shell/browser/ui/electron_desktop_window_tree_host_linux.cc",
"shell/browser/ui/file_dialog_gtk.cc",
"shell/browser/ui/message_box_gtk.cc",
"shell/browser/ui/tray_icon_gtk.cc",
"shell/browser/ui/tray_icon_gtk.h",
"shell/browser/ui/views/client_frame_view_linux.cc",
"shell/browser/ui/views/client_frame_view_linux.h",
"shell/common/application_info_linux.cc",
"shell/common/language_util_linux.cc",
"shell/common/node_bindings_linux.cc",
Expand Down Expand Up @@ -413,6 +416,8 @@ filenames = {
"shell/browser/native_browser_view.h",
"shell/browser/native_window.cc",
"shell/browser/native_window.h",
"shell/browser/native_window_features.cc",
"shell/browser/native_window_features.h",
"shell/browser/native_window_observer.h",
"shell/browser/net/asar/asar_file_validator.cc",
"shell/browser/net/asar/asar_file_validator.h",
Expand Down
17 changes: 17 additions & 0 deletions shell/browser/native_window.cc
Expand Up @@ -13,6 +13,7 @@
#include "base/values.h"
#include "content/public/browser/web_contents_user_data.h"
#include "shell/browser/browser.h"
#include "shell/browser/native_window_features.h"
#include "shell/browser/window_list.h"
#include "shell/common/color_util.h"
#include "shell/common/gin_helper/dictionary.h"
Expand All @@ -25,6 +26,11 @@
#include "ui/display/win/screen_win.h"
#endif

#if defined(USE_OZONE) || defined(USE_X11)
#include "ui/base/ui_base_features.h"
#include "ui/ozone/public/ozone_platform.h"
#endif

namespace gin {

template <>
Expand Down Expand Up @@ -108,6 +114,17 @@ NativeWindow::NativeWindow(const gin_helper::Dictionary& options,
if (parent)
options.Get("modal", &is_modal_);

#if defined(USE_OZONE)
// 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) {
has_client_frame_ = true;
}
#endif

WindowList::AddWindow(this);
}

Expand Down
6 changes: 6 additions & 0 deletions shell/browser/native_window.h
Expand Up @@ -332,6 +332,7 @@ class NativeWindow : public base::SupportsUserData,
bool has_frame() const { return has_frame_; }
void set_has_frame(bool has_frame) { has_frame_ = has_frame; }

bool has_client_frame() const { return has_client_frame_; }
bool transparent() const { return transparent_; }
bool enable_larger_than_screen() const { return enable_larger_than_screen_; }

Expand Down Expand Up @@ -381,6 +382,11 @@ class NativeWindow : public base::SupportsUserData,
// Whether window has standard frame.
bool has_frame_ = true;

// Whether window has standard frame, but it's drawn by Electron (the client
// application) instead of the OS. Currently only has meaning on Linux for
// Wayland hosts.
bool has_client_frame_ = false;

// Whether window is transparent.
bool transparent_ = false;

Expand Down
10 changes: 10 additions & 0 deletions shell/browser/native_window_features.cc
@@ -0,0 +1,10 @@
// Copyright (c) 2022 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include "shell/browser/native_window_features.h"

namespace features {
const base::Feature kWaylandWindowDecorations{
"WaylandWindowDecorations", base::FEATURE_DISABLED_BY_DEFAULT};
}
14 changes: 14 additions & 0 deletions shell/browser/native_window_features.h
@@ -0,0 +1,14 @@
// Copyright (c) 2022 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#ifndef ELECTRON_SHELL_BROWSER_NATIVE_WINDOW_FEATURES_H_
#define ELECTRON_SHELL_BROWSER_NATIVE_WINDOW_FEATURES_H_

#include "base/feature_list.h"

namespace features {
extern const base::Feature kWaylandWindowDecorations;
}

#endif // ELECTRON_SHELL_BROWSER_NATIVE_WINDOW_FEATURES_H_
28 changes: 20 additions & 8 deletions shell/browser/native_window_views.cc
Expand Up @@ -19,6 +19,7 @@
#include "content/public/browser/desktop_media_id.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/native_browser_view_views.h"
#include "shell/browser/native_window_features.h"
#include "shell/browser/ui/drag_util.h"
#include "shell/browser/ui/inspectable_web_contents.h"
#include "shell/browser/ui/inspectable_web_contents_view.h"
Expand Down Expand Up @@ -47,9 +48,11 @@
#include "base/strings/string_util.h"
#include "shell/browser/browser.h"
#include "shell/browser/linux/unity_service.h"
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
#include "shell/browser/ui/views/client_frame_view_linux.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "shell/browser/ui/views/native_frame_view.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/window/native_frame_view.h"

#if defined(USE_X11)
Expand Down Expand Up @@ -78,7 +81,6 @@
#include "ui/display/screen.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/color_utils.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#endif

namespace electron {
Expand Down Expand Up @@ -227,9 +229,10 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
params.bounds = bounds;
params.delegate = this;
params.type = views::Widget::InitParams::TYPE_WINDOW;
params.remove_standard_frame = !has_frame();
params.remove_standard_frame = !has_frame() || has_client_frame();

if (transparent())
// If a client frame, we need to draw our own shadows.
if (transparent() || has_client_frame())
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;

// The given window is most likely not rectangular since it uses
Expand All @@ -253,6 +256,13 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
// Set WM_CLASS.
params.wm_class_name = base::ToLowerASCII(name);
params.wm_class_class = name;

if (base::FeatureList::IsEnabled(features::kWaylandWindowDecorations)) {
auto* native_widget = new views::DesktopNativeWidgetAura(widget());
params.native_widget = native_widget;
params.desktop_window_tree_host =
new ElectronDesktopWindowTreeHostLinux(this, native_widget);
}
#endif

widget()->Init(std::move(params));
Expand Down Expand Up @@ -337,7 +347,7 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
::SetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE, ex_style);
#endif

if (has_frame()) {
if (has_frame() && !has_client_frame()) {
// TODO(zcbenz): This was used to force using native frame on Windows 2003,
// we should check whether setting it in InitParams can work.
widget()->set_frame_type(views::Widget::FrameType::kForceNative);
Expand Down Expand Up @@ -1553,7 +1563,7 @@ bool NativeWindowViews::ShouldDescendIntoChildForEventHandling(
return false;

// And the events on border for dragging resizable frameless window.
if (!has_frame() && resizable_) {
if ((!has_frame() || has_client_frame()) && resizable_) {
auto* frame =
static_cast<FramelessView*>(widget()->non_client_view()->frame_view());
return frame->ResizingBorderHitTest(location) == HTNOWHERE;
Expand All @@ -1573,10 +1583,12 @@ NativeWindowViews::CreateNonClientFrameView(views::Widget* widget) {
frame_view->Init(this, widget);
return frame_view;
#else
if (has_frame()) {
if (has_frame() && !has_client_frame()) {
return std::make_unique<NativeFrameView>(this, widget);
} else {
auto frame_view = std::make_unique<FramelessView>();
auto frame_view = has_frame() && has_client_frame()
? std::make_unique<ClientFrameViewLinux>()
: std::make_unique<FramelessView>();
frame_view->Init(this, widget);
return frame_view;
}
Expand Down
149 changes: 149 additions & 0 deletions shell/browser/ui/electron_desktop_window_tree_host_linux.cc
@@ -0,0 +1,149 @@
// Copyright (c) 2021 Ryan Gonzalez.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// Portions of this file are sourced from
// chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc,
// Copyright (c) 2019 The Chromium Authors,
// which is governed by a BSD-style license

#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"

#include <vector>

#include "base/i18n/rtl.h"
#include "shell/browser/ui/views/client_frame_view_linux.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/platform_window/platform_window.h"
#include "ui/views/linux_ui/linux_ui.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
#include "ui/views/window/non_client_view.h"

namespace electron {

ElectronDesktopWindowTreeHostLinux::ElectronDesktopWindowTreeHostLinux(
NativeWindowViews* native_window_view,
views::DesktopNativeWidgetAura* desktop_native_widget_aura)
: views::DesktopWindowTreeHostLinux(native_window_view->widget(),
desktop_native_widget_aura),
native_window_view_(native_window_view) {}

ElectronDesktopWindowTreeHostLinux::~ElectronDesktopWindowTreeHostLinux() =
default;

bool ElectronDesktopWindowTreeHostLinux::SupportsClientFrameShadow() const {
return platform_window()->CanSetDecorationInsets() &&
platform_window()->IsTranslucentWindowOpacitySupported();
}

void ElectronDesktopWindowTreeHostLinux::OnWidgetInitDone() {
views::DesktopWindowTreeHostLinux::OnWidgetInitDone();
UpdateFrameHints();
}

void ElectronDesktopWindowTreeHostLinux::OnBoundsChanged(
const BoundsChange& change) {
views::DesktopWindowTreeHostLinux::OnBoundsChanged(change);
UpdateFrameHints();
}

void ElectronDesktopWindowTreeHostLinux::OnWindowStateChanged(
ui::PlatformWindowState old_state,
ui::PlatformWindowState new_state) {
views::DesktopWindowTreeHostLinux::OnWindowStateChanged(old_state, new_state);
UpdateFrameHints();
}

void ElectronDesktopWindowTreeHostLinux::OnNativeThemeUpdated(
ui::NativeTheme* observed_theme) {
UpdateFrameHints();
}

void ElectronDesktopWindowTreeHostLinux::OnDeviceScaleFactorChanged() {
UpdateFrameHints();
}

void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
if (SupportsClientFrameShadow() && native_window_view_->has_frame() &&
native_window_view_->has_client_frame()) {
UpdateClientDecorationHints(static_cast<ClientFrameViewLinux*>(
native_window_view_->widget()->non_client_view()->frame_view()));
}

SizeConstraintsChanged();
}

void ElectronDesktopWindowTreeHostLinux::UpdateClientDecorationHints(
ClientFrameViewLinux* view) {
ui::PlatformWindow* window = platform_window();
bool showing_frame = !native_window_view_->IsFullscreen();
float scale = device_scale_factor();

bool should_set_opaque_region = window->IsTranslucentWindowOpacitySupported();

gfx::Insets insets;
gfx::Insets input_insets;
if (showing_frame) {
insets = view->GetBorderDecorationInsets();
if (base::i18n::IsRTL()) {
insets.Set(insets.top(), insets.right(), insets.bottom(), insets.left());
}

input_insets = view->GetInputInsets();
}

gfx::Insets scaled_insets = gfx::ScaleToCeiledInsets(insets, scale);
window->SetDecorationInsets(&scaled_insets);

gfx::Rect input_bounds(view->GetWidget()->GetWindowBoundsInScreen().size());
input_bounds.Inset(insets + input_insets);
gfx::Rect scaled_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale);
window->SetInputRegion(&scaled_bounds);

if (should_set_opaque_region) {
// The opaque region is a list of rectangles that contain only fully
// opaque pixels of the window. We need to convert the clipping
// rounded-rect into this format.
SkRRect rrect = view->GetRoundedWindowContentBounds();
gfx::RectF rectf(view->GetWindowContentBounds());
rectf.Scale(scale);
// It is acceptable to omit some pixels that are opaque, but the region
// must not include any translucent pixels. Therefore, we must
// conservatively scale to the enclosed rectangle.
gfx::Rect rect = gfx::ToEnclosedRect(rectf);

// Create the initial region from the clipping rectangle without rounded
// corners.
SkRegion region(gfx::RectToSkIRect(rect));

// Now subtract out the small rectangles that cover the corners.
struct {
SkRRect::Corner corner;
bool left;
bool upper;
} kCorners[] = {
{SkRRect::kUpperLeft_Corner, true, true},
{SkRRect::kUpperRight_Corner, false, true},
{SkRRect::kLowerLeft_Corner, true, false},
{SkRRect::kLowerRight_Corner, false, false},
};
for (const auto& corner : kCorners) {
auto radii = rrect.radii(corner.corner);
auto rx = std::ceil(scale * radii.x());
auto ry = std::ceil(scale * radii.y());
auto corner_rect = SkIRect::MakeXYWH(
corner.left ? rect.x() : rect.right() - rx,
corner.upper ? rect.y() : rect.bottom() - ry, rx, ry);
region.op(corner_rect, SkRegion::kDifference_Op);
}

// Convert the region to a list of rectangles.
std::vector<gfx::Rect> opaque_region;
for (SkRegion::Iterator i(region); !i.done(); i.next())
opaque_region.push_back(gfx::SkIRectToRect(i.rect()));
window->SetOpaqueRegion(&opaque_region);
}
}

} // namespace electron

0 comments on commit 7caa88c

Please sign in to comment.