GnusNetworkManager

Somewhat experimental integration between Gnus agent and NetworkManager over D-Bus. Kill IMAP connections and unplug Gnus agent when network goes down, plug agent when network comes up.

I find this useful when running Emacs+Gnus on my laptop, where network conditions often change, especially on suspend/resume. It was annoying that Gnus often hung as a result of these events, usually caused by hanging network sub-processes (openssl, gnutls-cli).

Requires Emacs23 with D-Bus bindings and NetworkManager running. Probably only useful if you’re running Emacs on a modern GNU/Linux distribution of some sort.

Related part of my ~/.gnus:

(require 'dbus)
(setq gnus-nm-dbus-registration nil)
(defvar gnus-nm-connected-hook nil
  "Functions to run when network is connected.")
(defvar gnus-nm-disconnected-hook nil
  "Functions to run when network is disconnected.")

(defun imap-nuke-server-processes()
 "Brutally kill running IMAP server background processes. Useful
when Gnus hangs on network outs or changes."
  (interactive)
  (let ((sm (if gnus-select-method
                (cons gnus-select-method gnus-secondary-select-methods)
              gnus-secondary-select-methods)))
    (while sm
      (let ((method (car (car sm)))
            (vserv (nth 1 (car sm))))
        (when (and (eq 'nnimap method)
                   (buffer-local-value 'imap-process (get-buffer (nnimap-get-server-buffer vserv))))
          (gnus-message 6 "Killing IMAP process for server %s" vserv)
          (delete-process (buffer-local-value 'imap-process (get-buffer (nnimap-get-server-buffer vserv))))))
      (setq sm (cdr sm)))))

(defun gnus-nm-agent-unplug()
  "Kill IMAP server processes and unplug Gnus agent."
  (gnus-message 6 "Network is disconnected, unplugging Gnus agent.")
  (with-current-buffer gnus-group-buffer
    (imap-nuke-server-processes) ; optional, help prevent hangs in IMAP processes when network has gone down.
    (gnus-agent-toggle-plugged nil)))

(defun gnus-nm-agent-plug() 
  "Plug Gnus agent."
  (gnus-message 6 "Network is connected, plugging Gnus agent.")
  (with-current-buffer gnus-group-buffer
    (gnus-agent-toggle-plugged t)))

(defun gnus-nm-state-dbus-signal-handler (nmstate)
  "Handles NetworkManager signals and runs appropriate hooks."
  (when (and (fboundp 'gnus-alive-p) (gnus-alive-p))
    (cond
     ((or (= 4 nmstate) (= 1 nmstate))
      (run-hooks 'gnus-nm-disconnected-hook))
     ((= 3 nmstate)
      (run-hooks 'gnus-nm-connected-hook)))))

(defun gnus-nm-enable()
  "Enable integration with NetworkManager."
  (interactive)
  (when (not gnus-nm-dbus-registration)
    (progn (setq gnus-nm-dbus-registration
                 (dbus-register-signal :system
                  "org.freedesktop.NetworkManager" "/org/freedesktop/NetworkManager"
                  "org.freedesktop.NetworkManager" "StateChanged"
                  'gnus-nm-state-dbus-signal-handler))
           (gnus-message 6 "Enabled integration with NetworkManager"))))

(defun gnus-nm-disable()
  "Disable integration with NetworkManager."
  (interactive)
  (when gnus-nm-dbus-registration
      (progn (dbus-unregister-object gnus-nm-dbus-registration)
             (setq gnus-nm-dbus-registration nil)
             (gnus-message 6 "Disabled integration with NetworkManager"))))

;; Add hooks for plugging/unplugging on network state change:
(add-hook 'gnus-nm-connected-hook 'gnus-nm-agent-plug)
(add-hook 'gnus-nm-disconnected-hook 'gnus-nm-agent-unplug)
;; Add hooks for enabling/disabling integration on startup/shutdown:
(add-hook 'gnus-started-hook 'gnus-nm-enable)
(add-hook 'gnus-exit-gnus-hook 'gnus-nm-disable)

If you want to start up Gnus in offline or online state depending on the current network status, you can add a custom Gnus startup function in ~/.emacs, something like this:

(require 'dbus)
(defun nm-is-connected()
  (equal 3 (dbus-get-property
            :system "org.freedesktop.NetworkManager" "/org/freedesktop/NetworkManager"
            "org.freedesktop.NetworkManager" "State")))
(defun switch-to-or-startup-gnus ()
  "Switch to Gnus group buffer if it exists, otherwise start Gnus in plugged or unplugged state, 
depending on network status."
  (interactive)
  (if (or (not (fboundp 'gnus-alive-p))
          (not (gnus-alive-p)))
      (if (nm-is-connected)
          (gnus)
        (gnus-unplugged))
    (switch-to-buffer gnus-group-buffer)
    (delete-other-windows)))

– Øyvind Stegard

NetworkManager 0.9

With Network Manager 0.9 arriving, the nm-states were renumbered (refer to http://projects.gnome.org/NetworkManager//developers/migrating-to-09/spec.html#type-NM_STATE and http://projects.gnome.org/NetworkManager//developers/spec-08.html#type-NM_STATE).

The following adapted functions work fine for me:

(defun gnus-nm-state-dbus-signal-handler (nmstate)
  "Handles NetworkManager signals and runs appropriate hooks."
  (when (and (fboundp 'gnus-alive-p) (gnus-alive-p))
    (cond
     ((or (= 20 nmstate) (= 10 nmstate)
          (= 50 nmstate) (= 60 nmstate))
      (run-hooks 'gnus-nm-disconnected-hook))
     ((= 70 nmstate)
      (run-hooks 'gnus-nm-connected-hook)))))

(defun nm-is-connected()
  (equal 70 (dbus-get-property
            :system "org.freedesktop.NetworkManager" "/org/freedesktop/NetworkManager"
            "org.freedesktop.NetworkManager" "State")))

– Jonas Hörsch


CategoryGnus