在Linux下创建一个虚拟鼠标设备还是比较简单的,使用内核uinput模块提供的函数即可。创建出虚拟鼠标以后,在主线程监听键盘的事件,当特定的键(此处使用了小键盘的数字键8、2、4、6)被按下或弹起后,进行记录。在另一个线程根据主线程记录的flag创建输入事件,然后将输入事件写入虚拟鼠标设备即可。
在实现程序时一个让我思考时间比较长的问题是:是否需要另外创建一个线程来写虚拟鼠标设备。当一个键被按下后,我们需要不断的写向相应方向移动的事件,这里是需要定时循环写的。如果不另开线程的话,我们需要在这个循环中先使用select或poll读键盘事件,可能等到了事件,也可能等不到事件;然后对相应的键进行记录;然后写虚拟鼠标设备;最后睡眠一定的时间,睡眠的时间是受前面等待事件的时间影响的,这样才能使得每个循环花费时间是相同的。这样做应该也行得通,但我觉得不如另开一个线程,在两个循环里分别读写事件实现起来比较方便。不过另开线程需要为线程安全做一点小工作,原因是有些flag可能需要在两个线程里写,使用标准库提供的atomic模板就能做到。
原本以为没几行代码,没想到写着写着就长了,功能也不太全,没有实现左右按键的功能,不过好歹能直接跑起来,有需要的时候进行扩展就是了。实现代码时主要参考了这些资料:
https://www.kernel.org/doc/html/v4.12/input/uinput.html
https://cgit.freedesktop.org/evtest
代码地址为:https://github.com/im-red/virtualmouse
也把代码贴在这里:

