lkml.org 
[lkml]   [2013]   [Dec]   [10]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH] drivers/accessibility: audio feedback as characters are generated
Date
From: Karl Dahlke <eklhad@gmail.com>

This module generates soft clicks as printable characters are sent to the
foreground tty.
These sounds simulate an old fashion teletype running at 2400 baud.
This serves as valuable feedback for a blind user,
even if speech or braille is not working.
And there are many reasons why speech or brail might not function.
With clicks alone, it is possible to log in and issue some commands
in an attempt to get speech working again.
Also, these clicks throttle the output, so that screen after screen
does not fly by before the blind user can hit control S
and find out what is happening.
A separate sequence of tones indicates a printk message, like an audible alarm.
These sounds can be turned on and off by a master switch at runtime.

Signed-off-by: Karl Dahlke <eklhad@gmail.com>
---

This module depends on the additional kd sounds that are in the patch
I submitted earlier with subject
[PATCH] drivers/input/misc: new PC speaker sounds, clicks and notes
In fact this patch will not build without the kd patch.
I kept them separate however because they are logically different.
The kd patch is a general enhancement to the pc speaker sounds
of the kernel, which anyone can use for any purpose, even games.
This is specifically an accessibility module that happens to use those sounds.

This code passes checkpatch.pl, and has been exercised (in some form)
for years, going back to the 2.2 kernel series.
Thank you for your review and consideration.

