diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp
index 2dbd59b156021b103e6569f33d34f1247b140ae8..6b1413d05494851ca76373a91ed5f3569e93df1b 100644
--- a/indra/llcommon/lleventdispatcher.cpp
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -109,6 +109,16 @@ bool LLEventDispatcher::attemptCall(const std::string& name, const LLSD& event)
     return true;                    // tell caller we were able to call
 }
 
+LLEventDispatcher::Callable LLEventDispatcher::get(const std::string& name) const
+{
+    DispatchMap::const_iterator found = mDispatch.find(name);
+    if (found == mDispatch.end())
+    {
+        return Callable();
+    }
+    return found->second.first;
+}
+
 LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
     LLEventDispatcher(pumpname, key),
     mPump(pumpname, true),          // allow tweaking for uniqueness
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h
index ef83ebabc1fa60cb0b5a5a0c2eb713e6304eba5e..671f2a4d1cf19944a659d9d3e21704db4485db63 100644
--- a/indra/llcommon/lleventdispatcher.h
+++ b/indra/llcommon/lleventdispatcher.h
@@ -80,6 +80,10 @@ class LL_COMMON_API LLEventDispatcher
     /// @a required prototype specified at add() time, die with LL_ERRS.
     void operator()(const LLSD& event) const;
 
+    /// Fetch the Callable for the specified name. If no such name was
+    /// registered, return an empty() Callable.
+    Callable get(const std::string& name) const;
+
 private:
     template <class CLASS, typename METHOD>
     void addMethod(const std::string& name, const METHOD& method, const LLSD& required)
diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp
index 8bf769a13211b02c24d2c633152c80d911913a40..e5f347ddc404696933b6ccc7e78840d67268670d 100644
--- a/indra/newview/lllogininstance.cpp
+++ b/indra/newview/lllogininstance.cpp
@@ -68,10 +68,14 @@ LLLoginInstance::LLLoginInstance() :
 	mUserInteraction(true),
 	mSkipOptionalUpdate(false),
 	mAttemptComplete(false),
-	mTransferRate(0.0f)
+	mTransferRate(0.0f),
+	mDispatcher("LLLoginInstance", "change")
 {
 	mLoginModule->getEventPump().listen("lllogininstance", 
 		boost::bind(&LLLoginInstance::handleLoginEvent, this, _1));
+	mDispatcher.add("fail.login", boost::bind(&LLLoginInstance::handleLoginFailure, this, _1));
+	mDispatcher.add("connect",    boost::bind(&LLLoginInstance::handleLoginSuccess, this, _1));
+	mDispatcher.add("disconnect", boost::bind(&LLLoginInstance::handleDisconnect, this, _1));
 }
 
 LLLoginInstance::~LLLoginInstance()
@@ -185,9 +189,9 @@ bool LLLoginInstance::handleLoginEvent(const LLSD& event)
 	std::cout << "LoginListener called!: \n";
 	std::cout << event << "\n";
 
-	if(!(event.has("state") && event.has("progress")))
+	if(!(event.has("state") && event.has("change") && event.has("progress")))
 	{
-		llerrs << "Unknown message from LLLogin!" << llendl;
+		llerrs << "Unknown message from LLLogin: " << event << llendl;
 	}
 
 	mLoginState = event["state"].asString();
@@ -198,19 +202,17 @@ bool LLLoginInstance::handleLoginEvent(const LLSD& event)
 		mTransferRate = event["transfer_rate"].asReal();
 	}
 
-	if(mLoginState == "offline")
+	// Call the method registered in constructor, if any, for more specific
+	// handling
+	LLEventDispatcher::Callable method(mDispatcher.get(event["change"]));
+	if (! method.empty())
 	{
-		handleLoginFailure(event);
+		method(event);
 	}
-	else if(mLoginState == "online")
-	{
-		handleLoginSuccess(event);
-	}
-
 	return false;
 }
 
