diff --git a/indra/llui/llchat.h b/indra/llui/llchat.h
index 027581078a46df3bf0053b4bbb713a4689166c49..523175dee08bc566c3c5b47506047a33f209cca0 100644
--- a/indra/llui/llchat.h
+++ b/indra/llui/llchat.h
@@ -52,7 +52,8 @@ typedef enum e_chat_type
 	CHAT_TYPE_DEBUG_MSG = 6,
 	CHAT_TYPE_REGION = 7,
 	CHAT_TYPE_OWNER = 8,
-	CHAT_TYPE_DIRECT = 9		// From llRegionSayTo()
+	CHAT_TYPE_DIRECT = 9,		// From llRegionSayTo()
+	CHAT_TYPE_OOC = 64
 } EChatType;
 
 typedef enum e_chat_audible_level
diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h
index 3f13691a3051287526707b84aba491c200c4c630..bac309cd8089f25c8b8f6e440a8e125817c74adb 100644
--- a/indra/llui/llchatentry.h
+++ b/indra/llui/llchatentry.h
@@ -79,11 +79,13 @@ class LLChatEntry : public LLTextEditor
 	 */
 	void	expandText();
 
+public:
 	/**
 	 * Implements line history so previous entries can be recalled by CTRL UP/DOWN
 	 */
 	void	updateHistory();
 
+private:
 	BOOL	handleSpecialKey(const KEY key, const MASK mask);
 
 
diff --git a/indra/newview/llchatbar.cpp b/indra/newview/llchatbar.cpp
index 8833e85772670da55ed1a440193949e8e5cde1b0..0300e14fc950cb4f76bf5f84c4242672e9f7b41a 100644
--- a/indra/newview/llchatbar.cpp
+++ b/indra/newview/llchatbar.cpp
@@ -173,6 +173,12 @@ BOOL LLChatBar::handleKeyHere( KEY key, MASK mask )
 			sendChat(CHAT_TYPE_SHOUT);
 			handled = TRUE;
 		}
+		else if (mask == MASK_ALT)
+		{
+			// shout
+			sendChat(CHAT_TYPE_OOC);
+			handled = TRUE;
+		}
 		else if (mask == MASK_NONE)
 		{
 			// say
diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp
index d543012e83519253e2bfa9f8f0e4892740cd4201..65b8db2bb9b87bda24939fa19806fefa40dc69de 100644
--- a/indra/newview/llfloaterimnearbychat.cpp
+++ b/indra/newview/llfloaterimnearbychat.cpp
@@ -508,6 +508,12 @@ BOOL LLFloaterIMNearbyChat::handleKeyHere( KEY key, MASK mask )
 		sendChat(CHAT_TYPE_SHOUT);
 		handled = TRUE;
 	}
