Semuanya dimulai dengan pindah ke versi baru dari satu distribusi Linux, dan di sana - GNOME Shell yang terkenal (singkatnya GH), dalam Javascript. Baiklah, ok, di JS dan sebagainya, JS berfungsi - dan oke.
Pada saat yang sama, kecepatan pekerjaan saya telah lama menuntut untuk menemukan mailer yang normal, alih-alih mengerem dan menenggelamkan banyak megabita dari tab outlook.office.com
di browser. Dan sekarang saya menemukan, di zaman kita ada beberapa kandidat yang hampir sempurna, satu masalah - pengirim surat mulai memberi saya pemberitahuan tentang surat-surat baru - dan suara, serta tulisan pop-up.
Apa yang harus dilakukan Keputusan untuk menulis ekstensi Jangan Ganggu tidak datang segera, saya benar-benar tidak ingin menulis sepeda dan / atau terjebak dalam pengembangan / kode / ton kesalahan, tetapi saya memutuskan, dan sekarang saya ingin berbagi pengalaman saya dengan Habr 1

Persyaratan teknis
Saya ingin memilikinya satu besar tombol untuk mematikan notifikasi dan suara untuk waktu pilihan Anda: 20 menit, 40 menit, 1 jam, 2 jam, 4, 8 dan 24 jam. 2 Ya, waktu seperti di Slack.
Pada hamparan extensions.gnome.org ada ekstensi "Do Not Disturb Button", yang berfungsi sebagai model untuk menulis ekstensi. Do Not Disturb Time .

