كيفية كتابة امتداد لـ GNOME Shell: وضع عدم الإزعاج

بدأ كل شيء بالانتقال إلى نسخة جديدة من توزيعة لينكس واحدة ، وهناك - جنوم شل سيئة السمعة (GH باختصار) ، في جافا سكريبت. حسنًا ، حسنًا ، على JS وهكذا على JS ، يعمل - حسنًا.


في الوقت نفسه ، طالبت وتيرة عملي منذ فترة طويلة بالعثور على مرسل بريد عادي ، بدلاً من كبح واستهلاك أطنان الميغابايت من علامة التبويب outlook.office.com في المتصفح. والآن وجدت ، في عصرنا ، هناك العديد من المرشحين الممتازين تقريبًا ، مشكلة واحدة - بدأ البريد في إخطاري برسائل الرسائل الجديدة - والصوت ، والنقوش المنبثقة.


ماذا تفعل لم يأت قرار كتابة ملحق "الرجاء عدم الإزعاج" على الفور ، ولم أرغب حقًا في كتابة دراجة و / أو التورط في تطوير / رمز / الكثير من الأخطاء ، لكنني قررت ، والآن أريد مشاركة تجربتي مع هابر. 1



المتطلبات الفنية


أريد الحصول عليها واحد كبير زر لإيقاف تشغيل الإشعارات والأصوات للوقت الذي تختاره: 20 دقيقة و 40 دقيقة وساعة واحدة وساعتان و 4 و 8 و 24 ساعة. 2 نعم ، توقيت مثل سلاك.


على امتداد امتدادات extension.gnome.org ، كان هناك امتداد "زر عدم الإزعاج" ، والذي كان بمثابة نموذج لكتابة امتداده عدم الإزعاج الوقت .


النتيجة النهائية: عدم الإزعاج


لا تزعج الوقت


التثبيت من extension.gnome.org .
المصادر على github : ضع النجوم ، الشوكة ، اعرض التحسينات.


كيفية تثبيت ملحق GH: تعليمات


  1. قم بتثبيت حزمة chrome-gnome-shell ، موصل متصفح ، باستخدام Ubuntu كمثال:
    sudo apt install chrome-gnome-shell
  2. باستخدام الرابط ، قم بتثبيت ملحق المستعرض:
    • اتبع الرابط انقر هنا لتثبيت ملحق المتصفح
    • في Ubuntu 18.04 ، عملت معي في متصفح Chrome / Chromium ، في Fedora 28/29 - في Firefox و Chromium
  3. نحن نبحث عن الامتداد المطلوب في القائمة https://extensions.gnome.org : تمكين أو تعطيل أو تغيير إعدادات الامتداد.
  4. ربح!

ابدأ


إنشاء امتداد من الصفر:


 $ 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 بأعلى اليسار ، كما في لقطة الشاشة أعلاه.


إذن من أين تبدأ؟ لنبدأ بالوثائق. لدينا وثائق رسمية ، كل شيء. لكن لا ، الوثائق الرسمية صغيرة جدًا ومجزأة ، ولكن بفضل 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


رمز العمل النهائي هنا .


ميزات جنوم شيل وجافا سكريبت


Outside هي نهاية عام 2018 ، و Node.js / V8 هي الأداة الرئيسية لتشغيل كود Javascript. كل تطوير الويب الحديث يرتكز على "عقدة".


لكن جنوم شل والبنية التحتية بأكملها حوله تستخدم محرك جافا سكريبت مختلفًا ، SpiderMonkey في Mozilla ، وهذا يجلب الكثير من الاختلافات المهمة في الأداء.


وحدات الاستيراد


على عكس Node.js ، لا يوجد طلب () هنا ، ولا يوجد استيراد ES6 خيالي أيضًا. بدلاً من ذلك ، هناك كائن استيراد خاص ، يؤدي الوصول إلى سماته إلى تحميل الوحدة:


  //const PanelMenu = require("ui/panelMenu"); const PanelMenu = imports.ui.panelMenu; 

في هذه الحالة ، قمنا بتنزيل وحدة js / ui / panelMenu.js من مكتبة حزم جنوم شيل ، والتي تنفذ وظيفة زر بقائمة منبثقة.


نعم ، جميع الأزرار الموجودة في لوحة سطح مكتب Linux الحديث باستخدام GNOME تستند إلى panelMenu.js. بما في ذلك: نفس الزر الأيمن مع مؤشرات البطارية ، Wi-fi ، مستوى الصوت ؛ إدخال لغة الإدخال en-ru.


بعد ذلك ، هناك سمة مميزة خاصة imports.searchPath - هذه قائمة بالمسارات (الخطوط) حيث سيتم البحث عن وحدات JS الخاصة بنا. على سبيل المثال ، قمنا بتخصيص وظيفة مؤقت لوحدة timeUtils.js منفصلة ووضعناها بالقرب من نقطة الإدخال الخاصة بالامتداد extension.js. وقت الاستيراد Utils.js على النحو التالي:


 //     , -  ~/.local/share/gnome-shell/extensions/<your-extension>/ const Me = imports.misc.extensionUtils.getCurrentExtension(); //       imports.searchPath.unshift(Me.path); //   const timeUtils = imports.timeUtils; 

