With zsh, it should just be a matter of:
precmd() print -z 'foo '
Or to avoid it overriding commands queued by the user with Alt+q:
zle-line-init() { [ -n "$BUFFER" ] || LBUFFER='foo '; }
zle -N zle-line-init
With bash, instead of xdotool, you could use the TIOCSTI ioctl:
insert() {
perl -le 'require "sys/ioctl.ph";
ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV' -- "$@"
}
PROMPT_COMMAND='insert "foo "'
It's preferable to xdotool because it's inserting those characters directly in the input buffer of the device bash is reading from. xdotool would only work if there's a X server running (wouldn't work on the console or real terminals or over ssh (without -X) for instance), that it is the one identified by $DISPLAY and that's the one you're interacting with, and that the terminal emulator bash is running in has the focus when $PROMPT_COMMAND is evaluated.
Now, like in your xdotool case, because it'sthe ioctl() is done before the prompt is displayed and the tty terminal line discipline is put out of icanon without echoicanon+echo mode by readline, you're likely to see the echo of that foo by the tty line discipline messing up the display.
You could work around that by instead inserting a string whose echo is invisible (like U+200B if using exclusively Unicode locales) and bind that to an action that inserts "foo ":
insert() {
perl -le 'require "sys/ioctl.ph";
ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV' -- "$@"
}
bind $'"\u200b":"foo "'
PROMPT_COMMAND="insert $'\u200b'"
Or you could delay the TIOCSTI ioctl enough for readline to have time to initialise and disable:
insert_with_delay() {
perl -le 'require "sys/ioctl.ph";
$delay = shift @ARGV;
unless(fork) {
select undef, undef, undef, $delay;
ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV;
}' -- "$@";
}
PROMPT_COMMAND='insert_with_delay 0.05 "foo "'