Tudo começou com a mudança para uma nova versão de uma distribuição Linux, e lá - o famoso GNOME Shell (GH, abreviado), em Javascript. Bem, ok, no JS e no JS, funciona - e tudo bem.
Ao mesmo tempo, o ritmo do meu trabalho há muito tempo exige encontrar uma mala direta, em vez das toneladas de megabytes de travagem e consumo outlook.office.com
guia outlook.office.com
no navegador. E agora descobri que, em nosso tempo, existem vários candidatos quase excelentes, um problema - a mala direta começou a me receber notificações de novas cartas - e som e inscrições pop-up.
O que fazer A decisão de escrever a extensão Não perturbe não veio imediatamente, eu realmente não queria escrever uma bicicleta e / ou ficar atolado no desenvolvimento / código / toneladas de erros, mas decidi e agora quero compartilhar minha experiência com Habr. 1

Requisitos técnicos
Eu quero ter um grande para desativar as notificações e sons para o horário de sua escolha: 20 minutos, 40 minutos, 1 hora, 2 horas, 4, 8 e 24 horas. 2 Sim, cronometrando como no Slack.
Nas extensões de extensions.gnome.org, havia uma extensão "Não perturbe o botão", que serviu de modelo para escrever sua extensão Não perturbe o tempo .

Instale a partir de extensions.gnome.org .
Fontes no github : coloque estrelas, garfo, ofereça melhorias.
Como instalar a extensão GH: instruções
- Instale o pacote chrome-gnome-shell , um conector do navegador, usando o Ubuntu como exemplo:
sudo apt install chrome-gnome-shell
- Usando o link, instale a extensão do navegador:
- siga o link Clique aqui para instalar a extensão do navegador
- no Ubuntu 18.04, funcionou para mim no navegador Chrome / Chromium, no Fedora 28/29 - no Firefox e no Chromium
- Estamos procurando a extensão desejada na lista https://extensions.gnome.org : ativar, desativar, alterar as configurações de extensão.
- LUCRO!
Iniciar
Crie uma extensão do zero:
$ 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'
O arquivo extension.js
no diretório correspondente é o ponto de entrada em nosso aplicativo, em uma versão mínima semelhante a esta:
function enable() {}
Primeiro código
Primeiro, queremos adicionar um botão ao Status Menu
canto superior direito, como na imagem acima.
Então por onde começar? Oh, vamos começar com a documentação. Temos documentação oficial, tudo. Mas não, a documentação oficial é muito pequena e fragmentada, mas graças a julio641742
e sua documentação não oficial, conseguimos o que precisamos:
Esse código cria o objeto de chave dndButton
classe dndButton
- este é um botão especialmente projetado para o painel Menu Status. E nós o inserimos neste painel usando a função Main.panel.addToStatusArea (). 3
Insira itens de menu com manipuladores aparafusados a eles, exemplo:
let menuItem = new PopupMenu.PopupMenuItem("hello, world!"); menuItem.connect("activate", (menuItem, event) => { log("hello, world!"); }); dndButton.menu.addMenuItem(menuItem);
Obrigado, julio641742, pela documentação! Link:
https://github.com/julio641742/gnome-shell-extension-reference
O código final de trabalho está aqui .
Recursos de shell e Javascript do GNOME
Fora é o final de 2018 e o Node.js / V8 é a principal ferramenta para executar o código Javascript. Todo o desenvolvimento web moderno se baseia em um "nó".
Mas o Shell do GNOME e toda a infraestrutura ao seu redor usam um mecanismo Javascript diferente, o SpiderMonkey da Mozilla, e isso traz muitas diferenças importantes no desempenho.
Módulos de importação
Diferentemente do Node.js, não há require () aqui e também nenhuma importação ES6. Em vez disso, existe um objeto de importação especial, acessando os atributos que levam ao carregamento do módulo:
//const PanelMenu = require("ui/panelMenu"); const PanelMenu = imports.ui.panelMenu;
Nesse caso, baixamos o módulo js / ui / panelMenu.js da biblioteca de pacotes GNOME Shell, que implementa a funcionalidade de um botão com um menu pop-up.
Sim, todos os botões no painel de uma área de trabalho moderna do Linux usando o GNOME são baseados no panelMenu.js. Incluindo: o mesmo botão direito com indicadores de bateria, Wi-fi, volume de som; interruptor de idioma de entrada en-ru.
Em seguida, existe um atributo especial imports.searchPath
- esta é uma lista de caminhos (linhas) nas quais nossos módulos JS serão pesquisados. Por exemplo, alocamos uma função de timer para um módulo timeUtils.js separado e a colocamos perto do ponto de entrada da nossa extensão extension.js. Importe timeUtils.js da seguinte maneira:
// , - ~/.local/share/gnome-shell/extensions/<your-extension>/ const Me = imports.misc.extensionUtils.getCurrentExtension(); // imports.searchPath.unshift(Me.path); // const timeUtils = imports.timeUtils;
Registrando, Depurando Javascript
Bem, como não temos o Node.js, temos nosso próprio log. Em vez de console.log (), várias funções de registro estão disponíveis no código, consulte gjs /../ global.cpp, static_funcs:
- "log" = g_message ("JS LOG:" + message) - efetuando login no stderr, exemplo:
$ cat helloWorld.js log("hello, world"); $ gjs helloWorld.js Gjs-Message: 17:20:21.048: JS LOG: hello, world
- "logError" - registra a pilha de exceções:
- o primeiro argumento necessário é uma exceção e, em seguida, uma vírgula separa o que você deseja
- Por exemplo, se você precisar imprimir a pilha no lugar certo:
try { throw new Error('bum!'); } catch(e) { logError(e, "what a fuck"); }
e isso vai chamar stderr em grande estilo:
(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); - somente texto + "\ n" no stdout, sem prefixos e cores, diferente do log ()
- "printerr" = g_printerr ("% s \ n", txt) - a diferença da impressão é a do stderr
Mas não há depurador para o SpiderMonkey pronto para uso (não foi em vão que eu escrevi meticulosamente acima de todas as ferramentas disponíveis para registro, use-o!). Se desejar, você pode tentar JSRDbg: um , dois .
Existe vida para o código JS fora do GNOME Shell?
Existe. Aplicativos completos, incluindo uma interface gráfica do usuário (GUI), podem ser escritos em Javascript! Você precisa executá-los usando o binar gjs, o iniciador de código JS-GTK, um exemplo de criação de uma janela da 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
Mencionei acima a divisão do código em módulos e o carregamento do Javascript. A questão surge, mas como é o próprio módulo para determinar se é lançado como um módulo "principal" ou carregado a partir de outro módulo?
Python tem uma construção autêntica:
if __name__ == "__main__": main()
No Node.js - da mesma forma:
if (require.main === module) { main(); }
Não encontrei uma resposta oficial para esta pergunta para Gjs / GH, mas criei uma técnica que me apressei a compartilhar com o leitor (por que alguém leu “dosyudova”? Respeito!).
Portanto, o truque complicado é baseado na análise da pilha de chamadas atual, se consiste em 2 ou mais linhas, então não estamos no módulo main ():
if ( new Error().stack.split(/\r\n|\r|\n/g).filter(line => line.length > 0) .length == 1 ) { main(); }
Serviço de limpeza
Cada extensão do Shell do GNOME tem acesso a todos os objetos em todo o Shell do GNOME. Por exemplo, para exibir o número de notificações ainda não lidas, chegamos ao contêiner na Notification Area
, localizada no centro acima, número 4 na imagem (clique na inscrição com a hora atual, é clicável na vida real, não aqui):

