###Idea Both me and my wife watch Game of Thrones weekly, although I do it with much less fidgeting, sighing and dissatisfied head shaking, since I haven’t read the books. So, the other day, I saw Oatmeal’s comic on the subject and immediately a wicked thought moved into the apartment of my mind, made some popcorn, put on a pair of dirty old sweatpants and sat down in front of the TV. And when they do that, there’s just no kicking them out. I started wondering if I can put together an app that will make my wife’s laptop be able to type only the word “hodor”, no matter which keys on the keyboard she presses or which application is currently active. Well, the specification sounded simple enough, but many a night had been lost on making just such specs come to life. Never the less, I thought I’d give it a try as a weekend project, the result can be seen on github, and here’s the story:
###Research I knew from the start that I wanted to do this in C#, but if I wanted to intercept everything that’s being typed on the keyboard in every application, I had to get down to a level that is lower than .NET. So, that’s where Windows API and pinvoke comes into play. With a bit of googling, I figured out that I’ll basically need four Windows API .dll exports:
- hooks up our delegate to an event
- as the name says, sends input to the system
- is called from the hook delegate to basically say: “proceed with this event as planned”
- you guessed it, unhooks our delegate from the event
###Proof of Concept The first thing I decided to make was a small console application and the logic behind it was pretty simple and straightforward: we intercept the keypress using SetWindowsHookEx and block it, and then we substitute it with our own using SendInput. There is only one little problem. Our delegate is hooked up to be called every time a keypress event is caught. This means that it will also be executed when we call SendInput. So, when the key is pressed, our handler gets called, which calls SendInput, which triggers our handler again and so on… To avoid this, we’ll just use a flag to let our handler know that the keypress came from the SendInput method and not from the user. Most of the magic can be found in the Hodor.Libraries project, in the WinApiInputIntercept class:
As for the SendInput:
I won’t go into details about INPUT, KEYBDINPUT, ScanCodeShort, VirtualKeyShort etc. here, because they are structs and enums almost completely copy-pasted from the pinvoke site. Basically, the above code creates a keyboard input and sends it to the system. So, to sum up, when a keyboard event is detected, we handle it by suppressing it, selecting the appropriate letter from the string ‘hodor ‘ and sending it to the system instead.
Now that the logic is in the library project, the console app’s main method looks very simple:
And, after I’ve built and started the console application, it worked! No matter which application was in focus, no matter which key I pressed, all I got on the screen was ‘hodor hodor hodor…’ . Great, but as soon as it worked, I got another evil idea.
###Plot Thickens Now, I could have simply made the console application invisible, sneak over to the wife’s laptop, start it there and then wait in the corner and giggle like a little girl, but no… How about if I remotely start and stop the hodorization from my computer. Now that would be something. So that would mean a server and a client app. OK, I can make that. A simple WPF client that invokes two methods, one to start and one to stop the hodorization. And on the server side, a WCF service will do just fine, and since all of the logic I need is already contained in the Library project, it should be a breeze. And so it was. In the Hodor.Service project, you can find a WCF service library which implements two methods that we need:
As you can see, this code looks a lot like the console application, with two exceptions:
- Dehodorize method
- Wrapping stuff in a Task.Run()
Dehodorize is, I think, self-explanatory, but let’s take a closer look at that Task.Run(). If you ever had a chance to work with async-await, you already understand what’s going on here, but for anyone that’s seeing this for the first time: this Task.Run() is basically just running the code inside the curly braces in a separate thread. And why would we all of a sudden need threading here? Well, what the Application.Run() method is saying to our program is “now sit tight here and wait and listen for system messages”. And like that, it blocks the main thread of the app at that line. But, if we want our service to react when we send it a call to Dehodorize() we need it to not be blocked. So, we run that part in a separate thread and our main thread is free to wait for further calls from the client. And, since both threads still belong to the same process, when we receive a Dehodorize call, executing Aplication.Exit() will tell our blocked thread to unblock and proceed execution, which will in turn unhook our handler from keyboard input. Sounds simple enough, right? Well, hosting the service library in Visual Studio’s WCF service host and running it confirms that it works. Now comes the question of hosting the library in some kind of process.
###Problems Here is where I ran into the first big problem. My first choice for hosting the service was a windows service. I can easily just install it on the laptop, make it automatically start every time it boots and my job would be done. So I did all that and realized that it simply doesn’t work. The reason is very simple. In Windows 7, windows services do not have access to keyboard input events. You can hook up the handler without exception, but it simply never gets called. Considering that this is how many keyloggers work, it’s not an unsound logic, but forces me to host the WCF in a console application. And this introduces two new problems: the application needs to be invisible and it needs to be automatically started when the system is rebooted.
###Solutions The invisibility problem is easily solved. There are several ways to make a console app not visible to a user, but my favourite is to, once a console project is created in visual studio, go to properties of the project and from the Output Type drop-down choose Windows Application instead of Console Application. Now, for the second problem, it’s going to take a bit more effort.
To be honest, the only viable solution I could come up with for starting the application on system boot was the task scheduler. It has the option to execute a process on boot, retry on fail and run it as administrator. But now, the whole setup seems to be getting complicated. Lets see, after building the application, we need to copy it to the computer, check if it’s working, go into task scheduler, fill out a bunch of options etc. So, the next solution, whose job was to try and simplify all the previous solutions, was to make an installer. Imagine my surprise when I found out that Visual Studio 2013 does not have an installer project. Luckily for me, the community already made a lot of fuss about this and Microsoft responded by making it possible to add this functionality in the form of an extension. Creating the installer was a rather elementary process and I won’t go into details here. So, the only remaining thing was scheduling the task on boot. Turns out, there is a library for that, too, the Task Scheduler Managed Wrapper. I made a little application that schedules our console service host to be started on user logon:
And all that was left was to add this scheduler to be executed in the installer. This can be done in Visual Studio, by right-clicking on the installer project and selecting View -> Custom Actions:
and then adding custom action to Install:
###Epilogue I had fun remotely switching hodorization on and off on my wife’s laptop as she was starting to cope with the possibility that she was slowly sinking into madness, but truth be told, I had much more fun making the Hodorizer. I think it was also a good example of something I suspect every programmer comes to face sooner or later - a one line specification suddenly growing to include several projects - a service library, a service host, a client app, a scheduler and an installer. All in all, I thought it made a nice weekend project and a cool story to tell my grandchildren. Well, OK, a nice weekend project.