/********************************************************************* pcclicks.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? It provides 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. Copyright (C) Karl Dahlke, 2008. This software may be freely distributed under the GPL, general public license, as articulated by the Free Software Foundation. This module uses notifiers, and will not work with kernels prior to 2.6.24. Type `uname -a` to find your kernel version. Compile this as a lkernel module on your system. Then run insmod on the resulting kernel object. *********************************************************************/ #include #include #include #include #include #include /* for fg_console */ #include #include #include /* for inb() outb() */ #ifndef VT_PREWRITE #define VT_PREWRITE VT_WRITE #endif /* We aren't yet running under a kernel with Karl's prewrite patch... */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Karl Dahlke - eklhad@gmail.com"); MODULE_DESCRIPTION ("Console output generates clicks, resembling a mechanical teletype."); /********************************************************************* The module parameter src can be set to p or w. This is the source of the clicks. p stands for prewrite; just before the characters are written to the console. This is the default. w stands for write; clicks accompany characters on the screen. There is only one real difference between the two sources. Suppose your program cranks out 100 letters at one go. If you have set src=p, you will hear 100 clicks. But if you have set src=w, you will hear 80 clicks, then the swoop of the carriage return, then 20 more clicks. The return is an artifact of screen wrap, and is not part of the program's output. Other than that, you probably won't notice any difference. *********************************************************************/ static char *src = "p"; module_param(src, charp, 0); MODULE_PARM_DESC(src, "Source of the clicks; p (prewrite) or w (write)"); /********************************************************************* Here is a symbol that we export to other modules, so they can turn these clicks on and off. *********************************************************************/ bool pcclicks_on = true; EXPORT_SYMBOL_GPL(pcclicks_on); /********************************************************************* 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. Isn't that frustrating? So it is helpful to hear a high beep every time a capital letter is echoed. 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. 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. What follows is kinda complicated, but it actually works. *********************************************************************/ #define MAXKEYPENDING 24 static char inkeybuffer[MAXKEYPENDING]; static unsigned long inkeytime[MAXKEYPENDING]; static short nkeypending; /* number of keys pending */ static int keyminor; /* minor number of tty where the keystrokes came from */ #define ECHOEXPIRE 3 /* in seconds */ static DEFINE_SPINLOCK(keybuflock); /* Drop remembered keystrokes prior to mark */ static void dropKeysPending(int mark) { int i, j; for(i = 0, j = mark; j < nkeypending; ++i, ++j) { inkeybuffer[i] = inkeybuffer[j]; inkeytime[i] = inkeytime[j]; } nkeypending -= mark; } /* dropKeysPending */ /* char is displayed on screen; is it echo? */ static int charIsEcho(char c) { int rc = 0; char d; int j; /* Do the high runner case first. */ if(!nkeypending) return 0; spin_lock(&keybuflock); for(j = 0; j < nkeypending; ++j) { d = inkeybuffer[j]; if(d == c && inkeytime[j] + HZ * ECHOEXPIRE >= jiffies) break; } if(j < nkeypending) { rc = 1; ++j; } dropKeysPending(j); spin_unlock(&keybuflock); return rc; } /* charIsEcho */ static int keystroke(struct notifier_block *this_nb, unsigned long type, void *data) { struct keyboard_notifier_param *param = data; char key = param->value; struct vc_data *vc = param->vc; int minor = vc->vc_num + 1; int downflag = param->down; int shiftstate = param->shift; spin_lock(&keybuflock); /* Sorry, this doesn't work in unicode. */ if(type != KBD_KEYSYM) goto done; /* Only the key down events */ if(downflag == 0) goto done; if(key == 0) goto done; /* no control or alt keys */ if(shiftstate & 0xe) goto done; /* If we changed consoles, clear the pending keystrokes */ if(minor != keyminor) { nkeypending = 0; keyminor = minor; } /* make sure there's room, then push the key */ if(nkeypending == MAXKEYPENDING) { int j; for(j = 1; j < nkeypending; ++j) { inkeybuffer[j - 1] = inkeybuffer[j]; inkeytime[j - 1] = inkeytime[j]; } --nkeypending; } inkeybuffer[nkeypending] = key; inkeytime[nkeypending] = jiffies; ++nkeypending; done: spin_unlock(&keybuflock); 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 = -100 }; /********************************************************************* Here are some routines to click the speaker, or pause, or make tones, etc. *********************************************************************/ /* intervals, measured in microseconds */ #define TICKS_CLICK 600 #define TICKS_CHARWAIT 4200 #define TICKS_TOPCR 260 #define TICKS_BOTCR 60 #define TICKS_INCCR 2 #define PORT_SPEAKER 0x61 #define PORT_TIMERVAL 0x42 #define PORT_TIMER2 0x43 static DEFINE_SPINLOCK(speakerlock); /* togle the inbuilt speaker */ static void spk_toggle(void) { unsigned char c; int flags; spin_lock_irqsave(&speakerlock, flags); c = inb(PORT_SPEAKER); /* cannot interrupt a tone with a click */ if((c & 3) != 3) { c &= ~1; c ^= 2; outb(c, PORT_SPEAKER); } spin_unlock_irqrestore(&speakerlock, flags); } /* spk_toggle */ /* pause this many microseconds */ static struct timeval last_tv; static void suspend(int usec) { struct timeval tv; /* Add offset to the last time marked. */ last_tv.tv_usec += usec; if(last_tv.tv_usec >= 1000000) { last_tv.tv_usec -= 1000000; ++last_tv.tv_sec; } do_gettimeofday(&tv); if(tv.tv_sec > last_tv.tv_sec || (tv.tv_sec == last_tv.tv_sec && tv.tv_usec > last_tv.tv_usec)) { /* Apparently last_tv was long ago. */ /* Perhaps we haven't displayed text in a while, */ /* or perhaps we were swapped out for a while. */ last_tv = tv; /* structure copy */ return; } /* adjusting last_tv, because it was too old. */ /* If we set the clock back an hour, for daylight savings time, * and we don't take precautions here, we'll be sitting for an hour. */ if(last_tv.tv_sec > tv.tv_sec + 2) { last_tv = tv; /* structure copy */ return; } do { do_gettimeofday(&tv); } while(tv.tv_sec < last_tv.tv_sec || (tv.tv_sec == last_tv.tv_sec && tv.tv_usec < last_tv.tv_usec)); } /* suspend */ /* the sound of a character click */ void pcclicks_click(void) { do_gettimeofday(&last_tv); spk_toggle(); suspend(TICKS_CLICK); spk_toggle(); } /* pcclicks_click */ EXPORT_SYMBOL_GPL(pcclicks_click); void pcclicks_cr(void) { int i; for(i = TICKS_TOPCR; i > TICKS_BOTCR; i -= TICKS_INCCR) { spk_toggle(); suspend(i); } } /* pcclicks_cr */ EXPORT_SYMBOL_GPL(pcclicks_cr); /********************************************************************* Push notes onto a sound fifo and play them via an asynchronous thread. *********************************************************************/ #define SF_LEN 32 /* length of sound fifo */ static short sf_fifo[SF_LEN]; static short sf_head, sf_tail; /* Pop the next sound out of the sound fifo. */ static void popfifo(unsigned long); static struct timer_list note_timer = TIMER_INITIALIZER(popfifo, 0, 0); static void popfifo(unsigned long notUsed) { short i, freq, duration; spin_lock(&speakerlock); /* Apparently it's ok to delete a timer that has expired, */ /* or has never been added in the first place. */ del_timer(¬e_timer); if((i = sf_tail) == sf_head) { /* turn off singing speaker */ outb(inb_p(PORT_SPEAKER) & 0xFC, PORT_SPEAKER); goto done; /* sound fifo is empty */ } /* First short holds the frequency */ freq = sf_fifo[i++]; if(i == SF_LEN) i = 0; /* wrap around */ duration = sf_fifo[i++]; if(i == SF_LEN) i = 0; sf_tail = i; mod_timer(¬e_timer, jiffies + duration * (HZ / 100)); if(freq < 0) { outb(inb_p(PORT_SPEAKER) & 0xFC, PORT_SPEAKER); } else { duration = 1193180 / freq; outb_p(inb_p(PORT_SPEAKER) | 3, PORT_SPEAKER); /* set command for counter 2, 2 byte write */ outb_p(0xB6, PORT_TIMER2); outb_p(duration & 0xff, PORT_TIMERVAL); outb((duration >> 8) & 0xff, PORT_TIMERVAL); } done: spin_unlock(&speakerlock); } /* popfifo */ /* Put a string of notes into the sound fifo. */ void pcclicks_notes(const short *p) { short i; spin_lock(&speakerlock); i = sf_head; /* Copy shorts into the fifo, until the terminating zero. */ while(*p) { sf_fifo[i++] = *p++; if(i == SF_LEN) i = 0; /* wrap around */ if(i == sf_tail) { /* fifo is full */ spin_unlock(&speakerlock); return; } } sf_head = i; spin_unlock(&speakerlock); /* first sound, get things started. */ if(!timer_pending(¬e_timer)) popfifo(0); } /* pcclicks_notes */ EXPORT_SYMBOL_GPL(pcclicks_notes); void pcclicks_bell(void) { static const short notes[] = { 1800, 10, 0, 0 }; pcclicks_notes(notes); } /* pcclicks_bell */ EXPORT_SYMBOL_GPL(pcclicks_bell); static void soundFromChar(char c, int minor) { int isecho = charIsEcho(c); static const short capnotes[] = { 3000, 3, 0, 0 }; /* are sounds disabled? */ if(!pcclicks_on) return; /* sound the bell on background screens, but nothing else */ if(c != '\07' && minor != fg_console + 1) return; if(c == '\07') { pcclicks_bell(); return; } if(c == '\n') { pcclicks_cr(); return; } if(isecho && c >= 'A' && c <= 'Z') { pcclicks_notes(capnotes); return; } /* I don't know what to do with nonprintable characters. */ /* I'll just pause, like they are spaces. */ if(c >= 0 && c <= ' ') { suspend(TICKS_CHARWAIT); return; } /* regular printable character */ suspend(TICKS_CHARWAIT - TICKS_CLICK); pcclicks_click(); } /* soundFromChar */ /********************************************************************* Get char from the console, and make the sound. *********************************************************************/ static int vt_event = VT_PREWRITE; /* what are we looking for */ 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; if(type == vt_event) soundFromChar(param->c, minor); /* * If it's the bell, I make the beep, not the console. * This is the only char that this module eats. * Although eating char events is facilitated by VT_PREWRITE, * which is not available until 2.6.26. */ if(param->c == 7) return NOTIFY_STOP; return NOTIFY_DONE; } /* vt_out */ static struct notifier_block nb_vt = { .notifier_call = vt_out, .priority = -100 }; /********************************************************************* Load and unload the module. *********************************************************************/ static int __init checkinit(void) { int rc; rc = register_vt_notifier(&nb_vt); if(rc) return rc; rc = register_keyboard_notifier(&nb_key); if(rc) { unregister_vt_notifier(&nb_vt); } if(src[0] == 'w') vt_event = VT_WRITE; return rc; } /* checkinit */ static void __exit checkexit(void) { unregister_keyboard_notifier(&nb_key); unregister_vt_notifier(&nb_vt); } /* checkexit */ module_init(checkinit); module_exit(checkexit);