let unseenlist = Main.panel.statusArea.dateMenu._messageList._notificationSection._list;
Você pode descobrir quantas notificações não lidas se inscreve em eventos de adição e exclusão de notificações:
let number = unseenlist.get_n_children(); unseenlist.connect("actor-added", () => { log("added!"); }); unseenlist.connect("actor-removed", () => { log("removed!"); });
Isso é bom, mas às vezes o usuário pode decidir que não precisa mais da extensão X e clicar no botão para desativar a extensão. Para uma extensão, isso equivale a chamar a função disable (), e todos os esforços devem ser feitos para que a extensão desativada não quebre o GH de trabalho:
function disable() { dndButton.destroy(); }
Nesse caso, além de excluir o botão em si, é necessário cancelar a inscrição nos eventos "ator adicionado" / "ator removido", por exemplo:
var signal = unseenlist.connect("actor-added", () => { log("added!"); }); function disable() { dndButton.destroy(); unseenlist.disconnect(signal); }
Se isso não for feito, o código dos manipuladores continuará sendo chamado no evento correspondente, tente atualizar o estado do botão de menu que ainda não existe e ... O GNOME Shell começará a falhar. Bem, sim, faremos isso, os usuários jurarão, as pedras voarão para os desenvolvedores do GNOME Shell e o GNOME em geral. A imagem real, Th.
Portanto, o GNOME Shell / Gjs é uma simbiose de dois sistemas, Glib / GTK e Javascript, e eles têm uma abordagem diferente para o gerenciamento de recursos. Glib / GTK requer a liberação explícita de seus recursos (botões, temporizadores, etc.). Se o objeto foi criado pelo mecanismo Javascript, agimos como de costume (não libere nada).
Como resultado, assim que nossa extensão estiver pronta e não "fluir", você poderá publicá-la com segurança em https://extensions.gnome.org .
Modo GnomeSession.PresenceStatus.BUSY e DBus.
Se você não se esqueceu, estamos fazendo a extensão "Não perturbe", que desliga a exibição de notificações para o usuário.
O GNOME já possui uma bandeira responsável por esse estado. Após o login do usuário, é criado o processo gnome-session, no qual esse sinalizador está localizado: este é o atributo GsmPresencePrivate.status, consulte as fontes do gnome-session, gnome-session / gsm-presence.c. Temos acesso a esse sinalizador através da interface DBus (como comunicação entre processos).
Não apenas nós, mas o próprio GH precisa de informações nessa bandeira para não mostrar notificações. Isso é fácil de encontrar na fonte GH:
this._presence = new GnomeSession.Presence((proxy, error) => { this._onStatusChanged(proxy.status); }); ... this._presence.connectSignal('StatusChanged', (proxy, senderName, [status]) => { this._onStatusChanged(status); });
Nesse caso, o método _onStatusChanged é um manipulador que responde a uma alteração de estado. Copiamos esse código para nós mesmos, adaptamos - descobrimos quatro notificações, houve um som.
Sem som / sem som
A maioria dos desktops Linux modernos são controlados pelo PulseAudio, trabalho notório autor do programa do notório Lennart Poettering. Até agora, eu não tinha conseguido escrever o código PulseAudio e fiquei feliz por ter a oportunidade de entender o PulseAudio em algum nível.
Como resultado, verificou-se que, para mudo / não mudo, um utilitário pactl
é pactl
, ou melhor, três comandos baseados nele:
- "pactl info": encontre o
default sink
- qual saída de som, se houver várias, é o som padrão - "pactl list sinks": encontre o status mudo / não mudo do dispositivo correspondente
- "pactl set-sink-mute% (defaultSink) s% (isMute) s": para ativar / desativar o som
Portanto, nossa tarefa é executar comandos / processos, ler sua saída stdout e procurar os valores desejados regularmente. Em suma, uma tarefa padrão.
No GNOME, a biblioteca principal glib é responsável pela criação de processos e existe uma excelente documentação . E é claro que ela está em C. E nós temos JS. Sabe-se que o pacote Gjs criou uma camada inteligente e "intuitiva" entre a C-API e o Javascript. Mas você ainda entende que precisa de exemplos e não pode ficar sem pesquisar no Google.
Como resultado, graças à excelente essência, obtemos o código de trabalho:
let resList = GLib.spawn_command_line_sync(cmd);
Salvando configurações no registro
Obviamente, não há registro no Linux. Aqui você não é o Windows. Existe uma melhor, chamada GSettings (esta é a API), várias opções de implementação estão ocultas por trás dela, por padrão o GNOME usa o Dconf. É assim que a estrutura da GUI se parece:

