Sunday, September 13, 2009

"Spotify Friends" Part 2

Part 2. Controlling Spotify playback by injecting keyboard messages

As we disscussed in Part 1, a Windows application has a WndProc that receives messages such as WM_KEYDOWN from the operating system when the user interacts with the window. Now we want to send those messages ourselves to simulate the user pressing shortcuts (such as [space] for play/pause).

We first prepare by spying on the Spotify window with Spy++ (a tool that comes with Microsoft Visual Studio). Attach to Spotify using Spy++ and filter out just the keyboard messages. Now when you press the shortcut keys in Spotify you will see exactly what messages were sent from the OS. This happens when we press [space]:



To send our own [space] message we do the following:
  • Find the Handle of the Spotify window
  • Send the message using PostMessage( .. ) in user32.dll
Finding the Handle (and getting current song) of the Spotify window in C#

We now search for all processes called Spotify. When we find the right process we can get both the handle we are looking for and the title of the main window - which shows what song (if any) is currently playing. If we cannot find the process then we assume Spotify is not running.

spotifyWindow = 0;

Process[] processes = Process.GetProcessesByName("Spotify");
foreach (Process proc in processes)
{
    if (!proc.MainModule.FileName.EndsWith("spotify.exe")) continue;

    if (proc.MainWindowTitle.Length >= 10)
    {
        NowPlaying = proc.MainWindowTitle.Substring(10);
        SpotifyStatus = AppStatus.Playing;
    }
    else
    {
        NowPlaying = "";
        SpotifyStatus = AppStatus.Paused;
    }

    spotifyWindow = (int)proc.MainWindowHandle;
}

if (spotifyWindow == (int)IntPtr.Zero)
    SpotifyStatus = AppStatus.NotRunning;


Sending messages to the Spotify window

Sending [space] press is just a matter of copycatting the messages we saw in Spy++. But first we need to import the PostMessage( .. ) from user32.dll.
[DllImport("USER32.DLL")]

static extern int PostMessage(

    int hwnd,

    int msg,

    int character,

    uint count);



If you are in a WPF project this is a good time to - like I said at the end of Part 1 - add a reference to System.Windows.Forms and add:

using FormsNS = System.Windows.Forms

If you have a Form application you just use Keys directly. To send [space] we do this:
const int WM_KEYDOWN = 0x100;

const int WM_KEYUP = 0x101;

if (spotifyWindow != 0)

{

     PostMessage(spotifyWindow, WM_KEYDOWN, (int)FormsNS.Keys.Space, 1);

     Thread.Sleep(100);

     PostMessage(spotifyWindow, WM_KEYUP, (int)FormsNS.Keys.Space, 1);

}

This time we get away with putting any integer as the last argument in PostMessage, but we will have to be more precise later on when we are sending the more complex shortcuts (like Ctrl+Right).

Sending Ctrl + Key to Spotify

If we look in Spy++ it seems like we can get away with simulating both the [ctrl] and the [right] key at the same time, but that will not work. My theory is that when Spotify gets the [right] key it will look at the keyboard to see if [ctrl] is pressed (because this is easier to do). Our messages so far will not fool Spotify if it looks at the state of the keyboard - so we now need to choose one out of two solutions:
  1. Force ourselves to use [ctrl] + [any other keys] in our global hot-key combo (Spotify will see that [ctrl] is pressed when we send [right] for example)

    or
  2. Use other key combos, and fake that [ctrl] is pressed
If you are satisfied with choice 1 you just need to send the [right] (etc.) keys correctly and you are done. For that reason we will first look at how we send those keys, then look at how we can spoof the [ctrl] key.


Sending [up], [down], [left] or [right]

This time we need to get the last argument in PostMessage correct. We press [ctrl] + [right] in Spotify while spying with Spy++. Then we right-click and look at the Properties on the WM_KEYDOWN message that has VK_RIGHT in it (should be the second one if you cleared the log). In Properties we first see wParam which is the code for the key - (int)FormNS.Keys.Right in this example. We also see the lParam which we need to send as the last argument in PostMessage.

But I have already done that work for you. Here you go:
PostMessage(spotifyWindow, WM_KEYDOWN, (int)FormsNS.Keys.Right, 0x014D0001);
PostMessage(spotifyWindow, WM_KEYUP, (int)FormsNS.Keys.Right, 0xC14D0001);

PostMessage(spotifyWindow, WM_KEYDOWN, (int)FormsNS.Keys.Left, 0x014B0001);
PostMessage(spotifyWindow, WM_KEYUP, (int)FormsNS.Keys.Left, 0xC14B0001);

PostMessage(spotifyWindow, WM_KEYDOWN, (int)FormsNS.Keys.Up, 0x01480001);
PostMessage(spotifyWindow, WM_KEYUP, (int)FormsNS.Keys.Up, 0xC1480001);

PostMessage(spotifyWindow, WM_KEYDOWN, (int)FormsNS.Keys.Down, 0x01500001);
PostMessage(spotifyWindow, WM_KEYUP, (int)FormsNS.Keys.Down, 0xC1500001);



NOTE: Put in a Thread.Sleep(100) between each key you send. I guess the application will not react if the keys are pressed for an extremely short timespan.
If you choose to have [ctrl] in your global hot-keys you now know all you need to know to control Spotify's playback, volume and get the currently playing song.


Faking the [ctrl] key being pressed

To set the [ctrl] key as down or up we need to import the following method in user32.dll:
[DllImport("user32.dll", EntryPoint = "keybd_event", CharSet = CharSet.Auto, ExactSpelling = true)]

