这一切都始于迁移到一个Linux发行版的新版本,然后就是用Javascript开发的臭名昭著的 GNOME Shell(简称GH)。 好吧,好的,在JS上也是如此,在JS上也可以工作-好的。
同时,长期以来,我的工作节奏一直要求找到一个正常的邮件程序,而不是浏览器中的outlook.office.com
选项卡令人the目结舌的兆字节。 现在我发现,在我们这个时代,有几位几乎是优秀的候选人,一个麻烦-邮递员开始让我收到新信件的通知-声音和弹出式题词。
怎么办 编写“请勿打扰”扩展程序的决定并没有马上做出,我真的不想写自行车和/或陷入开发/代码/大量错误的泥潭,但是我决定,现在我想与Habr分享我的经验。 1个

技术要求
我想要 一个大 按钮可关闭您选择的时间的通知和声音:20分钟,40分钟,1小时,2小时,4、8和24小时。 2是的,时间就像在Slack中一样。
在extensions.gnome.org的扩展中,有一个扩展名“请勿打扰按钮”,用作编写其扩展名“ 请勿打扰时间”的模型 。

从extensions.gnome.org安装。
github上的资料 :放星星,叉子,提供改进。
如何安装GH扩展程序:说明
- 以Ubuntu为例,安装chrome-gnome-shell软件包(浏览器连接器):
sudo apt install chrome-gnome-shell
- 使用链接,安装浏览器扩展:
- 点击链接, 点击此处安装浏览器扩展
- 在Ubuntu 18.04中,它适用于Chrome / Chromium浏览器,Fedora 28/29-在Firefox和Chromium中均适用
- 我们正在列表https://extensions.gnome.org中寻找所需的扩展名:启用,禁用,更改扩展名设置。
- 赢利!
开始
从头开始创建扩展:
$ gnome-shell-extension-tool --create-extension Name: Do Not Disturb Time Description: Disables notifications and sound for a period Uuid: dnd@catbo.net Created extension in '~/.local/share/gnome-shell/extensions/dnd@catbo.net'
对应目录中的extension.js
文件是我们应用程序的入口点,在最小版本中,它看起来像这样:
function enable() {}
第一个代码
首先,我们想Status Menu
右上方的Status Menu
添加一个按钮,如上面的屏幕截图所示。
那么从哪里开始呢? 哦,让我们从文档开始。 我们拥有所有官方文档。 但是,没有,官方文档非常小且分散,但是由于julio641742
及其非官方文档,我们得到了我们需要的东西:
此代码创建dndButton
类的dndButton
键对象-这是专门为“状态菜单”面板设计的按钮。 然后使用Main.panel.addToStatusArea()函数将其插入此面板。 3
插入菜单项,并附加处理程序,例如:
let menuItem = new PopupMenu.PopupMenuItem("hello, world!"); menuItem.connect("activate", (menuItem, event) => { log("hello, world!"); }); dndButton.menu.addMenuItem(menuItem);
感谢julio641742提供的文档! 连结:
https://github.com/julio641742/gnome-shell-extension-reference
最终的工作代码在这里 。
GNOME Shell和Javascript功能
外部是2018年底,Node.js / V8是运行Javascript代码的主要工具。 所有现代Web开发都基于“节点”。
但是GNOME Shell及其周围的整个基础结构使用了不同的Javascript引擎,即Mozilla的SpiderMonkey,这带来了许多重要的性能差异。
导入模块
与Node.js不同,这里没有require(),也没有花哨的ES6-import。 相反,有一个特殊的导入对象,访问该对象的属性会导致模块的加载:
//const PanelMenu = require("ui/panelMenu"); const PanelMenu = imports.ui.panelMenu;
在这种情况下,我们从GNOME Shell包库中下载了js / ui / panelMenu.js模块,该模块实现了带有弹出菜单的按钮的功能。
是的,使用GNOME的现代Linux桌面面板中的所有按钮均基于panelMenu.js。 包括:与电池指示器,Wi-fi,音量相同的右按钮; 输入语言开关en-ru。
接下来,有一个特殊的属性imports.searchPath
这是将在其中搜索我们的JS模块的路径(行)的列表。 例如,我们将计时器函数分配给单独的timeUtils.js模块,并将其放在扩展程序extension.js的输入点附近。 导入timeUtils.js如下:
// , - ~/.local/share/gnome-shell/extensions/<your-extension>/ const Me = imports.misc.extensionUtils.getCurrentExtension(); // imports.searchPath.unshift(Me.path); // const timeUtils = imports.timeUtils;
记录,调试Javascript
好吧,由于我们没有Node.js,因此我们拥有自己的日志记录。 该代码中提供了几个日志记录功能,而不是console.log(),请参见gjs /../ global.cpp,static_funcs:
- “ log” = g_message(“ JS LOG:” +消息)-登录stderr,例如:
$ cat helloWorld.js log("hello, world"); $ gjs helloWorld.js Gjs-Message: 17:20:21.048: JS LOG: hello, world
- “ logError”-记录异常堆栈:
- 第一个必需的参数是一个例外,然后用逗号分隔所需的内容
- 例如,如果您需要在正确的位置打印纸叠:
try { throw new Error('bum!'); } catch(e) { logError(e, "what a fuck"); }
这将以stderr样式绘制:
(gjs:28674): Gjs-WARNING **: 13:39:46.951: JS ERROR: what a fuck: Error: bum! ggg@./gtk.js:5:15 ddd@./gtk.js:12:5 @./gtk.js:15:1
- “ print” = g_print(“%s \ n”,txt); -stdout中只有文本+“ \ n”,没有前缀和颜色,与log()不同
- “ printerr” = g_printerr(“%s \ n”,txt)-与print的区别在于stderr
但是,没有开箱即用的SpiderMonkey调试器(我毫不费力地在所有可用的日志记录工具上辛苦地编写了它,使用它!)。 如果需要,可以尝试JSRDbg: 一 , 二 。
GNOME Shell之外的JS代码还有生命吗?
有。 可以使用Javascript编写功能齐全的应用程序,包括图形用户界面(GUI)! 您需要使用gjs binar,JS-GTK代码启动器(创建GUI窗口的示例)来运行它们:
$ which gjs /usr/bin/gjs $ dpkg --search /usr/bin/gjs gjs: /usr/bin/gjs $ cat gtk.js const Gtk = imports.gi.Gtk; Gtk.init(null); let win = new Gtk.Window(); win.connect("delete-event", () => { Gtk.main_quit(); }); win.show_all(); Gtk.main(); $ gjs gtk.js
上面,我提到将代码分成模块并从Javascript加载它们。 问题出现了,但是如何在模块本身中确定它是作为“主”模块启动还是从另一个模块加载?
Python具有真实的构造:
if __name__ == "__main__": main()
在Node.js中-类似地:
if (require.main === module) { main(); }
我没有找到有关Gjs / GH的正式答案,但是我想出了一种技术,必须与读者分享(为什么有人读“ dosyudova”?尊重!)。
因此,这个棘手的技巧是基于对当前调用堆栈的分析,如果它包含2行或更多行,则我们不在main()模块中:
if ( new Error().stack.split(/\r\n|\r|\n/g).filter(line => line.length > 0) .length == 1 ) { main(); }
家政服务
每个GNOME Shell扩展都可以访问整个GNOME Shell中的所有对象。 例如,要显示仍未读取的通知数,我们将它们带到位于上方中心的Notification Area
的容器,该位置在图片中为数字4(单击当前时间的铭文,可在现实生活中单击,而不是在此处单击):

