diff --git a/configure.ac b/configure.ac index 87278026f..e3da44de3 100644 --- a/configure.ac +++ b/configure.ac @@ -38,7 +38,7 @@ fi AM_CONDITIONAL(BUILD_RECORDER, $build_recorder) -PKG_CHECK_MODULES(MUTTER_PLUGIN, gtk+-2.0 dbus-glib-1 metacity-plugins gjs-gi-1.0 libgnome-menu $recorder_modules) +PKG_CHECK_MODULES(MUTTER_PLUGIN, gtk+-2.0 dbus-glib-1 metacity-plugins gjs-gi-1.0 libgnome-menu gdk-x11-2.0 clutter-x11-0.9 clutter-glx-0.9 $recorder_modules) PKG_CHECK_MODULES(TIDY, clutter-0.9) PKG_CHECK_MODULES(BIG, clutter-0.9 gtk+-2.0 librsvg-2.0) PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0) diff --git a/js/ui/overlay.js b/js/ui/overlay.js index 6c1793df0..47097ddbd 100644 --- a/js/ui/overlay.js +++ b/js/ui/overlay.js @@ -18,8 +18,12 @@ const Panel = imports.ui.panel; const Tweener = imports.ui.tweener; const Workspaces = imports.ui.workspaces; -const OVERLAY_BACKGROUND_COLOR = new Clutter.Color(); -OVERLAY_BACKGROUND_COLOR.from_pixel(0x000000ff); +const ROOT_OVERLAY_COLOR = new Clutter.Color(); +ROOT_OVERLAY_COLOR.from_pixel(0x000000bb); + +// The factor to scale the overlay wallpaper with. This should not be less +// than 3/2, because the rule of thirds is used for positioning (see below). +const BACKGROUND_SCALE = 2; const LABEL_HEIGHT = 16; // We use SIDESHOW_PAD for the padding on the left side of the sideshow and as a gap @@ -138,19 +142,23 @@ Sideshow.prototype = { let asideXFactor = wideScreen ? WORKSPACES_X_FACTOR_ASIDE_MODE_WIDE_SCREEN : WORKSPACES_X_FACTOR_ASIDE_MODE_REGULAR_SCREEN; this._expandedSideshowColumns = wideScreen ? EXPANDED_SIDESHOW_COLUMNS_WIDE_SCREEN : EXPANDED_SIDESHOW_COLUMNS_REGULAR_SCREEN; - this._width = displayGridColumnWidth - SIDESHOW_PAD; + this._width = displayGridColumnWidth; + this._displayWidth = this._width - SIDESHOW_PAD; + + this._expandedWidth = displayGridColumnWidth * asideXFactor; // this figures out the additional width we can give to the display in the 'More' mode, // assuming that we want to keep the columns the same width in both modes - this._additionalWidth = ((this._width + SIDESHOW_PAD) / SIDESHOW_COLUMNS) * + this._additionalWidth = (this._width / SIDESHOW_COLUMNS) * (this._expandedSideshowColumns - SIDESHOW_COLUMNS); - let previewWidth = displayGridColumnWidth * asideXFactor - this._width - - this._additionalWidth - SIDESHOW_SECTION_SPACING * 2; + let previewWidth = this._expandedWidth - this._width - + this._additionalWidth - SIDESHOW_SECTION_SPACING; let global = Shell.Global.get(); this.actor = new Clutter.Group(); - this._searchEntry = new SearchEntry(this._width); + this.actor.height = global.screen_height; + this._searchEntry = new SearchEntry(this._displayWidth); this.actor.add_actor(this._searchEntry.actor); this._searchEntry.actor.set_position(SIDESHOW_PAD, Panel.PANEL_HEIGHT + SIDESHOW_PAD); @@ -246,8 +254,7 @@ Sideshow.prototype = { return false; }); - this._appsSection = new Big.Box({ background_color: OVERLAY_BACKGROUND_COLOR, - x: SIDESHOW_PAD, + this._appsSection = new Big.Box({ x: SIDESHOW_PAD, y: this._searchEntry.actor.y + this._searchEntry.actor.height, padding_top: SIDESHOW_SECTION_PADDING_TOP, spacing: SIDESHOW_SECTION_SPACING}); @@ -265,7 +272,7 @@ Sideshow.prototype = { this._appsContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL }); this._appsSection.append(this._appsContent, Big.BoxPackFlags.EXPAND); - this._appDisplay = new AppDisplay.AppDisplay(this._width, this._itemDisplayHeight / 2, SIDESHOW_COLUMNS, SIDESHOW_PAD); + this._appDisplay = new AppDisplay.AppDisplay(this._displayWidth, this._itemDisplayHeight / 2, SIDESHOW_COLUMNS, SIDESHOW_PAD); let sideArea = this._appDisplay.getSideArea(); sideArea.hide(); this._appsContent.append(sideArea, Big.BoxPackFlags.NONE); @@ -286,8 +293,7 @@ Sideshow.prototype = { this._appsDisplayControlBox = new Big.Box({x_align: Big.BoxAlignment.CENTER}); this._appsDisplayControlBox.append(this._appDisplay.displayControl, Big.BoxPackFlags.NONE); - this._docsSection = new Big.Box({ background_color: OVERLAY_BACKGROUND_COLOR, - x: SIDESHOW_PAD, + this._docsSection = new Big.Box({ x: SIDESHOW_PAD, y: this._appsSection.y + this._appsSection.height, padding_top: SIDESHOW_SECTION_PADDING_TOP, spacing: SIDESHOW_SECTION_SPACING}); @@ -298,7 +304,7 @@ Sideshow.prototype = { height: LABEL_HEIGHT}); this._docsSection.append(this._docsText, Big.BoxPackFlags.EXPAND); - this._docDisplay = new DocDisplay.DocDisplay(this._width, this._itemDisplayHeight - this._appsContent.height, SIDESHOW_COLUMNS, SIDESHOW_PAD); + this._docDisplay = new DocDisplay.DocDisplay(this._displayWidth, this._itemDisplayHeight - this._appsContent.height, SIDESHOW_COLUMNS, SIDESHOW_PAD); this._docsSection.append(this._docDisplay.actor, Big.BoxPackFlags.EXPAND); let moreDocsBox = new Big.Box({x_align: Big.BoxAlignment.END}); @@ -316,7 +322,7 @@ Sideshow.prototype = { this._docsDisplayControlBox = new Big.Box({x_align: Big.BoxAlignment.CENTER}); this._docsDisplayControlBox.append(this._docDisplay.displayControl, Big.BoxPackFlags.NONE); - this._details = new Big.Box({ x: SIDESHOW_PAD + this._width + this._additionalWidth + SIDESHOW_SECTION_SPACING, + this._details = new Big.Box({ x: this._width + this._additionalWidth + SIDESHOW_SECTION_SPACING, y: Panel.PANEL_HEIGHT + SIDESHOW_PAD, width: previewWidth, height: global.screen_height - Panel.PANEL_HEIGHT - SIDESHOW_PAD - bottomHeight, @@ -449,8 +455,15 @@ Sideshow.prototype = { transition: "easeOutQuad", onComplete: this._onAppsSectionExpanded, onCompleteScope: this - }); - + }); + + this.actor.set_clip(0, 0, this.actor.width, this.actor.height); + Tweener.addTween(this.actor, + { clipWidthRight: this._expandedWidth, + time: ANIMATION_TIME, + transition: "easeOutQuad" + }); + this.emit('more-activated'); }, @@ -464,7 +477,7 @@ Sideshow.prototype = { this._moreAppsLink.actor.hide(); this._appsSection.set_clip(0, 0, this._appsSection.width, this._appsSection.height); - + this.actor.set_clip(0, 0, this.actor.width, this.actor.height); this._docDisplay.show(); // We need to be reducing the clip on the applications section so that the last application to @@ -483,7 +496,11 @@ Sideshow.prototype = { time: ANIMATION_TIME, transition: "easeOutQuad" }); - + Tweener.addTween(this.actor, + { clipWidthRight: this._width, + time: ANIMATION_TIME, + transition: "easeOutQuad" + }); this.emit('less-activated'); }, @@ -492,6 +509,7 @@ Sideshow.prototype = { _onAppsSectionExpanded: function() { this._appsSection.remove_clip(); this._docDisplay.hide(); + this.actor.remove_clip(); }, // Updates the applications section to contain fewer items. Selects the first item in the @@ -500,6 +518,7 @@ Sideshow.prototype = { // Removes the clip from the documents section, so that the clip does not limit the size of // the section if it is expanded later. _onAppsSectionReduced: function() { + this.actor.remove_clip(); if (this._moreAppsMode != STATE_PENDING_INACTIVE) return; this._moreAppsMode = STATE_INACTIVE; @@ -516,7 +535,7 @@ Sideshow.prototype = { _updateAppsSection: function() { if (this._moreAppsMode) { // Subtract one from columns since we are displaying menus - this._appDisplay.setExpanded(true, this._width, this._additionalWidth, + this._appDisplay.setExpanded(true, this._displayWidth, this._additionalWidth, this._itemDisplayHeight + SIDESHOW_SECTION_MISC_HEIGHT, this._expandedSideshowColumns - 1); this._moreAppsLink.setText("Less..."); @@ -524,7 +543,7 @@ Sideshow.prototype = { this.actor.add_actor(this._details); this._details.append(this._appDisplay.selectedItemDetails, Big.BoxPackFlags.NONE); } else { - this._appDisplay.setExpanded(false, this._width, 0, + this._appDisplay.setExpanded(false, this._displayWidth, 0, this._appsSectionDefaultHeight - SIDESHOW_SECTION_MISC_HEIGHT, SIDESHOW_COLUMNS); this._moreAppsLink.setText("More..."); @@ -550,6 +569,8 @@ Sideshow.prototype = { this._moreDocsLink.actor.hide(); this._docsSection.set_clip(0, 0, this._docsSection.width, this._docsSection.height); + this.actor.set_clip(0, 0, this.actor.width, this.actor.height); + // Move the selection to the docs section if it was in the apps section. this._appDisplay.unsetSelected(); if (!this._docDisplay.hasSelected()) @@ -574,7 +595,14 @@ Sideshow.prototype = { transition: "easeOutQuad", onComplete: this._onDocsSectionExpanded, onCompleteScope: this - }); + }); + + Tweener.addTween(this.actor, + { clipWidthRight: this._expandedWidth, + time: ANIMATION_TIME, + transition: "easeOutQuad" + }); + this.emit('more-activated'); }, @@ -589,7 +617,7 @@ Sideshow.prototype = { this._moreDocsLink.actor.hide(); this._docsSection.set_clip(0, 0, this._docsSection.width, this._docsSection.height); - + this.actor.set_clip(0, 0, this.actor.width, this.actor.height); this._appsContent.show(); Tweener.addTween(this._docsSection, @@ -606,6 +634,12 @@ Sideshow.prototype = { time: ANIMATION_TIME, transition: "easeOutQuad" }); + Tweener.addTween(this.actor, + { clipWidthRight: this._width, + time: ANIMATION_TIME, + transition: "easeOutQuad" + }); + this.emit('less-activated'); }, @@ -614,6 +648,7 @@ Sideshow.prototype = { _onDocsSectionExpanded: function() { this._docsSection.remove_clip(); this._appsContent.hide(); + this.actor.remove_clip(); }, // Updates the documents section to contain fewer items. Selects the first item in the @@ -622,6 +657,7 @@ Sideshow.prototype = { // Removes the clip from the applications section, so that the clip does not limit the size of // the section if it is expanded later. _onDocsSectionReduced: function() { + this.actor.remove_clip(); this._updateDocsSection(); if (!this._docDisplay.hasItems()) this._appDisplay.selectFirstItem(); @@ -634,7 +670,7 @@ Sideshow.prototype = { // changed, which is ensured by _setMoreDocsMode() and _unsetMoreDocsMode() functions. _updateDocsSection: function() { if (this._moreDocsMode) { - this._docDisplay.setExpanded(true, this._width, this._additionalWidth, + this._docDisplay.setExpanded(true, this._displayWidth, this._additionalWidth, this._itemDisplayHeight + SIDESHOW_SECTION_MISC_HEIGHT, this._expandedSideshowColumns); this._moreDocsLink.setText("Less..."); @@ -642,7 +678,7 @@ Sideshow.prototype = { this.actor.add_actor(this._details); this._details.append(this._docDisplay.selectedItemDetails, Big.BoxPackFlags.NONE); } else { - this._docDisplay.setExpanded(false, this._width, 0, + this._docDisplay.setExpanded(false, this._displayWidth, 0, this._docsSectionDefaultHeight - SIDESHOW_SECTION_MISC_HEIGHT, SIDESHOW_COLUMNS); this._moreDocsLink.setText("More..."); @@ -684,14 +720,25 @@ Overlay.prototype = { this.visible = false; this._hideInProgress = false; - let background = new Clutter.Rectangle({ color: OVERLAY_BACKGROUND_COLOR, - reactive: true, - x: 0, - y: 0, - width: global.screen_width, - height: global.screen_width }); + // A scaled root pixmap actor is used as a background. It is zoomed in + // to the lower right intersection of the lines that divide the image + // evenly in a 3x3 grid. This is based on the rule of thirds, a + // compositional rule of thumb in visual arts. The choice for the + // lower right point is based on a quick survey of GNOME wallpapers. + let background = global.create_root_pixmap_actor(); + background.width = global.screen_width * BACKGROUND_SCALE; + background.height = global.screen_height * BACKGROUND_SCALE; + background.x = -global.screen_width * (4 * BACKGROUND_SCALE - 3) / 6; + background.y = -global.screen_height * (4 * BACKGROUND_SCALE - 3) / 6; this._group.add_actor(background); + // Draw a semitransparent rectangle over the background for readability. + let backOver = new Clutter.Rectangle({ color: ROOT_OVERLAY_COLOR, + width: global.screen_width, + height: global.screen_height - Panel.PANEL_HEIGHT, + y: Panel.PANEL_HEIGHT }); + this._group.add_actor(backOver); + this._group.hide(); global.overlay_group.add_actor(this._group); @@ -699,7 +746,6 @@ Overlay.prototype = { this._sideshow = new Sideshow(); this._group.add_actor(this._sideshow.actor); this._workspaces = null; - this._workspacesBackground = null; this._sideshow.connect('activated', function(sideshow) { // TODO - have some sort of animation/effect while // transitioning to the new app. We definitely need @@ -713,17 +759,6 @@ Overlay.prototype = { let workspacesX = displayGridColumnWidth * asideXFactor + WORKSPACE_GRID_PADDING; me._workspaces.addButton.hide(); me._workspaces.updatePosition(workspacesX, null); - // lower the sideshow below the workspaces background, so that the workspaces - // background covers the parts of the sideshow that are gradually being - // revealed from underneath it - me._sideshow.actor.lower(me._workspacesBackground); - Tweener.addTween(me._workspacesBackground, - { x: displayGridColumnWidth * asideXFactor, - time: ANIMATION_TIME, - transition: "easeOutQuad", - onComplete: me._animationDone, - onCompleteScope: me - }); } }); this._sideshow.connect('less-activated', function(sideshow) { @@ -731,16 +766,6 @@ Overlay.prototype = { let workspacesX = displayGridColumnWidth + WORKSPACE_GRID_PADDING; me._workspaces.addButton.show(); me._workspaces.updatePosition(workspacesX, null); - // lower the sideshow below the workspaces background, so that the workspaces - // background covers the parts of the sideshow as it slides in over them - me._sideshow.actor.lower(me._workspacesBackground); - Tweener.addTween(me._workspacesBackground, - { x: displayGridColumnWidth, - time: ANIMATION_TIME, - transition: "easeOutQuad", - onComplete: me._animationDone, - onCompleteScope: me - }); } }); }, @@ -790,18 +815,6 @@ Overlay.prototype = { let addButtonX = workspacesX + workspacesWidth - addButtonSize; let addButtonY = screenHeight - Math.floor(displayGridRowHeight * 4/5); - // We use the workspaces background to have it fill the full height of the overlay when we are sliding - // the workspaces out to uncover the expanded items display and also when we are sliding the - // workspaces back in to gradually cover the expanded items display. If we don't have such background, - // we get a few items above or below the workspaces display that disappear or appear abruptly. - this._workspacesBackground = new Clutter.Rectangle({ color: OVERLAY_BACKGROUND_COLOR, - reactive: false, - x: displayGridColumnWidth, - y: Panel.PANEL_HEIGHT, - width: displayGridColumnWidth * columnsUsed, - height: global.screen_height - Panel.PANEL_HEIGHT }); - this._group.add_actor(this._workspacesBackground); - this._workspaces = new Workspaces.Workspaces(workspacesWidth, workspacesHeight, workspacesX, workspacesY, addButtonSize, addButtonX, addButtonY); this._group.add_actor(this._workspaces.actor); @@ -816,11 +829,26 @@ Overlay.prototype = { global.window_group.hide(); this._group.show(); - // Dummy tween, just waiting for the workspace animation - Tweener.addTween(this, - { time: ANIMATION_TIME, - onComplete: this._animationDone, + // Try to make the menu not too visible behind the empty space between + // the workspace previews by sliding in its clipping rectangle. + // We want to finish drawing the sideshow just before the top workspace fully + // slides in on the top. Which means that we have more time to wait before + // drawing the sideshow if the active workspace is displayed on the bottom of + // the workspaces grid, and almost no time to wait if it is displayed in the top + // row of the workspaces grid. The calculations used below try to roughly + // capture the animation ratio for when workspaces are covering the top of the overlay + // vs. when workspaces are already below the top of the overlay, and apply it + // to clipping the sideshow. The clipping is removed in this._showDone(). + this._sideshow.actor.set_clip(0, 0, + this._workspaces.getFullSizeX(), + this._sideshow.actor.height); + Tweener.addTween(this._sideshow.actor, + { clipWidthRight: this._sideshow._width + WORKSPACE_GRID_PADDING + this._workspaces.getWidthToTopActiveWorkspace(), + time: ANIMATION_TIME, + transition: "easeOutQuad", + onComplete: this._showDone, onCompleteScope: this + }); }, @@ -828,14 +856,28 @@ Overlay.prototype = { if (!this.visible || this._hideInProgress) return; + let global = Shell.Global.get(); + this._hideInProgress = true; // lower the sideshow, so that workspaces display is on top and covers the sideshow while it is sliding out - this._sideshow.actor.lower(this._workspacesBackground); + this._sideshow.actor.lower(this._workspaces.actor); this._workspaces.hide(); - // Dummy tween, just waiting for the workspace animation - Tweener.addTween(this, - { time: ANIMATION_TIME, + // Try to make the menu not too visible behind the empty space between + // the workspace previews by sliding in its clipping rectangle. + // The logic used is the same as described in this.show(). If the active workspace + // is displayed in the top row, than almost full animation time is needed for it + // to reach the top of the overlay and cover the sideshow fully, while if the + // active workspace is in the lower row, than the top left workspace reaches the + // top of the overlay sooner as it is moving out of the way. + // The clipping is removed in this._hideDone(). + this._sideshow.actor.set_clip(0, 0, + this._sideshow.actor.width + WORKSPACE_GRID_PADDING + this._workspaces.getWidthToTopActiveWorkspace(), + this._sideshow.actor.height); + Tweener.addTween(this._sideshow.actor, + { clipWidthRight: this._workspaces.getFullSizeX() + this._workspaces.getWidthToTopActiveWorkspace() - global.screen_width, + time: ANIMATION_TIME, + transition: "easeOutQuad", onComplete: this._hideDone, onCompleteScope: this }); @@ -844,21 +886,20 @@ Overlay.prototype = { //// Private methods //// // Raises the sideshow to the top, so that we can tell if the pointer is above one of its items. - // We need to do this every time animation of the workspaces is done bacause the workspaces actor - // currently covers the whole screen, regardless of where the workspaces are actually displayed. - // On the other hand, we need the workspaces to be on top when they are sliding in, out, - // and to the side because we want them to cover the sideshow as they do that. + // We need to do this once the workspaces are shown because the workspaces actor currently covers + // the whole screen, regardless of where the workspaces are actually displayed. // // Once we rework the workspaces actor to only cover the area it actually needs, we can // remove this workaround. Also http://bugzilla.openedhand.com/show_bug.cgi?id=1513 requests being // able to pick only a reactive actor at a certain position, rather than any actor. Being able // to do that would allow us to not have to raise the sideshow. - _animationDone: function() { + _showDone: function() { if (this._hideInProgress) return; - this._sideshow.actor.raise_top(); - }, + this._sideshow.actor.raise_top(); + this._sideshow.actor.remove_clip(); + }, _hideDone: function() { let global = Shell.Global.get(); @@ -868,9 +909,7 @@ Overlay.prototype = { this._workspaces.destroy(); this._workspaces = null; - this._workspacesBackground.destroy(); - this._workspacesBackground = null; - + this._sideshow.actor.remove_clip(); this._sideshow.hide(); this._group.hide(); @@ -904,3 +943,14 @@ function _clipHeightTopGet(actor) { function _clipHeightTopSet(actor, clipHeight) { actor.set_clip(0, actor.height - clipHeight, actor.width, clipHeight); } + +Tweener.registerSpecialProperty("clipWidthRight", _clipWidthRightGet, _clipWidthRightSet); + +function _clipWidthRightGet(actor) { + let [xOffset, yOffset, clipWidth, clipHeight] = actor.get_clip(); + return clipWidth; +} + +function _clipWidthRightSet(actor, clipWidth) { + actor.set_clip(0, 0, clipWidth, actor.height); +} diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index 37a9c1d2e..cb560a87f 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -800,26 +800,6 @@ Workspaces.prototype = { activeWorkspace.actor.raise_top(); this._positionWorkspaces(global, activeWorkspace); - // Create a backdrop rectangle, so that you don't see the - // other parts of the overlay (eg, sidebar) through the gaps - // between the workspaces when they're zooming in/out - this._backdrop = new Clutter.Rectangle({ color: Overlay.OVERLAY_BACKGROUND_COLOR, - x: this._backdropX, - y: this._backdropY, - width: this._backdropWidth, - height: this._backdropHeight - }); - this.actor.add_actor(this._backdrop); - this._backdrop.lower_bottom(); - Tweener.addTween(this._backdrop, - { x: this._x, - y: this._y, - width: this._width, - height: this._height, - time: Overlay.ANIMATION_TIME, - transition: "easeOutQuad" - }); - // Create (+) button buttonSize = addButtonSize; this.addButton = new Clutter.Texture({ x: addButtonX, @@ -870,15 +850,6 @@ Workspaces.prototype = { for (let w = 0; w < this._workspaces.length; w++) this._workspaces[w].zoomFromOverlay(); - - Tweener.addTween(this._backdrop, - { x: this._backdropX, - y: this._backdropY, - width: this._backdropWidth, - height: this._backdropHeight, - time: Overlay.ANIMATION_TIME, - transition: "easeOutQuad" - }); }, destroy : function() { @@ -890,25 +861,36 @@ Workspaces.prototype = { this.actor.destroy(); this.actor = null; - this._backdrop = null; global.screen.disconnect(this._nWorkspacesNotifyId); global.window_manager.disconnect(this._switchWorkspaceNotifyId); }, + getFullSizeX : function() { + return this._workspaces[0].fullSizeX; + }, + + // If j-th workspace in the i-th row is active, returns the full width + // of j workspaces including empty space if i = 1, or the width of one + // workspace. + // Used in overlay.js to determine when it is ok to remove the sideshow + // during animations for entering and leaving the overlay. + getWidthToTopActiveWorkspace : function() { + let global = Shell.Global.get(); + let activeWorkspaceIndex = global.screen.get_active_workspace_index(); + let activeWorkspace = this._workspaces[activeWorkspaceIndex]; + + if (activeWorkspace.gridRow == 0) + return (activeWorkspace.gridCol + 1) * global.screen_width + activeWorkspace.gridCol * GRID_SPACING; + else + return global.screen_width; + }, + // Updates the workspaces display based on the current dimensions and position. _updateInOverlay : function() { let global = Shell.Global.get(); this._positionWorkspaces(global); - Tweener.addTween(this._backdrop, - { x: this._x, - y: this._y, - width: this._width, - height: this._height, - time: Overlay.ANIMATION_TIME, - transition: "easeOutQuad" - }); // Position/scale the desktop windows and their children for (let w = 0; w < this._workspaces.length; w++) @@ -973,12 +955,6 @@ Workspaces.prototype = { workspace.fullSizeX = (workspace.gridCol - activeWorkspace.gridCol) * (global.screen_width + GRID_SPACING); workspace.fullSizeY = (workspace.gridRow - activeWorkspace.gridRow) * (global.screen_height + GRID_SPACING); } - - // And the backdrop - this._backdropX = this._workspaces[0].fullSizeX; - this._backdropY = this._workspaces[0].fullSizeY; - this._backdropWidth = gridWidth * (global.screen_width + GRID_SPACING) - GRID_SPACING; - this._backdropHeight = gridHeight * (global.screen_height + GRID_SPACING) - GRID_SPACING; }, _workspacesChanged : function() { @@ -1036,7 +1012,6 @@ Workspaces.prototype = { // Slide old workspaces out for (let w = 0; w < lostWorkspaces.length; w++) { let workspace = lostWorkspaces[w]; - workspace.actor.raise(this._backdrop); workspace.slideOut(function () { workspace.destroy(); }); } diff --git a/src/shell-global.c b/src/shell-global.c index 6bbe40c30..2a2b7477f 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -4,7 +4,9 @@ #include "shell-wm.h" #include "display.h" +#include #include +#include #include #include #include @@ -37,6 +39,9 @@ struct _ShellGlobal { ShellWM *wm; gboolean keyboard_grabbed; const char *imagedir; + + /* Displays the root window; see shell_global_create_root_pixmap_actor() */ + ClutterGLXTexturePixmap *root_pixmap; }; enum { @@ -153,6 +158,8 @@ shell_global_init (ShellGlobal *global) global->grab_notifier = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); g_signal_connect (global->grab_notifier, "grab-notify", G_CALLBACK (grab_notify), global); global->grab_active = FALSE; + + global->root_pixmap = NULL; } static void @@ -728,3 +735,129 @@ shell_global_create_vertical_gradient (ClutterColor *top, return texture; } + +/* + * Updates the global->root_pixmap actor with the root window's pixmap or fails + * with a warning. + */ +static void +update_root_window_pixmap (ShellGlobal *global) +{ + Atom type; + int format; + gulong nitems; + gulong bytes_after; + guchar *data; + + if (!XGetWindowProperty (gdk_x11_get_default_xdisplay (), + gdk_x11_get_default_root_xwindow (), + gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"), + 0, LONG_MAX, + False, + AnyPropertyType, + &type, &format, &nitems, &bytes_after, &data) && + type != None) + { + /* Got a property. */ + if (type == XA_PIXMAP && format == 32 && nitems == 1) + { + /* Was what we expected. */ + clutter_x11_texture_pixmap_set_pixmap (CLUTTER_X11_TEXTURE_PIXMAP (global->root_pixmap), + *(Pixmap *)data); + } + else + { + g_warning ("Could not get the root window pixmap"); + } + + XFree(data); + } +} + +/* + * Called when the X server emits a root window change event. If the event is + * about a new pixmap, update the global->root_pixmap actor. + */ +static GdkFilterReturn +root_window_filter (GdkXEvent *native, GdkEvent *event, gpointer data) +{ + XEvent *xevent = (XEvent *)native; + + if ((xevent->type == PropertyNotify) && + (xevent->xproperty.window == gdk_x11_get_default_root_xwindow ()) && + (xevent->xproperty.atom == gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"))) + update_root_window_pixmap (SHELL_GLOBAL (data)); + + return GDK_FILTER_CONTINUE; +} + +/* + * Called when the root window pixmap actor is destroyed. + */ +static void +root_pixmap_destroy (GObject *sender, gpointer data) +{ + ShellGlobal *global = SHELL_GLOBAL (data); + + gdk_window_remove_filter (gdk_get_default_root_window (), + root_window_filter, global); + global->root_pixmap = NULL; +} + +/** + * shell_global_create_root_pixmap_actor: + * @global: a #ShellGlobal + * + * Creates an actor showing the root window pixmap. + * + * Return value: (transfer none): a #ClutterActor with the root window pixmap. + * The actor is floating, hence (transfer none). + */ +ClutterActor * +shell_global_create_root_pixmap_actor (ShellGlobal *global) +{ + GdkWindow *window; + GdkEventMask events; + gboolean created_new_pixmap = FALSE; + ClutterActor *clone; + + /* The actor created is actually a ClutterClone of global->root_pixmap. */ + + if (global->root_pixmap == NULL) + { + global->root_pixmap = CLUTTER_GLX_TEXTURE_PIXMAP (clutter_glx_texture_pixmap_new ()); + + /* The low and medium quality filters give nearest-neighbor resizing. */ + clutter_texture_set_filter_quality (CLUTTER_TEXTURE (global->root_pixmap), + CLUTTER_TEXTURE_QUALITY_HIGH); + + /* The pixmap actor is only referenced by its clones. */ + g_object_ref_sink (global->root_pixmap); + + g_signal_connect (G_OBJECT (global->root_pixmap), "destroy", + G_CALLBACK (root_pixmap_destroy), global); + + /* Watch the root window for changes. */ + window = gdk_get_default_root_window (); + events = gdk_window_get_events (window); + events |= GDK_PROPERTY_CHANGE_MASK; + gdk_window_set_events (window, events); + /* Metacity handles some root window property updates in its global + * event filter, though not this one. For all root window property + * updates, the global filter returns GDK_FILTER_CONTINUE, so our + * window specific filter will be called. + */ + gdk_window_add_filter (window, root_window_filter, global); + + update_root_window_pixmap (global); + + created_new_pixmap = TRUE; + } + + clone = clutter_clone_new (CLUTTER_ACTOR (global->root_pixmap)); + + if (created_new_pixmap) + g_object_unref(global->root_pixmap); + + return clone; +} diff --git a/src/shell-global.h b/src/shell-global.h index 70cc962f3..9ce837f7e 100644 --- a/src/shell-global.h +++ b/src/shell-global.h @@ -69,6 +69,8 @@ void shell_global_reexec_self (ShellGlobal *global); ClutterCairoTexture *shell_global_create_vertical_gradient (ClutterColor *top, ClutterColor *bottom); +ClutterActor *shell_global_create_root_pixmap_actor (ShellGlobal *global); + G_END_DECLS #endif /* __SHELL_GLOBAL_H__ */