Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

读取devices这种方式也是线程阻塞的吗?有考虑过使用pynput这种方式吗 #1

Open
TimmyWangDouDou opened this issue Feb 20, 2021 · 8 comments

Comments

@TimmyWangDouDou
Copy link

你好
看了你的实现方式,实时监听devices文件流。pynput库也提供了监听鼠标键盘的方法,不知道pynput的视线方式是否跟你的相同。

我最近在做一款centos系统下使用python记录当前用户键盘和鼠标点击次数的工具,我希望这个工具能在后台运行,但是目前使用pynput和pyxhook在交互界面运行脚本都可以捕获并记录下来,一旦配置到自启动脚本里运行,就没有数据了。想请教一下,有没有什么好的办法

感谢。

@Kuangcp
Copy link
Owner

Kuangcp commented Feb 20, 2021

  1. 是阻塞的
  2. 没懂需求,自启动监听到按键数据的话,用evdev是一定能实现的
    • 但是有个缺点是,监听的 InputEvent 会随着键盘的插拔发生改变,所以需要人为介入判断是否监听到正确的 InputEvent,
    • 也许有方法可以避开这个问题

@TimmyWangDouDou
Copy link
Author

感谢您的答复。

监听的 InputEvent 会随着键盘的插拔发生改变
对于这种情况,我有个想法。因为鼠标和键盘输入code的值有对应的鼠标值Map和键盘值Map,倘若我们仅需要鼠标和键盘2个事件,我们可以不配置具体的event给evdev,方法如下:
1.脚本启动时从/proc/bus/input/device获取所有mouse和kbd对应的event值
2.监听这些mouse和kbd事件,并且在每次收到事件时检查code是否在对应的Map内,若不在则重新获取所有mouse和kbd对应的event值并重新开始监听

因为需要达到效果的环境是centos6.9,所以我在/etc/rc.d/init.d中放入了自启动脚本,脚本会在系统加载时执行这行命令 python3 /usr/pynput/workinput.py (脚本和py文件都赋予了777权限),然后我重启系统,在图形界面上使用root登录后,仅看到脚本日志,没有py文件产生的鼠标键盘事件收集日志。所以想请问下您,是否是我的自启动配置不正确,还是有其他的方式来实现这种自启动呢?

我在stackoverflow上也提出了这个问题,里面有详细的源码和日志信息
https://stackoverflow.com/questions/66290544/how-can-i-monitor-mouse-and-keyboard-event-in-background-when-systemcentos6-9

@TimmyWangDouDou
Copy link
Author

TimmyWangDouDou commented Feb 22, 2021

今天找到init.d放自启动脚本不生效的原因了,主要是init.d在执行脚本时/etc/profile文件还没有加载,此时运行python命令会报找不到openssl相关库(3.8.7不自带openssl,但是使用python命令又需要,所以手动下载了openssl库,配置该库lib到profile以便所有用户都能获取到,然后重新重新配置了python3.8.7的Setup文件并编译)

目前将启动脚本放到了/etc/profile.d文件夹下,在用户登录后系统会自动运行改文件夹下的所有脚本。很庆幸,这样设置后在用户登录后,日志成功记录了用户的鼠标和键盘点击事件,但是交互界面被阻塞了,如下图所示:

image

我先猜想是py文件里主进程阻塞导致,然后在脚本内使用了线程来起监听,但是结果还是一样被阻塞。现在想一下,应该是系统加载profile.d文件夹内的所有sh脚本的主进程被阻塞了,想请问下您是如何解决这个阻塞的问题呢?

@Kuangcp
Copy link
Owner

Kuangcp commented Feb 22, 2021

我现在的实现方式是,监听的进程让他阻塞在后台运行,并做好需要的埋点;逻辑计算和交互新起一个进程 ;我的需求场景里,数据传递是单向的,也就是从监听进程到逻辑进程,目前都通过 redis,。个人感觉只能用进程间通信的方式来解决了。

你把监听配置成了启动加载,是否可以把监听脚本独立出来,然后以例如 (脚本执行命令 &)这样的方式运行,这样脚本进程会成为1号进程的子进程,不会阻塞加载的进程,我猜这样是可以的

@TimmyWangDouDou
Copy link
Author

我这里的场景数据传输也是单向的,从监听进程发送HTTP请求传输数据到一台server上接收。

