Electron-vue開發實戰7——命令行調用與系統級別右鍵菜單項的實現

原文首發在個人博客,歡迎關注!html

前言

前段時間,我用electron-vue開發了一款跨平臺(目前支持主流三大桌面操做系統)的免費開源的圖牀上傳應用——PicGo,在開發過程當中踩了很多的坑,不只來自應用的業務邏輯自己,也來自electron自己。在開發這個應用過程當中,我學了很多的東西。由於我也是從0開始學習electron,因此不少經歷應該也能給初學、想學electron開發的同窗們一些啓發和指示。故而寫一份Electron的開發實戰經歷,用最貼近實際工程項目開發的角度來闡述。但願能幫助到你們。vue

預計將會從幾篇系列文章或方面來展開:react

  1. electron-vue入門
  2. Main進程和Renderer進程的簡單開發
  3. 引入基於Lodash的JSON database——lowdb
  4. 跨平臺的一些兼容措施
  5. 經過CI發佈以及更新的方式
  6. 開發插件系統——CLI部分
  7. 開發插件系統——GUI部分
  8. 命令行調用與系統級別右鍵菜單的實現
  9. 想到再寫...

說明

PicGo是採用electron-vue開發的,因此若是你會vue,那麼跟着一塊兒來學習將會比較快。若是你的技術棧是其餘的諸如reactangular,那麼純按照本教程雖然在render端(能夠理解爲頁面)的構建可能學習到的東西很少,不過在main端(Electron的主進程)應該仍是能學習到相應的知識的。git

若是以前的文章沒閱讀的朋友能夠先從以前的文章跟着看。本文主要是基於PicGo v2.1.0版本更新的重要內容作的講述。github

命令行調用

咱們在使用一些Electron開發的應用程序的時候,能夠發現有些程序是能夠經過命令行喚起的。好比VSCode,在macOS的.bash_profile裏能夠設置alias code='/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code',這樣就能夠在命令行裏經過code xxx.js來調用VSCode打開文件了。若是想打開當前目錄,能夠經過code .,若是想打開某個目錄code xxx等等。web

命令行調用裏其實還涉及到一個問題,有的時候咱們的應用是個「單例應用」,也就是不能「多開」。如何在只能單開的應用裏,也實現命令行調用呢?好比PicGo,在軟件打開的時候,命令行調用它也能上傳圖片,而不是打開一個新的PicGo窗口。沒事,下面會詳細說明。shell

實現命令行調用

首先咱們要來實現命令行調用。其實Electron的命令行調用沒有什麼特殊的地方,與在Node.js端很相似。我以PicGo舉例:npm

當咱們在Windows下安裝好了PicGo以後,能夠在安裝目錄裏找到PicGo.exe。你有沒有想過在命令行裏運行這個exe會怎麼樣呢?在安裝目錄裏打開powershell,輸入.\PicGo.exe,你會發現PicGo已經被打開了。若是我是加了一些參數打開會怎麼樣呢.\PicGo.exe uploadjson

咱們能夠在main進程裏的ready事件裏把命令行參數打印出來:windows

app.on('ready', () => {
  console.log(process.argv) // ['D:\\PicGo.exe', 'upload']
})
複製代碼

關鍵出現了,咱們能夠經過process.argv這個在Node.js端獲取命令行參數的關鍵變量一樣得到Electron被命令行打開後的命令行參數。那麼咱們就能夠在main進程的ready階段經過獲取的process.argv參數來實現咱們對應的功能。

對於PicGo而言,若是經過命令行打開它,而且傳遞了upload xxx.jpg的話,咱們就能夠認爲用戶須要調用PicGo來實現上傳一張圖片。那麼咱們能夠這麼作(如下是實例代碼):

import path from 'path'
import fs from 'fs-extra'
const getUploadFiles = (argv = process.argv, cwd = process.cwd()) => {
   files = argv.slice(2) // 過濾['D:\\PicGo.exe', 'upload']這兩個參數,直接獲取須要上傳的圖片路徑
   let result = []
   if (files.length > 0) { // 若是圖片列表不爲空
     result = files.map(item => {
       if (path.isAbsolute(item)) { // 若是是絕對路徑
         return {
           path: item
         }
       } else {
         let tempPath = path.join(cwd, item) // 若是是相對路徑,就拼接
         if (fs.existsSync(tempPath)) { // 判斷文件是否存在
           return {
             path: tempPath
           }
         } else {
           return null
         }
       }
     }).filter(item => item !== null) // 排除爲null的路徑
   }
   return result // 返回結果
}
複製代碼

