Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/taojy123/KeymouseGo
Browse files Browse the repository at this point in the history
  • Loading branch information
ZutJoe committed Sep 30, 2022
2 parents c26d974 + 95ae851 commit f44fbf0
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 85 deletions.
22 changes: 13 additions & 9 deletions Event/WindowsEvents.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
from Event.Event import Event
from loguru import logger

import ctypes
import win32con
from win32gui import GetDC
from win32print import GetDeviceCaps

hDC = GetDC(0)
SW = GetDeviceCaps(hDC, win32con.DESKTOPHORZRES)
SH = GetDeviceCaps(hDC, win32con.DESKTOPVERTRES)
user32 = ctypes.windll.user32
user32.SetProcessDPIAware()
numofmonitors = user32.GetSystemMetrics(win32con.SM_CMONITORS)
# 主屏分辨率
SW, SH = user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)


class WindowsEvent(Event):
Expand Down Expand Up @@ -53,12 +53,16 @@ def execute(self, thd=None):

# 更好的兼容 win10 屏幕缩放问题
if isinstance(x, int) and isinstance(y, int):
nx = int(x * 65535 / SW)
ny = int(y * 65535 / SH)
if numofmonitors > 1:
win32api.SetCursorPos([x, y])
else:
nx = int(x * 65535 / SW)
ny = int(y * 65535 / SH)
win32api.mouse_event(win32con.MOUSEEVENTF_ABSOLUTE | win32con.MOUSEEVENTF_MOVE, nx, ny, 0, 0)
else:
nx = int(x * 65535)
ny = int(y * 65535)
win32api.mouse_event(win32con.MOUSEEVENTF_ABSOLUTE | win32con.MOUSEEVENTF_MOVE, nx, ny, 0, 0)
win32api.mouse_event(win32con.MOUSEEVENTF_ABSOLUTE | win32con.MOUSEEVENTF_MOVE, nx, ny, 0, 0)

if self.message == 'mouse left down':
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
Expand Down
2 changes: 2 additions & 0 deletions Event/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
if system() == 'Windows':
import Event.WindowsEvents as _Event
event_cls = _Event.WindowsEvent
flag_multiplemonitor = _Event.numofmonitors > 1
elif system() in ['Linux', 'Darwin']:
import Event.UniversalEvents as _Event
event_cls = _Event.UniversalEvent
flag_multiplemonitor = False
else:
raise OSError("Unsupported platform '{}'".format(system()))

Expand Down
7 changes: 6 additions & 1 deletion KeymouseGo.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ def add_lib_path(libpaths):
sys.path.append(libpath)


add_lib_path([os.path.join(os.getcwd(), 'plugins')])
def to_abs_path(*args):
return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
*args)


add_lib_path([os.path.join(to_abs_path('plugins'))])


def resize_layout(ui, ratio_w, ratio_h):
Expand Down
45 changes: 28 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# KeymouseGo v5.0
# KeymouseGo v5.1

功能:记录用户的鼠标键盘操作,通过触发按钮自动执行之前记录的操作,可设定执行的次数,可以理解为 `精简绿色版``按键精灵`

Expand All @@ -25,24 +25,24 @@

# 安装

