Zune Now Playing support for Digsby and Winamp-compatible nowplaying software.

July 17, 2010 by Dave · 4 Comments
Filed under: Code Snippets, Software 

The Zune media player delivers Now Playing information to Windows Live Messenger, but other software isn’t able to pick up these messages.  Quite a few users have asked for Zune Now Playing support in Digsby, so I put together this little app that lets this work.  The application itself is generic, converting Zune now playing messages into the more widely implemented Winamp now playing method.

Download the executable and source (C++).

Code Listing:

#include "stdafx.h"
#include "ZuneNowPlaying.h"
#include "LimitSingleInstance.h" // KB243953
#include <regex>
 
const std::tr1::wregex pattern(L".*?\\\\0Music\\\\0.*?\\\\0.*?\\\\0(.*?)\\\\0(.*?)\\\\0");
 
CLimitSingleInstance g_SingleInstanceObj(L"{24F3CD83-9FFE-4DE6-951D-63958942F834}");
 
HWND hWndWinamp;
HWND hWndWindowsLiveMessenger;
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
		case WM_COPYDATA:
			{
				COPYDATASTRUCT *cds = (COPYDATASTRUCT*)lParam;
				wchar_t *str = (wchar_t*)cds-&gt;lpData;
 
				std::tr1::wcmatch result;
				if(std::tr1::regex_search(str, result, pattern))
				{
					std::wstring ret = result[2].str() + L" - " + result[1].str();
					SetWindowText(hWndWinamp, ret.c_str());
				}
				else
				{
					SetWindowText(hWndWinamp, L"");
				}
				break;
			}
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		case WM_USER:
			if (lParam == 104)
			{
				return 1; // Winamp=Playing (always)
			}
	}
	return DefWindowProc(hWnd, message, wParam, lParam);
}
 
void RegisterClasses(HINSTANCE hInstance)
{
	WNDCLASSEX wcex = WNDCLASSEX();
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.lpfnWndProc	= WndProc;
	wcex.hInstance		= hInstance;
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszClassName	= L"MsnMsgrUIManager";
	RegisterClassEx(&amp;wcex);
 
	wcex.lpszClassName	= L"Winamp v1.x";
	RegisterClassEx(&amp;wcex);
}
 