拿到圖片列表後就執行自帶的上傳邏輯便可。下面說說單開應用的命令行調用注意事項。

實現單例應用的命令行調用

Electron的發展很快,本文講述的Electron版本爲當前最新的v4.1.4,因此關於實現單例應用的api也是跟隨官方文檔走的,若是你的Electron版本不是v4.x,那麼須要找對應版本的Electron文檔。

當前版本下實現單例應用的官方例子是:

const { app } = require('electron')
let myWindow = null

const gotTheLock = app.requestSingleInstanceLock() // 拿到單例鎖

if (!gotTheLock) { // 若是一個應用二次打開,那麼getTheLock爲false
  app.quit() // 當即退出二次打開的應用
} else {
  app.on('second-instance', (event, commandLine, workingDirectory) => { // 一個應用嘗試打開第二個實例時觸發
    // Someone tried to run a second instance, we should focus our window.
    if (myWindow) {
      if (myWindow.isMinimized()) myWindow.restore()
      myWindow.focus()
    }
  })

  // Create myWindow, load the rest of the app, etc...
  app.on('ready', () => {
  })
}
複製代碼

注意有個second-instance事件。當咱們試圖在打開一個單例應用以後再打開這個應用的時候,就會觸發這個事件。而且這個事件的回調函數裏,有commandLineworkingDeirectory,實際上它們就是process.argv和對應的cwd(執行路徑)。所以咱們能夠在這個事件裏書寫當應用試圖被二次打開的時候應該作的事的邏輯。如下依然以PicGo舉例:

app.on('second-instance', (event, commandLine, workingDirectory) => {
 let files = getUploadFiles(commandLine, workingDirectory)
 if (files === null || files.length > 0) { // 若是有文件列表做爲參數,說明是命令行啓動
   if (files === null) { // 若是爲null說明是讓PicGo上傳剪貼板的圖片
     uploadClipboardFiles()
   } else { // 不然說明是讓PicGo上傳具體的圖片文件
     // ...
     uploadChoosedFiles(win.webContents, files)
   }
 } else { // 若是files === [] 說明並非命令行啓動或者並無帶額外參數
   if (settingWindow) { // 說明用戶是點擊了PicGo圖標啓動,那麼這個時候把原有的窗口調出來並focus便可
     if (settingWindow.isMinimized()) {
       settingWindow.restore()
     }
     settingWindow.focus()
   }
 }
})
複製代碼

這裏咱們經過讀取commandLine參數,來判斷用戶是用命令行來調用PicGo上傳圖片的,仍是僅僅是經過PicGo的圖標再次打開PicGo的。關鍵的邏輯就是判斷commandLine裏有沒有關鍵的參數,從而得出是不是從命令行調用咱們的應用的。若是用戶僅僅是經過PicGo圖標再次打開PicGo,那麼咱們應該把以前打開過的窗口復原並激活,告訴用戶你以前已經打開過這個應用了。固然具體的業務邏輯不能一律而論,這裏只是我對PicGo的一點理解,只需知道核心是監聽second-instance事件便可。

如下是上述實現的截圖,注意命令行輸出都只在第一個終端進程裏,說明咱們實現了單例應用的命令行調用:

macOS的命令行調用

其實這個章節到上面基本結束。不過我想起我演示的是在Windows下作的,相對簡單。而macOS下的命令行調用Electron應用會有個坑,因此仍是要說一下爲好。(因爲我沒有Linux機器,因此Linux部分就不說明了,有興趣的朋友能夠測試一下跟我反饋!)

你們都知道macOS的應用基本是放在Application下的,因此咱們會很天然想到直接命令行調用它們:

open /Applications/PicGo.app
複製代碼

可是這樣作並不能傳遞參數進去,由於執行命令的是open

因此咱們須要到更深層次的路徑啓動PicGo並傳遞參數進去:

/Applications/PicGo.app/Contents/MacOS/PicGo upload xxx.jpg
複製代碼

只有這樣才能像Windows那樣相似PicGo.exe來實現調用。

