Bug 1521012 - [Linux/GNOME] Use X shape mask to draw transparent corners when we draw to titlebar, r=lsalzman

To support rounded corners of Gtk+ titlebar themes (Adwaita, Radiance..) in GNOME we need to use X shape mask
as fully transparent toplevel window causes various issues (like Bug 1516224).

We draw mShell as transparent and mContainer as non-transparent with shape mask applied. The shape mask
is generated only when titlebar rendering is enabled and it's generated from GtkHeaderBar Widget
to match the exact look.

We use existing mTransparencyBitmap for the shape mask where mTransparencyBitmapForTitlebar controls
whether it's a general shape mask or our specialised shape for titlebar only.

This is already enabled for GNOME environment by default. So there's a new preference
widget.default-hidden-titlebar added to easily disable it if any issue appears
during testing.

Differential Revision: https://phabricator.services.mozilla.com/D17283

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Martin Stransky 2019-01-23 12:36:25 +00:00
Родитель 618075899f
Коммит 59a8114e76
5 изменённых файлов: 158 добавлений и 20 удалений

Просмотреть файл

@ -5995,3 +5995,7 @@ pref("prio.enabled", false);
// External.AddSearchProvider is deprecated and it will be removed in the next
// cycles.
pref("dom.sidebar.enabled", true);
#if defined(MOZ_WIDGET_GTK)
pref("widget.default-hidden-titlebar", true);
#endif

Просмотреть файл

