上次说到要实现一个无线鼠标控制的网络电台播放程序(远程控制:)),今天有空就实现了。主要的难点就是读取鼠标数据,昨天花了一天时间看了linux kernel的driver/input/mousedev.c源代码,看懂了数据格式和控制原理,就很简单了:)。
鼠标的设备文件是/dev/input/mice,默认读取的是PS2数据,分3个字节,分别是3个按键、X、Y坐标。不过要注意的是XY坐标都是相对坐标(不超过127,有符号),如果要获得滚轮数据需要打开im扩展,直接向mice写数据串{ 0xf3, 200, 0xf3, 100, 0xf3, 80 }就行了,这个在mousedev.c写的很清楚了,PS2格式参考看这里。
解决了鼠标控制,具体播放就开个进程放mplayer,把所有电台URL放在playlist里就行了,mplayer支持从标准输入的按键控制,0和9控制音量,<和>对应前进和后退,p是暂停。进程间通信就用pipe,不过建pipe,绑定stdin有点麻烦,有现成的API做了这些了,popen和pclose就可以搞定这些。最后用鼠标左键前翻,右键后翻,中键暂停,滚轮控制音量。
Talk is cheap, show me the code: github
// @author: bl4kraven
// @brief: just a network radio player and control by mouse.
// using mplayer as backend, left button previous, right button forward and middle button pause.
// @see linux kernel drivers/input/mousedev.c
// http://www.computer-engineering.org/ps2mouse/
//
#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
typedef unsigned char BYTE;
#pragma pack(1)
struct imps2_data
{
BYTE btn_left:1;
BYTE btn_right:1;
BYTE btn_middle:1;
BYTE NONE:1;
BYTE x_sign:1; // x offset sign
BYTE y_sign:1; // y offset sign
BYTE x_overflow:1; // x offset is overflow
BYTE y_overflow:1; // y offset is overflow
// x/y movement offset relative to its position
signed char x;
signed char y;
signed char z;
};
#pragma pack()
int main(int argc, char *argv[])
{
BYTE mousedev_imps_seq[] = { 0xf3, 200, 0xf3, 100, 0xf3, 80 };
int mice_fd = open("/dev/input/mice", O_RDWR|O_NONBLOCK);
if (mice_fd == -1)
{
fprintf(stderr, "Open mice fail");
return 1;
}
// set mice mode, so can read rolling wheels
int nRet = write(mice_fd, mousedev_imps_seq, sizeof(mousedev_imps_seq));
if (nRet < 0)
{
fprintf(stderr, "set mice imps2 fail\n");
return 1;
}
FILE *stream = popen("mplayer -quiet -softvol -softvol-max 300 -playlist channel.txt", "w");
if (stream == NULL || stream < 0)
{
fprintf(stderr, "popen mplayer fail\n");
return 1;
}
while (true)
{
fd_set fdset;
//struct timeval tv;
//tv.tv_sec = 0;
//tv.tv_usec = 200000;
FD_ZERO(&fdset);
FD_SET(mice_fd, &fdset);
int ret = select(mice_fd+1, &fdset, NULL, NULL, NULL);
if (ret < 0)
{
fprintf(stderr, "select return error\n");
return 1;
}
else
{
if (FD_ISSET(mice_fd, &fdset))
{
imps2_data data;
while (true)
{
int nLen = read(mice_fd, &data, sizeof(data));
if (nLen == 0)
{
fprintf(stderr, "End of file \n");
return 1;
}
else if (nLen == -1)
{
if (errno != EAGAIN)
{
fprintf(stderr, "read fail\n");
return 1;
}
break;
}
else
{
// nLen = 1 is ack
if (nLen == sizeof(data))
{
//printf("nLen:%d, left:%d right:%d middle:%d X:%d Y:%d Z:%d\n",
// nLen, data.btn_left, data.btn_right, data.btn_middle, data.x, data.y, data.z);
if (data.btn_left)
{
if (data.btn_right)
{
// OK quit
close(mice_fd);
fprintf(stream, "q");
fflush(stream);
// wait process exit
pclose(stream);
return 0;
}
// preivous
fprintf(stream, "<");
fflush(stream);
}
else if (data.btn_right)
{
// forword
fprintf(stream, ">");
fflush(stream);
}
else if (data.btn_middle)
{
// pause
fprintf(stream, "p");
fflush(stream);
}
else if (data.z != 0)
{
if (data.z > 0)
{
// rolling down
fprintf(stream, "9");
fflush(stream);
}
else
{
// rolling up
fprintf(stream, "0");
fflush(stream);
}
}
}
}
}
}
}
}
return 0;
}