杂文笔记

    返回首页    发表留言
本文作者:李德强
          手柄模拟键盘鼠标
 
 

        今天我们来一起学习在Linux环境下如何编写游戏手柄驱动,并通过手柄按键模拟键盘鼠标操作,最终在Linux系统中使用游戏手柄来玩魔兽世界游戏。

一、操作系统的选择

        网上很多朋友在对Linux操作系统选择时非常困惑,不知道应该选用哪一个发行版进行学习。的确,Linux的发行版非常众多,要想在众多发行版中选出一个适合自己学习和工作的确实比较困难。我自己使用Linux已经有相当长的一段时间了,自己曾经使用过发行版有:RedHat Linux、CentOS、Ubuntu Desktop、Fedora、Debian、OpenSUSE、Gentoo、ArchLinux这里我所说的使用过是指确确实实在电脑上安装并使用超过3个月以上,而不是通过虚拟机安装简单的体验。

        提起Linux,很多人都会想到Ubuntu,并且把Ubuntu作为Linux的代名词。我们需要跟大家说的是,Linux的发行版众呈纷纭,每一个发行版都有其相应的优点和缺点。Ubuntu只是Linux家族中一个较为流行的发行版而已,千万不能把二者等价想看。很多朋友热爱Ubuntu因为其安装简便,图形化界面操作简易、美观,在Ubuntu系统下完成工作环境的搭建和部署较为容易,因此就使得Ubuntu成为了人们心中热衷的Linux发行版。但实际上Ubuntu系统却略显笨重,对于Linux入门者来说是个不错的选择,但是Ubuntu的这种更人性化的图形界面会让使用者陷入图形化界面操作的误区,而对Linux的命令行界面产生排斥。

        实际上,Linux的命令行界面功能强大,强大的命令行界面完全超越图形化界面。而对于众多Linux的发行版来说,软件包参差不齐,图形界面也各有千秋,但命令行界面的Shell命令却一致统一,因此学好Linux命令行是进入Linux大门的关键,不要把主要精力放在其多样化的图形界面上,学好了命令行界面,无论在什么发行版下工作和学习有会游刃有余,而不依赖于各个图形化界面的不同操作方式。图形化界面只是为了使用者操作方便而开发的一套可视化的用户界面,例如GNOME、KED、XFCE4、LXQT、MATE-COMPIZ、LXDE、SOAS等等。

        作者同样尝试过各个不同的图形化界面,最终的选择的发行版为ArchLinux,图形化界面为XFCE4。

                   -`                 
                  .o+`                 lidq@archlinux
                 `ooo/                 OS: Arch Linux 
                `+oooo:                Kernel: x86_64 Linux 5.2.8-arch1-1-ARCH
               `+oooooo:               Uptime: 56m
               -+oooooo+:              Packages: 1242
             `/:-:++oooo+:             Shell: bash 5.0.7
            `/++++/+++++++:            Resolution: 3360x1080
           `/++++++++++++++:           DE: XFCE4
          `/+++ooooooooooooo/`         WM: Xfwm4
         ./ooosssso++osssssso+`        WM Theme: Agualemon
        .oossssso-````/ossssss+`       GTK Theme: Adwaita [GTK2]
       -osssssso.      :ssssssso.      Icon Theme: Mezzanotte
      :osssssss/        osssso+++.     Font: Sans 10
     /ossssssss/        +ssssooo/-     CPU: Intel Core i9-9900K @ 16x 5GHz [27.8°C]
   `/ossssso+/:-        -:/+osssso+-   GPU: GeForce RTX 2060
  `+sso+:-`                 `.-/+oso:  RAM: 2401MiB / 64330MiB
 `++:.                           `-/+/
 .`                                 `/

 

