diff -up firefox-57.0/browser/app/profile/firefox.js.1399611 firefox-57.0/browser/app/profile/firefox.js --- firefox-57.0/browser/app/profile/firefox.js.1399611 2017-11-22 12:17:33.717682523 +0100 +++ firefox-57.0/browser/app/profile/firefox.js 2017-11-22 12:17:33.728682488 +0100 @@ -457,11 +457,7 @@ pref("browser.tabs.loadBookmarksInBackgr pref("browser.tabs.loadBookmarksInTabs", false); pref("browser.tabs.tabClipWidth", 140); pref("browser.tabs.tabMinWidth", 76); -#ifdef UNIX_BUT_NOT_MAC -pref("browser.tabs.drawInTitlebar", false); -#else pref("browser.tabs.drawInTitlebar", true); -#endif // Offer additional drag space to the user. The drag space // will only be shown if browser.tabs.drawInTitlebar is true. diff -up firefox-57.0/browser/base/content/browser-tabsintitlebar.js.1399611 firefox-57.0/browser/base/content/browser-tabsintitlebar.js --- firefox-57.0/browser/base/content/browser-tabsintitlebar.js.1399611 2017-11-02 17:16:30.000000000 +0100 +++ firefox-57.0/browser/base/content/browser-tabsintitlebar.js 2017-11-22 12:17:33.728682488 +0100 @@ -14,6 +14,11 @@ var TabsInTitlebar = { this._readPref(); Services.prefs.addObserver(this._prefName, this); + // Always disable on unsupported GTK versions. + if (AppConstants.MOZ_WIDGET_TOOLKIT == "gtk3") { + this.allowedBy("gtk", window.matchMedia("(-moz-gtk-csd-available)")); + } + // We need to update the appearance of the titlebar when the menu changes // from the active to the inactive state. We can't, however, rely on // DOMMenuBarInactive, because the menu fires this event and then removes diff -up firefox-57.0/browser/base/moz.build.1399611 firefox-57.0/browser/base/moz.build --- firefox-57.0/browser/base/moz.build.1399611 2017-11-02 17:16:30.000000000 +0100 +++ firefox-57.0/browser/base/moz.build 2017-11-22 12:17:33.728682488 +0100 @@ -57,7 +57,7 @@ DEFINES['APP_LICENSE_BLOCK'] = '%s/conte if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'): DEFINES['CONTEXT_COPY_IMAGE_CONTENTS'] = 1 -if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'): +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa', 'gtk3'): DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'): diff -up firefox-57.0/browser/themes/linux/browser.css.1399611 firefox-57.0/browser/themes/linux/browser.css --- firefox-57.0/browser/themes/linux/browser.css.1399611 2017-11-02 17:16:32.000000000 +0100 +++ firefox-57.0/browser/themes/linux/browser.css 2017-11-22 12:17:33.728682488 +0100 @@ -556,7 +556,9 @@ html|span.ac-emphasize-text-url { #nav-bar, #toolbar-menubar:not([autohide="true"]):not(:-moz-lwtheme):-moz-system-metric(menubar-drag), -#TabsToolbar:not(:-moz-lwtheme):-moz-system-metric(menubar-drag) { +#TabsToolbar:not(:-moz-lwtheme):-moz-system-metric(menubar-drag), +#main-window[tabsintitlebar] #toolbar-menubar:not([autohide="true"]), +#main-window[tabsintitlebar] #TabsToolbar { -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-drag"); } @@ -713,3 +715,85 @@ html|span.ac-emphasize-text-url { .restore-tabs-button:hover:active:not([disabled="true"]) { padding: 3px; } + +@media not all and (-moz-gtk-csd-available) { + #main-window > #titlebar { + /* We need to hide the titlebar explicitly on versions of GTK without CSD. */ + display: none; + } +} + +/* Titlebar/CSD */ +@media (-moz-gtk-csd-available) { + #main-window[tabsintitlebar][sizemode="normal"] > #titlebar { + min-height: calc(var(--tab-min-height) + 12px); + } + + #main-window[tabsintitlebar] #titlebar:-moz-lwtheme { + visibility: hidden; + } + #main-window[tabsintitlebar] #titlebar-content:-moz-lwtheme { + visibility: visible; + } + + #main-window[tabsintitlebar][sizemode="normal"] > #titlebar { + -moz-appearance: -moz-window-titlebar; + } + #main-window[tabsintitlebar][sizemode="maximized"] > #titlebar { + -moz-appearance: -moz-window-titlebar-maximized; + } + + /* The button box must appear on top of the navigator-toolbox in order for + * click and hover mouse events to work properly for the button in the restored + * window state. Otherwise, elements in the navigator-toolbox, like the menubar, + * can swallow those events. + */ + #titlebar-buttonbox { + z-index: 1; + } + + /* titlebar command buttons */ + /* Use full scale icons here as the Gtk+ does. */ + @media (-moz-gtk-csd-minimize-button) { + #titlebar-min { + list-style-image: url("moz-icon://stock/window-minimize-symbolic"); + -moz-appearance: -moz-window-button-minimize; + } + } + @media not all and (-moz-gtk-csd-minimize-button) { + #titlebar-min { + display: none; + } + } + + @media (-moz-gtk-csd-maximize-button) { + #titlebar-max { + list-style-image: url("moz-icon://stock/window-maximize-symbolic"); + -moz-appearance: -moz-window-button-maximize; + } + #main-window[sizemode="maximized"] #titlebar-max { + list-style-image: url("moz-icon://stock/window-restore-symbolic"); + -moz-appearance: -moz-window-button-restore; + } + } + @media not all and (-moz-gtk-csd-maximize-button) { + #titlebar-max { + display: none; + } + #main-window[sizemode="maximized"] #titlebar-max { + display: none; + } + } + + @media (-moz-gtk-csd-close-button) { + #titlebar-close { + list-style-image: url("moz-icon://stock/window-close-symbolic"); + -moz-appearance: -moz-window-button-close; + } + } + @media not all and (-moz-gtk-csd-close-button) { + #titlebar-close { + display: none; + } + } +} diff -up firefox-57.0/dom/base/nsGkAtomList.h.1399611 firefox-57.0/dom/base/nsGkAtomList.h --- firefox-57.0/dom/base/nsGkAtomList.h.1399611 2017-11-02 17:16:34.000000000 +0100 +++ firefox-57.0/dom/base/nsGkAtomList.h 2017-11-22 12:17:33.729682485 +0100 @@ -2270,6 +2270,10 @@ GK_ATOM(touch_enabled, "touch-enabled") GK_ATOM(menubar_drag, "menubar-drag") GK_ATOM(swipe_animation_enabled, "swipe-animation-enabled") GK_ATOM(physical_home_button, "physical-home-button") +GK_ATOM(gtk_csd_available, "gtk-csd-available") +GK_ATOM(gtk_csd_minimize_button, "gtk-csd-minimize-button") +GK_ATOM(gtk_csd_maximize_button, "gtk-csd-maximize-button") +GK_ATOM(gtk_csd_close_button, "gtk-csd-close-button") // windows theme selector metrics GK_ATOM(windows_classic, "windows-classic") @@ -2306,6 +2310,10 @@ GK_ATOM(_moz_device_orientation, "-moz-d GK_ATOM(_moz_is_resource_document, "-moz-is-resource-document") GK_ATOM(_moz_swipe_animation_enabled, "-moz-swipe-animation-enabled") GK_ATOM(_moz_physical_home_button, "-moz-physical-home-button") +GK_ATOM(_moz_gtk_csd_available, "-moz-gtk-csd-available") +GK_ATOM(_moz_gtk_csd_minimize_button, "-moz-gtk-csd-minimize-button") +GK_ATOM(_moz_gtk_csd_maximize_button, "-moz-gtk-csd-maximize-button") +GK_ATOM(_moz_gtk_csd_close_button, "-moz-gtk-csd-close-button") // application commands GK_ATOM(Back, "Back") diff -up firefox-57.0/gfx/src/nsThemeConstants.h.1399611 firefox-57.0/gfx/src/nsThemeConstants.h --- firefox-57.0/gfx/src/nsThemeConstants.h.1399611 2017-07-31 18:20:46.000000000 +0200 +++ firefox-57.0/gfx/src/nsThemeConstants.h 2017-11-22 12:17:33.729682485 +0100 @@ -299,6 +299,7 @@ enum ThemeWidgetType : uint8_t { NS_THEME_MAC_SOURCE_LIST, NS_THEME_MAC_SOURCE_LIST_SELECTION, NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION, + NS_THEME_GTK_WINDOW_DECORATION, ThemeWidgetType_COUNT }; diff -up firefox-57.0/layout/style/nsCSSRuleProcessor.cpp.1399611 firefox-57.0/layout/style/nsCSSRuleProcessor.cpp --- firefox-57.0/layout/style/nsCSSRuleProcessor.cpp.1399611 2017-09-14 22:16:05.000000000 +0200 +++ firefox-57.0/layout/style/nsCSSRuleProcessor.cpp 2017-11-22 12:17:33.729682485 +0100 @@ -1180,6 +1180,30 @@ nsCSSRuleProcessor::InitSystemMetrics() sSystemMetrics->AppendElement(nsGkAtoms::physical_home_button); } + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDAvailable, + &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::gtk_csd_available); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDMinimizeButton, + &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::gtk_csd_minimize_button); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDMaximizeButton, + &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::gtk_csd_maximize_button); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDCloseButton, + &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::gtk_csd_close_button); + } + #ifdef XP_WIN if (NS_SUCCEEDED( LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsThemeIdentifier, diff -up firefox-57.0/layout/style/nsMediaFeatures.cpp.1399611 firefox-57.0/layout/style/nsMediaFeatures.cpp --- firefox-57.0/layout/style/nsMediaFeatures.cpp.1399611 2017-11-02 17:16:35.000000000 +0100 +++ firefox-57.0/layout/style/nsMediaFeatures.cpp 2017-11-22 12:17:33.730682482 +0100 @@ -788,6 +788,42 @@ nsMediaFeatures::features[] = { GetSystemMetric }, + { + &nsGkAtoms::_moz_gtk_csd_available, + nsMediaFeature::eMinMaxNotAllowed, + nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, + { &nsGkAtoms::gtk_csd_available }, + GetSystemMetric + }, + + { + &nsGkAtoms::_moz_gtk_csd_minimize_button, + nsMediaFeature::eMinMaxNotAllowed, + nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, + { &nsGkAtoms::gtk_csd_minimize_button }, + GetSystemMetric + }, + + { + &nsGkAtoms::_moz_gtk_csd_maximize_button, + nsMediaFeature::eMinMaxNotAllowed, + nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, + { &nsGkAtoms::gtk_csd_maximize_button }, + GetSystemMetric + }, + + { + &nsGkAtoms::_moz_gtk_csd_close_button, + nsMediaFeature::eMinMaxNotAllowed, + nsMediaFeature::eBoolInteger, + nsMediaFeature::eNoRequirements, + { &nsGkAtoms::gtk_csd_close_button }, + GetSystemMetric + }, + // Internal -moz-is-glyph media feature: applies only inside SVG glyphs. // Internal because it is really only useful in the user agent anyway // and therefore not worth standardizing. diff -up firefox-57.0/modules/libpref/init/all.js.1399611 firefox-57.0/modules/libpref/init/all.js --- firefox-57.0/modules/libpref/init/all.js.1399611 2017-11-02 17:16:31.000000000 +0100 +++ firefox-57.0/modules/libpref/init/all.js 2017-11-22 12:17:33.730682482 +0100 @@ -4913,6 +4913,7 @@ pref("gfx.apitrace.enabled",false); pref("gfx.xrender.enabled",false); pref("widget.chrome.allow-gtk-dark-theme", false); pref("widget.content.allow-gtk-dark-theme", false); +pref("widget.allow-client-side-decoration", false); #endif #endif diff -up firefox-57.0/toolkit/modules/moz.build.1399611 firefox-57.0/toolkit/modules/moz.build --- firefox-57.0/toolkit/modules/moz.build.1399611 2017-11-02 17:16:34.000000000 +0100 +++ firefox-57.0/toolkit/modules/moz.build 2017-11-22 12:17:33.730682482 +0100 @@ -259,7 +259,7 @@ EXTRA_JS_MODULES.sessionstore += [ ] DEFINES['INSTALL_COMPACT_THEMES'] = 1 -if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'): +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa', 'gtk3'): DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'): diff -up firefox-57.0/widget/gtk/gtk3drawing.cpp.1399611 firefox-57.0/widget/gtk/gtk3drawing.cpp --- firefox-57.0/widget/gtk/gtk3drawing.cpp.1399611 2017-11-02 17:16:34.000000000 +0100 +++ firefox-57.0/widget/gtk/gtk3drawing.cpp 2017-11-22 12:17:33.731682479 +0100 @@ -17,6 +17,7 @@ #include "WidgetStyleCache.h" #include +#include static gboolean checkbox_check_state; static gboolean notebook_has_tab_gap; @@ -39,9 +40,25 @@ static gint moz_gtk_menu_item_paint(WidgetNodeType widget, cairo_t *cr, GdkRectangle* rect, GtkWidgetState* state, GtkTextDirection direction); +static void +moz_gtk_add_style_margin(GtkStyleContext* style, + gint* left, gint* top, gint* right, gint* bottom); +static void +moz_gtk_add_style_border(GtkStyleContext* style, + gint* left, gint* top, gint* right, gint* bottom); +static void +moz_gtk_add_style_padding(GtkStyleContext* style, + gint* left, gint* top, gint* right, gint* bottom); +static void moz_gtk_add_margin_border_padding(GtkStyleContext *style, + gint* left, gint* top, + gint* right, gint* bottom); +static void moz_gtk_add_border_padding(GtkStyleContext *style, + gint* left, gint* top, + gint* right, gint* bottom); static GtkBorder GetMarginBorderPadding(GtkStyleContext* aStyle); + // GetStateFlagsFromGtkWidgetState() can be safely used for the specific // GtkWidgets that set both prelight and active flags. For other widgets, // either the GtkStateFlags or Gecko's GtkWidgetState need to be carefully @@ -233,6 +250,43 @@ moz_gtk_splitter_get_metrics(gint orient return MOZ_GTK_SUCCESS; } +void +moz_gtk_get_window_border(gint* top, gint* right, gint* bottom, gint* left) +{ + MOZ_ASSERT(gtk_check_version(3, 20, 0) == nullptr, + "Window decorations are only supported on GTK 3.20+."); + + GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW); + + *top = *right = *bottom = *left = 0; + moz_gtk_add_border_padding(style, left, top, right, bottom); + GtkBorder windowMargin; + gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &windowMargin); + + style = GetStyleContext(MOZ_GTK_WINDOW_DECORATION); + + // Available on GTK 3.20+. + static auto sGtkRenderBackgroundGetClip = + (void (*)(GtkStyleContext*, gdouble, gdouble, gdouble, gdouble, GdkRectangle*)) + dlsym(RTLD_DEFAULT, "gtk_render_background_get_clip"); + + GdkRectangle shadowClip; + sGtkRenderBackgroundGetClip(style, 0, 0, 0, 0, &shadowClip); + + // Transfer returned inset rectangle to GtkBorder + GtkBorder shadowBorder = { + static_cast(-shadowClip.x), // left + static_cast(shadowClip.width + shadowClip.x), // right + static_cast(-shadowClip.y), // top + static_cast(shadowClip.height + shadowClip.y), // bottom + }; + + *left += MAX(windowMargin.left, shadowBorder.left); + *right += MAX(windowMargin.right, shadowBorder.right); + *top += MAX(windowMargin.top, shadowBorder.top); + *bottom += MAX(windowMargin.bottom, shadowBorder.bottom); +} + static gint moz_gtk_window_paint(cairo_t *cr, GdkRectangle* rect, GtkTextDirection direction) @@ -302,6 +356,24 @@ moz_gtk_button_paint(cairo_t *cr, GdkRec } static gint +moz_gtk_header_bar_button_paint(cairo_t *cr, GdkRectangle* rect, + GtkWidgetState* state, + GtkReliefStyle relief, GtkWidget* widget, + GtkTextDirection direction) +{ + GtkBorder margin; + GtkStyleContext* style = gtk_widget_get_style_context(widget); + gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin); + + rect->x += margin.left; + rect->y += margin.top; + rect->width -= margin.left + margin.right; + rect->height -= margin.top + margin.bottom; + + return moz_gtk_button_paint(cr, rect, state, relief, widget, direction); +} + +static gint moz_gtk_toggle_paint(cairo_t *cr, GdkRectangle* rect, GtkWidgetState* state, gboolean selected, gboolean inconsistent, @@ -1948,6 +2020,38 @@ moz_gtk_info_bar_paint(cairo_t *cr, GdkR return MOZ_GTK_SUCCESS; } +static gint +moz_gtk_header_bar_paint(WidgetNodeType widgetType, + cairo_t *cr, GdkRectangle* rect, GtkWidgetState* state) +{ + GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state); + GtkStyleContext *style; + + style = GetStyleContext(widgetType, GTK_TEXT_DIR_LTR, + state_flags); + InsetByMargin(rect, style); + gtk_render_background(style, cr, rect->x, rect->y, rect->width, + rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); + + return MOZ_GTK_SUCCESS; +} + +void +moz_gtk_header_bar_paint(cairo_t *cr, GdkRectangle* rect) +{ + static GtkWidgetState state; + moz_gtk_header_bar_paint(MOZ_GTK_HEADER_BAR, cr, rect, &state); +} + +void +moz_gtk_get_header_bar_border(gint* top, gint* right, gint* bottom, gint* left) +{ + *left = *top = *right = *bottom = 0; + moz_gtk_add_border_padding(GetStyleContext(MOZ_GTK_HEADER_BAR), + left, top, right, bottom); +} + static void moz_gtk_add_style_margin(GtkStyleContext* style, gint* left, gint* top, gint* right, gint* bottom) @@ -1999,6 +2103,14 @@ static void moz_gtk_add_margin_border_pa moz_gtk_add_style_padding(style, left, top, right, bottom); } +static void moz_gtk_add_border_padding(GtkStyleContext *style, + gint* left, gint* top, + gint* right, gint* bottom) +{ + moz_gtk_add_style_border(style, left, top, right, bottom); + moz_gtk_add_style_padding(style, left, top, right, bottom); +} + static GtkBorder GetMarginBorderPadding(GtkStyleContext* aStyle) { @@ -2054,8 +2166,7 @@ moz_gtk_get_widget_border(WidgetNodeType // XXX: Subtract 1 pixel from the padding to account for the default // padding in forms.css. See bug 1187385. *left = *top = *right = *bottom = -1; - moz_gtk_add_style_padding(style, left, top, right, bottom); - moz_gtk_add_style_border(style, left, top, right, bottom); + moz_gtk_add_border_padding(style, left, top, right, bottom); return MOZ_GTK_SUCCESS; } @@ -2076,10 +2187,8 @@ moz_gtk_get_widget_border(WidgetNodeType *left = *top = *right = *bottom = gtk_container_get_border_width(GTK_CONTAINER( GetWidget(MOZ_GTK_TREE_HEADER_CELL))); - style = GetStyleContext(MOZ_GTK_TREE_HEADER_CELL); - moz_gtk_add_style_border(style, left, top, right, bottom); - moz_gtk_add_style_padding(style, left, top, right, bottom); + moz_gtk_add_border_padding(style, left, top, right, bottom); return MOZ_GTK_SUCCESS; } case MOZ_GTK_TREE_HEADER_SORTARROW: @@ -2105,8 +2214,7 @@ moz_gtk_get_widget_border(WidgetNodeType gtk_container_get_border_width(GTK_CONTAINER( GetWidget(MOZ_GTK_COMBOBOX_BUTTON))); style = GetStyleContext(MOZ_GTK_COMBOBOX_BUTTON); - moz_gtk_add_style_padding(style, left, top, right, bottom); - moz_gtk_add_style_border(style, left, top, right, bottom); + moz_gtk_add_border_padding(style, left, top, right, bottom); /* If there is no separator, don't try to count its width. */ separator_width = 0; @@ -2160,10 +2268,8 @@ moz_gtk_get_widget_border(WidgetNodeType style = gtk_widget_get_style_context(w); *left = *top = *right = *bottom = gtk_container_get_border_width(GTK_CONTAINER(w)); - moz_gtk_add_style_border(style, - left, top, right, bottom); - moz_gtk_add_style_padding(style, - left, top, right, bottom); + moz_gtk_add_border_padding(style, + left, top, right, bottom); return MOZ_GTK_SUCCESS; } case MOZ_GTK_MENUPOPUP: @@ -2210,6 +2316,21 @@ moz_gtk_get_widget_border(WidgetNodeType return MOZ_GTK_SUCCESS; } + case MOZ_GTK_HEADER_BAR: + case MOZ_GTK_HEADER_BAR_MAXIMIZED: + { + style = GetStyleContext(widget); + moz_gtk_add_border_padding(style, left, top, right, bottom); + return MOZ_GTK_SUCCESS; + } + case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE: + case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE: + case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE: + { + style = GetStyleContext(widget); + moz_gtk_add_margin_border_padding(style, left, top, right, bottom); + return MOZ_GTK_SUCCESS; + } /* These widgets have no borders, since they are not containers. */ case MOZ_GTK_CHECKBUTTON_LABEL: @@ -2646,6 +2767,36 @@ GetScrollbarMetrics(GtkOrientation aOrie return metrics; } +void +moz_gtk_window_decoration_paint(cairo_t *cr, GdkRectangle* rect) +{ + gint top, right, bottom, left; + moz_gtk_get_window_border(&top, &right, &bottom, &left); + + cairo_save(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + cairo_rectangle(cr, rect->x, rect->y, left, rect->height); + cairo_fill(cr); + cairo_rectangle(cr, rect->x+rect->width-right, rect->y, right, rect->height); + cairo_fill(cr); + cairo_rectangle(cr, rect->x, rect->y, rect->width, top); + cairo_fill(cr); + cairo_rectangle(cr, rect->x, rect->height-bottom, rect->width, bottom); + cairo_fill(cr); + cairo_restore(cr); + + GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW_DECORATION, + GTK_TEXT_DIR_NONE); + rect->x += left; + rect->y += top; + rect->width -= left + right; + rect->height -= top + bottom; + + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); +} + + /* cairo_t *cr argument has to be a system-cairo. */ gint moz_gtk_widget_paint(WidgetNodeType widget, cairo_t *cr, @@ -2671,6 +2822,14 @@ moz_gtk_widget_paint(WidgetNodeType widg GetWidget(MOZ_GTK_BUTTON), direction); break; + case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE: + case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE: + case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE: + return moz_gtk_header_bar_button_paint(cr, rect, state, + (GtkReliefStyle) flags, + GetWidget(widget), + direction); + break; case MOZ_GTK_CHECKBUTTON: case MOZ_GTK_RADIOBUTTON: return moz_gtk_toggle_paint(cr, rect, state, @@ -2877,6 +3036,10 @@ moz_gtk_widget_paint(WidgetNodeType widg case MOZ_GTK_INFO_BAR: return moz_gtk_info_bar_paint(cr, rect, state); break; + case MOZ_GTK_HEADER_BAR: + case MOZ_GTK_HEADER_BAR_MAXIMIZED: + return moz_gtk_header_bar_paint(widget, cr, rect, state); + break; default: g_warning("Unknown widget type: %d", widget); } diff -up firefox-57.0/widget/gtk/gtkdrawing.h.1399611 firefox-57.0/widget/gtk/gtkdrawing.h --- firefox-57.0/widget/gtk/gtkdrawing.h.1399611 2017-07-31 18:20:53.000000000 +0200 +++ firefox-57.0/widget/gtk/gtkdrawing.h 2017-11-22 12:17:33.731682479 +0100 @@ -268,8 +268,14 @@ typedef enum { MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL, /* Paints the background of a window, dialog or page. */ MOZ_GTK_WINDOW, + /* Used only as a container for MOZ_GTK_HEADER_BAR_MAXIMIZED. */ + MOZ_GTK_WINDOW_MAXIMIZED, /* Window container for all widgets */ MOZ_GTK_WINDOW_CONTAINER, + /* Window with the 'csd' style class. */ + MOZ_GTK_WINDOW_CSD, + /* Client-side window decoration node. Available on GTK 3.20+. */ + MOZ_GTK_WINDOW_DECORATION, /* Paints a GtkInfoBar, for notifications. */ MOZ_GTK_INFO_BAR, /* Used for widget tree construction. */ @@ -290,6 +296,14 @@ typedef enum { MOZ_GTK_COMBOBOX_ENTRY_ARROW, /* Used for scrolled window shell. */ MOZ_GTK_SCROLLED_WINDOW, + /* Paints a GtkHeaderBar */ + MOZ_GTK_HEADER_BAR, + /* Paints a GtkHeaderBar in maximized state */ + MOZ_GTK_HEADER_BAR_MAXIMIZED, + /* Paints a GtkHeaderBar title buttons */ + MOZ_GTK_HEADER_BAR_BUTTON_CLOSE, + MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE, + MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE, MOZ_GTK_WIDGET_NODE_COUNT } WidgetNodeType; @@ -542,6 +556,32 @@ gint moz_gtk_get_menu_separator_height(g */ gint moz_gtk_splitter_get_metrics(gint orientation, gint* size); +#if (MOZ_WIDGET_GTK == 3) +/** + * Gets the margins to be used for window decorations, typically the extra space + * required to draw a drop shadow (obtained from gtk_render_background_get_clip). + * Only available on GTK 3.20+. + */ +void moz_gtk_get_window_border(gint* top, gint* right, gint* bottom, gint* left); + +/** + * Draw window decorations, typically a shadow. + * Only available on GTK 3.20+. + */ +void moz_gtk_window_decoration_paint(cairo_t *cr, GdkRectangle* rect); + +/** + * Gets the border of window header bar, only available on GTK 3.20+. + */ +void moz_gtk_get_header_bar_border(gint* top, gint* right, gint* bottom, gint* left); + +/** + * Draw window header bar, only available on GTK 3.20+. + */ +void moz_gtk_header_bar_paint(cairo_t *cr, GdkRectangle* rect); + +#endif + /** * Get the YTHICKNESS of a tab (notebook extension). */ diff -up firefox-57.0/widget/gtk/mozgtk/mozgtk.c.1399611 firefox-57.0/widget/gtk/mozgtk/mozgtk.c --- firefox-57.0/widget/gtk/mozgtk/mozgtk.c.1399611 2017-11-02 17:16:34.000000000 +0100 +++ firefox-57.0/widget/gtk/mozgtk/mozgtk.c 2017-11-22 12:17:33.731682479 +0100 @@ -580,6 +580,8 @@ STUB(gtk_style_context_set_state) STUB(gtk_style_properties_lookup_property) STUB(gtk_tree_view_column_get_button) STUB(gtk_widget_get_preferred_size) +STUB(gtk_widget_get_preferred_width) +STUB(gtk_widget_get_preferred_height) STUB(gtk_widget_get_state_flags) STUB(gtk_widget_get_style_context) STUB(gtk_widget_path_append_type) @@ -589,6 +591,10 @@ STUB(gtk_widget_path_iter_add_class) STUB(gtk_widget_path_get_object_type) STUB(gtk_widget_path_new) STUB(gtk_widget_path_unref) +STUB(gtk_widget_set_margin_left) +STUB(gtk_widget_set_margin_right) +STUB(gtk_widget_set_margin_top) +STUB(gtk_widget_set_margin_bottom) STUB(gtk_widget_set_visual) STUB(gtk_app_chooser_dialog_new_for_content_type) STUB(gtk_app_chooser_get_type) @@ -601,6 +607,7 @@ STUB(gtk_color_chooser_get_type) STUB(gtk_color_chooser_set_rgba) STUB(gtk_color_chooser_get_rgba) STUB(gtk_color_chooser_set_use_alpha) +STUB(gtk_window_get_size) #endif #ifdef GTK2_SYMBOLS diff -up firefox-57.0/widget/gtk/nsLookAndFeel.cpp.1399611 firefox-57.0/widget/gtk/nsLookAndFeel.cpp --- firefox-57.0/widget/gtk/nsLookAndFeel.cpp.1399611 2017-11-02 17:16:34.000000000 +0100 +++ firefox-57.0/widget/gtk/nsLookAndFeel.cpp 2017-11-22 12:17:33.731682479 +0100 @@ -642,6 +642,22 @@ nsLookAndFeel::GetIntImpl(IntID aID, int case eIntID_ContextMenuOffsetHorizontal: aResult = 2; break; + case eIntID_GTKCSDAvailable: + EnsureInit(); + aResult = sCSDAvailable; + break; + case eIntID_GTKCSDMaximizeButton: + EnsureInit(); + aResult = sCSDMaximizeButton; + break; + case eIntID_GTKCSDMinimizeButton: + EnsureInit(); + aResult = sCSDMinimizeButton; + break; + case eIntID_GTKCSDCloseButton: + EnsureInit(); + aResult = sCSDCloseButton; + break; default: aResult = 0; res = NS_ERROR_FAILURE; @@ -1048,6 +1064,40 @@ nsLookAndFeel::EnsureInit() gtk_widget_destroy(window); g_object_unref(labelWidget); + + // Require GTK 3.20 for client-side decoration support. + // 3.20 exposes gtk_render_background_get_clip, which is required for + // calculating shadow metrics for decorated windows. + sCSDAvailable = gtk_check_version(3, 20, 0) == nullptr; + if (sCSDAvailable) { + sCSDAvailable = + mozilla::Preferences::GetBool("widget.allow-client-side-decoration", + false); + } + + const gchar* decorationLayout = nullptr; + if (gtk_check_version(3, 12, 0) == nullptr) { + static auto sGtkHeaderBarGetDecorationLayoutPtr = + (const gchar* (*)(GtkWidget*)) + dlsym(RTLD_DEFAULT, "gtk_header_bar_get_decoration_layout"); + + GtkWidget* headerBar = GetWidget(MOZ_GTK_HEADER_BAR); + decorationLayout = sGtkHeaderBarGetDecorationLayoutPtr(headerBar); + if (!decorationLayout) { + g_object_get(settings, "gtk-decoration-layout", &decorationLayout, + nullptr); + } + } + + if (decorationLayout) { + sCSDCloseButton = (strstr(decorationLayout, "close") != nullptr); + sCSDMaximizeButton = (strstr(decorationLayout, "maximize") != nullptr); + sCSDMinimizeButton = (strstr(decorationLayout, "minimize") != nullptr); + } else { + sCSDCloseButton = true; + sCSDMaximizeButton = true; + sCSDMinimizeButton = true; + } } // virtual diff -up firefox-57.0/widget/gtk/nsLookAndFeel.h.1399611 firefox-57.0/widget/gtk/nsLookAndFeel.h --- firefox-57.0/widget/gtk/nsLookAndFeel.h.1399611 2017-11-02 17:16:34.000000000 +0100 +++ firefox-57.0/widget/gtk/nsLookAndFeel.h 2017-11-22 12:17:33.732682476 +0100 @@ -32,6 +32,8 @@ public: virtual char16_t GetPasswordCharacterImpl(); virtual bool GetEchoPasswordImpl(); + bool IsCSDAvailable() const { return sCSDAvailable; } + protected: // Cached fonts @@ -82,6 +84,10 @@ protected: char16_t sInvisibleCharacter; float sCaretRatio; bool sMenuSupportsDrag; + bool sCSDAvailable; + bool sCSDMaximizeButton; + bool sCSDMinimizeButton; + bool sCSDCloseButton; bool mInitialized; void EnsureInit(); diff -up firefox-57.0/widget/gtk/nsNativeThemeGTK.cpp.1399611 firefox-57.0/widget/gtk/nsNativeThemeGTK.cpp --- firefox-57.0/widget/gtk/nsNativeThemeGTK.cpp.1399611 2017-11-02 17:16:34.000000000 +0100 +++ firefox-57.0/widget/gtk/nsNativeThemeGTK.cpp 2017-11-22 12:17:33.732682476 +0100 @@ -23,6 +23,7 @@ #include "nsIDOMHTMLInputElement.h" #include "nsGkAtoms.h" #include "nsAttrValueInlines.h" +#include "nsWindow.h" #include "mozilla/EventStates.h" #include "mozilla/Services.h" @@ -703,6 +704,24 @@ nsNativeThemeGTK::GetGtkWidgetAndState(u case NS_THEME_GTK_INFO_BAR: aGtkWidgetType = MOZ_GTK_INFO_BAR; break; + case NS_THEME_WINDOW_TITLEBAR: + aGtkWidgetType = MOZ_GTK_HEADER_BAR; + break; + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + aGtkWidgetType = MOZ_GTK_HEADER_BAR_MAXIMIZED; + break; + case NS_THEME_WINDOW_BUTTON_CLOSE: + aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE; + break; + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE; + break; + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE; + break; + case NS_THEME_WINDOW_BUTTON_RESTORE: + aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE; + break; default: return false; } @@ -1627,6 +1646,10 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n case NS_THEME_MENULIST: case NS_THEME_TOOLBARBUTTON: case NS_THEME_TREEHEADERCELL: + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: { if (aWidgetType == NS_THEME_MENULIST) { // Include the arrow size. @@ -1892,9 +1915,21 @@ nsNativeThemeGTK::ThemeSupportsWidget(ns case NS_THEME_DIALOG: #if (MOZ_WIDGET_GTK == 3) case NS_THEME_GTK_INFO_BAR: + case NS_THEME_GTK_WINDOW_DECORATION: #endif return !IsWidgetStyled(aPresContext, aFrame, aWidgetType); + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + // GtkHeaderBar is available on GTK 3.10+, which is used for styling + // title bars and title buttons. + return gtk_check_version(3, 10, 0) == nullptr && + !IsWidgetStyled(aPresContext, aFrame, aWidgetType); + case NS_THEME_MENULIST_BUTTON: if (aFrame && aFrame->GetWritingMode().IsVertical()) { return false; @@ -1978,6 +2013,13 @@ nsNativeThemeGTK::GetWidgetTransparency( #else return eTransparent; #endif + case NS_THEME_GTK_WINDOW_DECORATION: + { + nsWindow* window = static_cast(aFrame->GetNearestWidget()); + if (window) + return window->IsComposited() ? eTransparent : eOpaque; + return eOpaque; + } } return eUnknownTransparency; diff -up firefox-57.0/widget/gtk/nsWindow.cpp.1399611 firefox-57.0/widget/gtk/nsWindow.cpp --- firefox-57.0/widget/gtk/nsWindow.cpp.1399611 2017-11-22 12:17:33.724682501 +0100 +++ firefox-57.0/widget/gtk/nsWindow.cpp 2017-11-22 13:28:29.352498513 +0100 @@ -85,6 +85,7 @@ #include "nsIPropertyBag2.h" #include "GLContext.h" #include "gfx2DGlue.h" +#include "nsLookAndFeel.h" #ifdef ACCESSIBILITY #include "mozilla/a11y/Accessible.h" @@ -139,6 +140,8 @@ using namespace mozilla::widget; #include "mozilla/layers/APZCTreeManager.h" +#include "gtkdrawing.h" + using namespace mozilla; using namespace mozilla::gfx; using namespace mozilla::widget; @@ -186,6 +189,8 @@ static gboolean expose_event_cb #else static gboolean expose_event_cb (GtkWidget *widget, cairo_t *rect); +static gboolean expose_event_decoration_draw_cb (GtkWidget *widget, + cairo_t *cr); #endif static gboolean configure_event_cb (GtkWidget *widget, GdkEventConfigure *event); @@ -231,7 +236,6 @@ static void screen_composited_change gpointer user_data); static void widget_composited_changed_cb (GtkWidget* widget, gpointer user_data); - #if (MOZ_WIDGET_GTK == 3) static void scale_changed_cb (GtkWidget* widget, GParamSpec* aPSpec, @@ -440,6 +444,7 @@ nsWindow::nsWindow() mContainer = nullptr; mGdkWindow = nullptr; + mIsCSDEnabled = false; mShell = nullptr; mCompositorWidgetDelegate = nullptr; mHasMappedToplevel = false; @@ -481,6 +486,9 @@ nsWindow::nsWindow() mLastScrollEventTime = GDK_CURRENT_TIME; #endif mPendingConfigures = 0; + mDrawWindowDecoration = false; + mDecorationSize = {0,0,0,0}; + mCSDSupportLevel = CSD_SUPPORT_UNKNOWN; } nsWindow::~nsWindow() @@ -1479,8 +1487,8 @@ LayoutDeviceIntRect nsWindow::GetScreenBounds() { LayoutDeviceIntRect rect; - if (mIsTopLevel && mContainer) { - // use the point including window decorations + if (mIsTopLevel && mContainer && !IsClientDecorated()) { + // use the point including default Gtk+ window decorations gint x, y; gdk_window_get_root_origin(gtk_widget_get_window(GTK_WIDGET(mContainer)), &x, &y); rect.MoveTo(GdkPointToDevicePixels({ x, y })); @@ -1606,6 +1614,10 @@ nsWindow::SetCursor(nsCursor aCursor) return; gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), newCursor); + if (IsClientDecorated()) { + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mShell)), + newCursor); + } } } } @@ -1662,6 +1674,10 @@ nsWindow::SetCursor(imgIContainer* aCurs if (cursor) { if (mContainer) { gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), cursor); + if (IsClientDecorated()) { + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mShell)), + cursor); + } rv = NS_OK; } #if (MOZ_WIDGET_GTK == 3) @@ -2176,6 +2192,12 @@ nsWindow::OnExposeEvent(cairo_t *cr) return TRUE; } + // Clip upper part of the mContainer to get visible rounded corners + // of GtkHeaderBar which is renderd to mShell. + if (mIsCSDEnabled) { + ApplyCSDClipping(); + } + // If this widget uses OMTC... if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT || GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_WR) { @@ -2586,6 +2608,53 @@ nsWindow::OnMotionNotifyEvent(GdkEventMo } } #endif /* MOZ_X11 */ + // Client is decorated and we're getting the motion event for mShell + // window which draws the CSD decorations around mContainer. + if (IsClientDecorated()) { + if (aEvent->window == gtk_widget_get_window(mShell)) { + GdkWindowEdge edge; + LayoutDeviceIntPoint refPoint = + GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); + if (CheckResizerEdge(refPoint, edge)) { + nsCursor cursor = eCursor_none; + switch (edge) { + case GDK_WINDOW_EDGE_NORTH: + cursor = eCursor_n_resize; + break; + case GDK_WINDOW_EDGE_NORTH_WEST: + cursor = eCursor_nw_resize; + break; + case GDK_WINDOW_EDGE_NORTH_EAST: + cursor = eCursor_ne_resize; + break; + case GDK_WINDOW_EDGE_WEST: + cursor = eCursor_w_resize; + break; + case GDK_WINDOW_EDGE_EAST: + cursor = eCursor_e_resize; + break; + case GDK_WINDOW_EDGE_SOUTH: + cursor = eCursor_s_resize; + break; + case GDK_WINDOW_EDGE_SOUTH_WEST: + cursor = eCursor_sw_resize; + break; + case GDK_WINDOW_EDGE_SOUTH_EAST: + cursor = eCursor_se_resize; + break; + } + SetCursor(cursor); + return; + } + } + // We're not on resize handle - check if we need to reset cursor back. + if (mCursor == eCursor_n_resize || mCursor == eCursor_nw_resize || + mCursor == eCursor_ne_resize || mCursor == eCursor_w_resize || + mCursor == eCursor_e_resize || mCursor == eCursor_s_resize || + mCursor == eCursor_sw_resize || mCursor == eCursor_se_resize) { + SetCursor(eCursor_standard); + } + } WidgetMouseEvent event(true, eMouseMove, this, WidgetMouseEvent::eReal); @@ -2756,6 +2825,20 @@ nsWindow::OnButtonPressEvent(GdkEventBut if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) return; + if (IsClientDecorated() && aEvent->window == gtk_widget_get_window(mShell)) { + // Check to see if the event is within our window's resize region + GdkWindowEdge edge; + LayoutDeviceIntPoint refPoint = + GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y); + if (CheckResizerEdge(refPoint, edge)) { + gdk_window_begin_resize_drag(gtk_widget_get_window(mShell), + edge, aEvent->button, + aEvent->x_root, aEvent->y_root, + aEvent->time); + return; + } + } + gdouble pressure = 0; gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure); mLastMotionPressure = pressure; @@ -3341,6 +3424,8 @@ nsWindow::OnWindowStateEvent(GtkWidget * #endif //ACCESSIBILITY } + UpdateClientDecorations(); + if (mWidgetListener) { mWidgetListener->SizeModeChanged(mSizeState); if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) { @@ -3405,6 +3490,7 @@ nsWindow::OnCompositedChanged() presShell->ThemeChanged(); } } + UpdateClientDecorations(); } void @@ -3593,7 +3679,8 @@ nsWindow::Create(nsIWidget* aParent, GtkWindow *topLevelParent = nullptr; nsWindow *parentnsWindow = nullptr; GtkWidget *eventWidget = nullptr; - bool shellHasCSD = false; + GtkWidget *drawWidget = nullptr; + bool drawToContainer = false; if (aParent) { parentnsWindow = static_cast(aParent); @@ -3640,29 +3727,47 @@ nsWindow::Create(nsIWidget* aParent, GTK_WINDOW_TOPLEVEL : GTK_WINDOW_POPUP; mShell = gtk_window_new(type); - bool useAlphaVisual = (mWindowType == eWindowType_popup && - aInitData->mSupportTranslucency); + bool useAlphaVisual = false; +#if (MOZ_WIDGET_GTK == 3) + // When CSD is available we can emulate it for toplevel windows. + // Content is rendered to mContainer and transparent decorations to mShell. + if (GetCSDSupportLevel() != CSD_SUPPORT_NONE && + mWindowType == eWindowType_toplevel) { + int32_t isCSDAvailable = false; + nsresult rv = LookAndFeel::GetInt(LookAndFeel::eIntID_GTKCSDAvailable, + &isCSDAvailable); + if (NS_SUCCEEDED(rv)) { + mIsCSDEnabled = useAlphaVisual = isCSDAvailable; + } + } else +#endif + if (mWindowType == eWindowType_popup) { + useAlphaVisual = aInitData->mSupportTranslucency; + } // mozilla.widget.use-argb-visuals is a hidden pref defaulting to false // to allow experimentation if (Preferences::GetBool("mozilla.widget.use-argb-visuals", false)) useAlphaVisual = true; + // An ARGB visual is only useful if we are on a compositing + // window manager. + GdkScreen *screen = gtk_widget_get_screen(mShell); + if (useAlphaVisual && !gdk_screen_is_composited(screen)) { + useAlphaVisual = false; + } + // We need to select an ARGB visual here instead of in // SetTransparencyMode() because it has to be done before the - // widget is realized. An ARGB visual is only useful if we - // are on a compositing window manager. + // widget is realized. if (useAlphaVisual) { - GdkScreen *screen = gtk_widget_get_screen(mShell); - if (gdk_screen_is_composited(screen)) { #if (MOZ_WIDGET_GTK == 2) - GdkColormap *colormap = gdk_screen_get_rgba_colormap(screen); - gtk_widget_set_colormap(mShell, colormap); + GdkColormap *colormap = gdk_screen_get_rgba_colormap(screen); + gtk_widget_set_colormap(mShell, colormap); #else - GdkVisual *visual = gdk_screen_get_rgba_visual(screen); - gtk_widget_set_visual(mShell, visual); + GdkVisual *visual = gdk_screen_get_rgba_visual(screen); + gtk_widget_set_visual(mShell, visual); #endif - } } // We only move a general managed toplevel window if someone has @@ -3756,24 +3861,56 @@ nsWindow::Create(nsIWidget* aParent, mContainer = MOZ_CONTAINER(container); #if (MOZ_WIDGET_GTK == 3) - // "csd" style is set when widget is realized so we need to call - // it explicitly now. - gtk_widget_realize(mShell); - - // We can't draw directly to top-level window when client side - // decorations are enabled. We use container with GdkWindow instead. - GtkStyleContext* style = gtk_widget_get_style_context(mShell); - shellHasCSD = gtk_style_context_has_class(style, "csd"); -#endif - if (!shellHasCSD) { - // Use mShell's window for drawing and events. - gtk_widget_set_has_window(container, FALSE); - // Prevent GtkWindow from painting a background to flicker. - gtk_widget_set_app_paintable(mShell, TRUE); - } - // Set up event widget - eventWidget = shellHasCSD ? container : mShell; + /* There are tree possible situations here: + * + * 1) We're running on Gtk+ < 3.20 without any decorations. Content + * is rendered to mShell window and we listen Gtk+ events on mShell. + * 2) We're running on Gtk+ > 3.20 and window decorations are drawn + * by default by Gtk+. Content is rendered to mContainer, + * we listen events on mContainer. mShell contains default Gtk+ + * window decorations rendered by Gtk+. + * 3) We're running on Gtk+ > 3.20 and both window decorations and + * content is rendered by gecko. We emulate Gtk+ decoration rendering + * to mShell and we need to listen Gtk events on both mShell + * and mContainer. + */ + + // When client side decorations are enabled (rendered by us or by Gtk+) + // the decoration is rendered to mShell (toplevel) window and + // we draw our content to mContainer. + if (mIsCSDEnabled) { + drawToContainer = true; + } else { + // mIsCSDEnabled can be disabled by preference so look at actual + // toplevel window style to to detect active "csd" style. + // The "csd" style is set when widget is realized so we need to call + // it explicitly now. + gtk_widget_realize(mShell); + + GtkStyleContext* style = gtk_widget_get_style_context(mShell); + drawToContainer = gtk_style_context_has_class(style, "csd"); + } +#endif + drawWidget = (drawToContainer) ? container : mShell; + // When we draw decorations on our own we need to handle resize events + // because Gtk+ does not provide resizers for undecorated windows. + // The CSD on mShell borders act as resize handlers + // so we need to listen there. + eventWidget = (drawToContainer && !mIsCSDEnabled) ? container : mShell; + gtk_widget_add_events(eventWidget, kEvents); + if (eventWidget != drawWidget) { + // CSD is rendered by us (not by Gtk+) so we also need to listen + // at mShell window for events. + gtk_widget_add_events(drawWidget, kEvents); + } + + // Prevent GtkWindow from painting a background to flicker. + gtk_widget_set_app_paintable(drawWidget, TRUE); + + // gtk_container_add() realizes the child widget so we need to + // set it now. + gtk_widget_set_has_window(container, drawToContainer); gtk_container_add(GTK_CONTAINER(mShell), container); gtk_widget_realize(container); @@ -3783,7 +3920,7 @@ nsWindow::Create(nsIWidget* aParent, gtk_widget_grab_focus(container); // the drawing window - mGdkWindow = gtk_widget_get_window(eventWidget); + mGdkWindow = gtk_widget_get_window(drawWidget); if (mWindowType == eWindowType_popup) { // gdk does not automatically set the cursor for "temporary" @@ -3856,6 +3993,11 @@ nsWindow::Create(nsIWidget* aParent, // label the drawing window with this object so we can find our way home g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this); + if (mIsCSDEnabled) { + // label the CSD window with this object so we can find our way home + g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), + "nsWindow", this); + } if (mContainer) g_object_set_data(G_OBJECT(mContainer), "nsWindow", this); @@ -3893,6 +4035,10 @@ nsWindow::Create(nsIWidget* aParent, g_signal_connect_after(default_settings, "notify::gtk-font-name", G_CALLBACK(theme_changed_cb), this); + if (mIsCSDEnabled) { + g_signal_connect(G_OBJECT(mShell), "draw", + G_CALLBACK(expose_event_decoration_draw_cb), nullptr); + } } if (mContainer) { @@ -3943,7 +4089,7 @@ nsWindow::Create(nsIWidget* aParent, G_CALLBACK(drag_data_received_event_cb), nullptr); GtkWidget *widgets[] = { GTK_WIDGET(mContainer), - !shellHasCSD ? mShell : nullptr }; + !drawToContainer ? mShell : nullptr }; for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) { // Visibility events are sent to the owning widget of the relevant // window but do not propagate to parent widgets so connect on @@ -3973,7 +4119,6 @@ nsWindow::Create(nsIWidget* aParent, // Don't let GTK mess with the shapes of our GdkWindows GTK_PRIVATE_SET_FLAG(eventWidget, GTK_HAS_SHAPE_MASK); #endif - // These events are sent to the owning widget of the relevant window // and propagate up to the first widget that handles the events, so we // need only connect on mShell, if it exists, to catch events on its @@ -4110,6 +4255,12 @@ nsWindow::NativeResize() size.width, size.height)); if (mIsTopLevel) { + // When we draw decorations add extra space to draw shadows + // around the main window. + if (mDrawWindowDecoration) { + size.width += mDecorationSize.left + mDecorationSize.right; + size.height += mDecorationSize.top + mDecorationSize.bottom; + } gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height); } else if (mContainer) { @@ -4166,6 +4317,11 @@ nsWindow::NativeMoveResize() if (mIsTopLevel) { // x and y give the position of the window manager frame top-left. gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y); + + if (mDrawWindowDecoration) { + size.width += mDecorationSize.left + mDecorationSize.right; + size.height += mDecorationSize.top + mDecorationSize.bottom; + } // This sets the client window size. gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height); } @@ -5524,6 +5680,33 @@ expose_event_cb(GtkWidget *widget, cairo return FALSE; } + +/* static */ +gboolean +expose_event_decoration_draw_cb(GtkWidget *widget, cairo_t *cr) +{ + GdkWindow* gdkWindow = gtk_widget_get_window(widget); + if (gtk_cairo_should_draw_window(cr, gdkWindow)) { + RefPtr window = get_window_for_gtk_widget(widget); + if (!window) { + NS_WARNING("Cannot get nsWindow from GtkWidget"); + } + else if (window->IsClientDecorated()) { + cairo_save(cr); + gtk_cairo_transform_to_window(cr, widget, gdkWindow); + + GdkRectangle rect = {0,0,0,0}; + gtk_window_get_size(GTK_WINDOW(widget), &rect.width, &rect.height); + moz_gtk_window_decoration_paint(cr, &rect); + + rect.height = 40; + moz_gtk_header_bar_paint(cr, &rect); + cairo_restore(cr); + } + } + return TRUE; +} + #endif //MOZ_WIDGET_GTK == 2 static gboolean @@ -6576,6 +6759,28 @@ nsWindow::ClearCachedResources() } } +NS_IMETHODIMP +nsWindow::SetNonClientMargins(LayoutDeviceIntMargin &aMargins) +{ + SetDrawsInTitlebar(aMargins.top == 0); + return NS_OK; +} + +void +nsWindow::SetDrawsInTitlebar(bool aState) +{ + if (!mIsCSDEnabled || aState == mDrawWindowDecoration) + return; + + if (mShell) { + gtk_window_set_decorated(GTK_WINDOW(mShell), !aState); + gtk_widget_set_app_paintable(mShell, aState); + } + + mDrawWindowDecoration = aState; + UpdateClientDecorations(); +} + gint nsWindow::GdkScaleFactor() { @@ -6846,6 +7051,158 @@ nsWindow::SynthesizeNativeTouchPoint(uin } #endif +bool +nsWindow::IsClientDecorated() const +{ + return mDrawWindowDecoration && mSizeState == nsSizeMode_Normal; +} + +int +nsWindow::GetClientResizerSize() +{ + if (!mShell) + return 0; + + // GTK uses a default size of 20px as of 3.20. + gint size = 20; + gtk_widget_style_get(mShell, "decoration-resize-handle", &size, nullptr); + + return GdkCoordToDevicePixels(size); +} + +nsWindow::CSDSupportLevel +nsWindow::GetCSDSupportLevel() { + if (mCSDSupportLevel != CSD_SUPPORT_UNKNOWN) { + return mCSDSupportLevel; + } + const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP"); + if (currentDesktop) { + if (strcmp(currentDesktop, "GNOME") == 0) { + mCSDSupportLevel = CSD_SUPPORT_FULL; + } else if (strcmp(currentDesktop, "XFCE") == 0) { + mCSDSupportLevel = CSD_SUPPORT_FLAT; + } else if (strcmp(currentDesktop, "X-Cinnamon") == 0) { + mCSDSupportLevel = CSD_SUPPORT_FULL; + } else if (strcmp(currentDesktop, "KDE") == 0) { + mCSDSupportLevel = CSD_SUPPORT_FLAT; + } else if (strcmp(currentDesktop, "LXDE") == 0) { + mCSDSupportLevel = CSD_SUPPORT_FLAT; + } else if (strcmp(currentDesktop, "openbox") == 0) { + mCSDSupportLevel = CSD_SUPPORT_FLAT; + } else if (strcmp(currentDesktop, "i3") == 0) { + mCSDSupportLevel = CSD_SUPPORT_NONE; + } else if (strcmp(currentDesktop, "MATE") == 0) { + mCSDSupportLevel = CSD_SUPPORT_FLAT; + } else { + mCSDSupportLevel = CSD_SUPPORT_NONE; + } + } + return mCSDSupportLevel; +} + +void +nsWindow::UpdateClientDecorations() +{ + // When the CSD is not fully supported by window manager (ie. WM is not + // expecting that application is going to draw window shadows) we can't + // add shadows widths to the window margin. That would lead to completely + // opaque black border of the window. + if (!mDrawWindowDecoration || GetCSDSupportLevel() != CSD_SUPPORT_FULL) + return; + + gint top = 0, right = 0, bottom = 0, left = 0; + if (mSizeState == nsSizeMode_Normal) { + moz_gtk_get_window_border(&top, &right, &bottom, &left); + } + + static auto sGdkWindowSetShadowWidth = + (void (*)(GdkWindow*, gint, gint, gint, gint)) + dlsym(RTLD_DEFAULT, "gdk_window_set_shadow_width"); + sGdkWindowSetShadowWidth(gtk_widget_get_window(mShell), + left, right, top, bottom); + + mDecorationSize.left = left; + mDecorationSize.right = right; + mDecorationSize.top = top; + mDecorationSize.bottom = bottom; + + // Gtk+ doesn't like when we set mContainer margin bigger than actual + // mContainer window size. That happens when we're called early and the + // mShell/mContainer is not allocated/resized yet and has default 1x1 size. + // Just resize to some minimal value which will be changed + // by Gecko soon. + GtkAllocation allocation; + gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation); + if (allocation.width < left + right || allocation.height < top + bottom) { + gtk_widget_get_preferred_width(GTK_WIDGET(mContainer), nullptr, + &allocation.width); + gtk_widget_get_preferred_height(GTK_WIDGET(mContainer), nullptr, + &allocation.height); + allocation.width += left + right + 1; + allocation.height += top + bottom + 1; + gtk_widget_size_allocate(GTK_WIDGET(mContainer), &allocation); + } + + gtk_widget_set_margin_left(GTK_WIDGET(mContainer), mDecorationSize.left); + gtk_widget_set_margin_right(GTK_WIDGET(mContainer), mDecorationSize.right); + gtk_widget_set_margin_top(GTK_WIDGET(mContainer), mDecorationSize.top); + gtk_widget_set_margin_bottom(GTK_WIDGET(mContainer), mDecorationSize.bottom); +} + +void +nsWindow::ApplyCSDClipping() +{ + if (IsClientDecorated()) { + gint top, right, bottom, left; + moz_gtk_get_header_bar_border(&top, &right, &bottom, &left); + cairo_rectangle_int_t rect = { 0, top, mBounds.width, mBounds.height}; + cairo_region_t *region = cairo_region_create_rectangle(&rect); + gdk_window_shape_combine_region(mGdkWindow, region, 0, 0); + cairo_region_destroy(region); + } else { + gdk_window_shape_combine_region(mGdkWindow, nullptr, 0, 0); + } +} + +bool +nsWindow::CheckResizerEdge(LayoutDeviceIntPoint aPoint, GdkWindowEdge& aOutEdge) +{ + gint scale = GdkScaleFactor(); + gint left = scale * mDecorationSize.left; + gint top = scale * mDecorationSize.top; + gint right = scale * mDecorationSize.right; + gint bottom = scale * mDecorationSize.bottom; + + int resizerSize = GetClientResizerSize(); + int topDist = aPoint.y; + int leftDist = aPoint.x; + int rightDist = mBounds.width - aPoint.x; + int bottomDist = mBounds.height - aPoint.y; + + //TODO -> wrong sizes + + if (leftDist <= resizerSize && topDist <= resizerSize) { + aOutEdge = GDK_WINDOW_EDGE_NORTH_WEST; + } else if (rightDist <= resizerSize && topDist <= resizerSize) { + aOutEdge = GDK_WINDOW_EDGE_NORTH_EAST; + } else if (leftDist <= resizerSize && bottomDist <= resizerSize) { + aOutEdge = GDK_WINDOW_EDGE_SOUTH_WEST; + } else if (rightDist <= resizerSize && bottomDist <= resizerSize) { + aOutEdge = GDK_WINDOW_EDGE_SOUTH_EAST; + } else if (topDist <= top) { + aOutEdge = GDK_WINDOW_EDGE_NORTH; + } else if (leftDist <= left) { + aOutEdge = GDK_WINDOW_EDGE_WEST; + } else if (rightDist <= right) { + aOutEdge = GDK_WINDOW_EDGE_EAST; + } else if (bottomDist <= bottom) { + aOutEdge = GDK_WINDOW_EDGE_SOUTH; + } else { + return false; + } + return true; +} + int32_t nsWindow::RoundsWidgetCoordinatesTo() { diff -up firefox-57.0/widget/gtk/nsWindow.h.1399611 firefox-57.0/widget/gtk/nsWindow.h --- firefox-57.0/widget/gtk/nsWindow.h.1399611 2017-11-02 17:16:34.000000000 +0100 +++ firefox-57.0/widget/gtk/nsWindow.h 2017-11-22 12:17:33.733682473 +0100 @@ -123,6 +123,7 @@ public: double aHeight, bool aRepaint) override; virtual bool IsEnabled() const override; + bool IsComposited() const; void SetZIndex(int32_t aZIndex) override; virtual void SetSizeMode(nsSizeMode aMode) override; @@ -351,6 +352,9 @@ public: #endif virtual void GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) override; + NS_IMETHOD SetNonClientMargins(LayoutDeviceIntMargin& aMargins) override; + void SetDrawsInTitlebar(bool aState) override; + // HiDPI scale conversion gint GdkScaleFactor(); @@ -367,6 +371,9 @@ public: LayoutDeviceIntRect GdkRectToDevicePixels(GdkRectangle rect); virtual bool WidgetTypeSupportsAcceleration() override; + + // Decorations + bool IsClientDecorated() const; protected: virtual ~nsWindow(); @@ -384,6 +391,16 @@ protected: virtual void RegisterTouchWindow() override; + int GetClientResizerSize(); + + // Informs the window manager about the size of the shadows surrounding + // a client-side decorated window. + void UpdateClientDecorations(); + + // Returns true if the given point (in device pixels) is within a resizer + // region of the window. Only used when drawing decorations client side. + bool CheckResizerEdge(LayoutDeviceIntPoint aPoint, GdkWindowEdge& aOutEdge); + nsCOMPtr mParent; // Is this a toplevel window? bool mIsTopLevel; @@ -432,12 +449,12 @@ private: gint* aRootX, gint* aRootY); void ClearCachedResources(); nsIWidgetListener* GetListener(); - bool IsComposited() const; - + void ApplyCSDClipping(); GtkWidget *mShell; MozContainer *mContainer; GdkWindow *mGdkWindow; + bool mIsCSDEnabled; PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate; @@ -536,6 +553,10 @@ private: // leaving fullscreen nsSizeMode mLastSizeMode; + // If true, draw our own window decorations (where supported). + bool mDrawWindowDecoration; + GtkBorder mDecorationSize; + static bool DragInProgress(void); void DispatchMissedButtonReleases(GdkEventCrossing *aGdkEvent); @@ -567,6 +588,17 @@ private: RefPtr mIMContext; mozilla::UniquePtr mCurrentTimeGetter; + typedef enum { CSD_SUPPORT_FULL, // CSD including shadows + CSD_SUPPORT_FLAT, // CSD without shadows + CSD_SUPPORT_NONE, // WM does not support CSD at all + CSD_SUPPORT_UNKNOWN + } CSDSupportLevel; + /** + * Get the support of Client Side Decoration by checking + * the XDG_CURRENT_DESKTOP environment variable. + */ + CSDSupportLevel GetCSDSupportLevel(); + CSDSupportLevel mCSDSupportLevel; }; #endif /* __nsWindow_h__ */ diff -up firefox-57.0/widget/gtk/WidgetStyleCache.cpp.1399611 firefox-57.0/widget/gtk/WidgetStyleCache.cpp --- firefox-57.0/widget/gtk/WidgetStyleCache.cpp.1399611 2017-11-02 17:16:34.000000000 +0100 +++ firefox-57.0/widget/gtk/WidgetStyleCache.cpp 2017-11-22 12:17:33.733682473 +0100 @@ -26,10 +26,14 @@ static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType); static GtkWidget* -CreateWindowWidget() +CreateWindowWidget(WidgetNodeType type) { GtkWidget *widget = gtk_window_new(GTK_WINDOW_POPUP); gtk_widget_set_name(widget, "MozillaGtkWidget"); + if (type == MOZ_GTK_WINDOW_CSD) { + GtkStyleContext* style = gtk_widget_get_style_context(widget); + gtk_style_context_add_class(style, "csd"); + } return widget; } @@ -101,7 +105,7 @@ CreateTooltipWidget() { MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr, "CreateTooltipWidget should be used for Gtk < 3.20 only."); - GtkWidget* widget = CreateWindowWidget(); + GtkWidget* widget = CreateWindowWidget(MOZ_GTK_WINDOW); GtkStyleContext* style = gtk_widget_get_style_context(widget); gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOOLTIP); return widget; @@ -529,11 +533,82 @@ CreateNotebookWidget() } static GtkWidget* +CreateHeaderBar(bool aMaximized) +{ + MOZ_ASSERT(gtk_check_version(3, 10, 0) == nullptr, + "GtkHeaderBar is only available on GTK 3.10+."); + if (gtk_check_version(3, 10, 0) != nullptr) + return nullptr; + + static auto sGtkHeaderBarNewPtr = (GtkWidget* (*)()) + dlsym(RTLD_DEFAULT, "gtk_header_bar_new"); + static const char* MOZ_GTK_STYLE_CLASS_TITLEBAR = "titlebar"; + + GtkWidget* headerbar = sGtkHeaderBarNewPtr(); + if (aMaximized) { + GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP); + gtk_widget_set_name(window, "MozillaMaximizedGtkWidget"); + GtkStyleContext* style = gtk_widget_get_style_context(window); + gtk_style_context_add_class(style, "maximized"); + GtkWidget *fixed = gtk_fixed_new(); + gtk_container_add(GTK_CONTAINER(window), fixed); + gtk_container_add(GTK_CONTAINER(fixed), headerbar); + // Save the window container so we don't leak it. + sWidgetStorage[MOZ_GTK_WINDOW_MAXIMIZED] = window; + } else { + AddToWindowContainer(headerbar); + } + + // Emulate what create_titlebar() at gtkwindow.c does. + GtkStyleContext* style = gtk_widget_get_style_context(headerbar); + gtk_style_context_add_class(style, MOZ_GTK_STYLE_CLASS_TITLEBAR); + gtk_style_context_add_class(style, "default-decoration"); + + return headerbar; +} + +// TODO - Also return style for buttons located at Maximized toolbar. +static GtkWidget* +CreateHeaderBarButton(WidgetNodeType aWidgetType) +{ + MOZ_ASSERT(gtk_check_version(3, 10, 0) == nullptr, + "GtkHeaderBar is only available on GTK 3.10+."); + + if (gtk_check_version(3, 10, 0) != nullptr) + return nullptr; + + static const char* MOZ_GTK_STYLE_CLASS_TITLEBUTTON = "titlebutton"; + + GtkWidget* widget = gtk_button_new(); + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_HEADER_BAR)), widget); + + GtkStyleContext* style = gtk_widget_get_style_context(widget); + gtk_style_context_add_class(style, MOZ_GTK_STYLE_CLASS_TITLEBUTTON); + + switch (aWidgetType) { + case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE: + gtk_style_context_add_class(style, "close"); + break; + case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE: + gtk_style_context_add_class(style, "minimize"); + break; + case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE: + gtk_style_context_add_class(style, "maximize"); + break; + default: + break; + } + + return widget; +} + +static GtkWidget* CreateWidget(WidgetNodeType aWidgetType) { switch (aWidgetType) { case MOZ_GTK_WINDOW: - return CreateWindowWidget(); + case MOZ_GTK_WINDOW_CSD: + return CreateWindowWidget(aWidgetType); case MOZ_GTK_WINDOW_CONTAINER: return CreateWindowContainerWidget(); case MOZ_GTK_CHECKBUTTON_CONTAINER: @@ -610,6 +685,13 @@ CreateWidget(WidgetNodeType aWidgetType) return CreateComboBoxEntryButtonWidget(); case MOZ_GTK_COMBOBOX_ENTRY_ARROW: return CreateComboBoxEntryArrowWidget(); + case MOZ_GTK_HEADER_BAR: + case MOZ_GTK_HEADER_BAR_MAXIMIZED: + return CreateHeaderBar(aWidgetType == MOZ_GTK_HEADER_BAR_MAXIMIZED); + case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE: + case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE: + case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE: + return CreateHeaderBarButton(aWidgetType); default: /* Not implemented */ return nullptr; @@ -1049,6 +1131,10 @@ GetCssNodeStyleInternal(WidgetNodeType a GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK); return gtk_widget_get_style_context(widget); } + case MOZ_GTK_WINDOW_DECORATION: + style = CreateChildCSSNode("decoration", + MOZ_GTK_WINDOW_CSD); + break; default: return GetWidgetRootStyle(aNodeType); } @@ -1214,6 +1300,8 @@ ResetWidgetCache(void) /* This will destroy all of our widgets */ if (sWidgetStorage[MOZ_GTK_WINDOW]) gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]); + if (sWidgetStorage[MOZ_GTK_WINDOW_MAXIMIZED]) + gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW_MAXIMIZED]); /* Clear already freed arrays */ mozilla::PodArrayZero(sWidgetStorage); diff -up firefox-57.0/widget/LookAndFeel.h.1399611 firefox-57.0/widget/LookAndFeel.h --- firefox-57.0/widget/LookAndFeel.h.1399611 2017-11-02 17:16:34.000000000 +0100 +++ firefox-57.0/widget/LookAndFeel.h 2017-11-22 12:17:33.733682473 +0100 @@ -405,6 +405,30 @@ public: eIntID_PhysicalHomeButton, /* + * A boolean value indicating whether client-side decorations are + * supported by the user's GTK version. + */ + eIntID_GTKCSDAvailable, + + /* + * A boolean value indicating whether client-side decorations should + * contain a minimize button. + */ + eIntID_GTKCSDMinimizeButton, + + /* + * A boolean value indicating whether client-side decorations should + * contain a maximize button. + */ + eIntID_GTKCSDMaximizeButton, + + /* + * A boolean value indicating whether client-side decorations should + * contain a close button. + */ + eIntID_GTKCSDCloseButton, + + /* * Controls whether overlay scrollbars display when the user moves * the mouse in a scrollable frame. */