Pasang dari extensions.gnome.org .
Sumber di github : pasang bintang, garpu, tawarkan peningkatan.
Cara menginstal ekstensi GH: instruksi
- Instal paket chrome-gnome-shell , konektor browser, menggunakan Ubuntu sebagai contoh:
sudo apt install chrome-gnome-shell
- Dengan menggunakan tautan, instal ekstensi browser:
- ikuti tautannya Klik di sini untuk menginstal ekstensi browser
- di Ubuntu 18.04, ia bekerja untuk saya di browser Chrome / Chromium, di Fedora 28/29 - baik di Firefox maupun di Chromium
- Kami mencari ekstensi yang diinginkan dalam daftar https://extensions.gnome.org : aktifkan, nonaktifkan, ubah pengaturan ekstensi.
- KEUNTUNGAN!
Mulai
Buat ekstensi dari awal:
$ 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'
File extension.js
di direktori yang sesuai adalah titik masuk dalam aplikasi kami, dalam versi minimal terlihat seperti ini:
function enable() {}
Kode pertama
Pertama, kami ingin menambahkan tombol ke Status Menu
kanan atas, seperti pada tangkapan layar di atas.
Jadi dari mana harus memulai? Oh, mari kita mulai dengan dokumentasinya. Kami memiliki dokumentasi resmi, semuanya. Tetapi tidak, dokumentasi resmi sangat kecil dan terfragmentasi, tetapi berkat julio641742
dan dokumentasinya yang tidak resmi, kami mendapatkan yang kami butuhkan:
Kode ini menciptakan objek kunci dndButton
dari kelas dndButton
- ini adalah tombol yang dirancang khusus untuk panel Status Menu. Dan kami memasukkannya ke panel ini menggunakan fungsi Main.panel.addToStatusArea (). 3
Masukkan item menu dengan penangan yang dibaut padanya, misalnya:
let menuItem = new PopupMenu.PopupMenuItem("hello, world!"); menuItem.connect("activate", (menuItem, event) => { log("hello, world!"); }); dndButton.menu.addMenuItem(menuItem);
Terima kasih, julio641742, untuk dokumentasinya! Tautan:
https://github.com/julio641742/gnome-shell-extension-reference
Kode kerja terakhir ada di sini .
Fitur Shell GNOME dan Javascript
Di luar adalah akhir 2018, dan Node.js / V8 adalah alat utama untuk menjalankan kode Javascript. Semua pengembangan web modern bertumpu pada "simpul."
Tetapi GNOME Shell dan seluruh infrastruktur di sekitarnya menggunakan mesin Javascript yang berbeda, SpiderMonkey Mozilla, dan ini membawa banyak perbedaan penting dalam kinerja.
Modul Impor
Tidak seperti Node.js, tidak ada keharusan () di sini, dan tidak ada impor ES6 mewah juga. Sebagai gantinya, ada objek impor khusus, yang mengakses atribut yang mengarah ke pemuatan modul:
//const PanelMenu = require("ui/panelMenu"); const PanelMenu = imports.ui.panelMenu;
Dalam hal ini, kami mengunduh modul js / ui / panelMenu.js dari pustaka paket GNOME Shell, yang mengimplementasikan fungsi tombol dengan menu pop-up.
Ya, semua tombol di panel desktop Linux modern menggunakan GNOME didasarkan pada panelMenu.js. Termasuk: tombol kanan yang sama dengan indikator baterai, Wi-fi, volume suara; masukan bahasa saklar en-ru.
Selanjutnya, ada atribut khusus imports.searchPath
- ini adalah daftar jalur (baris) tempat modul JS kami akan dicari. Sebagai contoh, kami mengalokasikan fungsi timer ke modul timeUtils.js yang terpisah dan meletakkannya di dekat titik input ekstensi kami, extension.js. Impor timeUtils.js sebagai berikut:
// , - ~/.local/share/gnome-shell/extensions/<your-extension>/ const Me = imports.misc.extensionUtils.getCurrentExtension(); // imports.searchPath.unshift(Me.path); // const timeUtils = imports.timeUtils;
Logging, debugging Javascript
Yah, karena kita tidak punya Node.js, maka kita punya logging sendiri. Alih-alih console.log (), beberapa fungsi logging tersedia dalam kode, lihat gjs /../ global.cpp, static_funcs:
- "log" = g_message ("JS LOG:" + message) - masuk ke stderr, contoh:
$ cat helloWorld.js log("hello, world"); $ gjs helloWorld.js Gjs-Message: 17:20:21.048: JS LOG: hello, world
- "logError" - mencatat tumpukan pengecualian:
- argumen pertama yang diperlukan adalah pengecualian, kemudian koma memisahkan apa yang Anda inginkan
- contoh, jika Anda perlu mencetak tumpukan di tempat yang tepat:
try { throw new Error('bum!'); } catch(e) { logError(e, "what a fuck"); }
dan ini akan menarik stderr dalam gaya:
(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); - hanya teks + "\ n" di stdout, tanpa awalan dan pewarnaan, tidak seperti log ()
- "printerr" = g_printerr ("% s \ n", txt) - perbedaan dari hasil cetak adalah di stderr
Tetapi tidak ada debugger untuk SpiderMonkey di luar kotak (itu tidak sia-sia bahwa saya susah payah menulis di atas semua alat yang tersedia untuk login, gunakan!). Jika diinginkan, Anda dapat mencoba JSRDbg: satu , dua .
Apakah ada kehidupan untuk kode JS di luar GNOME Shell?
Ada. Aplikasi berfitur lengkap, termasuk antarmuka pengguna grafis (GUI), dapat ditulis dalam Javascript! Anda perlu menjalankannya menggunakan gjs binar, peluncur kode JS-GTK, contoh membuat jendela 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
Di atas, saya menyebutkan kode pemisahan ke dalam modul dan memuatnya dari Javascript. Muncul pertanyaan, tetapi bagaimana dalam modul itu sendiri untuk menentukan apakah itu diluncurkan sebagai modul "utama", atau diambil dari modul lain?
Python memiliki konstruksi otentik:
if __name__ == "__main__": main()
Di Node.js - sama:
if (require.main === module) { main(); }
Saya tidak menemukan jawaban resmi untuk pertanyaan ini untuk Gjs / GH, tetapi saya datang dengan teknik sedemikian rupa sehingga saya cepat-cepat berbagi dengan pembaca (mengapa seseorang membaca "dosyudova"? Hormat!).
Jadi, trik rumit didasarkan pada analisis tumpukan panggilan saat ini, jika terdiri dari 2 baris atau lebih, maka kita tidak berada dalam modul main ():
if ( new Error().stack.split(/\r\n|\r|\n/g).filter(line => line.length > 0) .length == 1 ) { main(); }
Rumah tangga
Setiap ekstensi GNOME Shell memiliki akses ke semua objek di seluruh Shell GNOME. Misalnya, untuk menampilkan jumlah pemberitahuan yang masih belum dibaca, kami sampai di wadah bersama mereka di Notification Area
, yang terletak di tengah di atas, nomor 4 di gambar (klik pada tulisan dengan waktu saat ini, itu dapat diklik dalam kehidupan nyata, bukan di sini):