let unseenlist = Main.panel.statusArea.dateMenu._messageList._notificationSection._list;
您可以查明有多少未读通知,订阅添加和删除通知的事件:
let number = unseenlist.get_n_children(); unseenlist.connect("actor-added", () => { log("added!"); }); unseenlist.connect("actor-removed", () => { log("removed!"); });
很好,但是用户有时可以决定不再需要X扩展名,然后单击按钮以禁用扩展名。 对于扩展,这等效于调用disable()函数,并且应尽一切努力使关闭的扩展不会破坏正在运行的GH:
function disable() { dndButton.destroy(); }
在这种情况下,除了删除按钮本身之外,您还需要取消订阅“添加了角色的事件” /“已删除角色的事件”的订阅,例如:
var signal = unseenlist.connect("actor-added", () => { log("added!"); }); function disable() { dndButton.destroy(); unseenlist.disconnect(signal); }
如果不这样做,则将继续在相应的事件上调用处理程序的代码,尝试更新尚不存在的菜单按钮的状态,并且... GNOME Shell将开始失败。 好吧,是的,我们会做到的,用户会发誓,这块石头将飞向GNOME Shell开发人员和GNOME。 真实情况,Th。
因此GNOME Shell / Gjs是Glib / GTK和Javascript这两个系统的共生,它们具有不同的资源管理方法。 Glib / GTK需要显式释放其资源(按钮,计时器等)。 如果对象是由Javascript引擎创建的,则我们将照常运行(不释放任何内容)。
因此,一旦我们的扩展程序准备好并且没有“流动”,您可以安全地将其发布在https://extensions.gnome.org上 。
GnomeSession.PresenceStatus.BUSY和DBus模式。
如果您还没有忘记的话,我们正在做“请勿打扰”扩展程序,它将关闭向用户显示的通知。
GNOME已经有一个标志负责此状态。 用户登录后,将创建gnome-session进程,该标志位于其中:这是GsmPresencePrivate.status属性,请参阅gnome-session的源,gnome-session / gsm-presence.c。 我们可以通过DBus接口(例如进程间通信)访问此标志。
不仅我们,GH本身也需要有关此标志的信息,以便不显示通知。 在GH来源中很容易找到:
this._presence = new GnomeSession.Presence((proxy, error) => { this._onStatusChanged(proxy.status); }); ... this._presence.connectSignal('StatusChanged', (proxy, senderName, [status]) => { this._onStatusChanged(status); });
在这种情况下,_onStatusChanged方法是响应状态更改的处理程序。 我们将此代码复制给我们自己,进行修改-我们找出了4个通知,有声音。
静音/取消静音
大多数现代Linux台式机均由PulseAudio控制, 臭名昭著的工作 该程序的作者是臭名昭著的Lennart Poettering。 到目前为止,我还没有写过PulseAudio代码,我很高兴能有机会在一定程度上了解PulseAudio。
结果,事实证明,对于静音/取消静音,一个pactl
实用程序就pactl
,或者基于它的三个命令是:
- “ pactl info”:找出
default sink
-如果有多个声音输出,则默认声音 - “ pactl list sinks”:找出相应设备的静音/取消静音状态
- “ pactl set-sink-mute%(defaultSink)s%(isMute)s”:用于静音/取消静音
因此,我们的任务是运行命令/进程,读取其输出标准输出并定期搜索所需的值。 简而言之,这是一项标准任务。
在GNOME中,glib核心库负责创建流程,并且有出色的文档 。 当然,她在C。还有JS。 众所周知,Gjs软件包在C-API和Javascript之间建立了一个智能的“直观”层。 但是您仍然了解,您需要示例,而且您不能不进行谷歌搜索。
结果,由于出色的要旨,我们得到了有效的代码:
let resList = GLib.spawn_command_line_sync(cmd);
保存设置 在注册表中
当然,Linux中没有注册表。 在这里您不是Windows。 有一个更好的名称,叫做GSettings(这是API),它后面隐藏了几个实现选项,默认情况下GNOME使用Dconf。 这是GUI框架的外观:

