The original article first in my blog, welcome to pay attention to!

preface

Some time ago, I used electron vue to develop a cross-platform (currently supporting the three major desktop operating systems) free open source map bed upload application — PicGo. During the development process, I stepped in many holes, not only from the application business logic itself, but also from electron itself. I learned a lot in the process of developing this application. Since I also started to learn electron from 0, so many experiences should also be able to give some inspiration and instructions to students who want to learn electron development for the first time. Therefore, write a Electron development of actual combat experience, with the most close to the actual project development Angle to elaborate. I hope it helps.

It is expected to be launched from several series of articles or aspects:

  1. Electron – vue primer
  2. Simple development of the Main and Renderer processes
  3. JSON database lowDB is introduced based on Lodash
  4. Some cross-platform compatibility measures
  5. Release and update through CI
  6. Develop plug-in system – CLI part
  7. Develop plug-in system – GUI part
  8. Command line call and system-level right-click menu implementation
  9. Think of writing again…

instructions

PicGo is developed using electron vue, so if you know vue, it will be faster to learn along. If you have a technology stack like React or Angular, you may not learn much from the Render side of the build, but you should be able to do so on the Electron side.

If the previous article did not read friends can follow from the previous article. This article is mainly based on the important content of PicGo V2.1.0 update.

Command line call

When we use some of the applications developed by Electron, we can see that some programs can be invoked from the command line. Such as VSCode on macOS. Can set the alias in the following code = ‘/ Applications/Visual \ Studio \ code app/Contents/Resources/app/bin/code’, This opens the file by calling VSCode from the command line with code xxx.js. If you want to open the current directory, you can use code., if you want to open a directory code XXX, etc.

Another problem with command line calls is that sometimes our applications are “singletons”, meaning they can’t be “opened more than once”. How do you implement command line calls in a singleton application? PicGo, for example, also uploads images when called from the command line while the app is open, rather than opening a new PicGo window. That’s fine. I’ll explain more below.

Implement command line calls

First we’ll implement the command line call. There’s nothing special about the Electron command line call, much like it does on the Node.js side. I use PicGo as an example:

Once you have PicGo installed on Windows, you can find picgo.exe in the installation directory. Have you ever wondered what would happen if you ran the EXE from the command line? Open powershell in the installation directory, type.\ picgo.exe, and you will find PicGo already opened. What if I open it with some parameters.\ picgo.exe upload

We can print the command line arguments in the ready event in the main process:

app.on('ready', () = > {console.log(process.argv) // ['D:\\PicGo.exe', 'upload']
})
Copy the code

Argv, the key variable that gets the command line parameters on the Node.js side, also gets the command line parameters after Electron is opened on the command line. We can then use the process.argv parameter in the Ready phase of the main process to implement our corresponding functions.

For PicGo, if you open it from the command line and pass the upload xxx.jpg, you can assume that the user needs to call PicGo to upload an image. So we can do this (here is the example code) :

import path from 'path'
import fs from 'fs-extra'
const getUploadFiles = (argv = process.argv, cwd = process.cwd()) = > {
   files = argv.slice(2) // Filter ['D:\\ picgo. exe', 'upload'] to directly obtain the path of the image to be uploaded
   let result = []
   if (files.length > 0) { // If the list of images is not empty
     result = files.map(item= > {
       if (path.isAbsolute(item)) { // If it is an absolute path
         return {
           path: item
         }
       } else {
         let tempPath = path.join(cwd, item) // If it is a relative path, splice it
         if (fs.existsSync(tempPath)) { // Check whether the file exists
           return {
             path: tempPath
           }
         } else {
           return null
         }
       }
     }).filter(item= >item ! = =null) // Exclude null paths
   }
   return result // Return the result
}
Copy the code

Once you get the list of images, perform your own upload logic. The following are the considerations for command-line calls in a single application.

Implement the command line invocation of the singleton application

The development of Electron is very fast. The version of Electron described in this article is the latest V4.1.4, so the API for implementing singleton application also follows the official document. If your version of Electron is not V4.

The official examples of implementing singletons under the current version are:

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

const gotTheLock = app.requestSingleInstanceLock() // Get the singleton lock

