diff --git a/include/components/controller.hpp b/include/components/controller.hpp
index d6755bc7..f5593a96 100644
--- a/include/components/controller.hpp
+++ b/include/components/controller.hpp
@@ -75,6 +75,8 @@ class controller
  private:
   size_t setup_modules(alignment align);
 
+  bool try_forward_legacy_action(const string cmd);
+
   connection& m_connection;
   signal_emitter& m_sig;
   const logger& m_log;
diff --git a/src/components/controller.cpp b/src/components/controller.cpp
index 5d3ef6f0..a0cc7404 100644
--- a/src/components/controller.cpp
+++ b/src/components/controller.cpp
@@ -391,66 +391,18 @@ void controller::process_eventqueue() {
 }
 
 /**
- * Process stored input data
+ * Tries to match the given command to a legacy action string and sends the
+ * appropriate new action (and data) to the right module if possible.
+ *
+ * \returns true iff the given command matches a legacy action string and was
+ *          successfully forwarded to a module
  */
-void controller::process_inputdata() {
-  if (m_inputdata.empty()) {
-    return;
-  }
-
-  const string cmd = m_inputdata;
-  m_inputdata.clear();
-
-  m_log.trace("controller: Processing inputdata: %s", cmd);
-
-  /*
-   * Module inputs have the following form (w/o the quotes): "#NAME.ACTION[.DATA]"
-   * where 'NAME' is the name of the module (for which '.' is a forbidden
-   * character) and 'ACTION' is the input that is sent to the module. 'DATA'
-   * is optional data that is attached to the action and is also sent to the
-   * module.
-   */
-  if (cmd.front() == '#') {
-    try {
-      auto res = actions_util::parse_action_string(cmd);
-
-      auto handler_name = std::get<0>(res);
-      auto action = std::get<1>(res);
-      auto data = std::get<2>(res);
-
-      m_log.info(
-          "Forwarding data to input handlers (name: '%s', action: '%s', data: '%s') ", handler_name, action, data);
-
-      int num_delivered = 0;
-
-      // Forwards the action to all input handlers that match the name
-      for (auto&& handler : m_inputhandlers) {
-        if (handler->input_handler_name() == handler_name) {
-          if (!handler->input(std::forward<string>(action), std::forward<string>(data))) {
-            m_log.err("The '%s' module does not support the '%s' action.", handler_name, action);
-          }
-
-          num_delivered++;
-        }
-      }
-
-      if (num_delivered == 0) {
-        m_log.err("There exists no input handler with name '%s' (input: %s)", handler_name, cmd);
-      } else {
-        m_log.info("Delivered input to %d input handler%s", num_delivered, num_delivered > 1 ? "s" : "");
-      }
-    } catch (runtime_error& e) {
-      m_log.err("Invalid action string (reason: %s)", e.what());
-    }
-
-    return;
-  }
-
+bool controller::try_forward_legacy_action(const string cmd) {
   /*
    * Maps legacy action names to a module type and the new action name in that module.
    *
    * We try to match the old action name as a prefix, and everything after it will also be added to the end of the new
-   * action string (for example "mpdseek+5" will be redirected to "seek+5" in the first mpd module).
+   * action string (for example "mpdseek+5" will be redirected to "seek.+5" in the first mpd module).
    *
    * The action will be delivered to the first module of that type so that it is consistent with existing behavior.
    * If the module does not support the action or no matching module is found, the command is forwarded to the shell.
@@ -511,7 +463,7 @@ void controller::process_inputdata() {
     A_MAP("menu-close", menu_module, EVENT_CLOSE),
   };
 #undef A_MAP
-// clang-format on
+  // clang-format on
 
   // Check if any key in the map is a prefix for the `cmd`
   for (const auto& entry : legacy_actions) {
@@ -537,20 +489,79 @@ void controller::process_inputdata() {
               "Forwarding legacy action '%s' to module '%s' as '%s' with data '%s'", cmd, handler_name, action, data);
           if (!handler_ptr->input(std::forward<string>(action), std::forward<string>(data))) {
             m_log.err("Failed to forward deprecated action to %s module", type);
-            /*
-             * Forward to shell if the module cannot accept the action to not break existing behavior.
-             * goto is used to break out of multiple loops at once.
-             */
-            goto forward_shell;
+            // Forward to shell if the module cannot accept the action to not break existing behavior.
+            return false;
           }
           // Only deliver to the first matching module.
-          return;
+          return true;
         }
       }
     }
   }
 
-forward_shell:
+  /*
+   * If we couldn't find any matching legacy action, we return false and let
+   * the command be forwarded to the shell
+   */
+  return false;
+}
+
+/**
+ * Process stored input data
+ */
+void controller::process_inputdata() {
+  if (m_inputdata.empty()) {
+    return;
+  }
+
+  const string cmd = m_inputdata;
+  m_inputdata.clear();
+
+  m_log.trace("controller: Processing inputdata: %s", cmd);
+
+  // Every command that starts with '#' is considered an action string.
+  if (cmd.front() == '#') {
+    string handler_name;
+    string action;
+    string data;
+
+    try {
+      auto res = actions_util::parse_action_string(cmd);
+
+      handler_name = std::get<0>(res);
+      action = std::get<1>(res);
+      data = std::get<2>(res);
+    } catch (runtime_error& e) {
+      m_log.err("Invalid action string (reason: %s)", e.what());
+      return;
+    }
+
+    m_log.info("Forwarding data to input handlers (name: '%s', action: '%s', data: '%s') ", handler_name, action, data);
+
+    int num_delivered = 0;
+
+    // Forwards the action to all input handlers that match the name
+    for (auto&& handler : m_inputhandlers) {
+      if (handler->input_handler_name() == handler_name) {
+        if (!handler->input(std::forward<string>(action), std::forward<string>(data))) {
+          m_log.err("The '%s' module does not support the '%s' action.", handler_name, action);
+        }
+
+        num_delivered++;
+      }
+    }
+
+    if (num_delivered == 0) {
+      m_log.err("There exists no input handler with name '%s' (input: %s)", handler_name, cmd);
+    } else {
+      m_log.info("Delivered input to %d input handler%s", num_delivered, num_delivered > 1 ? "s" : "");
+    }
+    return;
+  }
+
+  if (this->try_forward_legacy_action(cmd)) {
+    return;
+  }
 
   try {
     // Run input as command if it's not an input for a module