-有什么比将设置存储在纯文本文件中更好的了? -询问那些有胡子的老Linux用户。 GSettings的主要功能是您可以轻松订阅设置更改,例如:
const Gio = imports.gi.Gio; settings = new Gio.Settings({ settings_schema: schemaObj }); settings.connect("changed::mute-audio", function() { log("I see, you changed it!"); });
到目前为止,“请勿打扰”中的唯一设置是“静音”选项,该选项允许用户在用户要求的“安静时间”内关闭或关闭声音。
还有一些经典的GTK GUI
为了向用户展示扩展名的设置(而不是用脏爪子进入注册表),GH提供了编写GUI代码并将其放入prefs.js文件的buildPrefsWidget()函数的功能。 在这种情况下,在“已安装的扩展程序”列表中扩展程序的对面,我们将看到一个附加按钮“配置此扩展程序”,单击此按钮将显示我们的美丽。
让我们创建一个单独的“关于”选项卡,因为众所周知,如果没有Ebout,程序将无法完成。
一般而言,要构建经典的图形界面,GTK拥有各种构建基块和
,您可以在此处查看其数量和质量。
我们将只使用其中一些:
- Gtk.Notebook是一个标签,就像在浏览器中一样
- Gtk.VBox是一个用于垂直构造小部件列表的容器
- Gtk.Label是基本元素,是题字,能够格式化HTML
function buildPrefsWidget() {
最终屏幕截图:

选配
1.操作方式:支持与操作在我的情况下,程序员的工作涉及两种模式:
1)在支持模式下,当您需要快速响应事件时-邮件,Slack,Skype等
2)在操作模式下,至关重要的是将通知减少至少20分钟,否则焦点将丢失并且最终的劳动生产率可忽略不计。 为此,请勿打扰模式很有用。
2.如何关闭声音似乎一个完全静音,太多的静音。 实际上,实际上,理想情况下,您希望在“请勿打扰”模式下听到Slack / Skype,但其余声音(真实通知)却没有。 但是为此,它们需要以某种方式加以区分。 当然,您可以制作一个专门用于通知的声音API(并且该API已经存在),只有总有一个不使用此类功能的程序/程序员。 Mailspring邮件程序就是一个例子:它只是通过audio
标签播放声音,而无法以任何方式将它们与Slack通话中的语音区分开。
3. PanelMenu.ButtonPanelMenu.Button-这是面板+弹出菜单中的实际按钮,您可以自己弄清楚它并从头开始创建, 伤口处的家伙都会欣赏它们! 我的目标是快速取得结果,因此我复制了非官方文档中的代码。
4. SetStatusRemote()实际上使用SetStatusRemote()启动模式更改。