#include <err.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/uinput.h>
#include <string.h>
#include <dirent.h>
#include <vector>
#include <string>
#include <iostream>
#include <poll.h>
#include <signal.h>
#include <pthread.h>
#include <atomic>#define STR(X) STR2(X)
#define STR2(X) #X#define ENSURE(CONDITION, ...) if (!(CONDITION)) { err(EXIT_FAILURE, __FILE__ ":" STR(__LINE__) " " __VA_ARGS__); }static const char *EVENT_DEV_NAME = "event";
static const char *EVENT_DEV_PREFIX = "/dev/input/";
static const char *UINPUT_NAME = "/dev/uinput";static const int BITS_PER_LONG = sizeof(long) * 8;inline int nbytes(int x)
{return (x - 1) / BITS_PER_LONG + 1;
}inline bool testBits(int bit, const unsigned long array[])
{unsigned long result = array[bit / BITS_PER_LONG] >> bit % BITS_PER_LONG & 1;return (result == 1);
}static int isEventDevice(const struct dirent *dir)
{return strncmp(EVENT_DEV_NAME, dir->d_name, strlen(EVENT_DEV_NAME)) == 0;
}static void createVirtualMouse(int fd)
{// enable mouse button left and relative eventsioctl(fd, UI_SET_EVBIT, EV_KEY);ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);ioctl(fd, UI_SET_EVBIT, EV_REL);ioctl(fd, UI_SET_RELBIT, REL_X);ioctl(fd, UI_SET_RELBIT, REL_Y);struct uinput_user_dev uud;memset(&uud, 0, sizeof(uud));snprintf(uud.name, UINPUT_MAX_NAME_SIZE, "Virtual Mouse");int ret = write(fd, &uud, sizeof(uud));ENSURE(ret == sizeof(uud));ioctl(fd, UI_DEV_CREATE);
}static std::vector<std::string> getAllEventDevicePath()
{struct dirent **direntList;int ndev = scandir(EVENT_DEV_PREFIX, &direntList, isEventDevice, versionsort);ENSURE(ndev > 0, "No event device found");std::vector<std::string> result(ndev);for (int i = 0; i < ndev; i++){result[i] = std::string(EVENT_DEV_PREFIX) + std::string(direntList[i]->d_name);}return std::move(result);
}// A simple RAII class
class FileOpener
{public:explicit FileOpener(const char *pathname, int flags){fd = open(pathname, flags);}explicit FileOpener(const char *pathname, int flags, mode_t mode){fd = open(pathname, flags, mode);}~FileOpener(){if (fd >= 0){close(fd);}}int getFd(){return fd;}private:int fd;
};static bool isSupportType(const std::string &path, int type)
{FileOpener opener(path.c_str(), O_RDONLY);int fd = opener.getFd();if (fd < 0){return false;}unsigned long supportType[nbytes(EV_MAX)];ioctl(fd, EVIOCGBIT(0, EV_MAX), supportType);if (!testBits(type, supportType)){return false;}return true;
}static bool isSupportTypeCode(const std::string &path, int type, int code)
{if (!isSupportType(path, type)){return false;}FileOpener opener(path.c_str(), O_RDONLY);int fd = opener.getFd();if (fd < 0){return false;}unsigned long supportCode[nbytes(KEY_MAX)];ioctl(fd, EVIOCGBIT(type, KEY_MAX), supportCode);if (!testBits(code, supportCode)){return false;}return true;
}static const int NEEDED_KEY[] = { KEY_KP8, KEY_KP2, KEY_KP4, KEY_KP6 };
// device reports KEY_KP8 KEY_KP2 KEY_KP4 KEY_KP6 is valid keyboard
static bool isValidKeyboard(const std::string &path)
{for (unsigned int i = 0; i < (sizeof(NEEDED_KEY) / sizeof(int)); i++){if (!isSupportTypeCode(path, EV_KEY, NEEDED_KEY[i])){return false;}}return true;
}static std::vector<std::string> getAllValidKeyboard()
{std::vector<std::string> validDevice;std::vector<std::string> allDevice = getAllEventDevicePath();for (unsigned int i = 0; i < allDevice.size(); i++){if (isValidKeyboard(allDevice[i])){validDevice.push_back(allDevice[i]);}}return validDevice;
}static std::string getOneNumLockDevice()
{std::vector<std::string> allDevice = getAllEventDevicePath();for (unsigned int i = 0; i < allDevice.size(); i++){if (isSupportTypeCode(allDevice[i], EV_LED, LED_CAPSL)){return allDevice[i];}}return std::string("");
}static int uinputFd = 0;// program should exit
static volatile bool shouldStop = false;// if numlock is on, we don't move mouse
static bool numlockOn = false;const static int UP = 0;
const static int DOWN = 1;
const static int LEFT = 2;
const static int RIGHT = 3;
const static int DIRECTION_NUM = 4;// move status
// change to true/false when corresponding key is pressed/released
static bool isMoving[DIRECTION_NUM] = { false };// move times from the point that corresponding key is pressed
// reset to 0 when key is released
static std::atomic_int moveTimes[DIRECTION_NUM];inline void resetMoveTimes()
{for (int i = 0; i < DIRECTION_NUM; i++){moveTimes[i] = 0;}
}// move step is affected by move times
// so we can apply some acceleration strategy
static int moveStep(int times)
{const int MIN_STEP = 1;const int MAX_STEP = 10;const int MIN_POINT = 50;const int MAX_POINT = 200;if (times <= MIN_POINT){return MIN_STEP;}else if (times <= MAX_POINT){return MIN_STEP + (times - MIN_POINT) * 1.0 / (MAX_POINT - MIN_POINT) * (MAX_STEP - MIN_STEP);}else{return MAX_STEP;}
}// write mouse move event every timeInterval ms
static int timeInterval = 10;static void setIsMoving(int index, int value)
{isMoving[index] = value;if (!value){moveTimes[index] = 0;}
}static void keyAction(int index, int value)
{ENSURE(index >= 0 && index < DIRECTION_NUM);if (value == 1){setIsMoving(index, true);}else if (value == 0){setIsMoving(index, false);}else{// do nothing}
}static void handleKeyEvent(const struct input_event &ev)
{if (numlockOn){return;}int index = -1;switch (ev.code){case KEY_KP8: index = UP; break;case KEY_KP2: index = DOWN; break;case KEY_KP4: index = LEFT; break;case KEY_KP6: index = RIGHT; break;default: index = -1; break;}if (index != -1){keyAction(index, ev.value);}
}static void handleLEDEvent(const struct input_event &ev)
{if (ev.code == LED_NUML){if (ev.value == 0){numlockOn = false;}else if (ev.value == 1){numlockOn = true;for (int i = 0; i < DIRECTION_NUM; i++){setIsMoving(i, false);}}}
}static void handleEvent(const struct input_event &ev)
{if (ev.type == EV_KEY){handleKeyEvent(ev);}else if (ev.type == EV_LED){handleLEDEvent(ev);}
}static void handleDevice(int fd)
{struct input_event ev[64];int rd = read(fd, ev, sizeof(ev));ENSURE(rd % sizeof(struct input_event) == 0, "expected %d bytes, got %d\n", (int) sizeof(struct input_event), rd % (int) sizeof(struct input_event));int n = rd / sizeof(struct input_event);for (int j = 0; j < n; j++){handleEvent(ev[j]);}
}static void interrupt_handler(int sig)
{(void) sig;shouldStop = true;
}static bool queryNumlock()
{std::string ledDevice = getOneNumLockDevice();// if there is no led device, we suppose that numlock is always offif (ledDevice == std::string("")){return false;}FileOpener opener(ledDevice.c_str(), O_RDONLY);int fd = opener.getFd();ENSURE(fd >= 0);unsigned long supportLED[nbytes(LED_MAX)];ioctl(fd, EVIOCGLED(sizeof(supportLED)), supportLED);if (testBits(LED_NUML, supportLED)){return true;}else{return false;}
}static void incMoveTimes()
{for (int i = 0; i < DIRECTION_NUM; i++){if (isMoving[i]){moveTimes[i]++;}}
}static void calcMoveSteps(int *steps)
{for (int i = 0; i < DIRECTION_NUM; i++){if (isMoving[i]){steps[i] = moveStep(moveTimes[i]);}}// if opposite key is pressed, we set the steps all to 0if (isMoving[UP] && isMoving[DOWN]){steps[UP] = 0;steps[DOWN] = 0;}if (isMoving[LEFT] && isMoving[RIGHT]){steps[LEFT] = 0;steps[RIGHT] = 0;}
}// ok, let's write device to move the mouse
static void writeDevice(int *steps)
{int x = steps[RIGHT] - steps[LEFT];int y = steps[DOWN] - steps[UP];struct input_event ev[3];memset(ev, 0, sizeof(ev));ev[0].type = EV_REL;ev[0].code = REL_X;ev[0].value = x;ev[1].type = EV_REL;ev[1].code = REL_Y;ev[1].value = y;ev[2].type = EV_SYN;ev[2].code = SYN_REPORT;int ret = write(uinputFd, ev, sizeof(ev));ENSURE(ret == sizeof(ev));
}static void moveMouse()
{incMoveTimes();int steps[DIRECTION_NUM] = { 0 };calcMoveSteps(steps);writeDevice(steps);
}static void *moveMouseThread(void *arg)
{(void) arg;while(true){moveMouse();usleep(timeInterval * 1000);}return nullptr;
}int main()
{uinputFd  = open(UINPUT_NAME, O_WRONLY);ENSURE(uinputFd >= 0);createVirtualMouse(uinputFd);numlockOn = queryNumlock();std::vector<std::string> kbds = getAllValidKeyboard();ENSURE(kbds.size() >= 1, "There should be at least one keyboard");struct pollfd fds[kbds.size()];memset(fds, 0, sizeof(fds));for (unsigned int i = 0; i < kbds.size(); i++){fds[i].fd = open(kbds[i].c_str(), O_RDONLY);ENSURE(fds[i].fd >= 0, "Open keyboard %s failed", kbds[i].c_str());fds[i].events = POLLIN;}signal(SIGINT, interrupt_handler);signal(SIGTERM, interrupt_handler);pthread_t tid;int ret = pthread_create(&tid, nullptr, moveMouseThread, nullptr);ENSURE(ret == 0);resetMoveTimes();while (!shouldStop){for (unsigned int i = 0; i < kbds.size(); i++){fds[i].revents = 0;}poll(fds, kbds.size(), -1);for (unsigned int i = 0; i < kbds.size(); i++){if (fds[i].revents & POLLIN){handleDevice(fds[i].fd);}}}for (unsigned int i = 0; i < kbds.size(); i++){close(fds[i].fd);}return 0;
}

在Linux下实现一个使用键盘控制的虚拟鼠标相关推荐

  1. Windows的启动u盘linux,如何在linux下制作一个windows的可启动u盘?

    如何在linux下制作一个windows的可启动u盘? 情景是这样的,有一个windows10的iso,现在想通过U盘安装,要求即支持UEFI(启动引导器),又支持Legacy(启动引导器),因为有一 ...

  2. linux加法计算器程序,Linux下实现一个计算器程序

    (集体内容详见ChinaUnix社区) 现在要在Linux下实现一个计算器程序, 它有如下的要求: 1.  能识别英文数字:比如 three hundred and ninety two, 要能转换为 ...

  3. linux查看进程grep工作组,Linux下查看一个进程打开了哪...-linux 如何找到进程的工作目录...-使用 grep 恢复误删的文本文件_169IT.COM...

    Linux下查看一个进程打开了哪些文件的命令示例,供大家学习参考. 查看进程14755(httpd)打开了哪些文件: 代码如下: localhost:~# lsof -p 14755 COMMAND ...

  4. linux命令进u 盘,在Linux下制作一个磁盘文件,在u-boot 阶段对emmc 烧写整个Linux系统方法...

    在Linux 下制作一个磁盘文件, 可以给他分区,以及存储文件,然后dd 到SD卡便可启动系统. 在u-boot 下启动后可以读取该文件,直接在u-boot 阶段就可以做烧写操作,省略了进入系统后才进 ...

  5. linux应用程序的编写实验原理,操作系统实验 1.在linux下编写一个应用程序 联合开发网 - pudn.com...

    操作系统实验 所属分类:Linux/Unix编程 开发工具:C/C++ 文件大小:1KB 下载次数:3 上传日期:2019-05-01 20:34:21 上 传 者:烟雨南风起 说明:  1.在lin ...

  6. 在Linux下执行一个文件时候提示“权限不够”的解决办法

    在Linux下执行一个文件时候提示"权限不够"的解决办法如下 转到那个文件的目录下面执行下面命令: chmod 777 filename

  7. linux编写php,Linux 下编写一个 PHP 扩展

    假设需求 开发一个叫做 helloWord 的扩展. 扩展里有一个函数,helloWord(). echo helloWord('Tom'); //返回:Hello World: Tom 本地环境 P ...

  8. 解决Linux下无法利用shell脚本启动conda虚拟环境问题

    解决Linux下无法利用shell脚本启动conda虚拟环境问题 问题 直接在脚本中激活conda环境时遇到了一个问题 解决 查看.bashrc文件 .bashrc文件在 /home/用户名 里面,因 ...

  9. 在Linux下写一个自己的命令

    01 什么是命令? 很多朋友对Linux的一个印象就是各种各样的命令行,在图形化界面不够成熟的时期,Linux系统开发人员都是通过命令行直接操控OS,其实和DOS系统差不多,几乎可以通过键盘搞定所有的 ...

  10. linux+笔记:linux下判断一个文件或者目录是否存在

    工作中涉及到文件系统,有时候需要判断文件和目录是否存在,下面是一些前人的经验分享. 转自:http://blog.csdn.net/adcxf/article/details/6386901 转自:h ...

最新文章

  1. 互联网时代的云服务器四大功能
  2. Java多线程之多线程工程代码编写思维方式
  3. 汇编语言(十一)之统计非数字字符个数
  4. python对角线图_python对角线图_python – 在Seaborn Jointplot上绘制对角线(相等的线)...
  5. [CF671E] Organizing a Race
  6. python 打印输出xml数据问题
  7. mysql 枚举索引_MySQL 索引总结
  8. 循环结构中break、continue、return和exit的区别
  9. 学习打印机,了解打印命令
  10. 实现一周之内自动登录的 cookie和session还有localStorage的存储机制
  11. 【PhD Debate - 14】将科幻照进现实——元宇宙数字人的当下与未来
  12. epson喷墨一体机打印照片偏色的调整
  13. c语言解一元二次方程代码,一元二次方程求解程序完整代码
  14. 单点登录 Ucenter 分析
  15. 中国电子商务网经的盈利策略
  16. blender绑定后,姿态模式 骨骼动 模型不动
  17. 域环境SID相同到底有什么影响?
  18. Elasticsearch引入可搜索快照(searchable snapshot)
  19. iPhone4S降级至iOS6
  20. keytool-importkeypair 的使用

热门文章

  1. java小游戏实战局域网联机_结对编程3——黄金点小游戏实现局域网联机
  2. js制作简易班级抽签程序
  3. 公转对讲融合项目如何实现对接?
  4. 插头DP/轮廓线DP
  5. Oracle的卸载步骤(详细图示)
  6. 计算机无法预览文件,win10系统百度网盘文件无法预览的解决方法
  7. ab服务器网页,使用ab进行web服务器压测详解
  8. Acme CAD Converter 8.9.8.1480 汉化破解版本
  9. sprintf函数用法详解
  10. PCL .stl格式转成.pcd格式点云文件