static extern void keybd_event(byte vk, byte scan, int flags, int extrainfo);

To press down we pull this one-liner:
keybd_event((byte)FormsNS.Keys.ControlKey, 0x1D, 1, 0);


And to release [ctrl]:
keybd_event((byte)FormsNS.Keys.ControlKey, 0x1D, 1 | 2, 0);

Using these you can now have global hot-keys such as [alt] + [up] (volume up) and [alt] + [right] (next song). When Spotify checks to see the status of the [ctrl] key it will see it as being pressed.

Summary

Looking back we can sum up what we need to do to send key-presses to another window:
  • Get the Handle of the window. The Handle is used by the Windows GUI manager to refer to uniquely identify windows (and other controls).
  • Send the correct messages via PostMessage. Note that the last argument might be important. In that case we can easily get it with Spy++.
  • If we do not want to hold down [ctrl], but we want the application to think [ctrl] is being held down, then we probably need to use keyd_event( .. ) to set [ctrl]

Saturday, September 12, 2009

C# / WPF project: "Spotify Friends" Part 1

Let's kick it off with a mixture of C# with WPF and (kind of) C++.
I'll start by saying; this project is in no way associated with or endorsed by the amazingly great music service Spotify. This project has no intent of misusing the Spotify trademark or in any other way step on anyones toes. "Spotify Friends" is just an internal code-name.

Overview


"Spotify Friends" is a [client(s) --> server] project that lets users see what their friends are listening to at the moment. The client also adds the functionallity of controlling the playback with global hot-keys; changing the song or volume while playing games or surfing - without opening the Spotify window.


Part 1. Global keyboard hooking in C# Forms and WPF


How it's done in C++


If we were to make a Windows GUI application in C++, we would have a method in our GUI-class called WndProc which would receive messages from the operating system, such as WM_KEYDOWN or WM_LBUTTONDOWN (left button down). The WM_KEYDOWN message will only come to us when the user pushes a button and our window is the currently active one, so we can't rely on that one for our global hot-keying. Luckily the people a Microsoft created just what we need; a method in user32.dll called RegisterHotKey( .. ) that allows us to ask the OS:  "Can you please notify me any time the user presses Alt + Z? :)".  If no other application has been granted that combination, the OS will gladly say yes. So from now on when the user presses Alt + Z, our WndProc will receive a message called WM_HOTKEY and parameters explaining which key combination was pressed.


Registering for hot-keys in C#


We need to run the RegisterHotKey method in user32.dll. To get access to this method we import it like this:







// Register a hot key with Windows
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr windowHandle, int id, uint modifiers, uint virtualKey);

// Unregister a certain hot key with Windows
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr windowHandle, int id);




To register a hot-key we must have a handle to our window.

Getting the Handle to our window in a Windows.Form application:

IntPtr handle = this.Handle;





Getting the Handle to our window in a WPF application:

IntPtr handle = new WindowInteropHelper(this).Handle;

Now when we have the handle we can register a hot-key when our window has loaded. The id we use when registering is used later for unregistering. Here is a simple example of registering:

// Register a hot key


if (!RegisterHotKey(handle, 1, (uint) (ModifierKeys.Alt | ModifierKeys.Control), (uint) 'P'))
throw new InvalidOperationException("Could not register the hot key");


Listening to messages in a Windows.Form application


In C# we can create a Windows.Forms application. This is the old school way of creating a GUI application, which was the only way back in the days of .NET 2.x. In a Form class we can override the WndProc and get direct access to the messages that the OS is sending to our window. We can now listen to the WM_HOTKEY message and react accordingly.


Alright, how is this done in WPF?


WPF is very different from Windows.Form and doesn't have a WndProc method. But we will make our own. Let's call it WndProc.



const int WM_HOTKEY = 0x0312;






public IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    // check if we got a hot key pressed.
    if (msg == WM_HOTKEY)
    {
        // get the keys.
        uint key = (uint)(((int)lParam >> 16) & 0xFFFF);
        ModifierKeys modifier = (ModifierKeys)((int)lParam & 0xFFFF);

        // invoke the event to notify the parent.
        KeyPressed(modifier, key);
    }

    return IntPtr.Zero;
}



Now we just ask an underlying mechanism of WPF to kindly send us all the messages. We can preferably do this when the window has loaded.

HwndSource src = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle); 



src.AddHook(new HwndSourceHook(WndProc));












Summary of global hot-key listening


We have now done the following:
  • Imported the RegisterHotKey( .. ) method from user32.dll
  • Used it to register a hot-key combo i.e Ctrl+Shift+P
  • Getting our code to parse messages in WndProc
    • In Windows.Form: By overriding WndProc( .. )
    • In WPF: By creating WndProc( .. ) and adding a hook
These are the main steps, and our application should now be reacting to the global hot-keys that we want.

Sources and tips

Thanks to Christian Liensberger for his article on hooks in C#.
Thanks to softwerx for his response showing how to use WndProc in WPF.
The Keys enum in Windows.Forms can be used when registering and parsing keys. In a WPF project one can add a reference to System.Windows.Forms and then add:

using FormsNS = System.Windows.Forms;




This will avoid conflicts with MessageBox for example in WPF vs. Forms. We can now easily register and keys like this:

// Register a hot key



if (!RegisterHotKey(handle, 1, (uint)(ModifierKeys.Alt | ModifierKeys.Control), (uint)FormsNS.Keys.Down))
throw new InvalidOperationException("Could not register the hot key");

This way we don't need to define "virtual key-codes", since they correspond to the uint value of keys in Keys.