From c0ea59aed73b5425ad78e6bdb6f8f12e2b44567e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Wed, 15 Sep 2010 18:40:20 +0200
Subject: Added Snarl notification support


diff --git a/3rdParty/Snarl/SConscript b/3rdParty/Snarl/SConscript
new file mode 100644
index 0000000..99900f7
--- /dev/null
+++ b/3rdParty/Snarl/SConscript
@@ -0,0 +1,17 @@
+Import("env")
+
+################################################################################
+# Flags
+################################################################################
+if env.get("HAVE_SNARL", True) :
+	if env["SCONS_STAGE"] == "flags" :
+		env["SNARL_FLAGS"] = {
+				"CPPPATH": [Dir(".")],
+				"LIBPATH": [Dir(".")],
+				"LIBS": ["Snarl"],
+			}
+
+	elif env["SCONS_STAGE"] == "build" :
+		myenv = env.Clone()
+		myenv.Replace(CCFLAGS = [flag for flag in env["CCFLAGS"] if flag not in ["-W", "-Wall"]])
+		myenv.StaticLibrary("Snarl", ["SnarlInterface.cpp"], CPPPATH = ["."])
diff --git a/3rdParty/Snarl/SnarlInterface.cpp b/3rdParty/Snarl/SnarlInterface.cpp
new file mode 100644
index 0000000..f287b8e
--- /dev/null
+++ b/3rdParty/Snarl/SnarlInterface.cpp
@@ -0,0 +1,812 @@
+// About:
+//   Snarl C++ interface implementation
+//   To understand what the different functions do and what they return, please
+//   have a look at the API on http://www.fullphat.net/dev/api.htm.
+//   Also have a look at mSnarl_i.bas found in the CVS/SVN repository for Snarl.
+//
+//   The functions in SnarlInterface both have ANSI(UTF8) and UNICODE versions.
+//   If the LPCWSTR (unicode) version of the functions are called, the strings
+//   are converted to UTF8 by SnarlInterface before sent to Snarl. So using the
+//   ANSI/UTF8/LPCSTR versions of the functions are faster!
+//   
+//
+// Difference to VB implementation:
+//   Please note that string functions return NULL when they fail and not an
+//   empty string. So check for NULL...
+//   Function names doesn't have the pre "sn".
+//
+// 
+// Authors:
+//   Written and maintained by Toke Noer N�ttrup
+//   Original C++ version by "Whitman"
+//
+// License etc. :
+//   Feel free to use this code and class as you see fit.
+//   If you improve the code, it would be nice of you to take contact to the
+//   authors, so all can get the benifit.
+//
+//   There is no guarantee that the code is correct or functional.
+//   USE AT YOUR OWN RISK
+//-----------------------------------------------------------------------------
+
+// History
+//  2008/12/31 : Implemented V39 API
+//             : Moved SnarlInterface into new Snarl namespace and moved enums etc. out of class
+//             : Added WCHAR overloads for all functions
+//  2008/08/27 : Fixed return value of IsMessageVisible and HideMessage (returns false on failure now)
+//             : Fixed critical error in the new overloaded UpdateMessage() function
+//  2008/08/27 : x64 compiler fix
+//  2008/08/24 : Renamed all functions to not have prepended "sn".
+//             : Memory allocation functions added. (Use FreeString to free strings returned by Snarl)
+//             : Added m_nLastMessageId to the class. (Use GetLastMessageId() to get it)
+//             : Overloaded a few functions, so one don't have include the message id. (UpdateMessage f.i.)
+
+//  2008/06/20 : Fixed snShowMessageEx so it actually sends the extended version - oops
+//             : Fixed compiler warning C4800: forcing value to bool 'true' or 'false' (performance warning)
+
+//  2008/05/19 : uSend and uSendEx would always set return value to M_OK on successfull call
+//  2008/04/14 : Updated to follow (what should be) the final Snarl 2.0 API
+//  2008/03/28 : Few fixes for Snarl 2.0
+//  2007/05/23 : snGetGlobalMsg & snGetSnarlWindow made static
+//  2007/03/25 : 1.6 RC1 fixup
+//  2007/03/04 : Added - snGetAppPath, snGetIconsPath, snGetVersionEx, 
+//                       snSetTimeout, uSendEx
+
+
+#include "SnarlInterface.h"
+
+using namespace Snarl;
+
+
+//-----------------------------------------------------------------------------
+// Constructor/Destructor
+//-----------------------------------------------------------------------------
+SnarlInterface::SnarlInterface()
+: m_hwndFrom(NULL), m_nLastMessageId(0)
+{
+
+}
+
+SnarlInterface::~SnarlInterface()
+{
+
+}
+
+
+//-----------------------------------------------------------------------------
+// snShowMessage()
+
+/// Displays a message with Title and Text. Timeout controls how long the
+/// message is displayed for (in seconds) (omitting this value means the message
+/// is displayed indefinately). IconPath specifies the location of a PNG image
+/// which will be displayed alongside the message text.
+/// <returns>Message Id on success or M_RESULT on failure</returns>
+
+LONG32 SnarlInterface::ShowMessage(LPCSTR szTitle, LPCSTR szText, LONG32 timeout, LPCSTR szIconPath, HWND hWndReply, WPARAM uReplyMsg)
+{
+	SNARLSTRUCT ss;
+	ZeroMemory((void*)&ss, sizeof(ss));
+
+	ss.Cmd = SNARL_SHOW;
+	StringCbCopyA((LPSTR)&ss.Title, SNARL_STRING_LENGTH, szTitle);
+	StringCbCopyA((LPSTR)&ss.Text,  SNARL_STRING_LENGTH, szText);
+	StringCbCopyA((LPSTR)&ss.Icon,  SNARL_STRING_LENGTH, szIconPath);
+	ss.Timeout = timeout;
+
+	ss.LngData2 = reinterpret_cast<LONG32>(hWndReply);
+	ss.Id = static_cast<LONG32>(uReplyMsg);
+
+	m_nLastMessageId = Send(ss);
+	return m_nLastMessageId;
+}
+
+LONG32 SnarlInterface::ShowMessage(LPCWSTR szTitle, LPCWSTR szText, LONG32 timeout, LPCWSTR szIconPath, HWND hWndReply, WPARAM uReplyMsg)
+{
+	LPSTR szUTF8Title = WideToUTF8(szTitle);
+	LPSTR szUTF8Text  = WideToUTF8(szText);
+	LPSTR szUFT8IconPath = WideToUTF8(szIconPath);
+	
+	LONG32 result = ShowMessage(szUTF8Title, szUTF8Text, timeout, szUFT8IconPath, hWndReply, uReplyMsg);
+	
+	delete [] szUTF8Title;
+	delete [] szUTF8Text;
+	delete [] szUFT8IconPath;
+	
+	return result;
+}
+	
+//-----------------------------------------------------------------------------
+// snShowMessageEx()
+
+/// Displays a notification. This function is identical to snShowMessage()
+/// except that Class specifies an alert previously registered with
+/// snRegisterAlert() and SoundFile can optionally specify a WAV sound to play
+/// when the notification is displayed on screen.
+
+/// <returns>Message Id on success or M_RESULT on failure</returns>
+
+LONG32 SnarlInterface::ShowMessageEx(LPCSTR szClass, LPCSTR szTitle, LPCSTR szText, LONG32 timeout, LPCSTR szIconPath, HWND hWndReply, WPARAM uReplyMsg, LPCSTR szSoundFile)
+{
+	SNARLSTRUCTEX ssex;
+	ZeroMemory((void*)&ssex, sizeof(ssex));
+
+	ssex.Cmd = SNARL_EX_SHOW;
+	ssex.Timeout = timeout;
+	ssex.LngData2 = reinterpret_cast<LONG32>(hWndReply);
+	ssex.Id = static_cast<LONG32>(uReplyMsg);
+
+	StringCbCopyA((LPSTR)&ssex.Class, SNARL_STRING_LENGTH, szClass);
+	StringCbCopyA((LPSTR)&ssex.Title, SNARL_STRING_LENGTH, szTitle);
+	StringCbCopyA((LPSTR)&ssex.Text,  SNARL_STRING_LENGTH, szText);
+	StringCbCopyA((LPSTR)&ssex.Icon,  SNARL_STRING_LENGTH, szIconPath);
+	StringCbCopyA((LPSTR)&ssex.Extra, SNARL_STRING_LENGTH, szSoundFile);
+
+	m_nLastMessageId = Send(ssex);
+	return m_nLastMessageId;
+}
+
+LONG32 SnarlInterface::ShowMessageEx(LPCWSTR szClass, LPCWSTR szTitle, LPCWSTR szText, LONG32 timeout, LPCWSTR szIconPath, HWND hWndReply, WPARAM uReplyMsg, LPCWSTR szSoundFile)
+{
+	LPSTR szUTF8Class = WideToUTF8(szClass);
+	LPSTR szUTF8Title = WideToUTF8(szTitle);
+	LPSTR szUTF8Text  = WideToUTF8(szText);
+	LPSTR szUFT8IconPath = WideToUTF8(szIconPath);
+	LPSTR szUFT8SoundFile = WideToUTF8(szSoundFile);
+	
+	LONG32 result = ShowMessageEx(szUTF8Class, szUTF8Title, szUTF8Text, timeout, szUFT8IconPath, hWndReply, uReplyMsg, szUFT8SoundFile);
+	
+	delete [] szUTF8Class;
+	delete [] szUTF8Title;
+	delete [] szUTF8Text;
+	delete [] szUFT8IconPath;
+	delete [] szUFT8SoundFile;
+	
+	return result;
+}
+
+//-----------------------------------------------------------------------------
+// snHideMessage()
+
+/// Hides the notification specified by Id. Id is the value returned by
+/// snShowMessage() or snShowMessageEx() when the notification was initially
+/// created. This function returns True if the notification was successfully
+/// hidden or False otherwise (for example, the notification may no longer exist).
+
+BOOL SnarlInterface::HideMessage(LONG32 Id)
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_HIDE;
+	ss.Id = Id;
+
+	LONG32 n = Send(ss);
+	return (n == -1 || n == 1) ? TRUE : FALSE;
+}
+
+BOOL SnarlInterface::HideMessage()
+{
+	return HideMessage(m_nLastMessageId);
+}
+
+//-----------------------------------------------------------------------------
+// snIsMessageVisible()
+
+/// Returns True if the notification specified by Id is still visible, or
+/// False if not. Id is the value returned by snShowMessage() or
+/// snShowMessageEx() when the notification was initially created.
+
+BOOL SnarlInterface::IsMessageVisible(LONG32 Id)
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_IS_VISIBLE;
+	ss.Id = Id;
+
+	// We are getting -1 when true, checking for 1 just in case. We don't want to return true for the other M_RESULT returns
+	LONG32 n = Send(ss);	
+	return (n == -1 || n == 1) ? TRUE : FALSE;
+}
+
+BOOL SnarlInterface::IsMessageVisible()
+{
+	if (m_nLastMessageId == 0)
+		return FALSE;
+
+	return IsMessageVisible(m_nLastMessageId);
+}
+
+//-----------------------------------------------------------------------------
+// snUpdateMessage()
+
+/// Changes the title and text in the message specified by Id to the values
+/// specified by Title and Text respectively. Id is the value returned by 
+/// snShowMessage() or snShowMessageEx() when the notification was originally
+/// created. To change the timeout parameter of a notification, use snSetTimeout()
+
+M_RESULT SnarlInterface::UpdateMessage(LONG32 id, LPCSTR szTitle, LPCSTR szText, LPCSTR szIconPath)
+{
+	SNARLSTRUCT ss;
+	ZeroMemory((void*)&ss, sizeof(ss));
+
+	ss.Cmd = SNARL_UPDATE;
+	ss.Id = id;
+	
+	StringCbCopyA((LPSTR)&ss.Title, SNARL_STRING_LENGTH, szTitle);
+	StringCbCopyA((LPSTR)&ss.Text,  SNARL_STRING_LENGTH, szText);
+	StringCbCopyA((LPSTR)&ss.Icon,  SNARL_STRING_LENGTH, szIconPath);
+
+	return static_cast<M_RESULT>(Send(ss));
+}
+
+M_RESULT SnarlInterface::UpdateMessage(LONG32 id, LPCWSTR szTitle, LPCWSTR szText, LPCWSTR szIconPath)
+{
+	LPSTR szParam1 = WideToUTF8(szTitle);
+	LPSTR szParam2 = WideToUTF8(szText);
+	LPSTR szParam3 = WideToUTF8(szIconPath);
+	
+	M_RESULT result = UpdateMessage(id, szParam1, szParam2, szParam3);
+	
+	delete [] szParam1;
+	delete [] szParam2;
+	delete [] szParam3;
+	
+	return result;
+}
+
+M_RESULT SnarlInterface::UpdateMessage(LPCSTR szTitle, LPCSTR szText, LPCSTR szIconPath)
+{
+	return UpdateMessage(m_nLastMessageId, szTitle, szText, szIconPath);
+}
+
+M_RESULT SnarlInterface::UpdateMessage(LPCWSTR szTitle, LPCWSTR szText, LPCWSTR szIconPath)
+{
+	return UpdateMessage(m_nLastMessageId, szTitle, szText, szIconPath);
+}
+
+//-----------------------------------------------------------------------------
+// snRegisterConfig
+
+/// Registers an application's configuration interface with Snarl.
+/// AppName is the text that's displayed in the Applications list so it should
+/// be people friendly ("My cool app" rather than "my_cool_app").
+
+M_RESULT SnarlInterface::RegisterConfig(HWND hWnd, LPCSTR szAppName, LONG32 replyMsg)
+{
+	return RegisterConfig2(hWnd, szAppName, replyMsg, "");
+}
+
+M_RESULT SnarlInterface::RegisterConfig(HWND hWnd, LPCWSTR szAppName, LONG32 replyMsg)
+{
+	return RegisterConfig2(hWnd, szAppName, replyMsg, L"");
+}
+
+//-----------------------------------------------------------------------------
+// snRegisterConfig2
+
+/// Registers an application's configuration interface with Snarl.
+/// This function is identical to snRegisterConfig() except that Icon can be
+/// used to specify a PNG image which will be displayed against the
+/// application's entry in Snarl's Preferences panel.
+
+M_RESULT SnarlInterface::RegisterConfig2(HWND hWnd, LPCSTR szAppName, LONG32 replyMsg, LPCSTR szIcon)
+{
+	if (!szAppName || !szIcon)
+		return M_BAD_POINTER;
+
+	SNARLSTRUCT ss;
+
+	m_hwndFrom = hWnd;
+
+	ss.Cmd = SNARL_REGISTER_CONFIG_WINDOW_2;
+	ss.LngData2 = reinterpret_cast<LONG32>(hWnd);
+	ss.Id = replyMsg;
+	StringCbCopyA((LPSTR)&ss.Title, SNARL_STRING_LENGTH, szAppName);
+	StringCbCopyA((LPSTR)&ss.Icon, SNARL_STRING_LENGTH, szIcon);
+
+	return static_cast<M_RESULT>(Send(ss));
+}
+
+M_RESULT SnarlInterface::RegisterConfig2(HWND hWnd, LPCWSTR szAppName, LONG32 replyMsg, LPCWSTR szIcon)
+{
+	LPSTR szParam1 = WideToUTF8(szAppName);
+	LPSTR szParam2 = WideToUTF8(szIcon);
+	
+	M_RESULT result = RegisterConfig2(hWnd, szParam1, replyMsg, szParam2);
+	
+	delete [] szParam1;
+	delete [] szParam2;
+	
+	return result;
+}
+
+
+//-----------------------------------------------------------------------------
+// snRevokeConfig
+
+/// Removes the application previously registered using snRegisterConfig() or
+/// snRegisterConfig2(). hWnd should be the same as that used during registration.
+
+M_RESULT SnarlInterface::RevokeConfig(HWND hWnd)
+{
+	SNARLSTRUCT ss;
+	
+	m_hwndFrom = NULL;
+
+	ss.Cmd = SNARL_REVOKE_CONFIG_WINDOW;
+	ss.LngData2 = reinterpret_cast<LONG32>(hWnd);
+
+	return static_cast<M_RESULT>(Send(ss));
+}
+
+
+//-----------------------------------------------------------------------------
+// snGetVersion()
+
+/// Checks if Snarl is currently running and, if it is, retrieves the major and
+/// minor release version numbers in Major and Minor respectively.
+/// Returns True if Snarl is running, False otherwise.
+
+BOOL SnarlInterface::GetVersion(WORD* Major, WORD* Minor)
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_GET_VERSION;
+	LONG32 versionInfo = Send(ss);
+	if (versionInfo > 0 && versionInfo != M_FAILED && versionInfo != M_TIMED_OUT) {
+		*Major = HIWORD(versionInfo);
+		*Minor = LOWORD(versionInfo);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+
+//-----------------------------------------------------------------------------
+// snGetVersionEx
+
+/// Returns the Snarl system version number. This is an integer value which
+/// represents the system build number and can be used to identify the specific
+/// version of Snarl running
+
+LONG32 SnarlInterface::GetVersionEx()
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_GET_VERSION_EX;
+	return Send(ss);
+}
+
+
+//-----------------------------------------------------------------------------
+// snSetTimeout()
+
+/// Sets the timeout of existing notification Id to Timeout seconds. Id is the
+/// value returned by snShowMessage() or snShowMessageEx() when the notification
+/// was first created. 
+
+M_RESULT SnarlInterface::SetTimeout(LONG32 Id, LONG32 Timeout)
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_SET_TIMEOUT;
+	ss.Id = Id;
+	ss.LngData2 = Timeout;
+
+	return static_cast<M_RESULT>(Send(ss));
+}
+
+M_RESULT SnarlInterface::SetTimeout(LONG32 Timeout)
+{
+	return SetTimeout(m_nLastMessageId, Timeout);
+}
+
+//-----------------------------------------------------------------------------
+// snRegisterAlert()
+
+/// Registers an alert of Class for application AppName which must have previously
+/// been registered with either snRegisterConfig() or snRegisterConfig2().
+
+M_RESULT SnarlInterface::RegisterAlert(LPCSTR szAppName, LPCSTR szClass)
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_REGISTER_ALERT;
+	StringCbCopyA((LPSTR)&ss.Title, SNARL_STRING_LENGTH, szAppName);
+	StringCbCopyA((LPSTR)&ss.Text, SNARL_STRING_LENGTH, szClass);
+
+	return static_cast<M_RESULT>(Send(ss));
+}
+
+M_RESULT SnarlInterface::RegisterAlert(LPCWSTR szAppName, LPCWSTR szClass)
+{
+	LPSTR szParam1 = WideToUTF8(szAppName);
+	LPSTR szParam2 = WideToUTF8(szClass);
+	
+	M_RESULT result = RegisterAlert(szParam1, szParam2);
+	
+	delete [] szParam1;
+	delete [] szParam2;
+	
+	return result;
+}
+
+//-----------------------------------------------------------------------------
+// snGetGlobalMsg()
+
+/// Returns the atom that corresponds to the "SnarlGlobalEvent" registered
+/// Windows message. This message is sent by Snarl when it is first starts and
+/// when it shuts down.
+
+LONG32 SnarlInterface::GetGlobalMsg()
+{
+	return RegisterWindowMessage(SNARL_GLOBAL_MSG);
+}
+
+
+//-----------------------------------------------------------------------------
+// snGetSnarlWindow
+
+HWND SnarlInterface::GetSnarlWindow()
+{
+	return FindWindow(NULL, _T("Snarl"));
+}
+
+
+//-----------------------------------------------------------------------------
+// snGetAppPath()
+
+/// Returns a pointer to the path.
+/// ** Remember to call FreeString
+
+LPCTSTR SnarlInterface::GetAppPath()
+{
+	HWND hWnd = GetSnarlWindow();
+	if (hWnd)
+	{
+		HWND hWndPath = FindWindowEx(hWnd, 0, _T("static"), NULL);
+		if (hWndPath)
+		{
+			TCHAR strTmp[MAX_PATH] = {0};
+			int nReturn = GetWindowText(hWndPath, strTmp, MAX_PATH);
+			if (nReturn > 0) {
+				TCHAR* strReturn = AllocateString(nReturn + 1);
+				StringCchCopy(strReturn, nReturn + 1, strTmp);
+				return strReturn;
+			}
+		}
+	}
+	return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// snGetIconsPath()
+
+/// Returns a pointer to the iconpath.
+/// ** Remember to call FreeString when done with the string
+
+LPCTSTR SnarlInterface::GetIconsPath()
+{
+	TCHAR* szIconPath = NULL;
+	LPCTSTR szPath = GetAppPath();
+	if (!szPath)
+		return NULL;
+
+	size_t nLen = 0;
+	if (SUCCEEDED(StringCbLength(szPath, MAX_PATH, &nLen)))
+	{
+		nLen += 10 + 1; // etc\\icons\\ + NULL
+		szIconPath = AllocateString(nLen);
+
+		StringCbCopy(szIconPath, nLen * sizeof(TCHAR), szPath);
+		StringCbCat(szIconPath, nLen * sizeof(TCHAR), _T("etc\\icons\\"));
+	}
+	
+	FreeString(szPath);
+
+	return szIconPath;
+}
+
+
+//-----------------------------------------------------------------------------
+// snSetAsSnarlApp()
+
+/// Identifies an application as a Snarl App.  (V39)
+
+void SnarlInterface::SetAsSnarlApp(HWND hWndOwner, SNARL_APP_FLAGS Flags)
+{
+	if (IsWindow(hWndOwner)) {
+		SetProp(hWndOwner, _T("snarl_app"), reinterpret_cast<HANDLE>(1));
+		SetProp(hWndOwner, _T("snarl_app_flags"), reinterpret_cast<HANDLE>(Flags));
+	}
+}
+
+
+//-----------------------------------------------------------------------------
+// snGetAppMsg()
+
+/// Returns the global Snarl Application message  (V39)
+
+LONG32 SnarlInterface::GetAppMsg()
+{
+	return RegisterWindowMessage(SNARL_APP_MSG);
+}
+
+
+//-----------------------------------------------------------------------------
+// snRegisterApp()
+
+/// Registers an application with Snarl  (V39)
+
+M_RESULT SnarlInterface::RegisterApp(LPCSTR Application, LPCSTR SmallIcon, LPCSTR LargeIcon, HWND hWnd, LONG32 ReplyMsg)
+{
+	m_hwndFrom = hWnd;
+	  
+    SNARLSTRUCT ss;
+	ss.Cmd = SNARL_REGISTER_APP;
+	
+	StringCbCopyA((LPSTR)&ss.Title, SNARL_STRING_LENGTH, Application);
+	StringCbCopyA((LPSTR)&ss.Icon,  SNARL_STRING_LENGTH, SmallIcon);
+	StringCbCopyA((LPSTR)&ss.Text,  SNARL_STRING_LENGTH, LargeIcon);
+
+	ss.LngData2 = reinterpret_cast<LONG32>(hWnd);
+	ss.Id = ReplyMsg;
+	ss.Timeout = GetCurrentProcessId();
+
+	return static_cast<M_RESULT>(Send(ss));
+}
+
+M_RESULT SnarlInterface::RegisterApp(LPCWSTR Application, LPCWSTR SmallIcon, LPCWSTR LargeIcon, HWND hWnd, LONG32 ReplyMsg)
+{
+	LPSTR szParam1 = WideToUTF8(Application);
+	LPSTR szParam2 = WideToUTF8(SmallIcon);
+	LPSTR szParam3 = WideToUTF8(LargeIcon);
+	
+	M_RESULT result = RegisterApp(szParam1, szParam2, szParam3, hWnd, ReplyMsg);
+	
+	delete [] szParam1;
+	delete [] szParam2;
+	delete [] szParam3;
+	
+	return result;
+}
+
+
+//-----------------------------------------------------------------------------
+// snUnregisterApp()
+
+/// Unregisters an application with Snarl  (V39)
+
+M_RESULT SnarlInterface::UnregisterApp()
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_UNREGISTER_APP;
+	ss.LngData2 = GetCurrentProcessId();
+	
+	m_hwndFrom = NULL;
+	
+	return static_cast<M_RESULT>(Send(ss));
+}
+
+
+//-----------------------------------------------------------------------------
+// snShowNotification()
+
+/// Displays a Snarl notification using registered class  (V39)
+/// <returns>Message Id on success or M_RESULT on failure</returns>
+
+LONG32 SnarlInterface::ShowNotification(LPCSTR Class, LPCSTR Title, LPCSTR Text, LONG32 Timeout, LPCSTR Icon, HWND hWndReply, LONG32 uReplyMsg, LPCSTR Sound)
+{
+	SNARLSTRUCTEX ssex;
+	ssex.Cmd = SNARL_SHOW_NOTIFICATION;
+	
+	StringCbCopyExA((LPSTR)&ssex.Title, SNARL_STRING_LENGTH, Title, NULL, NULL, STRSAFE_IGNORE_NULLS);
+	StringCbCopyExA((LPSTR)&ssex.Text,  SNARL_STRING_LENGTH, Text,  NULL, NULL, STRSAFE_IGNORE_NULLS);
+	StringCbCopyExA((LPSTR)&ssex.Icon,  SNARL_STRING_LENGTH, Icon,  NULL, NULL, STRSAFE_IGNORE_NULLS);
+
+	ssex.Timeout = Timeout;
+	ssex.LngData2 = reinterpret_cast<LONG32>(hWndReply);
+	ssex.Id = uReplyMsg;
+
+	StringCbCopyExA((LPSTR)&ssex.Extra, SNARL_STRING_LENGTH, Sound, NULL, NULL, STRSAFE_IGNORE_NULLS);
+	StringCbCopyA((LPSTR)&ssex.Class,  SNARL_STRING_LENGTH, Class);
+    
+	ssex.Reserved1 = GetCurrentProcessId();
+	
+	m_nLastMessageId = Send(ssex);
+	return m_nLastMessageId;
+}
+
+LONG32 SnarlInterface::ShowNotification(LPCWSTR Class, LPCWSTR Title, LPCWSTR Text, LONG32 Timeout, LPCWSTR Icon, HWND hWndReply, LONG32 uReplyMsg, LPCWSTR Sound)
+{
+	LPSTR szParam1 = WideToUTF8(Class);
+	LPSTR szParam2 = WideToUTF8(Title);
+	LPSTR szParam3 = WideToUTF8(Text);
+	LPSTR szParam4 = WideToUTF8(Icon);
+	LPSTR szParam5 = WideToUTF8(Sound);
+	
+	LONG32 result = ShowNotification(szParam1, szParam2, szParam3, Timeout, szParam4, hWndReply, uReplyMsg, szParam5);
+	
+	delete [] szParam1;
+	delete [] szParam2;
+	delete [] szParam3;
+	delete [] szParam4;
+	delete [] szParam5;
+	
+	return result;
+}
+
+
+//-----------------------------------------------------------------------------
+// snChangeAttribute()
+
+/// (V39)
+
+M_RESULT SnarlInterface::ChangeAttribute(LONG32 Id, SNARL_ATTRIBUTES Attr, LPCSTR Value)
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_CHANGE_ATTR;
+	ss.Id = Id;
+	ss.LngData2 = Attr;
+	
+	StringCbCopyExA((LPSTR)&ss.Text, SNARL_STRING_LENGTH, Value, NULL, NULL, STRSAFE_IGNORE_NULLS);
+	
+	return static_cast<M_RESULT>(Send(ss));
+}
+
+M_RESULT SnarlInterface::ChangeAttribute(LONG32 Id, SNARL_ATTRIBUTES Attr, LPCWSTR Value)
+{
+	LPSTR szParam1 = WideToUTF8(Value);
+	
+	M_RESULT result = ChangeAttribute(Id, Attr, szParam1);
+	
+	delete [] szParam1;
+	
+	return result;
+}
+
+M_RESULT SnarlInterface::ChangeAttribute(SNARL_ATTRIBUTES Attr, LPCSTR Value)
+{
+	return ChangeAttribute(m_nLastMessageId, Attr, Value);
+}
+
+M_RESULT SnarlInterface::ChangeAttribute(SNARL_ATTRIBUTES Attr, LPCWSTR Value)
+{
+	return ChangeAttribute(m_nLastMessageId, Attr, Value);
+}
+
+
+//-----------------------------------------------------------------------------
+// snSetClassDefault()
+
+/// Sets the default value for an alert class  (V39)
+
+M_RESULT SnarlInterface::SetClassDefault(LPCSTR Class, SNARL_ATTRIBUTES Attr, LPCSTR Value)
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_SET_CLASS_DEFAULT;
+	ss.LngData2 = Attr;
+	ss.Timeout = GetCurrentProcessId();
+	
+	StringCbCopyExA((LPSTR)&ss.Text, SNARL_STRING_LENGTH, Class, NULL, NULL, STRSAFE_IGNORE_NULLS);
+	StringCbCopyExA((LPSTR)&ss.Icon, SNARL_STRING_LENGTH, Value, NULL, NULL, STRSAFE_IGNORE_NULLS);
+
+	return static_cast<M_RESULT>(Send(ss));
+}
+
+M_RESULT SnarlInterface::SetClassDefault(LPCWSTR Class, SNARL_ATTRIBUTES Attr, LPCWSTR Value)
+{
+	LPSTR szParam1 = WideToUTF8(Class);
+	LPSTR szParam2 = WideToUTF8(Value);
+	
+	M_RESULT result = SetClassDefault(szParam1, Attr, szParam2);
+	
+	delete [] szParam1;
+	delete [] szParam2;
+	
+	return result;
+}
+
+
+//-----------------------------------------------------------------------------
+// snGetRevision()
+
+/// Gets the current Snarl revision (build) number  (V39)
+/// Returns the build version number, or M_RESULT on failure.
+
+LONG32 SnarlInterface::GetRevision()
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_GET_REVISION;
+	ss.LngData2 = 0xFFFE;
+
+	return Send(ss);
+}
+
+
+//-----------------------------------------------------------------------------
+// snAddClass()
+
+/// (V39)
+
+M_RESULT SnarlInterface::AddClass(LPCSTR Class, LPCSTR Description, SNARL_CLASS_FLAGS Flags, LPCSTR DefaultTitle, LPCSTR DefaultIcon, LONG32 DefaultTimeout)
+{
+	SNARLSTRUCT ss;
+	ss.Cmd = SNARL_ADD_CLASS;
+	ss.LngData2 = Flags;
+	ss.Timeout = GetCurrentProcessId();
+	
+	StringCbCopyExA((LPSTR)&ss.Text,  SNARL_STRING_LENGTH, Class, NULL, NULL, STRSAFE_IGNORE_NULLS);
+	StringCbCopyExA((LPSTR)&ss.Title, SNARL_STRING_LENGTH, Description, NULL, NULL, STRSAFE_IGNORE_NULLS);
+
+	LONG32 result = Send(ss);
+
+	if (static_cast<M_RESULT>(result) == M_OK)
+	{
+		SetClassDefault(Class, SNARL_ATTRIBUTE_TITLE, DefaultTitle);
+		SetClassDefault(Class, SNARL_ATTRIBUTE_ICON, DefaultIcon);
+		if (DefaultTimeout > 0) {
+			char str[64] = {0};
+			StringCbPrintfA((LPSTR)&str, sizeof(str), "%d", DefaultTimeout);
+			SetClassDefault(Class, SNARL_ATTRIBUTE_TIMEOUT, str);
+		}
+		
+		return M_OK;
+	}
+	else
+		return M_FAILED;
+}
+
+M_RESULT SnarlInterface::AddClass(LPCWSTR Class, LPCWSTR Description, SNARL_CLASS_FLAGS Flags, LPCWSTR DefaultTitle, LPCWSTR DefaultIcon, LONG32 DefaultTimeout)
+{
+	LPCSTR szClass        = WideToUTF8(Class);
+	LPCSTR szDescription  = WideToUTF8(Description);
+	LPCSTR szDefaultTitle = WideToUTF8(DefaultTitle);
+	LPCSTR szDefaultIcon  = WideToUTF8(DefaultIcon);
+	
+	M_RESULT result = AddClass(szClass, szDescription, Flags, szDefaultTitle, szDefaultIcon, DefaultTimeout);
+	
+	delete [] szClass;
+	delete [] szDescription;
+	delete [] szDefaultTitle;
+	delete [] szDefaultIcon;
+	
+	return result;
+}
+
+//-----------------------------------------------------------------------------
+// Private functions
+//-----------------------------------------------------------------------------
+
+template <class T>
+LONG32 SnarlInterface::Send(T ss)
+{
+	DWORD_PTR nReturn = M_FAILED;
+
+	HWND hWnd = GetSnarlWindow();
+	if (IsWindow(hWnd))
+	{
+		COPYDATASTRUCT cds;
+		cds.dwData = 2;
+		cds.cbData = sizeof(ss);
+		cds.lpData = &ss;
+
+		if (SendMessageTimeout(hWnd, WM_COPYDATA, (WPARAM)m_hwndFrom, (LPARAM)&cds, SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG, 1000, &nReturn) == 0)
+		{
+			if (GetLastError() == ERROR_TIMEOUT)
+				nReturn = M_TIMED_OUT;
+		}
+	}
+
+	return static_cast<LONG32>(nReturn);
+}
+
+//-----------------------------------------------------------------------------
+
+// Remember to : delete [] returned string
+
+LPSTR SnarlInterface::WideToUTF8(LPCWSTR szWideStr)
+{
+	if (szWideStr == NULL)
+		return NULL;
+
+    int nSize = WideCharToMultiByte(CP_UTF8, 0, szWideStr, -1, NULL, 0, NULL, NULL);
+    LPSTR szUTF8 = new char[nSize];
+    WideCharToMultiByte(CP_UTF8, 0, szWideStr, -1, szUTF8, nSize, NULL, NULL);
+    
+    return szUTF8;
+}
\ No newline at end of file
diff --git a/3rdParty/Snarl/SnarlInterface.h b/3rdParty/Snarl/SnarlInterface.h
new file mode 100644
index 0000000..137d597
--- /dev/null
+++ b/3rdParty/Snarl/SnarlInterface.h
@@ -0,0 +1,218 @@
+#ifndef SNARL_INTERFACE
+#define SNARL_INTERFACE
+
+#include <tchar.h>
+#include <windows.h>
+#include <strsafe.h>
+
+
+namespace Snarl {
+
+	static const LPCTSTR SNARL_GLOBAL_MSG = _T("SnarlGlobalEvent");
+	static const LPCTSTR SNARL_APP_MSG    = _T("SnarlAppMessage");
+
+	static const int SNARL_STRING_LENGTH = 1024;
+	static const int SNARL_UNICODE_LENGTH = SNARL_STRING_LENGTH / 2;
+
+	static const LONG32 SNARL_LAUNCHED = 1;                // Snarl has just started running
+	static const LONG32 SNARL_QUIT = 2;                    // Snarl is about to stop running
+	static const LONG32 SNARL_ASK_APPLET_VER = 3;          // (R1.5) Reserved for future use
+	static const LONG32 SNARL_SHOW_APP_UI = 4;             // (R1.6) Application should show its UI
+
+	static const LONG32 SNARL_NOTIFICATION_CLICKED = 32;   // notification was right-clicked by user
+	static const LONG32 SNARL_NOTIFICATION_TIMED_OUT = 33;
+	static const LONG32 SNARL_NOTIFICATION_ACK = 34;       // notification was left-clicked by user
+	static const LONG32 SNARL_NOTIFICATION_MENU = 35;           // V39 - menu item selected
+	static const LONG32 SNARL_NOTIFICATION_MIDDLE_BUTTON = 36;  // V39 - notification middle-clicked by user
+	static const LONG32 SNARL_NOTIFICATION_CLOSED = 37;         // V39 - user clicked the close gadget
+
+	static const LONG32 SNARL_NOTIFICATION_CANCELLED = SNARL_NOTIFICATION_CLICKED;  // Added in R1.6
+
+	static const DWORD WM_SNARLTEST = WM_USER + 237;    // note hardcoded WM_USER value!
+
+	// --------------------------------------------------------------------
+	
+	typedef enum M_RESULT {
+		M_ABORTED         = 0x80000007,
+		M_ACCESS_DENIED   = 0x80000009,
+		M_ALREADY_EXISTS  = 0x8000000C,
+		M_BAD_HANDLE      = 0x80000006,
+		M_BAD_POINTER     = 0x80000005,
+		M_FAILED          = 0x80000008,
+		M_INVALID_ARGS    = 0x80000003,
+		M_NO_INTERFACE    = 0x80000004,
+		M_NOT_FOUND       = 0x8000000B,
+		M_NOT_IMPLEMENTED = 0x80000001,
+		M_OK              = 0x00000000,
+		M_OUT_OF_MEMORY   = 0x80000002,
+		M_TIMED_OUT       = 0x8000000A
+	};
+
+	enum SNARL_COMMANDS {
+		SNARL_SHOW = 1,
+		SNARL_HIDE,
+		SNARL_UPDATE,
+		SNARL_IS_VISIBLE,
+		SNARL_GET_VERSION,
+		SNARL_REGISTER_CONFIG_WINDOW,
+		SNARL_REVOKE_CONFIG_WINDOW,
+
+		/* R1.6 onwards */
+		SNARL_REGISTER_ALERT,
+		SNARL_REVOKE_ALERT,   // for future use
+		SNARL_REGISTER_CONFIG_WINDOW_2,
+		SNARL_GET_VERSION_EX,
+		SNARL_SET_TIMEOUT,
+		
+		/* following introduced in Snarl V39 (R2.1) */
+		SNARL_SET_CLASS_DEFAULT,
+		SNARL_CHANGE_ATTR,
+		SNARL_REGISTER_APP,
+		SNARL_UNREGISTER_APP,
+		SNARL_ADD_CLASS,
+
+		/* extended commands (all use SNARLSTRUCTEX) */
+		SNARL_EX_SHOW = 0x20,
+		SNARL_SHOW_NOTIFICATION                // V39
+	};
+	
+	static const SNARL_COMMANDS SNARL_GET_REVISION = SNARL_REVOKE_ALERT;
+	
+	typedef enum SNARL_APP_FLAGS {
+		SNARL_APP_HAS_PREFS = 1,
+		SNARL_APP_HAS_ABOUT = 2
+	};
+	
+	static const LONG32 SNARL_APP_PREFS = 1;
+	static const LONG32 SNARL_APP_ABOUT = 2;
+
+	
+	/* --------------- V39 additions --------------- */
+	
+	/* snAddClass() flags */
+	enum SNARL_CLASS_FLAGS {
+		SNARL_CLASS_ENABLED = 0,
+		SNARL_CLASS_DISABLED = 1,
+		SNARL_CLASS_NO_DUPLICATES = 2,         // means Snarl will suppress duplicate notifications
+		SNARL_CLASS_DELAY_DUPLICATES = 4       // means Snarl will suppress duplicate notifications within a pre-set time period
+	};
+
+	/* Class attributes */
+	typedef enum SNARL_ATTRIBUTES {
+		SNARL_ATTRIBUTE_TITLE = 1,
+		SNARL_ATTRIBUTE_TEXT,
+		SNARL_ATTRIBUTE_ICON,
+		SNARL_ATTRIBUTE_TIMEOUT,
+		SNARL_ATTRIBUTE_SOUND,
+		SNARL_ATTRIBUTE_ACK,               // file to run on ACK
+		SNARL_ATTRIBUTE_MENU
+	};
+
+	/* ------------------- end of V39 additions ------------------ */
+	
+	struct SNARLSTRUCT {
+		SNARL_COMMANDS Cmd;
+		LONG32 Id;
+		LONG32 Timeout;
+		LONG32 LngData2;
+		char Title[SNARL_STRING_LENGTH];
+		char Text[SNARL_STRING_LENGTH];
+		char Icon[SNARL_STRING_LENGTH];
+	};
+
+	struct SNARLSTRUCTEX {
+		SNARL_COMMANDS Cmd;
+		LONG32 Id;
+		LONG32 Timeout;
+		LONG32 LngData2;
+		char Title[SNARL_STRING_LENGTH];
+		char Text[SNARL_STRING_LENGTH];
+		char Icon[SNARL_STRING_LENGTH];
+
+		char Class[SNARL_STRING_LENGTH];
+		char Extra[SNARL_STRING_LENGTH];
+		char Extra2[SNARL_STRING_LENGTH];
+		LONG32 Reserved1;
+		LONG32 Reserved2;
+	};
+
+
+	
+	// ------------------------------------------------------------------------
+	// SnarlInterface class definition
+	// ------------------------------------------------------------------------
+	
+	class SnarlInterface {
+		public:
+			SnarlInterface();
+			~SnarlInterface();
+
+			static HWND   GetSnarlWindow();		
+			static LONG32 GetGlobalMsg();
+
+			
+			LPTSTR AllocateString(size_t n) { return new TCHAR[n]; }
+			void FreeString(LPCTSTR str)    { delete [] str; str = NULL; }
+			
+
+			LONG32  ShowMessage(LPCSTR szTitle, LPCSTR szText, LONG32 timeout = 0, LPCSTR szIconPath = "", HWND hWndReply = NULL, WPARAM uReplyMsg = 0);
+			LONG32  ShowMessage(LPCWSTR szTitle, LPCWSTR szText, LONG32 timeout = 0, LPCWSTR szIconPath = L"", HWND hWndReply = NULL, WPARAM uReplyMsg = 0);
+			LONG32  ShowMessageEx(LPCSTR szClass, LPCSTR szTitle, LPCSTR szText, LONG32 timeout = 0, LPCSTR szIconPath = "", HWND hWndReply = NULL, WPARAM uReplyMsg = 0, LPCSTR szSoundFile = "");
+			LONG32  ShowMessageEx(LPCWSTR szClass, LPCWSTR szTitle, LPCWSTR szText, LONG32 timeout = 0, LPCWSTR szIconPath = L"", HWND hWndReply = NULL, WPARAM uReplyMsg = 0, LPCWSTR szSoundFile = L"");
+
+			LPCTSTR GetAppPath();    // ** Remember to FreeString when finished with the string !
+			LPCTSTR GetIconsPath();  // ** Remember to FreeString when finished with the string !
+
+			BOOL      GetVersion(WORD* Major, WORD* Minor);
+			LONG32    GetVersionEx();
+			BOOL      HideMessage();
+			BOOL      HideMessage(LONG32 Id);
+			BOOL      IsMessageVisible();
+			BOOL      IsMessageVisible(LONG32 Id);
+			M_RESULT  RegisterAlert(LPCSTR szAppName, LPCSTR szClass);
+			M_RESULT  RegisterAlert(LPCWSTR szAppName, LPCWSTR szClass);
+			M_RESULT  RegisterConfig(HWND hWnd, LPCSTR szAppName, LONG32 replyMsg);
+			M_RESULT  RegisterConfig(HWND hWnd, LPCWSTR szAppName, LONG32 replyMsg);
+			M_RESULT  RegisterConfig2(HWND hWnd, LPCSTR szAppName, LONG32 replyMsg, LPCSTR szIcon);
+			M_RESULT  RegisterConfig2(HWND hWnd, LPCWSTR szAppName, LONG32 replyMsg, LPCWSTR szIcon);
+			M_RESULT  RevokeConfig(HWND hWnd);
+			M_RESULT  SetTimeout(LONG32 Timeout);
+			M_RESULT  SetTimeout(LONG32 Id, LONG32 Timeout);
+			M_RESULT  UpdateMessage(LPCSTR szTitle, LPCSTR szText, LPCSTR szIconPath = "");
+			M_RESULT  UpdateMessage(LPCWSTR szTitle, LPCWSTR szText, LPCWSTR szIconPath = L"");
+			M_RESULT  UpdateMessage(LONG32 Id, LPCSTR szTitle, LPCSTR szText, LPCSTR szIconPath = "");
+			M_RESULT  UpdateMessage(LONG32 Id, LPCWSTR szTitle, LPCWSTR szText, LPCWSTR szIconPath = L"");
+			
+			/* V39 */
+			M_RESULT  AddClass(LPCSTR Class, LPCSTR Description = NULL, SNARL_CLASS_FLAGS Flags = SNARL_CLASS_ENABLED, LPCSTR DefaultTitle = NULL, LPCSTR DefaultIcon = NULL, LONG32 DefaultTimeout = 0);
+			M_RESULT  AddClass(LPCWSTR Class, LPCWSTR Description = NULL, SNARL_CLASS_FLAGS Flags = SNARL_CLASS_ENABLED, LPCWSTR DefaultTitle = NULL, LPCWSTR DefaultIcon = NULL, LONG32 DefaultTimeout = 0);
+			M_RESULT  ChangeAttribute(SNARL_ATTRIBUTES Attr, LPCSTR Value);
+			M_RESULT  ChangeAttribute(SNARL_ATTRIBUTES Attr, LPCWSTR Value);
+			M_RESULT  ChangeAttribute(LONG32 Id, SNARL_ATTRIBUTES Attr, LPCSTR Value);			
+			M_RESULT  ChangeAttribute(LONG32 Id, SNARL_ATTRIBUTES Attr, LPCWSTR Value);			
+			LONG32    GetAppMsg();
+			LONG32    GetRevision();
+			
+			M_RESULT  RegisterApp(LPCSTR Application, LPCSTR SmallIcon, LPCSTR LargeIcon, HWND hWnd = 0, LONG32 ReplyMsg = 0);
+			M_RESULT  RegisterApp(LPCWSTR Application, LPCWSTR SmallIcon, LPCWSTR LargeIcon, HWND hWnd = 0, LONG32 ReplyMsg = 0);
+			void      SetAsSnarlApp(HWND hWndOwner, SNARL_APP_FLAGS Flags = (SNARL_APP_FLAGS)(SNARL_APP_HAS_ABOUT | SNARL_APP_HAS_PREFS));
+			M_RESULT  SetClassDefault(LPCSTR Class, SNARL_ATTRIBUTES Attr, LPCSTR Value);
+			M_RESULT  SetClassDefault(LPCWSTR Class, SNARL_ATTRIBUTES Attr, LPCWSTR Value);
+			LONG32    ShowNotification(LPCSTR Class, LPCSTR Title = NULL, LPCSTR Text = NULL, LONG32 Timeout = 0, LPCSTR Icon = NULL, HWND hWndReply = NULL, LONG32 uReplyMsg = 0, LPCSTR Sound = NULL);
+			LONG32    ShowNotification(LPCWSTR Class, LPCWSTR Title = NULL, LPCWSTR Text = NULL, LONG32 Timeout = 0, LPCWSTR Icon = NULL, HWND hWndReply = NULL, LONG32 uReplyMsg = 0, LPCWSTR Sound = NULL);
+			M_RESULT  UnregisterApp();
+			
+			LONG32    GetLastMessageId() { return m_nLastMessageId; }
+
+		private:
+			template <class T> LONG32 Send(T ss);
+			LPSTR  WideToUTF8(LPCWSTR szWideStr);
+			
+			LONG32 m_nLastMessageId;
+			HWND   m_hwndFrom; // set during snRegisterConfig() or snRegisterConfig2()
+
+	};
+
+}
+
+#endif // SNARL_INTERFACE
diff --git a/BuildTools/SCons/SConstruct b/BuildTools/SCons/SConstruct
index 5a8791b..220ccf1 100644
--- a/BuildTools/SCons/SConstruct
+++ b/BuildTools/SCons/SConstruct
@@ -379,6 +379,10 @@ if env["PLATFORM"] == "darwin" :
 		env["GROWL_FRAMEWORK"] = "/Library/Frameworks/Growl.framework"
 	conf.Finish()
 