值得注意的是,Electron的macOS應用想要在生產階段打開debug模式查看console的輸出也是到上述應用的對應目錄下:

/Applications/PicGo.app/Contents/MacOS/PicGo --debug
複製代碼

Widnows相對簡單,只須要:

.\PicGo.exe --debug
複製代碼

(Linux請自測)

系統級別右鍵菜單

在實現了命令行調用的功能以後,我就在考慮給PicGo加上原生的系統右鍵菜單。這樣作的好處是用戶能夠直接在一張圖片上右鍵->經過PicGo上傳。例如:

Windows下:

macOS下:

接下來講說兩者在實現上不一樣的地方。(Linux沒有測試,歡迎有興趣的小夥伴測試一下跟我說說~)

Windows

Windows的右鍵菜單的原理其實很簡單,在註冊表裏寫入值就行。篇幅緣由不會對Windows註冊表的知識作過多的展開。咱們只關注往哪裏寫值,寫哪些值才能實現咱們要的效果。

首先咱們能夠看看VScode是如何實現右鍵菜單「Open with Code」的。

VScode的右鍵菜單

在系統裏按快捷鍵WIN+R而後輸入regedit打開註冊表編輯器,咱們來找到VSCode的右鍵菜單所在地:

HKEY_CLASSES_ROOT*shellVSCode:

能夠看到一個「默認」的屬性下的數據爲「Open w&ith Code」,這個就是咱們看到的菜單名。而一個叫「Icon」的屬性下的數據爲VSCodeexe安裝路徑。因此能夠認爲這個Icon能夠獲取exeIcon並顯示到菜單上。

不過這裏尚未看到如何將文件路徑做爲參數傳入VScode的。繼續看:

HKEY_CLASSES_ROOT*shellVSCodecommand:

command目錄下咱們看到了以下數據:

"C:\Users\PiEgg\AppData\Local\Programs\Microsoft VS Code\Code.exe" "%1"

能夠看出這個%1就是做爲參數傳給Code.exe的。有了VSCode做爲參考,給本身的Electron應用實現一個系統級別的右鍵菜單也不難了。有人可能會說我能夠在應用啓動階段經過某些npm包(好比windows-registry)來實現對註冊表的寫入。

不過實際上,在Windows平臺,若是你是用electron-builder打包的話有一個更簡潔的解決方案,那就是編寫NSIS腳原本實現,對此electron-builder官方給出的文檔能夠一看。

本文不對NSIS腳本作過多的描述,你只須要知道它是用來生成Windows安裝界面的一門腳本語言,你能夠經過它來控制安裝(卸載)界面都有哪些元素。而且它能夠接入安裝的生命週期,作一些操做,好比寫入註冊表。咱們利用這個特性,來給PicGo作一個安裝階段寫入註冊表的操做,實現系統級別的右鍵菜單。

electron-builderNSIS暴露的鉤子主要有customHeader, preInit, customInit, customInstall, customUnInstall,等等。

咱們能夠在customInstall階段經過獲取用戶安裝PicGo的路徑$INSTDIR來實現對註冊表關鍵值的寫入。本身書寫的installer.nsh默認放在項目的build目錄下,那麼electron-builder在構建Windows應用的時候將會自動讀取這個文件以及package.json裏的配置來生成安裝界面。

寫入註冊表的格式大概是這樣:

WriteRegStr <reg-path> <your-reg-path> <attr-name> <value>
複製代碼

如下是PicGo的installer.nsh,僅供參考:

!macro customInstall
   WriteRegStr HKCR "*\shell\PicGo" "" "Upload pictures w&ith PicGo"
   WriteRegStr HKCR "*\shell\PicGo" "Icon" "$INSTDIR\PicGo.exe"
   WriteRegStr HKCR "*\shell\PicGo\command" "" '"$INSTDIR\PicGo.exe" "upload" "%1"'
!macroend
!macro customUninstall
   DeleteRegKey HKCR "*\shell\PicGo"
!macroend
複製代碼

注意HKCR便是註冊表目錄HKEY_CLASSES_ROOT的縮寫。在寫value的時候若是要寫多個參數,能夠用單引號包起來。attr-name不寫即爲默認。相信有了VSCode的右鍵菜單註冊表說明,你也能看得懂上面的PicGo的腳本了。同時注意咱們應該在卸載階段將以前寫的註冊表刪除,以避免用戶卸載了應用以後菜單還在,上述腳本的後面部分是是在作這個事情。

