Updated speechd-el config

Unfortunately being again forced to use an audio desktop, following my updated speechd-el specific emacs configuartion.

Regarding my choice of speechd-el over emacspeak, why emacs is IMHO the best platform for visually impaired users see my previous blog entry.

prepare for speechd-el

  • Being used to Common Lisp - I want lexical-binding t.
  • I want to have a backtrace on error that happens. That makes it easier grok an error.
  • Whenever a Help buffer opens up, I want to switch to the window.
  • Add the directory of speechd-el to the info-directories list.
  • I want org files startup folded
;;; ...  -*- lexical-binding: t -*-
(setq debug-on-error t)
(setf help-window-select t)
(setf Info-additional-directory-list
      (list "/home/okflo/install/speechd-el/"))
(setq org-startup-folded t)

load speechd-el

  • add the location of speechd-el to load-path
  • reduce to ssip output driver, as I don't use braille currently.
  • require speechd, speechd-ssip, speechd-speak and speechd-el
(add-to-list 'load-path (expand-file-name "~/install/speechd-el/"))
(require 'speechd)
(require 'speechd-ssip)
(require 'speechd-speak)
(require 'speechd-el)
(setf speechd-out-active-drivers '(ssip))



Various speechd-el specific settings. I prefer explicitly setting variables over customize:

(setf speechd-speak-read-command-keys nil)
(setf speechd-speak-read-command-name nil)

(setf speechd-speak-whole-line t)

(setf speechd-speak-echo 'character)

(setf speechd-speak-use-index-marks t)

(setf speechd-speak-buffer-insertions 'one-line)

(setf speechd-speak-ignore-command-keys
      '( forward-char backward-char right-char left-char
         next-line previous-line delete-char
         comint-delchar-or-maybe-eof  delete-backward-char
         delete-forward-char c-electric-backspace

(setf speechd-speak-by-properties-on-movement t)

(setf speechd-speak-state-changes
        ;; buffer-read-only
        ;; frame-identification
        ;; minor-modes
        ;; terminal-coding
        ;; input-method


Define voices and for which face to use which voice, f.e.:

  • higher pitch for various links
  • lower pitch for headlines or directories
  • reduce rate for info-quote
(setf speechd-voices '((voice-link . ((pitch . 50)))
                       (voice-function-name . ((pitch . -30)
                                               (rate . -10)
                                               (style . 3)
                                               (punctuation-mode . all)
                                               (capital-character-mode . icon)))
                       (voice-whisper . ((pitch . 50)
                                         (rate . 10)
                                         (style . 3)
                                         (punctuation-mode . all)
                                         (capital-character-mode . icon)))
                       (voice-distinct . ((pitch . 5)
                                          (rate . -40)
                                          (style . 3)
                                          (punctuation-mode . all)
                                          (capital-character-mode . icon)))
                       (voice-heading . ((pitch . -250)))
                       (voice-source-code . ((pitch . 0)
                                             (rate . -20)
                                             (punctuation-mode . all)
                                             (capital-character-mode . icon)))))

(setf speechd-face-voices '((font-lock-function-name-face . voice-function-name)
                            (Info-quoted . voice-distinct)
                            (info-title-1 . voice-heading)
                            (info-title-2 . voice-heading)
                            (info-title-3 . voice-heading)
                            (info-title-4 . voice-heading)
                            (info-menu-header . voice-heading)
                            (info-xref . voice-link)
                            (info-xref-visited . voice-link)
                            (link . voice-link)
                            (shr-link . voice-link)
                            (shr-selected-link . voice-link)
                            (eshell-ls-directory . voice-link)
                            (eshell-ls-backup . voice-whisper)
                            (eshell-ls-executable . voice-function-name)
                            (eshell-prompt . voice-heading)
                            (dired-directory . voice-link)
                            (elpher-gemini . voice-link)
                            (org-level-1 . voice-heading)
                            (org-level-2 . voice-heading)
                            (org-level-3 . voice-heading)
                            (org-block . voice-source-code)
                            (org-source . voice-source-code)
                            (org-link . voice-link)
                            (shr-h1 . voice-heading)
                            (shrface-h1-face . voice-heading)
                            (shrface-h2-face . voice-heading)
                            (shrface-h3-face . voice-heading)
                            (shrface-h4-face . voice-heading)
                            (elpher-gemini-heading1 . voice-heading)
                            (elpher-gemini-heading2 . voice-heading)
                            (elpher-gemini-heading3 . voice-heading)
                            (shrface-href-face . voice-link)))

Application specific adaptions

reading and information of window/frame/etc

  • 'C-e f': speak associated file of current buffer
  • 'C-e TAB w': speak current window size and buffer name
  • 'C-e TAB W': speak number of windows in frame and their buffer-names
  • 'C-e TAB (': speak number of chars before point in line
  • 'C-e TAB )': speak number of chars after point in line
(define-key speechd-speak-mode-map "h"

(defun okflo-speak-file-associated-with-buffer ()
  (if buffer-file-name
      (message buffer-file-name)
    (message "none")))

(define-key speechd-speak-mode-map "f"

(defun okflo-speak-window-size ()
   (format "Window width %s columns and window height %s lines containing %s"

(define-key speechd-speak-info-map "w"

(defun okflo-describe-windows-in-frame ()
  (let* ((windows (window-list))
          (cl-loop for iw in windows
                   collect (buffer-name (window-buffer iw)))))
    (message (format "Frame has %s windows containing buffers %s"
                     (length windows)

(define-key speechd-speak-info-map "W"

(defun okflo-position-in-text-line ()
   (format "Cursor in line at %s"
           (- (point) (line-beginning-position)))))

(define-key speechd-speak-info-map "("

(defun okflo-remaining-chars-in-line ()
   (format "%s remaining chars in line"
           (- (line-end-position) (point)))))

(define-key speechd-speak-info-map ")"


Forget about helm, consult and all the other frameworks for file-completions, etc. They won't work together with speechd-el, and actually, vanilla completion buffer is perfect.

  • additionally to M-up and M-down also define C-<up>/<left> and C-<down>/<right> for completion, as they are much more reachable on my keyboard.
  • add 'M-g M-c' to global keymap - so that we also can jump to Completions after complete-at-point
    • have fore each completion candidate a seperate line in completion buffer.
(define-key minibuffer-mode-map (kbd "C-<up>") 'minibuffer-previous-completion)
(define-key minibuffer-mode-map (kbd "C-<left>") 'minibuffer-previous-completion)

(define-key minibuffer-mode-map (kbd "C-<down>") 'minibuffer-next-completion)
(define-key minibuffer-mode-map (kbd "C-<right>") 'minibuffer-next-completion)

(keymap-global-set "M-g M-c" 'switch-to-completions)

(setf completions-format 'one-column)
(setf completions-detailed t)


  • <C-e k> to speak next yank
  • give feedback for kill-ring-save, how many characters have been saved.
(defun okflo-speak-next-yank ()
  (speechd-say-text (car kill-ring)))

(define-key speechd-speak-mode-map "k" 'okflo-speak-next-yank)

 kill-ring-save after
 (speechd-say-text (format "Saved %s chars"
                           (length (car kill-ring)))))


  • eldoc-mode is to noisy, speaking all the time the echo-area
  • instead - bind <M-g M-d> to open up a buffer with eldoc information and switch to this buffer.
(global-eldoc-mode -1)
(defun okflo-speak-eldoc ()
  (eldoc-print-current-symbol-info t)
  (other-window 1))
(keymap-global-set "M-g M-d" 'okflo-speak-eldoc)

fast switch between german and english voice

my native tongue is (Austrian) German - but I also want to have English.

  • <C-e g> switches to German
  • <C-e C-g> switches to English
(defun okflo-switch-to-german ()
  (speechd-set-voice "petra-ml-embedded-high")
  (speechd-set-language "de")
  (speechd-set-rate 28)
  (message "Deutsch"))

(define-key speechd-speak-mode-map "g" 'okflo-switch-to-german)

(defun okflo-switch-to-english ()
  (speechd-set-voice "allison-embedded-high")
  (speechd-set-language "en")
  (speechd-set-rate 14)
  (message "English"))

(define-key speechd-speak-mode-map (kbd "C-g") 'okflo-switch-to-english)

control sound volume

Control volume via amixer

  • <C-e -> decreases by 5%
  • <C-e +> increases by 5%
(defun okflo-volume- ()
  (shell-command "amixer sset Master 5%-")
  (message "Volume down"))

(define-key speechd-speak-mode-map "-" 'okflo-volume-)

(defun okflo-volume+ ()
  (shell-command "amixer sset Master 5%+")
  (message "Volume up"))

(define-key speechd-speak-mode-map "+" 'okflo-volume+)

filter current buffer in a new buffer

takes the content of the current buffer and copies it into a new buffer "Filter: <buffer-name>". This buffer is associated with no file. Purpose of this buffer is to (destructively) filter the content and make it easier to grok it.

  • press 'C-e f' to activate it.
  • commands as minor mode `okflo-filter-mode` (always affecting the whole buffer!) for the new buffer:
    • 'C-c d': Delete all lines containing regex.
    • 'C-c k': Keep all lines containing regex.
    • 'C-c h': Count number of lines containing regex.
(defun okflo-keep-lines (regexp &optional rstart rend interactive)
     (keep-lines-read-args "Keep lines containing match for regexp")))
  (let ((orig-lines (count-lines (point-min) (point-max))))
    (keep-lines regexp (point-min) (point-max))
    (message (format "Reduced from %s to %s lines" orig-lines (count-lines (point-min) (point-max))))))

(defun okflo-delete-lines (regexp &optional rstart rend interactive)
     (keep-lines-read-args "Flush lines containing match for regexp")))
  (let ((orig-lines (count-lines (point-min) (point-max))))
    (flush-lines regexp (point-min) (point-max))
    (message (format "Reduced from %s to %s lines" orig-lines (count-lines (point-min) (point-max))))))

(define-minor-mode okflo-filter-mode
  "Easily descructively parse the content."
  :lighter "OFM"
  :keymap `((,(kbd "C-c k") . okflo-keep-lines)
            (,(kbd "C-c d") . okflo-delete-lines)
            (,(kbd "C-c h") . how-many)))

(defun okflo-filter-buffer ()
  (let* ((tobe-filtered-buf (current-buffer))
         (buf-name (format "*Filter:<%s>*"(buffer-name tobe-filtered-buf)))
         (new-buf (get-buffer-create buf-name)))
      (copy-to-buffer new-buf (point-min) (point-max)))
    (set-buffer new-buf)
    (switch-to-buffer new-buf)

(define-key speechd-speak-mode-map "F" 'okflo-filter-buffer)

speak time

'C-e t': speaks current time and date - give the string to be spoken a face, to speak it distinctively.

(defun okflo-speak-time ()
  (let* ((dt (decode-time (current-time)))
         (months '(January Febuary March April May June July August September October December))
         (time-text (format "Time %s:%s / Date %s %s %s"
                            (decoded-time-hour dt)
                            (decoded-time-minute dt)
                            (nth (1- (decoded-time-month dt)) months)
                            (decoded-time-day dt)
                            (decoded-time-year dt))))
     (propertize time-text 'face 'mode-line)
     :priority 'important)))

(define-key speechd-speak-mode-map "t" 'okflo-speak-time)


<C-e ~> speaks current battery status of my laptop.

(require 'battery)

(defun okflo-speak-battery-status ()

(define-key speechd-speak-mode-map "~" 'okflo-speak-battery-status)


  • add 'C-e …' to be available in semi-char-mode so that we can read/speak.
(setf eat-semi-char-non-bound-keys '([?\C-e]
                                     [?\C-x] [?\C-\\] [?\C-q] [?\C-g] [?\C-h] [?\e ?\C-c] [?\C-u]
                                     [?\e ?x] [?\e ?:] [?\e ?!] [?\e ?&]
                                     [C-insert] [M-insert] [S-insert] [C-M-insert]
                                     [C-S-insert] [M-S-insert] [C-M-S-insert]
                                     [C-delete] [M-delete] [S-delete] [C-M-delete]
                                     [C-S-delete] [M-S-delete] [C-M-S-delete]
                                     [C-deletechar] [M-deletechar]
                                     [S-deletechar] [C-M-deletechar] [C-S-deletechar]
                                     [M-S-deletechar] [C-M-S-deletechar]
                                     [C-up] [C-down] [C-right] [C-left]
                                     [M-up] [M-down] [M-right] [M-left]
                                     [S-up] [S-down] [S-right] [S-left]
                                     [C-M-up] [C-M-down] [C-M-right] [C-M-left]
                                     [C-S-up] [C-S-down] [C-S-right] [C-S-left]
                                     [M-S-up] [M-S-down] [M-S-right] [M-S-left]
                                     [C-M-S-up] [C-M-S-down] [C-M-S-right] [C-M-S-left]
                                     [C-home] [M-home] [S-home] [C-M-home] [C-S-home]
                                     [M-S-home] [C-M-S-home]
                                     [C-end] [M-end] [S-end] [C-M-end] [C-S-end]
                                     [M-S-end] [C-M-S-end]
                                     [C-prior] [M-prior] [S-prior] [C-M-prior]
                                     [C-S-prior] [M-S-prior] [C-M-S-prior]
                                     [C-next] [M-next] [S-next] [C-M-next] [C-S-next]
                                     [M-S-next] [C-M-S-next]))



  • after each command finished processing, give audio feedback and tell us how many lines of output are done.
  • have a command in eshell (d for done) to append as " ; d" to get audio feedback, when command has finished, optionally provide a text to be spoken after "d", defaults to "done.".
(cl-defun eshell/d (&optional (text "done."))
  (speechd-say-sound "at" :priority 'important)
  (speechd-say-text text :priority 'important))

(require 'em-alias)
;;(eshell/alias "ls" "ls -1 $@*")
(defun okflo-eshell-post-command-hook ()
  (when global-speechd-speak-mode
    (speechd-say-sound "at"
                       :priority 'important)
    (speechd-say-text (format "output %s lines"
                              (count-lines (eshell-end-of-output)
                      :priority 'important )))

(add-hook 'eshell-post-command-hook

(setf eshell-highlight-prompt t)


  • change entries in search-view, so that title gets spoken first.
  • elfeed-search-remain-on-entry needs to be set to t, otherwise following entries are spoken in entry-view
(require 'elfeed)

(setf elfeed-search-title-max-width 150)

(defun okflo-elfeed-search-print-entry (entry)
  "Print ENTRY to the buffer."
  (let* ((date (elfeed-search-format-date (elfeed-entry-date entry)))
         (title (or (elfeed-meta entry :title) (elfeed-entry-title entry) ""))
         (title-faces (elfeed-search--faces (elfeed-entry-tags entry)))
         (feed (elfeed-entry-feed entry))
          (when feed
            (or (elfeed-meta feed :title) (elfeed-feed-title feed))))
         (tags (mapcar #'symbol-name (elfeed-entry-tags entry)))
         (tags-str (mapconcat
                    (lambda (s) (propertize s 'face 'elfeed-search-tag-face))
                    tags ","))
         (title-width (- (window-width) 10 elfeed-search-trailing-width))
         (title-column (elfeed-format-column
                        title (elfeed-clamp
    (insert (propertize title-column 'face title-faces 'kbd-help title) " ")
    (when feed-title
      (insert (propertize feed-title 'face 'elfeed-search-feed-face) " "))
    (insert (propertize date 'face 'elfeed-search-date-face) " ")
    (when tags
      (insert "(" tags-str ")"))))

(setf elfeed-search-print-entry-function #'okflo-elfeed-search-print-entry)

(setf elfeed-search-remain-on-entry t)


  • get feedback, whether entry is folded, bound to <C-c f> in org-buffers.
(defun okflo-say-org-fold-status ()
    (if (org-fold-folded-p)
        (speechd-say-text "folded" :priority 'important)
      (speechd-say-text "not folded" :priority 'important))))

(define-key org-mode-map (kbd "C-c f") 'okflo-say-org-fold-status)


  • have an audio-feedback, when load/rendering of page is done.
  • <j> for jumping to next H* tags
    • j jumps to next H1 tag
    • N prefix + j jums to next HN tag, f.e. <3j> jumps to next H3 tag.
(require 'eww)

(defun okflo-eww-page-loaded ()
  (when global-speechd-speak-mode
    (speechd-say-sound "piano-3.wav" :priority 'important)
    (speechd-say-text "page loaded" :priority 'important)))

(add-hook 'eww-after-render-hook #'okflo-eww-page-loaded)

(defun okflo-eww-move-cursor-to-next-heading (level)
  (interactive "P") 
  (setf level (or level 1))
  (let ((next-match (text-property-search-forward 'outline-level level t t)))
    (setf mydebug next-match)
    (if next-match
        (goto-char (prop-match-beginning next-match))
      (message "No match"))))

(define-key eww-mode-map (kbd "j") 'okflo-eww-move-cursor-to-next-heading)


  • when viewing a message speak its subject
(require 'mu4e)
(setf mu4e-headers-fields
      '((:from-or-to . 30) (:thread-subject . 70) (:flags . 6) (:human-date . 12)))

(defun okflo-mu4e-speak-subject ()
  (when global-speechd-speak-mode
    (speechd-say-text (cl-getf (mu4e-message-at-point) :subject) :priority 'important)))

(add-hook 'mu4e-view-rendered-hook


I use exwm as my window manager.

  • <s-[0-9]> jumps to frame [0-9] giving audio feedback.
  • <s-^> switches display on/off by calling xrandr.
 after (progn
         (speechd-say-sound "hash" :priority 'important)
          (format "%s"

(defvar okflo-display t)

(defun okflo-switch-display-on/off ()
  (if okflo-display
        (call-process "/usr/bin/xrandr" nil nil nil "--output" "eDP1" "--off")
        (setf okflo-display nil)
        (speechd-say-text "Display switched off"))
      (call-process "/usr/bin/xrandr" nil nil nil "--output" "eDP1" "--auto")
      (setf okflo-display t)
      (speechd-say-text "Display switched on"))))

(global-set-key (kbd "s-^") 'okflo-switch-display-on/off)


  • switch to my preferrd voice
  • start speechd-el

Date: 2024-04-10 Wed 00:00

Author: Otto Diesenbacher-Reinm├╝ller