5

Longer version of question: how can I efficiently detect and indicate when emacs is dictation safe?

By "dictation safe", I really mean "when can type random printable ASCII characters on my keyboard, and know that they will typically self-insert, or at least not invoke random destructive commands like deleting files".

"Indicate" - for my purposes, that means putting some sort of special text or other marking in the emacs frame title / OS window title. That's trivial, you don't need to answer that part, unless you have a better suggestion that applies to my use case is explained below.

"Efficiently detect" means, well exactly that. I can scan the keyboard map looking for self-insert-command, but I certainly don't want to do this on every keypress. I only want have to do this when a new mode, whether major or minor, is loaded that I don't know about, or when there have been other things that change the keyboard mappings.

More below, including some places where looking for self-insert-command is insufficient.

Why am I asking about "dictation safety"?

As you might guess, I use speech recognition and voice control on my computer because of RSI, and I'm a very long time user of emacs. Currently I am using both Dragon and the latest thing, Talon Voice, which has enabled some things that Dragon was unable to do by providing more control over dictation.

Most speech recognition systems control their target applications by emulating keyboard and mouse events. Most speech commands are "open-loop" getting very little feedback from their target applications, although you can do things like inspect window sizes, and sometimes look ar file contents. Microsoft has a speech API, but even their own applications do not consistently use. Almost none of the applications that I use use SAPI. The most common way to provide a bit of context for speech commands is to look at the window title, as well as what application owns the window.

One of the most annoying things for voice control is when you are controlling applications that have key-bindings to unmodified printable ASCII keys. Ctrl+Alt modified key-bindings are not so bad, because they don't arise in dictation. But if I am in an emacs buffer/mode such as DirEd or BufferMenu, where many if not nearly all ordinary letters have non-self-insert key-bindings, if I accidentally dictate something lots of stuff can go on. Including sometimes deleting files.

Most if not all speech recognition systems have separate command and dictation modes. Most speech recognition systems also have a mixed mode: if an utterance is recognized as a command that is handled, else it is handled as dictation.

So why not just put myself in command mode when I'm in such an emacs buffer? Well, I switch between text and programming language buffers and buffer menu and dired a whole lot. Sometimes I forget. And in any case, having to the command over and over again is really annoying.

I have put the major mode in the frame title, and with Talon Voice I can enable/disable dictation according to the window title. I am using it for DirEd and BufferMenu. But emacs has many, many modes, I use many of them, and I want to be able to use more without tripping over myself every time I encounter something that is not dictation friendly. I was motivated to post this question, and actually to switch to Talon Voice, the umpteenth time I had to add another new major mode to my white/and/or blacklists of dictation safety.

Enough motivation, on to how to accomplish this

Scan keymaps for self-insert-command

Actually, rather than scan all the keymaps, I just loop over a-zA-Z0-9, the punctuation characters, and various whitespace characters, looking for (keybinding…) == self-insert-command.

If there are no self-insert-command findings found by this scan, it's likely not dictation safe. Although see below. Note however that many modes pass through to global bindings for the range \200 .. 3FFF7F as self-insert-command, which covers a whole slew of unicode code points. (I may have enabled some special unicode support to get this.) This motivates scanning for only the printable ascii characters.

BTW, ChatGPT suggests using map-char-table or standard-case-table -- but I don't think these are guaranteed to contain all printable input characters. E.g. not all characters need case conversion.