let unseenlist = Main.panel.statusArea.dateMenu._messageList._notificationSection._list;
Anda dapat mengetahui berapa banyak, notifikasi yang belum dibaca, berlangganan acara penambahan dan penghapusan notifikasi:
let number = unseenlist.get_n_children(); unseenlist.connect("actor-added", () => { log("added!"); }); unseenlist.connect("actor-removed", () => { log("removed!"); });
Ini bagus, tetapi pengguna terkadang dapat memutuskan bahwa ia tidak lagi membutuhkan ekstensi X, dan mengklik tombol untuk menonaktifkan ekstensi. Untuk ekstensi, ini sama dengan memanggil fungsi disable (), dan segala upaya harus dilakukan agar ekstensi yang dimatikan tidak merusak GH yang berfungsi:
function disable() { dndButton.destroy(); }
Dalam hal ini, selain menghapus tombol itu sendiri, Anda harus berhenti berlangganan dari acara "aktor-ditambahkan" / "aktor-dihapus", misalnya:
var signal = unseenlist.connect("actor-added", () => { log("added!"); }); function disable() { dndButton.destroy(); unseenlist.disconnect(signal); }
Jika ini tidak dilakukan, maka kode penangan akan terus dipanggil pada acara yang sesuai, cobalah untuk memperbarui status tombol menu yang belum ada dan ... GNOME Shell akan mulai gagal. Ya, kami akan melakukannya, pengguna akan bersumpah, batu akan terbang ke pengembang Shell GNOME dan GNOME secara umum. Gambaran nyata, Th.
Jadi GNOME Shell / Gjs adalah simbiosis dari dua sistem, Glib / GTK dan Javascript, dan mereka memiliki pendekatan yang berbeda untuk manajemen sumber daya. Glib / GTK membutuhkan pelepasan sumber dayanya secara eksplisit (tombol, timer, dll.). Jika objek itu dibuat oleh mesin Javascript, maka kami bertindak seperti biasa (jangan melepaskan apa pun).
Akibatnya, segera setelah ekstensi kami siap, dan tidak "mengalir", Anda dapat mempublikasikannya dengan aman di https://extensions.gnome.org .
Mode GnomeSession.PresenceStatus.BUSY dan DBus.
Jika Anda belum lupa, kami sedang melakukan ekstensi "Jangan Ganggu", yang mematikan tampilan pemberitahuan kepada pengguna.
GNOME sudah memiliki bendera yang bertanggung jawab untuk keadaan ini. Setelah pengguna login, proses gnome-session dibuat, di mana flag ini berada: ini adalah atribut GsmPresencePrivate.status, lihat sumber-sumber sesi-gnome, gnome-session / gsm-presence.c. Kami mendapatkan akses ke flag ini melalui antarmuka DBus (komunikasi antarproses semacam itu).
Bukan hanya kami, tetapi GH sendiri membutuhkan informasi tentang bendera ini agar tidak menampilkan pemberitahuan. Ini cukup mudah ditemukan di sumber GH:
this._presence = new GnomeSession.Presence((proxy, error) => { this._onStatusChanged(proxy.status); }); ... this._presence.connectSignal('StatusChanged', (proxy, senderName, [status]) => { this._onStatusChanged(status); });
Dalam kasus ini, metode _onStatusChanged adalah penangan yang merespons perubahan status. Kami menyalin kode ini ke diri kami sendiri, mengadaptasinya - kami menemukan 4 pemberitahuan, ada suara.
Bungkam / bunyikan
Sebagian besar desktop Linux modern dikendalikan oleh PulseAudio, pekerjaan terkenal program kepengarangan dari Lennart Poettering yang terkenal kejam. Sampai sekarang, saya belum sempat menulis kode PulseAudio, dan saya senang mendapat kesempatan untuk memahami PulseAudio pada tingkat tertentu.
Akibatnya, ternyata untuk mute / unmute, satu utilitas pactl
sudah pactl
, atau lebih tepatnya tiga perintah berdasarkan itu:
- "info pactl": cari tahu
default sink
- keluaran suara mana, jika ada beberapa, adalah bunyi default - "pactl list sinks": cari tahu status bisu / bunyikan perangkat yang bersangkutan
- "pactl set-sink-mute% (defaultSink) s% (isMute) s": untuk mute / unmute itu sendiri
Jadi, tugas kita adalah menjalankan perintah / proses, membaca stdout keluaran mereka dan mencari nilai yang diinginkan secara teratur. Singkatnya, tugas standar.
Di GNOME, pustaka inti glib bertanggung jawab untuk membuat proses, dan ada dokumentasi yang sangat bagus di dalamnya. Dan tentu saja dia di C. Dan kami memiliki JS. Diketahui bahwa paket Gjs membuat lapisan pintar, "intuitif" antara C-API dan Javascript. Tetapi Anda masih mengerti bahwa Anda membutuhkan contoh dan Anda tidak dapat melakukannya tanpa googling.
Alhasil, berkat intinya yang sangat baik , kami mendapatkan kode yang berfungsi:
let resList = GLib.spawn_command_line_sync(cmd);
Menyimpan Pengaturan dalam registri
Tidak, tentu saja, tidak ada registri di Linux. Di sini Anda bukan Windows. Ada yang lebih baik, yang disebut GSettings (ini adalah API), beberapa opsi implementasi tersembunyi di belakangnya, secara default GNOME menggunakan Dconf. Inilah yang menjadi kerangka kerja GUI untuknya:

