Alles begann mit der Umstellung auf eine neue Version einer Linux-Distribution und dort auf die berüchtigte GNOME-Shell (kurz GH) in Javascript. Nun, ok, auf JS so auf JS funktioniert es - und okay.
Gleichzeitig hat das Tempo meiner Arbeit lange Zeit verlangt, einen normalen Mailer zu finden, anstatt die Tonnen von Megabyte auf der Registerkarte outlook.office.com
im Browser zu bremsen und zu verschlingen. Und jetzt stellte ich fest, dass es in unserer Zeit mehrere fast ausgezeichnete Kandidaten gibt, ein Problem - der Mailer begann mich mit Benachrichtigungen über neue Briefe zu bekommen - und Ton und Popup-Inschriften.
Was zu tun ist? Die Entscheidung, die Do Not Disturb-Erweiterung zu schreiben, kam nicht sofort, ich wollte wirklich kein Fahrrad schreiben und / oder mich in der Entwicklung / im Code / in Tonnen von Fehlern festsetzen, aber ich entschied mich und möchte jetzt meine Erfahrungen mit Habr teilen. 1

Technische Anforderungen
Ich möchte haben eine große Schaltfläche zum Deaktivieren von Benachrichtigungen und Sounds für die Zeit Ihrer Wahl: 20 Minuten, 40 Minuten, 1 Stunde, 2 Stunden, 4, 8 und 24 Stunden. 2 Ja, Timing wie in Slack.
Auf den Erweiterungen von extensions.gnome.org gab es eine Erweiterung "Do Not Disturb Button", die als Modell für das Schreiben der Erweiterung Do Not Disturb Time diente .

