diff --git a/libempathy-gtk/empathy-chat-view.c b/libempathy-gtk/empathy-chat-view.c
index ada6698..097215c 100644
--- a/libempathy-gtk/empathy-chat-view.c
+++ b/libempathy-gtk/empathy-chat-view.c
@@ -201,3 +201,14 @@ empathy_chat_view_copy_clipboard (EmpathyChatView *view)
 	}
 }
 
+void
+empathy_chat_view_focus_toggled (EmpathyChatView *view,
+				 gboolean         has_focus)
+{
+	g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
+
+	if (EMPATHY_TYPE_CHAT_VIEW_GET_IFACE (view)->focus_toggled) {
+		EMPATHY_TYPE_CHAT_VIEW_GET_IFACE (view)->focus_toggled (view, has_focus);
+	}
+}
+
diff --git a/libempathy-gtk/empathy-chat-view.h b/libempathy-gtk/empathy-chat-view.h
index 96365a6..1af0721 100644
--- a/libempathy-gtk/empathy-chat-view.h
+++ b/libempathy-gtk/empathy-chat-view.h
@@ -68,6 +68,8 @@ struct _EmpathyChatViewIface {
 						  const gchar     *text,
 						  gboolean         match_case);
 	void             (*copy_clipboard)       (EmpathyChatView *view);
+	void             (*focus_toggled)        (EmpathyChatView *view,
+						  gboolean         has_focus);
 };
 
 GType            empathy_chat_view_get_type             (void) G_GNUC_CONST;
@@ -97,6 +99,8 @@ void             empathy_chat_view_highlight            (EmpathyChatView *view,
 							 const gchar     *text,
 							 gboolean         match_case);
 void             empathy_chat_view_copy_clipboard       (EmpathyChatView *view);
+void             empathy_chat_view_focus_toggled        (EmpathyChatView *view,
+							 gboolean         has_focus);
 
 G_END_DECLS
 
diff --git a/libempathy-gtk/empathy-chat.c b/libempathy-gtk/empathy-chat.c
index 53c4677..c146941 100644
--- a/libempathy-gtk/empathy-chat.c
+++ b/libempathy-gtk/empathy-chat.c
@@ -1753,6 +1753,14 @@ chat_input_realize_cb (GtkWidget   *widget,
 }
 
 static void
+chat_input_has_focus_notify_cb (GtkWidget   *widget,
+				GParamSpec  *pspec,
+				EmpathyChat *chat)
+{
+	empathy_chat_view_focus_toggled (chat->view, gtk_widget_has_focus (widget));
+}
+
+static void
 chat_insert_smiley_activate_cb (EmpathySmileyManager *manager,
 				EmpathySmiley        *smiley,
 				gpointer              user_data)
@@ -2710,7 +2718,9 @@ chat_create_ui (EmpathyChat *chat)
 
 	/* Add input GtkTextView */
 	chat->input_text_view = empathy_input_text_view_new ();
-
+	g_signal_connect (chat->input_text_view, "notify::has-focus",
+			  G_CALLBACK (chat_input_has_focus_notify_cb),
+			  chat);
 	g_signal_connect (chat->input_text_view, "key-press-event",
 			  G_CALLBACK (chat_input_key_press_event_cb),
 			  chat);
@@ -3789,9 +3799,11 @@ empathy_chat_messages_read (EmpathyChat *self)
 		return;
 
 	if (priv->tp_chat != NULL ) {
-			empathy_tp_chat_acknowledge_all_messages (priv->tp_chat);
+		empathy_tp_chat_acknowledge_all_messages (priv->tp_chat);
 	}
 	priv->unread_messages = 0;
+
+	empathy_chat_view_focus_toggled (self->view, TRUE);
 }
 
 /* Return TRUE if on of the contacts in this chat is composing */