+# Snarl
+if env["PLATFORM"] == "win32" :
+	env["HAVE_SNARL"] = True
+
 # LibXML
 conf = Configure(conf_env)
 if conf.CheckCHeader("libxml/parser.h") and conf.CheckLib("xml2") :
diff --git a/SwifTools/Notifier/GrowlNotifier.cpp b/SwifTools/Notifier/GrowlNotifier.cpp
index 0527ef8..0660910 100644
--- a/SwifTools/Notifier/GrowlNotifier.cpp
+++ b/SwifTools/Notifier/GrowlNotifier.cpp
@@ -71,7 +71,10 @@ GrowlNotifier::GrowlNotifier(const String& name) {
 	Growl_SetDelegate(&delegate_);
 }
 
-void GrowlNotifier::showMessage(Type type, const String& subject, const String& description, const ByteArray& picture, boost::function<void()> callback) {
+void GrowlNotifier::showMessage(Type type, const String& subject, const String& description, const boost::filesystem::path& picturePath, boost::function<void()> callback) {
+	ByteArray picture;
+	picture.readFromFile(picturePath.string());
+
 	CFStringRef cfSubject = SWIFTEN_STRING_TO_CFSTRING(subject);
 	CFStringRef cfDescription = SWIFTEN_STRING_TO_CFSTRING(description);
 	CFStringRef cfName = SWIFTEN_STRING_TO_CFSTRING(typeToString(type));
@@ -92,16 +95,4 @@ void GrowlNotifier::showMessage(Type type, const String& subject, const String&
 	CFRelease(cfSubject);
 }
 
-String GrowlNotifier::typeToString(Type type) {
-	switch (type) {
-		case ContactAvailable: return "Contact Becomes Available";
-		case ContactUnavailable: return "Contact Becomes Unavailable";
-		case ContactStatusChange: return "Contact Changes Status";
-		case IncomingMessage: return "Incoming Message";
-		case SystemMessage: return "System Message";
-	}
-	assert(false);
-	return "";
-}
-
 }
diff --git a/SwifTools/Notifier/GrowlNotifier.h b/SwifTools/Notifier/GrowlNotifier.h
index fab3b5e..5d618e6 100644
--- a/SwifTools/Notifier/GrowlNotifier.h
+++ b/SwifTools/Notifier/GrowlNotifier.h
@@ -8,6 +8,7 @@
 
 #include <CoreFoundation/CoreFoundation.h>
 #include <Growl/Growl.h>
+#include <boost/filesystem/fstream.hpp>
 
 #include "SwifTools/Notifier/Notifier.h"
 
@@ -23,12 +24,9 @@ namespace Swift {
 		public:
 			GrowlNotifier(const String& name);
 
-			virtual void showMessage(Type type, const String& subject, const String& description, const ByteArray& picture, boost::function<void()> callback);
+			virtual void showMessage(Type type, const String& subject, const String& description, const boost::filesystem::path& picture, boost::function<void()> callback);
 		
 		private:
-			String typeToString(Type type);
-
-		private:
 			Growl_Delegate delegate_;
 	};
 }
