diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 8451bc1223e9a00ec1a1ee520596055b797f3464..37df00d9ec45a8acb296a1c47b1e69ff86a21b37 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -394,6 +394,7 @@ set(viewer_SOURCE_FILES
     lltoolselectland.cpp
     lltoolselectrect.cpp
     lltracker.cpp
+    lluilistener.cpp
     lluploaddialog.cpp
     llurl.cpp
     llurldispatcher.cpp
@@ -848,6 +849,7 @@ set(viewer_HEADER_FILES
     lltoolselectrect.h
     lltracker.h
     lluiconstants.h
+    lluilistener.h
     lluploaddialog.h
     llurl.h
     llurldispatcher.h
diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9c643e78dec9ee2fcd8f0833a7697ec3308a75f6
--- /dev/null
+++ b/indra/newview/lluilistener.cpp
@@ -0,0 +1,50 @@
+/**
+ * @file   lluilistener.cpp
+ * @author Nat Goodspeed
+ * @date   2009-08-18
+ * @brief  Implementation for lluilistener.
+ * 
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "llviewerprecompiledheaders.h"
+// associated header
+#include "lluilistener.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "lluictrl.h"
+#include "llerror.h"
+
+LLUIListener::LLUIListener(const std::string& name):
+    LLDispatchListener(name, "op")
+{
+    add("call", &LLUIListener::call, LLSD().insert("function", LLSD()));
+}
+
+void LLUIListener::call(const LLSD& event) const
+{
+    LLUICtrl::commit_callback_t* func =
+        LLUICtrl::CommitCallbackRegistry::getValue(event["function"]);
+    if (! func)
+    {
+        // This API is intended for use by a script. It's a fire-and-forget
+        // API: we provide no reply. Therefore, a typo in the script will
+        // provide no feedback whatsoever to that script. To rub the coder's
+        // nose in such an error, crump rather than quietly ignoring it.
+        LL_ERRS("LLUIListener") << "function '" << event["function"] << "' not found" << LL_ENDL;
+    }
+    else
+    {
+        // Interestingly, view_listener_t::addMenu() (addCommit(),
+        // addEnable()) constructs a commit_callback_t callable that accepts
+        // two parameters but discards the first. Only the second is passed to
+        // handleEvent(). Therefore we feel completely safe passing NULL for
+        // the first parameter.
+        (*func)(NULL, event["parameter"]);
+    }
+}
diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h
new file mode 100644
index 0000000000000000000000000000000000000000..ea904a99ff86b82bae69b85d79a920ad5d84cc9f
--- /dev/null
+++ b/indra/newview/lluilistener.h
@@ -0,0 +1,29 @@
+/**
+ * @file   lluilistener.h
+ * @author Nat Goodspeed
+ * @date   2009-08-18
+ * @brief  Engage named functions as specified by XUI
+ * 
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLUILISTENER_H)
+#define LL_LLUILISTENER_H
+
+#include "lleventdispatcher.h"
+#include <string>
+
+class LLSD;
+
+class LLUIListener: public LLDispatchListener
+{
+public:
+    LLUIListener(const std::string& name);
+
+private:
+    void call(const LLSD& event) const;
+};
+
+#endif /* ! defined(LL_LLUILISTENER_H) */
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index a6157aa1e0b711184eba766d869e33fe1014bf9f..67dcf6bcaebf6af7b4ab40cecc42a07199214c7a 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -210,6 +210,7 @@
 #include "llwlparammanager.h"
 #include "llwaterparammanager.h"
 #include "llfloaternotificationsconsole.h"
+#include "lluilistener.h"
 
 #include "lltexlayer.h"
 
@@ -420,6 +421,8 @@ class LLMenuParcelObserver : public LLParcelObserver
 
 static LLMenuParcelObserver* gMenuParcelObserver = NULL;
 
+static LLUIListener sUIListener("UI");
+
 LLMenuParcelObserver::LLMenuParcelObserver()
 {
 	LLViewerParcelMgr::getInstance()->addObserver(this);