Как да направим аплет за Cinnamon (Урок за Force Quit аплет)


Автор: Clement Lefebvre (aka „Clem“)
Източник: Цък

Да се напишат аплети за Cinnamon е лесно и в повечето случаи те са далеч по-полезени от разширенията.

В този урок ние ще направим „Force Quit“ Cinnamon аплет и ще обясним защо е по-добър от „Force Quit“ разширението.

За тези, които не са запознати с „Force Quit“: Когато един прозорец спре да отговаря и не иска да се затвори, най-ефикасния начин да го принудим да спре е да убием неговия процес. Можете да използвате командата „PS“ (например ps -A, за всички процеси, б. пр.), за да му намерим идентификатора (ID) и да го убием с „Kill“ командата. Или може да стартираме командата „xkill“, и просто да кликнем върху прозореца, който искаме да убием. Точно това прави нашия „Force Quit“ аплет…, след като цъкнете върху него, курсора на мишката ще се промени на мерник, който да насочите към прозореца, от който искате да се отървете 🙂

Нашият аплет ще изглежда така:

1. Създаване на основната структура на аплета.

В този урок ние ще създадем аплет със следното UUID (уникален идентификатор): „force-quit@cinnamon.org“. Трябва да даваме на нашите аплети собствени UUID, използвайте или вашето име или името на домейна след знака @.

Всеки аплет е директория, чието име е UUID-то на аплета, съдържаща два файла:

metadata.json, който съдържа информацията за аплета, като неговото име, описание и др.
applet.js, който съдържа кода на аплета.

Аплетите се намират в директорията ~/.local/share/cinnamon/applets (или в /usr/share/cinnamon/applets, ако искате да са достъпни за всички потребители). Така че нека да създадем необходимите файлове и папки.

mkdir -p .local/share/cinnamon/applets/force-quit@cinnamon.org
cd .local/share/cinnamon/applets/force-quit@cinnamon.org
touch metadata.json
touch applet.js

2. Определяне на метаданните на аплета

Нека на отворим metadata.json и да опишем нашия аплет. Този файл дефинира UUID, името, описанието и иконката на нашия аплет и се използва от Cinnamon, за да го идентифицира и покаже на потребителя в настройките на Cinnamon.

{
"uuid": "force-quit@cinnamon.org",
"name": "Force Quit",
"description": "Click on the applet to launch xkill and force any window to quit immediately",
"icon": "force-exit"
}

Така че, в този пример, UUID на нашия аплет е „force-quit@cinnamon.org“, името му е „Force Quit“ и неговата иконка е „force-exit“ (иконката, както и името ѝ ще намерите във Вашата тема с иконки).

3. Избор на вид аплет

Cinnamon предлага различни видове на аплети:

  • TextApplet (които показват текст в панела)
  • IconApplet (които показват иконка в панела)
  • TextIconApplet (които показват както иконка, така и текст в панела)
  • Applet (за хардкор разработчиците, показва се една празна кутия, която можете да попълните самостоятелно)

В този урок, ние просто искаме да покажем една иконка в панела, така че ние ще използваме „IconApplet“.

4. Писане на нашия аплет

Това е кодът за нашия аплет:

const Lang = imports.lang;
const Applet = imports.ui.applet;
const GLib = imports.gi.GLib;
const Gettext = imports.gettext.domain('cinnamon-applets');
const _ = Gettext.gettext;
function MyApplet(orientation) {
this._init(orientation);
}
MyApplet.prototype = {
__proto__: Applet.IconApplet.prototype,
_init: function(orientation) {
Applet.IconApplet.prototype._init.call(this, orientation);
try {
this.set_applet_icon_name("force-exit");
this.set_applet_tooltip(_("Click here to kill a window"));
}
catch (e) {
global.logError(e);
}
},
on_applet_clicked: function(event) {
GLib.spawn_command_line_async('xkill');
}
};
function main(metadata, orientation) {
let myApplet = new MyApplet(orientation);
return myApplet;
}

Сега, да се разходим ред по ред.

Най-напред импортираме това, от което се нуждаем (включително самото API на аплета: imports.ui.applet):

const Lang = imports.lang;
const Applet = imports.ui.applet;
const GLib = imports.gi.GLib;
const Gettext = imports.gettext.domain('cinnamon-applets');
const _ = Gettext.gettext;

Следва да определим конструктора:

function MyApplet(orientation) {
this._init(orientation);

}

Имайте предвид, че всички аплети могат да се наричат MyApplet. Не е нужно да давате различно име на класа и ви препоръчвам да не го правите, нека държим нещата прости. „orientation“ се определя от Cinnamon. Тя определя къде в панела се намира аплета, дали е в началото или на края (това влияние върху ориентацията, може и да Ви се наложи някога).