今天按照您的提示在用户启动脚本里执行运行python脚本的命令行后面加了&就不再阻塞了 : )
但是遇到了一个新的难点:
round1:
我这边的场景需要知道鼠标或键盘单次事件是由哪个用户发起的,因为监听脚本是在root登录时启动的,所以我使用getpass和pwd获取当前用户始终是root
round2:
我使用不同用户(root和timmy)运行监听脚本(脚本输入到的日志目录相同),我在timmy登录窗口按下键盘,日志里在同一时间记录了2笔此次键盘事件如下图所示
image

在这种情境下,记录的用户键鼠事件就会比实际多了。想请教一下,这种原因是由于/dev/input/event键鼠事件是对整个系统硬件负责与登录用户无关导致的吗?是否有啥办法能处理这种情况呢

@TimmyWangDouDou
Copy link
Author

我的问题已经解决了。
Kuangcp,抱歉我这么晚才来更新结果。

我使用的环境是centos6.9,python版本3.8.4

1.关于不同用户登录系统重复启动监听程序的问题
由于启动python程序的sh脚本放在了profile.d文件夹内,所以任意用户登录系统时都会加载该脚本并启动监听程序。
为了避免重复记录并分清究竟是哪个登录用户进行了键盘和鼠标操作,我采取了以下策略:
a. 监听程序首先会判断当前系统用户,非root时直接退出程序
b. 通过 ps -ef|grep python的启动命令行来查看当前有哪些监听程序启动着
c. 确认已有监听程序并且调用者是为root,若果不是则杀死该进程并正常执行监听,若是则logger(root用户下已启动了监听) 并退出程序

2.如何判断当前的鼠标和键盘操作究竟是谁发起的
由于程序仅在root登录时启动,所以线程拥有者是root,此时获取到的当前用户始终只有root。若是timmy登录了系统进行操作,如何判断。我采用一种并非完全准确但实用的方式
a.监听程序内设置了一个3秒的scheduler
b.scheduler会检查3秒内键盘和鼠标的总事件数,若事件数为0则啥都不干,不为0则执行c操作
c.scheduler会根据 w -s -l 命令来返回信息中的IDLE(系统空闲时间)获取最小并且非0那行用户,该用户即为当前活跃用户
d.将此次3秒内的鼠标和键盘事件及活跃用户上传至数据收集服务器

*另外我发现一个有趣的事:
如果直接登录系统操作会记录所有的鼠标键盘事件,但通过VNC远程登录系统,则evdev不反馈任何通过VNC发起的鼠标和键盘操作。所以我去了解了下VNC的原理,是VNC客户端(发起远程的PC)通过RFB协议来通讯与VNC服务端(被远程的PC)通讯,服务端将接受到的键盘和鼠标操作通过应用层指令通知给了操作系统,所以会绕过底层硬件,从而evdev自然啥都不知道了

再次感谢Kuangcp 您宝贵的时间并不厌其烦的答疑!

@Kuangcp
Copy link
Owner

Kuangcp commented Apr 2, 2021

这个思路挺好的,在并行的用户活跃数不多的情况就比较精确了, 但是 w -s 不是总的活跃时间么,3s取样一次的按键数据, 这两个就没有必然的关联了,除非 计算 w -s 在3s 内的变化量。 我用的 manjaro 没有 -l 参数,不知道这个参数的意义

@TimmyWangDouDou
Copy link
Author

感谢您的肯定。

在我使用的centos6.9环境内,w后面跟-l和-s意义如下:
-l  使用详细格式列表,此为预设值
-s  使用简洁格式列表,不显示用户登入时间,终端机阶段作业和程序所耗费的CPU时间

IDLE表示某个程序上次从终端开始执行到现在所持续的时间。
举个例子,当前系统有俩用户A和B,监听程序在00:00:00启动,开始执行间隔3秒的schduler
此时时间是00:00:00,A登录系统并在终端命令行上输入了字母123
过了3秒,此时时间是00:00:03,w -s -l 结果显示有两行,A用户的IDLE值为3s,表示A用户在3秒前通过终端输入了命令123。和root用户输入的w -s -l IDLE值为0。 所以此时判断出活跃用户为A,记录的00:00:00~00:00:03这段时间内鼠标键盘统计总和属于A,上传数据后,清空计数器
又过了2秒钟,此时时间是00:00:05,B登录系统在终端命令行上输入了字母ABC
再过了1秒钟,此时时间是00:00:06, w -s -l结果有三行,显示A用户的IDLE值为6s,B用户的IDLE值为1s和root用户的 w -s -l IDLE为0。 所以此时判断出活跃用户为B,上传后并重置计数器

这种方式就像您说的在多用户同时活跃使用系统时就会变的不准确,我目前也想不出更好的办法了。
您提到的manjaro有机会我会尝试一下。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants