diff --git a/js/extensionPrefs/main.js b/js/extensionPrefs/main.js
index 457ab6c1d..428c32da3 100644
--- a/js/extensionPrefs/main.js
+++ b/js/extensionPrefs/main.js
@@ -13,6 +13,7 @@ const _ = Gettext.gettext;
 
 const Config = imports.misc.config;
 const ExtensionUtils = imports.misc.extensionUtils;
+const Settings = imports.misc.settings;
 
 const GnomeShellIface = '<node> \
 <interface name="org.gnome.Shell.Extensions"> \
@@ -275,15 +276,16 @@ const ExtensionRow = new Lang.Class({
         this.uuid = uuid;
 
         this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' });
-        this._settings.connect('changed::enabled-extensions', Lang.bind(this,
+        this._settingsListener = new Settings.SettingsListener(this._settings);
+        this._settingsListener.connect('changed::enabled-extensions', Lang.bind(this,
             function() {
                 this._switch.state = this._isEnabled();
             }));
-        this._settings.connect('changed::disable-extension-version-validation',
+        this._settingsListener.connect('changed::disable-extension-version-validation',
             Lang.bind(this, function() {
                 this._switch.sensitive = this._canEnable();
             }));
-        this._settings.connect('changed::disable-user-extensions',
+        this._settingsListener.connect('changed::disable-user-extensions',
             Lang.bind(this, function() {
                 this._switch.sensitive = this._canEnable();
             }));
diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js
index 0b6c0b50a..a0e9d6ab1 100644
--- a/js/gdm/loginDialog.js
+++ b/js/gdm/loginDialog.js
@@ -45,6 +45,8 @@ const Realmd = imports.gdm.realmd;
 const Tweener = imports.ui.tweener;
 const UserWidget = imports.ui.userWidget;
 
+const Settings = imports.misc.settings;
+
 const _FADE_ANIMATION_TIME = 0.25;
 const _SCROLL_ANIMATION_TIME = 0.5;
 const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;
@@ -396,15 +398,16 @@ const LoginDialog = new Lang.Class({
         this._gdmClient = new Gdm.Client();
 
         this._settings = new Gio.Settings({ schema_id: GdmUtil.LOGIN_SCREEN_SCHEMA });
-
-        this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY,
-                               Lang.bind(this, this._updateBanner));
-        this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY,
-                               Lang.bind(this, this._updateBanner));
-        this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY,
-                               Lang.bind(this, this._updateDisableUserList));
-        this._settings.connect('changed::' + GdmUtil.LOGO_KEY,
-                               Lang.bind(this, this._updateLogo));
+        this._settingsListener = new Settings.SettingsListener(this._settings);
+
+        this._settingsListener.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY,
+                                Lang.bind(this, this._updateBanner));
+        this._settingsListener.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY,
+                                Lang.bind(this, this._updateBanner));
+        this._settingsListener.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY,
+                                Lang.bind(this, this._updateDisableUserList));
+        this._settingsListener.connect('changed::' + GdmUtil.LOGO_KEY,
+                                Lang.bind(this, this._updateLogo));
 
         this._textureCache = St.TextureCache.get_default();
         this._updateLogoTextureId = this._textureCache.connect('texture-file-changed',
diff --git a/js/gdm/util.js b/js/gdm/util.js
index decfa14a9..cad826d26 100644
--- a/js/gdm/util.js
+++ b/js/gdm/util.js
@@ -17,6 +17,8 @@ const ShellEntry = imports.ui.shellEntry;
 const SmartcardManager = imports.misc.smartcardManager;
 const Tweener = imports.ui.tweener;
 
+const Settings = imports.misc.settings;
+
 const PASSWORD_SERVICE_NAME = 'gdm-password';
 const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
 const SMARTCARD_SERVICE_NAME = 'gdm-smartcard';
@@ -132,7 +134,8 @@ const ShellUserVerifier = new Lang.Class({
         this._preemptingService = null;
 
         this._settings = new Gio.Settings({ schema_id: LOGIN_SCREEN_SCHEMA });
-        this._settings.connect('changed',
+        this._settingsListener = new Settings.SettingsListener(this._settings)
+        this._settingsListener.connect('changed',
                                Lang.bind(this, this._updateDefaultService));
         this._updateDefaultService();
 
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index a1a419686..db55e939d 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -27,6 +27,7 @@
     <file>misc/smartcardManager.js</file>
     <file>misc/util.js</file>
     <file>misc/weather.js</file>
+    <file>misc/settings.js</file>
 
     <file>perf/core.js</file>
     <file>perf/hwtest.js</file>
diff --git a/js/misc/history.js b/js/misc/history.js
index 44d21b7d6..84161ceda 100644
--- a/js/misc/history.js
+++ b/js/misc/history.js
@@ -4,6 +4,7 @@ const Lang = imports.lang;
 const Signals = imports.signals;
 const Clutter = imports.gi.Clutter;
 const Params = imports.misc.params;
+const Settings = imports.misc.settings;
 
 const DEFAULT_LIMIT = 512;
 
@@ -21,7 +22,8 @@ const HistoryManager = new Lang.Class({
         this._historyIndex = 0;
         if (this._key) {
             this._history = global.settings.get_strv(this._key);
-            global.settings.connect('changed::' + this._key,
+            this._settingsListener = new Settings.SettingsListener(this._settings);
+            this._settingsListener.connect('changed::' + this._key,
                                     Lang.bind(this, this._historyChanged));
 
         } else {
diff --git a/js/misc/settings.js b/js/misc/settings.js
new file mode 100644
index 000000000..c3c979afe
--- /dev/null
+++ b/js/misc/settings.js
@@ -0,0 +1,32 @@
+const Lang = imports.lang;
+const Signals = imports.signals;
+
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const SettingsListener = new Lang.Class({
+    Name: 'SettingsListener',
+    _init: function(settings) {
+        const self = this;
+        self._settings = settings;
+        self._values = {};
+        settings.list_keys().map(function(key) {
+
+            // Listen for changes in the value
+            settings.connect('changed::' + key, function() {
+                const newValue = settings.get_value(key);
+
+                if (!newValue.equal(self._values[key])) {
+                    global.log('UPDATING AND EMITTING CHANGE');
+                    global.log(key);
+                    self._values[key] = newValue;
+                    self.emit('changed');
+                    self.emit('changed::' + key);
+                }
+            });
+
+            // Fetch the value initially (also necessary for the listener to work)
+            self._values[key] = settings.get_value(key);
+        });
+    },
+});
+Signals.addSignalMethods(SettingsListener.prototype);
diff --git a/js/misc/util.js b/js/misc/util.js
index a902aac5e..09002cf3b 100644
--- a/js/misc/util.js
+++ b/js/misc/util.js
@@ -13,6 +13,7 @@ const St = imports.gi.St;
 const Main = imports.ui.main;
 const Tweener = imports.ui.tweener;
 const Params = imports.misc.params;
+const Settings = imports.misc.settings;
 
 const SCROLL_TIME = 0.1;
 
@@ -44,6 +45,7 @@ const _urlRegexp = new RegExp(
     ')', 'gi');
 
 let _desktopSettings = null;
+let _desktopSettingsListener = null;
 
 // findUrls:
 // @str: string to find URLs in
@@ -291,8 +293,11 @@ function createTimeLabel(date, params) {
     if (_desktopSettings == null)
         _desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
 
+    if (_desktopSettingsListener == null)
+        _desktopSettingsListener = new Settings.SettingsListener(_desktopSettings);
+
     let label = new St.Label({ text: formatTime(date, params) });
-    let id = _desktopSettings.connect('changed::clock-format', function() {
+    let id = _desktopSettingsListener.connect('changed::clock-format', function() {
         label.text = formatTime(date, params);
     });
     label.connect('destroy', function() {
@@ -479,7 +484,9 @@ const AppSettingsMonitor = new Lang.Class({
         if (!this._settings || handler.id > 0)
             return;
 
-        handler.id = this._settings.connect('changed::' + handler.key,
+        const handlerSettingsListener = new Settings.SettingsListener(this._settings);
+
+        handler.id = handlerSettingsListener.connect('changed::' + handler.key,
                                             handler.callback);
         handler.callback(this._settings, handler.key);
     },
diff --git a/js/misc/weather.js b/js/misc/weather.js
index 02c07889f..3bcd2127f 100644
--- a/js/misc/weather.js
+++ b/js/misc/weather.js
@@ -9,6 +9,7 @@ const Signals = imports.signals;
 
 const PermissionStore = imports.misc.permissionStore;
 const Util = imports.misc.util;
+const Settings = imports.misc.settings;
 
 // Minimum time between updates to show loading indication
 const UPDATE_THRESHOLD = 10 * GLib.TIME_SPAN_MINUTE;
@@ -49,7 +50,8 @@ const WeatherClient = new Lang.Class({
                                       Lang.bind(this, this._onPermStoreChanged));
 
         this._locationSettings = new Gio.Settings({ schema_id: 'org.gnome.system.location' });
-        this._locationSettings.connect('changed::enabled',
+        this._locationSettingsListener = new Settings.SettingsListener(this._locationSettings);
+        this._locationSettingsListener.connect('changed::enabled',
                                        Lang.bind(this, this._updateAutoLocation));
 
         this._world = GWeather.Location.get_world();
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 3bad448fe..0b069c7a8 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -26,6 +26,7 @@ const Tweener = imports.ui.tweener;
 const Workspace = imports.ui.workspace;
 const Params = imports.misc.params;
 const Util = imports.misc.util;
+const Settings = imports.misc.settings;
 
 const MAX_APPLICATION_WORK_MILLIS = 75;
 const MENU_POPUP_TIMEOUT = 600;
@@ -474,7 +475,8 @@ const AllView = new Lang.Class({
             Main.queueDeferredWork(this._redisplayWorkId);
         }));
         this._folderSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
-        this._folderSettings.connect('changed::folder-children', Lang.bind(this, function() {
+        this._folderSettingsListener = new Settings.SettingsListener(this._folderSettings);
+        this._folderSettingsListener.connect('changed::folder-children', Lang.bind(this, function() {
             Main.queueDeferredWork(this._redisplayWorkId);
         }));
     },
@@ -917,7 +919,8 @@ const AppDisplay = new Lang.Class({
 
     _init: function() {
         this._privacySettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.privacy' });
-        this._privacySettings.connect('changed::remember-app-usage',
+        this._privacySettingsListener = new Settings.SettingsListener(this._privacySettings);
+        this._privacySettingsListener.connect('changed::remember-app-usage',
                                       Lang.bind(this, this._updateFrequentVisibility));
 
         this._views = [];
@@ -1286,7 +1289,8 @@ const FolderIcon = new Lang.Class({
                     this._popup.popdown();
             }));
 
-        this._folder.connect('changed', Lang.bind(this, this._redisplay));
+        this._folderListener = new Settings.SettingsListener(this._folder);
+        this._folderListener.connect('changed', Lang.bind(this, this._redisplay));
         this._redisplay();
     },
 
diff --git a/js/ui/appFavorites.js b/js/ui/appFavorites.js
index 12c83b0e3..cb5db5cc6 100644
--- a/js/ui/appFavorites.js
+++ b/js/ui/appFavorites.js
@@ -6,6 +6,8 @@ const Signals = imports.signals;
 
 const Main = imports.ui.main;
 
+const Settings = imports.misc.settings;
+
 const RENAMED_DESKTOP_IDS = {
     'baobab.desktop': 'org.gnome.baobab.desktop',
     'cheese.desktop': 'org.gnome.Cheese.desktop',
@@ -48,7 +50,7 @@ const AppFavorites = new Lang.Class({
 
     _init: function() {
         this._favorites = {};
-        global.settings.connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged));
+        new Settings.SettingsListener(global.settings).connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged));
         this.reload();
     },
 
diff --git a/js/ui/background.js b/js/ui/background.js
index 5f4ce0d3e..7a79c1043 100644
--- a/js/ui/background.js
+++ b/js/ui/background.js
@@ -107,6 +107,8 @@ const Main = imports.ui.main;
 const Params = imports.misc.params;
 const Tweener = imports.ui.tweener;
 
+const Settings = imports.misc.settings;
+
 const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
 
 const BACKGROUND_SCHEMA = 'org.gnome.desktop.background';
@@ -263,7 +265,9 @@ const Background = new Lang.Class({
                 this._refreshAnimation();
             });
 
-        this._settingsChangedSignalId = this._settings.connect('changed', Lang.bind(this, function() {
+        this._settingsListener = new Settings.SettingsListener(this._settings);
+
+        this._settingsChangedSignalId = this._settingsListener.connect('changed', Lang.bind(this, function() {
                                             this.emit('changed');
                                         }));
 
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
index f27c894dc..5c50d4d20 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -15,6 +15,7 @@ const MessageList = imports.ui.messageList;
 const MessageTray = imports.ui.messageTray;
 const Mpris = imports.ui.mpris;
 const Util = imports.misc.util;
+const Settings = imports.misc.settings;
 
 const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
 const SHOW_WEEKDATE_KEY = 'show-weekdate';
@@ -373,7 +374,7 @@ const Calendar = new Lang.Class({
         this._weekStart = Shell.util_get_week_start();
         this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.calendar' });
 
-        this._settings.connect('changed::' + SHOW_WEEKDATE_KEY, Lang.bind(this, this._onSettingsChange));
+        new Settings.SettingsListener(this._settings).connect('changed::' + SHOW_WEEKDATE_KEY, Lang.bind(this, this._onSettingsChange));
         this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY);
 
         // Find the ordering for month/year in the calendar heading
@@ -816,7 +817,11 @@ const EventsSection = new Lang.Class({
 
     _init: function() {
         this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
-        this._desktopSettings.connect('changed', Lang.bind(this, this._reloadEvents));
+        new Settings.SettingsListener(this._desktopSettings).connect('changed',
+            Lang.bind(this, this._reloadEvents)
+        );
+
+
         this._eventSource = new EmptyEventSource();
 
         this.parent();
@@ -868,6 +873,7 @@ const EventsSection = new Lang.Class({
     },
 
     _reloadEvents: function() {
+        global.log('RELOADING EVENTS');
         if (this._eventSource.isLoading)
             return;
 
diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js
index fc352b895..4501b5977 100644
--- a/js/ui/extensionSystem.js
+++ b/js/ui/extensionSystem.js
@@ -10,6 +10,8 @@ const St = imports.gi.St;
 const ExtensionUtils = imports.misc.extensionUtils;
 const Main = imports.ui.main;
 
+const Settings = imports.misc.settings;
+
 const ExtensionState = {
     ENABLED: 1,
     DISABLED: 2,
@@ -301,9 +303,10 @@ function _onVersionValidationChanged() {
 }
 
 function _loadExtensions() {
-    global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged);
-    global.settings.connect('changed::' + DISABLE_USER_EXTENSIONS_KEY, onEnabledExtensionsChanged);
-    global.settings.connect('changed::' + EXTENSION_DISABLE_VERSION_CHECK_KEY, _onVersionValidationChanged);
+    const settingsListener = new Settings.SettingsListener(global.settings);
+    settingsListener.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged);
+    settingsListener.connect('changed::' + DISABLE_USER_EXTENSIONS_KEY, onEnabledExtensionsChanged);
+    settingsListener.connect('changed::' + EXTENSION_DISABLE_VERSION_CHECK_KEY, _onVersionValidationChanged);
 
     enabledExtensions = getEnabledExtensions();
 
diff --git a/js/ui/keyboard.js b/js/ui/keyboard.js
index 6c43888db..3a61ca6e0 100644
--- a/js/ui/keyboard.js
+++ b/js/ui/keyboard.js
@@ -17,6 +17,8 @@ const Layout = imports.ui.layout;
 const Main = imports.ui.main;
 const MessageTray = imports.ui.messageTray;
 
+const Settings = imports.misc.settings;
+
 const KEYBOARD_REST_TIME = Layout.KEYBOARD_ANIMATION_TIME * 2 * 1000;
 
 const KEYBOARD_SCHEMA = 'org.gnome.shell.keyboard';
@@ -202,9 +204,9 @@ const Keyboard = new Lang.Class({
         this._timestamp = global.display.get_current_time_roundtrip();
 
         this._keyboardSettings = new Gio.Settings({ schema_id: KEYBOARD_SCHEMA });
-        this._keyboardSettings.connect('changed', Lang.bind(this, this._sync));
+        new Settings.SettingsListener(this._keyboardSettings).connect('changed', Lang.bind(this, this._sync));
         this._a11yApplicationsSettings = new Gio.Settings({ schema_id: A11Y_APPLICATIONS_SCHEMA });
-        this._a11yApplicationsSettings.connect('changed', Lang.bind(this, this._sync));
+        new Settings.SettingsListener(this._a11yApplicationsSettings).connect('changed', Lang.bind(this, this._sync));
         this._daemonProxy = null;
         this._lastDeviceId = null;
 
diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js
index 89fef3c30..2966959df 100644
--- a/js/ui/lookingGlass.js
+++ b/js/ui/lookingGlass.js
@@ -22,6 +22,8 @@ const Tweener = imports.ui.tweener;
 const Main = imports.ui.main;
 const JsParse = imports.misc.jsParse;
 
+const Settings = imports.misc.settings;
+
 const CHEVRON = '>>> ';
 
 /* Imports...feel free to add here as needed */
@@ -798,7 +800,7 @@ const LookingGlass = new Lang.Class({
         this.actor.connect('key-press-event', Lang.bind(this, this._globalKeyPressEvent));
 
         this._interfaceSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
-        this._interfaceSettings.connect('changed::monospace-font-name',
+        new Settings.SettingsListener(this._interfaceSettings).connect('changed::monospace-font-name',
                                         Lang.bind(this, this._updateFont));
         this._updateFont();
 
diff --git a/js/ui/magnifier.js b/js/ui/magnifier.js
index 6438c64c3..1ebacbd1e 100644
--- a/js/ui/magnifier.js
+++ b/js/ui/magnifier.js
@@ -19,6 +19,8 @@ const MagnifierDBus = imports.ui.magnifierDBus;
 const Params = imports.misc.params;
 const PointerWatcher = imports.ui.pointerWatcher;
 
+const Settings = imports.misc.settings;
+
 const MOUSE_POLL_FREQUENCY = 50;
 const CROSSHAIRS_CLIP_SIZE = [100, 100];
 const NO_CHANGE = 0.0;
@@ -447,71 +449,73 @@ const Magnifier = new Lang.Class({
         this._appSettings = new Gio.Settings({ schema_id: APPLICATIONS_SCHEMA });
         this._settings = new Gio.Settings({ schema_id: MAGNIFIER_SCHEMA });
 
-        this._appSettings.connect('changed::' + SHOW_KEY,
+        new Settings.SettingsListener(this._appSettings).connect('changed::' + SHOW_KEY,
                                   Lang.bind(this, function() {
             this.setActive(this._appSettings.get_boolean(SHOW_KEY));
         }));
 
-        this._settings.connect('changed::' + SCREEN_POSITION_KEY,
+        this._settingsListener = new Settings.SettingsListener(this._settings);
+
+        this._settingsListener.connect('changed::' + SCREEN_POSITION_KEY,
                                Lang.bind(this, this._updateScreenPosition));
-        this._settings.connect('changed::' + MAG_FACTOR_KEY,
+        this._settingsListener.connect('changed::' + MAG_FACTOR_KEY,
                                Lang.bind(this, this._updateMagFactor));
-        this._settings.connect('changed::' + LENS_MODE_KEY,
+        this._settingsListener.connect('changed::' + LENS_MODE_KEY,
                                Lang.bind(this, this._updateLensMode));
-        this._settings.connect('changed::' + CLAMP_MODE_KEY,
+        this._settingsListener.connect('changed::' + CLAMP_MODE_KEY,
                                Lang.bind(this, this._updateClampMode));
-        this._settings.connect('changed::' + MOUSE_TRACKING_KEY,
+        this._settingsListener.connect('changed::' + MOUSE_TRACKING_KEY,
                                Lang.bind(this, this._updateMouseTrackingMode));
-        this._settings.connect('changed::' + FOCUS_TRACKING_KEY,
+        this._settingsListener.connect('changed::' + FOCUS_TRACKING_KEY,
                                Lang.bind(this, this._updateFocusTrackingMode));
-        this._settings.connect('changed::' + CARET_TRACKING_KEY,
+        this._settingsListener.connect('changed::' + CARET_TRACKING_KEY,
                                Lang.bind(this, this._updateCaretTrackingMode));
 
-        this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY,
+        this._settingsListener.connect('changed::' + INVERT_LIGHTNESS_KEY,
                                Lang.bind(this, this._updateInvertLightness));
-        this._settings.connect('changed::' + COLOR_SATURATION_KEY,
+        this._settingsListener.connect('changed::' + COLOR_SATURATION_KEY,
                                Lang.bind(this, this._updateColorSaturation));
 
-        this._settings.connect('changed::' + BRIGHT_RED_KEY,
+        this._settingsListener.connect('changed::' + BRIGHT_RED_KEY,
                                Lang.bind(this, this._updateBrightness));
-        this._settings.connect('changed::' + BRIGHT_GREEN_KEY,
+        this._settingsListener.connect('changed::' + BRIGHT_GREEN_KEY,
                                Lang.bind(this, this._updateBrightness));
-        this._settings.connect('changed::' + BRIGHT_BLUE_KEY,
+        this._settingsListener.connect('changed::' + BRIGHT_BLUE_KEY,
                                Lang.bind(this, this._updateBrightness));
 
-        this._settings.connect('changed::' + CONTRAST_RED_KEY,
+        this._settingsListener.connect('changed::' + CONTRAST_RED_KEY,
                                Lang.bind(this, this._updateContrast));
-        this._settings.connect('changed::' + CONTRAST_GREEN_KEY,
+        this._settingsListener.connect('changed::' + CONTRAST_GREEN_KEY,
                                Lang.bind(this, this._updateContrast));
-        this._settings.connect('changed::' + CONTRAST_BLUE_KEY,
+        this._settingsListener.connect('changed::' + CONTRAST_BLUE_KEY,
                                Lang.bind(this, this._updateContrast));
 
-        this._settings.connect('changed::' + SHOW_CROSS_HAIRS_KEY,
+        this._settingsListener.connect('changed::' + SHOW_CROSS_HAIRS_KEY,
                                Lang.bind(this, function() {
             this.setCrosshairsVisible(this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY));
         }));
 
-        this._settings.connect('changed::' + CROSS_HAIRS_THICKNESS_KEY,
+        this._settingsListener.connect('changed::' + CROSS_HAIRS_THICKNESS_KEY,
                                Lang.bind(this, function() {
             this.setCrosshairsThickness(this._settings.get_int(CROSS_HAIRS_THICKNESS_KEY));
         }));
 
-        this._settings.connect('changed::' + CROSS_HAIRS_COLOR_KEY,
+        this._settingsListener.connect('changed::' + CROSS_HAIRS_COLOR_KEY,
                                Lang.bind(this, function() {
             this.setCrosshairsColor(this._settings.get_string(CROSS_HAIRS_COLOR_KEY));
         }));
 
-        this._settings.connect('changed::' + CROSS_HAIRS_OPACITY_KEY,
+        this._settingsListener.connect('changed::' + CROSS_HAIRS_OPACITY_KEY,
                                Lang.bind(this, function() {
             this.setCrosshairsOpacity(this._settings.get_double(CROSS_HAIRS_OPACITY_KEY));
         }));
 
-        this._settings.connect('changed::' + CROSS_HAIRS_LENGTH_KEY,
+        this._settingsListener.connect('changed::' + CROSS_HAIRS_LENGTH_KEY,
                                Lang.bind(this, function() {
             this.setCrosshairsLength(this._settings.get_int(CROSS_HAIRS_LENGTH_KEY));
         }));
 
-        this._settings.connect('changed::' + CROSS_HAIRS_CLIP_KEY,
+        this._settingsListener.connect('changed::' + CROSS_HAIRS_CLIP_KEY,
                                Lang.bind(this, function() {
             this.setCrosshairsClip(this._settings.get_boolean(CROSS_HAIRS_CLIP_KEY));
         }));
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index 63e6ca182..00007a45d 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -21,6 +21,7 @@ const Main = imports.ui.main;
 const Params = imports.misc.params;
 const Tweener = imports.ui.tweener;
 const Util = imports.misc.util;
+const Settings = imports.misc.settings;
 
 const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
 
@@ -164,7 +165,7 @@ const NotificationGenericPolicy = new Lang.Class({
         this.id = 'generic';
 
         this._masterSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.notifications' });
-        this._masterSettings.connect('changed', Lang.bind(this, this._changed));
+        new Settings.SettingsListener(this._masterSettings).connect('changed', Lang.bind(this, this._changed));
     },
 
     store: function() { },
@@ -217,8 +218,8 @@ const NotificationApplicationPolicy = new Lang.Class({
         this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.notifications.application',
                                             path: '/org/gnome/desktop/notifications/application/' + this._canonicalId + '/' });
 
-        this._masterSettings.connect('changed', Lang.bind(this, this._changed));
-        this._settings.connect('changed', Lang.bind(this, this._changed));
+        new Settings.SettingsListener(this._masterSettings).connect('changed', Lang.bind(this, this._changed));
+        new Settings.SettingsListener(this._settings).connect('changed', Lang.bind(this, this._changed));
     },
 
     store: function() {
diff --git a/js/ui/runDialog.js b/js/ui/runDialog.js
index dedb9d477..b6bbdb084 100644
--- a/js/ui/runDialog.js
+++ b/js/ui/runDialog.js
@@ -16,6 +16,7 @@ const ShellEntry = imports.ui.shellEntry;
 const Tweener = imports.ui.tweener;
 const Util = imports.misc.util;
 const History = imports.misc.history;
+const Settings = imports.misc.settings;
 
 const MAX_FILE_DELETED_BEFORE_INVALID = 10;
 
@@ -40,7 +41,7 @@ const RunDialog = new Lang.Class({
 
         this._lockdownSettings = new Gio.Settings({ schema_id: LOCKDOWN_SCHEMA });
         this._terminalSettings = new Gio.Settings({ schema_id: TERMINAL_SCHEMA });
-        global.settings.connect('changed::development-tools', Lang.bind(this, function () {
+        new Settings.SettingsListener(global.settings).connect('changed::development-tools', Lang.bind(this, function () {
             this._enableInternalCommands = global.settings.get_boolean('development-tools');
         }));
         this._enableInternalCommands = global.settings.get_boolean('development-tools');
diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js
index fd4f28c51..eeaa57bec 100644
--- a/js/ui/screenShield.js
+++ b/js/ui/screenShield.js
@@ -28,6 +28,7 @@ const ShellDBus = imports.ui.shellDBus;
 const SmartcardManager = imports.misc.smartcardManager;
 const Tweener = imports.ui.tweener;
 const Util = imports.misc.util;
+const Settings = imports.misc.settings;
 
 const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
 const LOCK_ENABLED_KEY = 'lock-enabled';
@@ -542,10 +543,10 @@ const ScreenShield = new Lang.Class({
             }));
 
         this._settings = new Gio.Settings({ schema_id: SCREENSAVER_SCHEMA });
-        this._settings.connect('changed::' + LOCK_ENABLED_KEY, Lang.bind(this, this._syncInhibitor));
+        new Settings.SettingsListener(this._settings).connect('changed::' + LOCK_ENABLED_KEY, Lang.bind(this, this._syncInhibitor));
 
         this._lockSettings = new Gio.Settings({ schema_id: LOCKDOWN_SCHEMA });
-        this._lockSettings.connect('changed::' + DISABLE_LOCK_KEY, Lang.bind(this, this._syncInhibitor));
+        new Settings.SettingsListener(this._lockSettings).connect('changed::' + DISABLE_LOCK_KEY, Lang.bind(this, this._syncInhibitor));
 
         this._isModal = false;
         this._hasLockScreen = false;
diff --git a/js/ui/search.js b/js/ui/search.js
index 4d29e59ef..ecb53be1e 100644
--- a/js/ui/search.js
+++ b/js/ui/search.js
@@ -19,6 +19,7 @@ const Overview = imports.ui.overview;
 const RemoteSearch = imports.ui.remoteSearch;
 const Separator = imports.ui.separator;
 const Util = imports.misc.util;
+const Settings = imports.misc.settings;
 
 const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';
 
@@ -419,10 +420,11 @@ const SearchResults = new Lang.Class({
         this._providers = [];
 
         this._searchSettings = new Gio.Settings({ schema_id: SEARCH_PROVIDERS_SCHEMA });
-        this._searchSettings.connect('changed::disabled', Lang.bind(this, this._reloadRemoteProviders));
-        this._searchSettings.connect('changed::enabled', Lang.bind(this, this._reloadRemoteProviders));
-        this._searchSettings.connect('changed::disable-external', Lang.bind(this, this._reloadRemoteProviders));
-        this._searchSettings.connect('changed::sort-order', Lang.bind(this, this._reloadRemoteProviders));
+        this._searchSettingsListener = new Settings.SettingsListener(this._searchSettings);
+        this._searchSettingsListener.connect('changed::disabled', Lang.bind(this, this._reloadRemoteProviders));
+        this._searchSettingsListener.connect('changed::enabled', Lang.bind(this, this._reloadRemoteProviders));
+        this._searchSettingsListener.connect('changed::disable-external', Lang.bind(this, this._reloadRemoteProviders));
+        this._searchSettingsListener.connect('changed::sort-order', Lang.bind(this, this._reloadRemoteProviders));
 
         this._searchTimeoutId = 0;
         this._cancellable = new Gio.Cancellable();
diff --git a/js/ui/status/accessibility.js b/js/ui/status/accessibility.js
index 6aa1064de..adc034a5c 100644
--- a/js/ui/status/accessibility.js
+++ b/js/ui/status/accessibility.js
@@ -10,6 +10,8 @@ const St = imports.gi.St;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
 
+const Settings = imports.misc.settings;
+
 const A11Y_SCHEMA                   = 'org.gnome.desktop.a11y';
 const KEY_ALWAYS_SHOW               = 'always-show-universal-access-status';
 
@@ -49,7 +51,7 @@ const ATIndicator = new Lang.Class({
         this.actor.add_child(this._hbox);
 
         this._a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA });
-        this._a11ySettings.connect('changed::' + KEY_ALWAYS_SHOW, Lang.bind(this, this._queueSyncMenuVisibility));
+        new Settings.SettingsListener(this._a11ySettings).connect('changed::' + KEY_ALWAYS_SHOW, Lang.bind(this, this._queueSyncMenuVisibility));
 
         let highContrast = this._buildHCItem();
         this.menu.addMenuItem(highContrast);
@@ -119,7 +121,7 @@ const ATIndicator = new Lang.Class({
 
     _buildItem: function(string, schema, key) {
         let settings = new Gio.Settings({ schema_id: schema });
-        settings.connect('changed::'+key, Lang.bind(this, function() {
+        new Settings.SettingsListener(settings).connect('changed::'+key, Lang.bind(this, function() {
             widget.setToggleState(settings.get_boolean(key));
 
             this._queueSyncMenuVisibility();
@@ -137,7 +139,9 @@ const ATIndicator = new Lang.Class({
     _buildHCItem: function() {
         let interfaceSettings = new Gio.Settings({ schema_id: DESKTOP_INTERFACE_SCHEMA });
         let wmSettings = new Gio.Settings({ schema_id: WM_SCHEMA });
-        interfaceSettings.connect('changed::' + KEY_GTK_THEME, Lang.bind(this, function() {
+        let interfaceSettingsListener = new Settings.SettingsListener(interfaceSettings);
+        let wmSettingsListener = new Settings.SettingsListener(wmSettings);
+        interfaceSettingsListener.connect('changed::' + KEY_GTK_THEME, Lang.bind(this, function() {
             let value = interfaceSettings.get_string(KEY_GTK_THEME);
             if (value == HIGH_CONTRAST_THEME) {
                 highContrast.setToggleState(true);
@@ -148,12 +152,12 @@ const ATIndicator = new Lang.Class({
 
             this._queueSyncMenuVisibility();
         }));
-        interfaceSettings.connect('changed::' + KEY_ICON_THEME, function() {
+        interfaceSettingsListener.connect('changed::' + KEY_ICON_THEME, function() {
             let value = interfaceSettings.get_string(KEY_ICON_THEME);
             if (value != HIGH_CONTRAST_THEME)
                 iconTheme = value;
         });
-        wmSettings.connect('changed::' + KEY_WM_THEME, function() {
+        wmSettingsListener.connect('changed::' + KEY_WM_THEME, function() {
             let value = wmSettings.get_string(KEY_WM_THEME);
             if (value != HIGH_CONTRAST_THEME)
                 wmTheme = value;
diff --git a/js/ui/status/keyboard.js b/js/ui/status/keyboard.js
index d4b14d538..f71f74446 100644
--- a/js/ui/status/keyboard.js
+++ b/js/ui/status/keyboard.js
@@ -18,6 +18,7 @@ const PopupMenu = imports.ui.popupMenu;
 const PanelMenu = imports.ui.panelMenu;
 const SwitcherPopup = imports.ui.switcherPopup;
 const Util = imports.misc.util;
+const Settings = imports.misc.settings;
 
 const INPUT_SOURCE_TYPE_XKB = 'xkb';
 const INPUT_SOURCE_TYPE_IBUS = 'ibus';
@@ -265,9 +266,10 @@ const InputSourceSessionSettings = new Lang.Class({
 
     _init: function() {
         this._settings = new Gio.Settings({ schema_id: this._DESKTOP_INPUT_SOURCES_SCHEMA });
-        this._settings.connect('changed::' + this._KEY_INPUT_SOURCES, Lang.bind(this, this._emitInputSourcesChanged));
-        this._settings.connect('changed::' + this._KEY_KEYBOARD_OPTIONS, Lang.bind(this, this._emitKeyboardOptionsChanged));
-        this._settings.connect('changed::' + this._KEY_PER_WINDOW, Lang.bind(this, this._emitPerWindowChanged));
+        this._settingsListener = new Settings.SettingsListener(this._settings);
+        this._settingsListener.connect('changed::' + this._KEY_INPUT_SOURCES, Lang.bind(this, this._emitInputSourcesChanged));
+        this._settingsListener.connect('changed::' + this._KEY_KEYBOARD_OPTIONS, Lang.bind(this, this._emitKeyboardOptionsChanged));
+        this._settingsListener.connect('changed::' + this._KEY_PER_WINDOW, Lang.bind(this, this._emitPerWindowChanged));
     },
 
     _getSourcesList: function(key) {
diff --git a/js/ui/status/location.js b/js/ui/status/location.js
index 7988dd4fc..b018a87f0 100644
--- a/js/ui/status/location.js
+++ b/js/ui/status/location.js
@@ -14,6 +14,8 @@ const Shell = imports.gi.Shell;
 const Signals = imports.signals;
 const St = imports.gi.St;
 
+const Settings = imports.misc.settings;
+
 const LOCATION_SCHEMA = 'org.gnome.system.location';
 const MAX_ACCURACY_LEVEL = 'max-accuracy-level';
 const ENABLED = 'enabled';
@@ -71,9 +73,10 @@ const Indicator = new Lang.Class({
         this.parent();
 
         this._settings = new Gio.Settings({ schema_id: LOCATION_SCHEMA });
-        this._settings.connect('changed::' + ENABLED,
+        this._settingsListener = new Settings.SettingsListener(this._settings)
+        this._settingsListener.connect('changed::' + ENABLED,
                                Lang.bind(this, this._onMaxAccuracyLevelChanged));
-        this._settings.connect('changed::' + MAX_ACCURACY_LEVEL,
+        this._settingsListener.connect('changed::' + MAX_ACCURACY_LEVEL,
                                Lang.bind(this, this._onMaxAccuracyLevelChanged));
 
         this._indicator = this._addIndicator();
diff --git a/js/ui/status/power.js b/js/ui/status/power.js
index 30b5e0498..1cbd11fff 100644
--- a/js/ui/status/power.js
+++ b/js/ui/status/power.js
@@ -10,6 +10,8 @@ const Main = imports.ui.main;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
 
+const Settings = imports.misc.settings;
+
 const BUS_NAME = 'org.freedesktop.UPower';
 const OBJECT_PATH = '/org/freedesktop/UPower/devices/DisplayDevice';
 
@@ -37,7 +39,7 @@ const Indicator = new Lang.Class({
         this.parent();
 
         this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
-        this._desktopSettings.connect('changed::' + SHOW_BATTERY_PERCENTAGE,
+        new Settings.SettingsListener(this._desktopSettings).connect('changed::' + SHOW_BATTERY_PERCENTAGE,
                                       Lang.bind(this, this._sync));
 
         this._indicator = this._addIndicator();
diff --git a/js/ui/status/system.js b/js/ui/status/system.js
index 8f99865e6..23faa6307 100644
--- a/js/ui/status/system.js
+++ b/js/ui/status/system.js
@@ -17,6 +17,8 @@ const Main = imports.ui.main;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
 
+const Settings = imports.misc.settings;
+
 const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
 const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
 const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
@@ -140,7 +142,9 @@ const Indicator = new Lang.Class({
 
         this._loginScreenSettings = new Gio.Settings({ schema_id: LOGIN_SCREEN_SCHEMA });
         this._lockdownSettings = new Gio.Settings({ schema_id: LOCKDOWN_SCHEMA });
+        this._lockdownSettingsListener = new Settings.SettingsListener(this._lockdownSettings);
         this._orientationSettings = new Gio.Settings({ schema_id: 'org.gnome.settings-daemon.peripherals.touchscreen' });
+        this._orientationSettingsListener = new Settings.SettingsListener(this._orientationSettings);
 
         this._session = new GnomeSession.SessionManager();
         this._loginManager = LoginManager.getLoginManager();
@@ -161,13 +165,13 @@ const Indicator = new Lang.Class({
                                   Lang.bind(this, this._updateMultiUser));
         this._userManager.connect('user-removed',
                                   Lang.bind(this, this._updateMultiUser));
-        this._lockdownSettings.connect('changed::' + DISABLE_USER_SWITCH_KEY,
+        this._lockdownSettingsListener.connect('changed::' + DISABLE_USER_SWITCH_KEY,
                                        Lang.bind(this, this._updateMultiUser));
-        this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
+        this._lockdownSettingsListener.connect('changed::' + DISABLE_LOG_OUT_KEY,
                                        Lang.bind(this, this._updateMultiUser));
-        this._lockdownSettings.connect('changed::' + DISABLE_LOCK_SCREEN_KEY,
+        this._lockdownSettingsListener.connect('changed::' + DISABLE_LOCK_SCREEN_KEY,
                                        Lang.bind(this, this._updateLockScreen));
-        global.settings.connect('changed::' + ALWAYS_SHOW_LOG_OUT_KEY,
+        new Settings.SettingsListener(global.settings).connect('changed::' + ALWAYS_SHOW_LOG_OUT_KEY,
                                 Lang.bind(this, this._updateMultiUser));
         this._updateSwitchUser();
         this._updateMultiUser();
@@ -185,10 +189,10 @@ const Indicator = new Lang.Class({
                 this._updateHaveShutdown();
                 this._updateHaveSuspend();
             }));
-        this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
+        this._lockdownSettingsListener.connect('changed::' + DISABLE_LOG_OUT_KEY,
                                        Lang.bind(this, this._updateHaveShutdown));
 
-        this._orientationSettings.connect('changed::orientation-lock',
+        this._orientationSettingsListener.connect('changed::orientation-lock',
                                           Lang.bind(this, this._updateOrientationLock));
         Main.layoutManager.connect('monitors-changed',
                                    Lang.bind(this, this._updateOrientationLock));
diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js
index b10930d4e..fa718e143 100644
--- a/js/ui/windowManager.js
+++ b/js/ui/windowManager.js
@@ -20,6 +20,8 @@ const WindowMenu = imports.ui.windowMenu;
 const PadOsd = imports.ui.padOsd;
 const EdgeDragAction = imports.ui.edgeDragAction;
 
+const Settings = imports.misc.settings;
+
 const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
 const MINIMIZE_WINDOW_ANIMATION_TIME = 0.2;
 const SHOW_WINDOW_ANIMATION_TIME = 0.15;
@@ -227,7 +229,7 @@ const WorkspaceTracker = new Lang.Class({
         global.screen.connect('restacked', Lang.bind(this, this._windowsRestacked));
 
         this._workspaceSettings = this._getWorkspaceSettings();
-        this._workspaceSettings.connect('changed::dynamic-workspaces', Lang.bind(this, this._queueCheckWorkspaces));
+        new Settings.SettingsListener(this._workspaceSettings).connect('changed::dynamic-workspaces', Lang.bind(this, this._queueCheckWorkspaces));
 
         this._nWorkspacesChanged();
     },
diff --git a/js/ui/workspaceThumbnail.js b/js/ui/workspaceThumbnail.js
index 980ffd665..95539c010 100644
--- a/js/ui/workspaceThumbnail.js
+++ b/js/ui/workspaceThumbnail.js
@@ -18,6 +18,8 @@ const WindowManager = imports.ui.windowManager;
 const Workspace = imports.ui.workspace;
 const WorkspacesView = imports.ui.workspacesView;
 
+const Settings = imports.misc.settings;
+
 // The maximum size of a thumbnail is 1/8 the width and height of the screen
 let MAX_THUMBNAIL_SCALE = 1/8.;
 
@@ -674,7 +676,7 @@ const ThumbnailsBox = new Lang.Class({
                               Lang.bind(this, this._onDragCancelled));
 
         this._settings = new Gio.Settings({ schema_id: OVERRIDE_SCHEMA });
-        this._settings.connect('changed::dynamic-workspaces',
+        new Settings.SettingsListener(this._settings).connect('changed::dynamic-workspaces',
             Lang.bind(this, this._updateSwitcherVisibility));
     },
 
diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js
index 2eacb5f2d..c1c14e4e3 100644
--- a/js/ui/workspacesView.js
+++ b/js/ui/workspacesView.js
@@ -17,6 +17,8 @@ const Tweener = imports.ui.tweener;
 const Workspace = imports.ui.workspace;
 const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
 
+const Settings = imports.misc.settings;
+
 const WORKSPACE_SWITCH_TIME = 0.25;
 
 const AnimationType = {
@@ -466,7 +468,7 @@ const WorkspacesDisplay = new Lang.Class({
         this._primaryScrollAdjustment = null;
 
         this._settings = new Gio.Settings({ schema_id: OVERRIDE_SCHEMA });
-        this._settings.connect('changed::workspaces-only-on-primary',
+        new Settings.SettingsListener(this._settings).connect('changed::workspaces-only-on-primary',
                                Lang.bind(this,
                                          this._workspacesOnlyOnPrimaryChanged));
         this._workspacesOnlyOnPrimaryChanged();
