diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js
index 39e2cd4..432a67e 100644
--- a/js/ui/workspaces.js
+++ b/js/ui/workspaces.js
@@ -28,6 +28,11 @@ WINDOWCLONE_TITLE_COLOR.from_pixel(0xffffffff);
 const FRAME_COLOR = new Clutter.Color();
 FRAME_COLOR.from_pixel(0xffffffff);
 
+const SCROLL_SCALE_AMOUNT = 0.2;
+
+const OVERLAY_COLOR = new Clutter.Color();
+OVERLAY_COLOR.from_pixel(0x000000AA);
+
 // Define a layout scheme for small window counts. For larger
 // counts we fall back to an algorithm. We need more schemes here
 // unless we have a really good algorithm.
@@ -69,10 +74,16 @@ WindowClone.prototype = {
         this.actor.connect('button-release-event',
                            Lang.bind(this, this._onButtonRelease));
 
+        this.actor.connect('scroll-event',
+                           Lang.bind(this, this._onScroll));
+
         this.actor.connect('enter-event',
                            Lang.bind(this, this._onEnter));
         this.actor.connect('leave-event',
                            Lang.bind(this, this._onLeave));
+
+        Main.overview.connect('hidden', Lang.bind(this, function(a, event) { if (this.actor.step > 0) this._zoomEnd(this.actor, event); } ));
+
         this._havePointer = false;
 
         this._draggable = DND.makeDraggable(this.actor);
@@ -106,16 +117,86 @@ WindowClone.prototype = {
             return;
 
         this._havePointer = false;
+        this._updateTitle();
+    },
 
-        if (Tweener.isTweening(this.actor))
-            return;
+    _onScroll : function (actor, event) {
+        let direction = event.get_scroll_direction();
+        if (direction == Clutter.ScrollDirection.UP) {
+            if (actor.step == undefined)
+                this._zoomStart(actor, event);
+            if (actor.step < 1.0) {
+                actor.step += SCROLL_SCALE_AMOUNT;
+                this._zoomUpdate(actor);
+            }
+        } else if (direction == Clutter.ScrollDirection.DOWN) {
+            if (actor.step > 0.0) {
+                actor.step -= SCROLL_SCALE_AMOUNT;
+                actor.step = Math.max(0, actor.step);
+                this._zoomUpdate(actor);
+            }
+            if (actor.step <= 0.0)
+                this._zoomEnd(actor, event);
+        }
 
-    	actor.raise(this.stackAbove);
-        this._updateTitle();
+    },
+
+    _zoomUpdate : function (actor) {
+        [actor.x, actor.y, actor.scale_x, actor.scale_y] = [actor.globalOrig[i] + (actor.target[i] - actor.globalOrig[i]) * actor.step for (i in [0, 1, 2, 3])];
+        this._overlay.opacity = actor.step * 255;
+        this._showTitle();
+    },
+
+    _zoomStart : function (actor, event) {
+        let global = Shell.Global.get();
+
+        this._overlay = new Clutter.Rectangle({ reactive: true,
+                                                color: OVERLAY_COLOR,
+                                                border_width: 0,
+                                                x: 0, y: 0,
+                                                width: global.screen_width,
+                                                height: global.screen_height,
+                                                opacity: 0});
+
+        global.overlay_group.add_actor(this._overlay);
+
+        actor.localOrig = [actor.x, actor.y, actor.scale_x, actor.scale_y];
+        let parent = actor.origParent = actor.get_parent();
+        [actorX, actorY] = actor.get_transformed_position();
+        [actorW, actorH] = actor.get_transformed_size();
+
+        this._overlay.raise(Main.overview._dash.actor);
+        this._overlay.show();
+
+        actor.reparent(global.stage);
+        this._showTitle();
+        this._title.reparent(global.stage);
+        [actor.x, actor.y, actor.scale_x, actor.scale_y] = actor.globalOrig = [actorX, actorY, actorW / actor.natural_width, actorH / actor.natural_height];
+
+        actor.raise_top();
+        this._title.raise_top();
+        this._title.hide();
+        actor.target = [Math.floor((global.screen_width  - actor.natural_width ) / 2), Math.floor((global.screen_height - actor.natural_height) / 2), 1.0, 1.0];
+        actor.step = 0;
+    },
+
+    _zoomEnd : function (actor, event) {
+        actor.reparent(actor.origParent);
+        this._title.reparent(actor.origParent);
+        [actor.x, actor.y, actor.scale_x, actor.scale_y] = actor.localOrig;
+        this._adjustTitle();
+        actor.localOrig = undefined;
+        actor.globalOrig = undefined;
+        actor.target = undefined;
+        actor.step = undefined;
+        this._overlay.destroy();
     },
 
     _onButtonRelease : function (actor, event) {
-        this.emit('selected', event.get_time());
+        if (event.get_button() == 3 && actor.step != undefined)
+            this._zoomEnd(actor, event);
+        else
+            this.emit('selected', event.get_time());
     },
 
     _onDragBegin : function (draggable, time) {
@@ -151,7 +232,7 @@ WindowClone.prototype = {
 
     _createTitle : function () {
         let window = this.realWindow;
-        
+
         let box = new Big.Box({ background_color : WINDOWCLONE_BG_COLOR,
                                 y_align: Big.BoxAlignment.CENTER,
                                 corner_radius: 5,
@@ -168,7 +249,7 @@ WindowClone.prototype = {
         // Get and cache the expected width (just the icon), with spacing, plus title
         box.fullWidth = box.width;
         box.hide(); // Hidden by default, show on mouseover
-        this._title = box;        
+        this._title = box;
 
         // Make the title a sibling of the window
         this.actor.get_parent().add_actor(box);
@@ -177,7 +258,7 @@ WindowClone.prototype = {
     _adjustTitle : function () {
         let title = this._title;
         if (!title)
-            return;    
+            return;
 
         let [cloneScreenWidth, cloneScreenHeight] = this.actor.get_transformed_size();
         let [titleScreenWidth, titleScreenHeight] = title.get_transformed_size();
@@ -462,7 +543,7 @@ Workspace.prototype = {
             let scale = Math.min(desiredWidth / cloneRect.width, desiredHeight / cloneRect.height, 1.0 / this.scale);
 
             icon.hide();
-            Tweener.addTween(clone.actor, 
+            Tweener.addTween(clone.actor,
                              { x: xCenter - 0.5 * scale * cloneRect.width,
                                y: yCenter - 0.5 * scale * cloneRect.height,
                                scale_x: scale,
@@ -576,9 +657,9 @@ Workspace.prototype = {
                                         }));
             return;
         }
-        
+
         if (!this._isOverviewWindow(win))
-            return;        
+            return;
 
         let clone = this._addWindowClone(win);
 
@@ -654,7 +735,7 @@ Workspace.prototype = {
                              });
         }
 
-        this._visible = false;        
+        this._visible = false;
     },
 
     // Animates grid shrinking/expanding when a row or column
@@ -694,7 +775,7 @@ Workspace.prototype = {
 
         this._visible = true;
     },
-    
+
     // Animates the removal of a workspace
     slideOut : function(onComplete) {
         let global = Shell.Global.get();
@@ -722,7 +803,7 @@ Workspace.prototype = {
         // making its exit.
         this._desktop.reactive = false;
     },
-    
+
     destroy : function() {
         let global = Shell.Global.get();
 
@@ -748,7 +829,7 @@ Workspace.prototype = {
     // Tests if @win should be shown in the Overview
     _isOverviewWindow : function (win) {
         let wintype = win.get_window_type();
-        if (wintype == Meta.WindowType.DESKTOP || 
+        if (wintype == Meta.WindowType.DESKTOP ||
             wintype == Meta.WindowType.DOCK)
             return false;
         return !win.is_override_redirect();
@@ -878,14 +959,14 @@ Workspaces.prototype = {
         this.actor = new Clutter.Group();
 
         let screenHeight = global.screen_height;
-          
+
         this._width = width;
         this._height = height;
         this._x = x;
         this._y = y;
 
         this._workspaces = [];
-        
+
         let activeWorkspaceIndex = global.screen.get_active_workspace_index();
         let activeWorkspace;
 
@@ -900,7 +981,7 @@ Workspaces.prototype = {
         activeWorkspace.actor.raise_top();
         this._positionWorkspaces(global, activeWorkspace);
 
-        // Save the button size as a global variable so we can us it to create 
+        // Save the button size as a global variable so we can us it to create
         // matching button sizes for workspace remove buttons.
         buttonSize = addButtonSize;
 
diff --git a/src/shell-global.c b/src/shell-global.c
index 7dd5c44..01a201b 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -338,6 +338,18 @@ shell_get_event_related (ClutterEvent *event)
 }
 
 /**
+ * shell_get_scroll_event_direction:
+ *
+ * Return value: direction scroll wheel was turned for a scroll event.
+ */
+ClutterScrollDirection
+shell_get_scroll_event_direction (ClutterEvent *event)
+{
+  g_return_val_if_fail (event->type == CLUTTER_SCROLL, CLUTTER_SCROLL_UP);
+  return event->scroll.direction;
+}
+
+/**
  * shell_global_get:
  *
  * Gets the singleton global object that represents the desktop.
diff --git a/src/shell-global.h b/src/shell-global.h
index 52d818d..b265ff5 100644
--- a/src/shell-global.h
+++ b/src/shell-global.h
@@ -42,6 +42,8 @@ guint16 shell_get_button_event_click_count(ClutterEvent *event);
 
 ClutterActor *shell_get_event_related(ClutterEvent *event);
 
+ClutterScrollDirection shell_get_scroll_event_direction(ClutterEvent *event);
+
 ShellGlobal *shell_global_get (void);
 
 MetaScreen *shell_global_get_screen (ShellGlobal  *global);
