前言
為了進一步提高開發工作效率,最近我們基於 electron
開發了一款媲美 uTools
的開源工具箱 rubick。該工具箱不僅僅開源,最重要的是可以使用 uTools
生態內所有開源外掛!這將是巨大的能力,意味著 uTools
生態內所有外掛可以無差異化使用到 rubick 中。為了更滿足 uTools
生態使用者的習慣,提高工作開發效率,我們又實現了 uTools
的超級皮膚能力:
程式碼倉庫
功能截圖:
資料夾下長按右建
選擇檔案後長按右鍵
選擇文字後長按右鍵
實現原理
獲取選中文案
要實現改功能核心是要讀取當前使用者選中的文案或者檔案,根據當前選擇內容進行不同功能展示。但是核心有一個問題是如何來實現獲取當前選中的內容。這個問題思考了很久很久,要想獲取選中的文案,感覺唯一的辦法是使用 ctrl + c
或者 command + c
來先複製到剪下板,再通過 electron clipboard
來獲取當前剪下板內容。但是 utools
可不是通過先複製再長按這樣的操作來實現的,而是直接選中文字或者檔案長按後呼起超級皮膚。所以一定要在右擊長按前獲取到當前選中的內容。
如果要這麼幹,可能真的無解了,之前就因為這麼想,才被無解了。正確的思路應該是先長按再獲取選中的內容。別看只是掉了個個,但實現確實天壤之別:
- 先獲取選中內容:這就要求我們必須監聽原生系統選中事件,但是
electron
並沒有提供能力,我們也無法監聽系統選擇事件。 - 先右擊,後獲取內容,這樣的好處在於先右擊可以通過監聽滑鼠右擊事件,相比選擇事件更加容易。
所以思路就有了,先監聽長按右擊事件:
// macos
const mouseEvents = require("osx-mouse");
const mouseTrack = mouseEvents();
// 按下去的 time
let down_time = 0;
// 是否彈起
let isPress = false;
// 監聽右擊
mouseTrack.on('right-down', () => {
isPress = true;
down_time = Date.now();
// 長按 500ms 後觸發
setTimeout(async () => {
if (isPress) {
// 獲取選中內容
const copyResult = await getSelectedText();
}, 500);
})
mouseTrack.on('right-up', () => {
isPress = false;
});
接下來一步就是要去實現獲取選中內容,要獲取選中內容有個比較騷的操作,就是:
- 通過
clipboard
先獲取當前剪下板內容,並存下 A - 通過
robot.js
來呼叫系統command + c
或者ctrl + c
- 再通過
clipboard
先獲取當前剪下板內容,並存下 B - 再將 A 寫到剪下板中,返回 B
先存剪下板內容的目的在於我們是偷偷幫使用者執行了複製動作,當讀取完使用者選擇內容後,需要回複使用者之前的剪下板內容。接下來看一下簡單的實現:
const getSelected = () => {
return new Promise((resolve) => {
// 快取之前的文案
const lastText = clipboard.readText('clipboard');
const platform = process.platform;
// 執行復制動作
if (platform === 'darwin') {
robot.keyTap('c', 'command');
} else {
robot.keyTap('c', 'control');
}
setTimeout(() => {
// 讀取剪下板內容
const text = clipboard.readText('clipboard') || ''
const fileUrl = clipboard.read('public.file-url');
// 恢復剪下板內容
clipboard.writeText(lastText);
resolve({
text,
fileUrl
})
}, 300);
})
}
通知超級皮膚視窗當前選中內容
當獲取到了選中內容後,接下來就是需要建立超級皮膚的 BrowserWindow
:
const { BrowserWindow, ipcMain, app } = require("electron");
module.exports = () => {
let win;
let init = (mainWindow) => {
if (win === null || win === undefined) {
createWindow();
}
};
let createWindow = () => {
win = new BrowserWindow({
frame: false,
autoHideMenuBar: true,
width: 250,
height: 50,
show: false,
alwaysOnTop: true,
webPreferences: {
webSecurity: false,
enableRemoteModule: true,
backgroundThrottling: false,
nodeIntegration: true,
devTools: false,
},
});
win.loadURL(`file://${__static}/plugins/superPanel/index.html`);
win.once('ready-to-show', () => win.show());
win.on("closed", () => {
win = undefined;
});
};
let getWindow = () => win;
return {
init: init,
getWindow: getWindow,
};
};
然後再通知 superPanel
進行內容展示:
win.webContents.send('trigger-super-panel', {
...copyResult,
optionPlugin: optionPlugin.plugins,
});
超級皮膚點選操作
接下來要實現超級皮膚點選操作,這塊也是比較簡單的了,直接上程式碼好了:
1. 開啟 Terminal
const { spawn } = require ('child_process');
spawn('open', [ '-a', 'Terminal', fileUrl ]);
2. 新建檔案
remote.dialog.showSaveDialog({
title: "請選擇要儲存的檔名",
buttonLabel: "儲存",
defaultPath: fileUrl.replace('file://', ''),
showsTagField: false,
nameFieldLabel: '',
}).then(result => {
fs.writeFileSync(result.filePath, '');
});
3. 複製路徑
clipboard.writeText(fileUrl.replace('file://', ''))
最後
本篇主要介紹如何實現一個類似於 utools 的超級皮膚功能,當然這遠遠不是 utools 的全部,下期我們再繼續介紹如何實現 utools 其他能力。歡迎大家前往體驗 Rubick 有問題可以隨時提 issue 我們會及時反饋。
另外,如果覺得設計實現思路對你有用,也歡迎給個 Star:https://github.com/clouDr-f2e/rubick