
Motrix 添加剪贴板监听功能。这个实现分为几个关键部分:
1. 安装依赖
首先在 Motrix 项目目录安装必要的依赖:
npm install ffi-napi ref-napi ref-struct-napi clipboardy
# 或者使用 yarn
yarn add ffi-napi ref-napi ref-struct-napi clipboardy2. 主进程代码修改
在 Motrix 的主进程文件(通常是 src/main/index.js 或 src/main/index.ts)中添加以下代码:
// 导入必要的模块
import { BrowserWindow, clipboard } from 'electron'
import ffi from 'ffi-napi'
import ref from 'ref-napi'
import StructType from 'ref-struct-napi'
// 定义 Windows API 类型
const HWND = ref.refType(ref.types.void)
const BOOL = ref.types.bool
const UINT = ref.types.uint32
const WPARAM = ref.types.uint32
const LPARAM = ref.types.uint32
const LRESULT = ref.types.uint32
// Windows 消息常量
const WM_CLIPBOARDUPDATE = 0x031D
// 加载 user32.dll
const user32 = ffi.Library('user32.dll', {
'AddClipboardFormatListener': [BOOL, [HWND]],
'RemoveClipboardFormatListener': [BOOL, [HWND]],
'GetClipboardSequenceNumber': [UINT, []]
})
class ClipboardMonitor {
constructor(mainWindow) {
this.mainWindow = mainWindow
this.isMonitoring = false
this.lastSequenceNumber = 0
this.downloadUrlPatterns = [
// HTTP/HTTPS/FTP 下载链接
/https?:\/\/[^\s<>"']+\.(zip|rar|7z|exe|msi|dmg|apk|iso|tar\.gz|tar\.bz2|mp4|mp3|pdf)/i,
// 磁力链接
/magnet:\?xt=urn:[a-z0-9]+:[a-zA-Z0-9]+/i,
// BT种子文件链接
/http.+\/announce/i,
// ED2K链接
/ed2k:\/\/\|file\|[^\|]+\|/i,
// Thunder链接
/thunder:\/\/[a-zA-Z0-9]+/i,
// 通用URL(如果有文件扩展名或常见下载参数)
/https?:\/\/[^\s<>"']+\?.*\b(?:download|file|id)=[^&\s]+/i,
// 包含常见文件扩展名的链接(即使没有协议前缀)
/(?:https?:\/\/)?[^\s<>"']+\.(zip|rar|7z|exe|msi|dmg|apk|iso)(?:\?[^\s]*)?/i
]
}
// 开始监听
start() {
if (this.isMonitoring) return
const hwnd = this.getWindowHandle()
if (!hwnd) {
console.error('无法获取窗口句柄')
return
}
// 注册剪贴板监听
const result = user32.AddClipboardFormatListener(hwnd)
if (result) {
this.isMonitoring = true
console.log('剪贴板监听已启动')
// 监听窗口消息
this.setupMessageListener()
} else {
console.error('启动剪贴板监听失败')
}
}
// 停止监听
stop() {
if (!this.isMonitoring) return
const hwnd = this.getWindowHandle()
if (hwnd) {
user32.RemoveClipboardFormatListener(hwnd)
this.isMonitoring = false
console.log('剪贴板监听已停止')
}
}
// 获取窗口原生句柄
getWindowHandle() {
if (!this.mainWindow || this.mainWindow.isDestroyed()) {
return null
}
const handleBuffer = this.mainWindow.getNativeWindowHandle()
if (handleBuffer) {
// 将Buffer转换为指针
return ref.address(handleBuffer)
}
return null
}
// 设置消息监听
setupMessageListener() {
if (!this.mainWindow || this.mainWindow.isDestroyed()) return
// 监听窗口消息
this.mainWindow.hookWindowMessage(WM_CLIPBOARDUPDATE, (wParam, lParam) => {
this.handleClipboardUpdate()
})
}
// 处理剪贴板更新
handleClipboardUpdate() {
try {
// 获取剪贴板序列号,避免重复处理
const currentSeq = user32.GetClipboardSequenceNumber()
if (currentSeq === this.lastSequenceNumber) {
return
}
this.lastSequenceNumber = currentSeq
// 读取剪贴板文本
const clipboardText = clipboard.readText()
if (!clipboardText) return
// 提取下载链接
const downloadLinks = this.extractDownloadLinks(clipboardText)
if (downloadLinks.length > 0) {
this.showDownloadDialog(downloadLinks)
}
} catch (error) {
console.error('处理剪贴板更新时出错:', error)
}
}
// 提取下载链接
extractDownloadLinks(text) {
const links = new Set() // 使用Set去重
// 按行分割文本
const lines = text.split(/\r?\n/)
for (const line of lines) {
// 跳过空行
if (!line.trim()) continue
// 检查是否匹配下载链接模式
for (const pattern of this.downloadUrlPatterns) {
const matches = line.match(pattern)
if (matches) {
// 返回完整的匹配结果
links.add(matches[0])
break
}
}
}
return Array.from(links)
}
// 显示下载对话框
showDownloadDialog(links) {
if (links.length === 1) {
// 单个链接,直接创建下载任务
this.createDownloadTask(links[0])
} else if (links.length > 1) {
// 多个链接,询问用户
const result = this.mainWindow.webContents.executeJavaScript(`
new Promise((resolve) => {
const links = ${JSON.stringify(links)}
const message = '检测到 ' + links.length + ' 个下载链接:\\n\\n' +
links.join('\\n') + '\\n\\n是否全部添加?'
if (confirm(message)) {
resolve('all')
} else {
resolve('single')
}
})
`)
result.then(choice => {
if (choice === 'all') {
links.forEach(link => this.createDownloadTask(link))
} else {
// 让用户选择其中一个
this.mainWindow.webContents.executeJavaScript(`
new Promise((resolve) => {
const links = ${JSON.stringify(links)}
let list = '请选择要下载的链接:\\n'
links.forEach((link, index) => {
list += (index + 1) + '. ' + link + '\\n'
})
const choice = prompt(list, '1')
resolve(parseInt(choice) - 1)
})
`).then(index => {
if (index >= 0 && index < links.length) {
this.createDownloadTask(links[index])
}
})
}
})
}
}
// 创建下载任务
createDownloadTask(url) {
if (!this.mainWindow || this.mainWindow.isDestroyed()) return
// 发送给渲染进程创建下载任务
this.mainWindow.webContents.send('create-download-task', {
url: url,
autoStart: true
})
// 显示窗口(如果最小化或隐藏)
if (this.mainWindow.isMinimized()) {
this.mainWindow.restore()
}
this.mainWindow.show()
this.mainWindow.focus()
}
}
// 在 Motrix 主窗口创建后初始化
function setupClipboardMonitor(mainWindow) {
// 创建监听器实例
const monitor = new ClipboardMonitor(mainWindow)
// 启动监听
monitor.start()
// 监听窗口关闭事件
mainWindow.on('closed', () => {
monitor.stop()
})
return monitor
}
// 导出供 Motrix 主进程使用
export { setupClipboardMonitor, ClipboardMonitor }3. 在 Motrix 主进程中集成
在 Motrix 的主进程入口文件(通常是 src/main/index.js)中找到创建主窗口的位置:
// 在创建 BrowserWindow 之后
function createMainWindow() {
const mainWindow = new BrowserWindow({
// ... 现有配置
})
// 设置剪贴板监听
if (process.platform === 'win32') {
// 延迟启动,确保窗口完全创建
setTimeout(() => {
const { setupClipboardMonitor } = require('./clipboard-monitor')
setupClipboardMonitor(mainWindow)
}, 1000)
} else {
// macOS 和 Linux 可以使用替代方案
setupCrossPlatformClipboardMonitor(mainWindow)
}
return mainWindow
}4. 跨平台兼容版本
对于 macOS 和 Linux,可以使用轮询方式实现简单的剪贴板监听:
// 跨平台剪贴板监听(轮询方式)
class CrossPlatformClipboardMonitor {
constructor(mainWindow) {
this.mainWindow = mainWindow
this.lastText = ''
this.interval = null
this.checkInterval = 1000 // 每秒检查一次
this.downloadUrlPatterns = [
// ... 同样的正则表达式
]
}
start() {
if (this.interval) return
this.lastText = clipboard.readText()
this.interval = setInterval(() => {
this.checkClipboard()
}, this.checkInterval)
console.log('跨平台剪贴板监听已启动')
}
stop() {
if (this.interval) {
clearInterval(this.interval)
this.interval = null
console.log('跨平台剪贴板监听已停止')
}
}
checkClipboard() {
try {
const currentText = clipboard.readText()
if (currentText && currentText !== this.lastText) {
this.lastText = currentText
this.handleClipboardChange(currentText)
}
} catch (error) {
console.error('检查剪贴板时出错:', error)
}
}
handleClipboardChange(text) {
// ... 同样的链接提取和下载逻辑
}
}5. 渲染进程处理
在渲染进程中(例如 src/renderer/components/ 中的某个组件),添加事件监听:
// 在渲染进程中监听创建下载任务事件
import { ipcRenderer } from 'electron'
export default {
mounted() {
ipcRenderer.on('create-download-task', this.handleNewDownloadTask)
},
beforeDestroy() {
ipcRenderer.removeListener('create-download-task', this.handleNewDownloadTask)
},
methods: {
handleNewDownloadTask(event, taskInfo) {
// 调用 Motrix 现有的新建任务方法
this.$store.dispatch('createTask', {
url: taskInfo.url,
// ... 其他选项
})
// 可选:显示通知
this.$notify({
title: '检测到下载链接',
message: `已添加: ${taskInfo.url.substring(0, 50)}...`,
type: 'success'
})
}
}
}6. 配置选项
最后,可以在 Motrix 的设置界面添加开关选项:
// 在设置状态管理中添加配置
const settings = {
// ... 现有设置
monitorClipboard: true,
monitorClipboardInterval: 1000,
autoCreateTask: true,
showNotification: true
}
// 在设置界面添加复选框
// <el-checkbox v-model="settings.monitorClipboard">启用剪贴板监听</el-checkbox>
// <el-checkbox v-model="settings.autoCreateTask">自动创建下载任务</el-checkbox>使用说明
- 将以上代码添加到 Motrix 源码对应位置
- 安装依赖后重新构建 Motrix
- 启动应用后,复制任何包含下载链接的文本,Motrix 会自动检测并弹出下载对话框
这个实现包含了以下特性:
- Windows 原生监听(实时响应)
- 跨平台备用方案(轮询)
- 智能链接提取(支持多种链接格式)
- 多链接处理(可选择全部下载或单个选择)
- 配置选项(可开关)
需要我详细解释某个部分,或者帮你调整正则表达式来匹配更多下载链接类型吗?
暂无评论