二、游戏手柄驱动

        我们今天的主要学习内容就是编写游戏手柄驱动。首先我们选用一款普通的USB有线游戏手柄,如下图:

        此游戏手柄下方有2个摇杆、左侧一个十字方向盘、右侧4个按钮,顶部左右各有2个按钮,另外,游戏手柄中间还有几个功能按钮,我们暂时只使用2个,加在一起我们需要处理20个按钮。将游戏手边的USB端插入电脑,在Linux系统中会自动识别此USB设备,并为其创建一个/dev/hidrawN的设备节点,其中N为数字,在不同的电脑上数字会不同,但通常为4,例如,在作者的电脑上接入游戏手柄之后系统为其分配的设备节点为/dev/hidraw4如下图所示:

        有了设备节点之后的事情就方便多了,我们可以编写程序,从此设备节点中读取此游戏手柄的按键信息:

int fd_joy = open("/dev/hidraw4", O_RDWR | O_NONBLOCK);
if (fd_joy < 0)
{
	printf("could not open %s.\n", devs->dev_j);
	goto _out;
}

while (1)
{
	uint8_t buff[0x20];
	int len = read(fd_joy, buff, 0x20);
	if (len <= 0)
	{
		return 0;
	}

	for (int i = 0; i < len; ++i) {
		printf("%02X ", buff[i]);
	}
	printf("\n");
	usleep(10 * 1000);
}

        这样将程序编译运行即可从游戏手柄中读出当前按键的相关信息,作者手中的游戏手柄在没有任何按键时数据如下:

84 85 7B 7B FF 0F 00 00

        我们可以将手柄中的按键一个一个按下,并查看每一个字节的变化内容来推算出按钮与字节的对应关系,例如作者的游戏手柄中左侧十字方向盘的4个按键分别对应数组中下标为5的字节的低4位的值:

上: 0x0

下: 0x4

左: 0x2

右: 0x6

空: 0xF

        其它按钮都可以根据这种方法来一一对应,这里不再赘述,请读者根据自己的游戏手柄自行计算并得到当前按键。

 

三、模拟鼠标键盘

        我们选择Linux来进行模拟按键开发是因为Linux给编程者提供了非常丰富的调用接口,我们可以使用程序对键盘和鼠标在文件系统中的设备节点进行读写,来完成模拟操作。我们首先来看一下键盘和鼠标的设备节点:

        我们可以通过下面命令来查看键盘设备和鼠标设备的event编号:

        由此看来,键盘设备为event4,鼠标设备为event3。

        键盘和鼠标的操作事件被定义在input.h当中,其结构体如下:

struct input_event {
	u16 type;
	u16 code;
	s32 value;
};

        其中type表示事件类型,在这里我们只使用EV_KEY,code为实际键盘和鼠标的按键码:

#define KEY_RESERVED		0
#define KEY_ESC			1
#define KEY_1			2
#define KEY_2			3
#define KEY_3			4
#define KEY_4			5
#define KEY_5			6
#define KEY_6			7
#define KEY_7			8
#define KEY_8			9
#define KEY_9			10
#define KEY_0			11
#define KEY_MINUS		12
#define KEY_EQUAL		13
#define KEY_BACKSPACE		14
#define KEY_TAB			15
#define KEY_Q			16
#define KEY_W			17
#define KEY_E			18
#define KEY_R			19
#define KEY_T			20
#define KEY_Y			21
#define KEY_U			22
#define KEY_I			23
#define KEY_O			24
#define KEY_P			25
#define KEY_LEFTBRACE		26
#define KEY_RIGHTBRACE		27
#define KEY_ENTER		28
#define KEY_LEFTCTRL		29
#define KEY_A			30
#define KEY_S			31
#define KEY_D			32
#define KEY_F			33
#define KEY_G			34
#define KEY_H			35
#define KEY_J			36
#define KEY_K			37
#define KEY_L			38
#define KEY_SEMICOLON		39
#define KEY_APOSTROPHE		40
#define KEY_GRAVE		41
#define KEY_LEFTSHIFT		42
#define KEY_BACKSLASH		43
#define KEY_Z			44
#define KEY_X			45
#define KEY_C			46
#define KEY_V			47
#define KEY_B			48
#define KEY_N			49
#define KEY_M			50
#define KEY_COMMA		51
#define KEY_DOT			52
#define KEY_SLASH		53
#define KEY_RIGHTSHIFT		54
#define KEY_KPASTERISK		55
#define KEY_LEFTALT		56
#define KEY_SPACE		57
#define KEY_CAPSLOCK		58
#define KEY_F1			59
#define KEY_F2			60
#define KEY_F3			61
#define KEY_F4			62
#define KEY_F5			63
#define KEY_F6			64
#define KEY_F7			65
#define KEY_F8			66
#define KEY_F9			67
#define KEY_F10			68
#define KEY_NUMLOCK		69
#define KEY_SCROLLLOCK		70
#define KEY_KP7			71
#define KEY_KP8			72
#define KEY_KP9			73
#define KEY_KPMINUS		74
#define KEY_KP4			75
#define KEY_KP5			76
#define KEY_KP6			77
#define KEY_KPPLUS		78
#define KEY_KP1			79
#define KEY_KP2			80
#define KEY_KP3			81
#define KEY_KP0			82
#define KEY_KPDOT		83

