diff --git a/playground/playground.js b/playground/playground.js
index 0de1f9afb..2367d539c 100644
--- a/playground/playground.js
+++ b/playground/playground.js
@@ -88,6 +88,24 @@ window.onload = function() {
         workspace.reportValue(data.id, data.value);
     });
 
+    // Feed mouse events as VM I/O events.
+    document.addEventListener('mousemove', function (e) {
+        var rect = canvas.getBoundingClientRect();
+        var coordinates = {
+            x: e.clientX - rect.left - rect.width / 2,
+            y: e.clientY - rect.top - rect.height / 2
+        };
+        window.vm.postIOData('mouse', coordinates);
+    });
+    canvas.addEventListener('mousedown', function (e) {
+        window.vm.postIOData('mouse', {isDown: true});
+        e.preventDefault();
+    });
+    canvas.addEventListener('mouseup', function (e) {
+        window.vm.postIOData('mouse', {isDown: false});
+        e.preventDefault();
+    });
+
     // Run threads
     vm.start();
 
diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js
new file mode 100644
index 000000000..ee75cc43a
--- /dev/null
+++ b/src/blocks/scratch3_sensing.js
@@ -0,0 +1,43 @@
+function Scratch3SensingBlocks(runtime) {
+    /**
+     * The runtime instantiating this block package.
+     * @type {Runtime}
+     */
+    this.runtime = runtime;
+}
+
+/**
+ * Retrieve the block primitives implemented by this package.
+ * @return {Object.<string, Function>} Mapping of opcode to Function.
+ */
+Scratch3SensingBlocks.prototype.getPrimitives = function() {
+    return {
+        'sensing_timer': this.getTimer,
+        'sensing_resettimer': this.resetTimer,
+        'sensing_mousex': this.getMouseX,
+        'sensing_mousey': this.getMouseY,
+        'sensing_mousedown': this.getMouseDown
+    };
+};
+
+Scratch3SensingBlocks.prototype.getTimer = function (args, util) {
+    return util.ioQuery('clock', 'projectTimer');
+};
+
+Scratch3SensingBlocks.prototype.resetTimer = function (args, util) {
+    util.ioQuery('clock', 'resetProjectTimer');
+};
+
+Scratch3SensingBlocks.prototype.getMouseX = function (args, util) {
+    return util.ioQuery('mouse', 'getX');
+};
+
+Scratch3SensingBlocks.prototype.getMouseY = function (args, util) {
+    return util.ioQuery('mouse', 'getY');
+};
+
+Scratch3SensingBlocks.prototype.getMouseDown = function (args, util) {
+    return util.ioQuery('mouse', 'getIsDown');
+};
+
+module.exports = Scratch3SensingBlocks;
diff --git a/src/engine/execute.js b/src/engine/execute.js
index 73b724965..d885a6c58 100644
--- a/src/engine/execute.js
+++ b/src/engine/execute.js
@@ -77,7 +77,14 @@ var execute = function (sequencer, thread) {
         startBranch: function (branchNum) {
             sequencer.stepToBranch(thread, branchNum);
         },
-        target: target
+        target: target,
+        ioQuery: function (device, func, args) {
+            // Find the I/O device and execute the query/function call.
+            if (runtime.ioDevices[device] && runtime.ioDevices[device][func]) {
+                var devObject = runtime.ioDevices[device];
+                return devObject[func].call(devObject, args);
+            }
+        }
     });
 
     // Deal with any reported value.
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index c32a72115..d244544aa 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -3,12 +3,17 @@ var Sequencer = require('./sequencer');
 var Thread = require('./thread');
 var util = require('util');
 
