Electron 安装和使用(三)-- 发声器electron进程间的通信

用远程事件从浏览器窗口关闭应用

请切换到02-basic-sound-machine这个tag:
git checkout 02-basic-sound-machine

简要重述–应用窗口(更准确的说是渲染进程)应该不能与GUI(用来关闭窗口)通信的,官方的Electron快速入门指南写到:

在web页面,不允许调用原生GUI相关的API,因为在web页面管理原生GUI资源是很危险的,会很容易泄露资源。如果你想在web页面施行GUI操作,web页面的渲染进程必须要与主进程通信,请求主进程来完成这些操作。

Electron提供ipc(进程间通信)模块来实现这类通信。ipc模块可实现从通道订阅消息,发送消息给通道的订阅者,通道区分消息的接收者,用字符来标识(例如,通道1,通道2)。消息也可以包含数据。当接收到消息,订阅者可以做出反应,甚至回复消息。消息最大的好处就是隔离 — 主进程不必知道哪个渲染进程发出消息。

这正是我们在做的 — 主进程(main.js)订阅「close-main-window」通道的消息,关闭按钮被点击时,渲染进程(index.js)通过通道发出消息。

在main.js里添加下面的代码,从通道订阅消息:

var ipc = require(‘ipc‘);

ipc.on(‘close-main-window‘, function () {
    app.quit();
});

引入ipc模块后,通过通道订阅消息就变得很简单,on()方法设置订阅的通道名,定义回调函数。

渲染进程要通过通道发送消息,将下面代码加入index.js:

var ipc = require(‘ipc‘);

var closeEl = document.querySelector(‘.close‘);
closeEl.addEventListener(‘click‘, function () {
    ipc.send(‘close-main-window‘);
});

同样,我们引入ipc模块,给关闭按钮的元素绑定一个click事件。当点击关闭按钮时,通过「close-main-window」通道的send()方法发送消息。

这里还有个小问题,如果不注意会卡住你,我们已经讨论过–可拖动区域的可点击性。index.css需要把关闭按钮定义成不可拖动:

.settings {
    ...
    -webkit-app-region: no-drag;
}

就这样,现在可以点击关闭按钮关闭我们的应用了。因为要监听事件或传递参数,通过ipc模块通信比较复杂。我们后面会看到一个传递参数的例子。


用全局快捷键播放声音

请切换到名为03-closable-sound-machine的tag:
git checkout 03-closable-sound-machine

基础的发声器工作顺利,但是我们有一个易用性问题–如果发声器一定需要切到应用窗口,再点击才能播放,这个发声器有什么用?

这时我们需要的就是全局快捷键。Electron提供一个全局快捷键模块,允许你监听自定义的键盘组合并做出反应。键盘组合也被叫做加速器,是一系列键盘点击组成的字符串(例如 “Ctrl+Shift+1”)。

既然我们想要捕捉一个原生GUI事件(全局快捷键),然后在应用窗口做出反应(播放声音),我们仍用ipc模块在主进程和渲染进程之间通信。

在深入到代码层面前,有两件事要考虑:

  1. 全局快捷键应在app的「ready」事件触发后被注册(在ready代码块中),
  2. 当通过ipc从主进程发送消息到渲染进程的时候,你要引用到那个窗口(就像「createWindow.webContent.send(‘channel’)」)

记住这些,现在用下面的代码来修改我们的main.js文件:

var globalShortcut = require(‘global-shortcut‘);

app.on(‘ready‘, function() {
    ... // existing code from earlier

    globalShortcut.register(‘ctrl+shift+1‘, function () {
            mainWindow.webContents.send(‘global-shortcut‘, 0);
    });
    globalShortcut.register(‘ctrl+shift+2‘, function () {
        mainWindow.webContents.send(‘global-shortcut‘, 1);
    });
});

首先,我们需要引入global-shortcut模块。然后当我们的程序加载完成,我们注册两个快捷键–一个响应Ctrl,Shift,1组合键,另一个响应Ctrl,Shift,2组合键。两者都会通过「global-shortcut」通道发送一条带一个参数的消息。我们用这些参数来播放相应的声音。在index.js中加入以下代码:

ipc.on(‘global-shortcut‘, function (arg) {
    var event = new MouseEvent(‘click‘);
    soundButtons[arg].dispatchEvent(event);
});

为了方便,我们会模拟一次按钮点击,用我们创建的soundButton选择器给按钮绑定一个播放声音。当收到带有参数1的消息,我们在soundButton[1]元素上模拟一次鼠标点击(在正式环境的应用,你应该封装播放声音的代码,并执行它)。


