#!/usr/bin/perl -w

#
# morsecat.pl
#
# Jacek Fedorynski <jfedor@jfedor.org>
# http://www.jfedor.org/
# 2001-11-15
#
# Displays a text file in Morse code using a LED on your keyboard.
#
# (as in the book Cryptonomicon by Neal Stephenson)
#
# If the DISPLAY environment variable is set we will use xset to control
# the LED. If it's not set we will use setleds (which is probably very
# Linux-specific). To make it work under X I had to change the InputDevice
# section for the keyboard in /etc/X11/XF86Config-4 to look like this
# (the side effect was that my xmodmap keyboard mappings stopped working):
#
# Section "InputDevice"
#         Identifier  "Keyboard0"
#         Driver      "keyboard"
#         Option      "Xleds" "1 2 3"
#         Option      "XkbDisable"
# EndSection
#
# If you know a way to control keyboard LEDs (or other LEDs) on other platforms
# please let me know. If you think there's something wrong with the Morse
# code this program generates also let me know.
#
# Have fun!
#

use strict;

my $UNIT_TIME = 0.12;		# length of a single dot in seconds

my %code = (
	'A' => [0, 1],
	'B' => [1, 0, 0, 0],
	'C' => [1, 0, 1, 0],
	'D' => [1, 0, 0],
	'E' => [0],
	'F' => [0, 0, 1, 0],
	'G' => [1, 1, 0],
	'H' => [0, 0, 0, 0],
	'I' => [0, 0],
	'J' => [0, 1, 1, 1],
	'K' => [1, 0, 1],
	'L' => [0, 1, 0, 0],
	'M' => [1, 1],
	'N' => [1, 0],
	'O' => [1, 1, 1],
	'P' => [0, 1, 1, 0],
	'Q' => [1, 1, 0, 1],
	'R' => [0, 1, 0],
	'S' => [0, 0, 0],
	'T' => [1],
	'U' => [0, 0, 1],
	'V' => [0, 0, 0, 1],
	'W' => [0, 1, 1],
	'X' => [1, 0, 0, 1],
	'Y' => [1, 0, 1, 1],
	'Z' => [1, 1, 0, 0],
	'0' => [1, 1, 1, 1, 1],
	'1' => [0, 1, 1, 1, 1],
	'2' => [0, 0, 1, 1, 1],
	'3' => [0, 0, 0, 1, 1],
	'4' => [0, 0, 0, 0, 1],
	'5' => [0, 0, 0, 0, 0],
	'6' => [1, 0, 0, 0, 0],
	'7' => [1, 1, 0, 0, 0],
	'8' => [1, 1, 1, 0, 0],
	'9' => [1, 1, 1, 1, 0],
	' ' => [2]
);

sub mysleep {
	my ($time) = @_;
	select(undef, undef, undef, $time);
}

sub turn {
	my ($on) = (@_);
	if (defined $ENV{DISPLAY}) {
		system("xset ".($on ? "" : "-")."led 2");
	} else {
		system("setleds -L ".($on ? "+" : "-")."num");
	}
}

my $buf;

my $filename = $ARGV[0] || "-";

open(INPUT, "< $filename") or die "cannot open input file";

while (sysread INPUT, $buf, 1) {
	if (defined $code{uc($buf)}) {
		for my $i (@{$code{uc $buf}}) {
			turn(1) unless $i == 2;
			if ($i == 0) {
				mysleep($UNIT_TIME);		# dot
			} elsif ($i == 1) {
				mysleep($UNIT_TIME * 3);	# dash
			} elsif ($i == 2) {
				mysleep($UNIT_TIME * 3);	# word space
			}
			turn(0);
			mysleep($UNIT_TIME);
		}
		mysleep($UNIT_TIME * 2);			# letter space
	}
}
