From 40587e704a2a8dfe7d29cc2e28e140f01c9f86bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sat, 2 Apr 2011 22:46:42 +0200
Subject: Added RFC5122 XMPP URI parsing and basic handling.

URI Handling currently only works on Mac OS X.

diff --git a/BuildTools/SCons/Tools/AppBundle.py b/BuildTools/SCons/Tools/AppBundle.py
index c271575..6a343f6 100644
--- a/BuildTools/SCons/Tools/AppBundle.py
+++ b/BuildTools/SCons/Tools/AppBundle.py
@@ -1,7 +1,7 @@
 import SCons.Util, os.path
 
 def generate(env) :
-  def createAppBundle(env, bundle, version = "1.0", resources = [], frameworks = [], info = {}) :
+  def createAppBundle(env, bundle, version = "1.0", resources = [], frameworks = [], info = {}, handlesXMPPURIs = False) :
     bundleDir = bundle + ".app"
     bundleContentsDir = bundleDir + "/Contents"
     resourcesDir = bundleContentsDir + "/Resources"
@@ -32,6 +32,18 @@ def generate(env) :
     for key, value in infoDict.items() :
       plist += "<key>" + key + "</key>\n"
       plist += "<string>" + value.encode("utf-8") + "</string>\n"
+    if handlesXMPPURIs :
+      plist += """<key>CFBundleURLTypes</key>
+<array>
+    <dict>
+        <key>CFBundleURLName</key>
+        <string>XMPP URL</string>
+        <key>CFBundleURLSchemes</key>
+        <array>
+            <string>xmpp</string>
+        </array>
+    </dict>
+</array>\n"""
     plist += """</dict>
   </plist>
   """
diff --git a/Slimber/Cocoa/CocoaAction.h b/Slimber/Cocoa/CocoaAction.h
deleted file mode 100644
index a46ef7c..0000000
--- a/Slimber/Cocoa/CocoaAction.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 <Cocoa/Cocoa.h>
-#include <boost/function.hpp>
-
-@interface CocoaAction : NSObject {
-	boost::function<void ()>* function;
-}
-
-/**
- * Acquires ownership of 'f'.
- */
-- (id) initWithFunction: (boost::function<void()>*) f;
-
-/**
- * Calls the functor passed as a parameter to the contsructor.
- */
-- (void) doAction: (id) sender;
-
-@end
diff --git a/Slimber/Cocoa/CocoaAction.mm b/Slimber/Cocoa/CocoaAction.mm
deleted file mode 100644
index 15498a1..0000000
--- a/Slimber/Cocoa/CocoaAction.mm
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "Slimber/Cocoa/CocoaAction.h"
-
-@implementation CocoaAction
-
-- (id) initWithFunction: (boost::function<void()>*) f {
-    if ([super init]) {
-			function = f;
-		}
-    return self;
-}
-
-- (void) dealloc {
-	delete function;
-	[super dealloc];
-}
-
-- (void) doAction: (id) sender {
-	(void) sender;
-	(*function)();
-}
-
-@end
diff --git a/Slimber/Cocoa/CocoaMenulet.h b/Slimber/Cocoa/CocoaMenulet.h
index 7f2758b..5c7c33e 100644
--- a/Slimber/Cocoa/CocoaMenulet.h
+++ b/Slimber/Cocoa/CocoaMenulet.h
@@ -9,7 +9,7 @@
 #include <Cocoa/Cocoa.h>
 
 #include "Slimber/Menulet.h"
