动机
很多人都会有一种经历,就是自己在截图的时候,就差一点点,但是因为鼠标移动太快,迟迟把握不住距离。又比如ps,ppt的时候,要把一个东西放到合适的位置,又是尴尬的因为速度太快而放不到地方。
从人机交互的角度理解,就是计算机不能够充分理解人类的意图。那么有两种解决方式,一是用自动化的方式,通过一种规则计算让计算机做出我们期望的应对。二是创造一种方式,给计算机传递合理的信息。
设计思路
手动微调
这个思路相比于纯自动是可以轻易实现的。
使用键盘输入上下左右,进行简单移动。实际上在ps和ppt位置放置里是有这个功能的,点一下可以通过上下左右移动。但是这个只是对图形进行作用,而不是传递到鼠标上的,在截图的时候就会很难受,我们需要的是在激活一种模式的情况下,将上下左右直接投射到鼠标上。
创建一个键盘监听,监控上下左右,对应微微移动鼠标即可。
调整逻辑
我们的调正并不是乱调,这会干扰其他鼠标操作,要搞清楚什么时候应该微调。仔细思考,我们最需要微调的时候其实只有按住鼠标左键的时候,所以逻辑也就很直观:如果鼠标处在按下状态,鼠标对我们的四个方向键才会有响应。
补充,键盘按下一个键是会连续输入的,所以可以直接实现按住快速滑动的效果。
你这时可能会反驳,其他情况下也会有需要啊,这就需要另一个激活机制了,这就是双击caps_lock键,同样可以激活,这样就可以同时使用方向键和鼠标移动了,在没有按下左键的情况下。
我还做了辅助按键,因为有时候上下左右是会有副作用的,这时就可以用wsad这类键去实现,这两个辅助键灵活使用有奇效。不激活也不会影响到正常输入。
代码
from pynput import mouse
from pynput import keyboard
from pynput.keyboard import Key, KeyCode
from pynput.mouse import Button
import time
class MouseBoard:
def __init__(self):
"""
本程序实现从键盘到鼠标移动的映射,可以实现当前电脑最小粒度微调
本程序专注于实现:移动
设有两个激活条件:双击caps_lock或者按下鼠标左键
一个退出激活条件:松开鼠标左键
当处于激活条件:可以使用辅助键或者上下左右实现移动,分别适用于非输入与输入情况
辅助键:默认edsf为上下左右,可以通过修改config中的四个键来实现自定义
不设界面,一个退出条件:esc键
"""
self.mouse_ctrl = mouse.Controller()
self.move_activate = False
self.activate_key = Key.caps_lock
self.pre_time = time.time()
self.time_threshold = 0.3
self.dir_keys = []
try:
with open('config.txt', encoding='utf-8') as file_obj:
line = file_obj.readline().strip()[0:4]
if line != '':
for i in range(4):
self.dir_keys.append(KeyCode.from_char(line[i]))
else:
self.dir_keys = [KeyCode.from_char('e'), KeyCode.from_char('d'),
KeyCode.from_char('s'), KeyCode.from_char('f')]
except FileNotFoundError as e:
self.dir_keys = [KeyCode.from_char('e'), KeyCode.from_char('d'),
KeyCode.from_char('s'), KeyCode.from_char('f')]
except IndexError as e:
self.dir_keys = [KeyCode.from_char('e'), KeyCode.from_char('d'),
KeyCode.from_char('s'), KeyCode.from_char('f')]
self.key_listener = keyboard.Listener(
on_press=self.on_press,
on_release=self.on_release
)
self.mouse_listener = mouse.Listener(
on_move=None,
on_click=self.mouse_click,
on_scroll=None
)
self.key_listener.start()
self.mouse_listener.start()
self.key_listener.join()
self.mouse_listener.join()
def on_press(self, key):
print(key, 'press')
if key == self.activate_key:
temp_time = time.time()
if temp_time - self.pre_time < self.time_threshold:
print('activate')
self.move_activate = True
self.pre_time = temp_time
if self.move_activate:
if key == Key.up or key == self.dir_keys[0]:
self.mouse_ctrl.move(0, -1)
elif key == Key.down or key == self.dir_keys[1]:
self.mouse_ctrl.move(0, 1)
elif key == Key.left or key == self.dir_keys[2]:
self.mouse_ctrl.move(-1, 0)
elif key == Key.right or key == self.dir_keys[3]:
self.mouse_ctrl.move(1, 0)
def on_release(self, key):
if key == Key.esc:
print('stop')
self.mouse_listener.stop()
self.key_listener.stop()
def mouse_click(self, x, y, button, pressed):
if button == Button.left:
if pressed:
self.move_activate = True
print('activate')
else:
self.move_activate = False
print('not activate')
if __name__ == "__main__":
MouseBoard()
操作冲突问题
你可能会很好奇,为什么我选择用caps_lock键作为滚动的激活键,事实是因为操作会大量冲突。
最开始我是打算按下鼠标中键,然后上下左右的,没响应,冲突。 后来尝试鼠标右键,不行。 那就用键盘,试了一下别的都有组合效果,只有shift,tab,caps了,但是shift也冲突,tab有单独作用,只有caps的作用和我们的目标不会在同时使用,所以就勉为其难地用了caps
纯自动——失败
失败的尝试
强化版:增加UI设置与托盘管理
代码如下,注释自看
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QFont, QCloseEvent
from PyQt5.QtWidgets import QWidget, QFormLayout, \
QLabel, QVBoxLayout, QHBoxLayout, QPushButton, \
QLineEdit, QSystemTrayIcon, QMenu, QAction, qApp, QApplication
import sys
from pynput import mouse
from pynput import keyboard
from pynput.keyboard import Key, KeyCode
from pynput.mouse import Button
import time
class Util:
@staticmethod
def get_auxiliary_keys():
auxiliary_keys = []
try:
with open('config.txt', 'r', encoding='utf-8') as file_obj:
line = file_obj.readline().strip()[0:4]
if line != '':
for i in range(4):
auxiliary_keys.append(KeyCode.from_char(line[i]))
else:
auxiliary_keys = [KeyCode.from_char('e'), KeyCode.from_char('d'),
KeyCode.from_char('s'), KeyCode.from_char('f')]
except FileNotFoundError as e:
auxiliary_keys = [KeyCode.from_char('e'), KeyCode.from_char('d'),
KeyCode.from_char('s'), KeyCode.from_char('f')]
except IndexError as e:
auxiliary_keys = [KeyCode.from_char('e'), KeyCode.from_char('d'),
KeyCode.from_char('s'), KeyCode.from_char('f')]
finally:
return auxiliary_keys
@staticmethod
def set_auxiliary_keys(line):
with open('config.txt', 'w', encoding='utf-8') as file_obj:
file_obj.write(line)
class MouseBoardUI(QWidget):
"""
显示主界面
"""
def __init__(self):
super(MouseBoardUI, self).__init__()
self.init_UI()
self.tray.show()
self.setWindowFlags(Qt.WindowStaysOnTopHint)
def init_UI(self):
self.setWindowTitle('MouseBoard辅助工具')
self.setWindowIcon(QIcon('MouseBoard.ico'))
self.fonts = [QFont(), QFont()]
self.fonts[0].setPointSize(30)
self.fonts[1].setPointSize(20)
self.state_show = QFormLayout()
self.activate_state = QLabel()
self.activate_state.setText(f'当前状态:休眠')
self.activate_state.setFont(self.fonts[0])
self.activate_key = QLabel()
self.activate_key.setText(f'激活方式:双击caps_lock或者按下鼠标左键')
self.activate_key.setFont(self.fonts[1])
self.main_key = QLabel()
self.main_key.setText(f'移动方式一:↑上 ↓下 ←左 →右')
self.main_key.setFont(self.fonts[1])
keys = Util.get_auxiliary_keys()
keys = [str(e).strip('\'') for e in keys]
self.auxiliary_key = QLabel()
self.auxiliary_key.setText(f'移动方式二:{keys[0]} 上 {keys[1]} 下 '
f'{keys[2]} 左 {keys[3]} 右')
self.auxiliary_key.setFont(self.fonts[1])
self.state_show.addRow(self.activate_state)
self.state_show.addRow(self.activate_key)
self.state_show.addRow(self.main_key)
self.state_show.addRow(self.auxiliary_key)
self.state_set = QVBoxLayout()
self.set_label = QLabel()
self.set_label.setText('按上下左右顺序设置辅助键(如 wsad)\n')
self.set_label.setFont(self.fonts[1])
self.state_set.addWidget(self.set_label)
self.state_update = QHBoxLayout()
self.state_set.addLayout(self.state_update)
self.directions_input = QLineEdit()
self.directions_input.setFont(self.fonts[0])
self.update_btn = QPushButton()
self.update_btn.setText('提交修改')
self.update_btn.setFont(self.fonts[0])
self.update_btn.clicked.connect(self.update_config)
self.state_update.addWidget(self.directions_input)
self.state_update.addWidget(self.update_btn)
vbox = QVBoxLayout()
vbox.addLayout(self.state_show)
vbox.addLayout(self.state_set)
self.setLayout(vbox)
self.tray = TrayModel(self)
def bound_BackListener(self, back_listener):
"""
因为MouseBoardUI要包含MouseBoard引用,MouseBoard要包含MouseBoardUI引用
所以不能再初始化实现,只能用一个函数实现
"""
self.MouseBoard = back_listener
def update_config(self):
line = self.directions_input.text()
Util.set_auxiliary_keys(line)
self.auxiliary_key.setText(f'移动方式二:{line[0]} 上 {line[1]} 下 '
f'{line[2]} 左 {line[3]} 右')
self.MouseBoard.auxiliary_keys = Util.get_auxiliary_keys()
def closeEvent(self, a0: QCloseEvent) -> None:
a0.ignore()
self.hide()
self.tray.show()
class TrayModel(QSystemTrayIcon):
"""
托盘显示
"""
def __init__(self, bound_window):
super(TrayModel, self).__init__()
self.window = bound_window
self.init_UI()
def init_UI(self):
self.setToolTip('MouseBoard')
self.menu = QMenu()
self.manage_action = QAction('参数设置', self, triggered=self.manage)
self.quit_action = QAction('退出MouseBoard', self, triggered=self.quit)
self.menu.addAction(self.manage_action)
self.menu.addAction(self.quit_action)
self.setContextMenu(self.menu)
self.setIcon(QIcon('MouseBoard.ico'))
self.activated.connect(self.app_click)
def manage(self):
self.window.showNormal()
self.window.activateWindow()
def quit(self):
qApp.quit()
def app_click(self):
self.window.showNormal()
self.window.activateWindow()
class MouseBoardBack:
def __init__(self, main_UI):
"""
本程序实现从键盘到鼠标移动的映射,可以实现当前电脑最小粒度微调
本程序专注于实现:移动
设有两个激活条件:双击caps_lock或者按下鼠标左键
一个退出激活条件:松开鼠标左键
当处于激活条件:可以使用辅助键或者上下左右实现移动,分别适用于非输入与输入情况
辅助键:默认edsf为上下左右,可以通过修改config中的四个键来实现自定义
不设界面,一个退出条件:esc键
"""
self.UI = main_UI
self.mouse_ctrl = mouse.Controller()
self.move_activate = False
self.activate_key = Key.caps_lock
self.pre_time = time.time()
self.time_threshold = 0.3
self.auxiliary_keys = Util.get_auxiliary_keys()
self.key_listener = keyboard.Listener(
on_press=self.on_press,
on_release=self.on_release
)
self.mouse_listener = mouse.Listener(
on_move=None,
on_click=self.mouse_click,
on_scroll=None
)
self.key_listener.start()
self.mouse_listener.start()
def on_press(self, key):
if key == self.activate_key:
temp_time = time.time()
if temp_time - self.pre_time < self.time_threshold:
self.UI.activate_state.setText('当前状态:激活')
self.move_activate = True
self.pre_time = temp_time
if self.move_activate:
if key == Key.up or key == self.auxiliary_keys[0]:
self.mouse_ctrl.move(0, -1)
elif key == Key.down or key == self.auxiliary_keys[1]:
self.mouse_ctrl.move(0, 1)
elif key == Key.left or key == self.auxiliary_keys[2]:
self.mouse_ctrl.move(-1, 0)
elif key == Key.right or key == self.auxiliary_keys[3]:
self.mouse_ctrl.move(1, 0)
def on_release(self, key):
pass
def mouse_click(self, x, y, button, pressed):
if button == Button.left:
if pressed:
self.move_activate = True
self.UI.activate_state.setText('当前状态:激活')
else:
self.move_activate = False
self.UI.activate_state.setText('当前状态:休眠')
if __name__ == "__main__":
app = QApplication(sys.argv)
main = MouseBoardUI()
back_listener = MouseBoardBack(main)
main.bound_BackListener(back_listener)
main.show()
sys.exit(app.exec_())
|