diff --git a/app/assets/images/default-favicon.ico b/app/assets/images/default-favicon.ico
new file mode 100644
index 00000000000..d20ae8eeded
Binary files /dev/null and b/app/assets/images/default-favicon.ico differ
diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js
index 954d8ad1b37..33e1627ce39 100644
--- a/app/assets/javascripts/discourse.js
+++ b/app/assets/javascripts/discourse.js
@@ -85,6 +85,14 @@ Discourse = Ember.Application.createWithMixins({
}, 200);
}.observes('title', 'hasFocus', 'notifyCount'),
+ faviconChanged: function() {
+ if(Discourse.SiteSettings.dynamic_favicon) {
+ $.faviconNotify(
+ Discourse.SiteSettings.favicon_url, this.get('notifyCount')
+ );
+ }
+ }.observes('notifyCount'),
+
// The classes of buttons to show on a post
postButtons: function() {
return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
diff --git a/app/assets/javascripts/external/jquery.faviconNotify.js b/app/assets/javascripts/external/jquery.faviconNotify.js
new file mode 100644
index 00000000000..2b071e30e7c
--- /dev/null
+++ b/app/assets/javascripts/external/jquery.faviconNotify.js
@@ -0,0 +1,224 @@
+/**
+ * jQuery Favicon Notify
+ *
+ * Updates the favicon to notify the user of changes. In the original tests I
+ * had an embedded font collection to allow any charachers - I decided that the
+ * ~130Kb and added complexity was overkill. As such it now uses a manual glyph
+ * set meaning that only numerical notifications are possible.
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * @author David King
+ * @copyright Copyright (c) 2011 +
+ * @url oodavid.com
+ */
+(function($){
+ var canvas;
+ var bg = '#000000';
+ var fg = '#FFFFFF';
+ var pos = 'br';
+ $.faviconNotify = function(icon, num, myPos, myBg, myFg){
+ // Default the positions
+ myPos = myPos || pos;
+ myFg = myFg || fg;
+ myBg = myBg || bg;
+ // Create a canvas if we need one
+ canvas = canvas || $('')[0];
+ if(canvas.getContext){
+ // Load the icon
+ $('').load(function(e){
+ // Load the icon into the canvas
+ canvas.height = canvas.width = 16;
+ var ctx = canvas.getContext('2d');
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ ctx.drawImage(this, 0, 0);
+ // We gots num?
+ if(num !== undefined){
+ num = parseFloat(num, 10);
+ // Convert the num into a glyphs array
+ var myGlyphs = [];
+ if(num > 99){
+ myGlyphs.push(glyphs['LOTS']);
+ } else {
+ num = num.toString().split('');
+ $.each(num, function(k,v){
+ myGlyphs.push(glyphs[v]);
+ });
+ }
+ // Merge the glyphs together
+ var combined = [];
+ var glyphHeight = myGlyphs[0].length;
+ $.each(myGlyphs, function(k,v){
+ for(y=0; y').attr('href', canvas.toDataURL('image/png')));
+ }).attr('src', icon)
+ }
+ };
+ var glyphs = {
+ '0': [
+ ' --- ',
+ ' -@@@- ',
+ '-@---@-',
+ '-@- -@-',
+ '-@- -@-',
+ '-@- -@-',
+ '-@---@-',
+ ' -@@@- ',
+ ' --- ' ],
+ '1': [
+ ' - ',
+ ' -@- ',
+ '-@@- ',
+ ' -@- ',
+ ' -@- ',
+ ' -@- ',
+ ' -@- ',
+ '-@@@-',
+ ' --- ' ],
+ '2': [
+ ' --- ',
+ ' -@@@- ',
+ '-@---@-',
+ ' - --@-',
+ ' -@@- ',
+ ' -@-- ',
+ '-@---- ',
+ '-@@@@@-',
+ ' ----- ' ],
+ '3': [
+ ' --- ',
+ ' -@@@- ',
+ '-@---@-',
+ ' - --@-',
+ ' -@@- ',
+ ' - --@-',
+ '-@---@-',
+ ' -@@@- ',
+ ' --- ' ],
+ '4': [
+ ' -- ',
+ ' -@@-',
+ ' -@-@-',
+ ' -@--@-',
+ '-@---@-',
+ '-@@@@@-',
+ ' ----@-',
+ ' -@-',
+ ' - ' ],
+ '5': [
+ ' ----- ',
+ '-@@@@@-',
+ '-@---- ',
+ '-@--- ',
+ '-@@@@- ',
+ ' ----@-',
+ '-@---@-',
+ ' -@@@- ',
+ ' --- ' ],
+ '6': [
+ ' --- ',
+ ' -@@@- ',
+ '-@---@-',
+ '-@---- ',
+ '-@@@@- ',
+ '-@---@-',
+ '-@---@-',
+ ' -@@@- ',
+ ' --- ' ],
+ '7': [
+ ' ----- ',
+ '-@@@@@-',
+ ' ----@-',
+ ' -@- ',
+ ' -@- ',
+ ' -@- ',
+ ' -@- ',
+ ' -@- ',
+ ' - ' ],
+ '8': [
+ ' --- ',
+ ' -@@@- ',
+ '-@---@-',
+ '-@---@-',
+ ' -@@@- ',
+ '-@---@-',
+ '-@---@-',
+ ' -@@@- ',
+ ' --- ' ],
+ '9': [
+ ' --- ',
+ ' -@@@- ',
+ '-@---@-',
+ '-@---@-',
+ ' -@@@@-',
+ ' ----@-',
+ '-@---@-',
+ ' -@@@- ',
+ ' --- ' ],
+ '!': [
+ ' - ',
+ '-@-',
+ '-@-',
+ '-@-',
+ '-@-',
+ '-@-',
+ ' - ',
+ '-@-',
+ ' - ' ],
+ '.': [
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' - ',
+ '-@-',
+ ' - ' ],
+ 'LOTS': [
+ ' - -- --- -- ',
+ '-@- -@@-@@@--@@-',
+ '-@--@--@-@--@- ',
+ '-@--@--@-@--@- ',
+ '-@--@--@-@- -@- ',
+ '-@--@--@-@- -@-',
+ '-@--@--@-@----@-',
+ '-@@@-@@--@-@@@- ',
+ ' --- -- - --- '
+ ]
+ };
+})(jQuery);
diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb
index e5b99a92260..93fee5b34ba 100644
--- a/app/models/site_setting.rb
+++ b/app/models/site_setting.rb
@@ -79,7 +79,8 @@ class SiteSetting < ActiveRecord::Base
setting(:invite_expiry_days, 14)
setting(:active_user_rate_limit_secs, 60)
setting(:previous_visit_timeout_hours, 1)
- setting(:favicon_url, '/assets/default-favicon.png')
+ client_setting(:favicon_url, '/assets/default-favicon.ico')
+ client_setting(:dynamic_favicon, false)
setting(:apple_touch_icon_url, '/assets/default-apple-touch-icon.png')
setting(:ninja_edit_window, 5.minutes.to_i)
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 47f225f6e61..9e689872a53 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -460,6 +460,7 @@ en:
logo_url: "The logo for your site eg: http://example.com/logo.png"
logo_small_url: "The small logo for your site used when scrolling down on topics eg: http://example.com/logo-small.png"
favicon_url: "A favicon for your site, see http://en.wikipedia.org/wiki/Favicon"
+ dynamic_favicon: "Show incoming message notifications on favicon"
apple_touch_icon_url: "Icon used for Apple touch devices. Recommended size is 144px by 144px."
notification_email: "The return email address used when sending system emails such as notifying users of lost passwords, new accounts etc"