-#include "Slimber/Cocoa/CocoaAction.h"
+#include <SwifTools/Cocoa/CocoaAction.h>
 
 class CocoaMenulet : public Menulet {
 	public:
diff --git a/Slimber/Cocoa/SConscript b/Slimber/Cocoa/SConscript
index e2d8221..d664846 100644
--- a/Slimber/Cocoa/SConscript
+++ b/Slimber/Cocoa/SConscript
@@ -16,7 +16,6 @@ myenv.Program("Slimber", [
 		"main.mm",
 		"CocoaController.mm",
 		"CocoaMenulet.mm",
-		"CocoaAction.mm"
 	])
 
 myenv.Nib("MainMenu")
diff --git a/SwifTools/Cocoa/CocoaAction.h b/SwifTools/Cocoa/CocoaAction.h
new file mode 100644
index 0000000..a46ef7c
--- /dev/null
+++ b/SwifTools/Cocoa/CocoaAction.h
@@ -0,0 +1,26 @@
+/*
+ * 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 <Cocoa/Cocoa.h>
+#include <boost/function.hpp>
+
+@interface CocoaAction : NSObject {
+	boost::function<void ()>* function;
+}
+
+/**
+ * Acquires ownership of 'f'.
+ */
+- (id) initWithFunction: (boost::function<void()>*) f;
+
+/**
+ * Calls the functor passed as a parameter to the contsructor.
+ */
+- (void) doAction: (id) sender;
+
+@end
diff --git a/SwifTools/Cocoa/CocoaAction.mm b/SwifTools/Cocoa/CocoaAction.mm
new file mode 100644
index 0000000..d560787
--- /dev/null
+++ b/SwifTools/Cocoa/CocoaAction.mm
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <SwifTools/Cocoa/CocoaAction.h>
+
+@implementation CocoaAction
+
+- (id) initWithFunction: (boost::function<void()>*) f {
+    if ([super init]) {
+			function = f;
+		}
+    return self;
+}
+
+- (void) dealloc {
+	delete function;
+	[super dealloc];
+}
+
+- (void) doAction: (id) sender {
+	(void) sender;
+	(*function)();
+}
+
+@end
diff --git a/SwifTools/Cocoa/SConscript b/SwifTools/Cocoa/SConscript
new file mode 100644
index 0000000..4ae4a07
--- /dev/null
+++ b/SwifTools/Cocoa/SConscript
@@ -0,0 +1,8 @@
+Import("swiftools_env", "env")
+
+sources = []
+if swiftools_env["PLATFORM"] == "darwin" and swiftools_env["target"] == "native" :
+	sources += ["CocoaAction.mm"]
+
+objects = swiftools_env.StaticObject(sources)
+swiftools_env.Append(SWIFTOOLS_OBJECTS = [objects])
diff --git a/SwifTools/Linkify.cpp b/SwifTools/Linkify.cpp
index 91c713f..a5deccb 100644
--- a/SwifTools/Linkify.cpp
+++ b/SwifTools/Linkify.cpp
@@ -12,7 +12,7 @@
 
 namespace Swift {
 
-static boost::regex linkifyRegexp("^https?://.*");
+static boost::regex linkifyRegexp("^(https?://|xmpp:).*");
 
 std::string Linkify::linkify(const std::string& input) {
 	std::ostringstream result;
diff --git a/SwifTools/SConscript b/SwifTools/SConscript
index d4747db..8d00418 100644
--- a/SwifTools/SConscript
+++ b/SwifTools/SConscript
@@ -50,8 +50,10 @@ if env["SCONS_STAGE"] == "build" :
 			"Application",
 			"Dock",
 			"Notifier",
+			"URIHandler",
 			"Idle/IdleQuerierTest",
 			"Idle/UnitTest",
+			"Cocoa",
 			"UnitTest"
 		])
 
diff --git a/SwifTools/URIHandler/MacOSXURIHandler.h b/SwifTools/URIHandler/MacOSXURIHandler.h
new file mode 100644
index 0000000..f803420
--- /dev/null
+++ b/SwifTools/URIHandler/MacOSXURIHandler.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <SwifTools/URIHandler/URIHandler.h>
+
+namespace Swift {
+	class MacOSXURIHandler : public URIHandler {
+		public:
+			MacOSXURIHandler();
+			virtual ~MacOSXURIHandler();
+
+			virtual void start();
+			virtual void stop();
+
+		private:
+			class Private;
+			Private* p;
+	};
+}
diff --git a/SwifTools/URIHandler/MacOSXURIHandler.mm b/SwifTools/URIHandler/MacOSXURIHandler.mm
new file mode 100644
index 0000000..3542e2f
--- /dev/null
+++ b/SwifTools/URIHandler/MacOSXURIHandler.mm
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <SwifTools/URIHandler/MacOSXURIHandler.h>
+
+#include <Cocoa/Cocoa.h>
+#include <iostream>
+
+using namespace Swift;
+
+@interface MacOSXURIEventHandler : NSObject {
+	URIHandler* handler;
+}
+- (id) initWithHandler: (URIHandler*) handler;
+- (void) getUrl: (NSAppleEventDescriptor*) event withReplyEvent: (NSAppleEventDescriptor*) replyEvent;
+
+@end
+@implementation MacOSXURIEventHandler
+	- (id) initWithHandler: (URIHandler*) h {
+		if ([super init]) {
+			handler = h;
+		}
+		return self;
+	}
+
+	- (void) getUrl: (NSAppleEventDescriptor*) event withReplyEvent: (NSAppleEventDescriptor*) replyEvent {
+		(void) replyEvent;
+		NSString* url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
+		handler->onURI(std::string([url UTF8String]));
+	}
+@end
+
+class MacOSXURIHandler::Private {
+	public:
+		MacOSXURIEventHandler* eventHandler;
+};
+
+MacOSXURIHandler::MacOSXURIHandler() {
+	p = new Private();
+	p->eventHandler = [[MacOSXURIEventHandler alloc] initWithHandler: this]; 
+}
+
+MacOSXURIHandler::~MacOSXURIHandler() {
+	[p->eventHandler release];
+	delete p;
+}
+
+void MacOSXURIHandler::start() {
+	[[NSAppleEventManager sharedAppleEventManager] setEventHandler:p->eventHandler andSelector:@selector(getUrl:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
+	NSString* bundleID = [[NSBundle mainBundle] bundleIdentifier];
+	LSSetDefaultHandlerForURLScheme((CFStringRef)@"xmpp", (CFStringRef)bundleID);
+}
+
+void MacOSXURIHandler::stop() {
+	[[NSAppleEventManager sharedAppleEventManager] removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL];
+}
diff --git a/SwifTools/URIHandler/MacOSXURIHandlerHelpers.h b/SwifTools/URIHandler/MacOSXURIHandlerHelpers.h
new file mode 100644
index 0000000..5a2db7a
--- /dev/null
+++ b/SwifTools/URIHandler/MacOSXURIHandlerHelpers.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+	void registerAppAsDefaultXMPPURIHandler();
+}
diff --git a/SwifTools/URIHandler/MacOSXURIHandlerHelpers.mm b/SwifTools/URIHandler/MacOSXURIHandlerHelpers.mm
new file mode 100644
index 0000000..dca91b8
--- /dev/null
+++ b/SwifTools/URIHandler/MacOSXURIHandlerHelpers.mm
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <SwifTools/URIHandler/MacOSXURIHandlerHelpers.h>
+
+#include <Cocoa/Cocoa.h>
+
+namespace Swift {
+	void registerAppAsDefaultXMPPURIHandler() {
+		NSString* bundleID = [[NSBundle mainBundle] bundleIdentifier];
+		LSSetDefaultHandlerForURLScheme((CFStringRef)@"xmpp", (CFStringRef)bundleID);
+	}
+}
diff --git a/SwifTools/URIHandler/NullURIHandler.h b/SwifTools/URIHandler/NullURIHandler.h
new file mode 100644
index 0000000..28c35bb
--- /dev/null
+++ b/SwifTools/URIHandler/NullURIHandler.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <SwifTools/URIHandler/URIHandler.h>
+
+namespace Swift {
+	class NullURIHandler : public URIHandler {
+		public:
+			virtual void start() {
+			}
+
+			virtual void stop() {
+			}
+	};
+}
diff --git a/SwifTools/URIHandler/SConscript b/SwifTools/URIHandler/SConscript
new file mode 100644
index 0000000..42c6ca8
--- /dev/null
+++ b/SwifTools/URIHandler/SConscript
@@ -0,0 +1,23 @@
+Import("swiftools_env", "env")
+
+sources = [
+		"XMPPURI.cpp",
+		"URIHandler.cpp",
+	]
+
+if swiftools_env["PLATFORM"] == "darwin" and swiftools_env["target"] == "native" :
+	sources += [
+			"MacOSXURIHandler.mm",
+			"MacOSXURIHandlerHelpers.mm",
+		]
+elif swiftools_env["PLATFORM"] == "win32" :
+	sources += []
+else :
+	sources += []
+
+objects = swiftools_env.StaticObject(sources)
+swiftools_env.Append(SWIFTOOLS_OBJECTS = [objects])
+
+env.Append(UNITTEST_SOURCES = [
+		File("UnitTest/XMPPURITest.cpp"),
+	])
diff --git a/SwifTools/URIHandler/URIHandler.cpp b/SwifTools/URIHandler/URIHandler.cpp
new file mode 100644
index 0000000..91e54e5
--- /dev/null
+++ b/SwifTools/URIHandler/URIHandler.cpp
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <SwifTools/URIHandler/URIHandler.h>
+
+using namespace Swift;
+
+URIHandler::URIHandler() {
+}
+
+URIHandler::~URIHandler() {
+}
diff --git a/SwifTools/URIHandler/URIHandler.h b/SwifTools/URIHandler/URIHandler.h
new file mode 100644
index 0000000..9dd13a8
--- /dev/null
+++ b/SwifTools/URIHandler/URIHandler.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <Swiften/Base/boost_bsignals.h>
+
+namespace Swift {
+	class URIHandler {
+		public:
+			URIHandler();
+			virtual ~URIHandler();
+
+			boost::signal<void (const std::string&)> onURI;
+	};
+}
diff --git a/SwifTools/URIHandler/UnitTest/XMPPURITest.cpp b/SwifTools/URIHandler/UnitTest/XMPPURITest.cpp
new file mode 100644
index 0000000..8d03b60
--- /dev/null
+++ b/SwifTools/URIHandler/UnitTest/XMPPURITest.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <SwifTools/URIHandler/XMPPURI.h>
+
+using namespace Swift;
+
+class XMPPURITest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(XMPPURITest);
+		CPPUNIT_TEST(testFromString_Authority);
+		CPPUNIT_TEST(testFromString_AuthorityWithPath);
+		CPPUNIT_TEST(testFromString_AuthorityWithFragment);
+		CPPUNIT_TEST(testFromString_AuthorityWithPathAndFragment);
+		CPPUNIT_TEST(testFromString_AuthorityWithIntlChars);
+		CPPUNIT_TEST(testFromString_AuthorityWithQueryWithoutParameters);
+		CPPUNIT_TEST(testFromString_AuthorityWithQueryWithParameters);
+		CPPUNIT_TEST(testFromString_AuthorityWithQueryWithoutParametersWithFragment);
+		CPPUNIT_TEST(testFromString_AuthorityWithQueryWithParametersWithFragment);
+		CPPUNIT_TEST(testFromString_Path);
+		CPPUNIT_TEST(testFromString_PathWithFragment);
+		CPPUNIT_TEST(testFromString_PathWithIntlChars);
+		CPPUNIT_TEST(testFromString_PathWithInvalidEscapedChar);
+		CPPUNIT_TEST(testFromString_PathWithIncompleteEscapedChar);
+		CPPUNIT_TEST(testFromString_PathWithIncompleteEscapedChar2);
+		CPPUNIT_TEST(testFromString_PathWithQueryWithoutParameters);
+		CPPUNIT_TEST(testFromString_PathWithQueryWithParameters);
+		CPPUNIT_TEST(testFromString_PathWithQueryWithoutParametersWithFragment);
+		CPPUNIT_TEST(testFromString_PathWithQueryWithParametersWithFragment);
+		CPPUNIT_TEST(testFromString_NoPrefix);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void testFromString_Authority() {
+			XMPPURI testling = XMPPURI::fromString("xmpp://foo@bar.com");
+
+			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), testling.getAuthority());
+		}
+
+		void testFromString_AuthorityWithPath() {
+			XMPPURI testling = XMPPURI::fromString("xmpp://foo@bar.com/baz@example.com");
+
+			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), testling.getAuthority());
+			CPPUNIT_ASSERT_EQUAL(JID("baz@example.com"), testling.getPath());
+		}
+
+		void testFromString_AuthorityWithFragment() {
+			XMPPURI testling = XMPPURI::fromString("xmpp://foo@bar.com#myfragment");
+
+			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), testling.getAuthority());
+			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment());
+		}
+
+		void testFromString_AuthorityWithPathAndFragment() {
+			XMPPURI testling = XMPPURI::fromString("xmpp://foo@bar.com/baz@example.com#myfragment");
+
+			CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com"), testling.getAuthority());
+			CPPUNIT_ASSERT_EQUAL(JID("baz@example.com"), testling.getPath());
+			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment());
+		}
+
+		void testFromString_AuthorityWithIntlChars() {
+			XMPPURI testling = XMPPURI::fromString("xmpp://nasty!%23$%25()*+,-.;=\%3F\%5B\%5C\%5D\%5E_\%60\%7B\%7C\%7D~node@example.com");
+
+			CPPUNIT_ASSERT_EQUAL(JID("nasty!#$\%()*+,-.;=?[\\]^_`{|}~node@example.com"), testling.getAuthority());
+		}
+
+		void testFromString_AuthorityWithQueryWithoutParameters() {
+			XMPPURI testling = XMPPURI::fromString("xmpp://test@example.com?message");
+
+			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getAuthority());
+			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType());
+		}
+
+		void testFromString_AuthorityWithQueryWithParameters() {
+			XMPPURI testling = XMPPURI::fromString("xmpp://test@example.com?message;subject=Test%20Message;body=Here%27s%20a%20test%20message");
+
+			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getAuthority());
+			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType());
+			CPPUNIT_ASSERT_EQUAL(std::string("Test Message"), get(testling.getQueryParameters(), "subject"));
+			CPPUNIT_ASSERT_EQUAL(std::string("Here's a test message"), get(testling.getQueryParameters(), "body"));
+		}
+
+		void testFromString_AuthorityWithQueryWithoutParametersWithFragment() {
+			XMPPURI testling = XMPPURI::fromString("xmpp://test@example.com?message#myfragment");
+
+			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getAuthority());
+			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType());
+			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment());
+		}
+
+		void testFromString_AuthorityWithQueryWithParametersWithFragment() {
+			XMPPURI testling = XMPPURI::fromString("xmpp://test@example.com?message;subject=Test%20Message;body=Here%27s%20a%20test%20message#myfragment");
+
+			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getAuthority());
+			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType());
+			CPPUNIT_ASSERT_EQUAL(std::string("Test Message"), get(testling.getQueryParameters(), "subject"));
+			CPPUNIT_ASSERT_EQUAL(std::string("Here's a test message"), get(testling.getQueryParameters(), "body"));
+			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment());
+		}
+
+		void testFromString_Path() {
+			XMPPURI testling = XMPPURI::fromString("xmpp:baz@example.com");
+
+			CPPUNIT_ASSERT_EQUAL(JID("baz@example.com"), testling.getPath());
+		}
+
+		void testFromString_PathWithFragment() {
+			XMPPURI testling = XMPPURI::fromString("xmpp:baz@example.com#myfragment");
+
+			CPPUNIT_ASSERT_EQUAL(JID("baz@example.com"), testling.getPath());
+			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment());
+		}
+
+		void testFromString_PathWithIntlChars() {
+			XMPPURI testling = XMPPURI::fromString("xmpp:nasty!%23$%25()*+,-.;=\%3F\%5B\%5C\%5D\%5E_\%60\%7B\%7C\%7D~node@example.com");
+
+			CPPUNIT_ASSERT_EQUAL(JID("nasty!#$\%()*+,-.;=?[\\]^_`{|}~node@example.com"), testling.getPath());
+		}
+
+		void testFromString_PathWithInvalidEscapedChar() {
+			XMPPURI testling = XMPPURI::fromString("xmpp:test%%@example.com");
+
+			CPPUNIT_ASSERT_EQUAL(JID(), testling.getPath());
+		}
+
+		void testFromString_PathWithIncompleteEscapedChar() {
+			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com%");
+
+			CPPUNIT_ASSERT_EQUAL(JID(), testling.getPath());
+		}
+
+		void testFromString_PathWithIncompleteEscapedChar2() {
+			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com%1");
+
+			CPPUNIT_ASSERT_EQUAL(JID(), testling.getPath());
+		}
+
+		void testFromString_PathWithQueryWithoutParameters() {
+			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com?message");
+
+			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getPath());
+			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType());
+		}
+
+		void testFromString_PathWithQueryWithParameters() {
+			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com?message;subject=Test%20Message;body=Here%27s%20a%20test%20message");
+
+			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getPath());
+			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType());
+			CPPUNIT_ASSERT_EQUAL(std::string("Test Message"), get(testling.getQueryParameters(), "subject"));
+			CPPUNIT_ASSERT_EQUAL(std::string("Here's a test message"), get(testling.getQueryParameters(), "body"));
+		}
+
+		void testFromString_PathWithQueryWithoutParametersWithFragment() {
+			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com?message#myfragment");
+
+			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getPath());
+			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType());
+			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment());
+		}
+
+		void testFromString_PathWithQueryWithParametersWithFragment() {
+			XMPPURI testling = XMPPURI::fromString("xmpp:test@example.com?message;subject=Test%20Message;body=Here%27s%20a%20test%20message#myfragment");
+
+			CPPUNIT_ASSERT_EQUAL(JID("test@example.com"), testling.getPath());
+			CPPUNIT_ASSERT_EQUAL(std::string("message"), testling.getQueryType());
+			CPPUNIT_ASSERT_EQUAL(std::string("Test Message"), get(testling.getQueryParameters(), "subject"));
+			CPPUNIT_ASSERT_EQUAL(std::string("Here's a test message"), get(testling.getQueryParameters(), "body"));
+			CPPUNIT_ASSERT_EQUAL(std::string("myfragment"), testling.getFragment());
+		}
+
+		void testFromString_NoPrefix() {
+			XMPPURI testling = XMPPURI::fromString("baz@example.com");
+
+			CPPUNIT_ASSERT(testling.isNull());
+		}
+
+	private:
+		std::string get(const std::map<std::string, std::string>& m, const std::string& k) {
+			std::map<std::string, std::string>::const_iterator i = m.find(k);
+			return i == m.end() ? "" : i->second;
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(XMPPURITest);
diff --git a/SwifTools/URIHandler/XMPPURI.cpp b/SwifTools/URIHandler/XMPPURI.cpp
new file mode 100644
index 0000000..d9fde07
--- /dev/null
+++ b/SwifTools/URIHandler/XMPPURI.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <SwifTools/URIHandler/XMPPURI.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/find_format.hpp>
+#include <boost/algorithm/string/formatter.hpp>
+#include <boost/algorithm/string/find_iterator.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <sstream>
+#include <stdexcept>
+#include <vector>
+
+using namespace Swift;
+
+namespace {
+
+	struct PercentEncodedCharacterFinder {
+		template<typename Iterator>
+		boost::iterator_range<Iterator> operator()(Iterator begin, Iterator end) {
+			boost::iterator_range<Iterator> r = boost::first_finder("%")(begin, end);
+			if (r.end() == end) {
+				return r;
+			}
+			else {
+				if (r.end() + 1 == end || r.end() + 2 == end) {
+					throw std::runtime_error("Incomplete escape character");
+				}
+				else {
+					r.advance_end(2);
+					return r;
+				}
+			}
+		}
+	};
+
+	struct PercentUnencodeFormatter {
+		template<typename FindResult>
+		std::string operator()(const FindResult& match) const {
+			std::stringstream s;
+			s << std::hex << std::string(match.begin() + 1, match.end());
+			unsigned int value;
+			s >> value;
+			if (s.fail() || s.bad()) {
+				throw std::runtime_error("Invalid escape character");
+			}
+			return std::string(reinterpret_cast<const char*>(&value), 1);
+		}
+	};
+	
+
+	std::string unescape(const std::string& s) {
+		try {
+			return boost::find_format_all_copy(s, PercentEncodedCharacterFinder(), PercentUnencodeFormatter());
+		}
+		catch (const std::exception&) {
+			return "";
+		}
+	}
+}
+
+XMPPURI::XMPPURI() {
+}
+
+XMPPURI XMPPURI::fromString(const std::string& s) {
+	XMPPURI result;
+	if (boost::starts_with(s, "xmpp:")) {
+		std::string uri = s.substr(5, s.npos);
+		bool parsePath = true;
+		bool parseQuery = true;
+		bool parseFragment = true;
+
+		// Parse authority
+		if (boost::starts_with(uri, "//")) {
+			size_t i = uri.find_first_of("/#?", 2);
+			result.setAuthority(JID(unescape(uri.substr(2, i - 2))));
+			if (i == uri.npos) {
+				uri = "";
+				parsePath = parseQuery = parseFragment = false;
+			}
+			else {
+				if (uri[i] == '?') {
+					parsePath = false;
+				}
+				else if (uri[i] == '#') {
+					parseQuery = parsePath = false;
+				}
+				uri = uri.substr(i + 1, uri.npos);
+			}
+		}
+
+		// Parse path
+		if (parsePath) {
+			size_t i = uri.find_first_of("#?");
+			result.setPath(JID(unescape(uri.substr(0, i))));
+			if (i == uri.npos) {
+				uri = "";
+				parseQuery = parseFragment = false;
+			}
+			else {
+				if (uri[i] == '#') {
+					parseQuery = false;
+				}
+				uri = uri.substr(i + 1, uri.npos);
+			}
+		}
+
+		// Parse query
+		if (parseQuery) {
+			size_t end = uri.find_first_of("#");
+			std::string query = uri.substr(0, end);
+			bool haveType = false;
+			typedef boost::split_iterator<std::string::iterator> split_iterator;
+	    for (split_iterator it = boost::make_split_iterator(query, boost::first_finder(";")); it != split_iterator(); ++it) {
+	    	if (haveType) {
+	    		std::vector<std::string> keyValue;
+	    		boost::split(keyValue, *it, boost::is_any_of("="));
+	    		if (keyValue.size() == 1) {
+	    			result.addQueryParameter(unescape(keyValue[0]), "");
+	    		}
+	    		else if (keyValue.size() >= 2) {
+	    			result.addQueryParameter(unescape(keyValue[0]), unescape(keyValue[1]));
+	    		}
+	    	}
+	    	else {
+	    		result.setQueryType(unescape(boost::copy_range<std::string>(*it)));
+	    		haveType = true;
+	    	}
+	    }
+	    uri = (end == uri.npos ? "" : uri.substr(end + 1, uri.npos));
+		}
+
+		// Parse fragment
+		if (parseFragment) {
+			result.setFragment(unescape(uri));
+		}
+	}
+	return result;
+}
diff --git a/SwifTools/URIHandler/XMPPURI.h b/SwifTools/URIHandler/XMPPURI.h
new file mode 100644
index 0000000..266b79b
--- /dev/null
+++ b/SwifTools/URIHandler/XMPPURI.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <map>
+
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+	class XMPPURI {
+		public:
+			XMPPURI();
+
+			const JID& getAuthority() const {
+				return authority;
+			}
+
+			void setAuthority(const JID& j) {
+				authority = j;
+			}
+
+			const JID& getPath() const {
+				return path;
+			}
+
+			void setPath(const JID& j) {
+				path = j;
+			}
+
+			const std::string& getQueryType() const {
+				return queryType;
+			}
+
+			void setQueryType(const std::string& q) {
+				queryType = q;
+			}
+
+			const std::map<std::string, std::string>& getQueryParameters() const {
+				return queryParameters;
+			}
+
+			void addQueryParameter(const std::string& key, const std::string& path) {
+				queryParameters[key] = path;
+			}
+
+			const std::string& getFragment() const {
+				return fragment;
+			}
+
+			void setFragment(const std::string& f) {
+				fragment = f;
+			}
+
+			bool isNull() const {
+				return !authority.isValid() && !path.isValid();
+			}
+
+			static XMPPURI fromString(const std::string&);
+
+		private:
+			JID authority;
+			JID path;
+			std::string fragment;
+			std::string queryType;
+			std::map<std::string, std::string> queryParameters;
+	};
+}
diff --git a/SwifTools/UnitTest/SConscript b/SwifTools/UnitTest/SConscript
index 8034905..9fd1375 100644
--- a/SwifTools/UnitTest/SConscript
+++ b/SwifTools/UnitTest/SConscript
@@ -2,5 +2,5 @@ Import("env")
 
 env.Append(UNITTEST_SOURCES = [
 		File("LinkifyTest.cpp"),
-		File("TabCompleteTest.cpp")
+		File("TabCompleteTest.cpp"),
 	])
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 5f51ac6..972501f 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -158,13 +158,13 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
 	else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) {
 		handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getNick(), false);
 	}