-bool LLLoginInstance::handleLoginFailure(const LLSD& event)
+void LLLoginInstance::handleLoginFailure(const LLSD& event)
 {
 	// Login has failed. 
 	// Figure out why and respond...
@@ -264,11 +266,9 @@ bool LLLoginInstance::handleLoginFailure(const LLSD& event)
 	{
 		attemptComplete();
 	}
-
-	return false;
 }
 
-bool LLLoginInstance::handleLoginSuccess(const LLSD& event)
+void LLLoginInstance::handleLoginSuccess(const LLSD& event)
 {
 	if(gSavedSettings.getBOOL("ForceMandatoryUpdate"))
 	{
@@ -286,7 +286,11 @@ bool LLLoginInstance::handleLoginSuccess(const LLSD& event)
 	{
 		attemptComplete();
 	}
-	return false;
+}
+
+void LLLoginInstance::handleDisconnect(const LLSD& event)
+{
+    // placeholder
 }
 
 bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key)
diff --git a/indra/newview/lllogininstance.h b/indra/newview/lllogininstance.h
index 6a2ccf919e3c2e5fa0e0a28697d4e44f0c077df6..19d7449bc15bfc7eef3af996c3e7667e014ecbde 100644
--- a/indra/newview/lllogininstance.h
+++ b/indra/newview/lllogininstance.h
@@ -33,6 +33,7 @@
 #ifndef LL_LLLOGININSTANCE_H
 #define LL_LLLOGININSTANCE_H
 
+#include "lleventdispatcher.h"
 #include <boost/scoped_ptr.hpp>
 #include <boost/function.hpp>
 class LLLogin;
@@ -85,8 +86,9 @@ class LLLoginInstance : public LLSingleton<LLLoginInstance>
 	bool updateDialogCallback(const LLSD& notification, const LLSD& response);
 
 	bool handleLoginEvent(const LLSD& event);
-	bool handleLoginFailure(const LLSD& event);
-	bool handleLoginSuccess(const LLSD& event);
+	void handleLoginFailure(const LLSD& event);
+	void handleLoginSuccess(const LLSD& event);
+	void handleDisconnect(const LLSD& event);
 
 	bool handleTOSResponse(bool v, const std::string& key);
 
@@ -106,6 +108,7 @@ class LLLoginInstance : public LLSingleton<LLLoginInstance>
 	int mLastExecEvent;
 	UpdaterLauncherCallback mUpdaterLauncher;
 	boost::scoped_ptr<LLEventStream> mUpdateAppResponse;
+	LLEventDispatcher mDispatcher;
 };
 
 #endif
diff --git a/indra/newview/tests/lllogininstance_test.cpp b/indra/newview/tests/lllogininstance_test.cpp
index 75db76df27e29e21943e3c30ccf98150d7addb72..009be35f641390be0b032aaee9355d0c85694733 100644
--- a/indra/newview/tests/lllogininstance_test.cpp
+++ b/indra/newview/tests/lllogininstance_test.cpp
@@ -225,6 +225,7 @@ namespace tut
 		// Dummy success response.
 		LLSD response;
 		response["state"] = "online";
+		response["change"] = "connect";
 		response["progress"] = 1.0;
 		response["transfer_rate"] = 7;
 		response["data"] = "test_data";
@@ -240,6 +241,7 @@ namespace tut
 
 		response.clear();
 		response["state"] = "offline";
+		response["change"] = "disconnect";
 		response["progress"] = 0.0;
 		response["transfer_rate"] = 0;
 		response["data"] = "test_data";
@@ -267,6 +269,7 @@ namespace tut
 		// TOS failure response.
 		LLSD response;
 		response["state"] = "offline";
+		response["change"] = "fail.login";
 		response["progress"] = 0.0;
 		response["transfer_rate"] = 7;
 		response["data"]["reason"] = "tos";
@@ -326,6 +329,7 @@ namespace tut
 		// Update needed failure response.
 		LLSD response;
 		response["state"] = "offline";
+		response["change"] = "fail.login";
 		response["progress"] = 0.0;
 		response["transfer_rate"] = 7;
 		response["data"]["reason"] = "update";
