按键精灵想必很多人都玩过,使用录制功能将鼠标和键盘的操作录制下来,录制好后就可以通过回放自动执行之前录制的操作,可以设置重复执行的次数,这样就可以将一些重复的劳动交给脚本自动化去完成。使用Python编写一个和脚本精灵一样的程序非常简单,并且代码量足够少,好处是可以不再依赖按键精灵,像我这种有轻微洁癖的程序猿就喜欢自己动手实现,依赖Python的为所欲为的特性,可以任意编码让自己的按键精灵更加强大。
按键精灵的实现可以拆解分为录制和回放两个步骤,对应到Python程序的实现也可以分为两步:
1.监听鼠标键盘的事件和坐标,写入到文件中记录起来。
2.读取监听时写入的文件,执行文件中的坐标和事件操作。
要实现这两个功能,得先从基础开始。我把任务拆解成两大部分,鼠标和键盘属于不同的两个输入设备,所以分开实现“鼠标精灵”和“键盘精灵”两个程序,最后融合这两个模块实现一个相对完整的按键精灵。
鼠标事件监听from pynput import mouse
# 鼠标移动事件
def on_move(x, y):
print('[Move]', (x, y))
# 鼠标点击事件
def on_click(x, y, button, pressed):
print('[Click]', (x, y, button.name, pressed))
# 鼠标滚动事件
def on_scroll(x, y, x_axis, y_axis):
print('[Scroll]', (x, y, x_axis, y_axis))
# 监听事件绑定
with mouse.Listener(on_move=on_move, on_click=on_click, on_scroll=on_scroll) as listener:
listener.join()
onMove(x,y)函数接收鼠标当前的x轴和y轴坐标,启动程序并移动鼠标时,就会调用该方法。
on_click(x, y, button, pressed)函数接收鼠标的点击事件,x和y为当前点击事件的鼠标坐标,button参数对象的name属性值为left或者right,通过该属性值可以判断是鼠标的左键还是右键产生的点击事件,pressed参数值为True时表示当前鼠标左或右键按压,False时表示鼠标左或右键抬起事件。
on_scroll(x, y, x_axis, y_axis)接收四个参数,前两个参数依旧是当前事件的鼠标坐标轴,x_axis的值>0表示向上,<0表示向下,同样的y_axis的负值和正值代表左滑和右滑状态。
鼠标事件执行from pynput.mouse import Button, Controller
import time
# 获取鼠标对象
mouse = Controller()
# 输出鼠标当前的坐标
print(mouse.position)
# 将新的坐标赋值给鼠标对象
mouse.position = (100, 500)
for index in range(0, 30):
# 鼠标移动到指定坐标轴
mouse.move(index, -index)
print(mouse.position)
time.sleep(0.01)
for index in range(0, 30):
# 鼠标移动到指定坐标轴
mouse.move(-index, index)
print(mouse.position)
time.sleep(0.01)
# 鼠标右键按下
mouse.press(Button.right)
time.sleep(0.01)
# 鼠标右键抬起
mouse.release(Button.right)
# 鼠标左键点击
mouse.click(Button.left, 1)
# 鼠标滚轮滚动距离500
mouse.scroll(0, 500)
和鼠标事件监听一样,对应的我们可以操作鼠标的各种事件:移动、左/右按压、左/右抬起、左/右点击、上下左右滚动
上面代码中的mouse.move(x,y)函数,表示从当前鼠标位置进行位移的距离,x和y的值是以当前位置为0点开始算的。
而且不能简单的用坐标轴去相减得到位移距离,所以后续的程序我会使用mouse.position = (x, y)这个函数来操作鼠标的移动,
这个函数可以将鼠标设置到指定位置,只要我们记录之前的鼠标移动轨迹,就可以通过读取之前的记录文件按顺序重新对鼠标进行赋值操作。达到回放的效果。
带录制回放功能的鼠标精灵
结合鼠标事件的监听和执行,并且将事件记录到文件中,再加上Python自带的GUI,就可以写出一个简单的鼠标精灵了。
实现思路:定义一个json格式的对象来统一不同鼠标事件的内容格式{
"name":"mouse",
"event":"click",
"target":"left",
"action":true,
"location":{
"x":"0",
"y":"0"
}
}
鼠标事件的name值为mouse,考虑到后续还有可能会有其他设备的事件,比如键盘事件。
鼠标的事件为点击事件,将event赋值为click。
target表示目标,点击了鼠标左键,所以目标值为left
action表示动作,鼠标点击分为按压和抬起,true表示抬起。
location的值包含一个json对象,里面为当前事件鼠标的x和y坐标。
鼠标的移动和滑动事件以此类推,用同样的模板格式进行记录。将记录的数据写入到文件中
执行回放时通过逐行读取文件,解析文件中的json数据通过name、event、location等值进行事件执行。
之所以选择json时因为解析起来比较方便。当然你也可以使用数据库,比如SQLite等,或者自己定义一套自己的格式。
json的存储方式有太多的冗余字符了,空间占用目前不是考虑的首要因素,这里先用最快的方式实现它。
GUI用了Python自带的tkinter库,为了防止UI阻塞,所以使用了多线程,同样是Python自带库threading。
整个实现其实只用了一个三方库 pynput
鼠标事件的监听录制和执行回放的完整代码如下:import json
import threading
import time
import tkinter
from pynput import mouse
from pynput.mouse import Button, Controller
# 鼠标动作模板
def mouse_action_template():
return {
"name": "mouse",
"event": "default",
"target": "default",
"action": "default",
"location": {
"x": "0",
"y": "0"
}
}
# 鼠标动作监听
class MouseActionListener(threading.Thread):
def __init__(self, file_name):
super().__init__()
self.file_name = file_name
def run(self):
with open(self.file_name, 'w', encoding='utf-8') as file:
# 鼠标移动事件
def on_move(x, y):
template = mouse_action_template()
template['event'] = 'move'
template['location']['x'] = x
template['location']['y'] = y
file.writelines(json.dumps(template) + "\n")
file.flush()
# 鼠标点击事件
def on_click(x, y, button, pressed):
template = mouse_action_template()
template['event'] = 'click'
template['target'] = button.name
template['action'] = pressed
template['location']['x'] = x
template['location']['y'] = y
file.writelines(json.dumps(template) + "\n")
file.flush()
# 鼠标滚动事件
def on_scroll(x, y, x_axis, y_axis):
template = mouse_action_template()
template['event'] = 'scroll'
template['location']['x'] = x_axis
template['location']['y'] = y_axis
file.writelines(json.dumps(template) + "\n")
file.flush()
with mouse.Listener(on_move=on_move, on_click=on_click, on_scroll=on_scroll) as listener:
listener.join()
# 鼠标动作执行
class MouseActionExecute(threading.Thread):
def __init__(self, file_name):
super().__init__()
self.file_name = file_name
def run(self):
with open(self.file_name, 'r', encoding='utf-8') as file:
mouse_exec = Controller()
line = file.readline()
time.sleep(0.01)
while line:
obj = json.loads(line)
if obj['name'] == 'mouse':
if obj['event'] == 'move':
mouse_exec.position = (obj['location']['x'], obj['location']['y'])
time.sleep(0.01)
elif obj['event'] == 'click':
if obj['action']:
if obj['target'] == 'left':
mouse_exec.press(Button.left)
else:
mouse_exec.press(Button.right)
else:
if obj['target'] == 'left':
mouse_exec.release(Button.left)
else:
mouse_exec.release(Button.right)
time.sleep(0.01)
elif obj['event'] == 'scroll':
mouse_exec.scroll(obj['location']['x'], obj['location']['y'])
time.sleep(0.01)
line = file.readline()
def button_onClick(action):
m1 = MouseActionListener(file_name='mouse.action')
m2 = MouseActionExecute(file_name='mouse.action')
if action == 'listener':
if startListenerBtn['text'] == '录制':
m1.start()
startListenerBtn['text'] = '录制中...关闭程序停止录制'
startListenerBtn['state'] = 'disabled'
elif action == 'execute':
if startExecuteBtn['text'] == '回放':
m2.start()
startExecuteBtn['text'] = '回放中...关闭程序停止回放'
startExecuteBtn['state'] = 'disabled'
if __name__ == '__main__':
root = tkinter.Tk()
root.title('鼠标精灵-蓝士钦')
root.geometry('200x200+400+100')
startListenerBtn = tkinter.Button(root, text="录制", command=lambda: button_onClick('listener'))
startListenerBtn.place(x=10, y=10, width=180, height=80)
startExecuteBtn = tkinter.Button(root, text="回放", command=lambda: button_onClick('execute'))
startExecuteBtn.place(x=10, y=110, width=180, height=80)
root.mainloop()
按键精灵的鼠标部分到这里就基本完成了。
运行程序,点击录制,然后就可以用你的鼠标在屏幕上一顿操作。然后关闭本程序。
接着重新打开程序,点击回放,就会发现鼠标可以按照之前录制的动作进行自动工作了。
记住千万不要在录制时,还没关闭程序的时候就点击回放,这样会陷入无限循环里面,会导致不停的录制不停的回放。
还有键盘的程序后续补上,程序待完善中,未完待续。
键盘事件监听from pynput import keyboard
# 按键按下监听
def on_press(key):
try:
print('press key {0}, vk: {1}'.format(key.char, key.vk))
except AttributeError:
print('special press key {0}, vk: {1}'.format(key, key.value.vk))
# 按键释放监听
def on_release(key):
if key == keyboard.Key.esc:
# 停止监听
return False
try:
print('release key {0}, vk: {1}'.format(key.char, key.vk))
except AttributeError:
print('special release key {0}, vk: {1}'.format(key, key.value.vk))
# 键盘监听
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
键盘监听相对于鼠标监听来说,回调的函数只有两个on_press按键按下 和on_release按键释放。
由于pynput这个库对键盘的不同按键事件封装进行了区分,比如普通的数字和字母键按下会进入on_press方法,通过传入的key.char属性值可以得到按键对应在键盘上的字符,但如果是Shift等其他特殊键,就没有char属性,会产生异常。
只要捕获异常后直接通过key就可以取到特殊键对应的字符。我觉得这是pynput做得有点不够优雅的地方。
普通的键有key.vk属性值,代表键盘上字符对应的编码值,特殊键的编码值要通过key.value.vk来取。
键盘事件执行from pynput.keyboard import Key, Controller, KeyCode
# 键盘控制对象
keyboard = Controller()
# 按下 a 键
keyboard.press('a')
# 释放 a 键
keyboard.release('a')
# 按下 Shift 键
keyboard.press(Key.shift)
keyboard.press('b')
keyboard.release('b')
keyboard.press('c')
keyboard.release('c')
# 释放 Shift 键
keyboard.release(Key.esc)
# 按下 Shift 键,然后依次按下其他按键,完成后Shift键自动释放
with keyboard.pressed(Key.shift):
keyboard.press('d')
keyboard.release('d')
keyboard.press('e')
keyboard.release('e')
# 依次按下 python (包括前面的空格)
keyboard.type(' python')
# 按下 vk值为56的键 shift 键
keyboard.touch(KeyCode.from_vk(56), True)
keyboard.touch('a', True)
keyboard.touch('a', False)
# 释放 shift 键
keyboard.touch(Key.shift, False)
和监听方法对应,执行键盘的按键方法有press(key)按压 release(key)释放。
除此之外还有touch(key,is_press)函数,key表示要操作的键,is_press 为True时表示按压,为False时表示释放。
无论是哪种按压事件,Key都可以通过其他方式构造,比如知道Shift的vk值为56,那么就可以通过KeyCode.from_vk(56)来构造一个Shift的Key。
通过VK编码构造Key的方式很有用,因为当你要按出一个@符号时,需要同时按住Shift和2。通过on_press(key)监听得到的值是一个@符号,如果录制程序录制了一个@符号将无法通过keyboard.press('@')这种方式直接执行。
所以接下来的键盘录制回放程序我将通过定义一个键盘动作模板,然后通过VK值准确的记录每个键以及每个组合键的编码。然后通过keyboard.press(KeyCode.from_vk(vk))进行回放。
带录制回放功能的键盘精灵import json
import threading
import time
import tkinter
from pynput import keyboard
from pynput.keyboard import Controller, KeyCode
# 键盘动作模板
def keyboard_action_template():
return {
"name": "keyboard",
"event": "default",
"vk": "default"
}
# 键盘动作监听
class KeyboardActionListener(threading.Thread):
def __init__(self, file_name):
super().__init__()
self.file_name = file_name
def run(self):
with open(self.file_name, 'w', encoding='utf-8') as file:
# 键盘按下监听
def on_press(key):
template = keyboard_action_template()
template['event'] = 'press'
try:
template['vk'] = key.vk
except AttributeError:
template['vk'] = key.value.vk
finally:
file.writelines(json.dumps(template) + "\n")
file.flush()
# 键盘抬起监听
def on_release(key):
if key == keyboard.Key.esc:
# 停止监听
startListenerBtn['text'] = '录制'
startListenerBtn['state'] = 'normal'
return False
template = keyboard_action_template()
template['event'] = 'release'
try:
template['vk'] = key.vk
except AttributeError:
template['vk'] = key.value.vk
finally:
file.writelines(json.dumps(template) + "\n")
file.flush()
# 键盘监听
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
# 键盘动作执行
class KeyboardActionExecute(threading.Thread):
def __init__(self, file_name):
super().__init__()
self.file_name = file_name
def run(self):
with open(self.file_name, 'r', encoding='utf-8') as file:
keyboard_exec = Controller()
line = file.readline()
time.sleep(3)
while line:
obj = json.loads(line)
if obj['name'] == 'keyboard':
if obj['event'] == 'press':
keyboard_exec.press(KeyCode.from_vk(obj['vk']))
time.sleep(0.01)
elif obj['event'] == 'release':
keyboard_exec.release(KeyCode.from_vk(obj['vk']))
time.sleep(0.01)
line = file.readline()
startExecuteBtn['text'] = '回放'
startExecuteBtn['state'] = 'normal'
def button_onClick(action):
m1 = KeyboardActionListener(file_name='keyboard.action')
m2 = KeyboardActionExecute(file_name='keyboard.action')
if action == 'listener':
if startListenerBtn['text'] == '录制':
m1.start()
startListenerBtn['text'] = '录制中...esc停止录制'
startListenerBtn['state'] = 'disabled'
elif tener'))
startListenerBtn.place(x=10, y=10, width=180, height=80)
startExecuteBtn = tkinter.Button(root, text="回放", command=lambda: button_onClick('execute'))
startExecuteBtn.place(x=10, y=110, width=180, height=80)
root.mainloop()
键盘精灵的录制和回放程序到这里以及算是一个基础版本了,可以正常使用。并且新增了esc键监听,当用户点击esc时将会结束录制。
鼠标精灵和键盘精灵都可以单独的运行使用。
大多数场景下这两者的功能都会使用到,所以接下来我要实现一个完成的按键精灵,同时包含鼠标和键盘的录制回放功能。
在之前的代码基础上进一步封装。
考虑的基本要素如下:记录鼠标和记录键盘的事件采用不同的json模板进行定义,采用响应式对用户的操作进行监听,用户静止不动则不会写入文件。
同时监听鼠标和键盘,为了避免多线程写同一个文件的锁操作,我将鼠标和键盘的录制记录分为两个不同的文件。
录制和回放的操作通常都需要有一个等待时间的设置,所以代码里加上了GUI的设置部分,GUI没有设计所以后续这块代码要优化。
考虑录制和回放倒计时需要UI提示用户并且定时触发线程执行,所以封装了一个UI更新线程的类。
按键精灵0.1版本完整代码如下:
键鼠录制的按键精灵0.1版本import json
import threading
import time
import tkinter
from pynput import keyboard, mouse
from pynput.keyboard import Controller as KeyBoardController, KeyCode
from pynput.mouse import Button, Controller as MouseController
# 键盘动作模板
def keyboard_action_template():
return {
"name": "keyboard",
"event": "default",
"vk": "default"
}
# 鼠标动作模板
def mouse_action_template():
return {
"name": "mouse",
"event": "default",
"target": "default",
"action": "default",
"location": {
"x": "0",
"y": "0"
}
}
# 倒计时监听,更新UI触发自定义线程对象
class UIUpdateCutDownExecute(threading.Thread):
def __init__(self, cut_down_time, custom_thread_list):
super().__init__()
self.cut_down_time = cut_down_time
self.custom_thread_list = custom_thread_list
def run(self):
while self.cut_down_time > 0:
for custom_thread in self.custom_thread_list:
if custom_thread['obj_ui'] is not None:
custom_thread['obj_ui']['text'] = str(self.cut_down_time)
custom_thread['obj_ui']['state'] = 'disabled'
self.cut_down_time = self.cut_down_time - 1
time.sleep(1)
else:
for custom_thread in self.custom_thread_list:
if custom_thread['obj_ui'] is not None:
custom_thread['obj_ui']['text'] = custom_thread['final_text']
custom_thread['obj_ui']['state'] = 'disabled'
if custom_thread['obj_thread'] is not None:
custom_thread['obj_thread'].start()
time.sleep(1)
# 键盘动作监听
class KeyboardActionListener(threading.Thread):
def __init__(self, file_name='keyboard.action'):
super().__init__()
self.file_name = file_name
def run(self):
with open(self.file_name, 'w', encoding='utf-8') as file:
# 键盘按下监听
def on_press(key):
template = keyboard_action_template()
template['event'] = 'press'
try:
template['vk'] = key.vk
except AttributeError:
template['vk'] = key.value.vk
finally:
file.writelines(json.dumps(template) + "\n")
file.flush()
# 键盘抬起监听
def on_release(key):
if key == keyboard.Key.esc:
# 停止监听
startListenerBtn['text'] = '开始录制'
startListenerBtn['state'] = 'normal'
keyboardListener.stop()
return False
template = keyboard_action_template()
template['event'] = 'release'
try:
template['vk'] = key.vk
except AttributeError:
template['vk'] = key.value.vk
finally:
file.writelines(json.dumps(template) + "\n")
file.flush()
# 键盘监听
with keyboard.Listener(on_press=on_press, on_release=on_release) as keyboardListener:
keyboardListener.join()
# 键盘动作执行
class KeyboardActionExecute(threading.Thread):
def __init__(self, file_name='keyboard.action', execute_count=0):
super().__init__()
self.file_name = file_name
self.execute_count = execute_count
def run(self):
while self.execute_count > 0:
with open(self.file_name, 'r', encoding='utf-8') as file:
keyboard_exec = KeyBoardController()
line = file.readline()
while line:
obj = json.loads(line)
if obj['name'] == 'keyboard':
if obj['event'] == 'press':
keyboard_exec.press(KeyCode.from_vk(obj['vk']))
time.sleep(0.01)
elif obj['event'] == 'release':
keyboard_exec.release(KeyCode.from_vk(obj['vk']))
time.sleep(0.01)
line = file.readline()
startExecuteBtn['text'] = '开始回放'
startExecuteBtn['state'] = 'normal'
self.execute_count = self.execute_count - 1
# 鼠标动作监听
class MouseActionListener(threading.Thread):
def __init__(self, file_name='mouse.action'):
super().__init__()
self.file_name = file_name
def run(self):
with open(self.file_name, 'w', encoding='utf-8') as file:
# 鼠标移动事件
def on_move(x, y):
template = mouse_action_template()
template['event'] = 'move'
template['location']['x'] = x
template['location']['y'] = y
file.writelines(json.dumps(template) + "\n")
file.flush()
# 鼠标点击事件
def on_click(x, y, button, pressed):
template = mouse_action_template()
template['event'] = 'click'
template['target'] = button.name
template['action'] = pressed
template['location']['x'] = x
template['location']['y'] = y
file.writelines(json.dumps(template) + "\n")
file.flush()
# 鼠标滚动事件
def on_scroll(x, y, x_axis, y_axis):
template = mouse_action_template()
template['event'] = 'scroll'
template['location']['x'] = x_axis
template['location']['y'] = y_axis
file.writelines(json.dumps(template) + "\n")
file.flush()
with mouse.Listener(on_move=on_move, on_click=on_click, on_scroll=on_scroll) as mouseListener:
mouseListener.join()
# 鼠标动作执行
class MouseActionExecute(threading.Thread):
def __init__(self, file_name='mouse.action', execute_count=0):
super().__init__()
self.file_name = file_name
self.execute_count = execute_count
def run(self):
while self.execute_count > 0:
with open(self.file_name, 'r', encoding='utf-8') as file:
mouse_exec = MouseController()
line = file.readline()
while line:
obj = json.loads(line)
if obj['name'] == 'mouse':
if obj['event'] == 'move':
mouse_exec.position = (obj['location']['x'], obj['location']['y'])
time.sleep(0.01)
elif obj['event'] == 'click':
if obj['action']:
if obj['target'] == 'left':
mouse_exec.press(Button.left)
else:
mouse_exec.press(Button.right)
else:
if obj['target'] == 'left':
mouse_exec.release(Button.left)
else:
mouse_exec.release(Button.right)
time.sleep(0.01)
elif obj['event'] == 'scroll':
mouse_exec.scroll(obj['location']['x'], obj['location']['y'])
time.sleep(0.01)
line = file.readline()
def command_adapter(action):
if action == 'listener':
if startListenerBtn['text'] == '开始录制':
custom_thread_list = [
{
'obj_thread': KeyboardActionListener(),
'obj_ui': startListenerBtn,
'final_text': '录制中...esc停止录制'
},
{
'obj_thread': MouseActionListener(),
'obj_ui': None,
'final_text': None
}
]
UIUpdateCutDownExecute(startTime.get(), custom_thread_list).start()
elif action == 'execute':
if startExecuteBtn['text'] == '开始回放':
custom_thread_list = [
{
'obj_thread': KeyboardActionExecute(execute_count=playCount.get()),
'obj_ui': startExecuteBtn,
'final_text': '回放中...关闭程序停止回放'
},
{
'obj_thread': MouseActionExecute(execute_count=playCount.get()),
'obj_ui': None,
'final_text': None
}
]
UIUpdateCutDownExecute(endTime.get(), custom_thread_list).start()
def isNumber(content):
if content.isdigit() or content == "":
return True
else:
return False
if __name__ == '__main__':
root = tkinter.Tk()
root.title('按键精灵-蓝士钦')
root.geometry('200x200+400+100')
listenerStartLabel = tkinter.Label(root, text='录制倒计时')
listenerStartLabel.place(x=10, y=10, width=80, height=20)
startTime = tkinter.IntVar()
listenerStartEdit = tkinter.Entry(root, textvariable=startTime)
listenerStartEdit.place(x=100, y=10, width=60, height=20)
startTime.set(3)
listenerTipLabel = tkinter.Label(root, text='秒')
listenerTipLabel.place(x=160, y=10, width=20, height=20)
startListenerBtn = tkinter.Button(root, text="开始录制", command=lambda: command_adapter('listener'))
startListenerBtn.place(x=10, y=45, width=180, height=30)
executeEndLabel = tkinter.Label(root, text='回放倒计时')
executeEndLabel.place(x=10, y=85, width=80, height=20)
endTime = tkinter.IntVar()
executeEndEdit = tkinter.Entry(root, textvariable=endTime)
executeEndEdit.place(x=100, y=85, width=60, height=20)
endTime.set(6)
executeTipLabel = tkinter.Label(root, text='秒')
executeTipLabel.place(x=160, y=85, width=20, height=20)
playCountLabel = tkinter.Label(root, text='回放次数')
playCountLabel.place(x=10, y=115, width=80, height=20)
playCount = tkinter.IntVar()
playCountEdit = tkinter.Entry(root, textvariable=playCount)
playCountEdit.place(x=100, y=115, width=60, height=20)
playCount.set(1)
playCountTipLabel = tkinter.Label(root, text='次')
playCountTipLabel.place(x=160, y=115, width=20, height=20)
startExecuteBtn = tkinter.Button(root, text="开始回放", command=lambda: command_adapter('execute'))
startExecuteBtn.place(x=10, y=145, width=180, height=30)
root.mainloop()
脚本精灵0.1版本完成?
还需要考虑的点:键盘事件没有记录用户每个动作之间的延迟时间,无法准确重放用户的输入节奏,后续考虑记录时间间隔点。
鼠标事件用户移动的越快,产生的点位变化也就越频繁,所以鼠标在回放时的速度与用户的操作基本一致。
鼠标没有停止回放的快捷键,要考虑如何停止回放鼠标事件。
输入法切换可能导致重放键盘按键时输入不准确,需要录制时是什么输入状态,重放时也要对应的键盘属性和状态
…还有很多需要考虑的点(原本只是想简单的做个示例程序)
程序只在MacOS平台上实验过,其他平台还未实验,一个相对完整的按键精灵在录制时应该获取更多的信息,这样在回放的时候才足够准确,后续考虑做一个更加精确的按键精灵,比如加入获取屏幕像素点,回放时通过采样比对,达到为所欲为功能。
GitHub地址:
https://github.com/lanshiqin/JerryMouse
期待大家来一起完善它?
来源:https://www.lanshiqin.com/2fb233e2/