diff --git a/SwifTools/Notifier/LoggingNotifier.h b/SwifTools/Notifier/LoggingNotifier.h
index 93349d9..eea07ef 100644
--- a/SwifTools/Notifier/LoggingNotifier.h
+++ b/SwifTools/Notifier/LoggingNotifier.h
@@ -12,16 +12,16 @@
 namespace Swift {
 	class LoggingNotifier : public Notifier {
 		public:
-			virtual void showMessage(Type type, const String& subject, const String& description, const ByteArray& picture, boost::function<void()> callback) {
+			virtual void showMessage(Type type, const String& subject, const String& description, const boost::filesystem::path& picture, boost::function<void()> callback) {
 				notifications.push_back(Notification(type, subject, description, picture, callback));
 			}
 
 			struct Notification {
-					Notification(Type type, const String& subject, const String& description, const ByteArray& picture, boost::function<void()> callback) : type(type), subject(subject), description(description), picture(picture), callback(callback) {}
+					Notification(Type type, const String& subject, const String& description, const boost::filesystem::path& picture, boost::function<void()> callback) : type(type), subject(subject), description(description), picture(picture), callback(callback) {}
 					Type type;
 					String subject;
 					String description;
-					ByteArray picture;
+					boost::filesystem::path picture;
 					boost::function<void()> callback;
 			};
 
diff --git a/SwifTools/Notifier/Notifier.cpp b/SwifTools/Notifier/Notifier.cpp
index 2c2cfe5..a976486 100644
--- a/SwifTools/Notifier/Notifier.cpp
+++ b/SwifTools/Notifier/Notifier.cpp
@@ -11,4 +11,34 @@ namespace Swift {
 Notifier::~Notifier() {
 }
 
+String Notifier::typeToString(Type type) {
+	switch (type) {
+		case ContactAvailable: return "Contact Becomes Available";
+		case ContactUnavailable: return "Contact Becomes Unavailable";
+		case ContactStatusChange: return "Contact Changes Status";
+		case IncomingMessage: return "Incoming Message";
+		case SystemMessage: return "System Message";
+	}
+	assert(false);
+	return "";
+}
+
+std::vector<Notifier::Type> Notifier::getAllTypes() {
+	std::vector<Type> result;
+	result.push_back(ContactAvailable);
+	result.push_back(ContactUnavailable);
+	result.push_back(ContactStatusChange);
+	result.push_back(IncomingMessage);
+	result.push_back(SystemMessage);
+	return result;
+}
+
+std::vector<Notifier::Type> Notifier::getDefaultTypes() {
+	std::vector<Type> result;
+	result.push_back(ContactAvailable);
+	result.push_back(IncomingMessage);
+	result.push_back(SystemMessage);
+	return result;
+}
+
 }
diff --git a/SwifTools/Notifier/Notifier.h b/SwifTools/Notifier/Notifier.h
index 3e3da8a..f1a89ef 100644
--- a/SwifTools/Notifier/Notifier.h
+++ b/SwifTools/Notifier/Notifier.h
@@ -7,6 +7,7 @@
 #pragma once
 
 #include <boost/function.hpp>
+#include <boost/filesystem/path.hpp>
 
 #include "Swiften/Base/String.h"
 
@@ -24,7 +25,12 @@ namespace Swift {
 				Type type,
 				const String& subject, 
 				const String& description, 
-				const ByteArray& picture, 
+				const boost::filesystem::path& picture,
 				boost::function<void()> callback) = 0;
+
+		protected:
+			String typeToString(Type type);
+			static std::vector<Type> getAllTypes();
+			static std::vector<Type> getDefaultTypes();
 	};
 }
diff --git a/SwifTools/Notifier/SConscript b/SwifTools/Notifier/SConscript
index 89fc31b..9ad2fd7 100644
--- a/SwifTools/Notifier/SConscript
+++ b/SwifTools/Notifier/SConscript
@@ -1,5 +1,7 @@
 Import("swiftools_env")
 
+myenv = swiftools_env.Clone()
+
 sources = [
 		"Notifier.cpp",
 	]
@@ -8,6 +10,11 @@ if swiftools_env.get("HAVE_GROWL", False) :
 	sources += [
 			"GrowlNotifier.cpp",
 		]
-
-objects = swiftools_env.StaticObject(sources)
+if swiftools_env.get("HAVE_SNARL", False) :
+	myenv.MergeFlags(myenv["SNARL_FLAGS"])
+	sources += [
+			"SnarlNotifier.cpp",
+		]
+		
+objects = myenv.StaticObject(sources)
 swiftools_env.Append(SWIFTOOLS_OBJECTS = objects)
diff --git a/SwifTools/Notifier/SnarlNotifier.cpp b/SwifTools/Notifier/SnarlNotifier.cpp
new file mode 100644
index 0000000..86069c9
--- /dev/null
+++ b/SwifTools/Notifier/SnarlNotifier.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "SwifTools/Notifier/SnarlNotifier.h"
+
+#include <cassert>
+#include <iostream>
+#include <boost/bind.hpp>
+
+#include "Swiften/Base/foreach.h"
+#include "SwifTools/Notifier/Win32NotifierWindow.h"
+
+#define SWIFT_SNARLNOTIFIER_MESSAGE_ID 0x4567 // Sounds sick to pick a number, but this is windows
+
+namespace Swift {
+
+SnarlNotifier::SnarlNotifier(const String& name, Win32NotifierWindow* window) : window(window) {
+	window->onMessageReceived.connect(boost::bind(&SnarlNotifier::handleMessageReceived, this, _1));
+	snarl.RegisterConfig(window->getID(), name.getUTF8Data(), 0);
+	foreach(Notifier::Type type, getAllTypes()) {
+		snarl.RegisterAlert(name.getUTF8Data(), typeToString(type).getUTF8Data());
+	}
+}
+
+SnarlNotifier::~SnarlNotifier() {
+	snarl.RevokeConfig(window->getID());
+	window->onMessageReceived.disconnect(boost::bind(&SnarlNotifier::handleMessageReceived, this, _1));
+	if (!notifications.empty()) {
+		std::cerr << "Warning: " << notifications.size() << " Snarl notifications pending" << std::endl;
+	}
+}
+
+void SnarlNotifier::showMessage(Type type, const String& subject, const String& description, const boost::filesystem::path& picture, boost::function<void()> callback) {
+	int notificationID = snarl.ShowMessageEx(typeToString(type).getUTF8Data(), subject.getUTF8Data(), description.getUTF8Data(), timeout, picture.string().c_str(), window->getID(), SWIFT_SNARLNOTIFIER_MESSAGE_ID);
+	if (notificationID > 0) {
+		notifications.insert(std::make_pair(notificationID, callback));
+	}
+}
+
+void SnarlNotifier::handleMessageReceived(MSG* message) {
+	if (message->message == SWIFT_SNARLNOTIFIER_MESSAGE_ID) {
+		int action = message->wParam;
+		if (action == Snarl::SNARL_NOTIFICATION_TIMED_OUT || action == Snarl::SNARL_NOTIFICATION_ACK || action == Snarl::SNARL_NOTIFICATION_CLOSED) {
+			int notificationID = message->lParam;
+			NotificationsMap::iterator i = notifications.find(notificationID);
+			if (i != notifications.end()) {
+				if (action == Snarl::SNARL_NOTIFICATION_ACK) {
+					i->second();
+				}
+				notifications.erase(i);
+			}
+			else {
+				std::cerr << "Warning: Orphaned Snarl notification received";
+			}
+		}
+	}
+}
+
+}
diff --git a/SwifTools/Notifier/SnarlNotifier.h b/SwifTools/Notifier/SnarlNotifier.h
new file mode 100644
index 0000000..45408ec
--- /dev/null
+++ b/SwifTools/Notifier/SnarlNotifier.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <map>
+
+#include "SwifTools/Notifier/Notifier.h"
+#include "SnarlInterface.h"
+
+namespace Swift {
+	class Win32NotifierWindow;
+
+	class SnarlNotifier : public Notifier {
+		public:
+			SnarlNotifier(const String& name, Win32NotifierWindow* window);
+			~SnarlNotifier();
+
+			virtual void showMessage(Type type, const String& subject, const String& description, const boost::filesystem::path& picture, boost::function<void()> callback);
+		
+		private:
+			void handleMessageReceived(MSG* message);
+
+		private:
+			static const int timeout = 3;
+			Snarl::SnarlInterface snarl;
+			Win32NotifierWindow* window;
+			typedef std::map<int, boost::function<void()> > NotificationsMap;
+			NotificationsMap notifications;
+	};
+}
diff --git a/SwifTools/Notifier/Win32NotifierWindow.h b/SwifTools/Notifier/Win32NotifierWindow.h
new file mode 100644
index 0000000..7ac149b
--- /dev/null
+++ b/SwifTools/Notifier/Win32NotifierWindow.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+//#include <windows.h>
+
+#include "Swiften/Base/boost_bsignals.h"
+
+namespace Swift {
+	class Win32NotifierWindow {
+		public:
+			virtual ~Win32NotifierWindow() {}
+
+			virtual HWND getID() const = 0;
+
+			boost::signal<void (MSG*)> onMessageReceived;
+	};
+}
diff --git a/Swift/Controllers/PresenceNotifier.cpp b/Swift/Controllers/PresenceNotifier.cpp
index ce7ae40..0c46f9b 100644
--- a/Swift/Controllers/PresenceNotifier.cpp
+++ b/Swift/Controllers/PresenceNotifier.cpp
@@ -90,7 +90,7 @@ void PresenceNotifier::showNotification(const JID& jid, Notifier::Type type) {
 	}
 	String title = name + " (" + getStatusType(jid) + ")";
 	String message = getStatusMessage(jid);
-	notifier->showMessage(type, title, message, avatarManager->getAvatar(jid), boost::bind(&PresenceNotifier::handleNotificationActivated, this, jid));
+	notifier->showMessage(type, title, message, avatarManager->getAvatarPath(jid), boost::bind(&PresenceNotifier::handleNotificationActivated, this, jid));
 }
 
 void PresenceNotifier::handleNotificationActivated(JID jid) {
diff --git a/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp b/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp
index 85433f3..a410665 100644
--- a/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp
+++ b/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp
@@ -145,7 +145,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
 			sendPresence(user1, StatusShow::Online);
 
 			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(notifier->notifications.size()));
-			CPPUNIT_ASSERT_EQUAL(ByteArray("abcdef"), notifier->notifications[0].picture);
+			CPPUNIT_ASSERT_EQUAL(boost::filesystem::path("/avatars/user1@bar.com/bla"), notifier->notifications[0].picture);
 		}
 
 		void testNotificationActivationEmitsSignal() {
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index ee97fc6..0118416 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -38,6 +38,9 @@
 #include "SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h"
 #if defined(HAVE_GROWL)
 #include "SwifTools/Notifier/GrowlNotifier.h"
+#elif defined(HAVE_SNARL)
+#include "QtWin32NotifierWindow.h"
+#include "SwifTools/Notifier/SnarlNotifier.h"
 #else
 #include "SwifTools/Notifier/NullNotifier.h"
 #endif
@@ -97,6 +100,9 @@ QtSwift::QtSwift(po::variables_map options) : autoUpdater_(NULL) {
 	soundPlayer_ = new QtSoundPlayer(applicationPathProvider_);
 #if defined(HAVE_GROWL)
 	notifier_ = new GrowlNotifier(SWIFT_APPLICATION_NAME);
+#elif defined(HAVE_SNARL)
+	notifierWindow_ = new QtWin32NotifierWindow();
+	notifier_ = new SnarlNotifier(SWIFT_APPLICATION_NAME, notifierWindow_);
 #else
 	notifier_ = new NullNotifier();
 #endif
@@ -155,6 +161,7 @@ QtSwift::QtSwift(po::variables_map options) : autoUpdater_(NULL) {
 }
 
 QtSwift::~QtSwift() {
+	delete notifier_;
 	delete autoUpdater_;
 	delete chatWindowFactory_;
 	foreach (QtMainWindowFactory* factory, rosterWindowFactories_) {
diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h
index abc8c75..092c45e 100644
--- a/Swift/QtUI/QtSwift.h
+++ b/Swift/QtUI/QtSwift.h
@@ -20,6 +20,9 @@
 #if defined(SWIFTEN_PLATFORM_MACOSX)
 #include "Swiften/Application/CocoaApplication.h"
 #endif
+#if defined(HAVE_SNARL)
+#include "SwifTools/Notifier/Win32NotifierWindow.h"
+#endif
 
 namespace po = boost::program_options;
 
@@ -75,6 +78,9 @@ namespace Swift {
 #if defined(SWIFTEN_PLATFORM_MACOSX)
 			CocoaApplication cocoaApplication_;
 #endif
+#if defined(HAVE_SNARL)
+			Win32NotifierWindow* notifierWindow_;
+#endif
 	};
 }
 
diff --git a/Swift/QtUI/QtWin32NotifierWindow.h b/Swift/QtUI/QtWin32NotifierWindow.h
new file mode 100644
index 0000000..b8d9c77
--- /dev/null
+++ b/Swift/QtUI/QtWin32NotifierWindow.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QWidget>
+
+#include "SwifTools/Notifier/Win32NotifierWindow.h"
+
+namespace Swift {
+	class QtWin32NotifierWindow : public QWidget, public Win32NotifierWindow {
+		public:
+			QtWin32NotifierWindow(QWidget* parent = NULL) {
+				setVisible(false);
+			}
+
+			bool winEvent (MSG* message, long* result ) {
+				onMessageReceived(message);
+				return false;
+			}
+
+			virtual HWND getID() const {
+				return winId();
+			}
+	};
+}
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 634207c..933133d 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -38,6 +38,9 @@ myenv.MergeFlags(env.get("EXPAT_FLAGS", ""))
 if myenv.get("HAVE_GROWL", False) :
 	myenv.MergeFlags(myenv["GROWL_FLAGS"])
 	myenv.Append(CPPDEFINES = ["HAVE_GROWL"])
+if myenv.get("HAVE_SNARL", False) :
+	myenv.MergeFlags(myenv["SNARL_FLAGS"])
+	myenv.Append(CPPDEFINES = ["HAVE_SNARL"])
 myenv.MergeFlags(myenv["PLATFORM_FLAGS"])
 
 myenv.Tool("qt4", toolpath = ["#/BuildTools/SCons/Tools"])
diff --git a/Swiften/Avatars/AvatarMemoryStorage.h b/Swiften/Avatars/AvatarMemoryStorage.h
index 13fbd9b..6f1ba49 100644
--- a/Swiften/Avatars/AvatarMemoryStorage.h
+++ b/Swiften/Avatars/AvatarMemoryStorage.h
@@ -22,8 +22,8 @@ namespace Swift {
 				return i == avatars.end() ? ByteArray() : i->second;
 			}
 
-			virtual boost::filesystem::path getAvatarPath(const String& /*hash*/) const {
-				return boost::filesystem::path();
+			virtual boost::filesystem::path getAvatarPath(const String& hash) const {
+				return boost::filesystem::path("/avatars") / hash.getUTF8String();
 			}
 
 		private:
diff --git a/Swiften/Avatars/DummyAvatarManager.h b/Swiften/Avatars/DummyAvatarManager.h
index db63b05..ec5068e 100644
--- a/Swiften/Avatars/DummyAvatarManager.h
+++ b/Swiften/Avatars/DummyAvatarManager.h
@@ -13,8 +13,8 @@
 namespace Swift {
 	class DummyAvatarManager : public AvatarManager {
 		public:
-			virtual boost::filesystem::path getAvatarPath(const JID&) const {
-				return boost::filesystem::path();
+			virtual boost::filesystem::path getAvatarPath(const JID& j) const {
+				return boost::filesystem::path("/avatars") / j.toString().getUTF8String();
 			}
 
 			virtual ByteArray getAvatar(const JID& jid) const {
-- 
cgit v0.10.2-6-g49f6