@@ -351,6 +355,7 @@ namespace tut
 		// Update needed failure response.
 		LLSD response;
 		response["state"] = "offline";
+		response["change"] = "fail.login";
 		response["progress"] = 0.0;
 		response["transfer_rate"] = 7;
 		response["data"]["reason"] = "update";
@@ -376,6 +381,7 @@ namespace tut
 		// Update needed failure response.
 		LLSD response;
 		response["state"] = "offline";
+		response["change"] = "fail.login";
 		response["progress"] = 0.0;
 		response["transfer_rate"] = 7;
 		response["data"]["reason"] = "optional";
@@ -401,6 +407,7 @@ namespace tut
 		// Update needed failure response.
 		LLSD response;
 		response["state"] = "offline";
+		response["change"] = "fail.login";
 		response["progress"] = 0.0;
 		response["transfer_rate"] = 7;
 		response["data"]["reason"] = "optional";
diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp
index c0d35f31d3d69e7dba54486eacfb7cb827d6d4bd..7a30315b9a8c7c30b110f4c3f78bf84c1fe942f8 100644
--- a/indra/viewer_components/login/lllogin.cpp
+++ b/indra/viewer_components/login/lllogin.cpp
@@ -70,10 +70,12 @@ class LLLogin::Impl
 	LLEventPump& getEventPump() { return mPump; }
 
 private:
-	void sendProgressEvent(const std::string& desc, const LLSD& data = LLSD::emptyMap())
+	void sendProgressEvent(const std::string& state, const std::string& change,
+						   const LLSD& data = LLSD())
 	{
 		LLSD status_data;
-		status_data["state"] = desc;
+		status_data["state"] = state;
+		status_data["change"] = change;
 		status_data["progress"] = 0.0f;
 
 		if(mAuthResponse.has("transfer_rate"))
@@ -81,7 +83,7 @@ class LLLogin::Impl
 			status_data["transfer_rate"] = mAuthResponse["transfer_rate"];
 		}
 
-		if(data.size() != 0)
+		if(data.isDefined())
 		{
 			status_data["data"] = data;
 		}
@@ -133,7 +135,7 @@ void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD credential
     LLSD rewrittenURIs;
     {
         LLEventTimeout filter(replyPump);
-        sendProgressEvent("srvrequest");
+        sendProgressEvent("offline", "srvrequest");
 
         // Request SRV record.
         LL_INFOS("LLLogin") << "Requesting SRV record from " << uri << LL_ENDL;
@@ -172,7 +174,7 @@ void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD credential
             LLSD progress_data;
             progress_data["attempt"] = attempts;
             progress_data["request"] = request;
-            sendProgressEvent("authenticating", progress_data);
+            sendProgressEvent("offline", "authenticating", progress_data);
 
             // We expect zero or more "Downloading" status events, followed by
             // exactly one event with some other status. Use postAndWait() the
@@ -187,7 +189,7 @@ void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD credential
                                      waitForEventOn(self, replyPump)))
             {
                 // Still Downloading -- send progress update.
-                sendProgressEvent("downloading");
+                sendProgressEvent("offline", "downloading");
             }
             status = mAuthResponse["status"].asString();
 
@@ -215,9 +217,14 @@ void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD credential
             // StatusComplete does not imply auth success. Check the
             // actual outcome of the request. We've already handled the
             // "indeterminate" case in the loop above.
-            sendProgressEvent((mAuthResponse["responses"]["login"].asString() == "true")?
-                              "online" : "offline",
-                              mAuthResponse["responses"]);
+            if (mAuthResponse["responses"]["login"].asString() == "true")
+            {
+                sendProgressEvent("online", "connect", mAuthResponse["responses"]);
+            }
+            else
+            {
+                sendProgressEvent("offline", "fail.login", mAuthResponse["responses"]);
+            }
             return;             // Done!
         }
         // If we don't recognize status at all, trouble