Installieren Sie von extensions.gnome.org .
Quellen auf Github : Sterne setzen, Gabel, Verbesserungen anbieten.
So installieren Sie die GH-Erweiterung: Anleitung
- Installieren Sie das chrome-gnome-shell- Paket, einen Browser-Connector, am Beispiel von Ubuntu:
sudo apt install chrome-gnome-shell
- Installieren Sie über den Link die Browser-Erweiterung:
- Folgen Sie dem Link Klicken Sie hier, um die Browser-Erweiterung zu installieren
- In Ubuntu 18.04 funktionierte es für mich im Chrome / Chromium-Browser, in Fedora 28/29 - sowohl in Firefox als auch in Chromium
- Wir suchen nach der gewünschten Erweiterung in der Liste https://extensions.gnome.org : Aktivieren, Deaktivieren, Ändern der Erweiterungseinstellungen.
- GEWINN!
Starten Sie
Erstellen Sie eine Erweiterung von Grund auf neu:
$ 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'
Die Datei extension.js
im entsprechenden Verzeichnis ist der Einstiegspunkt in unsere Anwendung. In einer minimalen Version sieht sie folgendermaßen aus:
function enable() {}
Erster Code
Zuerst möchten wir dem Status Menu
oben rechts eine Schaltfläche hinzufügen, wie im obigen Screenshot.
Wo soll ich anfangen? Oh, fangen wir mit der Dokumentation an. Wir haben alle offizielle Unterlagen. Aber nein, die offizielle Dokumentation ist sehr klein und fragmentiert, aber dank julio641742
und seiner inoffiziellen Dokumentation erhalten wir, was wir brauchen:
Dieser Code erstellt das Schlüsselobjekt dndButton
der dndButton
Klasse - dies ist eine Schaltfläche, die speziell für das Statusmenüfenster entwickelt wurde. Und wir fügen es mit der Funktion Main.panel.addToStatusArea () in dieses Panel ein. 3
Fügen Sie Menüelemente mit daran befestigten Handlern ein. Beispiel:
let menuItem = new PopupMenu.PopupMenuItem("hello, world!"); menuItem.connect("activate", (menuItem, event) => { log("hello, world!"); }); dndButton.menu.addMenuItem(menuItem);
Vielen Dank, julio641742, für die Dokumentation! Link:
https://github.com/julio641742/gnome-shell-extension-reference
Der endgültige Arbeitscode ist hier .
GNOME Shell- und Javascript-Funktionen
Draußen ist Ende 2018 und Node.js / V8 ist das Hauptwerkzeug zum Ausführen von Javascript-Code. Die gesamte moderne Webentwicklung beruht auf einem "Knoten".
Die GNOME-Shell und die gesamte umgebende Infrastruktur verwenden jedoch eine andere Javascript-Engine, Mozillas SpiderMonkey, und dies bringt viele wichtige Leistungsunterschiede mit sich.
Module importieren
Im Gegensatz zu Node.js gibt es hier kein require () und auch keinen ausgefallenen ES6-Import. Stattdessen gibt es ein spezielles Importobjekt, dessen Zugriff auf das Laden des Moduls führt:
//const PanelMenu = require("ui/panelMenu"); const PanelMenu = imports.ui.panelMenu;
In diesem Fall haben wir das Modul js / ui / panelMenu.js aus der GNOME Shell-Paketbibliothek heruntergeladen, die die Funktionalität einer Schaltfläche mit einem Popup-Menü implementiert.
Ja, alle Schaltflächen im Bedienfeld eines modernen Linux-Desktops, der GNOME verwendet, basieren auf panelMenu.js. Einschließlich: die gleiche rechte Taste mit Batterieanzeigen, Wi-Fi, Lautstärke; Eingabesprachenschalter en-ru.
Als nächstes gibt es ein spezielles Attribut imports.searchPath
- dies ist eine Liste von Pfaden (Zeilen), in denen unsere JS-Module durchsucht werden. Zum Beispiel haben wir eine Timer-Funktion einem separaten timeUtils.js-Modul zugewiesen und sie in der Nähe des Eingabepunkts unserer Erweiterung extension.js platziert. Importieren Sie timeUtils.js wie folgt:
// , - ~/.local/share/gnome-shell/extensions/<your-extension>/ const Me = imports.misc.extensionUtils.getCurrentExtension(); // imports.searchPath.unshift(Me.path); // const timeUtils = imports.timeUtils;
Protokollieren, Debuggen von Javascript
Nun, da wir Node.js nicht haben, haben wir unsere eigene Protokollierung. Anstelle von console.log () stehen im Code mehrere Protokollierungsfunktionen zur Verfügung, siehe gjs /../ global.cpp, static_funcs:
- "log" = g_message ("JS LOG:" + message) - Anmeldung in stderr, Beispiel:
$ cat helloWorld.js log("hello, world"); $ gjs helloWorld.js Gjs-Message: 17:20:21.048: JS LOG: hello, world
- "logError" - protokolliert den Ausnahmestapel:
- Das erste erforderliche Argument ist eine Ausnahme, dann wird das gewünschte durch ein Komma getrennt
- Beispiel: Wenn Sie den Stapel an der richtigen Stelle drucken müssen:
try { throw new Error('bum!'); } catch(e) { logError(e, "what a fuck"); }
und dies wird in stderr in stil zeichnen:
(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); - im Gegensatz zu log () nur Text + "\ n" in stdout, ohne Präfixe und Farbgebung
- "printerr" = g_printerr ("% s \ n", txt) - der Unterschied zum Druck ist der in stderr
Aber es gibt keinen sofort einsatzbereiten Debugger für SpiderMonkey (es war nicht umsonst, dass ich vor allem die verfügbaren Tools für die Protokollierung sorgfältig geschrieben habe, verwenden Sie ihn!). Falls gewünscht, können Sie JSRDbg ausprobieren: eins , zwei .
Gibt es Leben für JS-Code außerhalb von GNOME Shell?
Es gibt. Voll funktionsfähige Anwendungen, einschließlich einer grafischen Benutzeroberfläche (GUI), können in Javascript geschrieben werden! Sie müssen sie mit gjs binar, dem JS-GTK-Code-Launcher, ausführen. Ein Beispiel für das Erstellen eines GUI-Fensters:
$ 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
Oben habe ich erwähnt, dass Code in Module aufgeteilt und aus Javascript geladen wird. Die Frage stellt sich, aber wie kann im Modul selbst festgestellt werden, ob es als "Hauptmodul" gestartet oder von einem anderen Modul geladen wird?
Python hat ein authentisches Konstrukt:
if __name__ == "__main__": main()
In Node.js - ähnlich:
if (require.main === module) { main(); }
Ich habe keine offizielle Antwort auf diese Frage für Gjs / GH gefunden, aber ich habe mir eine solche Technik ausgedacht, die ich schnell mit dem Leser teilen möchte (warum hat jemand „dosyudova“ gelesen? Respekt!).
Der knifflige Trick basiert also auf der Analyse des aktuellen Aufrufstapels. Wenn er aus zwei oder mehr Zeilen besteht, befinden wir uns nicht im main () -Modul:
if ( new Error().stack.split(/\r\n|\r|\n/g).filter(line => line.length > 0) .length == 1 ) { main(); }
Housekeeping
Jede GNOME-Shell-Erweiterung hat Zugriff auf alle Objekte in der gesamten GNOME-Shell. Um beispielsweise die Anzahl der noch ungelesenen Benachrichtigungen anzuzeigen, gelangen wir zu dem Container mit ihnen im Notification Area
in der Mitte oben, Nummer 4 im Bild (klicken Sie auf die Inschrift mit der aktuellen Uhrzeit, sie kann im realen Leben angeklickt werden, nicht hier):