+	else if (KEY_RETURN == key && mask == MASK_ALT)
+	{
+		// shout
+		sendChat(CHAT_TYPE_OOC);
+		handled = TRUE;
+	}
 
 	if((mask == MASK_ALT) && isTornOff())
 	{
@@ -687,7 +693,7 @@ EChatType LLFloaterIMNearbyChat::processChatTypeTriggers(EChatType type, std::st
 
 void LLFloaterIMNearbyChat::sendChat( EChatType type )
 {
-	processChatIntern(mInputEditor, type);
+	processChat(mInputEditor, type, [&](const auto& emojistr) { updateUsedEmojis(emojistr); });
 
 	// If the user wants to stop chatting on hitting return, lose focus
 	// and go out of chat mode.
diff --git a/indra/newview/llfloaterimnearbychat.h b/indra/newview/llfloaterimnearbychat.h
index c3db4943abf934ae9108a90cb5b8e7281fc82b50..e4c6f0d42fe5b8469e87b2c2aca5202554a833e0 100644
--- a/indra/newview/llfloaterimnearbychat.h
+++ b/indra/newview/llfloaterimnearbychat.h
@@ -92,10 +92,7 @@ class LLFloaterIMNearbyChat final
 	static void sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate);
 
 	template <class T>
-	void processChatIntern(T* editor, EChatType type);
-
-	template <class T>
-	static void processChat(T* editor, EChatType type);
+	static void processChat(T* editor, EChatType type, std::function<void(const LLWString&)> func = nullptr);
 
 	static bool isWordsName(const std::string& name);
 
@@ -149,7 +146,7 @@ class LLFloaterIMNearbyChat final
 };
 
 template <class T>
-void LLFloaterIMNearbyChat::processChat(T* editor, EChatType type)
+void LLFloaterIMNearbyChat::processChat(T* editor, EChatType type, std::function<void(const LLWString&)> emoji_func)
 {
 	if (editor)
 	{
@@ -158,10 +155,11 @@ void LLFloaterIMNearbyChat::processChat(T* editor, EChatType type)
 		LLWStringUtil::replaceChar(text, 182, '\n'); // Convert paragraph symbols back into newlines.
 		if (!text.empty())
 		{
-			// Check if this is destined for another channel
-			S32 channel = 0;
-			stripChannelNumber(text, &channel);
-
+			if (emoji_func != nullptr)
+			{
+				emoji_func(text);
+			}
+			else
 			{
 				LLEmojiDictionary* dictionary = LLEmojiDictionary::getInstance();
 				llassert_always(dictionary);
@@ -180,59 +178,17 @@ void LLFloaterIMNearbyChat::processChat(T* editor, EChatType type)
 					LLFloaterEmojiPicker::saveState();
 			}
 
-			std::string utf8text = wstring_to_utf8str(text);
-			// Try to trigger a gesture, if not chat to a script.
-			std::string utf8_revised_text;
-			if (0 == channel)
-			{
-				applyOOCClose(utf8text);
-				applyMUPose(utf8text);
-
-				// discard returned "found" boolean
-				if (!LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text))
-				{
-					utf8_revised_text = utf8text;
-				}
-			}
-			else
-			{
-				utf8_revised_text = utf8text;
-			}
-
-			utf8_revised_text = utf8str_trim(utf8_revised_text);
-
-			type = processChatTypeTriggers(type, utf8_revised_text);
-
-			if (!utf8_revised_text.empty() && !ALChatCommand::parseCommand(utf8_revised_text))
-			{
-				// Chat with animation
-				sendChatFromViewer(utf8_revised_text, type, gSavedSettings.getBOOL("PlayTypingAnim"));
-			}
-		}
-
-		editor->setText(LLStringExplicit(""));
-	}
-
-	gAgent.stopTyping();
-}
-
-template <class T>
-void LLFloaterIMNearbyChat::processChatIntern(T* editor, EChatType type)
-{
-	if (editor)
-	{
-		LLWString text = editor->getWText();
-		LLWStringUtil::trim(text);
-		LLWStringUtil::replaceChar(text, 182, '\n'); // Convert paragraph symbols back into newlines.
-		if (!text.empty())
-		{
 			// Check if this is destined for another channel
 			S32 channel = 0;
 			stripChannelNumber(text, &channel);
 
-			updateUsedEmojis(text);
-
 			std::string utf8text = wstring_to_utf8str(text);
+
+			if (type == CHAT_TYPE_OOC)
+			{
+				utf8text = fmt::format("{} {} {}", gSavedSettings.getString("ChatOOCPrefix"), utf8text, gSavedSettings.getString("ChatOOCPostfix"));
+			}
+
 			// Try to trigger a gesture, if not chat to a script.
 			std::string utf8_revised_text;
 			if (0 == channel)
@@ -253,7 +209,8 @@ void LLFloaterIMNearbyChat::processChatIntern(T* editor, EChatType type)
 
 			utf8_revised_text = utf8str_trim(utf8_revised_text);
 
-			type = processChatTypeTriggers(type, utf8_revised_text);
+			EChatType nType = (type == CHAT_TYPE_OOC ? CHAT_TYPE_NORMAL : type);
+			type = processChatTypeTriggers(nType, utf8_revised_text);
 
 			if (!utf8_revised_text.empty() && !ALChatCommand::parseCommand(utf8_revised_text))
 			{
diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp
index 54b41240a16b2e3c8792474d5b9f8b0a9ffc8b37..1b88249332d4ad0eea5ec19d9ee5bf9a4e8845ed 100644
--- a/indra/newview/llfloaterimsession.cpp
+++ b/indra/newview/llfloaterimsession.cpp
@@ -306,7 +306,7 @@ void LLFloaterIMSession::onTeleportClicked(const LLUICtrl* pCtrl)
 }
 // [/SL:KB]
 
-void LLFloaterIMSession::sendMsgFromInputEditor()
+void LLFloaterIMSession::sendMsgFromInputEditor(bool ooc_chat)
 {
 	if (gAgent.isGodlike()
 		|| (mDialog != IM_NOTHING_SPECIAL)
@@ -323,6 +323,12 @@ void LLFloaterIMSession::sendMsgFromInputEditor()
 
 				// Truncate and convert to UTF8 for transport
 				std::string utf8_text = wstring_to_utf8str(text);
+
+				if (ooc_chat)
+				{
+					utf8_text = fmt::format("{} {} {}", gSavedSettings.getString("ChatOOCPrefix"), utf8_text, gSavedSettings.getString("ChatOOCPostfix"));
+				}
+
 				applyOOCClose(utf8_text);
                 applyMUPose(utf8_text);
 				sendMsg(utf8_text);
@@ -1514,6 +1520,20 @@ void LLFloaterIMSession::onIMChicletCreated( const LLUUID& session_id )
 	LLFloaterIMSession::addToHost(session_id);
 }
 
+// virtual
+BOOL LLFloaterIMSession::handleKeyHere(KEY key, MASK mask)
+{
+	BOOL handled = FALSE;
+
+	if (KEY_RETURN == key && mask == MASK_ALT)
+	{
+		mInputEditor->updateHistory();
+		sendMsgFromInputEditor(true);
+		handled = TRUE;
+	}
+	return handled;
+}
+
 boost::signals2::connection LLFloaterIMSession::setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb)
 {
 	return LLFloaterIMSession::sIMFloaterShowedSignal.connect(cb);
diff --git a/indra/newview/llfloaterimsession.h b/indra/newview/llfloaterimsession.h
index 6b169206abe037793ed0a826a1f28ed59ae6fa48..e58960d3858cd15077fcc984646aa9783f26daa0 100644
--- a/indra/newview/llfloaterimsession.h
+++ b/indra/newview/llfloaterimsession.h
@@ -97,7 +97,7 @@ class LLFloaterIMSession final
 	/*virtual*/ void updateMessages();
 	void reloadMessages(bool clean_messages = false);
 	static void onSendMsg(LLUICtrl*, void*);
-	void sendMsgFromInputEditor();
+	void sendMsgFromInputEditor(bool ooc_chat = false);
 	void sendMsg(const std::string& msg);
 
 	// callback for LLIMModel on new messages
@@ -143,6 +143,8 @@ class LLFloaterIMSession final
 
 	bool needsTitleOverwrite() { return mSessionNameUpdatedForTyping && mOtherTyping; }
 	S32 getLastChatMessageIndex() {return mLastMessageIndex;}
+
+	BOOL handleKeyHere(KEY key, MASK mask) override;
 private:
 
 	/*virtual*/ void refresh();