You'll have to do this separately for X and for every kind of text console you care about.
If you want to switch between arbitrary keymaps, you need to maintain state, as typically the console won't do it for you. That's what layout switchers such as Gnome's and KDE's do. But if you only want to switch between some well-known keymaps, you can query the console (assuming this feature is available on your console type), obtain the current code sent by a key and decide what to switch to based on that. Here are a few possibilities:
- If keycode 24 (the number will vary between console types) currently sends
q, the current keymap looks like qwerty, so switch to dvorak, otherwise switch to qwerty.
- If Q and W have adjacent keycodes, it's probably a qwerty layout.
- Add a fake key in the keymaps and look up its binding.
Here's an implementation for X and Linux, using the W=Q+1 test. Beware, I haven't tested it. Note that the script will usually not be usable through text mode remote logins.
if [ -n "$DISPLAY" ]; then
if xmodmap -pke | awk '$3=="=" && $4=="q" {q=$2}
$3=="=" && $4=="w" {w=$2}
END {exit w-q==1}'; then
setxkbmap us
else
setxkbmap dvorak
fi
elif [ "$TERM" = "linux" ]; then
if dumpkeys | awk '$3=="=" && $4=="q" {q=$2}
$3=="=" && $4=="w" {w=$2}
END {exit w-q==1}'; then
loadkeys us
else
loadkeys dvorak
fi
fi
On X Window, the best place to bind this to a key is using your desktop environment or window manager's key binding feature. You can make the key execute the X-specific part only. Of course, if your DE/WM has a layout switching feature, you might as well use it.
On the Linux console, the possibilities for key bindings are very limited. You can bind a key to the KeyboardSignal symbol. If you're using SysVinit, put kb::kbrequest:/path/to/script in /etc/inittab to run your script (as root!) when the signal is pressed. I don't know how to configure this key's action with Upstart.