diff --git a/src/xrt/compositor/client/comp_gl_client.c b/src/xrt/compositor/client/comp_gl_client.c
index 64f8a6ee095fdd89f34bff07f18794b0a3ca8064..7f8c413a2ecbbbeee6d8b731005c5ac507abe82a 100644
--- a/src/xrt/compositor/client/comp_gl_client.c
+++ b/src/xrt/compositor/client/comp_gl_client.c
@@ -92,6 +92,16 @@ client_gl_swapchain_release_image(struct xrt_swapchain *xsc, uint32_t index)
  *
  */
 
+static xrt_result_t
+client_gl_compositor_prepare_session(struct xrt_compositor *xc,
+                                     struct xrt_overlay_info *oi)
+{
+	struct client_gl_compositor *c = client_gl_compositor(xc);
+	// Pipe down call into fd compositor.
+	return xrt_comp_prepare_session(&c->xcfd->base, oi);
+}
+
+
 static xrt_result_t
 client_gl_compositor_begin_session(struct xrt_compositor *xc,
                                    enum xrt_view_type type)
@@ -308,6 +318,14 @@ client_gl_swapchain_create(struct xrt_compositor *xc,
 	return &sc->base.base;
 }
 
+static void
+client_gl_compositor_poll_events(struct xrt_compositor *xc,
+                                 struct xrt_ipc_event *e)
+{
+	struct client_gl_compositor *c = client_gl_compositor(xc);
+	c->xcfd->base.poll_events(&c->xcfd->base, e);
+}
+
 static void
 client_gl_compositor_destroy(struct xrt_compositor *xc)
 {
@@ -320,6 +338,7 @@ client_gl_compositor_init(struct client_gl_compositor *c,
                           client_gl_get_procaddr get_gl_procaddr)
 {
 	c->base.base.create_swapchain = client_gl_swapchain_create;
+	c->base.base.prepare_session = client_gl_compositor_prepare_session;
 	c->base.base.begin_session = client_gl_compositor_begin_session;
 	c->base.base.end_session = client_gl_compositor_end_session;
 	c->base.base.wait_frame = client_gl_compositor_wait_frame;
@@ -331,6 +350,7 @@ client_gl_compositor_init(struct client_gl_compositor *c,
 	c->base.base.layer_quad = client_gl_compositor_layer_quad;
 	c->base.base.layer_commit = client_gl_compositor_layer_commit;
 	c->base.base.destroy = client_gl_compositor_destroy;
+	c->base.base.poll_events = client_gl_compositor_poll_events;
 	c->xcfd = xcfd;
 
 	// Passthrough our formats from the fd compositor to the client.
diff --git a/src/xrt/compositor/client/comp_vk_client.c b/src/xrt/compositor/client/comp_vk_client.c
index 8c4854d2891433e2ec13073cd7c508471bff174a..a54d27e5fd6faaa5beda477eed70406479517f9d 100644
--- a/src/xrt/compositor/client/comp_vk_client.c
+++ b/src/xrt/compositor/client/comp_vk_client.c
@@ -139,6 +139,13 @@ client_vk_swapchain_release_image(struct xrt_swapchain *xsc, uint32_t index)
  * Compositor functions.
  *
  */
+static void
+client_vk_compositor_poll_events(struct xrt_compositor *xc,
+                                 struct xrt_ipc_event *e)
+{
+	struct client_vk_compositor *c = client_vk_compositor(xc);
+	c->xcfd->base.poll_events(&c->xcfd->base, e);
+}
 
 static void
 client_vk_compositor_destroy(struct xrt_compositor *xc)
@@ -159,6 +166,15 @@ client_vk_compositor_destroy(struct xrt_compositor *xc)
 	free(c);
 }
 
+static xrt_result_t
+client_vk_compositor_prepare_session(struct xrt_compositor *xc,
+                                     struct xrt_overlay_info *oi)
+{
+	struct client_vk_compositor *c = client_vk_compositor(xc);
+	// Pipe down call into fd compositor.
+	return xrt_comp_prepare_session(&c->xcfd->base, oi);
+}
+
 static xrt_result_t
 client_vk_compositor_begin_session(struct xrt_compositor *xc,
                                    enum xrt_view_type type)
@@ -436,6 +452,7 @@ client_vk_compositor_create(struct xrt_compositor_fd *xcfd,
 	    U_TYPED_CALLOC(struct client_vk_compositor);
 
 	c->base.base.create_swapchain = client_vk_swapchain_create;
+	c->base.base.prepare_session = client_vk_compositor_prepare_session;
 	c->base.base.begin_session = client_vk_compositor_begin_session;
 	c->base.base.end_session = client_vk_compositor_end_session;
 	c->base.base.wait_frame = client_vk_compositor_wait_frame;
@@ -447,6 +464,8 @@ client_vk_compositor_create(struct xrt_compositor_fd *xcfd,
 	c->base.base.layer_quad = client_vk_compositor_layer_quad;
 	c->base.base.layer_commit = client_vk_compositor_layer_commit;
 	c->base.base.destroy = client_vk_compositor_destroy;
+	c->base.base.poll_events = client_vk_compositor_poll_events;
+
 	c->xcfd = xcfd;
 	// passthrough our formats from the fd compositor to the client
 	for (uint32_t i = 0; i < xcfd->base.num_formats; i++) {
diff --git a/src/xrt/include/xrt/xrt_compositor.h b/src/xrt/include/xrt/xrt_compositor.h
index c13464501c51c42c39d1797a6592625bc03ce6f0..89c3a79234b79e488ddde2078de857a72cf5259d 100644
--- a/src/xrt/include/xrt/xrt_compositor.h
+++ b/src/xrt/include/xrt/xrt_compositor.h
@@ -386,12 +386,13 @@ struct xrt_compositor
 	 *
 	 * This function is very much WIP.
 	 */
-	void (*poll_events)(struct xrt_compositor *xc, uint64_t *WIP);
+	void (*poll_events)(struct xrt_compositor *xc, struct xrt_ipc_event *e);
 
 	/*!
 	 * This function is implicit in the OpenXR spec but made explicit here.
 	 */
-	void (*prepare_session)(struct xrt_compositor *xc);
+	xrt_result_t (*prepare_session)(struct xrt_compositor *xc,
+	                                struct xrt_overlay_info *oi);
 
 	/*!
 	 * See xrBeginSession.
@@ -516,9 +517,9 @@ xrt_comp_create_swapchain(struct xrt_compositor *xc,
  * @public @memberof xrt_compositor
  */
 static inline void
-xrt_comp_poll_events(struct xrt_compositor *xc, uint64_t *WIP)
+xrt_comp_poll_events(struct xrt_compositor *xc, struct xrt_ipc_event *e)
 {
-	xc->poll_events(xc, WIP);
+	xc->poll_events(xc, e);
 }
 
 /*!
@@ -528,10 +529,10 @@ xrt_comp_poll_events(struct xrt_compositor *xc, uint64_t *WIP)
  *
  * @public @memberof xrt_compositor
  */
-static inline void
-xrt_comp_prepare_session(struct xrt_compositor *xc)
+static inline xrt_result_t
+xrt_comp_prepare_session(struct xrt_compositor *xc, struct xrt_overlay_info *oi)
 {
-	xc->prepare_session(xc);
+	return xc->prepare_session(xc, oi);
 }
 
 /*!
diff --git a/src/xrt/include/xrt/xrt_defines.h b/src/xrt/include/xrt/xrt_defines.h
index 121e6a5450d19da86b9e1de490625179102a2f06..7b5f33c640b13a3e0463929346a13a38236eca70 100644
--- a/src/xrt/include/xrt/xrt_defines.h
+++ b/src/xrt/include/xrt/xrt_defines.h
@@ -18,6 +18,7 @@
 extern "C" {
 #endif
 
+#define XRT_EVENT_SIZE 256
 
 /*!
  * A base class for reference counted objects.
@@ -29,6 +30,68 @@ struct xrt_reference
 	uint32_t count;
 };
 
+/*!
+ * Overlay extension data - sent when creating an session
+ *
+ * @ingroup xrt_iface
+ */
+struct xrt_overlay_info
+{
+	bool is_overlay;
+	uint64_t flags;
+	uint32_t z_order;
+};
+
+#define XRT_MAX_APPLICATION_NAME_SIZE 128
+
+struct xrt_instance_info
+{
+	char application_name[XRT_MAX_APPLICATION_NAME_SIZE];
+	pid_t pid;
+};
+
+typedef enum xrt_ipc_event_type
+{
+	XRT_EVENT_OVERLAY,
+	XRT_EVENT_CLIENT_STATE
+} xrt_event_type;
+
+struct xrt_client_state_event
+{
+	uint8_t active;
+	uint8_t visible;
+	uint8_t focused;
+};
+
+struct xrt_overlay_event
+{
+	bool visible;
+};
+
+struct xrt_ipc_event
+{
+	bool valid;
+	xrt_event_type type;
+	uint8_t data[XRT_EVENT_SIZE];
+};
+
+/*!
+ * Client data - used to store per-client state information in the ipc
+ * server/compositor
+ *
+ * @ingroup xrt_iface
+ */
+struct xrt_client_state
+{
+	bool primary_application;
+	bool session_active;
+	bool session_visible;
+	bool session_focused;
+	bool session_overlay;
+	uint32_t z_order;
+	struct xrt_instance_info info;
+};
+
 /*!
  * Which blend mode does the device support, used as both a bitfield and value.
  *
diff --git a/src/xrt/include/xrt/xrt_instance.h b/src/xrt/include/xrt/xrt_instance.h
index 33bd792d6159bf76c5ba7a74bf510c98992df1cb..4b9178499de26ab6fb29547c2a2d93ff7cae46ce 100644
--- a/src/xrt/include/xrt/xrt_instance.h
+++ b/src/xrt/include/xrt/xrt_instance.h
@@ -10,6 +10,8 @@
 #pragma once
 
 #include "xrt/xrt_compiler.h"
+#include "xrt/xrt_defines.h"
+
 
 #ifdef __cplusplus
 extern "C" {
@@ -20,6 +22,8 @@ struct xrt_prober;
 struct xrt_device;
 struct xrt_compositor_fd;
 
+
+
 /*!
  * @ingroup xrt_iface
  * @{
@@ -131,6 +135,7 @@ struct xrt_instance
 	/*!
 	 * @}
 	 */
+	struct xrt_instance_info instance_info;
 };
 
 /*!
@@ -220,7 +225,8 @@ xrt_instance_destroy(struct xrt_instance **xinst_ptr)
  * @relates xrt_instance
  */
 int
-xrt_instance_create(struct xrt_instance **out_xinst);
+xrt_instance_create(struct xrt_instance **out_xinst,
+                    struct xrt_instance_info *ii);
 
 /*!
  * @}
diff --git a/src/xrt/ipc/CMakeLists.txt b/src/xrt/ipc/CMakeLists.txt
index 1f8f3240fa6cb1b8a0e9cd0364dcb63901b9a9cc..c998822365b01e36a4f3544cf9ff1b7fcde8c6b4 100644
--- a/src/xrt/ipc/CMakeLists.txt
+++ b/src/xrt/ipc/CMakeLists.txt
@@ -38,6 +38,7 @@ add_library(ipc_client STATIC
 	)
 target_include_directories(ipc_client INTERFACE
 	${CMAKE_CURRENT_SOURCE_DIR}
+	${CMAKE_CURRENT_BINARY_DIR}
 	)
 target_include_directories(ipc_client PRIVATE
 	${CMAKE_CURRENT_SOURCE_DIR}
@@ -66,6 +67,7 @@ add_library(ipc_server STATIC
 target_include_directories(ipc_server
 	INTERFACE
 	${CMAKE_CURRENT_SOURCE_DIR}
+	${CMAKE_CURRENT_BINARY_DIR}
 	)
 target_include_directories(ipc_server PRIVATE
 	${CMAKE_CURRENT_SOURCE_DIR}/../compositor
diff --git a/src/xrt/ipc/ipc_client_compositor.c b/src/xrt/ipc/ipc_client_compositor.c
index 79acfe98b2135104e3006ec5ec7eb90d4fe918e5..813c9ef8115c3d75c4134008896638760f8dca3f 100644
--- a/src/xrt/ipc/ipc_client_compositor.c
+++ b/src/xrt/ipc/ipc_client_compositor.c
@@ -224,6 +224,30 @@ ipc_compositor_swapchain_create(struct xrt_compositor *xc,
 	return &ics->base.base;
 }
 
+static xrt_result_t
+ipc_compositor_prepare_session(struct xrt_compositor *xc,
+                              struct xrt_overlay_info *oi)
+{
+	struct ipc_client_compositor *icc = ipc_client_compositor(xc);
+
+	IPC_SPEW(icc->ipc_c, "IPC: compositor create session");
+
+	IPC_CALL_CHK(ipc_call_session_create(icc->ipc_c, oi));
+	return res;
+}
+
+static void
+ipc_compositor_poll_events(struct xrt_compositor *xc, struct xrt_ipc_event *e)
+{
+	struct ipc_client_compositor *icc = ipc_client_compositor(xc);
+
+	IPC_SPEW(icc->ipc_c, "IPC: polling for events");
+
+	struct xrt_ipc_event event;
+	IPC_CALL_CHK(ipc_call_compositor_poll_events(icc->ipc_c, &event));
+	memcpy(e, &event, sizeof(struct xrt_ipc_event));
+}
+
 static xrt_result_t
 ipc_compositor_begin_session(struct xrt_compositor *xc,
                              enum xrt_view_type view_type)
@@ -445,6 +469,7 @@ ipc_client_compositor_create(ipc_connection_t *ipc_c,
 	    U_TYPED_CALLOC(struct ipc_client_compositor);
 
 	c->base.base.create_swapchain = ipc_compositor_swapchain_create;
+	c->base.base.prepare_session = ipc_compositor_prepare_session;
 	c->base.base.begin_session = ipc_compositor_begin_session;
 	c->base.base.end_session = ipc_compositor_end_session;
 	c->base.base.wait_frame = ipc_compositor_wait_frame;
@@ -456,6 +481,7 @@ ipc_client_compositor_create(ipc_connection_t *ipc_c,
 	c->base.base.layer_quad = ipc_compositor_layer_quad;
 	c->base.base.layer_commit = ipc_compositor_layer_commit;
 	c->base.base.destroy = ipc_compositor_destroy;
+	c->base.base.poll_events = ipc_compositor_poll_events;
 	c->ipc_c = ipc_c;
 
 	// fetch our format list on client compositor construction
diff --git a/src/xrt/ipc/ipc_client_instance.c b/src/xrt/ipc/ipc_client_instance.c
index 98ea73b72af37065f169172f9285e695bb860ce1..7ea04c82def610a17c8ec058276fdd3f47d95be8 100644
--- a/src/xrt/ipc/ipc_client_instance.c
+++ b/src/xrt/ipc/ipc_client_instance.c
@@ -183,7 +183,8 @@ ipc_client_instance_destroy(struct xrt_instance *xinst)
  * @public @memberof ipc_instance
  */
 int
-ipc_instance_create(struct xrt_instance **out_xinst)
+ipc_instance_create(struct xrt_instance **out_xinst,
+                    struct xrt_instance_info *i_info)
 {
 	struct ipc_client_instance *ii =
 	    U_TYPED_CALLOC(struct ipc_client_instance);
@@ -223,6 +224,15 @@ ipc_instance_create(struct xrt_instance **out_xinst)
 		return -1;
 	}
 
+	struct xrt_client_state desc = {0};
+	desc.info = *i_info;
+	r = ipc_call_system_set_client_info(&ii->ipc_c, &desc);
+	if (r != XRT_SUCCESS) {
+		IPC_ERROR(&ii->ipc_c, "Failed to set instance info");
+		free(ii);
+		return -1;
+	}
+
 	const int flags = MAP_SHARED;
 	const int access = PROT_READ | PROT_WRITE;
 	const size_t size = sizeof(struct ipc_shared_memory);
diff --git a/src/xrt/ipc/ipc_protocol.h b/src/xrt/ipc/ipc_protocol.h
index 69d7b1b1325801561d7fc67fb5d90bdaeda9d784..d8374e9867ec3cb668b06ffa608be9a6e1df9488 100644
--- a/src/xrt/ipc/ipc_protocol.h
+++ b/src/xrt/ipc/ipc_protocol.h
@@ -4,20 +4,23 @@
  * @file
  * @brief  Common protocol definition.
  * @author Pete Black <pblack@collabora.com>
+ * @author Jakob Bornecrantz <jakob@collabora.com>
  * @ingroup ipc
  */
 
 #pragma once
 
-#include "xrt/xrt_tracking.h"
-#include "xrt/xrt_device.h"
 #include "xrt/xrt_compiler.h"
-#include "xrt/xrt_compositor.h"
-
 #include "xrt/xrt_results.h"
+#include "xrt/xrt_defines.h"
+#include "xrt/xrt_instance.h"
+#include "xrt/xrt_compositor.h"
+#include "xrt/xrt_device.h"
+#include "xrt/xrt_tracking.h"
 
 #include <semaphore.h>
 
+
 #define IPC_MSG_SOCK_FILE "/tmp/monado_comp_ipc"
 #define IPC_MAX_SWAPCHAIN_FDS 8
 #define IPC_CRED_SIZE 1    // auth not implemented
@@ -26,7 +29,9 @@
 #define IPC_MAX_FORMATS 32 // max formats our server-side compositor supports
 #define IPC_MAX_DEVICES 8  // max number of devices we will map via shared mem
 #define IPC_MAX_LAYERS 16
-#define IPC_MAX_SLOTS 3
+#define IPC_MAX_SLOTS 128
+#define IPC_MAX_CLIENTS 8
+#define IPC_EVENT_QUEUE_SIZE 32
 
 #define IPC_SHARED_MAX_DEVICES 8
 #define IPC_SHARED_MAX_INPUTS 1024
@@ -172,10 +177,15 @@ struct ipc_shared_memory
 	} wait_frame;
 };
 
-/*
- *
- * Rest of protocol is generated.
- *
- */
+struct ipc_client_list
+{
+	int32_t ids[IPC_MAX_CLIENTS];
+};
 
-#include "ipc_protocol_generated.h"
+struct ipc_compositor_state
+{
+	uint8_t active;
+	uint8_t visible;
+	uint8_t focused;
+	int32_t z_order;
+};
diff --git a/src/xrt/ipc/ipc_server.h b/src/xrt/ipc/ipc_server.h
index 2b4bba8c1941cbbc2c257f1921662a67cc406653..e30b1e081b605fa43f6560f73cc9fb993804b3a2 100644
--- a/src/xrt/ipc/ipc_server.h
+++ b/src/xrt/ipc/ipc_server.h
@@ -62,7 +62,7 @@ extern "C" {
 
 #define IPC_SERVER_NUM_XDEVS 8
 #define IPC_MAX_CLIENT_SWAPCHAINS 32
-#define IPC_MAX_CLIENTS 8
+//#define IPC_MAX_CLIENTS 8
 
 struct xrt_instance;
 struct xrt_compositor;
@@ -85,6 +85,14 @@ struct ipc_swapchain_data
 	bool active;
 };
 
+
+struct ipc_queued_event
+{
+	bool pending;
+	uint64_t timestamp;
+	struct xrt_ipc_event event;
+};
+
 /*!
  * Holds the state for a single client.
  *
@@ -116,7 +124,10 @@ struct ipc_client_state
 	//! Whether we are currently rendering @ref render_state
 	bool rendering_state;
 
-	bool active;
+	struct xrt_client_state client_state;
+	struct ipc_queued_event queued_events[IPC_EVENT_QUEUE_SIZE];
+
+	int server_thread_index;
 };
 
 /*!
@@ -160,10 +171,16 @@ struct ipc_server
 
 	// Hack for now.
 	struct ipc_wait *iw;
-	struct os_thread thread;
-	volatile bool thread_started;
-	volatile bool thread_stopping;
-	volatile struct ipc_client_state thread_state;
+	struct os_thread thread[IPC_MAX_CLIENTS];
+	volatile bool thread_started[IPC_MAX_CLIENTS];
+	volatile bool thread_stopping[IPC_MAX_CLIENTS];
+	volatile struct ipc_client_state thread_state[IPC_MAX_CLIENTS];
+
+	volatile uint32_t current_slot_index;
+
+	int active_client_index;
+	int last_active_client_index;
+	pthread_mutex_t global_state_lock;
 };
 
 /*!
@@ -174,6 +191,14 @@ struct ipc_server
 int
 ipc_server_main(int argc, char **argv);
 
+/*!
+ * Called by client threads to manage global state
+ *
+ * @ingroup ipc_server
+ */
+void
+update_server_state(struct ipc_server *vs);
+
 /*!
  * Thread function for the client side dispatching.
  *
diff --git a/src/xrt/ipc/ipc_server_client.c b/src/xrt/ipc/ipc_server_client.c
index 7fc7bd171d23db2717bf539e454bd1e568855b21..c53e3e0ba2133de6db53e643d47edf2ac0a3366c 100644
--- a/src/xrt/ipc/ipc_server_client.c
+++ b/src/xrt/ipc/ipc_server_client.c
@@ -48,71 +48,189 @@ ipc_handle_instance_get_shm_fd(volatile struct ipc_client_state *cs,
 	*out_num_fds = 1;
 	return XRT_SUCCESS;
 }
+xrt_result_t
+ipc_handle_session_create(volatile struct ipc_client_state *ics,
+                          struct xrt_overlay_info *oi)
+{
+	ics->client_state.session_active = false;
+	ics->client_state.session_overlay = false;
+	ics->client_state.session_visible = false;
+	if (oi->is_overlay) {
+		ics->client_state.session_overlay = true;
+		ics->client_state.z_order = oi->z_order;
+		//@todo handle flags
+	}
+	update_server_state(ics->server);
+	return XRT_SUCCESS;
+}
 
 xrt_result_t
-ipc_handle_session_begin(volatile struct ipc_client_state *cs)
+ipc_handle_session_begin(volatile struct ipc_client_state *ics)
 {
-	cs->active = true;
+	// ics->client_state.session_active = true;
+	// update_server_state(ics->server);
 	return XRT_SUCCESS;
 }
 
 xrt_result_t
-ipc_handle_session_end(volatile struct ipc_client_state *cs)
+ipc_handle_session_end(volatile struct ipc_client_state *ics)
 {
-	cs->active = false;
+	ics->client_state.session_active = false;
+	update_server_state(ics->server);
 	return XRT_SUCCESS;
 }
 
 xrt_result_t
-ipc_handle_compositor_get_formats(volatile struct ipc_client_state *cs,
+ipc_handle_compositor_get_formats(volatile struct ipc_client_state *ics,
                                   struct ipc_formats_info *out_info)
 {
-	out_info->num_formats = cs->xc->num_formats;
-	for (size_t i = 0; i < cs->xc->num_formats; i++) {
-		out_info->formats[i] = cs->xc->formats[i];
+	out_info->num_formats = ics->xc->num_formats;
+	for (size_t i = 0; i < ics->xc->num_formats; i++) {
+		out_info->formats[i] = ics->xc->formats[i];
 	}
 
 	return XRT_SUCCESS;
 }
 
 xrt_result_t
-ipc_handle_compositor_wait_frame(volatile struct ipc_client_state *cs)
+ipc_handle_compositor_wait_frame(volatile struct ipc_client_state *ics)
 {
-	ipc_server_wait_add_frame(cs->server->iw, cs);
+	ipc_server_wait_add_frame(ics->server->iw, ics);
+	ics->client_state.session_active = true;
+	update_server_state(ics->server);
 	return XRT_SUCCESS;
 }
 
 xrt_result_t
-ipc_handle_compositor_begin_frame(volatile struct ipc_client_state *cs)
+ipc_handle_compositor_begin_frame(volatile struct ipc_client_state *ics)
 {
+	// update_server_state(ics->server);
 	return XRT_SUCCESS;
 }
 
 xrt_result_t
-ipc_handle_compositor_discard_frame(volatile struct ipc_client_state *cs)
+ipc_handle_compositor_discard_frame(volatile struct ipc_client_state *ics)
 {
 	return XRT_SUCCESS;
 }
 
 xrt_result_t
-ipc_handle_compositor_layer_sync(volatile struct ipc_client_state *cs,
+ipc_handle_compositor_layer_sync(volatile struct ipc_client_state *ics,
                                  uint32_t slot_id,
                                  uint32_t *out_free_slot_id)
 {
-	struct ipc_shared_memory *ism = cs->server->ism;
+	struct ipc_shared_memory *ism = ics->server->ism;
 	struct ipc_layer_slot *slot = &ism->slots[slot_id];
 
 	// Copy current slot data to our state.
-	cs->render_state = *slot;
-	cs->rendering_state = true;
+	ics->render_state = *slot;
+	ics->rendering_state = true;
+
+	os_mutex_lock((struct os_mutex *)&ics->server->global_state_lock);
+	*out_free_slot_id =
+	    (ics->server->current_slot_index + 1) % IPC_MAX_SLOTS;
+	ics->server->current_slot_index = *out_free_slot_id;
+	os_mutex_unlock((struct os_mutex *)&ics->server->global_state_lock);
+
+	return XRT_SUCCESS;
+}
 
-	*out_free_slot_id = (slot_id + 1) % IPC_MAX_SLOTS;
+xrt_result_t
+ipc_handle_compositor_poll_events(volatile struct ipc_client_state *cs,
+                                  struct xrt_ipc_event *out_event)
+{
 
+	uint64_t l_timestamp = UINT64_MAX;
+	volatile struct ipc_queued_event *event_to_send = NULL;
+	for (uint32_t i = 0; i < IPC_EVENT_QUEUE_SIZE; i++) {
+		volatile struct ipc_queued_event *e = &cs->queued_events[i];
+		if (e->pending == true && e->timestamp < l_timestamp) {
+			event_to_send = e;
+		}
+	}
+	// we always return an event in response to this call - but
+	// only 'real' events are marked valid
+	out_event->valid = false;
+	if (event_to_send) {
+		out_event->type = event_to_send->event.type;
+		memcpy(out_event->data, (void *)&event_to_send->event.data,
+		       sizeof(struct xrt_ipc_event));
+		out_event->valid = true;
+		event_to_send->pending = false;
+	}
 	return XRT_SUCCESS;
 }
 
 xrt_result_t
-ipc_handle_swapchain_create(volatile struct ipc_client_state *cs,
+ipc_handle_system_get_client_info(volatile struct ipc_client_state *_ics,
+                                  uint32_t id,
+                                  struct xrt_client_state *out_client_desc)
+{
+	if (id >= IPC_MAX_CLIENTS) {
+		return XRT_ERROR_IPC_FAILURE;
+	}
+	volatile struct ipc_client_state *ics = &_ics->server->thread_state[id];
+
+	if (ics->ipc_socket_fd <= 0) {
+		return XRT_ERROR_IPC_FAILURE;
+	}
+
+	*out_client_desc = ics->client_state;
+
+	//@todo: track this data in the ipc_client_state struct
+	out_client_desc->primary_application = false;
+	if (ics->server->active_client_index == (int)id) {
+		out_client_desc->primary_application = true;
+	}
+
+	return XRT_SUCCESS;
+}
+
+xrt_result_t
+ipc_handle_system_set_client_info(volatile struct ipc_client_state *ics,
+                                  struct xrt_client_state *client_desc)
+{
+	ics->client_state.info = client_desc->info;
+	return XRT_SUCCESS;
+}
+
+xrt_result_t
+ipc_handle_system_get_clients(volatile struct ipc_client_state *_ics,
+                              struct ipc_client_list *list)
+{
+	for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
+		volatile struct ipc_client_state *ics =
+		    &_ics->server->thread_state[i];
+		list->ids[i] = ics->server_thread_index;
+	}
+	return XRT_SUCCESS;
+}
+
+xrt_result_t
+ipc_handle_system_set_primary_client(volatile struct ipc_client_state *ics,
+                                     uint32_t client_id)
+{
+
+	ics->server->active_client_index = client_id;
+	printf("system setting active client to %d\n", client_id);
+	update_server_state(ics->server);
+	return XRT_SUCCESS;
+}
+
+xrt_result_t
+ipc_handle_system_set_focused_client(volatile struct ipc_client_state *ics,
+                                     uint32_t client_id)
+{
+
+	// cs->server->active_client_index = client_id;
+	printf("UNIMPLEMENTED: system setting focused client to %d\n",
+	       client_id);
+	// update_server_state(cs->server);
+	return XRT_SUCCESS;
+}
+
+xrt_result_t
+ipc_handle_swapchain_create(volatile struct ipc_client_state *ics,
                             enum xrt_swapchain_create_flags create,
                             enum xrt_swapchain_usage_bits bits,
                             int64_t format,
@@ -132,7 +250,7 @@ ipc_handle_swapchain_create(volatile struct ipc_client_state *cs,
 	// Our handle is just the index for now.
 	uint32_t index = 0;
 	for (; index < IPC_MAX_CLIENT_SWAPCHAINS; index++) {
-		if (!cs->swapchain_data[index].active) {
+		if (!ics->swapchain_data[index].active) {
 			break;
 		}
 	}
@@ -143,11 +261,11 @@ ipc_handle_swapchain_create(volatile struct ipc_client_state *cs,
 	}
 
 	// It's now safe to increment the number of swapchains.
-	cs->num_swapchains++;
+	ics->num_swapchains++;
 
 	// create the swapchain
 	struct xrt_swapchain *xsc =
-	    xrt_comp_create_swapchain(cs->xc,       // Compositor
+	    xrt_comp_create_swapchain(ics->xc,      // Compositor
 	                              create,       // Flags
 	                              bits,         // Usage
 	                              format,       // Format
@@ -160,14 +278,14 @@ ipc_handle_swapchain_create(volatile struct ipc_client_state *cs,
 
 	uint32_t num_images = xsc->num_images;
 
-	IPC_SPEW(cs->server, "IPC: Created swapchain %d\n", index);
+	IPC_SPEW(ics->server, "IPC: Created swapchain %d\n", index);
 
-	cs->xscs[index] = xsc;
-	cs->swapchain_data[index].active = true;
-	cs->swapchain_data[index].width = width;
-	cs->swapchain_data[index].height = height;
-	cs->swapchain_data[index].format = format;
-	cs->swapchain_data[index].num_images = num_images;
+	ics->xscs[index] = xsc;
+	ics->swapchain_data[index].active = true;
+	ics->swapchain_data[index].width = width;
+	ics->swapchain_data[index].height = height;
+	ics->swapchain_data[index].format = format;
+	ics->swapchain_data[index].num_images = num_images;
 
 	// return our result to the caller.
 	struct xrt_swapchain_fd *xcsfd = (struct xrt_swapchain_fd *)xsc;
@@ -190,14 +308,14 @@ ipc_handle_swapchain_create(volatile struct ipc_client_state *cs,
 }
 
 xrt_result_t
-ipc_handle_swapchain_wait_image(volatile struct ipc_client_state *cs,
+ipc_handle_swapchain_wait_image(volatile struct ipc_client_state *ics,
                                 uint32_t id,
                                 uint64_t timeout,
                                 uint32_t index)
 {
 	//! @todo Look up the index.
 	uint32_t sc_index = id;
-	struct xrt_swapchain *xsc = cs->xscs[sc_index];
+	struct xrt_swapchain *xsc = ics->xscs[sc_index];
 
 	xrt_swapchain_wait_image(xsc, timeout, index);
 
@@ -205,14 +323,14 @@ ipc_handle_swapchain_wait_image(volatile struct ipc_client_state *cs,
 }
 
 xrt_result_t
-ipc_handle_swapchain_acquire_image(volatile struct ipc_client_state *cs,
+ipc_handle_swapchain_acquire_image(volatile struct ipc_client_state *ics,
                                    uint32_t id,
                                    uint32_t *out_index)
 
 {
 	//! @todo Look up the index.
 	uint32_t sc_index = id;
-	struct xrt_swapchain *xsc = cs->xscs[sc_index];
+	struct xrt_swapchain *xsc = ics->xscs[sc_index];
 
 	xrt_swapchain_acquire_image(xsc, out_index);
 
@@ -220,13 +338,13 @@ ipc_handle_swapchain_acquire_image(volatile struct ipc_client_state *cs,
 }
 
 xrt_result_t
-ipc_handle_swapchain_release_image(volatile struct ipc_client_state *cs,
+ipc_handle_swapchain_release_image(volatile struct ipc_client_state *ics,
                                    uint32_t id,
                                    uint32_t index)
 {
 	//! @todo Look up the index.
 	uint32_t sc_index = id;
-	struct xrt_swapchain *xsc = cs->xscs[sc_index];
+	struct xrt_swapchain *xsc = ics->xscs[sc_index];
 
 	xrt_swapchain_release_image(xsc, index);
 
@@ -234,25 +352,25 @@ ipc_handle_swapchain_release_image(volatile struct ipc_client_state *cs,
 }
 
 xrt_result_t
-ipc_handle_swapchain_destroy(volatile struct ipc_client_state *cs, uint32_t id)
+ipc_handle_swapchain_destroy(volatile struct ipc_client_state *ics, uint32_t id)
 {
 	//! @todo Implement destroy swapchain.
-	cs->num_swapchains--;
+	ics->num_swapchains--;
 
-	xrt_swapchain_destroy((struct xrt_swapchain **)&cs->xscs[id]);
-	cs->swapchain_data[id].active = false;
+	xrt_swapchain_destroy((struct xrt_swapchain **)&ics->xscs[id]);
+	ics->swapchain_data[id].active = false;
 
 	return XRT_SUCCESS;
 }
 
 xrt_result_t
-ipc_handle_device_update_input(volatile struct ipc_client_state *cs,
+ipc_handle_device_update_input(volatile struct ipc_client_state *ics,
                                uint32_t id)
 {
 	// To make the code a bit more readable.
 	uint32_t device_id = id;
-	struct ipc_shared_memory *ism = cs->server->ism;
-	struct xrt_device *xdev = cs->server->xdevs[device_id];
+	struct ipc_shared_memory *ism = ics->server->ism;
+	struct xrt_device *xdev = ics->server->xdevs[device_id];
 	struct ipc_shared_device *idev = &ism->idevs[device_id];
 
 	// Update inputs.
@@ -268,7 +386,7 @@ ipc_handle_device_update_input(volatile struct ipc_client_state *cs,
 }
 
 xrt_result_t
-ipc_handle_device_get_tracked_pose(volatile struct ipc_client_state *cs,
+ipc_handle_device_get_tracked_pose(volatile struct ipc_client_state *ics,
                                    uint32_t id,
                                    enum xrt_input_name name,
                                    uint64_t at_timestamp,
@@ -278,7 +396,7 @@ ipc_handle_device_get_tracked_pose(volatile struct ipc_client_state *cs,
 
 	// To make the code a bit more readable.
 	uint32_t device_id = id;
-	struct xrt_device *xdev = cs->server->xdevs[device_id];
+	struct xrt_device *xdev = ics->server->xdevs[device_id];
 
 	// Get the pose.
 	xrt_device_get_tracked_pose(xdev, name, at_timestamp, out_timestamp,
@@ -288,7 +406,7 @@ ipc_handle_device_get_tracked_pose(volatile struct ipc_client_state *cs,
 }
 
 xrt_result_t
-ipc_handle_device_get_view_pose(volatile struct ipc_client_state *cs,
+ipc_handle_device_get_view_pose(volatile struct ipc_client_state *ics,
                                 uint32_t id,
                                 struct xrt_vec3 *eye_relation,
                                 uint32_t view_index,
@@ -297,7 +415,7 @@ ipc_handle_device_get_view_pose(volatile struct ipc_client_state *cs,
 
 	// To make the code a bit more readable.
 	uint32_t device_id = id;
-	struct xrt_device *xdev = cs->server->xdevs[device_id];
+	struct xrt_device *xdev = ics->server->xdevs[device_id];
 
 	// Get the pose.
 	xrt_device_get_view_pose(xdev, eye_relation, view_index, out_pose);
@@ -306,14 +424,14 @@ ipc_handle_device_get_view_pose(volatile struct ipc_client_state *cs,
 }
 
 xrt_result_t
-ipc_handle_device_set_output(volatile struct ipc_client_state *cs,
+ipc_handle_device_set_output(volatile struct ipc_client_state *ics,
                              uint32_t id,
                              enum xrt_output_name name,
                              union xrt_output_value *value)
 {
 	// To make the code a bit more readable.
 	uint32_t device_id = id;
-	struct xrt_device *xdev = cs->server->xdevs[device_id];
+	struct xrt_device *xdev = ics->server->xdevs[device_id];
 
 	// Set the output.
 	xrt_device_set_output(xdev, name, value);
@@ -360,19 +478,19 @@ setup_epoll(int listen_socket)
  */
 
 static void
-client_loop(volatile struct ipc_client_state *cs)
+client_loop(volatile struct ipc_client_state *ics)
 {
 	fprintf(stderr, "SERVER: Client connected\n");
 
 	// Claim the client fd.
-	int epoll_fd = setup_epoll(cs->ipc_socket_fd);
+	int epoll_fd = setup_epoll(ics->ipc_socket_fd);
 	if (epoll_fd < 0) {
 		return;
 	}
 
 	uint8_t buf[IPC_BUF_SIZE];
 
-	while (cs->server->running) {
+	while (ics->server->running) {
 		const int half_a_second_ms = 500;
 		struct epoll_event event = {0};
 
@@ -380,7 +498,8 @@ client_loop(volatile struct ipc_client_state *cs)
 		int ret = epoll_wait(epoll_fd, &event, 1, half_a_second_ms);
 		if (ret < 0) {
 			fprintf(stderr,
-			        "ERROR: Failed epoll_wait '%i', disconnecting "
+			        "ERROR: Failed epoll_wait '%i', "
+			        "disconnecting "
 			        "client.\n",
 			        ret);
 			break;
@@ -398,20 +517,22 @@ client_loop(volatile struct ipc_client_state *cs)
 		}
 
 		// Finally get the data that is waiting for us.
-		ssize_t len = recv(cs->ipc_socket_fd, &buf, IPC_BUF_SIZE, 0);
+		ssize_t len = recv(ics->ipc_socket_fd, &buf, IPC_BUF_SIZE, 0);
 		if (len < 4) {
 			fprintf(stderr,
-			        "ERROR: Invalid packet received, disconnecting "
+			        "ERROR: Invalid packet received, "
+			        "disconnecting "
 			        "client.\n");
 			break;
 		}
 
 		// Check the first 4 bytes of the message and dispatch.
 		ipc_command_t *ipc_command = (uint32_t *)buf;
-		ret = ipc_dispatch(cs, ipc_command);
+		ret = ipc_dispatch(ics, ipc_command);
 		if (ret < 0) {
 			fprintf(stderr,
-			        "ERROR: During packet handling, disconnecting "
+			        "ERROR: During packet handling, "
+			        "disconnecting "
 			        "client.\n");
 			break;
 		}
@@ -420,18 +541,19 @@ client_loop(volatile struct ipc_client_state *cs)
 	close(epoll_fd);
 	epoll_fd = -1;
 
-	close(cs->ipc_socket_fd);
-	cs->ipc_socket_fd = -1;
-
-	cs->active = false;
-	cs->num_swapchains = 0;
+	close(ics->ipc_socket_fd);
+	ics->server->thread_stopping[ics->server_thread_index] = true;
+	ics->ipc_socket_fd = -1;
+	ics->server_thread_index = -1;
+	memset((void *)&ics->client_state, 0, sizeof(struct xrt_client_state));
+	ics->num_swapchains = 0;
 
 	// Make sure to reset the renderstate fully.
-	cs->rendering_state = false;
-	cs->render_state.num_layers = 0;
-	for (uint32_t i = 0; i < ARRAY_SIZE(cs->render_state.layers); ++i) {
+	ics->rendering_state = false;
+	ics->render_state.num_layers = 0;
+	for (uint32_t i = 0; i < ARRAY_SIZE(ics->render_state.layers); ++i) {
 		volatile struct ipc_layer_entry *rl =
-		    &cs->render_state.layers[i];
+		    &ics->render_state.layers[i];
 
 		rl->swapchain_ids[0] = 0;
 		rl->swapchain_ids[1] = 0;
@@ -449,17 +571,17 @@ client_loop(volatile struct ipc_client_state *cs)
 
 	// Destroy all swapchains now.
 	for (uint32_t j = 0; j < IPC_MAX_CLIENT_SWAPCHAINS; j++) {
-		xrt_swapchain_destroy((struct xrt_swapchain **)&cs->xscs[j]);
-		cs->swapchain_data[j].active = false;
-		IPC_SPEW(cs->server, "IPC: Destroyed swapchain %d\n", j);
+		xrt_swapchain_destroy((struct xrt_swapchain **)&ics->xscs[j]);
+		ics->swapchain_data[j].active = false;
+		IPC_SPEW(ics->server, "IPC: Destroyed swapchain %d\n", j);
 	}
 
 	// Should we stop the server when a client disconnects?
-	if (cs->server->exit_on_disconnect) {
-		cs->server->running = false;
+	if (ics->server->exit_on_disconnect) {
+		ics->server->running = false;
 	}
 
-	ipc_server_wait_reset_client(cs->server->iw, cs);
+	ipc_server_wait_reset_client(ics->server->iw, ics);
 }
 
 
@@ -470,14 +592,17 @@ client_loop(volatile struct ipc_client_state *cs)
  */
 
 void *
-ipc_server_client_thread(void *_cs)
+ipc_server_client_thread(void *_ics)
 {
-	volatile struct ipc_client_state *cs = _cs;
+	volatile struct ipc_client_state *ics = _ics;
+
+	client_loop(ics);
 
-	client_loop(cs);
+	// cs->session_state = XR_SESSION_STATE_STOPPING;
+	update_server_state(ics->server);
 
-	cs->server->thread_stopping = true;
-	cs->server->thread_started = false;
+	ics->server->thread_stopping[ics->server_thread_index] = true;
+	ics->server->thread_started[ics->server_thread_index] = false;
 
 	return NULL;
 }
diff --git a/src/xrt/ipc/ipc_server_process.c b/src/xrt/ipc/ipc_server_process.c
index 0a44f520cd9d5c4a832ef243ced0f3cb39b8cbc2..867df4e2ad7933adaf674de26a7e2f7a3d43e109 100644
--- a/src/xrt/ipc/ipc_server_process.c
+++ b/src/xrt/ipc/ipc_server_process.c
@@ -14,6 +14,7 @@
 #include "xrt/xrt_compositor.h"
 #include "xrt/xrt_config_have.h"
 
+#include "os/os_time.h"
 #include "util/u_var.h"
 #include "util/u_misc.h"
 #include "util/u_debug.h"
@@ -50,10 +51,14 @@
  *
  */
 
-#define IPC_MAX_CLIENTS 8
-
 DEBUG_GET_ONCE_BOOL_OPTION(exit_on_disconnect, "IPC_EXIT_ON_DISCONNECT", false)
 
+struct _z_sort_data
+{
+	int32_t index;
+	int32_t z_order;
+};
+
 
 /*
  *
@@ -384,7 +389,7 @@ init_all(struct ipc_server *s)
 	s->running = true;
 	s->exit_on_disconnect = debug_get_bool_option_exit_on_disconnect();
 
-	int ret = xrt_instance_create(&s->xinst);
+	int ret = xrt_instance_create(&s->xinst, NULL);
 	if (ret < 0) {
 		teardown_all(s);
 		return ret;
@@ -447,6 +452,8 @@ init_all(struct ipc_server *s)
 	u_var_add_bool(s, &s->exit_on_disconnect, "exit_on_disconnect");
 	u_var_add_bool(s, (void *)&s->running, "running");
 
+
+
 	return 0;
 }
 
@@ -459,26 +466,46 @@ handle_listen(struct ipc_server *vs)
 		vs->running = false;
 	}
 
-	volatile struct ipc_client_state *cs = &vs->thread_state;
+	volatile struct ipc_client_state *cs = &vs->thread_state[0];
+	int32_t cs_index = -1;
 
 	// The return is the new fd;
 	int fd = ret;
 
-	if (vs->thread_started && !vs->thread_stopping) {
-		fprintf(stderr, "ERROR: Client already connected!\n");
+	// find the next free thread in our array (server_thread_index is -1)
+	// and have it handle this connection
+	for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
+		volatile struct ipc_client_state *_cs = &vs->thread_state[i];
+		if (_cs->server_thread_index < 0) {
+			cs = _cs;
+			cs_index = i;
+			break;
+		}
+	}
+	if (cs == NULL) {
+		fprintf(stderr, "ERROR: Max client count reached!\n");
+		close(fd);
+		return;
+	}
+
+	if (vs->thread_started[cs_index] && !vs->thread_stopping[cs_index]) {
+		// we should not get here
+		fprintf(stderr, "ERROR: Client state management error!\n");
 		close(fd);
 		return;
 	}
 
-	if (vs->thread_stopping) {
-		os_thread_join((struct os_thread *)&vs->thread);
-		os_thread_destroy((struct os_thread *)&vs->thread);
-		vs->thread_stopping = false;
+	if (vs->thread_stopping[cs_index]) {
+		os_thread_join((struct os_thread *)&vs->thread[cs_index]);
+		os_thread_destroy((struct os_thread *)&vs->thread[cs_index]);
+		vs->thread_stopping[cs_index] = false;
 	}
 
-	vs->thread_started = true;
+	vs->thread_started[cs_index] = true;
 	cs->ipc_socket_fd = fd;
-	os_thread_start((struct os_thread *)&vs->thread,
+	cs->server = vs;
+	cs->server_thread_index = cs_index;
+	os_thread_start((struct os_thread *)&vs->thread[cs_index],
 	                ipc_server_client_thread, (void *)cs);
 }
 
@@ -514,6 +541,57 @@ check_epoll(struct ipc_server *vs)
 	}
 }
 
+static uint32_t
+find_event_slot(volatile struct ipc_client_state *cs)
+{
+	uint64_t oldest_event_timestamp = UINT64_MAX;
+	uint32_t oldest_event_index = 0;
+	for (uint32_t i = 0; i < IPC_EVENT_QUEUE_SIZE; i++) {
+		if (cs->queued_events->timestamp < oldest_event_timestamp) {
+			oldest_event_index = i;
+		}
+		if (!cs->queued_events[i].pending) {
+			return i;
+		}
+	}
+
+	fprintf(stderr, "ERROR! event queue full - unconsumed event lost!\n");
+	return oldest_event_index;
+}
+
+static void
+transition_overlay_visibility(volatile struct ipc_client_state *cs,
+                              bool visible)
+{
+	uint32_t event_slot = find_event_slot(cs);
+	uint64_t timestamp = os_monotonic_get_ns();
+	volatile struct ipc_queued_event *qe = &cs->queued_events[event_slot];
+	qe->timestamp = timestamp;
+	qe->pending = true;
+	qe->event.valid = true;
+	qe->event.type = XRT_EVENT_OVERLAY;
+	struct xrt_overlay_event oe;
+	oe.visible = visible;
+	memcpy((void *)qe->event.data, &oe, sizeof(oe));
+}
+
+static void
+send_client_state(volatile struct ipc_client_state *ics)
+{
+	uint32_t event_slot = find_event_slot(ics);
+	uint64_t timestamp = os_monotonic_get_ns();
+	volatile struct ipc_queued_event *qe = &ics->queued_events[event_slot];
+	qe->timestamp = timestamp;
+	qe->pending = true;
+	qe->event.valid = true;
+	qe->event.type = XRT_EVENT_CLIENT_STATE;
+	struct xrt_client_state_event cse;
+	cse.active = ics->client_state.session_active;
+	cse.visible = ics->client_state.session_visible;
+	cse.focused = ics->client_state.session_focused;
+	memcpy((void *)qe->event.data, &cse, sizeof(cse));
+}
+
 static bool
 _update_projection_layer(struct comp_compositor *c,
                          volatile struct ipc_client_state *active_client,
@@ -576,75 +654,138 @@ _update_quad_layer(struct comp_compositor *c,
 	return true;
 }
 
+static int
+_overlay_sort_func(const void *a, const void *b)
+{
+	struct _z_sort_data *oa = (struct _z_sort_data *)a;
+	struct _z_sort_data *ob = (struct _z_sort_data *)b;
+	if (oa->z_order < ob->z_order) {
+		return -1;
+	} else if (oa->z_order > ob->z_order) {
+		return 1;
+	}
+	return 0;
+}
+
 static bool
 _update_layers(struct comp_compositor *c,
-               volatile struct ipc_client_state *active_client,
+               struct ipc_server *s,
                uint32_t *num_layers)
 {
-	volatile struct ipc_layer_slot *render_state =
-	    &active_client->render_state;
+	struct _z_sort_data z_data[IPC_MAX_CLIENTS];
+
+	// initialise, and fill in overlay app data
+	for (int32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
+		volatile struct ipc_client_state *ics = &s->thread_state[i];
+		z_data[i].index = -1;
+		z_data[i].z_order = -1;
+		// we need to create a list of overlay applications, sorted by z
+		if (ics->client_state.session_overlay) {
+			if (ics->client_state.session_active) {
+				z_data[i].index = i;
+				z_data[i].z_order = ics->client_state.z_order;
+			}
+		}
+	}
 
-	if (*num_layers != render_state->num_layers) {
-		//! @todo Resizing here would be faster
-		*num_layers = render_state->num_layers;
-		comp_renderer_destroy_layers(c->r);
-		comp_renderer_allocate_layers(c->r, render_state->num_layers);
+	// ensure our primary application is enabled,
+	// and rendered first in the stack
+	if (s->active_client_index >= 0) {
+		z_data[s->active_client_index].index = s->active_client_index;
+		z_data[s->active_client_index].z_order = INT32_MIN;
 	}
 
-	for (uint32_t i = 0; i < render_state->num_layers; i++) {
-		volatile struct ipc_layer_entry *layer =
-		    &render_state->layers[i];
-		switch (layer->data.type) {
-		case XRT_LAYER_STEREO_PROJECTION: {
-			if (!_update_projection_layer(c, active_client, layer,
-			                              i))
-				return false;
-			break;
-		}
-		case XRT_LAYER_QUAD: {
-			if (!_update_quad_layer(c, active_client, layer, i))
-				return false;
-			break;
+	// sort the stack array
+	qsort(z_data, IPC_MAX_CLIENTS, sizeof(struct _z_sort_data),
+	      _overlay_sort_func);
+
+	// count the layers
+	uint32_t n = 0;
+	for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
+		struct _z_sort_data *zd = &z_data[i];
+		if (zd->index >= 0) {
+			volatile struct ipc_client_state *ics =
+			    &s->thread_state[zd->index];
+			n += ics->render_state.num_layers;
 		}
+	}
+
+	if (*num_layers != n) {
+		// TODO: Resizing here would be faster
+		*num_layers = n;
+		comp_renderer_destroy_layers(c->r);
+		comp_renderer_allocate_layers(c->r, n);
+	}
+
+	// render the layer stack
+	uint32_t layer_id = 0;
+	for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
+		struct _z_sort_data *zd = &z_data[i];
+		if (zd->index >= 0) {
+			volatile struct ipc_client_state *ics =
+			    &s->thread_state[zd->index];
+			for (uint32_t j = 0; j < ics->render_state.num_layers;
+			     j++) {
+
+				volatile struct ipc_layer_entry *layer =
+				    &ics->render_state.layers[j];
+				switch (layer->data.type) {
+				case XRT_LAYER_STEREO_PROJECTION: {
+					_update_projection_layer(c, ics, layer,
+					                         layer_id);
+					layer_id++;
+					break;
+				}
+				case XRT_LAYER_QUAD: {
+					_update_quad_layer(c, ics, layer,
+					                   layer_id);
+					layer_id++;
+					break;
+				}
+				}
+			}
 		}
 	}
 	return true;
 }
 
+
+
 static int
-main_loop(struct ipc_server *vs)
+main_loop(struct ipc_server *s)
 {
-	struct xrt_compositor *xc = vs->xc;
+	struct xrt_compositor *xc = s->xc;
 	struct comp_compositor *c = comp_compositor(xc);
 
-	// make sure all our client connections have a handle to the compositor
-	// and consistent initial state
-	vs->thread_state.server = vs;
-	vs->thread_state.xc = xc;
+	// make sure all our client connections have a handle to the
+	// compositor and consistent initial state
 
 	uint32_t num_layers = 0;
 
-	while (vs->running) {
+	while (s->running) {
 
 		/*
 		 * Check polling.
 		 */
-		check_epoll(vs);
+		check_epoll(s);
 
 		/*
 		 * Update active client.
 		 */
 
 		volatile struct ipc_client_state *active_client = NULL;
-		if (vs->thread_state.active) {
-			active_client = &vs->thread_state;
+		if (s->active_client_index >= 0) {
+			active_client =
+			    &s->thread_state[s->active_client_index];
 		}
 
+
+
 		/*
 		 * Render the swapchains.
 		 */
 
-		if (active_client == NULL || !active_client->active ||
+		if (active_client == NULL ||
 		    active_client->num_swapchains == 0) {
 			if (num_layers != 0) {
 				COMP_DEBUG(c, "Destroying layers.");
@@ -656,17 +797,19 @@ main_loop(struct ipc_server *vs)
 			// swapchain indices and toggle wait to false
 			// when the client calls end_frame, signalling
 			// us to render.
+
 			if (active_client->rendering_state) {
-				if (!_update_layers(c, active_client,
-				                    &num_layers))
+				if (!_update_layers(c, s, &num_layers))
 					continue;
-
 				// set our client state back to waiting.
 				active_client->rendering_state = false;
 			}
 		}
 
 		comp_renderer_draw(c->r);
+		// we should release any slots that are not used by a
+		// rendering client
+
 
 		// Now is a good time to destroy objects.
 		comp_compositor_garbage_collect(c);
@@ -676,12 +819,188 @@ main_loop(struct ipc_server *vs)
 }
 
 
+static void
+handle_overlay_client_events(volatile struct ipc_client_state *ics,
+                             int active_id,
+                             int prev_active_id)
+{
+	// this is an overlay session.
+	if (ics->client_state.session_overlay) {
+
+		// switch between main applications
+		if (active_id >= 0 && prev_active_id >= 0) {
+			transition_overlay_visibility(ics, false);
+			transition_overlay_visibility(ics, true);
+		}
+
+		// switch from idle to active application
+		if (active_id >= 0 && prev_active_id < 0) {
+			transition_overlay_visibility(ics, true);
+		}
+
+		// switch from active application to idle
+		if (active_id < 0 && prev_active_id >= 0) {
+			transition_overlay_visibility(ics, false);
+		}
+	}
+}
+
+static void
+handle_focused_client_events(volatile struct ipc_client_state *ics,
+                             int active_id,
+                             int prev_active_id)
+{
+
+	// if our prev active id is -1 and our cur active id is -1, we
+	// can bail out early
+
+	if (active_id == -1 && prev_active_id == -1) {
+		return;
+	}
+
+	// set visibility/focus to false on all applications
+	ics->client_state.session_focused = false;
+	ics->client_state.session_visible = false;
+
+	// do we have a primary application?
+	if (active_id >= 0) {
+
+		// if we are an overlay, we are always visible
+		// if we have a primary application
+		if (ics->client_state.session_overlay) {
+			ics->client_state.session_visible = true;
+		}
+
+		// set visible + focused if we are the primary
+		// application
+		if (ics->server_thread_index == active_id) {
+			ics->client_state.session_visible = true;
+			ics->client_state.session_focused = true;
+		}
+		send_client_state(ics);
+		return;
+	}
+
+	// no primary application, set all overlays to synchronised
+	// state
+	if (ics->client_state.session_overlay) {
+		ics->client_state.session_focused = false;
+		ics->client_state.session_visible = false;
+		send_client_state(ics);
+	}
+}
+
+
+void
+init_server_state(struct ipc_server *s)
+{
+
+	// set up initial state for global vars, and each client state
+
+	s->active_client_index = -1; // we start off with no active client.
+	s->last_active_client_index = -1;
+	s->current_slot_index = 0;
+
+	for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
+		volatile struct ipc_client_state *cs = &s->thread_state[i];
+		cs->server = s;
+		cs->xc = s->xc;
+		cs->server_thread_index = -1;
+	}
+}
+
 /*
  *
  * Exported functions.
  *
  */
 
+void
+update_server_state(struct ipc_server *s)
+{
+
+	pthread_mutex_lock(&s->global_state_lock); // multiple threads
+	                                           // could call this at
+	                                           // the same time
+
+	// if our client that is set to active is still active,
+	// and it is the same as our last active client, we can
+	// early-out, as no events need to be sent
+
+	if (s->active_client_index >= 0) {
+
+		volatile struct ipc_client_state *ics =
+		    &s->thread_state[s->active_client_index];
+
+		if (ics->client_state.session_active &&
+		    s->active_client_index == s->last_active_client_index) {
+			pthread_mutex_unlock(&s->global_state_lock);
+			return;
+		}
+	}
+
+
+	// our active application has changed - this would typically be
+	// switched by the monado-ctl application or other app making a
+	// 'set active application' ipc call, or it could be a
+	// connection loss resulting in us needing to 'fall through' to
+	// the first active application
+	//, or finally to the idle 'wallpaper' images.
+
+
+	bool set_idle = true;
+	int fallback_active_application = -1;
+
+	// do we have a fallback application?
+	for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
+		volatile struct ipc_client_state *ics = &s->thread_state[i];
+		if (ics->client_state.session_overlay == false &&
+		    ics->server_thread_index >= 0 &&
+		    ics->client_state.session_active) {
+			fallback_active_application = i;
+			set_idle = false;
+		}
+	}
+
+	// if our currently-set active primary application is not
+	// actually active/displayable, use the fallback application
+	// instead.
+	volatile struct ipc_client_state *ics =
+	    &s->thread_state[s->active_client_index];
+	if (!(ics->client_state.session_overlay == false &&
+	      s->active_client_index >= 0 &&
+	      ics->client_state.session_active)) {
+		s->active_client_index = fallback_active_application;
+	}
+
+
+	// if we have no applications to fallback to, enable the idle
+	// wallpaper.
+	if (set_idle) {
+		s->active_client_index = -1;
+	}
+
+	for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
+
+		volatile struct ipc_client_state *ics = &s->thread_state[i];
+		if (ics->server_thread_index >= 0) {
+
+			handle_focused_client_events(
+			    ics, s->active_client_index,
+			    s->last_active_client_index);
+
+			handle_overlay_client_events(
+			    ics, s->active_client_index,
+			    s->last_active_client_index);
+		}
+	}
+
+	s->last_active_client_index = s->active_client_index;
+
+	pthread_mutex_unlock(&s->global_state_lock);
+	return;
+}
+
 int
 ipc_server_main(int argc, char **argv)
 {
@@ -692,6 +1011,7 @@ ipc_server_main(int argc, char **argv)
 		return ret;
 	}
 
+	init_server_state(s);
 	ret = main_loop(s);
 
 	teardown_all(s);
diff --git a/src/xrt/ipc/proto.json b/src/xrt/ipc/proto.json
index 6b4177b8b2da5a6f22e3cd332e1b4d02b9a03a4c..0282025be9e715c8283cab519970ccbfaf402cf0 100644
--- a/src/xrt/ipc/proto.json
+++ b/src/xrt/ipc/proto.json
@@ -3,6 +3,45 @@
 		"out_fds": true
 	},
 
+	"system_get_client_info": {
+		"in": [
+			{"name": "id", "type": "uint32_t"}
+		],
+		"out": [
+			{"name": "desc", "type": "struct xrt_client_state"}
+		]
+	},
+
+	"system_set_client_info": {
+		"in": [
+			{"name": "desc", "type": "struct xrt_client_state"}
+		]
+	},
+
+	"system_get_clients": {
+		"out": [
+			{"name": "clients", "type": "struct ipc_client_list"}
+		]
+	},
+
+	"system_set_primary_client": {
+		"in": [
+			{"name": "id", "type": "uint32_t"}
+		]
+	},
+
+	"system_set_focused_client": {
+		"in": [
+			{"name": "id", "type": "uint32_t"}
+		]
+	},
+
+	"session_create": {
+		"in": [
+			{"name": "overlay_info", "type": "struct xrt_overlay_info"}
+		]
+	},
+
 	"session_begin": {},
 
 	"session_end": {},
@@ -13,8 +52,7 @@
 		]
 	},
 
-	"compositor_wait_frame": {
-	},
+	"compositor_wait_frame": {},
 
 	"compositor_begin_frame": {},
 
@@ -29,6 +67,12 @@
 		]
 	},
 
+	"compositor_poll_events": {
+		"out": [
+			{"name": "event", "type": "struct xrt_ipc_event"}
+		]
+	},
+
 	"swapchain_create": {
 		"in": [
 			{"name": "create", "type": "enum xrt_swapchain_create_flags"},
diff --git a/src/xrt/ipc/proto.py b/src/xrt/ipc/proto.py
index ca669c0512c74a479d29312db97d7ff264c7987f..a5af0fffcdd1a208750a46de326a69e9c0c4c504 100755
--- a/src/xrt/ipc/proto.py
+++ b/src/xrt/ipc/proto.py
@@ -246,6 +246,7 @@ def generate_client_c(file, p):
     f.write(header.format(brief='Generated IPC client code', suffix='_client'))
     f.write('''
 #include "ipc_client.h"
+#include "ipc_protocol_generated.h"
 
 
 // clang-format off
@@ -305,6 +306,7 @@ def generate_client_h(file, p):
 #pragma once
 
 #include "ipc_protocol.h"
+#include "ipc_protocol_generated.h"
 #include "ipc_client.h"
 
 
@@ -409,6 +411,7 @@ def generate_server_header(file, p):
 #pragma once
 
 #include "ipc_protocol.h"
+#include "ipc_protocol_generated.h"
 #include "ipc_server.h"
 
 
diff --git a/src/xrt/state_trackers/gui/gui_prober.c b/src/xrt/state_trackers/gui/gui_prober.c
index bbdfbd67b4af4c82da3d57800e04e8a4cb355c70..f7636b9d6e8ff8e0c020ad3afa0d56f21dd54ff5 100644
--- a/src/xrt/state_trackers/gui/gui_prober.c
+++ b/src/xrt/state_trackers/gui/gui_prober.c
@@ -38,7 +38,7 @@ gui_prober_init(struct gui_program *p)
 	int ret = 0;
 
 	// Initialize the prober.
-	ret = xrt_instance_create(&p->instance);
+	ret = xrt_instance_create(&p->instance, NULL);
 	if (ret != 0) {
 		return do_exit(p, ret);
 	}
diff --git a/src/xrt/state_trackers/oxr/oxr_event.c b/src/xrt/state_trackers/oxr/oxr_event.c
index ec906d7b4d3b4b20e68d7c3cab2cd09e9a528ec4..5c2931d1edc7657ea07058dd7a5d229ac12c807f 100644
--- a/src/xrt/state_trackers/oxr/oxr_event.c
+++ b/src/xrt/state_trackers/oxr/oxr_event.c
@@ -157,6 +157,26 @@ oxr_event_push_XrEventDataSessionStateChanged(struct oxr_logger *log,
 	return XR_SUCCESS;
 }
 
+XrResult
+oxr_event_push_XrEventDataMainSessionVisibilityChangedEXTX(
+    struct oxr_logger *log, struct oxr_session *sess, bool visible)
+{
+	struct oxr_instance *inst = sess->sys->inst;
+	XrEventDataMainSessionVisibilityChangedEXTX *changed;
+	struct oxr_event *event = NULL;
+
+	ALLOC(log, inst, &event, &changed);
+	changed->type = XR_TYPE_EVENT_DATA_MAIN_SESSION_VISIBILITY_CHANGED_EXTX;
+	changed->flags = 0;
+	changed->visible = visible;
+	event->result = XR_SUCCESS;
+	lock(inst);
+	push(inst, event);
+	unlock(inst);
+
+	return XR_SUCCESS;
+}
+
 XrResult
 oxr_event_remove_session_events(struct oxr_logger *log,
                                 struct oxr_session *sess)
@@ -202,7 +222,7 @@ oxr_poll_event(struct oxr_logger *log,
 {
 	struct oxr_session *sess = inst->sessions;
 	while (sess) {
-		oxr_session_poll(sess);
+		oxr_session_poll(log, sess);
 		sess = sess->next;
 	}
 
diff --git a/src/xrt/state_trackers/oxr/oxr_extension_support.h b/src/xrt/state_trackers/oxr/oxr_extension_support.h
index 3b67dd02029939f015748a1eb90287f64bd41d55..0f8cd7974776e5d595af956dc3b0f73b21e5ee4e 100644
--- a/src/xrt/state_trackers/oxr/oxr_extension_support.h
+++ b/src/xrt/state_trackers/oxr/oxr_extension_support.h
@@ -101,6 +101,16 @@
 #define OXR_EXTENSION_SUPPORT_MND_headless(_)
 #endif
 
+/*
+ * XR_EXTX_overlay
+ */
+#if defined(XR_EXTX_overlay)
+#define OXR_HAVE_EXTX_overlay
+#define OXR_EXTENSION_SUPPORT_EXTX_overlay(_) _(EXTX_overlay, EXTX_OVERLAY)
+#else
+#define OXR_EXTENSION_SUPPORT_EXTX_overlay(_)
+#endif
+
 // end of GENERATED per-extension defines - do not modify - used by scripts
 
 /*!
@@ -131,5 +141,6 @@
     OXR_EXTENSION_SUPPORT_KHR_opengl_es_enable(_) \
     OXR_EXTENSION_SUPPORT_KHR_vulkan_enable(_) \
     OXR_EXTENSION_SUPPORT_MNDX_egl_enable(_) \
-    OXR_EXTENSION_SUPPORT_MND_headless(_)
+    OXR_EXTENSION_SUPPORT_MND_headless(_) \
+    OXR_EXTENSION_SUPPORT_EXTX_overlay(_)
 // clang-format on
diff --git a/src/xrt/state_trackers/oxr/oxr_instance.c b/src/xrt/state_trackers/oxr/oxr_instance.c
index f8910417b948beaa78ade77fe652761be5bc6d93..db324f8fa7f7399fa532e50dfc51879c56db2acf 100644
--- a/src/xrt/state_trackers/oxr/oxr_instance.c
+++ b/src/xrt/state_trackers/oxr/oxr_instance.c
@@ -7,11 +7,6 @@
  * @ingroup oxr_main
  */
 
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <math.h>
-
 #include "util/u_var.h"
 #include "util/u_time.h"
 #include "util/u_misc.h"
@@ -24,6 +19,13 @@
 #include "oxr_handle.h"
 #include "oxr_extension_support.h"
 
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
 
 DEBUG_GET_ONCE_BOOL_OPTION(debug_views, "OXR_DEBUG_VIEWS", false)
 DEBUG_GET_ONCE_BOOL_OPTION(debug_spaces, "OXR_DEBUG_SPACES", false)
@@ -167,7 +169,16 @@ oxr_instance_create(struct oxr_logger *log,
 	cache_path(log, inst, "/interaction_profiles/mnd/ball_on_stick_controller", &inst->path_cache.mnd_ball_on_stick_controller);
 	// clang-format on
 
-	xinst_ret = xrt_instance_create(&inst->xinst);
+	// fill in our application info - @todo - replicate all createInfo
+	// fields?
+
+	struct xrt_instance_info i_info = {0};
+	i_info.pid = (int32_t)getpid();
+	snprintf(i_info.application_name,
+	         sizeof(inst->xinst->instance_info.application_name), "%s",
+	         createInfo->applicationInfo.applicationName);
+
+	xinst_ret = xrt_instance_create(&inst->xinst, &i_info);
 	if (xinst_ret != 0) {
 		ret = oxr_error(log, XR_ERROR_RUNTIME_FAILURE,
 		                "Failed to create prober");
diff --git a/src/xrt/state_trackers/oxr/oxr_objects.h b/src/xrt/state_trackers/oxr/oxr_objects.h
index a15dca7f05fba030bc3c1c25eb91d7d839f51839..89feaa7ec2b9e408397ec3c296a0e1209ffc60c9 100644
--- a/src/xrt/state_trackers/oxr/oxr_objects.h
+++ b/src/xrt/state_trackers/oxr/oxr_objects.h
@@ -615,7 +615,7 @@ XrResult
 oxr_session_request_exit(struct oxr_logger *log, struct oxr_session *sess);
 
 void
-oxr_session_poll(struct oxr_session *sess);
+oxr_session_poll(struct oxr_logger *log, struct oxr_session *sess);
 
 /*!
  * Get the view space position at the given time in relation to the
@@ -828,6 +828,10 @@ oxr_event_push_XrEventDataSessionStateChanged(struct oxr_logger *log,
                                               XrSessionState state,
                                               XrTime time);
 
+XrResult
+oxr_event_push_XrEventDataMainSessionVisibilityChangedEXTX(
+    struct oxr_logger *log, struct oxr_session *sess, bool visible);
+
 /*!
  * This clears all pending events refers to the given session.
  */
diff --git a/src/xrt/state_trackers/oxr/oxr_session.c b/src/xrt/state_trackers/oxr/oxr_session.c
index 35b7e541996c4ba30b8591024a47f440cbab9359..42c4b2f01b777da9119da7c1cf009ec5d7d5785e 100644
--- a/src/xrt/state_trackers/oxr/oxr_session.c
+++ b/src/xrt/state_trackers/oxr/oxr_session.c
@@ -16,6 +16,7 @@
 #include "util/u_debug.h"
 #include "util/u_misc.h"
 #include "util/u_time.h"
+#include "os/os_time.h"
 
 #include "math/m_api.h"
 
@@ -210,10 +211,49 @@ oxr_session_request_exit(struct oxr_logger *log, struct oxr_session *sess)
 }
 
 void
-oxr_session_poll(struct oxr_session *sess)
+oxr_session_poll(struct oxr_logger *log, struct oxr_session *sess)
 {
 	struct xrt_compositor *xc = sess->compositor;
-	(void)xc; // TODO: dispatch to compositor
+	struct xrt_ipc_event e;
+	while (true) {
+		xc->poll_events(xc, &e);
+		if (!e.valid) {
+			break;
+		}
+
+		// dispatch based on event type
+		struct xrt_client_state_event *xcse =
+		    (struct xrt_client_state_event *)e.data;
+		switch (e.type) {
+		case XRT_EVENT_CLIENT_STATE: {
+
+			if (xcse->visible && sess->state) {
+				oxr_session_change_state(
+				    log, sess, XR_SESSION_STATE_VISIBLE);
+			}
+			if (xcse->focused) { // @todo: this only works
+				             // because focused always
+				             // coincides with visible
+				oxr_session_change_state(
+				    log, sess, XR_SESSION_STATE_FOCUSED);
+			}
+			if (!xcse->visible) {
+				oxr_session_change_state(
+				    log, sess, XR_SESSION_STATE_SYNCHRONIZED);
+			}
+
+
+		} break;
+		case XRT_EVENT_OVERLAY: {
+			struct xrt_overlay_event *ove =
+			    (struct xrt_overlay_event *)&e.data;
+			oxr_event_push_XrEventDataMainSessionVisibilityChangedEXTX(
+			    log, sess, ove->visible);
+		} break;
+
+		default: fprintf(stderr, "unhandled event type! %d", e.type);
+		}
+	}
 }
 
 XrResult
@@ -452,11 +492,15 @@ oxr_session_frame_wait(struct oxr_logger *log,
 		    frameState->predictedDisplayTime);
 	}
 
-	if (!sess->has_waited_once) {
+	if (!sess->has_waited_once && sess->state < XR_SESSION_STATE_VISIBLE) {
 		oxr_session_change_state(log, sess,
 		                         XR_SESSION_STATE_SYNCHRONIZED);
-		oxr_session_change_state(log, sess, XR_SESSION_STATE_VISIBLE);
-		oxr_session_change_state(log, sess, XR_SESSION_STATE_FOCUSED);
+		// oxr_session_change_state(log, sess,
+		// XR_SESSION_STATE_VISIBLE); //these states will be handled by
+		// messages received from the compositor
+		// oxr_session_change_state(log, sess,
+		// XR_SESSION_STATE_FOCUSED); //these states will be handled by
+		// messages received from the compositor
 		sess->has_waited_once = true;
 	}
 
@@ -1024,9 +1068,14 @@ oxr_session_frame_end(struct oxr_logger *log,
 
 
 	struct xrt_pose inv_offset = {0};
+
+
+
 	math_pose_invert(&sess->sys->head->tracking_origin->offset,
 	                 &inv_offset);
 
+
+
 	CALL_CHK(xrt_comp_layer_begin(xc, blend_mode));
 
 	for (uint32_t i = 0; i < frameEndInfo->layerCount; i++) {
@@ -1034,6 +1083,17 @@ oxr_session_frame_end(struct oxr_logger *log,
 		    frameEndInfo->layers[i];
 		assert(layer != NULL);
 
+		struct oxr_space *layer_space =
+		    (struct oxr_space *)layer->space;
+		if (layer_space->type == XR_REFERENCE_SPACE_TYPE_VIEW) {
+			inv_offset.position.x = 0.0f;
+			inv_offset.position.y = 0.0f;
+			inv_offset.position.z = 0.0f;
+			inv_offset.orientation.x = 0.0f;
+			inv_offset.orientation.y = 0.0f;
+			inv_offset.orientation.z = 0.0f;
+			inv_offset.orientation.w = 1.0f;
+		}
 		switch (layer->type) {
 		case XR_TYPE_COMPOSITION_LAYER_PROJECTION:
 			submit_projection_layer(
@@ -1195,6 +1255,22 @@ oxr_session_create(struct oxr_logger *log,
 		return ret;
 	}
 
+	struct xrt_compositor *xc = sess->compositor;
+
+	if (xc != NULL) {
+		struct xrt_overlay_info oi = {0};
+		const XrSessionCreateInfoOverlayEXTX *overlay_info =
+		    OXR_GET_INPUT_FROM_CHAIN(
+		        createInfo, XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX,
+		        XrSessionCreateInfoOverlayEXTX);
+		if (overlay_info) {
+			oi.is_overlay = true;
+			oi.flags = overlay_info->createFlags;
+			oi.z_order = overlay_info->sessionLayersPlacement;
+		}
+		xrt_comp_prepare_session(xc, &oi);
+	}
+
 	sess->ipd_meters = debug_get_num_option_ipd() / 1000.0f;
 	sess->static_prediction_s =
 	    debug_get_num_option_prediction_ms() / 1000.0f;
diff --git a/src/xrt/targets/CMakeLists.txt b/src/xrt/targets/CMakeLists.txt
index 1a7c98445211f401f8f5bed1e24e7a950d8781fa..5da63f6136ba4d8ce72a0b558b3ee60394ff1da4 100644
--- a/src/xrt/targets/CMakeLists.txt
+++ b/src/xrt/targets/CMakeLists.txt
@@ -20,4 +20,5 @@ endif()
 
 if(XRT_FEATURE_SERVICE)
 	add_subdirectory(service)
+        add_subdirectory(monado-ctl)
 endif()
diff --git a/src/xrt/targets/cli/cli_cmd_calibrate.c b/src/xrt/targets/cli/cli_cmd_calibrate.c
index eaa4fd91a0425cf165afb27f54e70dd59af8376d..2f3e788293ac640522dd08f9a59684537782ff0b 100644
--- a/src/xrt/targets/cli/cli_cmd_calibrate.c
+++ b/src/xrt/targets/cli/cli_cmd_calibrate.c
@@ -32,7 +32,7 @@ init(struct program *p)
 	int ret;
 
 	// Fist initialize the instance.
-	ret = xrt_instance_create(&p->xi);
+	ret = xrt_instance_create(&p->xi, NULL);
 	if (ret != 0) {
 		fprintf(stderr, "Failed to create instance\n");
 		return ret;
diff --git a/src/xrt/targets/cli/cli_cmd_probe.c b/src/xrt/targets/cli/cli_cmd_probe.c
index 9ed1e2fa811c5c8b1bcfd43817037c0c96611e71..f59c69a3a86b644c0d45c7b6eaf8b072c823d599 100644
--- a/src/xrt/targets/cli/cli_cmd_probe.c
+++ b/src/xrt/targets/cli/cli_cmd_probe.c
@@ -36,7 +36,7 @@ cli_cmd_probe(int argc, const char **argv)
 	// Initialize the prober.
 	printf(" :: Creating instance!\n");
 
-	ret = xrt_instance_create(&xi);
+	ret = xrt_instance_create(&xi, NULL);
 	if (ret != 0) {
 		return do_exit(&xi, 0);
 	}
diff --git a/src/xrt/targets/cli/cli_cmd_test.c b/src/xrt/targets/cli/cli_cmd_test.c
index aace672721ae72053b2f31496b9f11b34764caed..fc0b62fbb365f43f0f8712da797735c7bd569dec 100644
--- a/src/xrt/targets/cli/cli_cmd_test.c
+++ b/src/xrt/targets/cli/cli_cmd_test.c
@@ -36,7 +36,7 @@ cli_cmd_test(int argc, const char **argv)
 	// Initialize the prober.
 	printf(" :: Creating instance!\n");
 
-	ret = xrt_instance_create(&xi);
+	ret = xrt_instance_create(&xi, NULL);
 	if (ret != 0) {
 		return do_exit(&xi, 0);
 	}
diff --git a/src/xrt/targets/common/target_instance.c b/src/xrt/targets/common/target_instance.c
index 60cf99a2c50a4bb96f20256c9d0e2940e924cf8b..fd6ce91655b2594c41127c12913fba5756d8090f 100644
--- a/src/xrt/targets/common/target_instance.c
+++ b/src/xrt/targets/common/target_instance.c
@@ -35,7 +35,8 @@ t_instance_create_fd_compositor(struct xrt_instance *xinst,
  */
 
 int
-xrt_instance_create(struct xrt_instance **out_xinst)
+xrt_instance_create(struct xrt_instance **out_xinst,
+                    struct xrt_instance_info *i_info)
 {
 	struct xrt_prober *xp = NULL;
 
diff --git a/src/xrt/targets/common/target_instance_no_comp.c b/src/xrt/targets/common/target_instance_no_comp.c
index 48fc4899b93cc80710e2eb0a3d46e54896894ecd..927e8e26956a316c9c47d4477c46f65ba22639f6 100644
--- a/src/xrt/targets/common/target_instance_no_comp.c
+++ b/src/xrt/targets/common/target_instance_no_comp.c
@@ -29,7 +29,8 @@ t_instance_create_fd_compositor_stub(struct xrt_instance *xinst,
  */
 
 int
-xrt_instance_create(struct xrt_instance **out_xinst)
+xrt_instance_create(struct xrt_instance **out_xinst,
+                    struct xrt_instance_info *i_info)
 {
 	struct xrt_prober *xp = NULL;
 
diff --git a/src/xrt/targets/monado-ctl/CMakeLists.txt b/src/xrt/targets/monado-ctl/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6015210378e917d33eb0204e36f82202da94a0c6
--- /dev/null
+++ b/src/xrt/targets/monado-ctl/CMakeLists.txt
@@ -0,0 +1,21 @@
+# Copyright 2020, Collabora, Ltd.
+# SPDX-License-Identifier: BSL-1.0
+
+
+add_executable(monado-ctl
+	main.c
+	)
+
+    target_include_directories(monado-ctl PRIVATE
+    ipc
+    )
+
+target_link_libraries(monado-ctl PRIVATE
+        aux_util
+        ipc_client
+	)
+
+install(TARGETS monado-ctl
+	RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+	)
+
diff --git a/src/xrt/targets/monado-ctl/main.c b/src/xrt/targets/monado-ctl/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..8644badcbdff762b4616b11672cd29a55bc7cc06
--- /dev/null
+++ b/src/xrt/targets/monado-ctl/main.c
@@ -0,0 +1,161 @@
+
+#include "ipc_client.h"
+#include "ipc_client_generated.h"
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <ctype.h>
+#include <unistd.h>
+
+typedef enum op_mode
+{
+	MODE_GET,
+	MODE_SET_PRIMARY,
+	MODE_SET_FOCUSED,
+
+} op_mode_t;
+
+
+int
+main(int argc, char *argv[])
+{
+	ipc_connection_t ipc_c;
+	os_mutex_init(&ipc_c.mutex);
+
+	op_mode_t op_mode = MODE_GET;
+
+	// parse arguments
+	int c;
+	int s_val = 0;
+
+	opterr = 0;
+	while ((c = getopt(argc, argv, "p:f:")) != -1) {
+		switch (c) {
+		case 'p':
+			s_val = atoi(optarg);
+			if (s_val >= 0 && s_val < IPC_MAX_CLIENTS) {
+				op_mode = MODE_SET_PRIMARY;
+			}
+			break;
+		case 'f':
+			s_val = atoi(optarg);
+			if (s_val >= 0 && s_val < IPC_MAX_CLIENTS) {
+				op_mode = MODE_SET_FOCUSED;
+			}
+			break;
+
+		case '?':
+			if (optopt == 's') {
+				fprintf(stderr,
+				        "Option -s requires an id to set.\n");
+			} else if (isprint(optopt)) {
+				fprintf(stderr, "Option `-%c' unknown.\n",
+				        optopt);
+			} else {
+				fprintf(stderr, "Option `\\x%x' unknown.\n",
+				        optopt);
+			}
+			exit(1);
+		default: exit(0);
+		}
+	}
+
+	bool socket_created = true;
+	bool socket_connected = true;
+
+	int fd;
+	struct sockaddr_un addr;
+
+	if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+		printf("Socket Create Error!\n");
+		socket_created = false;
+	}
+
+	if (socket_created) {
+		memset(&addr, 0, sizeof(addr));
+		addr.sun_family = AF_UNIX;
+		strcpy(addr.sun_path, IPC_MSG_SOCK_FILE);
+		if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+			printf("Socket Connect error!\n");
+			socket_connected = false;
+		}
+	}
+
+	if (socket_connected) {
+
+		ipc_c.socket_fd = fd;
+
+		struct xrt_client_state cs;
+		cs.info.pid = getpid();
+		snprintf(cs.info.application_name,
+		         sizeof(cs.info.application_name), "%s", "monado-ctl");
+
+		xrt_result_t r =
+		    ipc_call_system_set_client_info(&ipc_c, &cs);
+
+		if (r != XRT_SUCCESS) {
+			printf("failed to set client info.\n");
+			exit(1);
+		}
+		switch (op_mode) {
+		case MODE_GET: {
+
+			struct ipc_client_list clients;
+			xrt_result_t r =
+			    ipc_call_system_get_clients(&ipc_c, &clients);
+			if (r != XRT_SUCCESS) {
+				printf("failed to get client list.\n");
+				exit(1);
+			}
+
+			for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
+				if (clients.ids[i] >= 0) {
+					struct xrt_client_state cs;
+					xrt_result_t r =
+					    ipc_call_system_get_client_info(
+					        &ipc_c, i, &cs);
+					if (r != XRT_SUCCESS) {
+						printf(
+						    "failed to get client info "
+						    "for client %d.\n",
+						    i);
+						exit(1);
+					}
+					printf(
+					    "id: %d\tact: %d\tdisp: "
+					    "%d\tfoc: %d\tovly: %d\tz: "
+					    "%d\tpid: "
+					    "%d\t %s\t\n",
+					    clients.ids[i], cs.session_active,
+					    cs.session_visible,
+					    cs.session_focused,
+					    cs.session_overlay, cs.z_order,
+					    cs.info.pid,
+					    cs.info.application_name);
+				}
+			}
+
+		} break;
+		case MODE_SET_PRIMARY: {
+			xrt_result_t r =
+			    ipc_call_system_set_primary_client(&ipc_c, s_val);
+			if (r != XRT_SUCCESS) {
+				printf("failed to set active client to %d.\n",
+				       s_val);
+				exit(1);
+			}
+		} break;
+		case MODE_SET_FOCUSED: {
+			xrt_result_t r =
+			    ipc_call_system_set_focused_client(&ipc_c, s_val);
+			if (r != XRT_SUCCESS) {
+				printf("failed to set focused client to %d.\n",
+				       s_val);
+				exit(1);
+			}
+		} break;
+		default: printf("Unrecognised operation mode.\n"); exit(1);
+		}
+	}
+	close(fd);
+}
diff --git a/src/xrt/targets/openxr/target.c b/src/xrt/targets/openxr/target.c
index 16ba1aaf747b2dd0c71968c178fad3c2fa13aebe..be2f8280fb4869b98ff0049a17d80221b87469f4 100644
--- a/src/xrt/targets/openxr/target.c
+++ b/src/xrt/targets/openxr/target.c
@@ -14,12 +14,14 @@
 
 // Forward declaration
 int
-ipc_instance_create(struct xrt_instance **out_xinst);
+ipc_instance_create(struct xrt_instance **out_xinst,
+                    struct xrt_instance_info *i_info);
 
 int
-xrt_instance_create(struct xrt_instance **out_xinst)
+xrt_instance_create(struct xrt_instance **out_xinst,
+                    struct xrt_instance_info *i_info)
 {
-	return ipc_instance_create(out_xinst);
+	return ipc_instance_create(out_xinst, i_info);
 }
 
 #else