bool CreateWindows(HINSTANCE hInstance)
{
	// Pretend to be Windows Live Messenger, so WM_COPYDATA messages are sent to us.
	hWndWindowsLiveMessenger = CreateWindow(L"MsnMsgrUIManager", L"", WS_DISABLED,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
	// Pretend to be Winamp (any version), so apps will pick us up for Now Playing.
	hWndWinamp = CreateWindow(L"Winamp v1.x", L"", WS_DISABLED,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
 
	return hWndWinamp &amp;&amp; hWndWindowsLiveMessenger;
}
 
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR /*lpCmdLine*/, int /*nCmdShow*/)
{
	MSG msg = MSG();
	if (!g_SingleInstanceObj.IsAnotherInstanceRunning())
	{
		RegisterClasses(hInstance);
 
		if (CreateWindows(hInstance))
		{
			while (GetMessage(&amp;msg, NULL, 0, 0))
			{
				TranslateMessage(&amp;msg);
				DispatchMessage(&amp;msg);
			}
		}
	}
	return (int)msg.wParam;
}</regex>

How it works:

It’s pretty simple, Zune software sends a WM_COPYDATA message to a window with class name MsnMsgrUIManager, the message contains a pointer to a unicode string, that looks something like this:

ZUNE\0Music\01\0{0} - {1}\0TRACK_TITLE\0TRACK_ARTIST\0TRACK_ALBUM\0zune:ZUNE_GUID\0
 
// TRACK_TITLE: Title
// TRACK_ARTIST: Artist
// TRACK_ALBUM: Album
// ZUNE_GUID:  A GUID that may refer to the Zune device, Zune account, or Zune software itself.
 
// Note:  {0} - {1} is the format of how the string should be displayed in WLM - I ignore this entirely.
// Note2:  This method is used by other software, so I don't gate on ZUNE for the sake of maximum flexibility.  
// Note3:  The "1" after Music specifies the playing state.  0=Stopped, 1=Playing.  I ignore this and instead just use whether or not the other fields exist.

Note:  The \0’s in the above string are literally \ and 0, not NULL characters.

Using a regular expression to match the fields, The artist and title are extracted from the string, and set as the window caption for a window with class name Winamp v1.x. Software (like Digsby, or other now playing apps) may use FindWindow to locate the fake Winamp player, and read the caption with GetWindowText.

You may need to install the Microsoft Visual C++ 2010 Redistributable Package (x86).

GVNotifier moving forward

July 10, 2010 by Dave · 1 Comment
Filed under: Uncategorized 

Thanks to an update on Lifehacker, GVNotifier has been installed 3,000 times in the past few days.  I’ve been flooded with emails and IMs–I apologize if I didn’t respond to you.  I generally try to respond to everything, but with working full-time, I wasn’t around and many messages were missed.  Thanks to those who have donated, and also those that left kind words!  I appreciate the feedback, good and bad.

As I have indicated in some email exchanges, I’m working full-time for the summer and and don’t have much spare time for development.  I’ll be picking back up at the end of August, but things are pretty stable right now.  The rest of this posts contains my thoughts on moving forward, as well as documentation of known issues and other reasoning.

Known Issues & Bugs

  • GVNotifier does not work outside of the USA:  This is a long-standing issue, Google allows connections to google.com/voice from a non-US IP address, but the authentication process is different.  I’ve built a new Silverlight library for Windows Phone 7 that uses ClientLogin, so I will likely switch over to this system.  (Milestone 1.5)
  • Search Crash:  Some people experience an occasional crash when searching contacts.  I have a fix planned.  The good news is that this is the only known crash.  (Milestone 1.5)
  • Bandwidth Usage:  A couple people have reported that the bandwidth usage is heavy.  This is true if you’re using metered internet.  I have an idea of how I can reduce usage, but this is not a priority right now.  (Milestone 1.6-1.7)
  • Contacts Sync:  Updating a contact that already has a window open doesn’t always work.  If you must sync contacts completely, a restart is required.  (Milestone 1.5)
  • Area code 406 numbers are hidden:  Since there actually are people with Google Voice in Montana, I’ll bring back a setting for this.  (Milestone 1.5)

FAQ

  • Can I change the polling rate?  Yes.  Edit %localappdata%\GVNotifierWPF\settings.xml.  Look for UpdateFreq, which is in seconds.  Using a zero-value will disable updating completely.
  • Can I invoke a manual check?  Yes.  Call GVNotifier.net.exe /check.  (Other arguments include: /signout, /quit and /update_contacts)
  • Can I change the notification sound?  Yes.  Edit %localappdata%\GVNotifierWPF\new.wav
  • Can I dial a number that isn’t in my contacts?  Yes.  Type the number into contacts search.
  • My contacts aren’t all showing, why?  GVNotifier will show contacts only if they have one or more phone numbers that are NOT within the 406 area code.
  • GVNotifier is doing something weird, what can I do?  Check out the log file at %localappdata%\GVNotifierWPF\log.txt.
  • Is my personal data safe?  As of 1.4.1, passwords are encrypted.  Your Google Voice login information is sent only to Google.com, and only via SSL.  Contacts information is stored in plain text, along with other settings.  The password encryption is not impossibly strong, but if you are worried, use full disk encryption (like BitLocker or TrueCrypt).
  • Where is %localappdata%?  Windows Vista/7:  C:\Users\<UserName>\AppData\Local.  Windows XP:  C:\Documents and Settings\<UserName>\Local Settings\Application Data.
  • Can I disable SMS for a certain contact?  Yes – include the text nosms in their Google Contact information.  The input box will be disabled for this contact.

New Features

  • Dial pad:  I’ll probably add this, even though there is a way to insert new numbers–I neglected how valuable a Dial pad would be with a touch computer. (Milestone 1.5)
  • SMS History:  I’d like to sync history in the message window, along with some other enhancements (contact photo, other contact info).  I’m considering adding an SMS History tab, but I’m not crazy about this.  (Milestone 1.6)
  • Mass-messaging:  I’d like to integrate groups from Google Contacts, and on-demand group messaging. (Milestone 1.7)
  • Let me know if you don’t see something you want here.  I consider any requests that aren’t ridiculous.

Things that are not being considered

  • Please don’t ask me to “compile this is without the .NET framework,” or “compile with .NET 2.0 because it is better.”  GVNotifier requires .NET 3.5sp1 and utilizes the Windows Presentation Foundation.  There is certainly a performance hit, but you’re welcome to not use this software.  I’ll be upgrading to .NET 4.0 in the future.
  • Open Source.  I’m not open-sourcing this project unless someone is willing to dedicate a large amount of time to adding new–and reliable–features.
  • “Making the UI more compact.”  I’m mostly happy with the interface, I will not be doing any NCA drawing (at the very top of the window; like Firefox4, Chrome, Opera, et al).  Getting this working perfectly is a waste of effort that is better spent fixing bugs and adding useful functionality.