let unseenlist = Main.panel.statusArea.dateMenu._messageList._notificationSection._list;
Sie können herausfinden, wie viele ungelesene Benachrichtigungen Ereignisse zum Hinzufügen und Löschen von Benachrichtigungen abonnieren:
let number = unseenlist.get_n_children(); unseenlist.connect("actor-added", () => { log("added!"); }); unseenlist.connect("actor-removed", () => { log("removed!"); });
Dies ist in Ordnung, aber der Benutzer kann manchmal entscheiden, dass er die X-Erweiterung nicht mehr benötigt, und auf die Schaltfläche klicken, um die Erweiterung zu deaktivieren. Für eine Erweiterung entspricht dies dem Aufrufen der Funktion disable (), und es sollten alle Anstrengungen unternommen werden, damit die deaktivierte Erweiterung das funktionierende GH nicht beschädigt:
function disable() { dndButton.destroy(); }
In diesem Fall müssen Sie zusätzlich zum Löschen der Schaltfläche selbst die Ereignisse "Schauspieler hinzugefügt" / "Schauspieler entfernt" abbestellen, zum Beispiel:
var signal = unseenlist.connect("actor-added", () => { log("added!"); }); function disable() { dndButton.destroy(); unseenlist.disconnect(signal); }
Wenn dies nicht getan wird, wird der Code der Handler beim entsprechenden Ereignis weiterhin aufgerufen. Versuchen Sie, den Status der Menüschaltfläche zu aktualisieren, die noch nicht vorhanden ist, und ... Die GNOME-Shell schlägt fehl. Nun ja, wir werden es tun, Benutzer werden schwören, die Steine werden zu den GNOME Shell-Entwicklern und GNOME im Allgemeinen fliegen. Das wirkliche Bild, Th.
GNOME Shell / Gjs ist also eine Symbiose aus zwei Systemen, Glib / GTK und Javascript, und sie verfolgen einen unterschiedlichen Ansatz beim Ressourcenmanagement. Glib / GTK erfordert die explizite Freigabe seiner Ressourcen (Schaltflächen, Timer usw.). Wenn das Objekt von der Javascript-Engine erstellt wurde, verhalten wir uns wie gewohnt (geben Sie nichts frei).
Sobald unsere Erweiterung fertig ist und nicht "fließt", können Sie sie daher sicher auf https://extensions.gnome.org veröffentlichen .
GnomeSession.PresenceStatus.BUSY- und DBus-Modus.
Wenn Sie nicht vergessen haben, führen wir die Erweiterung "Nicht stören" durch, mit der die Anzeige von Benachrichtigungen für den Benutzer deaktiviert wird.
GNOME hat bereits eine Flagge, die für diesen Zustand verantwortlich ist. Nach der Benutzeranmeldung wird der Gnome-Sitzungsprozess erstellt, in dem sich dieses Flag befindet: Dies ist das Attribut GsmPresencePrivate.status. Weitere Informationen finden Sie in den Quellen für gnome-session, gnome-session / gsm-Präsenz.c. Wir erhalten Zugriff auf dieses Flag über die DBus-Schnittstelle (solche Interprozesskommunikation).
Nicht nur wir, sondern auch GH selbst benötigt Informationen zu dieser Flagge, um keine Benachrichtigungen anzuzeigen. Dies ist in der GH-Quelle leicht zu finden:
this._presence = new GnomeSession.Presence((proxy, error) => { this._onStatusChanged(proxy.status); }); ... this._presence.connectSignal('StatusChanged', (proxy, senderName, [status]) => { this._onStatusChanged(status); });
In diesem Fall ist die Methode _onStatusChanged ein Handler, der auf eine Statusänderung reagiert. Wir kopieren diesen Code an uns selbst, passen ihn an - wir haben 4 Benachrichtigungen herausgefunden, es war ein Ton zu hören.
Stumm / Aufheben der Stummschaltung
Die meisten modernen Linux-Desktops werden von PulseAudio gesteuert. berüchtigte Arbeit Programmautorschaft des berüchtigten Lennart Poettering. Bis jetzt bin ich noch nicht dazu gekommen, PulseAudio-Code zu schreiben, und ich war froh, die Gelegenheit zu haben, PulseAudio auf einer bestimmten Ebene zu verstehen.
Als Ergebnis stellte sich heraus, dass für die Stummschaltung / Stummschaltung ein pactl
Dienstprogramm pactl
, oder vielmehr drei darauf basierende Befehle:
- "pactl info": Finden Sie die
default sink
- welche Tonausgabe, wenn es mehrere gibt, der Standardton ist - "pactl list sinks": Ermitteln Sie den Mute / Unute-Status des entsprechenden Geräts
- "pactl set-sink-mute% (defaultSink) s% (isMute) s": für Mute / Unmute selbst
Unsere Aufgabe ist es also, Befehle / Prozesse auszuführen, deren Ausgabe stdout zu lesen und regelmäßig nach den gewünschten Werten zu suchen. Kurz gesagt, eine Standardaufgabe.
Bei GNOME ist die glib-Kernbibliothek für die Erstellung von Prozessen verantwortlich, und es gibt eine hervorragende Dokumentation dazu. Und natürlich ist sie in C. Und wir haben JS. Es ist bekannt, dass das Gjs-Paket eine intelligente, "intuitive" Ebene zwischen der C-API und Javascript bildet. Aber Sie verstehen immer noch, dass Sie Beispiele brauchen und Sie können nicht ohne googeln auskommen.
Als Ergebnis erhalten wir dank des hervorragenden Kerns Arbeitscode :
let resList = GLib.spawn_command_line_sync(cmd);
Einstellungen speichern in der Registrierung
Natürlich gibt es unter Linux keine Registrierung. Hier bist du nicht Windows. Es gibt eine bessere, GSettings (dies ist die API), hinter der mehrere Implementierungsoptionen verborgen sind. Standardmäßig verwendet GNOME Dconf. So sieht das GUI-Framework aus:

- Was ist besser als das Speichern von Einstellungen in Nur-Text-Dateien? - Fragen Sie die alten und bärtigen Linux-Benutzer. Das Hauptmerkmal von GSettings ist, dass Sie Änderungen an Einstellungen einfach abonnieren können, zum Beispiel:
const Gio = imports.gi.Gio; settings = new Gio.Settings({ settings_schema: schemaObj }); settings.connect("changed::mute-audio", function() { log("I see, you changed it!"); });
Die einzige Einstellung in unserer Option "Nicht stören" ist die Option "Audio stumm schalten", mit der der Benutzer den Ton für eine "ruhige Stunde" oder nicht auf Wunsch des Benutzers ausschalten kann.
Und ein bisschen klassische GTK-GUI
Um dem Benutzer die Einstellungen unserer Erweiterung auf schöne Weise anzuzeigen (und nicht mit schmutzigen Pfoten in die Registrierung zu gelangen), bietet GH uns an, einen GUI-Code zu schreiben und ihn in die Funktion buildPrefsWidget () der Datei prefs.js einzufügen. In diesem Fall sehen wir gegenüber unserer Erweiterung in der Liste der "Installierten Erweiterungen" hier eine zusätzliche Schaltfläche "Diese Erweiterung konfigurieren", indem Sie darauf klicken, welche unserer Schönheit angezeigt wird.
Lassen Sie uns eine separate Registerkarte "Info" erstellen, da bekannt ist, dass das Programm ohne Ebout leider nicht vollständig ist.
Um eine klassische grafische Oberfläche zu erstellen, verfügt GTK im Allgemeinen über eine ganze Reihe von Bausteinen und
, deren Quantität und Qualität Sie hier überprüfen können.
Wir werden nur einige davon verwenden:
- Gtk.Notebook ist eine Registerkarte, wie in einem Browser
- Gtk.VBox ist ein Container zum vertikalen Strukturieren einer Liste von Widgets
- Gtk.Label ist ein Grundelement, eine Inschrift, mit der HTML formatiert werden kann
function buildPrefsWidget() {
Letzter Screenshot:

Optional
1. Betriebsarten: Unterstützung und BetriebDie Arbeit des Programmierers umfasst in meinem Fall zwei Modi:
1) im Support-Modus, wenn Sie schnell auf Ereignisse reagieren müssen - Mail, Slack, Skype und mehr
2) im Betriebsmodus, wenn es wichtig ist, Benachrichtigungen für mindestens 20 Minuten zu kürzen, andernfalls geht der Fokus verloren und die endgültige Arbeitsproduktivität ist vernachlässigbar. Hierfür ist der Modus „Nicht stören“ hilfreich.
2. So schalten Sie den Ton ausEs scheint, dass eine vollständige Stummschaltung, Stummschaltung, zu viel ist. In der Tat möchten Sie im Idealfall, dass Slack / Skype im Modus „Nicht stören“ gehört wird, der Rest der Sounds (echte Benachrichtigungen) jedoch nicht. Aber dafür müssen sie irgendwie unterschieden werden. Sie können natürlich eine Sound-API speziell für Benachrichtigungen erstellen (und diese ist bereits vorhanden), nur gibt es immer ein Programm / einen Programmierer, der / der diese Funktionalität nicht verwendet. Ein Beispiel ist der Mailspring-Mailer: Er spielt einfach Sounds über das audio
Tag ab und kann beispielsweise in keiner Weise von der Sprache in einem Slack-Anruf unterschieden werden.
3. PanelMenu.ButtonPanelMenu.Button - Dies ist die eigentliche Schaltfläche im Panel + Popup-Menü. Sie können es selbst herausfinden und von Grund auf neu erstellen. Beide werden von den Jungs an der Wunde geschätzt! Ich habe ein schnelles Ergebnis angestrebt und deshalb den Code aus inoffiziellen Unterlagen kopiert.
4. SetStatusRemote ()Initiieren Sie tatsächlich einen Moduswechsel mit SetStatusRemote ().