由於上一章實現了命令行調用,因此咱們的菜單就能夠經過'"$INSTDIR\PicGo.exe" "upload" "%1"'來實現菜單調用命令了。

macOS

macOS的話能夠經過實現自動化腳原本生成右鍵菜單。打開automator

而後新建一個快速操做:

將快速操做的工做流程限制到圖像文件,而且只做用於訪達.app裏,同時在左側菜單裏找到shell組件,將其拖拽到右側編輯區:

shell選擇成/bin/bash,傳遞輸入選成做爲自變量

而後將默認的內容改爲以下(實際上就差很少是以前說的macOS下如何命令行調用Electron應用的寫法):

/Applications/PicGo.app/Contents/MacOS/PicGo upload "$@" > /dev/null 2>&1 &
複製代碼

其中macOS的快捷操做裏,是經過"$@"來做爲參數傳遞的。

如何做爲右鍵菜單?只要把你生成的這個workflow文件(夾),放到~/Library/Services這個目錄下就好了。

這樣你就在你右鍵菜單裏看到它:

若是你的服務項過多的話,會在服務的二級菜單裏看到它:

其中,菜單名就是你生成的這個workflow的文件(夾)名。

那麼生成了這個workflow以後,咱們如何實現不讓用戶手動建立,而是自動幫他們放到~/Library/Services目錄下呢?macOS沒有Windows那麼方便的安裝工具腳本語言,那麼咱們能夠在main進程裏手動來實現這個功能。下面是PicGo的beforeOpen.js,其中咱們將咱們生成的workflow文件(夾)放到項目的static目錄下。

import fs from 'fs-extra'
import path from 'path'
import os from 'os'
if (process.env.NODE_ENV !== 'development') {
  global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\')
}
if (process.env.DEBUG_ENV === 'debug') {
  global.__static = path.join(__dirname, '../../../static').replace(/\\/g, '\\\\')
}
function beforeOpen () {
  const dest = `${os.homedir}/Library/Services/Upload pictures with PicGo.workflow`
  if (fs.existsSync(dest)) { // 判斷是否存在
    return true
  } else { // 若是不存在就複製過去
    try {
      fs.copySync(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
    } catch (e) {
      console.log(e)
    }
  }
}

export default beforeOpen
複製代碼

而後在主進程里加入這個方法,並判斷是否在macOS下運行:

// main/index.js
import beforeOpen from './utils/beforeOpen'
// ...
if (process.platform === 'darwin') {
  beforeOpen()
}
// ...
複製代碼

這樣用戶在安裝PicGo以後,打開軟件以後,他的右鍵菜單就多了一個「Upload pictures with PicGo」項了。

小結

至此,一個Electron應用的命令行調用以及系統級別右鍵菜單的實現就講述完了。固然可能還有其餘實現的方式,以及更細緻的實現(好比還能支持文件夾右鍵等等)。我在這裏也只是一個拋磚引玉,其餘的實現或者更好的實現方式須要本身摸索啦。固然本文沒有Linux的相關內容,主要是我時間有限而且沒有Linux機器,因此也但願有興趣的朋友本身在Linux下實現了本文的功能後也能跟我說說~

本文不少都是我在開發PicGo的時候碰到的問題、踩的坑。也許文中簡單的幾句話背後就是我無數次的查閱和調試。但願這篇文章可以給你的electron-vue開發帶來一些啓發。文中相關的代碼,你均可以在PicGoPicGo-Core的項目倉庫裏找到,歡迎star~若是本文可以給你帶來幫助,那麼將是我最開心的地方。若是喜歡,歡迎關注個人博客以及本系列文章的後續進展。(PS: 下一篇文章應該會講述一下如何構建一個Electron應用 可擴展的快捷鍵系統 。)

注:文中的圖片除未特意說明以外均屬於我我的做品,須要轉載請私信

參考文獻

感謝這些高質量的文章、問題等:

  1. 一個還不錯的圖牀工具-PicUploader
  2. Passing command line arguments to electron executable (after installing an already packaged app)
  3. Command Line Arguments in Dev Mode
  4. Electron app Docs
  5. 以及沒來得及記錄的那些好文章,感謝大家!
相關文章
相關標籤/搜索