- Apa yang lebih baik daripada menyimpan pengaturan dalam file teks biasa? - Tanyakan pengguna Linux yang lama dan berjanggut. Fitur utama GSettings adalah Anda dapat dengan mudah berlangganan perubahan pada pengaturan, misalnya:
const Gio = imports.gi.Gio; settings = new Gio.Settings({ settings_schema: schemaObj }); settings.connect("changed::mute-audio", function() { log("I see, you changed it!"); });
Satu-satunya pengaturan sejauh ini dalam "Jangan Ganggu" kami adalah opsi "bisu audio", yang memungkinkan pengguna untuk mematikan suara atau tidak selama durasi "jam tenang" atas permintaan pengguna.
Dan sedikit klasik, GTK GUI
Untuk memperlihatkan kepada pengguna pengaturan ekstensi kami dengan indah (dan tidak masuk ke dalam registri dengan cakar kotor), GH menawarkan kami untuk menulis kode GUI dan memasukkannya ke dalam fungsi buildPrefsWidget () dari file prefs.js. Dalam hal ini, berlawanan dengan ekstensi kami dalam daftar "Ekstensi yang Dipasang" di sini kita akan melihat tombol tambahan "Konfigurasikan ekstensi ini", dengan mengeklik mana keindahan kami akan muncul.
Mari kita buat tab About yang terpisah, karena diketahui bahwa tanpa Ebout, maaf, programnya tidak lengkap.
Secara umum, untuk membangun antarmuka grafis klasik, GTK memiliki seluruh jajaran blok bangunan,
, yang dapat Anda lihat kuantitas dan kualitasnya di sini .
Kami hanya akan menggunakan beberapa dari mereka:
- Gtk.Notebook adalah tab, seperti di browser
- Gtk.VBox adalah wadah untuk menyusun daftar widget secara vertikal
- Gtk.Label adalah elemen dasar, sebuah prasasti, dengan kemampuan untuk memformat HTML
function buildPrefsWidget() {
Tangkapan layar akhir:

Opsional
1. Mode pengoperasian: dukungan dan operasiPekerjaan programmer melibatkan 2 mode dalam kasus saya:
1) dalam mode dukungan, saat Anda perlu merespons acara dengan cepat - surat, Kendur, Skype, dan lainnya
2) dalam mode operasi, ketika sangat penting untuk mengurangi pemberitahuan setidaknya selama 20 menit, jika tidak maka fokus akan hilang dan produktivitas tenaga kerja akhir dapat diabaikan. Untuk ini, mode Do Not Disturb berguna.
2. Cara mematikan suaraTampaknya bisu yang lengkap, bisu, terlalu banyak. Memang, pada kenyataannya, idealnya, Anda ingin Slack / Skype didengar dalam mode Do Not Disturb, tetapi sisa suara (notifikasi nyata) tidak. Tetapi untuk ini mereka perlu dibedakan entah bagaimana. Anda tentu saja dapat membuat API suara khusus untuk notifikasi (dan ini sudah ada), hanya saja selalu ada program / programmer yang tidak menggunakan fungsionalitas tersebut. Contohnya adalah mailer Mailspring: ia hanya memutar suara melalui tag audio
, dan mereka tidak dapat dibedakan dengan cara apa pun, misalnya, dari ucapan dalam panggilan Slack.
3. PanelMenu.TombolPanelMenu.Button - ini adalah tombol aktual di menu panel + pop-up, dan Anda dapat mengetahuinya sendiri dan membuatnya dari awal, keduanya akan dihargai oleh orang - orang yang terluka ! Saya mengincar hasil yang cepat, dan karena itu saya menyalin kode dari dokumentasi tidak resmi.
4. SetStatusRemote ()Sebenarnya memulai perubahan mode menggunakan SetStatusRemote ().