diff -uprN a/Documentation/accessibility/ttyclicks.txt b/Documentation/accessibility/ttyclicks.txt
--- a/Documentation/accessibility/ttyclicks.txt 2013-12-10 18:48:27.000000000 -0500
+++ b/Documentation/accessibility/ttyclicks.txt 2013-12-10 18:48:27.000000000 -0500
@@ -0,0 +1,160 @@
+
+The tty clicks module
+ ---------------------
+ Karl Dahlke
+ eklhad@gmail.com
+
+When this module is installed, soft clicks accompany
+the nonspace characters that are sent to the console,
+while a longer swoop indicates a newline character.
+These sounds simulate an old fashion teletype (how many of you are old enough
+to remember those) running at 2400 baud.
+Why would you want such a thing?
+You probably wouldn't, but a blind person might.
+
+I can tell, by clicks alone, when the computer responds to a command,
+and I can discern the quantity and format of that response,
+without any speech or braille.
+In addition, the output is throttled, and does not fly by the screen
+in a flash, faster than I could possibly hit control S.
+
+It is important that this be a separate, stand alone kernel module
+that does not depend on anything else.
+If the adapter isn't running,
+if the synthesizer isn't working, if the braille display isn't working,
+if we're talking to the wrong serial port, if the sound card isn't working,
+if the sound card is not properly initialized,
+if the sound module is not loaded, if the wrong sound module is loaded,
+if an essential daemon is not running - at least I have my clicks.
+I can tell, to some degree, what the computer is doing.
+If it gets to a login prompt, I know what that sounds like.
+I can log in and listen for the single click of the $ prompt,
+which tells me I have a shell.
+Then I can issue some basic commands,
+trying to load modules and start the adapter.
+All this I do without speech.
+I need these clicks to be present whenever the kernel is running.
+To this end, ttyclicks is a self-contained module
+that is loaded first in the boot sequence,
+quite apart from any adapters.
+I can turn off the clicks at run time if I wish,
+but if the synthesizer ever fails me, for any reason,
+I have the clicks to fall back on.
+
+As you can see from the above paragraph,
+I don't want to run these clicks through anybody's sound card.
+There are too many points of failure along that path.
+Instead, I use the in-built toggle speaker at port 0x61.
+Not all machines have this speaker, but most do,
+and I wouldn't buy a computer that didn't.
+
+Other programs, outside of the linux kernel, can also take advantage of
+this legacy speaker.
+Grub, for instance, beeps when it brings up the menu,
+provided I put a control G in the title.
+This tells me that I have ten seconds to cursor up and down
+and select a target, if I want something other than the default.
+Beyound this, it tells me that the machine has gotten to grub successfully,
+and is not stuck in bios for some bizarre reason.
+It is difficult to overstate the importance of this little in-built speaker
+for the blind user.
+
+This module might not click and chirp if you are in X.
+It responds to text characters that pass through the standard tty,
+and probably doesn't do anything in graphics mode.
+Even the terminal emulator, running under a desktop, still runs through X,
+and probably won't work with ttyclicks.
+You really need to be at run level 3.
+See /etc/inittab for the run level.
+This module is for command line users,
+or users who will stay in command mode long enough to make sure their
+software is properly launched and speaking.
+
+Module parameters:
+
+As always, you can run modinfo ttyclicks.ko to review these parameters.
+
+enabled = 1 or 0
+
+If 1, then sounds are enabled as soon as the module is loaded.
+If 0, then sounds are not enabled.
+These sounds can be turned on or off at run time by the exported symbol
+ bool ttyclicks_on;
+Thus other modules can turn sounds on or off.
+In particular, any adapter that includes a lodable module
+has access to this master switch.
+
+fgtty = 1 or 0
+
+This parameter enables the clicks that accompany the display of characters
+from the foreground tty.
+The corresponding exported symbol is
+ bool ttyclicks_tty;
+
+kmsg = 1 or 0
+
+Printk messages do not pass through the tty,
+and do not generate the aforementioned clicks.
+However, if this switch is enabled,
+each printk message generates a distinctive sequence of tones.
+This is like an alarm,
+telling the user that he needs to read the message on the screen.
+The corresponding exported symbol is
+ bool ttyclicks_kmsg;
+
+cursormotion = 0 or 1
+
+Many screen programs generate ansi escape sequences that position the cursor
+and set character attributes such as underline etc.
+This is not true output, and does not generate clicks by default.
+However, you can set this parameter to 1 if you want these characters
+to generate clicks.
+
+Exported functions.
+These are all subject to the master switch ttyclicks_on.
+
+void ttyclicks_click(void);
+
+Generate a 0.6 millisecond pulse.
+
+void ttyclicks_cr(void);
+
+Generate a quick frequency swoop from 2900 to 3600 hz.
+This sound is associated with the newline character at the console,
+but you can use this function to generate the same sound whenever you wish.
+
+void ttyclicks_notes(const short *notelist);
+
+Play a series of notes in the background.
+Each note is defined by two shorts, a frequency and a duration.
+The frequency is in hurtz, and the duration is in hundredths of a second.
+A frequency of -1 is a rest.
+A frequency of 0 terminates the array.
+The queue can only hold 64 notes, so don't try to play an entire sonata.
+
+void ttyclicks_steps(int freq1, int freq2, int step, int duration)
+
+Play a series of notes, of the specified duration, moving from
+frequency freq1 to frequency freq2 in a geometric progression of step%.
+This is a customizable chromatic scale.
+The traditional scale, based on half tones, and starting at middle C,
+has an approximate step of 6%, and looks like this.
+ttyclicks_mksteps(260, 530, 6, 150);
+Use a negative step for a descending scale.
+
+void ttyclicks_bell(void);
+
+This function generates an 1800 hz tone for a tenth of a second.
+this is associated with the control G bell.
+The traditional bell is 1000 hz, but I wanted a slightly higher tone.
+As long as this module is loaded, it swallows control G, and sounds the bell,
+rather then passing it through to the console and letting the console
+ring the bell. This allows the master switch, ttyclicks_on,
+to enable or disable the control G bell along with all the other sounds.
+You can turn off all the sounds in one go, if they are bothering your roommate.
+
+This module is integral to many command line adapters,
+as a complement to speech or braille. Beyond this,
+it can prove invaluable if the adapter is not working for any reason.
+I have relied on this form of audio feedback, in some form or another,
+for over 30 years.
diff -uprN a/drivers/accessibility/Kconfig b/drivers/accessibility/Kconfig
--- a/drivers/accessibility/Kconfig 2013-12-10 18:48:27.000000000 -0500
+++ b/drivers/accessibility/Kconfig 2013-12-10 18:48:27.000000000 -0500
@@ -30,4 +30,19 @@ config A11Y_BRAILLE_CONSOLE