#define BTN_MOUSE		0x110
#define BTN_LEFT		0x110
#define BTN_RIGHT		0x111
#define BTN_MIDDLE		0x112
#define BTN_SIDE		0x113
#define BTN_EXTRA		0x114
#define BTN_FORWARD		0x115
#define BTN_BACK		0x116
#define BTN_TASK		0x117
...
//其他略

        value为按键实际的值,对于键盘和鼠标的按键来说0表示未按下,1表示按下;对于鼠标的移动事件可以表示鼠标的相对位置,于是我们就可以编写程序来将游戏手柄的按键模拟为键盘和鼠标的按键了,具体程序如下:

void* joy_to_input(void *arg)
{
	dev_s *devs = (dev_s *) arg;
	if (devs->dev_j == NULL)
	{
		goto _out;
	}
	if (devs->dev_k == NULL)
	{
		goto _out;
	}
	if (devs->dev_m == NULL)
	{
		goto _out;
	}

	inv_evt_s *evts = malloc(sizeof(inv_evt_s) * KEY_COUNT);
	if (evts == NULL)
	{
		printf("malloc evts error.\n");
		goto _out;
	}

	load_conf_file(devs->conf, evts);

	int fd_joy = open(devs->dev_j, O_RDWR | O_NONBLOCK);
	if (fd_joy < 0)
	{
		printf("could not open %s.\n", devs->dev_j);
		goto _out;
	}

	int fd_kbd = open(devs->dev_k, O_RDWR | O_NONBLOCK);
	if (fd_kbd < 0)
	{
		printf("could not open %s.\n", devs->dev_k);
		goto _out;
	}

	int fd_mouse = open(devs->dev_m, O_RDWR | O_NONBLOCK);
	if (fd_mouse < 0)
	{
		printf("could not open %s.\n", devs->dev_m);
		goto _out;
	}

	inv_evt_s event;
	inv_evt_s event_ctl_shift;
	event.type = EV_SYN;
	event.code = SYN_REPORT;
	event.value = 0;
	uint16_t *btns = malloc(sizeof(uint16_t) * KEY_COUNT);
	uint8_t ctl_key = 0;
	uint8_t ctl_cnt = 0;
	uint8_t shift_key = 0;
	uint8_t shift_cnt = 0;

	uint16_t *press_key = malloc(sizeof(uint16_t) * KEY_COUNT);
	uint16_t *press_cnt = malloc(sizeof(uint16_t) * KEY_COUNT);
	memset(press_key, 0, sizeof(uint16_t) * KEY_COUNT);
	memset(press_cnt, 0, sizeof(uint16_t) * KEY_COUNT);

	while (1)
	{
		int cnt = get_curr_joy_btn(fd_joy, btns);
		if (cnt != 0)
		{
			for (int i = 0; i < cnt; i++)
			{
				uint16_t btn = btns[i];

				if (evts[btn].type == EV_KEY 
                    && (evts[btn].code < 0x110 || evts[btn].code > 0x117))
				{
					if (evts[btn].code == KEY_1
                        || evts[btn].code == KEY_2
                        || evts[btn].code == KEY_3
                        || evts[btn].code == KEY_4)
					{
						evts[btn].value = 1;
						write(fd_kbd, &(evts[btn]), sizeof(inv_evt_s));
						write(fd_kbd, &event, sizeof(inv_evt_s));

						evts[btn].value = 0;
						write(fd_kbd, &(evts[btn]), sizeof(inv_evt_s));
						write(fd_kbd, &event, sizeof(inv_evt_s));
					}
					else
					{
						press_cnt[evts[btn].code] = 5;
						if (press_key[evts[btn].code] == 0)
						{
							press_key[evts[btn].code] = 1;

							evts[btn].value = 1;
							write(fd_kbd, &(evts[btn]), sizeof(inv_evt_s));
							write(fd_kbd, &event, sizeof(inv_evt_s));
						}
					}
				}

				if (evts[btn].type == EV_KEY && evts[btn].code >= 0x110 && evts[btn].code <= 0x117)
				{
					if (evts[btn].code == BTN_LEFT || evts[btn].code == BTN_RIGHT)
					{
						press_cnt[evts[btn].code] = 5;
						if (press_key[evts[btn].code] == 0)
						{
							press_key[evts[btn].code] = 1;

							evts[btn].value = 1;
							write(fd_mouse, &(evts[btn]), sizeof(inv_evt_s));
							write(fd_mouse, &event, sizeof(inv_evt_s));
						}
					}
					else
					{
						evts[btn].value = 1;
						write(fd_mouse, &(evts[btn]), sizeof(inv_evt_s));
						write(fd_mouse, &event, sizeof(inv_evt_s));

						evts[btn].value = 0;
						write(fd_mouse, &(evts[btn]), sizeof(inv_evt_s));
						write(fd_mouse, &event, sizeof(inv_evt_s));
					}
				}

				if (evts[btn].type == EV_REL)
				{
					write(fd_mouse, &(evts[btn]), sizeof(inv_evt_s));
					write(fd_mouse, &event, sizeof(inv_evt_s));
				}
			}
		}

		for (uint16_t i = 0; i < KEY_COUNT; i++)
		{
			if (press_cnt[i] != 0)
			{
				press_cnt[i]--;
			}

			else if (press_key[i] == 1)
			{
				press_key[i] = 0;
				event_ctl_shift.type = EV_KEY;
				event_ctl_shift.code = i;
				event_ctl_shift.value = 0;

				if (i == BTN_LEFT || i == BTN_RIGHT)
				{
					write(fd_mouse, &event_ctl_shift, sizeof(inv_evt_s));
					write(fd_mouse, &event, sizeof(inv_evt_s));
				}
				else
				{
					write(fd_kbd, &event_ctl_shift, sizeof(inv_evt_s));
					write(fd_kbd, &event, sizeof(inv_evt_s));
				}
			}
		}
		usleep(10 * 1000);
	}

	_out: ;

	return NULL;
}

 