@ -312,8 +312,12 @@ void moz_container_realize(GtkWidget *widget) {
attributes.width = allocation.width;
attributes.height = allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual(widget);
attributes.window_type = GDK_WINDOW_CHILD;
MozContainer *container = MOZ_CONTAINER(widget);
attributes.visual =
container->force_default_visual
? gdk_screen_get_system_visual(gtk_widget_get_screen(widget))
: gtk_widget_get_visual(widget);
window = gdk_window_new(parent, &attributes, attributes_mask);
gdk_window_set_user_data(window, widget);
@ -549,3 +553,7 @@ gboolean moz_container_surface_needs_clear(MozContainer *container) {
return state;
}
#endif
void moz_container_force_default_visual(MozContainer *container) {
container->force_default_visual = true;
}

Просмотреть файл

@ -79,6 +79,7 @@ struct _MozContainer {
gboolean surface_needs_clear;
gboolean ready_to_draw;
#endif
gboolean force_default_visual;
};
struct _MozContainerClass {
@ -89,6 +90,7 @@ GType moz_container_get_type(void);
GtkWidget *moz_container_new(void);
void moz_container_put(MozContainer *container, GtkWidget *child_widget, gint x,
gint y);
void moz_container_force_default_visual(MozContainer *container);
#ifdef MOZ_WAYLAND
struct wl_surface *moz_container_get_wl_surface(MozContainer *container);

Просмотреть файл

@ -425,6 +425,7 @@ nsWindow::nsWindow() {
mIsTransparent = false;
mTransparencyBitmap = nullptr;
mTransparencyBitmapForTitlebar = false;
mTransparencyBitmapWidth = 0;
mTransparencyBitmapHeight = 0;
@ -1925,12 +1926,17 @@ gboolean nsWindow::OnExposeEvent(cairo_t *cr) {
bool shaped = false;
if (eTransparencyTransparent == GetTransparencyMode()) {
if (mHasAlphaVisual) {
// Remove possible shape mask from when window manger was not
// previously compositing.
static_cast<nsWindow *>(GetTopLevelWidget())->ClearTransparencyBitmap();
if (mTransparencyBitmapForTitlebar) {
static_cast<nsWindow *>(GetTopLevelWidget())
->UpdateTitlebarTransparencyBitmap();
} else {
shaped = true;
if (mHasAlphaVisual) {
// Remove possible shape mask from when window manger was not
// previously compositing.
static_cast<nsWindow *>(GetTopLevelWidget())->ClearTransparencyBitmap();
} else {
shaped = true;
}
}
}
@ -3265,15 +3271,24 @@ nsresult nsWindow::Create(nsIWidget *aParent, nsNativeWidget aNativeParent,
bool shouldAccelerate = ComputeShouldAccelerate();
MOZ_ASSERT(shouldAccelerate | !useWebRender);
// Some Gtk+ themes use non-rectangular toplevel windows. To fully support
// such themes we need to make toplevel window transparent with ARGB
// visual. It may cause performanance issue so make it configurable and
// enable it by default for selected window managers. Also disable it for
// X11 SW rendering (Bug 1516224) by default.
if (mWindowType == eWindowType_toplevel &&
(shouldAccelerate || !mIsX11Display ||
Preferences::HasUserValue("mozilla.widget.use-argb-visuals"))) {
if (mWindowType == eWindowType_toplevel) {
// We enable titlebar rendering for toplevel windows only.
mCSDSupportLevel = GetSystemCSDSupportLevel();
// Some Gtk+ themes use non-rectangular toplevel windows. To fully
// support such themes we need to make toplevel window transparent
// with ARGB visual.
// It may cause performanance issue so make it configurable
// and enable it by default for selected window managers.
needsAlphaVisual = TopLevelWindowUseARGBVisual();
if (needsAlphaVisual && mIsX11Display && !shouldAccelerate) {
// We want to draw a transparent titlebar but we can't use
// ARGB visual due to Bug 1516224.
// We use ARGB visual for mShell only and shape mask
// for mContainer where is all our content drawn.
mTransparencyBitmapForTitlebar = true;
mCSDSupportLevel = CSD_SUPPORT_CLIENT;
}
}
bool isSetVisual = false;
@ -3404,9 +3419,6 @@ nsresult nsWindow::Create(nsIWidget *aParent, nsNativeWidget aNativeParent,
GtkWindowGroup *group = gtk_window_group_new();
gtk_window_group_add_window(group, GTK_WINDOW(mShell));
g_object_unref(group);
// We enable titlebar rendering for toplevel windows only.
mCSDSupportLevel = GetSystemCSDSupportLevel();
}
// Create a container to hold child windows and child GtkWidgets.
@ -3442,6 +3454,9 @@ nsresult nsWindow::Create(nsIWidget *aParent, nsNativeWidget aNativeParent,
gtk_widget_add_events(mShell, GDK_PROPERTY_CHANGE_MASK);
gtk_widget_set_app_paintable(mShell, TRUE);
}
if (mTransparencyBitmapForTitlebar) {
moz_container_force_default_visual(mContainer);
}
// If we draw to mContainer window then configure it now because
// gtk_container_add() realizes the child widget.
@ -4295,6 +4310,8 @@ nsresult nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect &aRect,
}
NS_ASSERTION(mIsTransparent, "Window is not transparent");
NS_ASSERTION(!mTransparencyBitmapForTitlebar,
"Transparency bitmap is already used for titlebar rendering");
if (mTransparencyBitmap == nullptr) {
int32_t size = GetBitmapStride(mBounds.width) * mBounds.height;
@ -4324,6 +4341,91 @@ nsresult nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect &aRect,
return NS_OK;
}
// We need to shape only a few pixels of the titlebar as we care about
// the corners only
#define TITLEBAR_SHAPE_MASK_HEIGHT 10
void nsWindow::UpdateTitlebarTransparencyBitmap() {
NS_ASSERTION(mTransparencyBitmapForTitlebar,
"Transparency bitmap is already used to draw window shape");
if (!mDrawInTitlebar || (mBounds.width == mTransparencyBitmapWidth &&
mBounds.height == mTransparencyBitmapHeight)) {
return;
}
bool maskCreate =
!mTransparencyBitmap || mBounds.width > mTransparencyBitmapWidth;
bool maskUpdate =
!mTransparencyBitmap || mBounds.width != mTransparencyBitmapWidth;
if (maskCreate) {
if (mTransparencyBitmap) {
delete[] mTransparencyBitmap;
}
int32_t size = GetBitmapStride(mBounds.width) * TITLEBAR_SHAPE_MASK_HEIGHT;
mTransparencyBitmap = new gchar[size];
mTransparencyBitmapWidth = mBounds.width;
} else {
mTransparencyBitmapWidth = mBounds.width;
}
mTransparencyBitmapHeight = mBounds.height;
if (maskUpdate) {
cairo_surface_t *surface = cairo_image_surface_create(
CAIRO_FORMAT_A8, mTransparencyBitmapWidth, TITLEBAR_SHAPE_MASK_HEIGHT);
if (!surface) return;
cairo_t *cr = cairo_create(surface);
GtkWidgetState state;
memset((void *)&state, 0, sizeof(state));
GdkRectangle rect = {0, 0, mTransparencyBitmapWidth,
TITLEBAR_SHAPE_MASK_HEIGHT};
moz_gtk_widget_paint(MOZ_GTK_HEADER_BAR, cr, &rect, &state, 0,
GTK_TEXT_DIR_NONE);
cairo_destroy(cr);
cairo_surface_mark_dirty(surface);
cairo_surface_flush(surface);
UpdateMaskBits(
mTransparencyBitmap, mTransparencyBitmapWidth,
TITLEBAR_SHAPE_MASK_HEIGHT,
nsIntRect(0, 0, mTransparencyBitmapWidth, TITLEBAR_SHAPE_MASK_HEIGHT),
cairo_image_surface_get_data(surface),
cairo_format_stride_for_width(CAIRO_FORMAT_A8,
mTransparencyBitmapWidth));
cairo_surface_destroy(surface);
}
if (!mNeedsShow) {
Display *xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
Pixmap maskPixmap = XCreateBitmapFromData(
xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
TITLEBAR_SHAPE_MASK_HEIGHT);
XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
ShapeSet);
if (mTransparencyBitmapHeight > TITLEBAR_SHAPE_MASK_HEIGHT) {
XRectangle rect = {0, 0, (unsigned short)mTransparencyBitmapWidth,
(unsigned short)(mTransparencyBitmapHeight -
TITLEBAR_SHAPE_MASK_HEIGHT)};
XShapeCombineRectangles(xDisplay, xDrawable, ShapeBounding, 0,
TITLEBAR_SHAPE_MASK_HEIGHT, &rect, 1, ShapeUnion,
0);
}
XFreePixmap(xDisplay, maskPixmap);
}
}
void nsWindow::GrabPointer(guint32 aTime) {
LOG(("GrabPointer time=0x%08x retry=%d\n", (unsigned int)aTime,
mRetryPointerGrab));
@ -6027,6 +6129,14 @@ void nsWindow::SetDrawsInTitlebar(bool aState) {
}
mDrawInTitlebar = aState;
if (mTransparencyBitmapForTitlebar) {
if (mDrawInTitlebar) {
UpdateTitlebarTransparencyBitmap();
} else {
ClearTransparencyBitmap();
}
}
}
gint nsWindow::GdkScaleFactor() {
@ -6367,6 +6477,11 @@ bool nsWindow::HideTitlebarByDefault() {
return hideTitlebar;
}
if (!Preferences::GetBool("widget.default-hidden-titlebar", false)) {
hideTitlebar = false;
return hideTitlebar;
}
const char *currentDesktop = getenv("XDG_CURRENT_DESKTOP");
hideTitlebar =
(currentDesktop && GetSystemCSDSupportLevel() != CSD_SUPPORT_NONE);
@ -6386,6 +6501,11 @@ bool nsWindow::TopLevelWindowUseARGBVisual() {
return useARGBVisual;
}
GdkScreen *screen = gdk_screen_get_default();
if (!gdk_screen_is_composited(screen)) {
useARGBVisual = false;
}
if (Preferences::HasUserValue("mozilla.widget.use-argb-visuals")) {
useARGBVisual =
Preferences::GetBool("mozilla.widget.use-argb-visuals", false);

Просмотреть файл

@ -308,6 +308,7 @@ class nsWindow final : public nsBaseWidget {
nsresult UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
uint8_t* aAlphas,
int32_t aStride);
void UpdateTitlebarTransparencyBitmap();
virtual void ReparentNativeWidget(nsIWidget* aNewParent) override;
@ -474,9 +475,6 @@ class nsWindow final : public nsBaseWidget {
uint32_t mHasMappedToplevel : 1, mIsFullyObscured : 1, mRetryPointerGrab : 1;
nsSizeMode mSizeState;
int32_t mTransparencyBitmapWidth;
int32_t mTransparencyBitmapHeight;
nsIntPoint mClientOffset;
#if GTK_CHECK_VERSION(3, 4, 0)
@ -565,6 +563,12 @@ class nsWindow final : public nsBaseWidget {
// full translucency at this time; each pixel is either fully opaque
// or fully transparent.
gchar* mTransparencyBitmap;
int32_t mTransparencyBitmapWidth;
int32_t mTransparencyBitmapHeight;
// The transparency bitmap is used instead of ARGB visual for toplevel
// window to draw titlebar.
bool mTransparencyBitmapForTitlebar;
// True when we're on compositing window manager and this
// window is using visual with alpha channel.
bool mHasAlphaVisual;