@@ -236,12 +243,12 @@ void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD credential
     // Here we got through all the rewrittenURIs without succeeding. Tell
     // caller this didn't work out so well. Of course, the only failure data
     // we can reasonably show are from the last of the rewrittenURIs.
-    sendProgressEvent("offline", mAuthResponse["responses"]);
+    sendProgressEvent("offline", "fail.login", mAuthResponse["responses"]);
 }
 
 void LLLogin::Impl::disconnect()
 {
-    sendProgressEvent("offline", mAuthResponse["responses"]);
+    sendProgressEvent("offline", "disconnect");
 }
 
 //*********************
diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp
index 51f00c8344514efee03d2823f3d67b7896e45ca5..a8ae2883d54ddcd13899e2d4a7bdc6284c46f686 100644
--- a/indra/viewer_components/login/tests/lllogin_test.cpp
+++ b/indra/viewer_components/login/tests/lllogin_test.cpp
@@ -265,12 +265,12 @@ namespace tut
 
 		login.connect("login.bar.com", credentials);
 
-		ensure_equals("SRV state", listener.lastEvent()["state"].asString(), "srvrequest"); 
+		ensure_equals("SRV state", listener.lastEvent()["change"].asString(), "srvrequest"); 
 
 		dummyLLAres.sendReply();
 
 		// Test Authenticating State prior to first response.
-		ensure_equals("Auth state 1", listener.lastEvent()["state"].asString(), "authenticating"); 
+		ensure_equals("Auth state 1", listener.lastEvent()["change"].asString(), "authenticating"); 
 		ensure_equals("Attempt 1", listener.lastEvent()["data"]["attempt"].asInteger(), 1); 
 		ensure_equals("URI 1", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.foo.com"); 
 
@@ -285,7 +285,7 @@ namespace tut
 		dummyXMLRPC.setResponse(data);
 		dummyXMLRPC.sendReply();
 
-		ensure_equals("Fail back to authenticate 1", listener.lastEvent()["state"].asString(), "authenticating"); 
+		ensure_equals("Fail back to authenticate 1", listener.lastEvent()["change"].asString(), "authenticating"); 
 		ensure_equals("Attempt 2", listener.lastEvent()["data"]["attempt"].asInteger(), 2); 
 		ensure_equals("URI 2", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.bar.com"); 
 
@@ -301,7 +301,7 @@ namespace tut
 		dummyXMLRPC.setResponse(data);
 		dummyXMLRPC.sendReply();
 
-		ensure_equals("Fail back to authenticate 2", listener.lastEvent()["state"].asString(), "authenticating"); 
+		ensure_equals("Fail back to authenticate 2", listener.lastEvent()["change"].asString(), "authenticating"); 
 		ensure_equals("Attempt 3", listener.lastEvent()["data"]["attempt"].asInteger(), 3); 
 		ensure_equals("URI 3", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.indeterminate.com"); 
 
@@ -350,11 +350,11 @@ namespace tut
 
 		login.connect("login.bar.com", credentials);
 
-		ensure_equals("SRV state", listener.lastEvent()["state"].asString(), "srvrequest"); 
+		ensure_equals("SRV state", listener.lastEvent()["change"].asString(), "srvrequest"); 
 
 		dummyLLAres.sendReply();
 
-		ensure_equals("Auth state", listener.lastEvent()["state"].asString(), "authenticating"); 
+		ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); 
 
 		// Send the failed auth request reponse
 		LLSD data;
@@ -397,11 +397,11 @@ namespace tut
 
 		login.connect("login.bar.com", credentials);
 
-		ensure_equals("SRV state", listener.lastEvent()["state"].asString(), "srvrequest"); 
+		ensure_equals("SRV state", listener.lastEvent()["change"].asString(), "srvrequest"); 
 
 		dummyLLAres.sendReply();
 
-		ensure_equals("Auth state", listener.lastEvent()["state"].asString(), "authenticating"); 
+		ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating"); 
 
 		// Send the failed auth request reponse
 		LLSD data;