diff --git a/data/magCursors/default-cursor.xpm b/data/magCursors/default-cursor.xpm
new file mode 100644
index 0000000..045a719
--- /dev/null
+++ b/data/magCursors/default-cursor.xpm
@@ -0,0 +1,70 @@
+/* XPM */
+static char* Normal[] = {
+"64 64 3 1 16 6",
+"  c None",
+"! c #ffffff",
+"# c #000000",
+"                                                                ",
+"                                                                ",
+"                                                                ",
+"                                                                ",
+"                                                                ",
+"                                                                ",
+"                !!                                              ",
+"                !#!                                             ",
+"                !##!                                            ",
+"                !###!                                           ",
+"                !####!                                          ",
+"                !#####!                                         ",
+"                !######!                                        ",
+"                !#######!                                       ",
+"                !########!                                      ",
+"                !#########!                                     ",
+"                !##########!                                    ",
+"                !###########!                                   ",
+"                !############!                                  ",
+"                !#############!                                 ",
+"                !##############!                                ",
+"                !###############!                               ",
+"                !################!                              ",
+"                !#################!                             ",
+"                !##################!                            ",
+"                !###################!                           ",
+"                !####################!                          ",
+"                !#####################!                         ",
+"                !######################!                        ",
+"                !#######################!                       ",
+"                !########################!                      ",
+"                !#########################!                     ",
+"                !##########################!                    ",
+"                !###########################!                   ",
+"                !############################!                  ",
+"                !################!!!!!!!!!!!!!                  ",
+"                !################!                              ",
+"                !################!!                             ",
+"                !#################!                             ",
+"                !########!!#######!!                            ",
+"                !#######!  !#######!                            ",
+"                !######!   !#######!!                           ",
+"                !#####!     !#######!                           ",
+"                !####!      !#######!!                          ",
+"                !###!        !#######!                          ",
+"                !##!         !#######!!                         ",
+"                !#!           !#######!                         ",
+"                !!            !#######!!                        ",
+"                !              !#######!                        ",
+"                               !#######!!                       ",
+"                                !#######!                       ",
+"                                !#######!!                      ",
+"                                 !#######!                      ",
+"                                 !#######!                      ",
+"                                  !######!                      ",
+"                                   !!!!!!!                      ",
+"                                                                ",
+"                                                                ",
+"                                                                ",
+"                                                                ",
+"                                                                ",
+"                                                                ",
+"                                                                ",
+"                                                                "};
diff --git a/js/ui/altTab.js b/js/ui/altTab.js
index 600e1cc..475df0a 100644
--- a/js/ui/altTab.js
+++ b/js/ui/altTab.js
@@ -55,7 +55,7 @@ AltTabPopup.prototype = {
         this._currentWindows = [];
         this._thumbnailTimeoutId = 0;
 
-        global.stage.add_actor(this.actor);
+        global.uiGroup.add_actor (this.actor);
     },
 
     show : function(backward) {
diff --git a/js/ui/magPrefsUI.js b/js/ui/magPrefsUI.js
new file mode 100644
index 0000000..ee9a674
--- /dev/null
+++ b/js/ui/magPrefsUI.js
@@ -0,0 +1,86 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Gtk = imports.gi.Gtk;
+const Clutter = imports.gi.Clutter;
+const Shell = imports.gi.Shell;
+const Big = imports.gi.Big;
+
+function MagPrefsUI (options) {
+    this._init (options);
+}
+
+MagPrefsUI.prototype = {
+    _init: function (options) {
+        var _magnifier = ( options ? options.magnifier : null );
+
+        this.win = new Gtk.Window ({ type: Gtk.WindowType.TOPLEVEL });
+        this.win.set_border_width (10);
+        this.win.set_title ("Magnifier Preferences");
+
+        // A vertical box to contain the controls
+        let vBox = new Gtk.VButtonBox();
+
+        // Creates a toggle button to flip whether magnifier is active or not.
+        let togButton = new Gtk.ToggleButton({ label: "Show _Magnifier" });
+        togButton.set_use_underline (true);
+        togButton.set_active (_magnifier.isActive());
+        togButton.connect("clicked", function() {
+            _magnifier.setActive (!(_magnifier.isActive()));
+        });
+        vBox.add (togButton);
+
+        // A checkbox for overlay mode (magnified view overlays current moust
+        // position.
+        let overlayCheckbox = new Gtk.CheckButton ({ label: "Movable lens" });
+        overlayCheckbox.set_use_underline (true);
+        overlayCheckbox.set_sensitive (! _magnifier.isFullScreenMode());
+        overlayCheckbox.set_active (_magnifier.isOverlayMouse());
+        overlayCheckbox.connect ("clicked", function() {
+            _magnifier.setOverlayMouse (! _magnifier.isOverlayMouse());
+         });
+
+        // Creates a toggle button to flip full screen mode.
+        let fullScreenToggle = new Gtk.ToggleButton({ label: "Toggle _Full Screen Mode" });
+        fullScreenToggle.set_use_underline (true);
+        fullScreenToggle.set_active (_magnifier.isFullScreenMode());
+        fullScreenToggle.connect("clicked", function() {
+            if (_magnifier.isFullScreenMode()) {
+                _magnifier.resetFullScreenMode();
+                overlayCheckbox.set_sensitive (true);
+            }
+            else {
+                _magnifier.setFullScreenMode();
+                overlayCheckbox.set_active (false);
+                overlayCheckbox.set_sensitive (false);
+            }
+        });
+        vBox.add (fullScreenToggle);
+
+        // A slider for adjusting the mag factor.
+        let slider = new Gtk.HScale();
+        slider.set_range (1, 10);
+        let [magX, magY] = _magnifier.getMagFactor();
+        slider.set_value (magX);
+        slider.connect ("value-changed", function() {
+            let val = slider.get_value();
+            _magnifier.setMagFactor (val, val);
+        });
+        vBox.add (slider);
+        vBox.add (overlayCheckbox);
+
+        // Put the controls in the window, and show it.
+        this.win.add (vBox);
+        togButton.show();
+        fullScreenToggle.show();
+        slider.show();
+        overlayCheckbox.show();
+        vBox.show();
+        this.win.show();
+        let [cx, cy] = _magnifier.getSize();
+        let [mx, my] = _magnifier.getMagFactor();
+        this.win.move ((cx/mx)/2, (cy/my)/2);
+
+    }   // end _init().
+
+}   // end MagPrefsUI().
+
diff --git a/js/ui/magnifier.js b/js/ui/magnifier.js
new file mode 100644
index 0000000..5f6ba7a
--- /dev/null
+++ b/js/ui/magnifier.js
@@ -0,0 +1,356 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Clutter = imports.gi.Clutter;
+const Gdk = imports.gi.Gdk;
+const Shell = imports.gi.Shell;
+const Big = imports.gi.Big;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+
+const MagPrefsUI = imports.ui.magPrefsUI;
+
+const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
+DEFAULT_BACKGROUND_COLOR.from_pixel (0x2266bbff);
+
+// Default settings
+const DEFAULT_X_MAGFACTOR = 2;          // Magnification factor:  2x.
+const DEFAULT_Y_MAGFACTOR = 2;
+const DEFAULT_MOUSE_POLL_TIME = 50;     // Frequency to poll current mouse location.
+const DEFAULT_OVERLAY_MOUSE = false;    // Place magnified view over current mouse.
+
+// Position, width and height of the magnified view.
+// Left edge, two-thirds down; half the width, and one-third the height.
+const DEFAULT_POS_SIZE = {
+    left: 0,
+    top: Shell.Global.get().stage.get_height() * .66,
+    width: Shell.Global.get().stage.get_width()/2,
+    height: Shell.Global.get().stage.get_height()/3
+};
+
+function Magnifier() {
+    this._init();
+}
+
+Magnifier.prototype = {
+    _init: function() {
+
+        // The Big.Box that is the container root of the magnified view.  The
+        // children are a background, and a clone of the stage's UIGroup.
+        var _magView = null;
+
+        // Clone of the stage's UIGroup.
+        var _uiGroupClone = null;
+
+        // the magnified mouse cursor Big.Box container.
+        var _cursorActor = null;
+
+        // whether the magnified views follows the mouse.
+        var _overlayMouse = DEFAULT_OVERLAY_MOUSE;
+
+        let global = Shell.Global.get();
+
+        //// Privileged methods ////
+
+        /**
+         * setActive:DEFAULT_MOUSE_POLL_TIME
+         * @activate  Boolean to activate of de-activate the Magnifier.
+         *            The magnified view is shown or hidden depending
+         *            on the sense of the argument, but remains an
+         *            actor on the stage.
+         */
+        this.setActive = function (activate) {
+            if (activate) {
+              _magView.show();
+            }
+            else {
+              _magView.hide();
+            }
+        }   // end setActive().
+
+        /**
+         * isActive:
+         * @return  Whether the magnifier is active (boolean).
+         */
+        this.isActive = function() {
+            let visible = _magView.get_flags() & Clutter.ActorFlags.VISIBLE;
+            return (visible == Clutter.ActorFlags.VISIBLE);
+
+        }   // end isActive().
+
+       /**
+         * setMagFactor:
+         * @inXmagFactor    the power to set the horizontal magnification factor to
+         *                  of the magnified view, e.g. 2x, 10x, etc.  If less than
+         *                  or equal to zero, it is treated as 1x.
+         * @inYmagFactor    the power to set the vertical magnification factor to
+         *                  of the magnified view.  If less thanor equal to zero,
+         *                  it is treated as 1x.
+         */
+        this.setMagFactor = function (inXMagFactor, inYMagFactor) {
+            let x = ( inXMagFactor <= 0 ? 1 : inXMagFactor );
+            let y = ( inYMagFactor <= 0 ? 1 : inYMagFactor );
+            _uiGroupClone.set_scale (x, y);
+
+        }   // end setMagFactor().
+
+        /**
+         * getMagFactor:
+         * @return  an array, {x, y}, that contains the horizontal and vertical
+         *          magnficaction factors (2x, 10X, etc.).
+         */
+        this.getMagFactor = function() {
+            return _uiGroupClone.get_scale();
+
+        }   // end getMagFactor().
+
+        /**
+         * setSize:
+         * @inWidth     The width to set the magnified view to.
+         * @inHeight    The height to set the magnified view to.
+         */
+        this.setSize = function (inWidth, inHeight) {
+            _magView.set_size (inWidth, inHeight);
+
+            // Reposition the mouse to the centre.
+            let [cx, cy] = this.getCentre();
+            _cursorActor.set_position (cx, cy);
+
+        }   // end setSize().
+
+        /**
+         * getSize:
+         * @return  an array, [width, height], that specifies the size of the
+         *          magnified view.
+         */
+        this.getSize = function() {
+            return _magView.get_size();
+
+        }   // end getSize().
+
+        /**
+         * setPosition:
+         * @inX     The x-coord of the new position.
+         * @inY     The y-coord of the new position.
+         * @return  The old position [x, y].
+         */
+        this.setPosition = function (inX, inY) {
+            let [x, y] = _magView.get_position();
+            _magView.set_position (inX, inY);
+            return [x, y];
+
+        }   // end setPosition().
+
+        /**
+         * getPosition:
+         * @return  an array, [x, y], that gives the position of the
+         *          magnified view.
+         */
+        this.getPosition = function() {
+            return _magView.get_position();
+
+        }   // end getPosition().
+
+        /**
+         * getCentre:
+         * @return  an array, [x, y], that gives the centre of the
+         *          magnified view.
+         */
+        this.getCentre = function() {
+            let [x, y] = _magView.get_size();
+            return [x/2, y/2];
+
+        }   // end getPosition().
+
+        /**
+         * isFullScreenMode:
+         * Does the magnified view occupy the whole screen?
+         */
+        this.isFullScreenMode = function() {
+            let retVal = false;
+            let [x, y] = _magView.get_position();
+            if (x == 0 && y == 0) {
+                [x, y] = _magView.get_size();
+                if (x == global.screen_width && y == global.screen_height) {
+                    retVal = true;
+                }
+            }
+            return retVal;
+
+        }   // end isFullScreenMode().
+
+        /**
+         * scrollToMousePos:
+         * Centre the magnified view to the position of the mouse.
+         * @return  an array, [x, y], of the new position of the clone of
+         * the UI group
+         */
+        this.scrollToMousePos = function() {
+            let [objUnder, mx, my, mask] =
+                Gdk.Screen.get_default().get_root_window().get_pointer();
+
+            // coords in _uiGroupClone are scaled by the magnification factor.
+            let [X, Y] = this.getMagFactor();
+            let MX = mx * X;
+            let MY = my * Y;
+
+            // get the centre of the magnified view, move UI clone
+            // to centre based on mouse location, and return new
+            // position.
+            let [cx, cy] = this.getCentre();
+            _uiGroupClone.set_position (cx - MX, cy - MY);
+
+            // If the magnified view is to overlay the current mouse location,
+            // then move it such that it is centred over the mouse. Don't do
+            // this in full screen mode, however.
+            if (_overlayMouse && !this.isFullScreenMode()) {
+                this.setPosition (mx - cx, my - cy);
+            }
+            return _uiGroupClone.get_position();
+
+        }   // end scrollToMousePos().
+
+        /**
+         * setMouseCursor:
+         * Set the magnified mouse cursor.
+         * @filename    The name of the file containing the cursor image.  It is
+         *              assumed to reside in
+         *              <code>global.imagedir/magCursors</code>.
+         */
+        this.setMouseCursor = function (filename) {
+            let uri = "file://" + global.imagedir + "magCursors/" + filename;
+            let cursorImg = Shell.TextureCache.get_default().load_uri_sync (
+                Shell.TextureCachePolicy.FOREVER, uri, 64, 64
+            );
+            let cursorActor = new Big.Box();
+            cursorActor.append (cursorImg, Big.BoxPackFlags.EXPAND);
+            _cursorActor = cursorActor;
+            _magView.add_actor (_cursorActor);
+            let [cx, cy] = this.getCentre();
+            _cursorActor.set_position (cx, cy);
+
+        }   // setMouseCursor().
+
+        /**
+         * setOverlayMouse:
+         * Set the sense of the overlay such that either the magnified view is
+         * centred over the mouse, or it is in a fixed position.  If the
+         * magnifier is in full screen mode, then a "true" argument is ignored.
+         * @inFlag  A boolean to set the overlay mouse mode with.
+         */
+        this.setOverlayMouse = function (inFlag) {
+            let fullScreen = this.isFullScreenMode();
+            _overlayMouse = (inFlag && !fullScreen);
+
+            // Reset to magnified view to constant position if not overlaying
+            // mouse, and not full screen.
+            if (!_overlayMouse && !fullScreen) {
+                this.setPosition (DEFAULT_POS_SIZE.left, DEFAULT_POS_SIZE.top);
+            }
+        }   // end setOverlayMouse().
+
+        /**
+         * isOverlayMouse:
+         * Get the sense of the overlay such that either the magnified view is
+         * centred over the mouse, or it is in a fixed position.  If the 
+         * magnifier is in full screen mode, then this is always "false".
+         * @returan  The overlay mode as a boolean.
+         */
+        this.isOverlayMouse = function() {
+            return _overlayMouse;
+
+        }   // end isOverlayMouse().
+
+        /// Initialization code ///
+
+        // Outer box is used to draw a frame around the magnified view.
+        const BORDER_COLOR = new Clutter.Color();
+        BORDER_COLOR.from_pixel(0x800000ff);
+        _magView = new Big.Box ({ border: 8, border_color: BORDER_COLOR, name: "Magnifier" });
+
+        // Hard coded (default) position and size of magnified view.
+        // Note:  should get this from preferences.
+        _magView.set_position (DEFAULT_POS_SIZE.left, DEFAULT_POS_SIZE.top);
+
+        // Use group to clip the contents
+        let aGroup = new Clutter.Group({ clip_to_allocation: true });
+        _magView.append (aGroup, Big.BoxPackFlags.EXPAND);
+        aGroup.set_size (DEFAULT_POS_SIZE.width, DEFAULT_POS_SIZE.height);
+
+        // Add a background for when the magnified uiGroup is scrolled
+        // out of view (don't want to see desktop showing through).
+        let background = new Clutter.Rectangle ({ color: DEFAULT_BACKGROUND_COLOR });
+        aGroup.add_actor (background);
+
+        // Clone the group that contains all of UI on the screen.  This is the
+        // chrome, the windows, etc.
+        _uiGroupClone = new Clutter.Clone ({ source: global.uiGroup });
+        aGroup.add_actor (_uiGroupClone);
+
+        // The contents of the clone are scaled from their original size
+        // to the allocated size of the clone, this is not useful to us
+        // since we want to scale by a fixed factor; the size of the window
+        // group is unpredictable because windows can go outside the
+        // screen, but we can set it to something fixed, since the size
+        // of a clutter actor doesn't affect clipping or display. Then we
+        // set the size of the clone to the same size. Actual size picked
+        // doesn't matter - use the size of the screen.
+        global.uiGroup.set_size (global.screen_width, global.screen_height);
+        background.set_size (global.screen_width, global.screen_height);
+        _uiGroupClone.set_size (global.screen_width, global.screen_height);
+
+        // Fixed factor scaling
+        _uiGroupClone.set_scale (DEFAULT_X_MAGFACTOR, DEFAULT_Y_MAGFACTOR);
+
+        _magView.hide();
+        global.stage.add_actor (_magView);
+
+        // Add the magnified mouse cursor
+        this.setMouseCursor ("default-cursor.xpm");
+
+        // Add the preferences window.
+        this.prefsUI = new MagPrefsUI.MagPrefsUI ( {magnifier: this} );
+
+        // Track the mouse.
+        // Note:  should get time-out interval from settings.
+        Mainloop.timeout_add (
+            DEFAULT_MOUSE_POLL_TIME, Lang.bind (this, this.scrollToMousePos)
+        );
+
+    },  // end _init()
+
+    /// Public methods ///
+
+    /**
+     * setFullScreenMode:
+     * Set the Magnifier to full-screen mode.
+     * Note:  disallows magnified view to overlay the mouse.
+     */
+    setFullScreenMode: function() {
+        if (!this.isFullScreenMode()) {
+            this.setPosition (0, 0);
+            this.setSize (global.screen_width, global.screen_height);
+            this.setOverlayMouse (false);
+        }
+    },   // end setFullScreenMode().
+
+    /**
+     * resetFullScreenMode:
+     * Reset the size of the magnifier view back to the size before
+     * it was set to full screen mode.
+     */
+    resetFullScreenMode: function() {
+        this.setPosition (DEFAULT_POS_SIZE.left, DEFAULT_POS_SIZE.top);
+        this.setSize (DEFAULT_POS_SIZE.width, DEFAULT_POS_SIZE.height);
+
+    },   // end resetFullScreenMode().
+
+    /**
+     * fullScreenCapable:
+     * Can the magnified view occupy the full screen?
+     * @return  true if it can or false otherwise.
+     */
+    fullScreenCapable: function() {
+        return true;
+    }
+
+}   // end Magnifier.prototype = ...
diff --git a/js/ui/main.js b/js/ui/main.js
index 6235846..1ef2f69 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -21,6 +21,7 @@ const LookingGlass = imports.ui.lookingGlass;
 const ShellDBus = imports.ui.shellDBus;
 const Sidebar = imports.ui.sidebar;
 const WindowManager = imports.ui.windowManager;
+const Magnifier = imports.ui.magnifier;
 
 const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
 DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
@@ -36,6 +37,8 @@ let recorder = null;
 let shellDBusService = null;
 let modalCount = 0;
 let modalActorFocusStack = [];
+let uiGroup = null;
+let magnifier = null;
 
 function start() {
     // Add a binding for "global" in the global JS namespace; (gjs
@@ -101,6 +104,16 @@ function start() {
     sidebar = new Sidebar.Sidebar();
     wm = new WindowManager.WindowManager();
 
+    // Set up stage hierarchy for the Magnifier -- it
+    // clones everything but the Magnifier actor itself.
+    //
+    uiGroup = new Clutter.Group ({ name: "uiGroup"});
+    global.window_group.reparent (uiGroup);
+    global.overlay_group.reparent (uiGroup);
+    chrome.actor.reparent (uiGroup);
+    global.stage.add_actor (uiGroup);
+    global.uiGroup = uiGroup;
+
     global.screen.connect('toggle-recording', function() {
         if (recorder == null) {
             recorder = new Shell.Recorder({ stage: global.stage });
@@ -123,6 +136,13 @@ function start() {
 
     global.stage.connect('captured-event', _globalKeyPressHandler);
 
+    // Install magnifier.
+    // Note:  to make active, call magnifier.setActive (true);
+    //
+    magnifier = new Magnifier.Magnifier();
+    global.magnifier = magnifier;
+
+
     Mainloop.idle_add(_removeUnusedWorkspaces);
 }
 