四、运行魔兽世界

        万事具备,只欠东风。现在我们来在Linux上运行魔兽世界游戏,这款游戏从上市以来到今天,以及可遇见的未来,一直只支持MacOS和Windows这两款操作系统,而对于想作者这样的Linux爱好者来说只好使用Wine、PlayOnLinux或CrossOver来运行Windows下的游戏软件,由于原声Wine需要进行非常多的自定义配置,并且CrossOver是收费的,因此在这里我们推荐使用PlayOnLinux,对于不同的Linux发行版,安装PlayOnLinux的指令略有不同,例如:

ArchLinux: pacman -S playonlinux
Fedora:    yum install playonlinux
Ubuntu:    apt-get install playonlinux

        安装完之后启动PlayOnLinux,然后点击Install a none-listed program,根据提示创建虚拟磁盘驱动器,并下载安装暴雪战网平台,如下图所示:

        安装成功之后,在PlayOnLinux的主界面中会出现Battle.net运行图表,将其选中并执行Run,即可打开战网,之后跟在Windows下一样,可以安装并运行魔兽世界游戏:

         进入游戏,打开我们的游戏手柄模拟键盘鼠标程序,将游戏手柄中指定的按键,作为游戏中特定的快捷键即可,最后我们来看看作者的运行效果。

 

 

    返回首页    返回顶部
  看不清?点击刷新

 

  Copyright © 2015-2023 问渠网 辽ICP备15013245号