تسجيل ، تصحيح أخطاء جافا سكريبت


حسنًا ، نظرًا لعدم وجود 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) ؛ - فقط نص + "\ n" في stdout ، بدون بادئات ولون ، على عكس log ()
  • "printerr" = g_printerr ("٪ s \ n"، txt) - الاختلاف عن الطباعة هو في stderr

ولكن لا يوجد مصحح أخطاء لـ SpiderMonkey خارج الصندوق (لم يكن عبثًا أن أكتب بشق الأنفس فوق جميع الأدوات المتاحة لتسجيل الدخول ، استخدمه!). إذا كنت ترغب في ذلك ، يمكنك تجربة JSRDbg: واحد ، اثنان .


هل هناك حياة لرمز JS خارج جنوم شيل؟


يوجد. يمكن كتابة التطبيقات كاملة المواصفات ، بما في ذلك واجهة المستخدم الرسومية (GUI) في جافا سكريبت! تحتاج إلى تشغيلها باستخدام bin gjs binar ، JS-GTK code launcher ، مثال على إنشاء نافذة 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 

أعلاه ، ذكرت تقسيم التعليمات البرمجية إلى وحدات وتحميلها من جافا سكريبت. السؤال الذي يطرح نفسه ، ولكن كيف في الوحدة نفسها لتحديد ما إذا كان يتم إطلاقها كوحدة "رئيسية" ، أو تحميلها من وحدة أخرى؟


تمتلك Python بنية أصلية:


 if __name__ == "__main__": main() 

في Node.js - بالمثل:


 if (require.main === module) { main(); } 

لم أجد إجابة رسمية على هذا السؤال لـ Gjs / GH ، لكني توصلت إلى تقنية أسارع إلى مشاركتها مع القارئ (لماذا قرأ شخص ما "dosyudova"؟ الاحترام!).


لذا ، فإن الحيلة الصعبة تعتمد على تحليل مكدس المكالمة الحالي ، إذا كان يتكون من خطين أو أكثر ، فإننا لسنا في الوحدة الرئيسية ():


 if ( new Error().stack.split(/\r\n|\r|\n/g).filter(line => line.length > 0) .length == 1 ) { main(); } 

التدبير المنزلي


كل ملحق من جنوم شيل لديه حق الوصول إلى جميع الكائنات في غنوم شيل بالكامل. على سبيل المثال ، لعرض عدد الإشعارات التي لا تزال غير مقروءة ، نصل إلى الحاوية معهم في منطقة 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 ، وينقر على الزر لتعطيل الامتداد. بالنسبة للملحق ، يكون هذا مكافئًا لاستدعاء وظيفة الإيقاف () ، ويجب بذل كل جهد ممكن حتى لا يقطع امتداد الإيقاف GH العامل:


 function disable() { dndButton.destroy(); } 

في هذه الحالة ، بالإضافة إلى حذف الزر نفسه ، تحتاج إلى إلغاء الاشتراك من الأحداث "تمت إضافة الممثل" / "تمت إزالة الممثل" ، على سبيل المثال:


 var signal = unseenlist.connect("actor-added", () => { log("added!"); }); function disable() { dndButton.destroy(); unseenlist.disconnect(signal); } 

إذا لم يتم ذلك ، فسيستمر استدعاء رمز المعالجات في الحدث المقابل ، حاول تحديث حالة زر القائمة غير الموجودة بالفعل ... وستبدأ GNOME Shell في الفشل. حسنًا ، نعم ، سنفعل ذلك ، سيقسم المستخدمون ، وستطير الأحجار إلى مطوري جنوم شيل وجنوم بشكل عام. الصورة الحقيقية ، ث.


لذا فإن جنوم شيل / جي جي إس هو تكافل بين نظامين ، جليب / جي تي كي وجافا سكريبت ، ولديهم نهج مختلف لإدارة الموارد. يتطلب Glib / GTK تحريرًا صريحًا لموارده (الأزرار والمؤقتات وما إلى ذلك). إذا تم إنشاء الكائن بواسطة محرك جافا سكريبت ، فإننا نتصرف كالمعتاد (لا تطلق أي شيء).


ونتيجة لذلك ، بمجرد أن تصبح الإضافة جاهزة ، ولا "تتدفق" ، يمكنك نشرها بأمان على https://extensions.gnome.org .


GnomeSession.PresenceStatus.BUSY ووضع DBus.


إذا لم تكن قد نسيت ، فنحن نقوم بالامتداد "عدم الإزعاج" ، والذي يوقف عرض الإخطارات للمستخدم.


جنوم لديه بالفعل علم مسؤول عن هذه الحالة. بعد تسجيل دخول المستخدم ، يتم إنشاء عملية جلسة جنوم ، حيث توجد هذه العلامة: هذه هي سمة GsmPresencePrivate.status ، راجع مصادر جلسة gnome ، جلسة gnome-session / gsm -وجود.ج. نحصل على الوصول إلى هذه العلامة من خلال واجهة 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 على مستوى ما.