if(! gotTheLock) {// If an application is opened twice, getTheLock is false
  app.quit() // Exit the application immediately
} else {
  app.on('second-instance', (event, commandLine, workingDirectory) => { // Triggered when an application tries to open a second instance
    // 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', () => {})}Copy the code

Notice that there is a second-instance event. This event is emitted when we try to open a singleton application after it has been opened. The event callback contains commandLine and workingDeirectory, which are actually process.argv and the corresponding CWD (execution path). So we can write in this event the logic of what to do when the application tries to open again. Here’s another PicGo example:

app.on('second-instance', (event, commandLine, workingDirectory) => {
 let files = getUploadFiles(commandLine, workingDirectory)
 if (files === null || files.length > 0) { // If there is a file list as an argument, the command line is started
   if (files === null) { // If null, PicGo is required to upload images from the clipboard
     uploadClipboardFiles()
   } else { // If not, PicGo is required to upload a specific image file
     // ...
     uploadChoosedFiles(win.webContents, files)
   }
 } else { // If files === [], the startup is not command-line or has no additional parameters
   if (settingWindow) { // The user clicked the PicGo icon to start, so at this time, the original window is brought up and focus
     if (settingWindow.isMinimized()) {
       settingWindow.restore()
     }
     settingWindow.focus()
   }
 }
})
Copy the code

Here we read the commandLine parameter to determine whether the user is calling PicGo from the commandLine to upload the image, or simply opening PicGo again from the PicGo icon. The key logic is to determine if the commandLine has any key arguments, and thus whether our application is invoked from the commandLine. If the user simply opens PicGo again with the PicGo icon, then we should restore and activate the previously opened window to tell the user that you have opened the app before. Of course, the specific business logic can not be generalised, this is just my understanding of PicGo, just know that the core is listening for second-instance events.

Here is a screenshot of the above implementation. Note that the command line output is only in the first terminal process, indicating that we have implemented the command line call of the singleton application:

MacOS command line call

In fact, this chapter basically ends up here. But I remember that my demo was done under Windows, which is relatively simple. The macOS command line call to Electron will cause a pit, so it’s best to mention this. (Since I do not have a Linux machine, so the Linux part is not clear, interested friends can test with me feedback!)

We all know that macOS applications are basically placed under Application, so it’s natural to call them directly from the command line:

open /Applications/PicGo.app
Copy the code

But this does not pass the argument in, because the command is open.

So we need to go to a deeper path to start PicGo and pass parameters in:

/Applications/PicGo.app/Contents/MacOS/PicGo upload xxx.jpg
Copy the code

This is the only way to make a picgo.exe call like Windows.

Note that the Electron macOS application that wants to open debug mode to view the output of the console in production goes to the same directory as the above application:

/Applications/PicGo.app/Contents/MacOS/PicGo --debug
Copy the code

Widnows is relatively simple, requiring only:

.\PicGo.exe --debug
Copy the code

(Please test yourself for Linux)

System level right-click menu

After implementing command line calls, I was thinking about adding a native system right-click menu to PicGo. The advantage of this is that users can directly right-click on an image -> upload via PicGo. Such as:

Windows:

Under macOS:

Let’s talk about the implementation differences. (There is no test for Linux. Please tell me about it if you are interested.)

Windows

Windows right menu principle is actually very simple, in the registry to write values on the line. The reason for this is not to expand too much on the Windows registry. We just care about where we write values and what values we write to get what we want.

First let’s take a look at how VScode implements the right-click menu “Open with Code”.

In the system, press WIN+R and type regedit to open the registry editor. Let’s find the location of VSCode’s right-click menu:

HKEY_CLASSES_ROOT*shellVSCode:

You can see a “default” property under the data is “Open w&ith Code”, this is the name of the menu we see. The data under a property called “Icon” is the exe installation path of VSCode. So it can be thought that this Icon can get the Icon of exe and display it on the menu.

I haven’t seen how to pass the file path as a parameter to VScode yet. Continue to look at:

HKEY_CLASSES_ROOT*shellVSCodecommand:

In the command directory we see the following data:

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

You can see that %1 is passed to code.exe as an argument. With VSCode as a reference, it’s not hard to implement a system-level right-click menu for your own Electron application. One could argue that I could write to the registry at application startup through some NPM package, such as Windows-Registry.

But actually, on Windows, there’s a simpler solution if you’re using the auto-Builder package, and that’s to write an NSIS script to implement it, which is available in the auto-Builder documentation.

NSIS scripting is not described in this article, but it is a scripting language used to generate the Windows installation interface, and you can control the elements of the installation (and uninstallation) interface. And it can tap into the lifecycle of the installation to do things like write to the registry. We use this feature, to PicGo to do an installation phase write registry operation, system level right-click menu.

The main hooks exposed by electron builder to NSIS are customHeader, preInit, customInit, customInstall, customUnInstall, and so on.

We can write registry key values in the customInstall phase by getting the user’s PicGo installation path, $INSTDIR. The installer. NSH file written by yourself is stored in the build directory of the project by default, so the electron Builder will automatically read this file and the configuration in package.json to generate the installation interface when building the Windows application.

The format for writing to the registry looks something like this:

WriteRegStr <reg-path> <your-reg-path> <attr-name> <value>
Copy the code

The following is the Installer. NSH of PicGo for your reference only:

! 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" ! macroendCopy the code

Note that HKCR stands for registry directory HKEY_CLASSES_ROOT. If you want to write multiple arguments to value, enclose them in single quotes. The default value is attr-name. I believe that with VSCode’s right-click menu registry description, you can also read the PicGo script above. Also note that we should delete the previously written registry during the uninstall phase, in case the menu remains after the user uninstalls the application, which is what the latter part of the above script does.

Since the command line invocation was implemented in the previous chapter, our menu can be implemented with ‘$INSTDIR\ picgo. exe” “upload” “%1″‘.

macOS

MacOS can generate right-click menus by implementing automated scripts. Open the automator:

Then create a new quick action:

Limit the quick workflow to image files and only apply to access.app. Also, find the shell component in the left menu and drag it to the right edit area:

Select the shell as /bin/bash and pass the input as the argument.

Then change the default to the following:

/Applications/PicGo.app/Contents/MacOS/PicGo upload "$@" > /dev/null 2>&1 &
Copy the code

In the macOS shortcut operation, it is passed by “$@” as a parameter.

How to use right-click menu? Just put your workflow folder in the ~/Library/Services directory.

So you can see it in your right-click menu:

If you have too many services, you will see it in the secondary menu of services:

The menu name is the name of the workflow file (folder) you generated.

Once you’ve generated this workflow, how do you create it for the user instead of letting them create it manually? MacOS doesn’t have the scripting language for installation tools that Windows does, so you can do this manually in the main process. Here is PicGo’s beforeOpen.js, where we put our generated Workflow file (folder) in the static directory of the project.

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)) { // Check whether it exists
    return true
  } else { // If it doesn't exist, copy it
    try {
      fs.copySync(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
    } catch (e) {
      console.log(e)
    }
  }
}

export default beforeOpen
Copy the code

Add this method to the main process and check if it is running on macOS:

// main/index.js
import beforeOpen from './utils/beforeOpen'
// ...
if (process.platform === 'darwin') {
  beforeOpen()
}
// ...
Copy the code

So after the user installs PicGo and opens the software, he will have a “Upload Pictures with PicGo” item in the right menu.

summary

At this point, a command line call for the Electron application and the system-level right-click menu implementation are covered. Of course, there could be other implementations, as well as more elaborate implementations (such as folder right-click support, etc.). I’m just trying to make a point here, but other implementations or better implementations need to be explored. Of course, there is no Linux related content in this article, mainly because I have limited time and do not have a Linux machine, so I also hope that interested friends can tell me about it after implementing the functions of this article in Linux

This article is a lot of problems I encountered in the development of PicGo, step on the pit. Perhaps behind a few simple words in the article is my countless times of reference and debugging. Hopefully this article has given you some insight into the development of electron- Vue. PicGo, picgo-core, picGo-Core, picGo-Core, PicGo-Core, PicGo-Core, PicGo-Core, PicGo-Core, Picgo-Core, Picgo-Core, Picgo-Core If so, please follow my blog and the rest of this series. (PS: The next article should cover how to build a scalable keyboard shortcut system for Electron.)

Note: the pictures in this article are all my personal works unless otherwise specified, please send a private message

reference

Thanks for the high quality articles, questions, etc:

  1. A good map bed tool -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. And all the great articles I didn’t get to record, thank you!