KovaaK has asked for the wisdom of the Perl Monks concerning the following question:

Hi guys,

A small pet project of mine that I am working on is writing a set of scripts that duplicates certain keyboard input from one computer to another. I got the server and client set up and running, and I can enter input from the server and have the client receive it.

The catch is that I want it to be able to both send and receive input while the windows aren't in focus. Through searching on this site, I found that someone recommended Win32::GuiTest for sending keyboard events without focus, and that is working great, but I haven't found anything for receiving keyboard events without focus.

Just incase anyone is curious, I'm writing this for use of running two characters in WoW - I want the "1" key to trigger on both computers when I press it on one. I'm fully aware that reading input while the program isn't in focus is exactly what is necessary to write a keylogger, but there are plenty of other legitimate uses for such functionality (global hotkeys being the first example to come to mind), so I expected something like this to be available, but I haven't come across it yet. Is there some package that does this?

Here is my current code... (not that it isn't functioning as I currently intend - I'm just missing a better alternative to ReadKey)

server:
#!/usr/bin/perl -w use strict; use IO::Socket; use Term::ReadKey; ReadMode 1; my ($PORT, $server, $client); $PORT = 9000; # pick something not in use $server = IO::Socket::INET->new( Proto => 'tcp', LocalPort => $PORT, Listen => SOMAXCONN, Reuse => 1 ); die "can't setup server" unless $server; print "[Server accepting clients]\n"; while ($client = $server->accept()) { $client->autoflush(1); print "Received connection!\n"; while (1) { my ($key); while (not defined ($key)) { $key = ReadKey(-1); } print "Get key $key\n"; print $client "$key\n"; $key = ReadKey(-1); #clear $key } } close $client; ReadMode 0;


client:
#!/usr/bin/perl -w use strict; use IO::Socket; use Win32::GuiTest qw(:ALL); my $host = shift || 'localhost'; my $port = shift || '9000'; my ($kidpid, $socket, $line); # create a tcp connection to the specified host and port $socket = IO::Socket::INET->new( Proto => 'tcp', PeerAddr => $host, PeerPort => $port ) or die "can't connect to port $port on $host: $!"; print "[Connected to $host:$port]\n"; while (1) { while (defined ($line = <$socket>)) { sleep(1); chomp($line); SendKeys($line); print "$line\n"; } }

Replies are listed 'Best First'.
Re: Reading keyboard input without focus
by Mr. Muskrat (Canon) on Nov 24, 2008 at 20:42 UTC
    You might be able to accomplish this with Win32::Console's PeekInput method. It should provide some ideas any way.
      Interesting... I don't think I'll be able to take it much further, but this is what I found out:
      use Win32::Console; $StdIn = new Win32::Console(STD_INPUT_HANDLE); my $key; while (!(defined @event)) { @event = $StdIn->PeekInput(); #@event = $StdIn->Input(); $key = chr($event[5]); } print join(" ", @event),"\n"; print "Got key $key!\n";
      Using the commented line of Input, the program works as expected and only accepts input when in focus. Using PeekInput will work if you type something while in focus, but only if you haven't typed something in another window after starting the program.

      Try running that code above, then after starting the program, type "a" into another window, then go back to the prompt. It won't get out of the loop... (Input doesn't have this issue).

      I'm guessing this is a dead-end though. PeekInput seems to be useful for reading the input without removing it from the buffer, not necessarily reading from a buffer when the program isn't in focus.

      Nonetheless, thanks for the suggestion.
Re: Reading keyboard input without focus
by lostjimmy (Chaplain) on Nov 24, 2008 at 21:23 UTC

    I think what you're in need of are global mouse and keyboard hooks. I've done this in a number of programs in c, but I doubt it will be as easy in Perl.

    Here is the traditional steps to take when writing a global mouse (or keyboard) hook in c

    1. Create a DLL which has an exported function (e.g. createMouseHook(HWND h)). This DLL needs to have a section of shared memory which will be used to store the HWND of the calling program. This function calls SetWindowsHookEx, using WH_MOUSE or WH_KEYBOARD as the hook type. The callback specified must also be in this DLL.
    2. Within the mouse hook callback, use SendMessage to send a WM_COPYDATA message to the window whose HWND was stored during createMouseHook. In a COPYDATASTRUCT struct, store the necessary information to be passed to the program.
    3. In your main program, in your WndProc function, when you encounter a WM_COPYDATA message, do the appropriate processing for that message

    These things are fairly trivial to do, but I've never attempted anything like it in Perl. Anyone know if there is an easier way to do this?

      Ah hah! Thanks to googling for "perl keyboard hook", I came across this! I'm surprised that I didn't think of the word "hook" for this - would have saved me some trouble. This should do the trick once I get some time to sort through it. Thanks much for the lead :)!

        Unfortunately all that code does is register a hot key. It's just a windows API call that enables a program to receive global hot keys (e.g. CTRL+ALT+Z). It won't allow your program to receive all keyboard input.

        There is a good essay on how to set up a global mouse hook over at the code project

Re: Reading keyboard input without focus
by Mr. Muskrat (Canon) on Nov 24, 2008 at 20:31 UTC
      Yes, that is a common term for it - I have been doing this since 2000 in EverQuest. However, I wanted to try to mess with Perl and learn something (which I already have in looking through the Sockets examples I found online) so I could build my own functionality with it from there.
Re: Reading keyboard input without focus
by KovaaK (Initiate) on Nov 24, 2008 at 20:22 UTC
    Eep, I almost forgot to mention - both computers are running Windows XP SP2.