ونتيجة لذلك ، اتضح أنه بالنسبة pactl / إلغاء كتم الصوت ، pactl أداة pactl واحدة pactl ، أو بالأحرى ثلاثة أوامر بناءً عليها:


  • "معلومات pactl": اكتشف default sink - أي إخراج صوتي ، إذا كان هناك العديد ، هو الصوت الافتراضي
  • "أحواض قائمة pactl": اكتشف حالة كتم الصوت / إلغاء كتم الصوت للجهاز المقابل
  • "pactl set-sink-mute٪ (defaultSink) s٪ (isMute) s": لكتم الصوت / إلغاء كتم الصوت نفسه

لذا ، فإن مهمتنا هي تشغيل الأوامر / العمليات ، وقراءة نتائج الإخراج والبحث عن القيم المطلوبة على أساس منتظم. باختصار ، إنها مهمة قياسية.


في جنوم ، المكتبة الأساسية هي المسؤولة عن إنشاء العمليات ، وهناك وثائق ممتازة عليها. وبالطبع هي في C. ولدينا شبيبة. من المعروف أن حزمة Gjs صنعت طبقة ذكية "بديهية" بين C-API و Javascript. لكنك ما زلت تفهم أنك تحتاج إلى أمثلة ولا يمكنك الاستغناء عن googling.


ونتيجة لذلك ، وبفضل الجوهر الممتاز ، نحصل على كود العمل:


 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 } 

حفظ الإعدادات في التسجيل


لا ، بالطبع ، لا يوجد تسجيل في لينكس. هنا أنت لست Windows. هناك خيار أفضل يسمى GSettings (هذه هي واجهة برمجة التطبيقات) ، وهناك العديد من خيارات التنفيذ مخفية خلفها ، بشكل افتراضي يستخدم جنوم Dconf. هذا ما يبدو عليه إطار عمل واجهة المستخدم الرسومية:


محرر dconf


- ما هو أفضل من تخزين الإعدادات في ملفات نص عادي؟ - اسأل مستخدمي لينكس القدامى والملتحين. الميزة الرئيسية لـ 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 ووضعه في وظيفة buildPrefsWidget () لملف prefs.js. في هذه الحالة ، مقابل الامتداد الخاص بنا في قائمة "الإضافات المثبتة" هنا ، سنرى زرًا إضافيًا "تكوين هذه الإضافة" ، عن طريق النقر على أي شكل سيظهر جمالنا.


لنقم بإنشاء علامة تبويب حول منفصلة ، لأنه من المعروف أنه بدون Ebout ، عذرًا ، البرنامج لم يكتمل.


بشكل عام ، لبناء واجهة رسومية كلاسيكية ، تحتوي 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; } 

لقطة الشاشة النهائية:



اختياري


1. أوضاع التشغيل: الدعم والتشغيل

يتضمن عمل المبرمج وضعين في حالتي:
1) في وضع الدعم ، عندما تحتاج إلى الاستجابة بسرعة للأحداث - البريد و Slack و Skype والمزيد
2) في وضع التشغيل ، عندما يكون من الضروري قطع الإخطارات لمدة 20 دقيقة على الأقل ، وإلا يتم فقد التركيز وتكون إنتاجية العمالة النهائية ضئيلة. لهذا ، يعد وضع عدم الإزعاج مفيدًا.


2. كيفية إيقاف الصوت

قد يبدو أن كتم الصوت الكامل ، والبكم ، أكثر من اللازم. في الواقع ، في الواقع ، من الناحية المثالية ، ترغب في سماع سلاك / سكايب في وضع عدم الإزعاج ، لكن بقية الأصوات (الإشعارات الحقيقية) ليست كذلك. ولكن لهذا يجب تمييزهم بطريقة أو بأخرى. يمكنك ، بالطبع ، إنشاء واجهة برمجة تطبيقات صوتية خاصة بالإشعارات (وهذا موجود بالفعل) ، فقط هناك دائمًا برنامج / مبرمج لا يستخدم هذه الوظائف. مثال على ذلك هو Mailspring mailer: فهو ببساطة يقوم بتشغيل الأصوات من خلال العلامة audio ، ولا يمكن تمييزها بأي شكل من الأشكال عن الكلام في مكالمة Slack.


3. PanelMenu.Button

PanelMenu.Button - هذا هو الزر الفعلي في قائمة اللوحة + المنبثقة ، ويمكنك معرفة ذلك بنفسك وإنشائه من الصفر ، وسوف يتم تقدير كل منهما من قبل الجرح ! كنت أسعى لتحقيق نتيجة سريعة ، وبالتالي قمت بنسخ الشفرة من وثائق غير رسمية.


4. SetStatusRemote ()

في الواقع بدء تغيير الوضع باستخدام SetStatusRemote ().

Source: https://habr.com/ru/post/ar428187/


All Articles