diff --git a/repos/os/src/server/input_merger/README b/repos/os/src/server/input_merger/README
new file mode 100644
index 0000000000..d2fedcdefa
--- /dev/null
+++ b/repos/os/src/server/input_merger/README
@@ -0,0 +1,27 @@
+This component merges the input events of multiple sources.
+
+Example configuration:
+
+<start name="input_merger">
+	<resource name="RAM" quantum="1M" />
+	<provides>
+		<service name="Input" />
+	</provides>
+	<config>
+		<input label="ps2" />
+		<input label="usb_hid" />
+	</config>
+	<route>
+		<service name="Input">
+			<if-arg key="label" value="ps2" /> <child name="ps2_drv" />
+		</service>
+		<service name="Input">
+			<if-arg key="label" value="usb_hid" /> <child name="usb_drv" />
+		</service>
+		<any-service> <parent /> <any-child /> </any-service>
+	</route>
+</start>
+
+For each 'input' config node, the component opens an 'Input' session with the
+configured label. This label is then evaluated by 'init' to route the session
+request to a specific input source component.
diff --git a/repos/os/src/server/input_merger/main.cc b/repos/os/src/server/input_merger/main.cc
new file mode 100644
index 0000000000..58558527fb
--- /dev/null
+++ b/repos/os/src/server/input_merger/main.cc
@@ -0,0 +1,169 @@
+/*
+ * \brief  Input event merger
+ * \author Christian Prochaska
+ * \date   2014-09-29
+ */
+
+/*
+ * Copyright (C) 2014 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU General Public License version 2.
+ */
+
+/* Genode includes */
+#include <input/component.h>
+#include <input_session/connection.h>
+#include <os/attached_dataspace.h>
+#include <os/config.h>
+#include <os/server.h>
+#include <os/static_root.h>
+#include <util/list.h>
+
+
+namespace Input_merger
+{
+	using namespace Genode;
+
+	class Input_source : public List<Input_source>::Element
+	{
+		private:
+
+			Input::Session_capability _create_session(const char *label)
+			{
+				char session_args[sizeof(Parent::Session_args)];
+
+				Arg_string::set_arg(session_args, sizeof(Parent::Session_args),
+				                    "ram_quota", "16K");
+				Arg_string::set_arg(session_args, sizeof(Parent::Session_args),
+				                    "label", label);
+
+				return env()->parent()->session<Input::Session>(session_args);
+			}
+
+		public:
+
+			Input::Session_client session_client;
+			Attached_dataspace    dataspace;
+
+			Input_source(const char *label)
+			: session_client(_create_session(label)),
+		  	  dataspace(session_client.dataspace()) { }
+
+		  	~Input_source()
+		  	{
+				env()->parent()->close(session_client);
+		  	}
+	};
+
+	struct Main;
+}
+
+
+/******************
+ ** Main program **
+ ******************/
+
+struct Input_merger::Main
+{
+	Server::Entrypoint &ep;
+	List<Input_source> input_source_list;
+
+	/*
+	 * Input session provided to our client
+	 */
+	Input::Session_component input_session_component;
+
+	/*
+	 * Attach root interface to the entry point
+	 */
+	Static_root<Input::Session> input_root { ep.manage(input_session_component) };
+
+	void handle_input(unsigned)
+	{
+		for (Input_source *input_source = input_source_list.first();
+		     input_source;
+		     input_source = input_source->next()) {
+
+			Input::Event const * const events =
+				input_source->dataspace.local_addr<Input::Event>();
+
+			unsigned const num = input_source->session_client.flush();
+			for (unsigned i = 0; i < num; i++)
+				input_session_component.submit(events[i]);
+		}
+	}
+
+	Signal_rpc_member<Main> input_dispatcher =
+		{ ep, *this, &Main::handle_input};
+
+
+	void handle_config_update(unsigned)
+	{
+		Genode::config()->reload();
+
+		for (Input_source *input_source;
+		     (input_source = input_source_list.first()); ) {
+			input_source_list.remove(input_source);
+			destroy(env()->heap(), input_source);
+		}
+
+		try {
+			for (Xml_node input_node = config()->xml_node().sub_node("input");
+			     ;
+			     input_node = input_node.next("input")) {
+
+				char label_buf[128];
+				input_node.attribute("label").value(label_buf, sizeof(label_buf));
+
+				Input_source *input_source(new (env()->heap()) Input_source(label_buf));
+
+				input_source_list.insert(input_source);
+
+				input_source->session_client.sigh(input_dispatcher);
+			}
+		} catch (Xml_node::Nonexistent_sub_node) { }
+
+	}
+
+	Signal_rpc_member<Main> config_update_dispatcher =
+		{ ep, *this, &Main::handle_config_update};
+
+	/**
+	 * Constructor
+	 */
+	Main(Server::Entrypoint &ep) : ep(ep)
+	{
+		input_session_component.event_queue().enabled(true);
+
+		/*
+		 * Apply initial configuration
+		 */
+		handle_config_update(0);
+
+		/*
+		 * Register signal handler
+		 */
+		Genode::config()->sigh(config_update_dispatcher);
+
+		/*
+		 * Announce service
+		 */
+		Genode::env()->parent()->announce(ep.manage(input_root));
+	}
+
+};
+
+
+/************
+ ** Server **
+ ************/
+
+namespace Server {
+
+	char const *name() { return "input_merger_ep"; }
+
+	size_t stack_size() { return 4*1024*sizeof(addr_t); }
+
+	void construct(Entrypoint &ep) { static Input_merger::Main inst(ep); }
+}
diff --git a/repos/os/src/server/input_merger/target.mk b/repos/os/src/server/input_merger/target.mk
new file mode 100644
index 0000000000..636922a886
--- /dev/null
+++ b/repos/os/src/server/input_merger/target.mk
@@ -0,0 +1,3 @@
+TARGET = input_merger
+SRC_CC = main.cc
+LIBS   = base config server