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]

3 comments:

  1. Wow! What an interesting solution to code for the Spotify in that way! I just reviewed code that you wrote a handful of years ago, and remember you as a good coder.
    Oskari Foresi Berner, NTI

    ReplyDelete
  2. Very useful post. This is my first time i visit here. I found so many interesting stuff in your blog especially its discussion. Really its great article. Keep it up. spotify playlists

    ReplyDelete
  3. Many of the cool Spotify features can be easily incorporated into convenient hand held controls.learn here

    ReplyDelete