-	else if (boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
+	else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
 		if (!joinMUCWindow_) {
 			joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow();
 			joinMUCWindow_->onJoinMUC.connect(boost::bind(&ChatsManager::handleJoinMUCRequest, this, _1, _2, _3));
 			joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this));
 		}
-		joinMUCWindow_->setMUC("");
+		joinMUCWindow_->setMUC(joinEvent->getRoom());
 		joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_));
 		joinMUCWindow_->show();
 	}
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 9a35cc1..3f86b43 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -65,6 +65,7 @@
 #include "Swiften/Network/NetworkFactories.h"
 #include <Swift/Controllers/ProfileController.h>
 #include <Swift/Controllers/ContactEditController.h>
+#include <Swift/Controllers/XMPPURIController.h>
 
 namespace Swift {
 
@@ -84,6 +85,7 @@ MainController::MainController(
 		CertificateStorageFactory* certificateStorageFactory,
 		Dock* dock,
 		Notifier* notifier,
+		URIHandler* uriHandler,
 		bool useDelayForLatency) :
 			eventLoop_(eventLoop),
 			networkFactories_(networkFactories),
@@ -92,6 +94,7 @@ MainController::MainController(
 			storagesFactory_(storagesFactory),
 			certificateStorageFactory_(certificateStorageFactory),
 			settings_(settings),
+			uriHandler_(uriHandler),
 			loginWindow_(NULL) ,
 			useDelayForLatency_(useDelayForLatency) {
 	storages_ = NULL;
@@ -121,6 +124,8 @@ MainController::MainController(
 	loginWindow_ = uiFactory_->createLoginWindow(uiEventStream_);
 	soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings, uiEventStream_);
 
+	xmppURIController_ = new XMPPURIController(uriHandler_, uiEventStream_);
+
 	std::string selectedLoginJID = settings_->getStringSetting("lastLoginJID");
 	bool loginAutomatically = settings_->getBoolSetting("loginAutomatically", false);
 	std::string cachedPassword;
@@ -167,6 +172,7 @@ MainController::~MainController() {
 	resetClient();
 
 	delete xmlConsoleController_;
+	delete xmppURIController_;
 	delete soundEventController_;
 	delete systemTrayController_;
 	delete eventController_;
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index f402f8f..f9722de 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -62,6 +62,8 @@ namespace Swift {
 	class Storages;
 	class StoragesFactory;
 	class NetworkFactories;
+	class URIHandler;
+	class XMPPURIController;
 
 	class MainController {
 		public:
@@ -76,6 +78,7 @@ namespace Swift {
 					CertificateStorageFactory* certificateStorageFactory,
 					Dock* dock,
 					Notifier* notifier,
+					URIHandler* uriHandler,
 					bool useDelayForLatency);
 			~MainController();
 
@@ -123,6 +126,7 @@ namespace Swift {
 			SettingsProvider *settings_;
 			ProfileSettingsProvider* profileSettings_;
 			Dock* dock_;
+			URIHandler* uriHandler_;
 			TogglableNotifier* notifier_;
 			PresenceNotifier* presenceNotifier_;
 			EventNotifier* eventNotifier_;
@@ -139,6 +143,7 @@ namespace Swift {
 			JID boundJID_;
 			SystemTrayController* systemTrayController_;
 			SoundEventController* soundEventController_;
+			XMPPURIController* xmppURIController_;
 			std::string vCardPhotoHash_;
 			std::string password_;
 			std::string certificateFile_;
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 61da9fb..c523419 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -54,6 +54,7 @@ if env["SCONS_STAGE"] == "build" :
 			"CertificateFileStorage.cpp",
 			"StatusUtil.cpp",
 			"Translator.cpp",
+			"XMPPURIController.cpp",	
 		])
 
 	env.Append(UNITTEST_SOURCES = [
diff --git a/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h b/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h
index dd2ff6c..2c7b105 100644
--- a/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h
+++ b/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h
@@ -6,18 +6,25 @@
 
 #pragma once
 
-#include <boost/optional.hpp>
 #include <boost/shared_ptr.hpp>
-
 #include <string>
+
 #include <Swift/Controllers/UIEvents/UIEvent.h>
+#include <Swiften/JID/JID.h>
 
 namespace Swift {
 	class RequestJoinMUCUIEvent : public UIEvent {
 		public:
 			typedef boost::shared_ptr<RequestJoinMUCUIEvent> ref;
 
-			RequestJoinMUCUIEvent() {
+			RequestJoinMUCUIEvent(const JID& room = JID()) : room(room) {
 			}
+
+			const JID& getRoom() const {
+				return room;
+			}
+
+		private:
+			JID room;
 	};
 }
diff --git a/Swift/Controllers/XMPPURIController.cpp b/Swift/Controllers/XMPPURIController.cpp
new file mode 100644
index 0000000..00759b8
--- /dev/null
+++ b/Swift/Controllers/XMPPURIController.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/XMPPURIController.h>
+
+#include <boost/bind.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <SwifTools/URIHandler/URIHandler.h>
+#include <SwifTools/URIHandler/XMPPURI.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+
+using namespace Swift;
+
+XMPPURIController::XMPPURIController(URIHandler* uriHandler, UIEventStream* uiEventStream) : uriHandler(uriHandler), uiEventStream(uiEventStream) {
+	uriHandler->onURI.connect(boost::bind(&XMPPURIController::handleURI, this, _1));
+}
+
+XMPPURIController::~XMPPURIController() {
+	uriHandler->onURI.disconnect(boost::bind(&XMPPURIController::handleURI, this, _1));
+}
+
+void XMPPURIController::handleURI(const std::string& s) {
+	XMPPURI uri = XMPPURI::fromString(s);
+	if (!uri.isNull()) {
+		if (uri.getQueryType() == "join") {
+			uiEventStream->send(boost::make_shared<RequestJoinMUCUIEvent>(uri.getPath()));
+		}
+		else {
+			uiEventStream->send(boost::make_shared<RequestChatUIEvent>(uri.getPath()));
+		}
+	}
+}
diff --git a/Swift/Controllers/XMPPURIController.h b/Swift/Controllers/XMPPURIController.h
new file mode 100644
index 0000000..54534d4
--- /dev/null
+++ b/Swift/Controllers/XMPPURIController.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <Swiften/Base/boost_bsignals.h>
+
+namespace Swift {
+	class URIHandler;
+	class JID;
+	class UIEventStream;
+
+	class XMPPURIController {
+		public:
+			XMPPURIController(URIHandler* uriHandler, UIEventStream* uiEventStream);
+			~XMPPURIController();
+
+			boost::signal<void (const JID&)> onStartChat;
+			boost::signal<void (const JID&)> onJoinMUC;
+
+		private:
+			void handleURI(const std::string&);
+
+		private:
+			URIHandler* uriHandler;
+			UIEventStream* uiEventStream;
+	};
+}
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index d4c306f..d977637 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -32,20 +32,28 @@
 #include "Swift/Controllers/BuildVersion.h"
 #include "SwifTools/AutoUpdater/AutoUpdater.h"
 #include "SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h"
+
 #if defined(SWIFTEN_PLATFORM_WINDOWS)
 #include "WindowsNotifier.h"
-#endif
-#if defined(HAVE_GROWL)
+#elif defined(HAVE_GROWL)
 #include "SwifTools/Notifier/GrowlNotifier.h"
 #elif defined(SWIFTEN_PLATFORM_LINUX)
 #include "FreeDesktopNotifier.h"
 #else
 #include "SwifTools/Notifier/NullNotifier.h"
 #endif
+
 #if defined(SWIFTEN_PLATFORM_MACOSX)
 #include "SwifTools/Dock/MacOSXDock.h"
-#endif
+#else
 #include "SwifTools/Dock/NullDock.h"
+#endif
+
+#if defined(SWIFTEN_PLATFORM_MACOSX)
+#include "QtURIHandler.h"
+#else
+#include <SwifTools/URIHandler/NullURIHandler.h>
+#endif
 
 namespace Swift{
 
@@ -123,6 +131,12 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
 	dock_ = new NullDock();
 #endif
 
+#if defined(SWIFTEN_PLATFORM_MACOSX)
+	uriHandler_ = new QtURIHandler();
+#else
+	uriHandler_ = new NullURIHandler();
+#endif
+
 	if (splitter_) {
 		splitter_->show();
 	}
@@ -145,6 +159,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
 				certificateStorageFactory_,
 				dock_,
 				notifier_,
+				uriHandler_,
 				options.count("latency-debug") > 0);
 		mainControllers_.push_back(mainController);
 	}
@@ -172,6 +187,7 @@ QtSwift::~QtSwift() {
 	}
 	delete tabs_;
 	delete splitter_;
+	delete uriHandler_;
 	delete dock_;
 	delete soundPlayer_;
 	delete chatWindowFactory_;
diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h
index 978fa14..4bf5c97 100644
--- a/Swift/QtUI/QtSwift.h
+++ b/Swift/QtUI/QtSwift.h
@@ -44,6 +44,7 @@ namespace Swift {
 	class QtMUCSearchWindowFactory;
 	class QtUserSearchWindowFactory;
 	class EventLoop;
+	class URIHandler;
 		
 	class QtSwift : public QObject {
 		Q_OBJECT
@@ -63,6 +64,7 @@ namespace Swift {
 			QSplitter* splitter_;
 			QtSoundPlayer* soundPlayer_;
 			Dock* dock_;
+			URIHandler* uriHandler_;
 			QtChatTabs* tabs_;
 			ApplicationPathProvider* applicationPathProvider_;
 			StoragesFactory* storagesFactory_;
diff --git a/Swift/QtUI/QtURIHandler.cpp b/Swift/QtUI/QtURIHandler.cpp
new file mode 100644
index 0000000..43f3ed1
--- /dev/null
+++ b/Swift/QtUI/QtURIHandler.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "QtURIHandler.h"
+
+#include <QCoreApplication>
+#include <QFileOpenEvent>
+#include <QUrl>
+
+#include "QtSwiftUtil.h"
+#ifdef Q_WS_MAC
+#include <SwifTools/URIHandler/MacOSXURIHandlerHelpers.h>
+#endif
+
+using namespace Swift;
+
+QtURIHandler::QtURIHandler() {
+	qApp->installEventFilter(this);
+#ifdef Q_WS_MAC
+	registerAppAsDefaultXMPPURIHandler();
+#endif
+}
+
+bool QtURIHandler::eventFilter(QObject*, QEvent* event) {
+	if (event->type() == QEvent::FileOpen) {
+		QFileOpenEvent* fileOpenEvent = static_cast<QFileOpenEvent*>(event);
+		if (fileOpenEvent->url().scheme() == "xmpp") {
+			onURI(Q2PSTRING(fileOpenEvent->url().toString()));
+			return true;
+		}
+	}
+	return false;
+}
diff --git a/Swift/QtUI/QtURIHandler.h b/Swift/QtUI/QtURIHandler.h
new file mode 100644
index 0000000..a4b56f8
--- /dev/null
+++ b/Swift/QtUI/QtURIHandler.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <SwifTools/URIHandler/URIHandler.h>
+
+class QUrl;
+
+namespace Swift {
+	class QtURIHandler : public QObject, public URIHandler {
+		public:
+			QtURIHandler();
+
+			virtual void start() {
+			}
+
+			virtual void stop() {
+			}
+
+		private:
+			bool eventFilter(QObject* obj, QEvent* event);
+	};
+}
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index d8d9abd..e2775a6 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -77,6 +77,7 @@ sources = [
     "QtStatusWidget.cpp",
 		"QtScaledAvatarCache.cpp",
     "QtSwift.cpp",
+    "QtURIHandler.cpp",
     "QtChatView.cpp",
     "QtChatTheme.cpp",
     "QtChatTabs.cpp",
@@ -215,7 +216,7 @@ if env["PLATFORM"] == "darwin" :
   if env["HAVE_GROWL"] :
     frameworks.append(env["GROWL_FRAMEWORK"])
   commonResources[""] = commonResources.get("", []) + ["../resources/MacOSX/Swift.icns"]
-  app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks)
+  app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True)
   if env["DIST"] :
     myenv.Command(["Swift-${SWIFT_VERSION}.dmg"], [app], ["Swift/Packaging/MacOSX/package.sh " + app.path + " Swift/Packaging/MacOSX/Swift.dmg.gz $TARGET $QTDIR"])
     
-- 
cgit v0.10.2-6-g49f6