Clickable telephone numbers in mu4e messages

| categories: emacs | tags: | View Comments

We recently updated our university phone system to a VoIP system that uses Cisco Jabber. I am excited about that because finally I can make phone calls from Emacs with a little applescript automation! So, spoiler alert, this post mostly only applies to Macs, unless you know how to automate a Jabber client to make calls. How to make the telephone numbers clickable is general though, and could be used to do other things as well.

(defun cisco-call (phone-number)
  (interactive "sPhone number: ")
  (do-applescript
   (format "tell application \"Cisco Jabber\"
        activate
        tell application \"System Events\" to keystroke \"n\" using {shift down, command down}
        tell application \"System Events\" to keystroke \"%s\"
        tell application \"System Events\" to key code 36 #return
end tell" phone-number)))
cisco-call

I would like to go a step further, and make clickable phone numbers in my Emacs buffers. Let's take a look at some options.

1 org-mode phone link

This is a no-brainer approach. We can define an org-mode link that runs the cisco-call function.

(org-add-link-type
 "phone"
 (lambda (phone-number)
   (cisco-call phone-number)))
lambda (phone-number) (cisco-call phone-number)

This makes simple link that just calls the number in the path of the link.

2 Clickable text with button-lock

I have used the button-lock package very often to make clickable text. Here we use it to highlight phone numbers matching a regular expression that seems to match most US numbers. This seems to work great in org-mode buffers.

(require 'rx)

(defvar highlight-phone-numbers nil
 "Button for `highlight-phone-numbers'")

(defun highlight-phone-numbers ()
  "Make phone numbers of the following types clickable:
  (xxx) xxx-xxxx
  xxx.xxx.xxx
  xxxxxxxxxx
  xxx-xxx-xxxx"
  (interactive)
  (let ((inhibit-read-only t))
    (setq highlight-phone-numbers
          (button-lock-set-button
           (rx
            ;; optional () around area code
            (optional "(")
            (= 3 digit)
            (optional ")")
            ;; delimiters
            (or (optional "-")
                (optional ".")
                (optional " "))
            (= 3 digit)
            (or (optional "-")
                (optional ".")
                (optional " "))
            (= 4 digit))
           (lambda ()
             (interactive)
             (cisco-call (get-surrounding-text-with-property 'phone-number)))
           :face '((:background "Darkolivegreen2")
                   (:underline t))
           :help-echo "click to call"
           :keyboard-binding (kbd "RET")
           :additional-property 'phone-number))))

(add-hook 'text-mode 'highlight-phone-numbers)
highlight-phone-numbers

3 Phone numbers in mu4e messages

For some reason, the button-lock package doesn't seem to work in mu4e message buffers, Maybe it is because . The highlight-regexp package does work though, so for these special buffers we use a new approach. We will just put text properties where we want them, and use those properties to make the text clickable.

The messages are in read-only buffers, but we can inhibit that so we can modify the properties. All we need to do is create a little key map as a copy of the existing map, define some keys on it, then search through the buffer adding properties to every phone number we find. I wrote a function that does that, and put that function in a hook to run each time I open a message. Whammo, now I have clickable phone numbers in email! It works pretty well for me.

(defface mu4e-phone-face
  '((t (:foreground "SteelBlue4" :background "Darkolivegreen2" :underline t)))
  "Phone number directive face.")

(defun mu4e-highlight-phone-numbers ()
  "Make phone numbers clickable in mu4e-view buffers."
  (interactive)
  (let ((phone-regex (rx
                      ;; optional () around area code
                      (optional "(")
                      (= 3 digit)
                      (optional ")")
                      ;; delimiters
                      (or (optional "-")
                          (optional ".")
                          (optional " "))
                      (= 3 digit)
                      (or (optional "-")
                          (optional ".")
                          (optional " "))
                      (= 4 digit))))
    (save-excursion
      (let ((inhibit-read-only t))
        (goto-char (point-min))
        (while (re-search-forward phone-regex nil t)
          (let ((map (copy-keymap mu4e-view-mode-map))
                (start (match-beginning 0))
                (end (match-end 0)))

            ;; set file to be clickable to open the source
            (define-key map [mouse-1]
              `(lambda ()
                 (interactive)
                 (cisco-call ,(match-string 0))))

            ;; let letter c also make the call
            (define-key map "c"
               `(lambda ()
                 (interactive)
                 (cisco-call ,(match-string 0))))

            (set-text-properties
             start end
             `(local-map, map
                          face mu4e-phone-face
                          mouse-face highlight
                          help-echo "mouse-1: click to call"))))))))

(add-hook 'mu4e-view-mode-hook 'mu4e-highlight-phone-numbers)

4 Summary

That works pretty well for me overall. The phone number regex is not perfect, e.g. it makes any 10 digit number clickable, and it doesn't recognize international numbers. I am not sure I can call those through the Jabber client anyway. This is purely convenience for me to easily make calls from emails, or other kinds of documents I might read in Emacs.

I don't use phone calls very often, but an interesting thing might be to open a phone log in org-mode, or open the contact that has that phone number to log that you called them, and provide some notes for them. Alternatively, open a new capture for a phone log that could be refiled later.

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

blog comments powered by Disqus