След това следва да определим тялото на нашия клас:

MyApplet.prototype = {
__proto__: Applet.IconApplet.prototype,
_init: function(orientation) {
Applet.IconApplet.prototype._init.call(this, orientation);

Както можете да видите тук, ние добавяме класа IconApplet, който позволява на аплетите да показват иконка в панела.

Вътре в аплета, правим следното:

try {
this.set_applet_icon_name("force-exit");
this.set_applet_tooltip(_("Click here to kill a window"));
}
catch (e) {
global.logError(e);
}

Използваме try/catch за да проверим за грешка, която може да възникне в аплета и да сме сигурни че тя ще бъде записана от системата. По този начин, ако нещо се обърка, може да видите грешката като натиснете Alt+F2, напишете lg и кликнете върху раздел „Errors“.

В този конкретен аплет, ние правим само тривиални неща … тук ние казваме на Cinnamon да покаже в панела иконката „force-exit“, както и сложихме подсказката _(“Click here to kill a window”). Тук ще отбележа само Gettext… той прави така че подсказката да бъде преведена на различни езици (аз няма да се занимавам с Gettext В този урок, но ние го използваме тук по същия начин, по който хората го използват в други проекти и езици).

След това, ние казваме на Cinnamon да стартира xkill, когато потребителят кликне върху нашия аплет:

on_applet_clicked: function(event) {
GLib.spawn_command_line_async('xkill');
}

И право напред … Cinnamon търпеливо чака да се случи on_applet_clicked ().

И накрая, остана само да добавим main функцията, тя вероятно ще бъде еднаква за всички аплети… тя кара Cinnamon да стартира нашия аплет:

function main(metadata, orientation) {
let myApplet = new MyApplet(orientation);
return myApplet;
}

5. Публикуване на аплета

Когато приключите да пишете аплет и след като сте го тествали, архивирайте папката, направете снимка на аплеата в действие, и го публикувайте на адрес http://cinnamon-spices.linuxmint.com/applets

Force-Quit аплета срещу Force-Quit разшрението

Нека да разгледаме този пример… аплет, който прави същото нещо, но вместо да е създаден като аплет, е разработен като разширение.

Ето неговите метаданни:

{
"uuid": "force-quit@cinnamon.org",
"name": "Force Quit",
"description": "Force Quit not responding applications",
"cinnamon-version": [ "1.2.0" ]
}

Вече може би да забелязахте проблем тук … това разширение е съвместимо със Cinnamon 1.2.0. Това означава, че с всяка нова версия, това разширение ще трябва да бъде актуализирано. Всъщност, разширенията не използват само API-та, те се вмъкват директно в кода на Cinnamon и следователно са тясно свързани с нeя (Cinnamon означава канела, б. пр.) и с конкретната версия. Това, за такъв тривиален пример като force-quit е ненужно ограничаване.

Сега, да погледнем в кода:

const Lang = imports.lang;
const St = imports.gi.St;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const Util = imports.misc.util;
const GLib = imports.gi.GLib;
function ForceQuit() {
this._init();
}
ForceQuit.prototype = {
__proto__: PanelMenu.SystemStatusButton.prototype,
_init: function() {
PanelMenu.SystemStatusButton.prototype._init.call(this, 'start-here');
this._button = new St.Button();
this._button.set_tooltip_text('Click me and select a Window to kill!');
this._button.set_child(new St.Icon({
icon_name: 'window-close',
icon_size: 17
}));
this._button.connect('clicked', Lang.bind(this, function () {
Util.spawn(['xkill']);
}));
}
};
let forceQuit;
function enable() {
forceQuit = new ForceQuit();
let _children = Main.panel._leftBox.get_children();
Main.panel._leftBox.insert_actor(forceQuit._button, _children.length - 1);
}
function disable() {
Main.panel._leftBox.remove_actor(forceQuit._button);
forceQuit.destroy();
}
function init() {
// do nothing
}

Както може да видите, в импорт секцията, разширението не само добавя API-та, също така присвоява и части от Cinnamon:

const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const Util = imports.misc.util;

Внесеното в аплета const Applet = imports.ui.applet; е относително безопасно и е малко вероятно да се промени. Ако се промени в предстоящите версии на Cinnamon, промените ще бъдат документирани и дoколкото е възможно API-то ще бъде обратно съвместимо. Но това няма да е така за елементите на Cinnamon. Но основното в нашия файл е panelMenu, а то може да мине през драстични промени и е възможно едно разширение да изисква много поддръжка. GNOME Shell разработчиците видяха това да се случва, когато всичките им GNOME Shell разширения спряха да работят с излизането на Gnome Shell 3.2.

Редовете код по-долу илюстрират колко тясно свързано с вътрешния код на Cinnamon, е това разширение:

let _children = Main.panel._leftBox.get_children();
Main.panel._leftBox.insert_actor(forceQuit._button, _children.length - 1);

Това разширение изисква, частна променливата, в която Cinnamon съхранява елементите от лявата част на панела си, нарича се _leftBox… То също така изисква Cinnamon да има само един панел, тогава какво се случва, ако сте избрали „класическо оформление“? Средата ще го добави, но без да е дефинирано къде. Местоположението на всеки аплет в предстоящите версии ще може да се конфигурира и така Cinnamon ще „знае“ къде да постави всеки аплет, в кой панел, и в коя позиция… Разширенията сами по себе си няма да знаят къде да отидат. Те ще бъдат на произволно място, освен ако разработчиците не предоставят на потребителите инструмент за конфигуриране.

Както можете да видите, наистина липсва гъвкавост.

И накрая, друг проблем с разширенията:

this._button = new St.Button();
this._button.set_tooltip_text('Click me and select a Window to kill!');
this._button.set_child(new St.Icon({
icon_name: 'window-close',
icon_size: 17
}));

Нов St.Button? Иконка с размер 17? …

Наистина, разширения дефинират свои собствени бутони… със собствена позиция, собствен размер на иконката и собствен стил в рамките на самия панел. Творците на теми, разбира се, не могат да дефинират стилове за тези разширения, така че потребителят завършва с микс от елементи в неговия/нейния панел оформени по различни начини, с различни граници и размери 🙂

При аплетите, темите определят как да изглеждат и всички аплети се оформят в един стил.

Както можете да видите в този конкретен пример, абсолютно безмислено е force-quit да се кодира като разширение. Всъщност, всичко, което добавя нещо към панела не трябва да е Cinnamon extension а Cinnamon applet, за да се ползват следните предимства:

  • Интегриране с темата.
  • Местоположението и зареждането, се извършва от Cinnamon и скоро ще може да се конфигурира от потребителя.
  • Гъвкав дизайн (който не е тясно свързан с вътрешния код и най-вероятно няма да има нужда от поддръжка за в бъдеще).
  • Удобно за ползване API (лесно е да се създаде иконка, етикет, подсказки и др.).

Това не означава никога да се използват разширения. Ако трябва да се постигне нещо по-сложно, за което няма API, или ако API-то на аплетита не е достатъчно за Вас, разбира се можете да използвате разширения. Но ако искате просто да добавите нещо към панела, моля не пишете разширения…

Още няколко неща, които можете да постигнете с аплетите:

За TextApplet и TextIconApplet:

set_applet_label(text): Задава заглавието да се вижда в панела.

За IconApplets и TextIconApplet:

set_applet_icon_name (icon name): задава иконката да се вижда в панела, като се използва нейното име (иконката ще бъде от темата).
set_applet_icon_path (FILENAME): Дава възможност да бъде посочен пътя до иконката, която да се вижда в панела.

За всички аплети:

set_applet_tooltip (text): задава подсказка за аплета.

Функции:

on_applet_clicked ():Извиква Cinnamon, когато потребителят кликне върху вашия аплет.

Променливи:

this._applet_context_menu: десен клик върху аплета извиква контекстно меню, всички аплети имат десен клик, той се генерира автоматично.

Примери:

Не се колебайте да разгледате кода на другите аплети в http://cinnamon-spices.linuxmint.com/applets

За повечето аплети е опредлен левия бутон на мишката за меню, за създаване на MyMenu класа. Не се колебайте да погледнете кода на аплетите в /usr/share/cinnamon/applets, за да видите как е постигнато това, или онлайн на https://github.com/linuxmint/Cinnamon/tree/master/files/usr/share/cinnamon/applets

Ако имате нужда от повече неща в API-то на аплетите (например, ако имате нужда Cinnamon да извършва действие, когато потребителят мине над аплета), само ни кажате и ние ще се разширим API-то.

Забавлявайте се 🙂

Advertisements

Вашият коментар

Попълнете полетата по-долу или кликнете върху икона, за да влезете:

WordPress.com лого

You are commenting using your WordPress.com account. Log Out / Промяна )

Twitter picture

You are commenting using your Twitter account. Log Out / Промяна )

Facebook photo

You are commenting using your Facebook account. Log Out / Промяна )

Google+ photo

You are commenting using your Google+ account. Log Out / Промяна )

Connecting to %s