mouse-keepalive 1.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mouse_keepalive-1.5.2/LICENSE +22 -0
- mouse_keepalive-1.5.2/MANIFEST.in +3 -0
- mouse_keepalive-1.5.2/PKG-INFO +189 -0
- mouse_keepalive-1.5.2/README.md +153 -0
- mouse_keepalive-1.5.2/pyproject.toml +108 -0
- mouse_keepalive-1.5.2/setup.cfg +4 -0
- mouse_keepalive-1.5.2/src/mouse_keepalive/__init__.py +16 -0
- mouse_keepalive-1.5.2/src/mouse_keepalive/__main__.py +9 -0
- mouse_keepalive-1.5.2/src/mouse_keepalive/move_mouse.py +319 -0
- mouse_keepalive-1.5.2/src/mouse_keepalive.egg-info/PKG-INFO +189 -0
- mouse_keepalive-1.5.2/src/mouse_keepalive.egg-info/SOURCES.txt +14 -0
- mouse_keepalive-1.5.2/src/mouse_keepalive.egg-info/dependency_links.txt +1 -0
- mouse_keepalive-1.5.2/src/mouse_keepalive.egg-info/entry_points.txt +3 -0
- mouse_keepalive-1.5.2/src/mouse_keepalive.egg-info/requires.txt +10 -0
- mouse_keepalive-1.5.2/src/mouse_keepalive.egg-info/top_level.txt +1 -0
- mouse_keepalive-1.5.2/tests/test_move_mouse.py +210 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 motoish
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mouse-keepalive
|
|
3
|
+
Version: 1.5.2
|
|
4
|
+
Summary: A cross-platform tool to automatically move the mouse at specified intervals to prevent system sleep or lock
|
|
5
|
+
Author: motoish
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/motoish/mouse-keepalive
|
|
8
|
+
Project-URL: Documentation, https://github.com/motoish/mouse-keepalive#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/motoish/mouse-keepalive
|
|
10
|
+
Project-URL: Issues, https://github.com/motoish/mouse-keepalive/issues
|
|
11
|
+
Keywords: mouse,automation,prevent-sleep,idle,keep-alive,cross-platform,cli,tool,macos,windows,linux
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: System :: Hardware
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: pyautogui>=0.9.54
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pylint>=2.17.0; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: yamllint>=1.32.0; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# Mouse Keepalive
|
|
38
|
+
|
|
39
|
+
**中文** | [English](README_EN.md)
|
|
40
|
+
|
|
41
|
+
跨平台 CLI 工具:按固定间隔轻微移动鼠标并立即回位,避免系统因长时间无操作而休眠或锁屏。
|
|
42
|
+
支持 **macOS / Windows / Linux**。
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## ✨ 功能亮点
|
|
47
|
+
|
|
48
|
+
- 跨平台(Python + [pyautogui](https://pypi.org/project/PyAutoGUI/))
|
|
49
|
+
- 可配置移动间隔与总运行时长
|
|
50
|
+
- 瞬时微移(约 1–2 像素),日常使用几乎无感
|
|
51
|
+
- **PyPI** 与 **npm** 均可安装;命令行别名 `mka`
|
|
52
|
+
- 默认输出每次移动日志;`-q` 可完全静默
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 📋 环境要求
|
|
57
|
+
|
|
58
|
+
| 方式 | 要求 |
|
|
59
|
+
|------|------|
|
|
60
|
+
| **PyPI / pipx**(推荐) | Python **3.11+** |
|
|
61
|
+
| **npm** | Node.js **14+**,且本机已安装 **Python 3.11+**(npm 包为启动器,实际逻辑在 Python 中) |
|
|
62
|
+
| **从源码运行** | Python 3.11+;见下方「本地开发」 |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 📦 安装
|
|
67
|
+
|
|
68
|
+
### PyPI(推荐)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install mouse-keepalive
|
|
72
|
+
# 或(隔离环境,推荐桌面使用)
|
|
73
|
+
pipx install mouse-keepalive
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### npm
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install -g mouse-keepalive
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
首次运行前请确保已安装 Python 3.11+;若缺少 `pyautogui`,npm 入口会尝试自动 `pip install pyautogui`。
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 🚀 快速开始
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# 默认:每 60 秒移动一次,Ctrl+C 停止
|
|
90
|
+
mka
|
|
91
|
+
|
|
92
|
+
# 每 30 秒移动一次
|
|
93
|
+
mka -i 30
|
|
94
|
+
|
|
95
|
+
# 每 2 分钟移动一次,共运行 1 小时
|
|
96
|
+
mka -i 120 -d 3600
|
|
97
|
+
|
|
98
|
+
# 静默模式(无日志)
|
|
99
|
+
mka -q
|
|
100
|
+
|
|
101
|
+
# 帮助
|
|
102
|
+
mka --help
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
等效命令:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
mouse-keepalive
|
|
109
|
+
python -m mouse_keepalive
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## ⚙️ 命令行参数
|
|
115
|
+
|
|
116
|
+
| 参数 | 说明 | 默认 |
|
|
117
|
+
|------|------|------|
|
|
118
|
+
| `-i, --interval` | 移动间隔(秒) | `60` |
|
|
119
|
+
| `-d, --duration` | 运行总时长(秒);省略则一直运行 | 无限 |
|
|
120
|
+
| `-q, --quiet` | 静默模式,不输出日志 | 关闭 |
|
|
121
|
+
| `-h, --help` | 显示帮助 | — |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 🧠 工作原理
|
|
126
|
+
|
|
127
|
+
程序按间隔获取当前光标位置,偏移 1–2 像素后**立即移回**,使系统仍检测到鼠标活动,同时尽量不打扰正常使用。
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 🛠 本地开发
|
|
132
|
+
|
|
133
|
+
仓库采用 **src 布局**,包路径为 `src/mouse_keepalive/`(对外导入名仍为 `mouse_keepalive`)。
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
git clone https://github.com/motoish/mouse-keepalive.git
|
|
137
|
+
cd mouse-keepalive
|
|
138
|
+
|
|
139
|
+
# 可编辑安装 + 开发依赖
|
|
140
|
+
pip install -e ".[dev]"
|
|
141
|
+
|
|
142
|
+
# 方式一:仓库根目录脚本(改代码后无需重装)
|
|
143
|
+
./move_mouse.sh
|
|
144
|
+
./move_mouse.sh -i 30 -q
|
|
145
|
+
|
|
146
|
+
# 方式二:Makefile
|
|
147
|
+
make run ARGS="-i 30"
|
|
148
|
+
|
|
149
|
+
# 方式三:安装后的 CLI
|
|
150
|
+
mka -i 30
|
|
151
|
+
|
|
152
|
+
# 测试与检查
|
|
153
|
+
make test
|
|
154
|
+
make lint
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`move_mouse.sh` 会优先使用 `.venv/bin/python`,并通过 `PYTHONPATH=src` 加载源码;**不会**随 PyPI/npm 包发布。
|
|
158
|
+
|
|
159
|
+
### 目录结构(简要)
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
mouse-keepalive/
|
|
163
|
+
├── src/mouse_keepalive/ # Python 包
|
|
164
|
+
├── bin/ # npm CLI(转发到 python -m mouse_keepalive)
|
|
165
|
+
├── tests/
|
|
166
|
+
├── move_mouse.sh # 仅本地开发
|
|
167
|
+
└── pyproject.toml
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## ⚠️ 注意事项
|
|
173
|
+
|
|
174
|
+
- 使用 **Ctrl + C** 可随时退出
|
|
175
|
+
- **macOS** 需在「系统设置 → 隐私与安全性 → 辅助功能」中允许终端或 Python
|
|
176
|
+
- 企业安全策略可能拦截模拟鼠标;若无效请联系管理员或加入白名单
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 📦 发布与版本
|
|
181
|
+
|
|
182
|
+
用户安装版本以 [PyPI](https://pypi.org/project/mouse-keepalive/) / [npm](https://www.npmjs.com/package/mouse-keepalive) 为准。
|
|
183
|
+
变更记录见 [CHANGELOG.md](CHANGELOG.md)。仓库使用 [release-please](https://github.com/googleapis/release-please) 管理版本;合并 Release PR 后会发布 `v*` 标签并自动发布到 npm / PyPI。
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 📄 许可证
|
|
188
|
+
|
|
189
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Mouse Keepalive
|
|
2
|
+
|
|
3
|
+
**中文** | [English](README_EN.md)
|
|
4
|
+
|
|
5
|
+
跨平台 CLI 工具:按固定间隔轻微移动鼠标并立即回位,避免系统因长时间无操作而休眠或锁屏。
|
|
6
|
+
支持 **macOS / Windows / Linux**。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ✨ 功能亮点
|
|
11
|
+
|
|
12
|
+
- 跨平台(Python + [pyautogui](https://pypi.org/project/PyAutoGUI/))
|
|
13
|
+
- 可配置移动间隔与总运行时长
|
|
14
|
+
- 瞬时微移(约 1–2 像素),日常使用几乎无感
|
|
15
|
+
- **PyPI** 与 **npm** 均可安装;命令行别名 `mka`
|
|
16
|
+
- 默认输出每次移动日志;`-q` 可完全静默
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 📋 环境要求
|
|
21
|
+
|
|
22
|
+
| 方式 | 要求 |
|
|
23
|
+
|------|------|
|
|
24
|
+
| **PyPI / pipx**(推荐) | Python **3.11+** |
|
|
25
|
+
| **npm** | Node.js **14+**,且本机已安装 **Python 3.11+**(npm 包为启动器,实际逻辑在 Python 中) |
|
|
26
|
+
| **从源码运行** | Python 3.11+;见下方「本地开发」 |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 📦 安装
|
|
31
|
+
|
|
32
|
+
### PyPI(推荐)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install mouse-keepalive
|
|
36
|
+
# 或(隔离环境,推荐桌面使用)
|
|
37
|
+
pipx install mouse-keepalive
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### npm
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g mouse-keepalive
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
首次运行前请确保已安装 Python 3.11+;若缺少 `pyautogui`,npm 入口会尝试自动 `pip install pyautogui`。
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 🚀 快速开始
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 默认:每 60 秒移动一次,Ctrl+C 停止
|
|
54
|
+
mka
|
|
55
|
+
|
|
56
|
+
# 每 30 秒移动一次
|
|
57
|
+
mka -i 30
|
|
58
|
+
|
|
59
|
+
# 每 2 分钟移动一次,共运行 1 小时
|
|
60
|
+
mka -i 120 -d 3600
|
|
61
|
+
|
|
62
|
+
# 静默模式(无日志)
|
|
63
|
+
mka -q
|
|
64
|
+
|
|
65
|
+
# 帮助
|
|
66
|
+
mka --help
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
等效命令:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
mouse-keepalive
|
|
73
|
+
python -m mouse_keepalive
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## ⚙️ 命令行参数
|
|
79
|
+
|
|
80
|
+
| 参数 | 说明 | 默认 |
|
|
81
|
+
|------|------|------|
|
|
82
|
+
| `-i, --interval` | 移动间隔(秒) | `60` |
|
|
83
|
+
| `-d, --duration` | 运行总时长(秒);省略则一直运行 | 无限 |
|
|
84
|
+
| `-q, --quiet` | 静默模式,不输出日志 | 关闭 |
|
|
85
|
+
| `-h, --help` | 显示帮助 | — |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 🧠 工作原理
|
|
90
|
+
|
|
91
|
+
程序按间隔获取当前光标位置,偏移 1–2 像素后**立即移回**,使系统仍检测到鼠标活动,同时尽量不打扰正常使用。
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 🛠 本地开发
|
|
96
|
+
|
|
97
|
+
仓库采用 **src 布局**,包路径为 `src/mouse_keepalive/`(对外导入名仍为 `mouse_keepalive`)。
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
git clone https://github.com/motoish/mouse-keepalive.git
|
|
101
|
+
cd mouse-keepalive
|
|
102
|
+
|
|
103
|
+
# 可编辑安装 + 开发依赖
|
|
104
|
+
pip install -e ".[dev]"
|
|
105
|
+
|
|
106
|
+
# 方式一:仓库根目录脚本(改代码后无需重装)
|
|
107
|
+
./move_mouse.sh
|
|
108
|
+
./move_mouse.sh -i 30 -q
|
|
109
|
+
|
|
110
|
+
# 方式二:Makefile
|
|
111
|
+
make run ARGS="-i 30"
|
|
112
|
+
|
|
113
|
+
# 方式三:安装后的 CLI
|
|
114
|
+
mka -i 30
|
|
115
|
+
|
|
116
|
+
# 测试与检查
|
|
117
|
+
make test
|
|
118
|
+
make lint
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`move_mouse.sh` 会优先使用 `.venv/bin/python`,并通过 `PYTHONPATH=src` 加载源码;**不会**随 PyPI/npm 包发布。
|
|
122
|
+
|
|
123
|
+
### 目录结构(简要)
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
mouse-keepalive/
|
|
127
|
+
├── src/mouse_keepalive/ # Python 包
|
|
128
|
+
├── bin/ # npm CLI(转发到 python -m mouse_keepalive)
|
|
129
|
+
├── tests/
|
|
130
|
+
├── move_mouse.sh # 仅本地开发
|
|
131
|
+
└── pyproject.toml
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## ⚠️ 注意事项
|
|
137
|
+
|
|
138
|
+
- 使用 **Ctrl + C** 可随时退出
|
|
139
|
+
- **macOS** 需在「系统设置 → 隐私与安全性 → 辅助功能」中允许终端或 Python
|
|
140
|
+
- 企业安全策略可能拦截模拟鼠标;若无效请联系管理员或加入白名单
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 📦 发布与版本
|
|
145
|
+
|
|
146
|
+
用户安装版本以 [PyPI](https://pypi.org/project/mouse-keepalive/) / [npm](https://www.npmjs.com/package/mouse-keepalive) 为准。
|
|
147
|
+
变更记录见 [CHANGELOG.md](CHANGELOG.md)。仓库使用 [release-please](https://github.com/googleapis/release-please) 管理版本;合并 Release PR 后会发布 `v*` 标签并自动发布到 npm / PyPI。
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 📄 许可证
|
|
152
|
+
|
|
153
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mouse-keepalive"
|
|
7
|
+
version = "1.5.2"
|
|
8
|
+
description = "A cross-platform tool to automatically move the mouse at specified intervals to prevent system sleep or lock"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "motoish"}
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"mouse",
|
|
17
|
+
"automation",
|
|
18
|
+
"prevent-sleep",
|
|
19
|
+
"idle",
|
|
20
|
+
"keep-alive",
|
|
21
|
+
"cross-platform",
|
|
22
|
+
"cli",
|
|
23
|
+
"tool",
|
|
24
|
+
"macos",
|
|
25
|
+
"windows",
|
|
26
|
+
"linux"
|
|
27
|
+
]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Intended Audience :: End Users/Desktop",
|
|
31
|
+
"License :: OSI Approved :: MIT License",
|
|
32
|
+
"Operating System :: MacOS",
|
|
33
|
+
"Operating System :: Microsoft :: Windows",
|
|
34
|
+
"Operating System :: POSIX :: Linux",
|
|
35
|
+
"Programming Language :: Python :: 3",
|
|
36
|
+
"Programming Language :: Python :: 3.11",
|
|
37
|
+
"Programming Language :: Python :: 3.12",
|
|
38
|
+
"Topic :: System :: Hardware",
|
|
39
|
+
"Topic :: Utilities",
|
|
40
|
+
]
|
|
41
|
+
dependencies = [
|
|
42
|
+
"pyautogui>=0.9.54",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
dev = [
|
|
47
|
+
"flake8>=6.0.0",
|
|
48
|
+
"black>=23.0.0",
|
|
49
|
+
"pylint>=2.17.0",
|
|
50
|
+
"mypy>=1.0.0",
|
|
51
|
+
"pytest>=7.0.0",
|
|
52
|
+
"pytest-cov>=4.0.0",
|
|
53
|
+
"yamllint>=1.32.0",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[project.scripts]
|
|
57
|
+
mouse-keepalive = "mouse_keepalive.move_mouse:main"
|
|
58
|
+
mka = "mouse_keepalive.move_mouse:main"
|
|
59
|
+
|
|
60
|
+
[project.urls]
|
|
61
|
+
Homepage = "https://github.com/motoish/mouse-keepalive"
|
|
62
|
+
Documentation = "https://github.com/motoish/mouse-keepalive#readme"
|
|
63
|
+
Repository = "https://github.com/motoish/mouse-keepalive"
|
|
64
|
+
Issues = "https://github.com/motoish/mouse-keepalive/issues"
|
|
65
|
+
|
|
66
|
+
[tool.setuptools.package-dir]
|
|
67
|
+
"" = "src"
|
|
68
|
+
|
|
69
|
+
[tool.setuptools.packages.find]
|
|
70
|
+
where = ["src"]
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
pythonpath = ["src"]
|
|
74
|
+
testpaths = ["tests"]
|
|
75
|
+
|
|
76
|
+
[tool.black]
|
|
77
|
+
line-length = 120
|
|
78
|
+
target-version = ['py311']
|
|
79
|
+
include = '\.pyi?$'
|
|
80
|
+
extend-exclude = '''
|
|
81
|
+
/(
|
|
82
|
+
# directories
|
|
83
|
+
\.eggs
|
|
84
|
+
| \.git
|
|
85
|
+
| \.hg
|
|
86
|
+
| \.mypy_cache
|
|
87
|
+
| \.tox
|
|
88
|
+
| \.venv
|
|
89
|
+
| venv
|
|
90
|
+
| _build
|
|
91
|
+
| buck-out
|
|
92
|
+
| build
|
|
93
|
+
| dist
|
|
94
|
+
)/
|
|
95
|
+
'''
|
|
96
|
+
|
|
97
|
+
[tool.mypy]
|
|
98
|
+
python_version = "3.11"
|
|
99
|
+
warn_return_any = true
|
|
100
|
+
warn_unused_configs = true
|
|
101
|
+
ignore_missing_imports = true
|
|
102
|
+
disallow_untyped_defs = false
|
|
103
|
+
no_implicit_optional = true
|
|
104
|
+
|
|
105
|
+
[[tool.mypy.overrides]]
|
|
106
|
+
module = "pyautogui.*"
|
|
107
|
+
ignore_missing_imports = true
|
|
108
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
自动移动鼠标工具 / Mouse Keepalive Tool
|
|
3
|
+
支持 macOS、Windows 和 Linux 系统 / Supports macOS, Windows and Linux
|
|
4
|
+
|
|
5
|
+
模块化架构:
|
|
6
|
+
- 核心逻辑在 move_mouse 模块中实现
|
|
7
|
+
- 所有入口点(CLI、模块调用)都使用相同的核心逻辑
|
|
8
|
+
- 支持依赖注入,便于测试
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "1.5.2"
|
|
12
|
+
__author__ = "motoish"
|
|
13
|
+
|
|
14
|
+
from .move_mouse import move_mouse, main, MouseMover, MouseController, MousePosition, ScreenSize
|
|
15
|
+
|
|
16
|
+
__all__ = ["move_mouse", "main", "MouseMover", "MouseController", "MousePosition", "ScreenSize"]
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
自动移动鼠标脚本 / Mouse Keepalive Script
|
|
4
|
+
支持 macOS、Windows 和 Linux 系统 / Supports macOS, Windows and Linux
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import platform
|
|
11
|
+
from typing import Optional, Callable, Tuple
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import pyautogui
|
|
16
|
+
except ImportError:
|
|
17
|
+
print("错误: 未安装 pyautogui 库")
|
|
18
|
+
print("Error: pyautogui library not installed")
|
|
19
|
+
print("请运行: pip install pyautogui")
|
|
20
|
+
print("Please run: pip install pyautogui")
|
|
21
|
+
sys.exit(1)
|
|
22
|
+
|
|
23
|
+
# 禁用 pyautogui 的安全功能(防止鼠标移到屏幕角落时停止)
|
|
24
|
+
# Disable pyautogui failsafe (prevents stopping when mouse moves to screen corner)
|
|
25
|
+
pyautogui.FAILSAFE = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class MousePosition:
|
|
30
|
+
"""鼠标位置数据类 / Mouse position data class"""
|
|
31
|
+
|
|
32
|
+
x: int
|
|
33
|
+
y: int
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ScreenSize:
|
|
38
|
+
"""屏幕尺寸数据类 / Screen size data class"""
|
|
39
|
+
|
|
40
|
+
width: int
|
|
41
|
+
height: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MouseController:
|
|
45
|
+
"""
|
|
46
|
+
鼠标控制器接口 / Mouse controller interface
|
|
47
|
+
封装鼠标操作,便于测试时替换实现
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def get_position(self) -> MousePosition:
|
|
51
|
+
"""获取当前鼠标位置 / Get current mouse position"""
|
|
52
|
+
x, y = pyautogui.position()
|
|
53
|
+
return MousePosition(x, y)
|
|
54
|
+
|
|
55
|
+
def get_screen_size(self) -> ScreenSize:
|
|
56
|
+
"""获取屏幕尺寸 / Get screen size"""
|
|
57
|
+
width, height = pyautogui.size()
|
|
58
|
+
return ScreenSize(width, height)
|
|
59
|
+
|
|
60
|
+
def move_to(self, x: int, y: int, duration: float = 0) -> None:
|
|
61
|
+
"""移动鼠标到指定位置 / Move mouse to specified position"""
|
|
62
|
+
pyautogui.moveTo(x, y, duration=duration)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class MouseMover:
|
|
66
|
+
"""
|
|
67
|
+
核心鼠标移动逻辑类 / Core mouse movement logic class
|
|
68
|
+
这个类包含所有核心逻辑,不依赖具体的 I/O 实现,便于测试
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
controller: Optional[MouseController] = None,
|
|
74
|
+
time_func: Optional[Callable[[], float]] = None,
|
|
75
|
+
sleep_func: Optional[Callable[[float], None]] = None,
|
|
76
|
+
print_func: Optional[Callable[[str], None]] = None,
|
|
77
|
+
):
|
|
78
|
+
"""
|
|
79
|
+
初始化鼠标移动器 / Initialize mouse mover
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
controller: 鼠标控制器,默认使用真实实现 / Mouse controller, defaults to real implementation
|
|
83
|
+
time_func: 时间函数,默认使用 time.time / Time function, defaults to time.time
|
|
84
|
+
sleep_func: 睡眠函数,默认使用 time.sleep / Sleep function, defaults to time.sleep
|
|
85
|
+
print_func: 打印函数,默认使用 print / Print function, defaults to print
|
|
86
|
+
"""
|
|
87
|
+
self.controller = controller or MouseController()
|
|
88
|
+
self.time_func = time_func or time.time
|
|
89
|
+
self.sleep_func = sleep_func or time.sleep
|
|
90
|
+
self.print_func = print_func or print
|
|
91
|
+
|
|
92
|
+
def calculate_next_position(
|
|
93
|
+
self, current_pos: MousePosition, screen_size: ScreenSize, move_count: int
|
|
94
|
+
) -> Tuple[int, int]:
|
|
95
|
+
"""
|
|
96
|
+
计算下一个鼠标位置 / Calculate next mouse position
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
current_pos: 当前鼠标位置 / Current mouse position
|
|
100
|
+
screen_size: 屏幕尺寸 / Screen size
|
|
101
|
+
move_count: 移动计数(用于交替方向)/ Move count (for alternating direction)
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
下一个位置的 (x, y) 坐标 / Next position (x, y) coordinates
|
|
105
|
+
"""
|
|
106
|
+
# 移动鼠标到稍微不同的位置(移动1-2像素)
|
|
107
|
+
# Move mouse to slightly different position (move 1-2 pixels)
|
|
108
|
+
offset_x = 1 if move_count % 2 == 0 else -1
|
|
109
|
+
offset_y = 1 if move_count % 2 == 0 else -1
|
|
110
|
+
|
|
111
|
+
new_x = current_pos.x + offset_x
|
|
112
|
+
new_y = current_pos.y + offset_y
|
|
113
|
+
|
|
114
|
+
# 确保坐标在屏幕范围内 / Ensure coordinates are within screen bounds
|
|
115
|
+
new_x = max(1, min(new_x, screen_size.width - 1))
|
|
116
|
+
new_y = max(1, min(new_y, screen_size.height - 1))
|
|
117
|
+
|
|
118
|
+
return new_x, new_y
|
|
119
|
+
|
|
120
|
+
def perform_move(self, move_count: int) -> Tuple[MousePosition, int]:
|
|
121
|
+
"""
|
|
122
|
+
执行一次鼠标移动 / Perform one mouse movement
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
move_count: 当前移动计数 / Current move count
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
(原始位置, 新的移动计数) / (Original position, new move count)
|
|
129
|
+
"""
|
|
130
|
+
# 获取当前鼠标位置 / Get current mouse position
|
|
131
|
+
current_pos = self.controller.get_position()
|
|
132
|
+
|
|
133
|
+
# 获取屏幕尺寸 / Get screen size
|
|
134
|
+
screen_size = self.controller.get_screen_size()
|
|
135
|
+
|
|
136
|
+
# 计算下一个位置 / Calculate next position
|
|
137
|
+
new_x, new_y = self.calculate_next_position(current_pos, screen_size, move_count)
|
|
138
|
+
|
|
139
|
+
# 移动鼠标 / Move mouse
|
|
140
|
+
self.controller.move_to(new_x, new_y, duration=0)
|
|
141
|
+
move_count += 1
|
|
142
|
+
|
|
143
|
+
# 立即移回原位置(这样用户感觉不到鼠标移动)
|
|
144
|
+
# Immediately move back to original position (user won't notice the movement)
|
|
145
|
+
self.controller.move_to(current_pos.x, current_pos.y, duration=0)
|
|
146
|
+
|
|
147
|
+
return current_pos, move_count
|
|
148
|
+
|
|
149
|
+
def run(
|
|
150
|
+
self,
|
|
151
|
+
interval: int = 60,
|
|
152
|
+
duration: Optional[int] = None,
|
|
153
|
+
*,
|
|
154
|
+
on_start: Optional[Callable[[], None]] = None,
|
|
155
|
+
on_move: Optional[Callable[[int, MousePosition, float], None]] = None,
|
|
156
|
+
on_finish: Optional[Callable[[int, float], None]] = None,
|
|
157
|
+
) -> Tuple[int, float]:
|
|
158
|
+
"""
|
|
159
|
+
运行鼠标移动循环 / Run mouse movement loop
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
interval: 移动间隔(秒)/ Movement interval (seconds)
|
|
163
|
+
duration: 运行时长(秒),None 表示无限运行 / Duration (seconds), None means infinite
|
|
164
|
+
on_start: 启动回调函数 / Start callback function
|
|
165
|
+
on_move: 移动回调函数 / Move callback function
|
|
166
|
+
on_finish: 完成回调函数 / Finish callback function
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
(移动次数, 运行时长) / (Move count, duration)
|
|
170
|
+
"""
|
|
171
|
+
if on_start:
|
|
172
|
+
on_start()
|
|
173
|
+
|
|
174
|
+
start_time = self.time_func()
|
|
175
|
+
move_count = 0
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
while True:
|
|
179
|
+
# 执行移动 / Perform move
|
|
180
|
+
current_pos, move_count = self.perform_move(move_count)
|
|
181
|
+
|
|
182
|
+
elapsed = self.time_func() - start_time
|
|
183
|
+
|
|
184
|
+
if on_move:
|
|
185
|
+
on_move(move_count, current_pos, elapsed)
|
|
186
|
+
|
|
187
|
+
# 检查是否达到运行时长 / Check if duration is reached
|
|
188
|
+
if duration and elapsed >= duration:
|
|
189
|
+
if on_finish:
|
|
190
|
+
on_finish(move_count, elapsed)
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
# 等待指定间隔 / Wait for specified interval
|
|
194
|
+
self.sleep_func(interval)
|
|
195
|
+
|
|
196
|
+
except KeyboardInterrupt:
|
|
197
|
+
elapsed = self.time_func() - start_time
|
|
198
|
+
if on_finish:
|
|
199
|
+
on_finish(move_count, elapsed)
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
elapsed = self.time_func() - start_time
|
|
203
|
+
return move_count, elapsed
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _make_log_callbacks(
|
|
207
|
+
mover: MouseMover,
|
|
208
|
+
interval: int,
|
|
209
|
+
duration: Optional[int],
|
|
210
|
+
quiet: bool,
|
|
211
|
+
) -> Tuple[
|
|
212
|
+
Optional[Callable[[], None]],
|
|
213
|
+
Optional[Callable[[int, MousePosition, float], None]],
|
|
214
|
+
Optional[Callable[[int, float], None]],
|
|
215
|
+
]:
|
|
216
|
+
"""构建日志回调;静默模式返回 (None, None, None)。"""
|
|
217
|
+
if quiet:
|
|
218
|
+
return None, None, None
|
|
219
|
+
|
|
220
|
+
def on_start() -> None:
|
|
221
|
+
mover.print_func("开始自动移动鼠标... / Starting mouse keepalive...")
|
|
222
|
+
mover.print_func(f"间隔 {interval}s" + (f",时长 {duration}s" if duration else ",无限运行(Ctrl+C 停止)"))
|
|
223
|
+
mover.print_func(f"OS: {platform.system()}")
|
|
224
|
+
mover.print_func("-" * 50)
|
|
225
|
+
|
|
226
|
+
def on_move(move_count: int, current_pos: MousePosition, elapsed: float) -> None:
|
|
227
|
+
mover.print_func(f"[{int(elapsed)}s] 已移动 {move_count} 次 " f"(位置: {current_pos.x}, {current_pos.y})")
|
|
228
|
+
|
|
229
|
+
def on_finish(move_count: int, elapsed: float) -> None:
|
|
230
|
+
if duration:
|
|
231
|
+
mover.print_func(f"\n已达运行时长 {duration}s,共移动 {move_count} 次")
|
|
232
|
+
else:
|
|
233
|
+
mover.print_func(f"\n已停止,共移动 {move_count} 次,运行 {int(elapsed)}s")
|
|
234
|
+
|
|
235
|
+
return on_start, on_move, on_finish
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def move_mouse(interval: int = 60, duration: Optional[int] = None, quiet: bool = False) -> None:
|
|
239
|
+
"""
|
|
240
|
+
自动移动鼠标 / Automatically move mouse
|
|
241
|
+
这是主要的公共 API,保持向后兼容性
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
interval: 移动间隔(秒),默认60秒 / Movement interval (seconds), default 60
|
|
245
|
+
duration: 运行时长(秒),None表示无限运行 / Duration (seconds), None means infinite
|
|
246
|
+
quiet: 静默模式,不输出日志 / Quiet mode, no log output
|
|
247
|
+
"""
|
|
248
|
+
mover = MouseMover()
|
|
249
|
+
on_start, on_move, on_finish = _make_log_callbacks(mover, interval, duration, quiet)
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
mover.run(
|
|
253
|
+
interval=interval,
|
|
254
|
+
duration=duration,
|
|
255
|
+
on_start=on_start,
|
|
256
|
+
on_move=on_move,
|
|
257
|
+
on_finish=on_finish,
|
|
258
|
+
)
|
|
259
|
+
except KeyboardInterrupt:
|
|
260
|
+
# on_finish 已经在 KeyboardInterrupt 处理中调用
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def main():
|
|
265
|
+
"""命令行入口函数 / Command line entry function"""
|
|
266
|
+
parser = argparse.ArgumentParser(
|
|
267
|
+
description="自动移动鼠标工具(防止系统进入休眠或锁定) / Mouse keepalive tool (prevents system sleep or lock)",
|
|
268
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
269
|
+
epilog="""
|
|
270
|
+
示例 / Examples:
|
|
271
|
+
mouse-keepalive # 每60秒移动一次,无限运行 / Move every 60s, infinite
|
|
272
|
+
mouse-keepalive -i 30 # 每30秒移动一次 / Move every 30s
|
|
273
|
+
mouse-keepalive -i 120 -d 3600 # 每120秒移动一次,运行1小时 / Move every 120s, run for 1 hour
|
|
274
|
+
mouse-keepalive -q # 静默模式 / Quiet mode
|
|
275
|
+
mka -i 30 # 使用简短别名 / Use short alias
|
|
276
|
+
python -m mouse_keepalive # 使用模块方式运行 / Run as module
|
|
277
|
+
""",
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
parser.add_argument(
|
|
281
|
+
"-i",
|
|
282
|
+
"--interval",
|
|
283
|
+
type=int,
|
|
284
|
+
default=60,
|
|
285
|
+
help="鼠标移动间隔(秒),默认60秒 / Mouse movement interval (seconds), default 60",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
parser.add_argument(
|
|
289
|
+
"-d",
|
|
290
|
+
"--duration",
|
|
291
|
+
type=int,
|
|
292
|
+
default=None,
|
|
293
|
+
help="运行时长(秒),默认无限运行 / Duration (seconds), default infinite",
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
parser.add_argument(
|
|
297
|
+
"-q",
|
|
298
|
+
"--quiet",
|
|
299
|
+
action="store_true",
|
|
300
|
+
help="静默模式,不输出日志 / Quiet mode, no log output",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
args = parser.parse_args()
|
|
304
|
+
|
|
305
|
+
if args.interval < 1:
|
|
306
|
+
print("错误: 移动间隔必须大于0")
|
|
307
|
+
print("Error: Interval must be greater than 0")
|
|
308
|
+
sys.exit(1)
|
|
309
|
+
|
|
310
|
+
if args.duration is not None and args.duration < 1:
|
|
311
|
+
print("错误: 运行时长必须大于0")
|
|
312
|
+
print("Error: Duration must be greater than 0")
|
|
313
|
+
sys.exit(1)
|
|
314
|
+
|
|
315
|
+
move_mouse(interval=args.interval, duration=args.duration, quiet=args.quiet)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
main()
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mouse-keepalive
|
|
3
|
+
Version: 1.5.2
|
|
4
|
+
Summary: A cross-platform tool to automatically move the mouse at specified intervals to prevent system sleep or lock
|
|
5
|
+
Author: motoish
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/motoish/mouse-keepalive
|
|
8
|
+
Project-URL: Documentation, https://github.com/motoish/mouse-keepalive#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/motoish/mouse-keepalive
|
|
10
|
+
Project-URL: Issues, https://github.com/motoish/mouse-keepalive/issues
|
|
11
|
+
Keywords: mouse,automation,prevent-sleep,idle,keep-alive,cross-platform,cli,tool,macos,windows,linux
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: System :: Hardware
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: pyautogui>=0.9.54
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pylint>=2.17.0; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: yamllint>=1.32.0; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# Mouse Keepalive
|
|
38
|
+
|
|
39
|
+
**中文** | [English](README_EN.md)
|
|
40
|
+
|
|
41
|
+
跨平台 CLI 工具:按固定间隔轻微移动鼠标并立即回位,避免系统因长时间无操作而休眠或锁屏。
|
|
42
|
+
支持 **macOS / Windows / Linux**。
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## ✨ 功能亮点
|
|
47
|
+
|
|
48
|
+
- 跨平台(Python + [pyautogui](https://pypi.org/project/PyAutoGUI/))
|
|
49
|
+
- 可配置移动间隔与总运行时长
|
|
50
|
+
- 瞬时微移(约 1–2 像素),日常使用几乎无感
|
|
51
|
+
- **PyPI** 与 **npm** 均可安装;命令行别名 `mka`
|
|
52
|
+
- 默认输出每次移动日志;`-q` 可完全静默
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 📋 环境要求
|
|
57
|
+
|
|
58
|
+
| 方式 | 要求 |
|
|
59
|
+
|------|------|
|
|
60
|
+
| **PyPI / pipx**(推荐) | Python **3.11+** |
|
|
61
|
+
| **npm** | Node.js **14+**,且本机已安装 **Python 3.11+**(npm 包为启动器,实际逻辑在 Python 中) |
|
|
62
|
+
| **从源码运行** | Python 3.11+;见下方「本地开发」 |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 📦 安装
|
|
67
|
+
|
|
68
|
+
### PyPI(推荐)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install mouse-keepalive
|
|
72
|
+
# 或(隔离环境,推荐桌面使用)
|
|
73
|
+
pipx install mouse-keepalive
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### npm
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install -g mouse-keepalive
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
首次运行前请确保已安装 Python 3.11+;若缺少 `pyautogui`,npm 入口会尝试自动 `pip install pyautogui`。
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 🚀 快速开始
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# 默认:每 60 秒移动一次,Ctrl+C 停止
|
|
90
|
+
mka
|
|
91
|
+
|
|
92
|
+
# 每 30 秒移动一次
|
|
93
|
+
mka -i 30
|
|
94
|
+
|
|
95
|
+
# 每 2 分钟移动一次,共运行 1 小时
|
|
96
|
+
mka -i 120 -d 3600
|
|
97
|
+
|
|
98
|
+
# 静默模式(无日志)
|
|
99
|
+
mka -q
|
|
100
|
+
|
|
101
|
+
# 帮助
|
|
102
|
+
mka --help
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
等效命令:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
mouse-keepalive
|
|
109
|
+
python -m mouse_keepalive
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## ⚙️ 命令行参数
|
|
115
|
+
|
|
116
|
+
| 参数 | 说明 | 默认 |
|
|
117
|
+
|------|------|------|
|
|
118
|
+
| `-i, --interval` | 移动间隔(秒) | `60` |
|
|
119
|
+
| `-d, --duration` | 运行总时长(秒);省略则一直运行 | 无限 |
|
|
120
|
+
| `-q, --quiet` | 静默模式,不输出日志 | 关闭 |
|
|
121
|
+
| `-h, --help` | 显示帮助 | — |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 🧠 工作原理
|
|
126
|
+
|
|
127
|
+
程序按间隔获取当前光标位置,偏移 1–2 像素后**立即移回**,使系统仍检测到鼠标活动,同时尽量不打扰正常使用。
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 🛠 本地开发
|
|
132
|
+
|
|
133
|
+
仓库采用 **src 布局**,包路径为 `src/mouse_keepalive/`(对外导入名仍为 `mouse_keepalive`)。
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
git clone https://github.com/motoish/mouse-keepalive.git
|
|
137
|
+
cd mouse-keepalive
|
|
138
|
+
|
|
139
|
+
# 可编辑安装 + 开发依赖
|
|
140
|
+
pip install -e ".[dev]"
|
|
141
|
+
|
|
142
|
+
# 方式一:仓库根目录脚本(改代码后无需重装)
|
|
143
|
+
./move_mouse.sh
|
|
144
|
+
./move_mouse.sh -i 30 -q
|
|
145
|
+
|
|
146
|
+
# 方式二:Makefile
|
|
147
|
+
make run ARGS="-i 30"
|
|
148
|
+
|
|
149
|
+
# 方式三:安装后的 CLI
|
|
150
|
+
mka -i 30
|
|
151
|
+
|
|
152
|
+
# 测试与检查
|
|
153
|
+
make test
|
|
154
|
+
make lint
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`move_mouse.sh` 会优先使用 `.venv/bin/python`,并通过 `PYTHONPATH=src` 加载源码;**不会**随 PyPI/npm 包发布。
|
|
158
|
+
|
|
159
|
+
### 目录结构(简要)
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
mouse-keepalive/
|
|
163
|
+
├── src/mouse_keepalive/ # Python 包
|
|
164
|
+
├── bin/ # npm CLI(转发到 python -m mouse_keepalive)
|
|
165
|
+
├── tests/
|
|
166
|
+
├── move_mouse.sh # 仅本地开发
|
|
167
|
+
└── pyproject.toml
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## ⚠️ 注意事项
|
|
173
|
+
|
|
174
|
+
- 使用 **Ctrl + C** 可随时退出
|
|
175
|
+
- **macOS** 需在「系统设置 → 隐私与安全性 → 辅助功能」中允许终端或 Python
|
|
176
|
+
- 企业安全策略可能拦截模拟鼠标;若无效请联系管理员或加入白名单
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 📦 发布与版本
|
|
181
|
+
|
|
182
|
+
用户安装版本以 [PyPI](https://pypi.org/project/mouse-keepalive/) / [npm](https://www.npmjs.com/package/mouse-keepalive) 为准。
|
|
183
|
+
变更记录见 [CHANGELOG.md](CHANGELOG.md)。仓库使用 [release-please](https://github.com/googleapis/release-please) 管理版本;合并 Release PR 后会发布 `v*` 标签并自动发布到 npm / PyPI。
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 📄 许可证
|
|
188
|
+
|
|
189
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
src/mouse_keepalive/__init__.py
|
|
6
|
+
src/mouse_keepalive/__main__.py
|
|
7
|
+
src/mouse_keepalive/move_mouse.py
|
|
8
|
+
src/mouse_keepalive.egg-info/PKG-INFO
|
|
9
|
+
src/mouse_keepalive.egg-info/SOURCES.txt
|
|
10
|
+
src/mouse_keepalive.egg-info/dependency_links.txt
|
|
11
|
+
src/mouse_keepalive.egg-info/entry_points.txt
|
|
12
|
+
src/mouse_keepalive.egg-info/requires.txt
|
|
13
|
+
src/mouse_keepalive.egg-info/top_level.txt
|
|
14
|
+
tests/test_move_mouse.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mouse_keepalive
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for mouse_keepalive.move_mouse module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import pytest
|
|
8
|
+
from unittest.mock import Mock, patch, MagicMock
|
|
9
|
+
|
|
10
|
+
src_root = Path(__file__).parent.parent / "src"
|
|
11
|
+
sys.path.insert(0, str(src_root))
|
|
12
|
+
|
|
13
|
+
from mouse_keepalive.move_mouse import (
|
|
14
|
+
MouseMover,
|
|
15
|
+
MouseController,
|
|
16
|
+
MousePosition,
|
|
17
|
+
ScreenSize,
|
|
18
|
+
move_mouse,
|
|
19
|
+
main,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Get the module object from sys.modules for patching
|
|
23
|
+
move_mouse_module = sys.modules['mouse_keepalive.move_mouse']
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestMouseMoverCore:
|
|
27
|
+
def test_calculate_next_position(self):
|
|
28
|
+
mover = MouseMover()
|
|
29
|
+
screen = ScreenSize(1920, 1080)
|
|
30
|
+
|
|
31
|
+
assert mover.calculate_next_position(MousePosition(100, 200), screen, 0) == (101, 201)
|
|
32
|
+
assert mover.calculate_next_position(MousePosition(100, 200), screen, 1) == (99, 199)
|
|
33
|
+
|
|
34
|
+
x, y = mover.calculate_next_position(MousePosition(0, 0), screen, 0)
|
|
35
|
+
assert (x, y) == (1, 1)
|
|
36
|
+
|
|
37
|
+
x, y = mover.calculate_next_position(MousePosition(1920, 1080), screen, 1)
|
|
38
|
+
assert (x, y) == (1919, 1079)
|
|
39
|
+
|
|
40
|
+
def test_perform_move_calls_controller(self):
|
|
41
|
+
ctrl = Mock(spec=MouseController)
|
|
42
|
+
ctrl.get_position.return_value = MousePosition(100, 200)
|
|
43
|
+
ctrl.get_screen_size.return_value = ScreenSize(1920, 1080)
|
|
44
|
+
|
|
45
|
+
mover = MouseMover(controller=ctrl)
|
|
46
|
+
original, next_count = mover.perform_move(move_count=0)
|
|
47
|
+
|
|
48
|
+
assert original == MousePosition(100, 200)
|
|
49
|
+
assert next_count == 1
|
|
50
|
+
assert ctrl.move_to.call_count == 2
|
|
51
|
+
|
|
52
|
+
def test_run_interval_and_duration(self):
|
|
53
|
+
ctrl = Mock(spec=MouseController)
|
|
54
|
+
ctrl.get_position.return_value = MousePosition(100, 200)
|
|
55
|
+
ctrl.get_screen_size.return_value = ScreenSize(1920, 1080)
|
|
56
|
+
|
|
57
|
+
times = [0, 0.1, 1.1, 2.1, 2.1]
|
|
58
|
+
mock_time = MagicMock(side_effect=times)
|
|
59
|
+
mock_sleep = MagicMock()
|
|
60
|
+
|
|
61
|
+
mover = MouseMover(controller=ctrl, time_func=mock_time, sleep_func=mock_sleep)
|
|
62
|
+
move_count, elapsed = mover.run(interval=1, duration=2)
|
|
63
|
+
|
|
64
|
+
assert move_count >= 2
|
|
65
|
+
assert elapsed == 2.1
|
|
66
|
+
assert mock_sleep.call_count >= 2
|
|
67
|
+
|
|
68
|
+
def test_run_callbacks_are_called(self):
|
|
69
|
+
ctrl = Mock(spec=MouseController)
|
|
70
|
+
ctrl.get_position.return_value = MousePosition(100, 200)
|
|
71
|
+
ctrl.get_screen_size.return_value = ScreenSize(1920, 1080)
|
|
72
|
+
|
|
73
|
+
times = [0, 0.1, 1.1, 1.1]
|
|
74
|
+
mock_time = MagicMock(side_effect=times)
|
|
75
|
+
mock_sleep = MagicMock()
|
|
76
|
+
|
|
77
|
+
on_start = Mock()
|
|
78
|
+
on_move = Mock()
|
|
79
|
+
on_finish = Mock()
|
|
80
|
+
|
|
81
|
+
mover = MouseMover(controller=ctrl, time_func=mock_time, sleep_func=mock_sleep)
|
|
82
|
+
mover.run(
|
|
83
|
+
interval=1, duration=1,
|
|
84
|
+
on_start=on_start, on_move=on_move, on_finish=on_finish
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
on_start.assert_called_once()
|
|
88
|
+
assert on_move.call_count >= 1
|
|
89
|
+
on_finish.assert_called_once()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestMoveMouseAPI:
|
|
93
|
+
@patch.object(move_mouse_module, "MouseController")
|
|
94
|
+
@patch("time.sleep")
|
|
95
|
+
@patch("time.time")
|
|
96
|
+
def test_move_mouse_basic(self, mock_time, mock_sleep, mock_controller_cls):
|
|
97
|
+
mock_controller = Mock(spec=MouseController)
|
|
98
|
+
mock_controller.get_position.return_value = MousePosition(100, 200)
|
|
99
|
+
mock_controller.get_screen_size.return_value = ScreenSize(1920, 1080)
|
|
100
|
+
mock_controller_cls.return_value = mock_controller
|
|
101
|
+
|
|
102
|
+
mock_time.side_effect = [0, 0.1, 1.1, 1.1]
|
|
103
|
+
|
|
104
|
+
move_mouse(interval=1, duration=1)
|
|
105
|
+
|
|
106
|
+
mock_controller.get_position.assert_called()
|
|
107
|
+
mock_controller.move_to.assert_called()
|
|
108
|
+
|
|
109
|
+
@patch.object(move_mouse_module, "MouseController")
|
|
110
|
+
@patch("time.sleep")
|
|
111
|
+
@patch("time.time")
|
|
112
|
+
def test_move_mouse_bounds(self, mock_time, mock_sleep, mock_controller_cls):
|
|
113
|
+
mock_controller = Mock(spec=MouseController)
|
|
114
|
+
mock_controller.get_position.return_value = MousePosition(0, 0)
|
|
115
|
+
mock_controller.get_screen_size.return_value = ScreenSize(1920, 1080)
|
|
116
|
+
mock_controller_cls.return_value = mock_controller
|
|
117
|
+
|
|
118
|
+
mock_time.side_effect = [0, 0.1, 1.1, 1.1]
|
|
119
|
+
|
|
120
|
+
move_mouse(interval=1, duration=1)
|
|
121
|
+
|
|
122
|
+
# Check that move_to was called with valid coordinates
|
|
123
|
+
# First call should be to new position (clamped to bounds), second back to original
|
|
124
|
+
move_calls = mock_controller.move_to.call_args_list
|
|
125
|
+
assert len(move_calls) >= 2
|
|
126
|
+
|
|
127
|
+
# First call: move to new position (should be clamped to 1, 1)
|
|
128
|
+
first_call = move_calls[0]
|
|
129
|
+
x1, y1 = first_call.args[0], first_call.args[1]
|
|
130
|
+
assert 1 <= x1 < 1920
|
|
131
|
+
assert 1 <= y1 < 1080
|
|
132
|
+
|
|
133
|
+
# Second call: move back to original position (0, 0) - this is expected
|
|
134
|
+
# The bounds check ensures the new position is valid, not the original
|
|
135
|
+
|
|
136
|
+
@patch.object(move_mouse_module, "MouseController")
|
|
137
|
+
@patch("time.sleep", side_effect=KeyboardInterrupt())
|
|
138
|
+
@patch("time.time")
|
|
139
|
+
def test_interrupt_does_not_crash(self, mock_time, mock_sleep, mock_controller_cls):
|
|
140
|
+
mock_controller = Mock(spec=MouseController)
|
|
141
|
+
mock_controller.get_position.return_value = MousePosition(100, 200)
|
|
142
|
+
mock_controller.get_screen_size.return_value = ScreenSize(1920, 1080)
|
|
143
|
+
mock_controller_cls.return_value = mock_controller
|
|
144
|
+
|
|
145
|
+
mock_time.side_effect = [0, 0.1, 0.1]
|
|
146
|
+
move_mouse(interval=1, duration=None)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestMainCLI:
|
|
150
|
+
@patch.object(move_mouse_module, "move_mouse")
|
|
151
|
+
def test_main_with_interval(self, mock_move):
|
|
152
|
+
import sys
|
|
153
|
+
orig = sys.argv
|
|
154
|
+
sys.argv = ["mouse-keepalive", "-i", "30"]
|
|
155
|
+
try:
|
|
156
|
+
main()
|
|
157
|
+
finally:
|
|
158
|
+
sys.argv = orig
|
|
159
|
+
|
|
160
|
+
mock_move.assert_called_once_with(interval=30, duration=None, quiet=False)
|
|
161
|
+
|
|
162
|
+
@patch.object(move_mouse_module, "move_mouse")
|
|
163
|
+
def test_main_with_quiet(self, mock_move):
|
|
164
|
+
import sys
|
|
165
|
+
orig = sys.argv
|
|
166
|
+
sys.argv = ["mouse-keepalive", "-q"]
|
|
167
|
+
try:
|
|
168
|
+
main()
|
|
169
|
+
finally:
|
|
170
|
+
sys.argv = orig
|
|
171
|
+
|
|
172
|
+
mock_move.assert_called_once_with(interval=60, duration=None, quiet=True)
|
|
173
|
+
|
|
174
|
+
@patch.object(move_mouse_module, "move_mouse")
|
|
175
|
+
@patch("builtins.print")
|
|
176
|
+
def test_main_invalid_params_exit(self, mock_print, mock_move_mouse):
|
|
177
|
+
"""Test that main() handles invalid parameters correctly"""
|
|
178
|
+
import sys
|
|
179
|
+
orig_argv = sys.argv
|
|
180
|
+
|
|
181
|
+
for argv in [
|
|
182
|
+
["mouse-keepalive", "-i", "0"],
|
|
183
|
+
["mouse-keepalive", "-i", "-5"],
|
|
184
|
+
["mouse-keepalive", "-d", "0"],
|
|
185
|
+
]:
|
|
186
|
+
sys.argv = argv
|
|
187
|
+
# sys.exit will be called, which raises SystemExit
|
|
188
|
+
# pytest may handle this, so we just verify error message was printed
|
|
189
|
+
try:
|
|
190
|
+
main()
|
|
191
|
+
except SystemExit:
|
|
192
|
+
pass # Expected - sys.exit raises SystemExit
|
|
193
|
+
|
|
194
|
+
# Verify error message was printed
|
|
195
|
+
print_messages = []
|
|
196
|
+
for call in mock_print.call_args_list:
|
|
197
|
+
if call.args:
|
|
198
|
+
print_messages.append(str(call.args[0]))
|
|
199
|
+
|
|
200
|
+
assert any("错误" in msg or "Error" in msg for msg in print_messages), \
|
|
201
|
+
f"Expected error message for argv {argv}, got: {print_messages}"
|
|
202
|
+
|
|
203
|
+
# move_mouse should not be called for invalid params
|
|
204
|
+
# (even if sys.exit doesn't work, we've mocked move_mouse to prevent infinite loops)
|
|
205
|
+
mock_move_mouse.assert_not_called()
|
|
206
|
+
|
|
207
|
+
mock_print.reset_mock()
|
|
208
|
+
mock_move_mouse.reset_mock()
|
|
209
|
+
|
|
210
|
+
sys.argv = orig_argv
|