But when do I do this scan? Obviously not every character (I've tried).

What I really want is a place that I can hook or defadvice

  • essentially when anything happens that might change what pressing a key X will do
  • e.g. on every mode change - both major and minor modes
  • possibly whenever a new key-binding is made
    • e.g. using define-key
    • but IIRC it is still possible to modify the keymap data structures by hand
  • when the set of active keymaps is changed

Unfortunately I can't see any conveniently relevant hooks.

Modes that redefine self-insert away

Org-mode has keymap entry ` org-self-insert-command'

Similarly when you make a dired buffer writeable, you get ` wdired--self-insert'

=> The presence of this remap entry is a pretty good indication that self-inserts are allowed to some extent, and hence it's probably dictation safe.

But of course this doesn't need to be done: some modes are older than entries, or at least may have been written by somebody who didn't know about entries, so they may have overridden any self-insert-command bindings without leaving any obvious clue.

I doubt that much can be done about this, but I thought I would ask anyway.

Minibuffer Modes

Minibuffer modes have their own keymaps. E.g. and incremental search binds printable characters like a-z to isearch-printable-char (I assume through the usual …).

But I will probably take the easy way out, and just assume that all minibuffers are dictation safe.

Text Properties and Overlays

It is my understanding that text properties and overlays can be associated with keymaps and key-bindings. Again, easy enough to scan, but the question was how to avoid repeatedly scanning. Are there any hooks that can be invoked when a text property or overlay changes?

Conclusion

Looping over the printable characters a-zA-Z0-9 etc. and using (key-binding…) is straightforward.

But doing this constantly, e.g. whatever emacs' point changes, is rather a lot of work.

I sure would like to find a set of hooks or defadvice points that could make this more efficient.

1
  • Wow! Great question! Do you have a repo with your current configuration? I’m sure many users would love to refer to a working configuration when they want similar setup. Thanks for asking your question! Commented Nov 13, 2024 at 16:27

1 Answer 1

2

I did not originally intend to answer my own question, and this may not be a good enough answer, but I asked ChatGPT in a slightly different way and I got much better suggestions:

Anyway, after detecting these changes in selected window or major/minor modes, do the somewhat expensive (key-binding ...) probe looping over a-zA-Z0-9 etc.

In combination with white/black lists of known dictation safe/unsafe modes, it's probably sufficient to meet most of my needs.

Although AFAICT these do not address text properties or overlays - or do they?

but better suggestions are welcome.

Hook changes in the selected (activated) window

(add-hook 'window-selection-change-functions ...) 

change-major-mode-hook

(add-hook 'change-major-mode-hook (lambda () (message "Major mode changing to %s" major-mode))) 

post-command-hook scan minor-mode-alist for changes

looping (mapcar for LISP bigots) over `minor-mode-alist'

It is unfortunate that this would have to be done in post-command-hook, but it's probably cheaper than the keymap scan.

define-key advice

... yada yada yada, with the problems already mentioned.

Watch the various keymap variables

I got excited when I saw this suggestion, but on thinking about it I think the (key-binding ...) probe looping for a-zA-Z0-9 etc. is probably better.

(add-variable-watcher 'emulation-mode-map-alists #'my-track-keymap-changes) 

Probably watching the list of variables from elisp Searching-Keymaps

(or (if overriding-terminal-local-map (find-in overriding-terminal-local-map)) (if overriding-local-map (find-in overriding-local-map) (or (find-in (get-char-property (point) 'keymap)) (find-in-any emulation-mode-map-alists) (find-in-any minor-mode-overriding-map-alist) (find-in-any minor-mode-map-alist) (if (get-char-property (point) 'local-map) (find-in (get-char-property (point) 'local-map)) (find-in (current-local-map))))) (find-in (current-global-map))) 

I don't particularly like doing this, since I've seen the number of these various keymaps grow dramatically over the many years that I've been using emacs, but it's probably good enough.

Actually, what I really dislike about this is that the equality check traverses all active keymaps, whereas the (key-binding ...) probe looping over a-zA-Z0-9 etc. is less work.

6
  • 2
    Note that variable watchers only help if the value cell of the variable symbol is being updated. For lists (which includes keymaps), the value is just a cons cell; and manipulating a list's elements needn't necessarily involve updating any variables pointing to that list (if the initial cons cell continues to link to the correct list, the variable needn't change). As keymaps are lists for which the initial element never needs to change, defining a key will never trigger a watcher for any variable pointing to that keymap. Commented Nov 5, 2024 at 8:57
  • 2
    Similarly, a variable pointing to an alist may or may not change if something is manipulated in the alist. If a new value is pushed to the front, the variable will undoubtedly be updated. If an existing value is manipulated, the variable might be updated, but it would depend upon the code doing the update (code is frequently agnostic to this and simply sets the variable in all cases, in which case a watcher would trigger; but it's certainly possible to update a list without doing that). Commented Nov 5, 2024 at 9:00
  • 1
    As for text properties and overlays... obviously it depends on the position of point as to whether such keymaps will be active or not, so I suspect you'd need to perform look-ups in post-command-hook to check these. Commented Nov 5, 2024 at 9:06
  • Yes, @phils, this is the sort of thing that made me not like the idea of monitoring the data structure at all. It would only be a component of anything. As in, if any of the listed changed, would need to rescan them. But there are so many other reasons to rescan them. Commented Nov 5, 2024 at 23:37
  • Would this work? Setting a pre-command-hook, inspecting this-command and this-command-keys to detect printable ASCII key triggering something that is not self-insert? It may be a bit late, but I can try to cancel the current command, and set the frame title for subsequent dictation squashing. --- This sounds too good to be true. --- Plus, I did something like this when I was actually trying to squash dictation rather than just detect it. Problem: fine points plays needed a way to allow user to escape. Commented Nov 6, 2024 at 0:27

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.