diff --git a/libempathy-gtk/empathy-theme-adium.c b/libempathy-gtk/empathy-theme-adium.c
index 659bbac..3572f42 100644
--- a/libempathy-gtk/empathy-theme-adium.c
+++ b/libempathy-gtk/empathy-theme-adium.c
@@ -62,6 +62,8 @@ typedef struct {
 	GList                *message_queue;
 	GtkWidget            *inspector_window;
 	GSettings            *gsettings_chat;
+	gboolean              has_focus;
+	gboolean              has_unread_message;
 } EmpathyThemeAdiumPriv;
 
 struct _EmpathyAdiumData {
@@ -317,6 +319,28 @@ theme_adium_match (const gchar **str, const gchar *match)
 	return FALSE;
 }
 
+static gboolean
+theme_adium_match_with_format (const gchar **str, const gchar *match,
+			       gchar **format)
+{
+	const gchar *cur = *str;
+	const gchar *end;
+
+	if (!theme_adium_match (&cur, match)) {
+		return FALSE;
+	}
+	cur++;
+
+	end = strstr (cur, "}%");
+	if (!end) {
+		return FALSE;
+	}
+
+	*format = g_strndup (cur , end - cur);
+	*str = end + 1;
+	return TRUE;
+}
+
 static void
 theme_adium_append_html (EmpathyThemeAdium *theme,
 			 const gchar       *func,
@@ -338,52 +362,55 @@ theme_adium_append_html (EmpathyThemeAdium *theme,
 	string = g_string_sized_new (len + strlen (message));
 	g_string_append_printf (string, "%s(\"", func);
 	for (cur = html; *cur != '\0'; cur++) {
-		const gchar *replace = NULL;
+		const gchar *replace = "";
 		gchar       *dup_replace = NULL;
+		gchar       *format = NULL;
 
-		if (theme_adium_match (&cur, "%message%")) {
-			replace = message;
-		} else if (theme_adium_match (&cur, "%messageClasses%")) {
-			replace = message_classes;
-		} else if (theme_adium_match (&cur, "%userIconPath%")) {
+		/* Those are all well known keywords that needs replacement in
+		 * html files. Please keep them in the same order than the adium
+		 * spec. See http://trac.adium.im/wiki/CreatingMessageStyles */
+		if (theme_adium_match (&cur, "%userIconPath%")) {
 			replace = avatar_filename;
-		} else if (theme_adium_match (&cur, "%sender%")) {
-			replace = name;
 		} else if (theme_adium_match (&cur, "%senderScreenName%")) {
 			replace = contact_id;
+		} else if (theme_adium_match (&cur, "%sender%")) {
+			replace = name;
+		} else if (theme_adium_match (&cur, "%senderColor%")) {
+			/* FIXME: A color derived from the user's name. If a
+			 * colon separated list of HTML colors is at
+			 * Incoming/SenderColors.txt it will be used instead of
+			 * the default colors.
+			 */
+		} else if (theme_adium_match (&cur, "%senderStatusIcon%")) {
+			/* FIXME: The path to the status icon of the sender
+			 * (available, away, etc...)
+			 */
+		} else if (theme_adium_match (&cur, "%messageDirection%")) {
+			/* FIXME: The text direction of the message
+			 * (either rtl or ltr)
+			 */
 		} else if (theme_adium_match (&cur, "%senderDisplayName%")) {
-			/* %senderDisplayName% -
-			 * "The serverside (remotely set) name of the sender,
-			 *  such as an MSN display name."
+			/* FIXME: The serverside (remotely set) name of the
+			 * sender, such as an MSN display name.
 			 *
-			 * We don't have access to that yet so we use local
-			 * alias instead.*/
+			 *  We don't have access to that yet so we use
+			 * local alias instead.
+			 */
 			replace = name;
-		} else if (theme_adium_match (&cur, "%service%")) {
-			replace = service_name;
-		} else if (theme_adium_match (&cur, "%shortTime%")) {
-			dup_replace = empathy_time_to_string_local (timestamp,
-				EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
-			replace = dup_replace;
-		} else if (theme_adium_match (&cur, "%time")) {
-			gchar *format = NULL;
-			gchar *end;
-			/* Time can be in 2 formats:
-			 * %time% or %time{strftime format}%
-			 * Extract the time format if provided. */
-			if (cur[1] == '{') {
-				cur += 2;
-				end = strstr (cur, "}%");
-				if (!end) {
-					/* Invalid string */
-					continue;
-				}
-				format = g_strndup (cur, end - cur);
-				cur = end + 1;
-			} else {
-				cur++;
-			}
-
+		} else if (theme_adium_match_with_format (&cur, "%textbackgroundcolor{", &format)) {
+			/* FIXME: This keyword is used to represent the
+			 * highlight background color. "X" is the opacity of the
+			 * background, ranges from 0 to 1 and can be any decimal
+			 * between.
+			 */
+		} else if (theme_adium_match (&cur, "%message%")) {
+			replace = message;
+		} else if (theme_adium_match (&cur, "%time%") ||
+			   theme_adium_match_with_format (&cur, "%time{", &format)) {
+			/* FIXME: format is not exactly strftime.
+			 * See NSDateFormatter spec:
+			 * http://www.stepcase.com/blog/2008/12/02/format-string-for-the-iphone-nsdateformatter/
+			 */
 			if (is_backlog) {
 				dup_replace = empathy_time_to_string_local (timestamp,
 					format ? format : EMPATHY_TIME_DATE_FORMAT_DISPLAY_SHORT);
@@ -392,7 +419,51 @@ theme_adium_append_html (EmpathyThemeAdium *theme,
 					format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
 			}
 			replace = dup_replace;
-			g_free (format);
+		} else if (theme_adium_match (&cur, "%shortTime%")) {
+			dup_replace = empathy_time_to_string_local (timestamp,
+				EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
+			replace = dup_replace;
+		} else if (theme_adium_match (&cur, "%service%")) {
+			replace = service_name;
+		} else if (theme_adium_match (&cur, "%variant%")) {
+			/* FIXME: The name of the active message style variant,
+			 * with all spaces replaced with an underscore.
+			 * A variant named "Alternating Messages - Blue Red"
+			 * will become "Alternating_Messages_-_Blue_Red".
+			 */
+		} else if (theme_adium_match (&cur, "%userIcons%")) {
+			/* FIXME: mus t be "hideIcons" if use preference is set
+			 * to hide avatars */
+			replace = "showIcons";
+		} else if (theme_adium_match (&cur, "%messageClasses%")) {
+			replace = message_classes;
+		} else if (theme_adium_match (&cur, "%status%")) {
+			/* FIXME: A description of the status event. This is
+			 * neither in the user's local language nor expected to
+			 * be displayed; it may be useful to use a different div
+			 * class to present different types of status messages.
+			 * The following is a list of some of the more important
+			 * status messages; your message style should be able to
+			 * handle being shown a status message not in this list,
+			 * as even at present the list is incomplete and is
+			 * certain to become out of date in the future:
+			 * 	online
+			 *	offline
+			 *	away
+			 *	away_message
+			 *	return_away
+			 *	idle
+			 *	return_idle
+			 *	date_separator
+			 *	contact_joined (group chats)
+			 *	contact_left
+			 *	error
+			 *	timed_out
+			 *	encryption (all OTR messages use this status)
+			 *	purple (all IRC topic and join/part messages use this status)
+			 *	fileTransferStarted
+			 *	fileTransferCompleted
+			 */
 		} else {
 			escape_and_append_len (string, cur, 1);
 			continue;
@@ -400,7 +471,9 @@ theme_adium_append_html (EmpathyThemeAdium *theme,
 
 		/* Here we have a replacement to make */
 		escape_and_append_len (string, replace, -1);
+
 		g_free (dup_replace);
+		g_free (format);
 	}
 	g_string_append (string, "\")");
 
@@ -432,6 +505,46 @@ theme_adium_append_event_escaped (EmpathyChatView *view,
 }
 
 static void
+theme_adium_remove_focus_marks (EmpathyThemeAdium *theme)
+{
+	WebKitDOMDocument *dom;
+	WebKitDOMNodeList *nodes;
+	guint i;
+
+	/* Remove focus and firstFocus class in all nodes having it */
+	dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
+	nodes = webkit_dom_document_query_selector_all (dom, ".focus", NULL);
+	for (i = 0; i < webkit_dom_node_list_get_length (nodes); i++) {
+		WebKitDOMNode *node = webkit_dom_node_list_item (nodes, i);
+		WebKitDOMHTMLElement *element = WEBKIT_DOM_HTML_ELEMENT (node);
+		gchar *class_name;
+		gchar **classes, **iter;
+		GString *new_class_name;
+		gboolean first = TRUE;
+
+		class_name = webkit_dom_html_element_get_class_name (element);
+		classes = g_strsplit (class_name, " ", -1);
+		new_class_name = g_string_sized_new (strlen (class_name));
+		for (iter = classes; *iter != NULL; iter++) {
+			if (tp_strdiff (*iter, "focus") &&
+			    tp_strdiff (*iter, "firstFocus")) {
+				if (!first) {
+					g_string_append_c (new_class_name, ' ');
+				}
+				g_string_append (new_class_name, *iter);
+				first = FALSE;
+			}
+		}
+
+		webkit_dom_html_element_set_class_name (element, new_class_name->str);
+
+		g_free (class_name);
+		g_strfreev (classes);
+		g_string_free (new_class_name, TRUE);
+	}
+}
+
+static void
 theme_adium_append_message (EmpathyChatView *view,
 			    EmpathyMessage  *msg)
 {
@@ -520,6 +633,17 @@ theme_adium_append_message (EmpathyChatView *view,
 
 	/* Define message classes */
 	message_classes = g_string_new ("message");
+	if (!priv->has_focus && !is_backlog) {
+		if (!priv->has_unread_message) {
+			/* This is the first message we receive since we lost
+			 * focus; remove redline from previous position. */
+			theme_adium_remove_focus_marks (theme);
+
+			g_string_append (message_classes, " firstFocus");
+			priv->has_unread_message = TRUE;
+		}
+		g_string_append (message_classes, " focus");
+	}
 	if (is_backlog) {
 		g_string_append (message_classes, " history");
 	}
@@ -531,6 +655,17 @@ theme_adium_append_message (EmpathyChatView *view,
 	} else {
 		g_string_append (message_classes, " incoming");
 	}
+	/* FIXME: other classes:
+	 * autoreply - the message is an automatic response, generally due to an
+	 *             away status
+	 * mention - the incoming message (in groupchat) matches your username
+	 *           or one of the mention keywords specified in Adium's
+	 *           advanced prefs.
+	 * status - the message is a status change
+	 * event - the message is a notification of something happening
+	 *         (for example, encryption being turned on)
+	 * %status% - See %status% in theme_adium_append_html()
+	 */
 
 	/* Define javascript function to use */
 	if (consecutive) {
@@ -732,6 +867,18 @@ theme_adium_copy_clipboard (EmpathyChatView *view)
 }
 
 static void
+theme_adium_focus_toggled (EmpathyChatView *view,
+			   gboolean         has_focus)
+{
+	EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
+
+	priv->has_focus = has_focus;
+	if (priv->has_focus) {
+		priv->has_unread_message = FALSE;
+	}
+}
+
+static void
 theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
 {
 	WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
@@ -850,6 +997,7 @@ theme_adium_iface_init (EmpathyChatViewIface *iface)
 	iface->find_abilities = theme_adium_find_abilities;
 	iface->highlight = theme_adium_highlight;
 	iface->copy_clipboard = theme_adium_copy_clipboard;
+	iface->focus_toggled = theme_adium_focus_toggled;
 }
 
 static void