If unsure, say N.

+config TTYCLICKS
+ tristate "tty clicks"
+ ---help---
+ Generate clicks, using the pc speaker, as characters are sent
+ to the foreground console. The sound is similar to an old style
+ teletype running at 2400 baud.
+ This provides audio feedback to a blind user,
+ even if speech or braille is not available.
+ It also throttles the output, so that screen after screen of text
+ does not fly by before the blind user has a chance to hit control s
+ and find out what is happening.
+ See Documentation/accessibility/ttyclicks.txt for more details.
+
+ If unsure, say N.
+
endif # ACCESSIBILITY
diff -uprN a/drivers/accessibility/Makefile b/drivers/accessibility/Makefile
--- a/drivers/accessibility/Makefile 2013-12-10 18:48:27.000000000 -0500
+++ b/drivers/accessibility/Makefile 2013-12-10 18:48:27.000000000 -0500
@@ -1 +1,3 @@
obj-y += braille/
+
+obj-$(CONFIG_TTYCLICKS) += ttyclicks.o
diff -uprN a/drivers/accessibility/ttyclicks.c b/drivers/accessibility/ttyclicks.c
--- a/drivers/accessibility/ttyclicks.c 2013-12-10 18:48:27.000000000 -0500
+++ b/drivers/accessibility/ttyclicks.c 2013-12-10 18:48:27.000000000 -0500
@@ -0,0 +1,409 @@
+/* ttyclicks.c: generate clicks as output is sent to the screen.
+ * If you are old enough, this will remind you of the sounds of a mechanical
+ * teletype running at 1200 baud.
+ * Why would you want such a thing?
+ * You might not, but a blind person might.
+ * The clicks provide valuable audio feedback to blind users.
+ * They know when the computer responds to a command,
+ * and they can discern the quantity and format of that response,
+ * before the speech synthesizer has uttered a word.
+ * It also throttles the output, which would otherwise fly by the screen
+ * faster than any blind person could even so much as hit control s.
+ * Please see Documentation/accessibility/ttyclicks.txt for more details.
+ *
+ * Copyright (C) Karl Dahlke, 2004-2013.
+ * This software may be freely distributed under the GPL,
+ * general public license, as articulated by the Free Software Foundation.
+ */
+
+#include <linux/notifier.h>
+#include <linux/keyboard.h>
+#include <linux/kd.h>
+#include <linux/vt.h>
+#include <linux/vt_kern.h> /* for fg_console and speaker sounds */
+#include <linux/ctype.h>
+#include <linux/console.h>
+#include <linux/module.h>
+#include <linux/io.h> /* for inb() outb() */
+#include <linux/delay.h>
+
+#include "ttyclicks.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Karl Dahlke - eklhad@gmail.com");
+MODULE_DESCRIPTION
+ ("Console output generates clicks, resembling a mechanical teletype.");
+
+static int enabled = 1;
+module_param(enabled, int, 0);
+MODULE_PARM_DESC(enabled,
+ "sounds are enabled as soon as this module is loaded, default = 1 (yes)");
+
+static int fgtty = 1;
+module_param(fgtty, int, 0);
+MODULE_PARM_DESC(fgtty,
+ "foreground tty generates clicks and chirps, default = 1 (yes)");
+
+static int cursormotion;
+module_param(cursormotion, int, 0);
+MODULE_PARM_DESC(cursormotion,
+ "generate clicks for output characters that move the cursor or set screen attributes; default is 0 (no)");
+
+static int kmsg = 1;
+module_param(kmsg, int, 0);
+MODULE_PARM_DESC(kmsg,
+ "kernel warning/error message generates a sequence of tones to get your attension, default = 1 (yes)");
+
+static int sleep;
+module_param(sleep, int, 0);
+MODULE_PARM_DESC(sleep, "sleep between the clicks of the output characters - not yet implemented.");
+
+/*
+ * Here are some symbols that we export to other modules
+ * so they can turn clicks on and off.
+ * They correspond (roughly) to the module parameters given above.
+ */
+
+bool ttyclicks_on = true;
+EXPORT_SYMBOL_GPL(ttyclicks_on);
+bool ttyclicks_tty = true;
+EXPORT_SYMBOL_GPL(ttyclicks_tty);
+bool ttyclicks_kmsg = true;
+EXPORT_SYMBOL_GPL(ttyclicks_kmsg);
+
+/*
+ * Don't click the ansi escape sequences that move the cursor on the screen.
+ * However, esc [ ... H moves the cursor to a new line, and might be worth
+ * a cr sound. Not sure about that one.
+ * escState holds the state of the escape sequence.
+ * 0 regular output, 1 escape received, 2 escape [ received.
+ * A letter ends the escape sequence and goes from state 2 back to state 0.
+ */
+
+static char escState;
+
+/*
+ * Kernel alert messages don't go through tty, and you won't hear any clicks.
+ * You don't want clicks anyways; you want something more.
+ * You want an unusual noise that alerts you -
+ * there is a kernel message that requires your attention!
+ * When a newline comes through printk,
+ * I make a sound with up and down tones.
+ */
+
+static const short printk_sound[] = {
+ 730, 7, 760, 7, 790, 7, 760, 7, 730, 7, -1, 7, 0, 0
+};
+
+static void my_printk(struct console *cons, const char *msg, unsigned int len)
+{
+ char c;
+ while (len--) {
+ c = *msg++;
+ if (c == '\n' && ttyclicks_on & ttyclicks_kmsg)
+ ttyclicks_notes(printk_sound);
+ }
+} /* my_printk */
+
+static struct console clickconsole = {
+ .name = "tty clicks",
+ .write = my_printk,
+ .flags = CON_ENABLED,
+};
+
+/*
+ * Now for something subtle.
+ * I often hit caps lock by accident. Don't we all?
+ * But I can't tell the difference, until I've typed an entire paragraph
+ * in upper case.
+ * So it is helpful to hear a high beep every time a capital letter is echoed.
+ * It's easy to test for upper case,
+ * but when does an output character represent an echo of an input character?
+ * Don't count on tty cooked mode to tell you;
+ * lots of editors put the tty in raw mode,
+ * and then there's ssh (when you're working remotely), and so on.
+ * No - we need a more general approach.
+ * I watch the keystrokes as they come in, and give them time stamps.
+ * If an output letter matches an input letter, and not too much time
+ * has gone by, I call it an echo character.
+ * It's not perfect, but nearly so.
+ * That is the only reason to monitor keystrokes.
+ * If I didn't want echo capital letters to sound different,
+ * I wouldn't need a keyboard notifier at all.
+ * Retain up to 16 keystrokes with time stamps in jiffies
+ * in a circular buffer with head and tail pointers.
+ * kb notifier puts keys on at the head,
+ * and vt notifier takes keys off at the tail,
+ * matching them against vt output and checking for echo.
+ */
+
+#define ECHOEXPIRE 3 /* in seconds */
+#define ECHOMAX 16
+
+static struct {
+ char inkey;
+ unsigned long intime;
+} echochars[ECHOMAX];
+static int echead, ectail;
+
+/* char is displayed on screen; is it echo? */
+static int charIsEcho(char c)
+{
+ int j = ectail;
+ char d;
+
+ while (j != echead) {
+ if ((long)jiffies - (long)echochars[j].intime > HZ * ECHOEXPIRE) {
+/* this keystroke too old, discard it */
+ if (++j == ECHOMAX)
+ j = 0;
+ ectail = j;
+ continue;
+ }
+ d = echochars[j].inkey;
+ if (++j == ECHOMAX)
+ j = 0;
+ if (d != c)
+ continue;
+ ectail = j;
+ return 1;
+ }
+
+ return 0;
+} /* charIsEcho */
+
+static int
+keystroke(struct notifier_block *this_nb, unsigned long type, void *data)
+{
+ struct keyboard_notifier_param *param = data;
+ char key = param->value;
+ int downflag = param->down;
+ int shiftstate = param->shift;
+ int j;
+
+ /* if no sounds, then no need to do anything. */
+ /* Also, no raw, no control keys, and only down events. */
+ if (!(ttyclicks_on & ttyclicks_tty) ||
+ type != KBD_KEYSYM ||
+ param->vc->vc_mode == KD_GRAPHICS ||
+ downflag == 0 ||
+ key == 0 ||
+ shiftstate & 0xe)
+ return NOTIFY_DONE;
+
+ j = echead;
+ if (++j == ECHOMAX)
+ j = 0;
+/* typing ahead, is there room for this key in the echo buffer? */
+ if (j != ectail) {
+ echochars[echead].inkey = key;
+ echochars[echead].intime = jiffies;
+ echead = j;
+ }
+
+ return NOTIFY_DONE;
+} /* keystroke */
+
+/*
+ * Here's the deal about notifier priority.
+ * All this module does is make clicks as characters appear on screen.
+ * Any other module is probably going to be more important.
+ * In fact other modules may want to eat events before this one
+ * makes its noises. So give this module a low priority.
+ * 0 is default, so select a negative number.
+ */
+
+static struct notifier_block nb_key = {
+ .notifier_call = keystroke,
+ .priority = -70
+};
+
+/* Here are some routines to click the speaker, or make tones, etc. */
+/* In each case ttyclicks_on is the master switch. */
+
+/* intervals measured in microseconds */
+#define TICKS_CLICK 600
+#define TICKS_CHARWAIT 4000
+
+/* the sound of a character click */
+void ttyclicks_click(void)
+{
+ if (ttyclicks_on)
+ kd_mkpulse(TICKS_CLICK);
+} /* ttyclicks_click */
+EXPORT_SYMBOL_GPL(ttyclicks_click);
+
+void ttyclicks_cr(void)
+{
+ if (ttyclicks_on)
+ kd_mksteps(2900, 3600, 10, 10);
+} /* ttyclicks_cr */
+EXPORT_SYMBOL_GPL(ttyclicks_cr);
+
+/* Put a string of notes into the sound fifo. */
+void ttyclicks_notes(const short *p)
+{
+ if (ttyclicks_on)
+ kd_mknotes(p);
+} /* ttyclicks_notes */
+EXPORT_SYMBOL_GPL(ttyclicks_notes);
+
+void ttyclicks_steps(int f1, int f2, int step, int duration)
+{
+ if (ttyclicks_on)
+ kd_mksteps(f1, f2, step, duration);
+} /* ttyclicks_steps */
+EXPORT_SYMBOL_GPL(ttyclicks_steps);
+
+void ttyclicks_bell(void)
+{
+ static const short notes[] = {
+ 1800, 10, 0, 0
+ };
+ ttyclicks_notes(notes);
+} /* ttyclicks_bell */
+EXPORT_SYMBOL_GPL(ttyclicks_bell);
+
+/* Returns a time in microseconds to wait. */
+static int soundFromChar(char c, int minor)
+{
+ static const short capnotes[] = {
+ 3000, 3, 0, 0
+ };
+
+/* are sounds disabled? */
+ if (!ttyclicks_on)
+ return 0;
+
+ if (c == '\07') {
+ ttyclicks_bell();
+ return 0;
+ }
+
+ if (!ttyclicks_tty)
+ return 0;
+
+/* Don't click for background screens */
+ if (minor != fg_console + 1)
+ return 0;
+
+ if (c == '\n') {
+ ttyclicks_cr();
+ return 0;
+ }
+
+ if (charIsEcho(c) && c >= 'A' && c <= 'Z') {
+ ttyclicks_notes(capnotes);
+ return TICKS_CHARWAIT;
+ }
+
+/* Treat a nonprintable characterlike a space; just pause. */
+ if (c >= 0 && c <= ' ')
+ return TICKS_CHARWAIT;
+
+/* regular printable character */
+ ttyclicks_click();
+ return TICKS_CHARWAIT - TICKS_CLICK;
+} /* soundFromChar */
+
+/* Get char from the console, and make the sound. */
+static int
+vt_out(struct notifier_block *this_nb, unsigned long type, void *data)
+{
+ struct vt_notifier_param *param = data;
+ struct vc_data *vc = param->vc;
+ int minor = vc->vc_num + 1;
+ int usecs;
+ int unicode = param->c;
+ char c = param->c;
+
+ if (type != VT_PREWRITE)
+ goto done;
+
+ if (param->vc->vc_mode == KD_GRAPHICS)
+ goto done;
+ if (unicode >= 128) {
+ escState = 0;
+ goto done;
+ }
+
+ if (escState == 2) {
+ if (isalpha(c)) {
+ /* a letter indicates end of escape sequence. */
+ escState = 0;
+/* perhaps check for c == 'H' here? */
+ }
+ goto done;
+ }
+
+ if (!isdigit(c) && c != '?' && c != '#' && c != ';')
+ escState = 0;
+
+ if (escState == 1) {
+ if (c == '[') {
+ escState = 2;
+ goto done;
+ }
+ } else if (c == '\33' && !cursormotion) {
+ escState = 1;
+ goto done;
+ }
+
+ usecs = soundFromChar(c, minor);
+
+/*
+ * If it's the bell, I make the beep, not the console.
+ * This is the only char that this module eats.
+ */
+
+ if (c == 7)
+ return NOTIFY_STOP;
+
+ if (usecs)
+ udelay(usecs);
+
+done:
+ return NOTIFY_DONE;
+} /* vt_out */
+
+static struct notifier_block nb_vt = {
+ .notifier_call = vt_out,
+ .priority = -70
+};
+
+/* Load and unload the module. */
+
+static int __init click_init(void)
+{
+ int rc;
+
+ ttyclicks_on = enabled;
+ ttyclicks_tty = fgtty;
+ ttyclicks_kmsg = kmsg;
+
+ rc = register_vt_notifier(&nb_vt);
+ if (rc)
+ return rc;
+
+ rc = register_keyboard_notifier(&nb_key);
+ if (rc) {
+ unregister_vt_notifier(&nb_vt);
+ return rc;
+ }
+
+ register_console(&clickconsole);
+
+ return rc;
+} /* click_init */
+
+static void __exit click_exit(void)
+{
+ ttyclicks_on = 0;
+
+ unregister_console(&clickconsole);
+ unregister_keyboard_notifier(&nb_key);
+ unregister_vt_notifier(&nb_vt);
+} /* click_exit */
+
+module_init(click_init);
+module_exit(click_exit);
diff -uprN a/include/linux/ttyclicks.h b/include/linux/ttyclicks.h
--- a/include/linux/ttyclicks.h 2013-12-10 18:48:27.000000000 -0500
+++ b/include/linux/ttyclicks.h 2013-12-10 18:48:27.000000000 -0500
@@ -0,0 +1,17 @@
+#ifndef _LINUX_TTYCLICKS_H
+#define _LINUX_TTYCLICKS_H
+
+/* External prototypes for ttyclicks.c */
+/* See Documentation/accessibility/ttyclicks.txt for documentation. */
+
+extern bool ttyclicks_on; /* speaker sounds activated */
+extern bool ttyclicks_tty; /* speaker sounds from tty */
+extern bool ttyclicks_kmsg; /* speaker sounds from kernel message */
+
+void ttyclicks_click(void);
+void ttyclicks_cr(void);
+void ttyclicks_bell(void);
+void ttyclicks_notes(const short *notelist);
+void ttyclicks_steps(int start, int end, int step, int duration);
+
+#endif

\
 
 \ /
  Last update: 2013-12-11 01:41    [W:0.377 / U:0.648 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site