diff --git a/include/components/controller.hpp b/include/components/controller.hpp
index 4bedacdf..a697d6f8 100644
--- a/include/components/controller.hpp
+++ b/include/components/controller.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <moodycamel/blockingconcurrentqueue.h>
+#include <uv.h>
 
 #include <thread>
 
@@ -51,7 +52,7 @@ class controller
   bool run(bool writeback, string snapshot_dst);
 
   bool enqueue(event&& evt);
-  bool enqueue(string&& input_data);
+  bool trigger_action(string&& input_data);
 
   void stop(bool reload);
 
@@ -60,6 +61,7 @@ class controller
   void conn_cb(int status, int events);
   void ipc_cb(string buf);
   void confwatch_handler(const char* fname, int events, int status);
+  void notifier_handler();
 
  protected:
   void read_events();
@@ -95,6 +97,14 @@ class controller
 
   std::unique_ptr<eventloop> eloop;
 
+  /**
+   * \brief Async handle to notify the eventloop
+   *
+   * This handle is used to notify the eventloop of changes which are not otherwise covered by other handles.
+   * E.g. click actions.
+   */
+  std::unique_ptr<uv_async_t> m_notifier{nullptr};
+
   /**
    * \brief State flag
    */
diff --git a/include/events/types.hpp b/include/events/types.hpp
index 34a3d965..a39fd0a0 100644
--- a/include/events/types.hpp
+++ b/include/events/types.hpp
@@ -10,7 +10,6 @@ enum class event_type {
   NONE = 0,
   UPDATE,
   CHECK,
-  INPUT,
   QUIT,
 };
 
@@ -48,13 +47,6 @@ namespace {
     return event{static_cast<int>(event_type::UPDATE), force};
   }
 
-  /**
-   * Create INPUT event
-   */
-  inline event make_input_evt() {
-    return event{static_cast<int>(event_type::INPUT)};
-  }
-
   /**
    * Create CHECK event
    */
diff --git a/src/components/controller.cpp b/src/components/controller.cpp
index e5a722a0..e19f2ce8 100644
--- a/src/components/controller.cpp
+++ b/src/components/controller.cpp
@@ -160,12 +160,12 @@ bool controller::enqueue(event&& evt) {
 /**
  * Enqueue input data
  */
-bool controller::enqueue(string&& input_data) {
+bool controller::trigger_action(string&& input_data) {
   if (!m_inputdata.empty()) {
     m_log.trace("controller: Swallowing input event (pending data)");
   } else {
     m_inputdata = forward<string>(input_data);
-    return enqueue(make_input_evt());
+    UV(uv_async_send, m_notifier.get());
   }
   return false;
 }
@@ -223,6 +223,12 @@ void controller::confwatch_handler(const char* filename, int, int) {
   stop(true);
 }
 
+void controller::notifier_handler() {
+  if (!m_inputdata.empty()) {
+    process_inputdata();
+  }
+}
+
 static void ipc_alloc_cb(uv_handle_t*, size_t, uv_buf_t* buf) {
   buf->base = new char[BUFSIZ];
   buf->len = BUFSIZ;
@@ -247,6 +253,10 @@ static void ipc_read_cb_wrapper(uv_stream_t* stream, ssize_t nread, const uv_buf
   }
 }
 
+static void notifier_cb_wrapper(uv_async_t *handle) {
+  static_cast<controller*>(handle->data)->notifier_handler();
+}
+
 /**
  * Read events from configured file descriptors
  */
@@ -279,6 +289,10 @@ void controller::read_events() {
       uv_read_start((uv_stream_t*)ipc_handle.get(), ipc_alloc_cb, ipc_read_cb_wrapper);
     }
 
+    m_notifier = std::make_unique<uv_async_t>();
+    uv_async_init(loop, m_notifier.get(), notifier_cb_wrapper);
+    m_notifier->data = this;
+
     eloop->run();
   } catch (const exception& err) {
     m_log.err("Fatal Error in eventloop: %s", err.what());
@@ -317,8 +331,6 @@ void controller::process_eventqueue() {
       } else {
         on(signals::eventqueue::exit_terminate{});
       }
-    } else if (evt.type == event_type::INPUT) {
-      process_inputdata();
     } else if (evt.type == event_type::UPDATE && evt.flag) {
       process_update(true);
     } else {
@@ -328,9 +340,6 @@ void controller::process_eventqueue() {
         if (next.type == event_type::QUIT) {
           evt = next;
           break;
-        } else if (next.type == event_type::INPUT) {
-          evt = next;
-          break;
         } else if (evt.type != next.type) {
           enqueue(move(next));
           break;
@@ -342,8 +351,6 @@ void controller::process_eventqueue() {
 
       if (evt.type == event_type::UPDATE) {
         process_update(evt.flag);
-      } else if (evt.type == event_type::INPUT) {
-        process_inputdata();
       } else if (evt.type == event_type::QUIT) {
         if (evt.flag) {
           on(signals::eventqueue::exit_reload{});
@@ -763,7 +770,7 @@ bool controller::on(const signals::ui::button_press& evt) {
     return false;
   }
 
-  enqueue(move(input));
+  trigger_action(move(input));
   return true;
 }
 
@@ -779,7 +786,7 @@ bool controller::on(const signals::ipc::action& evt) {
   }
 
   m_log.info("Enqueuing ipc action: %s", action);
-  enqueue(move(action));
+  trigger_action(move(action));
   return true;
 }