diff --git a/scripts/dbus.js b/scripts/dbus.js
--- a/scripts/dbus.js
+++ b/scripts/dbus.js
@@ -19,7 +19,7 @@
        * Listen for a particular DBus event.
        * @param match {Object} DBus match conditions ('path', 'iface', 'member')
        * @param handler {Function} The function to call upon receiving the event
-       * @returns {Callbacks} A jQuery callback object
+       * @returns {Event} A Tianbar event object
        */
       listen: function (match) {
         var evt = tianbar.createEvent();
@@ -32,6 +32,18 @@
         });
 
         return evt.callback;
+      },
+
+      /**
+       * Stop listening for a DBus event.
+       * @param evt {Event} A Tianbar event object returned by listen().
+       */
+      removeMatch: function (evt) {
+        $.ajax('tianbar:///dbus/' + busName + '/stop', {
+          data: {
+            index: evt.index
+          }
+        });
       },
 
       /**
diff --git a/scripts/power.js b/scripts/power.js
--- a/scripts/power.js
+++ b/scripts/power.js
@@ -193,6 +193,23 @@
 
   self.updated = $.Callbacks();
 
+  // A map of device paths to device states
+  self.devices = {};
+
+  // Display the status of all the devices
+  self.display = function () {
+    // TODO: support updating individual devices
+    var widget = self.widget();
+    widget.empty();
+
+    $.each(self.devices, function (_, device) {
+      widget.append(self.formatDevice(device));
+    });
+
+    self.updated.fire();
+  };
+
+  // Refresh the device list and each device status
   self.refresh = function () {
     dbus.system.call({
       'destination': 'org.freedesktop.UPower',
@@ -203,39 +220,40 @@
       ]
     }).done(function (devices) {
       devices = devices.body[0];
-      var queries = $.map(devices, function(device) {
-        return dbus.system.call({
-          'destination': 'org.freedesktop.UPower',
-          'path': device,
-          'iface': 'org.freedesktop.DBus.Properties',
-          'member': 'GetAll',
-          'body': [
-            'string:org.freedesktop.UPower.Device',
-          ]
-        });
+      // TODO: support device add and removal
+      $.each(devices, function(_, device) {
+        dbus.system.listen(
+          { path: device }
+        ).add(function() {
+            self.refreshDevice(device);
+          }
+        );
+        self.refreshDevice(device);
       });
-      $.when.apply($, queries).then(function (results) {
-        results = Array.prototype.slice.call(arguments, 0);
-
-        var widget = self.widget();
-        widget.empty();
-        $.each(results, function (_, st) {
-          st = st.body[0];
-          widget.append(self.formatDevice(st));
-        });
+    });
+  };
 
-        self.updated.fire();
-      });
+  // Refresh an individual device
+  self.refreshDevice = function (device) {
+    dbus.system.call({
+      'destination': 'org.freedesktop.UPower',
+      'path': device,
+      'iface': 'org.freedesktop.DBus.Properties',
+      'member': 'GetAll',
+      'body': [
+        'string:org.freedesktop.UPower.Device',
+      ]
+    }).done(function (properties) {
+      properties = properties.body[0];
+      self.devices[device] = properties;
+      self.display();
     });
   };
 
   $(document).ready(function () {
     dbus.system.listen(
-      {
-        iface: 'org.freedesktop.UPower'
-      },
-      self.refresh
-    );
+      { path: '/org/freedesktop/UPower' }
+    ).add(self.refresh);
     self.refresh();
   });
 
diff --git a/src/System/Tianbar/Plugin/DBus.hs b/src/System/Tianbar/Plugin/DBus.hs
--- a/src/System/Tianbar/Plugin/DBus.hs
+++ b/src/System/Tianbar/Plugin/DBus.hs
@@ -1,11 +1,14 @@
-module System.Tianbar.Plugin.DBus where
+module System.Tianbar.Plugin.DBus (DBusPlugin) where
 
 -- DBus connectivity
 
+import Control.Applicative
+import Control.Concurrent
 import Control.Monad
 import Control.Monad.IO.Class
 
 import Data.Aeson (encode)
+import qualified Data.Map as M
 
 import Happstack.Server
 
@@ -16,43 +19,77 @@
 import System.Tianbar.Plugin.DBus.JSON ()
 import System.Tianbar.Plugin.DBus.FromData ()
 
+data Bus = Bus { busClient :: Client
+               , busSignals :: MVar (M.Map String SignalHandler)
+               }
+
+busNew :: IO Client -> IO Bus
+busNew conn = Bus <$> conn <*> newMVar M.empty
+
+busDestroy :: Bus -> IO ()
+-- TODO: remove signals?
+busDestroy = disconnect . busClient
+
+busAddListener :: (MonadIO m) => Bus -> String -> SignalHandler -> m ()
+busAddListener bus index listener = liftIO $ modifyMVar_ (busSignals bus) $ \listeners ->
+    return $ M.insert index listener listeners
+
+busPopListener :: (MonadIO m) => Bus -> String -> m (Maybe SignalHandler)
+busPopListener bus index = liftIO $ modifyMVar (busSignals bus) $ \listeners -> do
+    let listener = M.lookup index listeners
+    let listeners' = M.delete index listeners
+    return (listeners', listener)
+
 data DBusPlugin = DBusPlugin { dbusHost :: Callbacks
-                             , dbusSession :: Client
-                             , dbusSystem :: Client
+                             , dbusSession :: Bus
+                             , dbusSystem :: Bus
                              }
 
-busNameMap :: [(String, DBusPlugin -> Client)]
+busNameMap :: [(String, DBusPlugin -> Bus)]
 busNameMap = [ ("session", dbusSession)
              , ("system", dbusSystem)
              ]
 
 instance Plugin DBusPlugin where
     initialize c = do
-        session <- connectSession
-        system <- connectSystem
+        session <- busNew connectSession
+        system <- busNew connectSystem
         return $ DBusPlugin c session system
 
-    destroy plugin = mapM_ disconnect [dbusSession plugin, dbusSystem plugin]
+    destroy plugin = mapM_ busDestroy [dbusSession plugin, dbusSystem plugin]
 
     handler plugin = dir "dbus" $ msum [ busHandler plugin busName (bus plugin)
                                        | (busName, bus) <- busNameMap
                                        ]
 
-busHandler :: DBusPlugin -> String -> Client -> ServerPartT IO Response
+busHandler :: DBusPlugin -> String -> Bus -> ServerPartT IO Response
 busHandler plugin busName bus = dir busName $ msum [ mzero
                                                    , listenHandler plugin bus
+                                                   , stopHandler plugin bus
                                                    , callHandler plugin bus
                                                    ]
 
-listenHandler :: DBusPlugin -> Client -> ServerPartT IO Response
-listenHandler plugin client = dir "listen" $ withData $ \matcher -> do
+listenHandler :: DBusPlugin -> Bus -> ServerPartT IO Response
+listenHandler plugin bus = dir "listen" $ withData $ \matcher -> do
     nullDir
     index <- look "index"
-    _ <- liftIO $ addMatch client matcher $ \sig -> callback (dbusHost plugin) index [sig]
+    listener <- liftIO $ addMatch (busClient bus) matcher $ \sig -> callback (dbusHost plugin) index [sig]
+    busAddListener bus index listener
     return $ toResponse ("ok" :: String)
 
-callHandler :: DBusPlugin -> Client -> ServerPartT IO Response
-callHandler _ client = dir "call" $ withData $ \mcall -> do
+stopHandler :: DBusPlugin -> Bus -> ServerPartT IO Response
+stopHandler _ bus = dir "stop" $ do
     nullDir
-    res <- liftIO $ call client mcall
+    index <- look "index"
+    listener <- busPopListener bus index
+    case listener of
+        Just l -> do
+            liftIO $ removeMatch (busClient bus) l
+            return $ toResponse ("ok" :: String)
+        Nothing -> mzero
+
+callHandler :: DBusPlugin -> Bus -> ServerPartT IO Response
+callHandler _ bus = dir "call" $ withData $ \mcall -> do
+    nullDir
+    res <- liftIO $ call (busClient bus) mcall
     return $ toResponse $ encode res
diff --git a/tianbar.cabal b/tianbar.cabal
--- a/tianbar.cabal
+++ b/tianbar.cabal
@@ -1,5 +1,5 @@
 name:                tianbar
-version:             0.4.4.0
+version:             0.4.5.0
 synopsis:            A desktop bar based on WebKit
 description:
   A desktop bar using WebKit for rendering as much as possible.