+// Virtual I/O devices.
+var Clock = require('../io/clock');
+var Mouse = require('../io/mouse');
+
 var defaultBlockPackages = {
     'scratch3_control': require('../blocks/scratch3_control'),
     'scratch3_event': require('../blocks/scratch3_event'),
     'scratch3_looks': require('../blocks/scratch3_looks'),
     'scratch3_motion': require('../blocks/scratch3_motion'),
-    'scratch3_operators': require('../blocks/scratch3_operators')
+    'scratch3_operators': require('../blocks/scratch3_operators'),
+    'scratch3_sensing': require('../blocks/scratch3_sensing')
 };
 
 /**
@@ -43,6 +48,11 @@ function Runtime (targets) {
      */
     this._primitives = {};
     this._registerBlockPackages();
+
+    this.ioDevices = {
+        'clock': new Clock(),
+        'mouse': new Mouse()
+    };
 }
 
 /**
diff --git a/src/index.js b/src/index.js
index db99f1d15..8807634a6 100644
--- a/src/index.js
+++ b/src/index.js
@@ -101,6 +101,17 @@ VirtualMachine.prototype.animationFrame = function () {
     this.runtime.animationFrame();
 };
 
+/**
+ * Post I/O data to the virtual devices.
+ * @param {?string} device Name of virtual I/O device.
+ * @param {Object} data Any data object to post to the I/O device.
+ */
+VirtualMachine.prototype.postIOData = function (device, data) {
+    if (this.runtime.ioDevices[device]) {
+        this.runtime.ioDevices[device].postData(data);
+    }
+};
+
 /*
  * Worker handlers: for all public methods available above,
  * we must also provide a message handler in case the VM is run
@@ -140,6 +151,9 @@ if (ENV_WORKER) {
         case 'animationFrame':
             self.vmInstance.animationFrame();
             break;
+        case 'postIOData':
+            self.vmInstance.postIOData(messageData.device, messageData.data);
+            break;
         default:
             if (e.data.id == 'RendererConnected') {
                 //initRenderWorker();
diff --git a/src/io/clock.js b/src/io/clock.js
new file mode 100644
index 000000000..b3bf1e0be
--- /dev/null
+++ b/src/io/clock.js
@@ -0,0 +1,16 @@
+var Timer = require('../util/timer');
+
+function Clock () {
+    this._projectTimer = new Timer();
+    this._projectTimer.start();
+}
+
+Clock.prototype.projectTimer = function () {
+    return this._projectTimer.timeElapsed() / 1000;
+};
+
+Clock.prototype.resetProjectTimer = function () {
+    this._projectTimer.start();
+};
+
+module.exports = Clock;
diff --git a/src/io/mouse.js b/src/io/mouse.js
new file mode 100644
index 000000000..7432c4358
--- /dev/null
+++ b/src/io/mouse.js
@@ -0,0 +1,33 @@
+var MathUtil = require('../util/math-util');
+
+function Mouse () {
+    this._x = 0;
+    this._y = 0;
+    this._isDown = false;
+}
+
+Mouse.prototype.postData = function(data) {
+    if (data.x) {
+        this._x = data.x;
+    }
+    if (data.y) {
+        this._y = data.y;
+    }
+    if (typeof data.isDown !== 'undefined') {
+        this._isDown = data.isDown;
+    }
+};
+
+Mouse.prototype.getX = function () {
+    return MathUtil.clamp(this._x, -240, 240);
+};
+
+Mouse.prototype.getY = function () {
+    return MathUtil.clamp(-this._y, -180, 180);
+};
+
+Mouse.prototype.getIsDown = function () {
+    return this._isDown;
+};
+
+module.exports = Mouse;
diff --git a/src/worker.js b/src/worker.js
index 70e6da5cb..248894537 100644
--- a/src/worker.js
+++ b/src/worker.js
@@ -51,6 +51,14 @@ VirtualMachine.prototype.getPlaygroundData = function () {
     this.vmWorker.postMessage({method: 'getPlaygroundData'});
 };
 
+VirtualMachine.prototype.postIOData = function (device, data) {
+    this.vmWorker.postMessage({
+        method: 'postIOData',
+        device: device,
+        data: data
+    });
+};
+
 VirtualMachine.prototype.start = function () {
     this.vmWorker.postMessage({method: 'start'});
 };