该软件通过 `Python` 语言编写,已编译为 `windows` 平台可执行文件,未安装 `Python` 的用户可直接下载 [release](https://github.com/taojy123/KeymouseGo/releases) 版本 ,直接点击 `KeymouseGo.exe` 运行
该软件通过 `Python` 语言编写,已打包为可执行文件,未安装 `Python` 的用户可直接下载 [release](https://github.com/taojy123/KeymouseGo/releases) 版本 ,直接点击 `KeymouseGo` 运行

### 源码编译,打包 exe 文件
### 源码打包可执行文件

+ Windows
```
1. 安装 Python3
2. pip install -r requirements-windows.txt
3. pip install pyinstaller
4. pyinstaller -F -w --add-data './assets;assets' KeymouseGo.py
4. pyinstaller -F -w --add-data "./assets;assets" KeymouseGo.py
```

+ Linux或Mac
```
1. 安装 Python3
2. pip3 install -r requirements-universal.txt
3. pip3 install pyinstaller
4. pyinstaller -F -w --add-data './assets:assets' KeymouseGo.py
4. pyinstaller -F -w --add-data "./assets:assets" KeymouseGo.py
```

# 使用方法
Expand All @@ -63,25 +63,25 @@

直接运行指定脚本:
```
> KeymouseGo.exe scripts/0314_1452.txt
> ./KeymouseGo scripts/0314_1452.txt
```

运行指定脚本3次:
```
> KeymouseGo.exe scripts/0314_1452.txt -rt 3
> KeymouseGo.exe scripts/0314_1452.txt --runtimes 3
> ./KeymouseGo scripts/0314_1452.txt -rt 3
> ./KeymouseGo scripts/0314_1452.txt --runtimes 3
```

以200%的速度运行指定脚本:
```
> KeymouseGo.exe scripts/0314_1452.txt -sp 200
> KeymouseGo.exe scripts/0314_1452.txt --speed 200
> ./KeymouseGo scripts/0314_1452.txt -sp 200
> ./KeymouseGo scripts/0314_1452.txt --speed 200
```

加载自定义扩展`MyExtension`运行指定脚本:
```
> KeymouseGo.exe scripts/0314_1452.txt -m MyExtension
> KeymouseGo.exe scripts/0314_1452.txt --module MyExtension
> ./KeymouseGo scripts/0314_1452.txt -m MyExtension
> ./KeymouseGo scripts/0314_1452.txt --module MyExtension
```

## 提示
Expand All @@ -104,18 +104,23 @@

部分系统环境中,可能出现无法录制完整的鼠标事件的情况,请以管理员身份/root身份运行此工具即可正常使用。

使用Mac的用户,需要确保程序在辅助功能白名单,如果使用打包的exec文件,则还需要确保终端也在辅助功能白名单。 如果app程序闪退,请尝试给予`~/.qt_material`目录下文件的写权限:
```bash
chmod -R 770 ~/.qt_material
```

## 脚本语法说明
> 演示屏幕分辨率为`1920 * 1080`
```
[
[3000, "EM", "mouse right down", [0.05208%, 0.1852%]], // 开始运行 `3000ms` 后,在屏幕相对坐标 `(0.05208, 0.1852)`即 `(100,200)` 处 `按下鼠标右键`;
[50, "EM", "mouse right up", [0.05208%, 0.1852%]], // 等待 `50ms` 后在相同位置 `抬起鼠标右键`;
[3000, "EM", "mouse right down", ["0.05208%", "0.1852%"]], // 开始运行 `3000ms` 后,在屏幕相对坐标 `(0.05208, 0.1852)`即 `(100,200)` 处 `按下鼠标右键`;
[50, "EM", "mouse right up", ["0.05208%", "0.1852%"]], // 等待 `50ms` 后在相同位置 `抬起鼠标右键`;
[1000, "EK", "key down", (70, 'F', 0)], // 等待 `1000ms` 后 `按下f键`;
[50, "EK", "key up", (70, 'F', 0)], // 等待 `50ms` 后 `抬起f键`;
[100, "EM", "mouse left down", [0.2604%, 0.4630%]], // 等待 `100ms` 后,在屏幕相对坐标 `(0.2604, 0.4630)`即 `(500, 500)` 处 `按下鼠标左键`;
[100, "EM", "mouse move", [0.2604%, 0.5556%]], // 等待 `100ms` 后,鼠标移动至相对坐标 `(0.2604, 0.5556)`即 `(500, 600)` 位置;
[100, "EM", "mouse left up", [0.3125%, 0.5556%]], // 等待 `100ms` 后,在屏幕相对坐标 `(0.3125, 0.5556)`即 `(600, 600)` 处 `抬起鼠标左键`;
[100, "EM", "mouse left down", ["0.2604%", "0.4630%"]], // 等待 `100ms` 后,在屏幕相对坐标 `(0.2604, 0.4630)`即 `(500, 500)` 处 `按下鼠标左键`;
[100, "EM", "mouse move", ["0.2604%", "0.5556%"]], // 等待 `100ms` 后,鼠标移动至相对坐标 `(0.2604, 0.5556)`即 `(500, 600)` 位置;
[100, "EM", "mouse left up", ["0.3125%", "0.5556%"]], // 等待 `100ms` 后,在屏幕相对坐标 `(0.3125, 0.5556)`即 `(600, 600)` 处 `抬起鼠标左键`;
[100, "EX", "input", "你好 world"], // 等待 `100ms` 后,在当前位置输入 `你好 world` 文字。
]
```
Expand All @@ -136,6 +141,7 @@
+ 当为输入文字动作时,为要输入的文字内容。
+ 每行 `//` 后的部分为注释内容。
+ 修改时请严格遵守格式,否则可能导致脚本无法运行,建议修改前先备份一下。
+ 横纵坐标为[-1, -1]时,表示在鼠标当前所在位置执行操作。


## 自定义扩展
Expand Down Expand Up @@ -175,6 +181,11 @@

暂时没法打包 `x86` 版本,32 位系统的同学请自行源码编译,或 [下载v1.5老版本](https://github.com/taojy123/KeymouseGo/releases/tag/v1.5) 使用

## v5.1

+ 支持在Linux和Mac环境下运行
+ 支持在多屏环境下运行(仅Windows)

## v5.0

+ 初步实现自定义扩展功能
Expand Down
11 changes: 5 additions & 6 deletions Recorder/WindowsRecorder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pyWinhook
from pyWinhook import cpyHook, HookConstants
import win32api
from Event import ScreenWidth as SW, ScreenHeight as SH
from Event import ScreenWidth as SW, ScreenHeight as SH, flag_multiplemonitor
from loguru import logger
import Recorder.globals as globalv
import collections
Expand Down Expand Up @@ -61,16 +61,15 @@ def on_mouse_event(event):
delay = 0
globalv.latest_time = globalv.current_ts()

x, y = pos
tx = x / SW
ty = y / SH
tpos = (tx, ty)
if not flag_multiplemonitor:
x, y = pos
pos = (x / SW, y / SH)

sevent = globalv.ScriptEvent({
'delay': delay,
'event_type': 'EM',
'message': message,
'action': tpos,
'action': pos,
'addon': None
})
record_signals.event_signal.emit(sevent)
Expand Down
5 changes: 3 additions & 2 deletions UIFileDialogFunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from UIFileDialogView import Ui_Dialog
from UIFunc import scripts, scripts_map

from KeymouseGo import to_abs_path

class FileDialog(Ui_Dialog):
def __init__(self):
Expand All @@ -20,7 +21,7 @@ def __init__(self):
self.main_window = QMainWindow()
self.filename = scripts[scripts_map['current_index']]
self.lineEdit.setText(self.filename)
self.path = os.path.join(os.getcwd(), "scripts")
self.path = os.path.join(to_abs_path("scripts"))
i18n_language = {
'简体中文': ['文件管理', '当前文件', '选择文件', '编辑脚本', '重命名', '文件没有被找到', '请输入新文件名: ', '更新成功', '文件名不能为空或空格'],
'English': ['File Manage', 'Current file', 'Choice', 'Edit', 'Rename', 'File not found', 'Please input new name', 'Success', 'File name cannot be empty or space']
Expand All @@ -35,7 +36,7 @@ def __init__(self):


def choice_file(self):
file = QFileDialog.getOpenFileName(self.main_window, "选择文件", dir='scripts', filter='*.txt')[0]
file = QFileDialog.getOpenFileName(self.main_window, "选择文件", dir=to_abs_path('scripts'), filter='*.txt')[0]
file_name = re.split(r'\\|\/', file)[-1]
scripts_map['current_index'] = scripts_map[file_name]
if file_name.strip() != '' and file_name is not None:
Expand Down
56 changes: 25 additions & 31 deletions UIFunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
from PySide2.QtMultimedia import QSoundEffect
from loguru import logger

from Event import ScriptEvent
from Event import ScriptEvent, flag_multiplemonitor
from UIView import Ui_UIView
from assets.plugins.ProcessException import *

from KeymouseGo import to_abs_path

os.environ['QT_ENABLE_HIGHDPI_SCALING'] = "1"
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)

Expand All @@ -36,8 +38,8 @@
logger.remove()
logger.add(sys.stdout, backtrace=True, diagnose=True,
level='DEBUG')
logger.add('logs/{time}.log', rotation='20MB', backtrace=True, diagnose=True,
level='DEBUG')
logger.add(to_abs_path('logs', '{time}.log'), rotation='20MB', backtrace=True, diagnose=True,
level='INFO')


def get_assets_path(*paths):
Expand All @@ -56,9 +58,9 @@ def get_assets_path(*paths):
def get_script_list_from_dir():
global scripts

if not os.path.exists('scripts'):
os.mkdir('scripts')
scripts = os.listdir('scripts')[::-1]
if not os.path.exists(to_abs_path('scripts')):
os.mkdir(to_abs_path('scripts'))
scripts = os.listdir(to_abs_path('scripts'))[::-1]
scripts = list(filter(lambda s: s.endswith('.txt'), scripts))


Expand Down Expand Up @@ -102,9 +104,9 @@ def __init__(self, app):
self.choice_script.setCurrentIndex(0)

self.choice_extension.addItems(['Extension'])
if not os.path.exists('plugins'):
os.mkdir('plugins')
for i in os.listdir('plugins'):
if not os.path.exists(to_abs_path('plugins')):
os.mkdir(to_abs_path('plugins'))
for i in os.listdir(to_abs_path('plugins')):
if i[-3:] == '.py':
self.choice_extension.addItems([i[:-3]])

Expand Down Expand Up @@ -261,23 +263,15 @@ def on_record_event(event: ScriptEvent):
# 录制事件
if not(not self.recording or self.running or self.pauserecord):
if self.extension.onrecord(event, self.actioncount):
if event.event_type == 'EM':
if event.event_type == 'EM' and not flag_multiplemonitor:
record = [event.delay, event.event_type, event.message]
tx, ty = event.action
if event.addon:
self.record.append(
[event.delay, event.event_type, event.message, ['{0}%'.format(tx), '{0}%'.format(ty)],
event.addon])
else:
self.record.append(
[event.delay, event.event_type, event.message, ['{0}%'.format(tx), '{0}%'.format(ty)]])
record.append(['{0}%'.format(tx), '{0}%'.format(ty)])
else:
if event.addon:
self.record.append(
[event.delay, event.event_type, event.message, event.action,
event.addon])
else:
self.record.append(
[event.delay, event.event_type, event.message, event.action])
record = [event.delay, event.event_type, event.message, event.action]
if event.addon:
record.append(event.addon)
self.record.append(record)
self.actioncount = self.actioncount + 1
text = '%d actions recorded' % self.actioncount
logger.debug('Recorded %s' % event)
Expand Down Expand Up @@ -337,8 +331,8 @@ def closeEvent(self, event):
event.accept()

def loadconfig(self):
if not os.path.exists('config.ini'):
with open('config.ini', 'w', encoding='utf-8') as f:
if not os.path.exists(to_abs_path('config.ini')):
with open(to_abs_path('config.ini'), 'w', encoding='utf-8') as f:
f.write('[Config]\n'
'StartHotKeyIndex=3\n'
'StopHotKeyIndex=6\n'
Expand All @@ -349,14 +343,14 @@ def loadconfig(self):
'Language=zh-cn\n'
'Extension=Extension\n'
'Theme=light_cyan_500.xml\n')
return QSettings('config.ini', QSettings.IniFormat)
return QSettings(to_abs_path('config.ini'), QSettings.IniFormat)

def get_script_path(self):
i = self.choice_script.currentIndex()
if i < 0:
return ''
script = self.scripts[i]
path = os.path.join(os.getcwd(), 'scripts', script)
path = os.path.join(to_abs_path('scripts'), script)
logger.info('Script path: {0}'.format(path))
return path

Expand Down Expand Up @@ -512,7 +506,7 @@ def run(self):
extension.onbeginp()
self.frame.playtune('start.wav')
while (self.j < extension.runtimes or extension.runtimes == 0) and nointerrupt:
logger.info('===========%d==============' % self.j)
logger.debug('===========%d==============' % self.j)
current_status = self.frame.tnumrd.text()
if current_status in ['broken', 'finished']:
self.frame.running = False
Expand Down Expand Up @@ -562,7 +556,7 @@ def getextension(cls, module_name='Extension', runtimes=1, speed=100, thd=None,
module = SourceFileLoader(module_name, get_assets_path('plugins', 'Extension.py')).load_module()
else:
module = SourceFileLoader(module_name,
os.path.join(os.getcwd(), 'plugins', '%s.py' % module_name)).load_module()
os.path.join(to_abs_path('plugins', '%s.py' % module_name))).load_module()
module_cls = getattr(module, module_name)
logger.info('Load plugin class {0} in module {1}'.format(module_cls, module_name))
return module_cls(runtimes, speed, thd, swap)
Expand Down Expand Up @@ -645,7 +639,7 @@ def run_sub_script(cls, extension, scriptpath: str, subextension_name: str = 'Ex
k = 0
nointerrupt = True
while (k < newextension.runtimes or newextension.runtimes == 0) and nointerrupt:
logger.info('========%d========' % k)
logger.debug('========%d========' % k)
try:
if newextension.onbeforeeachloop(k):
nointerrupt = nointerrupt and RunScriptClass.run_script_once(newevents, newextension, thd=thd, labeldict=labeldict)
Expand Down
2 changes: 1 addition & 1 deletion UIView.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def setupUi(self, UIView):
# setupUi

def retranslateUi(self, UIView):
UIView.setWindowTitle(QCoreApplication.translate("UIView", u"KeymomuseGo v5.0", None))
UIView.setWindowTitle(QCoreApplication.translate("UIView", u"KeymomuseGo v5.1", None))
self.groupBox.setTitle(QCoreApplication.translate("UIView", u"Hotkeys", None))
self.label_start_key.setText(QCoreApplication.translate("UIView", u"Launch/Pause", None))
self.choice_start.setCurrentText("")
Expand Down
2 changes: 1 addition & 1 deletion UIView.ui
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>KeymomuseGo v5.0</string>
<string>KeymomuseGo v5.1</string>
</property>
<property name="windowIcon">
<iconset resource="assets.qrc">
Expand Down
Loading

0 comments on commit f44fbf0

Please sign in to comment.