- O que é melhor do que armazenar configurações em arquivos de texto sem formatação? - Pergunte aos usuários antigos e barbudos do Linux. O principal recurso do GSettings é que você pode assinar facilmente alterações nas configurações, por exemplo:
const Gio = imports.gi.Gio; settings = new Gio.Settings({ settings_schema: schemaObj }); settings.connect("changed::mute-audio", function() { log("I see, you changed it!"); });
A única configuração até o momento em "Não perturbe" é a opção "áudio mudo", que permite ao usuário desligar ou não o som durante a "hora silenciosa" a pedido do usuário.
E um pouco clássico, GTK GUI
Para mostrar lindamente ao usuário as configurações de nossa extensão (e não entrar no registro com patas sujas), o GH nos oferece a gravação de um código da GUI e a inserção na função buildPrefsWidget () do arquivo prefs.js. Nesse caso, em frente à nossa extensão na lista de "Extensões instaladas" aqui , veremos um botão adicional "Configurar esta extensão", clicando no qual a nossa beleza aparecerá.
Vamos criar uma guia Sobre separada, porque sabe-se que sem o Ebout, desculpe, o programa não está completo.
De um modo geral, para criar uma interface gráfica clássica, o GTK possui toda uma gama de componentes básicos,
, os quais você pode conferir a quantidade e a qualidade aqui .
Usaremos apenas alguns deles:
- Gtk.Notebook é uma guia, como em um navegador
- Gtk.VBox é um contêiner para estruturar verticalmente uma lista de widgets
- Gtk.Label é um elemento básico, uma inscrição, com a capacidade de formatar HTML
function buildPrefsWidget() {
Captura de tela final:

Opcional
1. Modos de operação: suporte e operaçãoO trabalho do programador envolve 2 modos no meu caso:
1) no modo de suporte, quando você precisar responder rapidamente a eventos - correio, Slack, Skype e mais
2) no modo operacional, quando é vital reduzir as notificações por pelo menos 20 minutos, caso contrário, o foco é perdido e a produtividade final do trabalho é insignificante. Para isso, o modo Não perturbe é útil.
2. Como desligar o somPode parecer que um mudo completo, mudo, é demais. De fato, idealmente, você gostaria que o Slack / Skype fosse ouvido no modo Não perturbe, mas o restante dos sons (notificações reais) não é. Mas para isso eles precisam ser distinguidos de alguma forma. Obviamente, é possível criar uma API de som específica para notificações (e isso já existe), mas sempre existe um programa / programador que não usa essa funcionalidade. Um exemplo é o mailer do Mailspring: ele simplesmente reproduz sons através da tag de audio
e eles não podem ser distinguidos de forma alguma, digamos, da fala em uma chamada do Slack.
3. PanelMenu.ButtonPanelMenu.Button - este é o botão real no painel + menu pop-up, e você pode descobrir por si mesmo e criá-lo do zero, os dois serão apreciados pelos caras que estão feridos ! Eu estava buscando um resultado rápido e, portanto, copiei o código da documentação não oficial.
4. SetStatusRemote ()Realmente inicie uma alteração de modo usando SetStatusRemote ().