それはすべて、1つのLinuxディストリビューションの新しいバージョンに移行することから始まりました。そこには、Javascriptの悪名高い GNOME Shell(略してGH)があります。 JSでは、JSで機能します-そして大丈夫です。
同時に、ブラウザーのoutlook.office.com
タブの数メガバイトのブレーキングとガチャガチャではなく、私の仕事のペースは長い間、通常のメーラーを見つけることを要求してきました。 そして今、私は、私たちの時代には、いくつかのほぼ優秀な候補者がいることを発見しました、1つの問題-メーラーは新しい手紙の通知で私を取得し始めました-そして音とポップアップ碑文。
どうする Do Not Disturb拡張機能を作成するという決定はすぐには得られませんでした。自転車を作成したり、開発で動揺したり、コードやエラーが大量に発生したりすることは本当に望みませんでしたが、今ではHabrと経験を共有したいと考えています。 1
技術的要件
欲しいです 大きい 選択した時間に通知と音をオフにするボタン:20分、40分、1時間、2時間、4、8、24時間。 2ええ、スラックのようなタイミング。
extensions.gnome.orgの拡張部分には、「Do Not Disturb Button」という拡張機能があり、拡張機能「 Do Not Disturb Time」を記述するためのモデルとして機能しました 。
最終結果: 時間を邪魔しないでください
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' # gnome-shell Alt+F2, r, Enter # https://extensions.gnome.org/local/ # Gnome Shell - , gnome-shell systemd, journalctl -f /usr/bin/gnome-shell # , gnome-shell journalctl -f /usr/bin/gnome-shell | grep -E 'dnd|$'
対応するディレクトリのextension.js
ファイルは、アプリケーションのエントリポイントです。最小バージョンでは、次のようになります。
function enable() {} // ; function disable() {} // --||-- ; enable()
最初のコード
最初Status Menu
上のスクリーンショットのようStatus Menu
右上のStatus Menu
ボタンを追加します。
それではどこから始めますか? ああ、ドキュメントから始めましょう。 公式のドキュメント、すべてのものがあります。 しかし、公式ドキュメントは非常に小さく断片化されていますが、 julio641742
とその非公式ドキュメントのおかげで、必要なものが得られます。
// 1 - (1 - , 0 - , 0.5 - ) // true, let dndButton = new PanelMenu.Button(1, "DoNotDisturb", false); // `right` - (left/center/right) Main.panel.addToStatusArea("DoNotDisturbRole", dndButton, 0, "right"); let box = new St.BoxLayout(); dndButton.actor.add_child(box); let icon = new St.Icon({ style_class: "system-status-icon" }); icon.set_gicon(Gio.icon_new_for_string("/tmp/bell_normal.svg")); box.add(icon);
このコードは、 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シェルおよびJavaScript機能
外部は2018年末であり、Node.js / V8はJavascriptコードを実行するためのメインツールです。 最新のWeb開発はすべて「ノード」に基づいています。
ただし、GNOMEシェルとその周辺のインフラストラクチャは、異なるJavaScriptエンジンであるMozillaのSpiderMonkeyを使用しているため、パフォーマンスに多くの重要な違いが生じます。
モジュールのインポート
Node.jsとは異なり、require()はなく、派手なES6-importもありません。 代わりに、特別なimportsオブジェクトがあり、その属性にアクセスしてモジュールをロードします:
//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:" + message)-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); -log()とは異なり、stdoutのテキスト+ "\ n"のみ、接頭辞と色付けなし
- "printerr" = g_printerr( "%s \ n"、txt)-印刷との違いはstderrの違いです
しかし、すぐに使えるSpiderMonkey用のデバッガはありません(ロギングに使用できるすべてのツールよりも苦労して書いたのは無駄ではありませんでした!)。 必要に応じて、JSRDbg: one 、 twoを試すことができます。
GNOME Shellの外部にJSコードの寿命はありますか?
あります。 グラフィカルユーザーインターフェイス(GUI)を含むフル機能のアプリケーションは、Javascriptで記述できます。 GUIウィンドウの作成例である、js-GTKコードランチャーであるgjs binarを使用して実行する必要があります。
$ 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シェル拡張機能は、GNOMEシェル全体のすべてのオブジェクトにアクセスできます。 たとえば、まだ未読の通知の数を表示するには、上の中央にある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(); }
この場合、ボタン自体を削除することに加えて、イベント「actor-added」/「actor-removed」の登録を解除する必要があります。例:
var signal = unseenlist.connect("actor-added", () => { log("added!"); }); function disable() { dndButton.destroy(); unseenlist.disconnect(signal); }
これが行われない場合、ハンドラーのコードは対応するイベントで呼び出され続け、もう存在しないメニューボタンの状態を更新しようとします... GNOME Shellは失敗し始めます。 ええ、はい、私たちはそれをします、ユーザーは誓います、石はGNOMEシェル開発者とGNOME全般に飛ぶでしょう。 実際の画像、Th。
GNOME Shell / Gjsは、Glib / GTKとJavascriptの2つのシステムの共生であり、リソース管理へのアプローチが異なります。 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によって制御されます。 悪名高い仕事 悪名高いレナート・ポエタリングのプログラム制作者。 これまで、PulseAudioのコードを手に入れたことはありませんでしたが、PulseAudioをある程度理解する機会を得られたことを嬉しく思いました。
その結果、ミュート/ミュート解除には、1つのpactl
ユーティリティ、またはそれに基づく3つのコマンドで十分であることが判明しました。
- 「pactl info」:
default sink
見つける-複数のサウンド出力がある場合、どのサウンド出力がデフォルトのサウンドか - 「pactl list sinks」:対応するデバイスのミュート/ミュート解除ステータスを確認します
- 「pactl set-sink-mute%(defaultSink)s%(isMute)s」:ミュート/ミュート解除自体
したがって、私たちのタスクは、コマンド/プロセスを実行し、それらの出力stdoutを読み取り、定期的に目的の値を検索することです。 要するに、標準タスク。
GNOMEでは、glibコアライブラリがプロセスの作成を担当し、それに優れたドキュメントがいくつかあります。 そしてもちろん彼女はCにいます。JSがいます。 Gjsパッケージは、C-APIとJavascriptの間にスマートで「直感的な」レイヤーを作成したことが知られています。 しかし、例が必要であり、グーグルなしではできないことを理解しています。
その結果、優れた要点のおかげで、動作するコードが得られます。
let resList = GLib.spawn_command_line_sync(cmd); // res = true/false, / // status = int, // out/err = , stdout/stderr let [res, out, err, status] = resList; if (res != true || status != 0) { print("not ok!"); } else { // do something useful }
設定を保存する レジストリ内
もちろん、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なしでは、申し訳ありませんが、プログラムは完全ではないことがわかっているため、[About]タブを別に作成しましょう。
一般的に、古典的なグラフィカルインターフェイスを構築するために、GTKにはさまざまなビルディングブロック、
があり、 ここで数量と品質を確認できます 。
そのうちのいくつかを使用します。
- Gtk.Notebookはタブであり、ブラウザのようです
- Gtk.VBoxは、ウィジェットリストを垂直に構築するためのコンテナです
- Gtk.Labelは、HTMLをフォーマットする機能を持つ基本要素である碑文です。
function buildPrefsWidget() { // Gtk.Notebook GUI let notebook = new Gtk.Notebook(); ... // About, VBox c 10 , // margin/padding let aboutBox = new Gtk.VBox({ border_width: 10 }); // About notebook.append_page( aboutBox, new Gtk.Label({label: "About"}), ); // , // , (expand) aboutBox.pack_start( new Gtk.Label({ label: "<b>Do Not Disturb Time</b>", use_markup: true, }), true, // expand true, // fill 0, ); ... notebook.show_all(); return notebook; }
最終的なスクリーンショット:
オプショナル
私の場合、プログラマーの仕事には2つのモードが関係しています。
1)サポートモードで、メール、Slack、Skypeなどのイベントにすばやく応答する必要がある場合
2)動作モードでは、少なくとも20分間通知を削減することが重要です。そうしないと、フォーカスが失われ、最終的な労働生産性が無視できます。 これには、サイレントモードが便利です。
完全なミュート、ミュートは多すぎるように思えるかもしれません。 実際、理想的には、Slack / Skypeをサイレントモードで聞くことをお勧めしますが、その他のサウンド(実際の通知)は聞こえません。 しかし、このためには、何らかの形で区別する必要があります。 もちろん、通知専用のサウンドAPIを作成することもできます(これは既に存在します)。そのような機能を使用しないプログラム/プログラマーのみが常に存在します。 例としては、Mailspringメーラーがあります。これは単にaudio
タグを介してサウンドを再生するだけであり、Slackコールのスピーチと区別することはできません。
PanelMenu.Button-これはパネル+ポップアップメニューの実際のボタンであり、自分で把握してゼロから作成することができます。両方とも傷のある人に感謝されます! 迅速な結果を目指していたため、非公式のドキュメントからコードをコピーしました。
SetStatusRemote()を使用して、実際にモード変更を開始します。