在新的窗口修改键位配置

切换到名为04-global-shortcuts-bound的tag:
git checkout 04-global-shortcuts-bound

系统同时运行很多应用程序,我们预想的快捷键可能已经被占用了。这正是我们将要新建一个设置窗口,保存我们想要的键位修改的原因。

要实现这个目标,我们需要:

  • 主窗口要有一个设置按钮,
  • 一个设置窗口(需要相应的HTML,CSS和JavaScript文件),
  • ipc消息用来打开,关闭设置窗口及更新全局快捷键,
  • 保存或读取用户系统里JSON格式的设置文件。

设置按钮和设置窗口

类似关闭主窗口,当点击设置按钮时我们通过通道从index.js发送消息。将下面代码加入index.js:

var settingsEl = document.querySelector(‘.settings‘);
settingsEl.addEventListener(‘click‘, function () {
    ipc.send(‘open-settings-window‘);
});

点击设置按钮后,通道「open-settings-window」会发送一条消息到主进程。main.js现在需要做出响应,新建一个窗口,将下面代码插入main.js:

var settingsWindow = null;

ipc.on(‘open-settings-window‘, function () {
    if (settingsWindow) {
        return;
    }

    settingsWindow = new BrowserWindow({
        frame: false,
        height: 200,
        resizable: false,
        width: 200
    });

    settingsWindow.loadUrl(‘file://‘ + __dirname + ‘/app/settings.html‘);

    settingsWindow.on(‘closed‘, function () {
        settingsWindow = null;
    });
});

没有什么新概念,我们会像打开主窗口一样打开新的设置窗口。不同之处是要先检查设置窗口是不是已经被打开,以防重复打开。

打开后,需要一种方法关闭设置窗口。同样的,我们会通过通道发送一条消息,但这次消息是从settings.js发出,将下面代码写入setting.js:

‘use strict‘;

var ipc = require(‘ipc‘);

var closeEl = document.querySelector(‘.close‘);
closeEl.addEventListener(‘click‘, function (e) {
    ipc.send(‘close-settings-window‘);
});

在main.js里面监听那个通道,代码如下:

ipc.on(‘close-settings-window‘, function () {
    if (settingsWindow) {
        settingsWindow.close();
    }
});

我们的设置窗口就完成了。

保存和读取用户的设置

切换到名为05-settings-window-working的tag:
git checkout 05-settings-window-working

与设置窗口交互,保存设置,再读取到我们的应用的过程大致是这样的:

  • 编写一个可以保存和读取我们在JSON文件中保存设置信息的办法,
  • 初始化设置窗口时,显示这些设置,
  • 通过客户的交互更新设置,
  • 通知主程序新的设置。

我们可以简单的保存和读取main.js中的设置,但模块把逻辑抽象出来,以便我们可以在不同的地方引用,这看看起来更好。

Working with a JSON configuration

那就是我们新建configuration.js的原因。Node.js用CommonJS模块规范,这意味着你只可以暴露你的API,而其他文件或方法会引用API提供的方法。

为了让保存和读取更简便,使用nconf模块,它已经为我们抽象出读取和写入JSON文件的方法,非常符合我们的需求。但首先,我们要在CLI中执行下面的命令将它引入项目中:

npm install --save nconf

npm将nconf模块作为应用的依赖安装。在我们打包应用给终端用户时(相对用save-dev参数会只在开发环境中引入模块)将被引入和使用。

configuration.js文件非常的简单,在项目根目录下新建configuration.js文件,写入代码:

‘use strict‘;

var nconf = require(‘nconf‘).file({file: getUserHome() + ‘/sound-machine-config.json‘});

function saveSettings(settingKey, settingValue) {
    nconf.set(settingKey, settingValue);
    nconf.save();
}

function readSettings(settingKey) {
    nconf.load();
    return nconf.get(settingKey);
}

function getUserHome() {
    return process.env[(process.platform == ‘win32‘) ? ‘USERPROFILE‘ : ‘HOME‘];
}

module.exports = {
    saveSettings: saveSettings,
    readSettings: readSettings
};

nconf只需要知道你的设置要保存到哪里,这里我们设置为客户的主文件夹和一个文件名。获取用户的主文件夹非常简单,只需要区别不同系统调用Node.js(process.env)(如用getUserHome()方法)。

通过nconf的内建方法来保存或读取设置(set()方法保存,get()方法读取,用save()和load()方法进行文件操作),用符合CommonJS规范的module.exports语法来导出API。

初始化修改的快捷键

在我们进行设置的交互之前,应初始化设置,以防我们先启动应用丢失设置信息。我们把变更键保存在一个数组中,数组以「shortcutKeys」为键,在main.js里初始化,我们首先要引用configuration模块:

‘use strict‘;

var configuration = require(‘./configuration‘);

app.on(‘ready‘, function () {
    if (!configuration.readSettings(‘shortcutKeys‘)) {
        configuration.saveSettings(‘shortcutKeys‘, [‘ctrl‘, ‘shift‘]);
    }
    ...
}

尝试读取「shortcutKeys」键对应的值,如果读取不到,就设置一个初始值。

现在要重写main.js中的全局快捷键,这个方法可以在后面更新设置的时候直接调用。 去掉原来在main.js中注册快捷键的方法,改成:

app.on(‘ready‘, function () {
    ...
    setGlobalShortcuts();
}

function setGlobalShortcuts() {
    globalShortcut.unregisterAll();

    var shortcutKeysSetting = configuration.readSettings(‘shortcutKeys‘);
    var shortcutPrefix = shortcutKeysSetting.length === 0 ? ‘‘ : shortcutKeysSetting.join(‘+‘) + ‘+‘;

    globalShortcut.register(shortcutPrefix + ‘1‘, function () {
        mainWindow.webContents.send(‘global-shortcut‘, 0);
    });
    globalShortcut.register(shortcutPrefix + ‘2‘, function () {
        mainWindow.webContents.send(‘global-shortcut‘, 1);
    });
}

方法会重置全局快捷键,现在我们可以设置新的快捷键,从设置文件读取变更键数组,转换类加速器规则字符串,再注册全局快捷键。

与设置窗口交互

回到settings.js,我们要绑定click事件来修改我们的全局快捷键。首先,我们遍历所有勾选的复选框(从configuration模块中读取):

var configuration = require(‘../configuration.js‘);

var modifierCheckboxes = document.querySelectorAll(‘.global-shortcut‘);

for (var i = 0; i < modifierCheckboxes.length; i++) {
    var shortcutKeys = configuration.readSettings(‘shortcutKeys‘);
    var modifierKey = modifierCheckboxes[i].attributes[‘data-modifier-key‘].value;
    modifierCheckboxes[i].checked = shortcutKeys.indexOf(modifierKey) !== -1;

    ... // Binding of clicks comes here
}

现在我们要给复选框绑定行为。记得设置窗口(渲染进程)不能改动GUI绑定。这意味着我们需要从setting.js通过ipc发送消息(后面会处理消息):

for (var i = 0; i < modifierCheckboxes.length; i++) {
    ...

    modifierCheckboxes[i].addEventListener(‘click‘, function (e) {
        bindModifierCheckboxes(e);
    });
}

function bindModifierCheckboxes(e) {
    var shortcutKeys = configuration.readSettings(‘shortcutKeys‘);
    var modifierKey = e.target.attributes[‘data-modifier-key‘].value;

    if (shortcutKeys.indexOf(modifierKey) !== -1) {
        var shortcutKeyIndex = shortcutKeys.indexOf(modifierKey);
        shortcutKeys.splice(shortcutKeyIndex, 1);
    }
    else {
        shortcutKeys.push(modifierKey);
    }

    configuration.saveSettings(‘shortcutKeys‘, shortcutKeys);
    ipc.send(‘set-global-shortcuts‘);
}

我们遍历了所有的复选框,绑定click事件,在每次点击时判断是否含有变更键。然后根据结果,修改数组,保存结果到设置,再给主进程发送消息,它会更新我们的全局快捷键。

下面要在main.js里的设置「set-global-shortcuts」这个ipc通道来更新我们的全局快捷键:

ipc.on(‘set-global-shortcuts‘, function () {
    setGlobalShortcuts();
});

很简单,像这样,我们的全局快捷键就配置好了!

那怎样执行到最后一步呢?对桌面应用来说,另一个重要的概念就是菜单栏,还有就是怎样发布的平台的安装包。并且在下篇文章中我们会详细重点介绍并深入Electron.

时间: 2024-10-13 11:22:16

Electron 安装和使用(三)-- 发声器electron进程间的通信的相关文章

linux 进程间的通信

现在linux使用的进程间通信方式:(1)管道(pipe)和有名管道(FIFO)(2)信号(signal)(3)消息队列(4)共享内存(5)信号量(6)套接字(socket) 为何进行进程间的通信:A.数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间B.共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到.C.通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程).D.资源共享

Linux进程间的通信

一.管道 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: A. 管道是半双工的,数据只能向一个方向流动: B. 需要双工通信时,需要建立起两个管道: C. 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程): D. 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中. 匿名管道的创建:该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义;因此,一

Nginx学习——Nginx进程间的通信

nginx进程间的通信 进程间消息传递 共享内存 共享内存还是Linux下提供的最主要的进程间通信方式,它通过mmap和shmget系统调用在内存中创建了一块连续的线性地址空间,而通过munmap或者shmdt系统调用可以释放这块内存.使用共享内存的优点是当多个进程使用同一块共享内存时,在不论什么一个进程改动了共享内存中的内容后,其它进程通过訪问这段共享内存都可以得到改动后的内容. Nginx定义了ngx_shm_t结构体.用于描写叙述一块共享内存, typedef struct{ //指向共享

Linux/UNIX之进程间的通信(2)

进程间的通信(2) 有三种IPC我们称为XSI IPC,即消息队列.信号量以及共享存储器,它们之间有很多相似之处. 标识符和键 每个内核的IPC结构(消息队列.信号量或共享存储段)都用一个非负整数的标识符加以引用.例如,为了对一个消息队列发送或取消息,只需要知道其队列标识符.与文件描述符不同,IPC标识符不是小的整数.当一个IPC结构被创建,以后被删除时,与这种结果相关的标识符连续加1,知道达到一个整型数的最大值,然后又回到0. 标识符是IPC对象的内部名.为使多个合作进程能够在同一IPC对象上

同步线程和进程间的通信

最近回去学习了一下进程和进程间的通信,有时候很多东西久不看了也就一下子忘了== 这里面有好几个互斥对象使用线程的 1 void mListText(CString str) 2 { 3 m_list_text.AddString(str); 4 m_list_text.SendMessage(WM_VSCROLL, SB_PAGEDOWN, 0); 5 } 6 7 8 9 DWORD WINAPI Thread1(LPVOID lpParameter) 10 { 11 //GetDlgItem(

进程间的通信——pipe通信

进程间的通信分为三种  信号通信,管道通信.socket通信 当进程创建管道文件后,其建立的子进程自动继承该文件. 管道通信分为命名管道和未命名管道,他们的区别是命名管道在当创建他的进程结束后,系统仍存有该文件 管道的命令格式为 pipe(fds) 其中 fds定义为fds[2] fds[0]为读文件描述符,1为写文件描述符 原文地址:https://www.cnblogs.com/jiangxue2019/p/11965592.html

swoole进程间如何通信

Swoole进程间通信的方式 管道pipe 管道用于进程之间的数据交互,Linux系统本身提供了pipe函数用于创建一个半双工通信管道.半双工的通信方式中数据只能单向流动(一端只读一端只写),只能在具有亲缘关系(父子进程)的进程之间使用. 管道是进程间通信IPC中最基础的方式,管道有两种类型分别是命名管道.匿名管道. 匿名管道:专门用于具有血缘关系的进程之间,完成数据传递.命名管道:可以用在任何两个进程之间,Swoole中的管道都是匿名管道. 在Swoole中利用eventfd和UnixSock

Android 使用AIDL实现进程间的通信

在Android中,如果我们需要在不同进程间实现通信,就需要用到AIDL技术去完成. AIDL(android Interface Definition Language)是一种接口定义语言,编译器通过*.aidl文件的描述信息生成符合通信协议的Java代码,我们无需自己去写这段繁杂的代码,只需要在需要的时候调用即可,通过这种方式我们就可以完成进程间的通信工作.关于AIDL的编写规则我在这里就不多介绍了,读者可以到网上查找一下相关资料. 接下来,我就演示一个操作AIDL的最基本的流程. 首先,我

进程间的通信如何实现

进程间的通信如何实现 版权声明:本文为博主原创文章,未经博主允许不得转载.

【转】使用AIDL实现进程间的通信之复杂类型传递

使用AIDL实现进程间的通信之复杂类型传递 首先要了解一下AIDL对Java类型的支持. 1.AIDL支持Java原始数据类型. 2.AIDL支持String和CharSequence. 3.AIDL支持传递其他AIDL接口,但你引用的每个AIDL接口都需要一个import语句,即使位于同一个包中. 4.AIDL支持传递实现了android.os.Parcelable接口的复杂类型,同样在引用这些类型时也需要import语句.(Parcelable接口告诉Android运行时在封送(marsha