Winamp头上动土
遭遇
近日使用Winamp时发生极度不爽的事情,就是我打CS的时候习惯听着Winamp来打,但我的播放列表又很乱,有些我不想听的歌也在播放列表里面,我又懒得去整理,所以有时正要杀人的时候刚好播到我不喜欢听的歌,导致战斗力大减,被人凌辱,我总不能用Alt+Tab切换出桌面换了歌再进去厮杀吧,所以我只好拿Winamp开刀进行改造。
我想在打着CS的时候可以按键盘的某一个键就可以跳到下一首歌,这样我就不用忍受那些不喜欢的歌曲了,又可以提高战斗力,Winamp没有这样的热键,我只好自己动手丰衣足食。
关键问题:
1)Winamp没有控制接口给我,而我要控制他,那我只好自己找到他的窗口句柄然后给他发送消息来控制他。
2)要在任何时候任何地方得到键盘消息,我知道的有2种方法,1.写键盘驱动;2.写全局键盘钩子,我不会写驱动,只好写键盘钩子。
3)我总不能每次在进入CS前都去启动一下我的钩子,即使我愿意,钩子是需要进程的,我不愿意在我的任务栏多一个窗口,我的任务栏已经很挤了,如果就为了控制winamp就在任务栏多一个东西,人家看到指指点点,面子不够光彩,所以我要做成windows服务的形式。
给Winamp发送消息
通过SPY++发现Winamp播放主窗口的标题总是"歌曲名XXX - Winamp",所以可以用检查各个窗口的窗口标题的最后6个字符看是不是"Winamp"来判断一个窗口是不是Winamp的主播放窗口。
可以用
BOOL EnumWindows(
WNDENUMPROC lpEnumFunc,
LPARAM lParam
); //枚举桌面的各个窗口
来枚举到所有在桌面的窗口,如果Winamp在播放,他当然在桌面上。
第一个参数是枚举回调函数,函数原型是
BOOL CALLBACK EnumWindowsProc(
HWND hwnd,
LPARAM lParam
); //枚举回调函数
这个函数在 EnumWindows 每枚举到一个窗口时都会调用,回调函数的第一个参数就是窗口句柄,有了窗口句柄就可以为所欲为了。
通过spy++事件探查器探查到Winamp在点击‘下一首’按钮时的要跳到下一首播放只要发送一个菜单消息WM_COMMAND给Winampe那个playback-next 菜单项就可以跳到下一首播放,WM_COMMAND的第一个参数固定是0x00009C70(不知道什么意思),第二个参数固定是0
所以我只要用EnumWindows找到Winampe的主窗口,然后PostMessage发送一个WM_COMMAND消息就可以要Winamp跳到下一首播放。
键盘钩子
键盘钩子能够拦截应用程序的事件通知,抢在应用程序之前对事件进行处理,钩子API包括
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);//安装钩子
BOOL UnhookWindowsHookEx(
HHOOK hhk
); //御除钩子
LRESULT CallNextHookEx(
HHOOK hhk,
int nCode,
WPARAM wParam,
LPARAM lParam
);//通知下一个钩子
LRESULT CALLBACK KeyboardProc(
int code,
WPARAM wParam,
LPARAM lParam
);//键盘钩子回调原型
一般钩子只能钩到本进程的消息,要在我的进程里钩Winamp的消息就要安装一个全局的键盘钩子,只需要在Dll里安装钩子就可以做到这点。
建立一个Dll,代码如下
// WinampHook.cpp : 定义 DLL 应用程序的入口点。
//
//by Lingch
//2005-5-6
//http://www.lingch.net
//post2ling@hotmail.com
#include "stdafx.h"
#include<string>
using namespace std;
HANDLE hModule;
HHOOK hook;
BOOL CALLBACK EnumWindowsProc(HWND hwnd,DWORD lParam)
{
//获取窗口标题
char strTitle[128];
::GetWindowText (hwnd,strTitle,127);
string strT=strTitle;
//检查窗口标题是否以"Winamp"结尾
if(strT.size ()>=6)
{
string subStr=strT.substr (strT.size ()-6,6);
if(strcmp(subStr.c_str (),"Winamp")==0)
{
::PostMessage (hwnd,WM_COMMAND,0x00009C70,0);//下一首
}
}
return TRUE;
}
LRESULT CALLBACK KeyProc(int code,WPARAM wParam,LPARAM lParam)
{
if(wParam==VK_F12)//F12
{
::EnumWindows ((WNDENUMPROC)EnumWindowsProc,0);
}
return CallNextHookEx(hook,code,wParam,lParam);
}
//开始hook
extern "C" __declspec(dllexport)
int Hook()
{
hook=SetWindowsHookEx(WH_KEYBOARD,KeyProc,(HINSTANCE)hModule,0);
if(hook==NULL)
return -1;
return 0;
}
//停止hook
extern "C" __declspec(dllexport)
int UnHook()
{
if(UnhookWindowsHookEx (hook))
return 0;
else
return -1;
}
BOOL APIENTRY DllMain( HANDLE hMod,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
hModule=hMod;
return TRUE;
}
Windows服务
我要把我的钩子做成windows服务的形式,隐藏起来
Windows服务就是 控制 面板-管理工具-服务 里面那种,那种应用程序当成系统的一个服务,没有界面,没有交互,系统一启动还没用户登录就运行,直到关闭系统或者用户停止。
Windows服务有一个主控线程,这个线程控制服务的 开始、暂停、停止 等动作,这个主控线程的入口是 main 函数,但他只是控制服务的执行,服务的具体事务处理不是由这个主控线程执行,这个主控线程会启动一个服务线程用来执行服务的具体事务动作,服务线程的入口原型必须是
VOID WINAPI ServiceMain(DWORD dwArgc,LPTSTR* lpszArgv);
服务线程是服务的具体事务线程,要为他设计一个工作循环不能让他退出,如果他退出了,主控线程也没有存在的必要,到时主控线程会侦测到服务线程的退出,并也会退出,那服务就完了。
在主控线程创建了服务线程后,主控线程就进入
BOOL StartServiceCtrlDispatcher(const LPSERVICE_TABLE_ENTRY lpServiceTable);
函数阻塞,这个阻塞有点像GetMessage(…),他不消耗CPU时间,只是阻塞在那里等待结束或者暂停服务的命令发生。一旦结束或者暂停服务的命令发生,这个线程将被唤醒去执行
一个服务控制回调函数,控制回调函数的原型是
VOID WINAPI Handler(DWORD fdwControl);
里面可以判断发生了什么命令,比如 停止或者暂停命令。
服务控制回调函数用
SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(LPCTSTR lpServiceName,LPHANDLER_FUNCTION lpHandlerProc);
注册。
还有一个安装服务的问题,要把服务安装到 控制面板-管理工具-服务 里面和从里面御除还需要一些代码
安装服务用
SC_HANDLE CreateService(
SC_HANDLE hSCManager,
LPCTSTR lpServiceName,
LPCTSTR lpDisplayName,
DWORD dwDesiredAccess,
DWORD dwServiceType,
DWORD dwStartType,
DWORD dwErrorControl,
LPCTSTR lpBinaryPathName,
LPCTSTR lpLoadOrderGroup,
LPDWORD lpdwTagId,
LPCTSTR lpDependencies,
LPCTSTR lpServiceStartName,
LPCTSTR lpPassword
);
御除服务用
BOOL DeleteService(
SC_HANDLE hService);
下面是我的windows服务的代码
// WinampService.cpp : 定义控制台应用程序的入口点。
//
/*++
by Lingch
2005-5-6
http://www.lingch.net
post2ling@hotmail.com
--*/
#include "stdafx.h"
#define SZAPPNAME "WinampService"
#define SZSERVICENAME "WinampService"
#define SZSERVICEDISPLAYNAME "WinampService"
#define SZDEPENDENCIES ""
void WINAPI WinampServiceMain(DWORD argc, LPTSTR * argv);
void InstallService(const char * szServiceName);
void UnInstallService(const char * szServiceName);
int Start();
int Stop();
SERVICE_STATUS servicestatus;
SERVICE_STATUS_HANDLE servicestatushandle;
typedef int (*pHook)(void);
typedef int (*pUnHook)(void);
pHook Hook=NULL;
pUnHook UnHook=NULL;
HANDLE evStop=NULL;
int main(int argc, char* argv[])
{
//获取DLL输出函数
HMODULE h=::LoadLibrary (".//WinampHook.dll");
if(h==NULL)
{
printf("装载WinampHook.dll错误");
return -1;
}
Hook=(pHook)::GetProcAddress (h,"Hook");
UnHook=(pUnHook)::GetProcAddress (h,"UnHook");
if(Hook==NULL || UnHook==NULL)
{
printf("获取Dll输出函数错误");
::FreeLibrary (h);
return -1;
}
//安装和执行服务
if (argc==2)
{
if(::strcmp(argv[1]+1, "Install")==0)
{
InstallService("WinampService");
return 0;
}
else if(::strcmp(argv[1]+1, "UnInstall")==0)
{
UnInstallService("WinampService");
return 0;
}
}
//建立停止服务用的event
evStop=::CreateEvent (NULL,false,false,"evStop");
if(evStop==NULL)
{
printf("建立event失败");
return -1;
}
//开始服务
SERVICE_TABLE_ENTRY service_table_entry[] =
{
{ "WinampService",WinampServiceMain},
{NULL,NULL}
};
::StartServiceCtrlDispatcher(service_table_entry);
::FreeLibrary (h);
return 0;
}
//安装服务
void InstallService(const char * szServiceName)
{
SC_HANDLE handle = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
char szFilename[256];
::GetModuleFileName(NULL, szFilename, 255);
SC_HANDLE hService = ::CreateService(handle, szServiceName,
szServiceName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, szFilename, NULL,
NULL, NULL, NULL, NULL);
::CloseServiceHandle(hService);
::CloseServiceHandle(handle);
}
//御除服务
void UnInstallService(const char * szServiceName)
{
SC_HANDLE handle = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
SC_HANDLE hService=::OpenService (handle,szServiceName,SC_MANAGER_ALL_ACCESS);
::DeleteService (hService);
::CloseServiceHandle(hService);
::CloseServiceHandle(handle);
}
//服务事件回调
void WINAPI ServiceCtrlHandler(DWORD dwControl)
{
switch (dwControl)
{
case SERVICE_CONTROL_PAUSE:
servicestatus.dwCurrentState = SERVICE_PAUSE_PENDING;
servicestatus.dwCheckPoint =1;
servicestatus.dwWaitHint =5;
::SetServiceStatus(servicestatushandle, &servicestatus);
Stop();
servicestatus.dwCurrentState = SERVICE_PAUSED;
break;
case SERVICE_CONTROL_CONTINUE:
servicestatus.dwCurrentState = SERVICE_CONTINUE_PENDING;
servicestatus.dwCheckPoint =1;
servicestatus.dwWaitHint =5;
::SetServiceStatus(servicestatushandle, &servicestatus);
Start();
servicestatus.dwCurrentState = SERVICE_RUNNING;
break;
case SERVICE_CONTROL_STOP:
servicestatus.dwCurrentState = SERVICE_STOP_PENDING;
servicestatus.dwCheckPoint =1;
servicestatus.dwWaitHint =5;
::SetServiceStatus(servicestatushandle, &servicestatus);
Stop();
servicestatus.dwCurrentState = SERVICE_STOPPED;
break;
case SERVICE_CONTROL_SHUTDOWN:
Stop();
break;
case SERVICE_CONTROL_INTERROGATE:
servicestatus.dwCurrentState = SERVICE_RUNNING;
break;
}
::SetServiceStatus(servicestatushandle, &servicestatus);
}
//服务入口点
void WINAPI WinampServiceMain(DWORD argc, LPTSTR * argv)
{
servicestatus.dwServiceType = SERVICE_WIN32;
servicestatus.dwCurrentState = SERVICE_START_PENDING;
servicestatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
servicestatus.dwWin32ExitCode = 0;
servicestatus.dwServiceSpecificExitCode = 0;
servicestatus.dwCheckPoint = 0;
servicestatus.dwWaitHint = 0;
servicestatushandle =
::RegisterServiceCtrlHandler("WinampService", ServiceCtrlHandler);
if (servicestatushandle == (SERVICE_STATUS_HANDLE)0)
{
return;
}
bool bInitialized = false;
if(Start()==0)
bInitialized = true;
else
bInitialized= false;
servicestatus.dwCheckPoint = 0;
servicestatus.dwWaitHint = 0;
if (!bInitialized)
{
servicestatus.dwCurrentState = SERVICE_STOPPED;
servicestatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
servicestatus.dwServiceSpecificExitCode = 1;
}
else
{
servicestatus.dwCurrentState = SERVICE_RUNNING;
}
::SetServiceStatus(servicestatushandle, &servicestatus);
while(::WaitForSingleObject (evStop,1000)==WAIT_TIMEOUT)
;
return;
}
int Start()
{
//安装钩子
if(Hook()!=0)
{
return -1;
}
else
{
return 0;
}
}
int Stop()
{
::SetEvent (evStop);
//御除钩子
if(UnHook()!=0)
return -1;
else
return 0;
}
最后要注意的,F12右边的3个键是 Wake up 、Sleep 、Power ,小心按错。
从此我的电脑可以用F12控制Winamp播放下一首歌.
编译好打包的程序可以在我的主页上下载 http://www.lingch.net