Pull to refresh

Comments 5

UFO landed and left these words here
Нет-нет, не меняйте — тут всё правильно написано :)
Что-то я не понял… Одно криво написанное расширение запросто ломает всё? И это типа развитие?
Нормально же было раньше, когда всякие аплеты были отдельными процессами, встраивались через XEmbed (кажется так называется расширение X11 для запихивания одного окна в другое) в панель и жили там своей жизнью, не мешая никому, глючить могли сколько угодно.
Конечно, при загрузке расширения от совсем уж косяков есть защита try-catch, пример кода из GH:
    try {
        extension.stateObj.enable(); // <=== запуск расширения
        extension.state = ExtensionState.ENABLED;
        _signals.emit('extension-state-changed', extension);
        return;
    } catch(e) {
        logExtensionError(uuid, e);
        return;
    }

. На этом все.

С другой стороны, всякие песочницы усложняют код или вообще делают некоторые вещи невозможными. Вопрос не в развитии, а в компромиссе между простотой и отказоустойчивостью.
Sign up to leave a comment.

Articles

\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EФайл \u003Ccode\u003Eextension.js\u003C\u002Fcode\u003E в соответствующей директории является входной точкой в нашем приложении, в минимальном исполнении он выглядит так:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Efunction enable() {} \u002F\u002F вызывается при включении; создаем все здесь\nfunction disable() {} \u002F\u002F --||-- выключении; удаляем все созданное в enable()\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Ch3 id=\"pervyy-kod\"\u003EПервый код\u003C\u002Fh3\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EДля начала мы хотим добавить кнопку в \u003Ccode\u003EStatus Menu\u003C\u002Fcode\u003E справа сверху, как на скриншоте выше.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EИтак, с чего бы начать? О, начнем с документации. У нас же есть официальная документация, все дела. А вот нет, официальная документация очень невелика и разрозненна, однако благодаря \u003Ccode\u003Ejulio641742\u003C\u002Fcode\u003E и его неофициальной документации мы получаем то, что нужно:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003E \u002F\u002F 1 - выравнивание меню относительно кнопки(1 - слева, 0 - справа, 0.5 - по центру)\n \u002F\u002F true, если автоматически создавать меню\n let dndButton = new PanelMenu.Button(1, \"DoNotDisturb\", false);\n \u002F\u002F `right` - где мы хотим увидеть кнопку (left\u002Fcenter\u002Fright)\n Main.panel.addToStatusArea(\"DoNotDisturbRole\", dndButton, 0, \"right\");\n\n let box = new St.BoxLayout();\n dndButton.actor.add_child(box);\n\n let icon = new St.Icon({ style_class: \"system-status-icon\" });\n icon.set_gicon(Gio.icon_new_for_string(\"\u002Ftmp\u002Fbell_normal.svg\"));\n box.add(icon);\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EДанный код создает ключевой объект \u003Ccode\u003EdndButton\u003C\u002Fcode\u003E класса \u003Ccode\u003EPanelMenu.Button\u003C\u002Fcode\u003E — это кнопка, специально предназначенная для панели Status Menu. И мы ее вставляем в эту панель с помощью функции Main.panel.addToStatusArea().\u003Csup\u003E\u003Ca href=\"#dopolnitelno\"\u003E3\u003C\u002Fa\u003E\u003C\u002Fsup\u003E\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВставляем пункты меню с прикрученными к ним обработчиками, пример:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003E let menuItem = new PopupMenu.PopupMenuItem(\"hello, world!\");\n menuItem.connect(\"activate\", (menuItem, event) => {\n log(\"hello, world!\");\n });\n\n dndButton.menu.addMenuItem(menuItem);\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EСпасибо тебе, julio641742, за документацию! Ссылка:\u003Cbr\u002F\u003E\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fjulio641742\u002Fgnome-shell-extension-reference\"\u003Ehttps:\u002F\u002Fgithub.com\u002Fjulio641742\u002Fgnome-shell-extension-reference\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EИтоговый работающий код — по \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fmuravjov\u002Fdo-not-disturb\u002Fblob\u002F25f763d241556c7165d158e82a8a5db71418d1e2\u002Fextension.js\"\u003Eссылке\u003C\u002Fa\u003E.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Ch2 id=\"osobennosti-raboty-gnome-shell-i-javascript\"\u003EОсобенности работы GNOME Shell и Javascript\u003C\u002Fh2\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EНа дворе конец 2018-го, и Node.js\u002FV8 — основной инструмент для запуска Javascript-кода. Вся современная web-разработка держится на \"ноде\".\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EНо GNOME Shell и вся инфраструктура вокруг него использует другой Javascript-движок, SpiderMonkey от Mozilla, и отсюда следует много важных различий в работе.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Ch3 id=\"import-moduley\"\u003EИмпорт модулей\u003C\u002Fh3\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ отличие от Node.js, здесь нет require(), и модного ES6-import-а — тоже. Вместо этого есть специальный объект imports, обращение к атрибутам которого приводит к загрузке модуля:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode\u003E \u002F\u002Fconst PanelMenu = require(\"ui\u002FpanelMenu\");\n const PanelMenu = imports.ui.panelMenu;\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ данном случае мы загрузили модуль js\u002Fui\u002FpanelMenu.js из библиотеки пакета GNOME Shell, в котором реализован функционал кнопки с всплывающим меню. \u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EДа-да, все кнопки в панели современного десктопа Linux, использующего GNOME, написаны на базе panelMenu.js. В том числе: та самая правая кнопка с индикаторами батареи, Wi-fi, громкостью звука; переключалка языка ввода en-ru.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EДалее, есть особый атрибут \u003Ccode\u003Eimports.searchPath\u003C\u002Fcode\u003E — это список путей (строк), где будут искаться наши JS-модули. К примеру, мы выделили в отдельный модуль timeUtils.js функционал работы с таймером и положили его рядом с входной точкой нашего расширения, extension.js. Импортим timeUtils.js следующим образом:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode\u003E\u002F\u002F получаем путь до нашего расширения, где-то в ~\u002F.local\u002Fshare\u002Fgnome-shell\u002Fextensions\u002F<your-extension>\u002F\nconst Me = imports.misc.extensionUtils.getCurrentExtension();\n\u002F\u002F вставляем новый путь в начало списка\nimports.searchPath.unshift(Me.path);\n\u002F\u002F собственно импорт\nconst timeUtils = imports.timeUtils;\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Ch3 id=\"logirovanie-otladka-javascript\"\u003EЛогирование, отладка Javascript\u003C\u002Fh3\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EНу раз у нас не Node.js, то и логирование у нас свое. Вместо console.log() в коде доступны несколько функций логирования, см. gjs\u002F..\u002Fglobal.cpp, static_funcs:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cul\u003E\n\u003Cli\u003E\"log\" = g_message(\"JS LOG: \" + message) — логирование в stderr, пример:\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode\u003E$ cat helloWorld.js \nlog(\"hello, world\");\n\n$ gjs helloWorld.js \nGjs-Message: 17:20:21.048: JS LOG: hello, world\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cul\u003E\n\u003Cli\u003E\"logError\" — логирует стек исключения:\u003Cbr\u002F\u003E\n\u003Cul\u003E\n\u003Cli\u003Eпервый обязательный аргумент — исключение, затем через запятую — что хочешь\u003C\u002Fli\u003E\n\u003Cli\u003Eпример, если нужно распечатать стек в нужном месте:\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Etry {\n throw new Error('bum!');\n} catch(e) {\n logError(e, \"what a fuck\");\n}\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003Eи это нарисует в stderr в стиле:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode\u003E(gjs:28674): Gjs-WARNING **: 13:39:46.951: JS ERROR: what a fuck: Error: bum!\nggg@.\u002Fgtk.js:5:15\nddd@.\u002Fgtk.js:12:5\n@.\u002Fgtk.js:15:1\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cul\u003E\n\u003Cli\u003E\"print\" = g_print(\"%s\\n\", txt); — только текст + \"\\n\" в stdout, без префиксов и окраски, в отличие от log()\u003C\u002Fli\u003E\n\u003Cli\u003E\"printerr\" = g_printerr(\"%s\\n\", txt) — отличие от print в том, что в stderr\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EА вот отладчика для SpiderMonkey из коробки нет (не зря же я кропотливо выписал выше все доступные инструменты для логирования, пользуйтесь!). При желании можно попробовать JSRDbg: \u003Ca href=\"https:\u002F\u002Fwiki.gnome.org\u002FProjects\u002FGnomeShell\u002FDebuggingJavaScript\"\u003Eраз\u003C\u002Fa\u003E, \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fswojtasiak\u002Fjsrdbg\"\u003Eдва\u003C\u002Fa\u003E. \u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Ch2 id=\"a-est-li-zhizn-dlya-js-koda-vne-gnome-shell\"\u003EА есть ли жизнь для JS-кода вне GNOME Shell?\u003C\u002Fh2\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EЕсть. Полнофункциональные приложения, включая графический интерфейс (GUI), можно писать на Javascript! Запускать их нужно с помощью бинаря gjs, пускальщика JS-GTK-кода, пример создания GUI-окошка:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode\u003E$ which gjs\n\u002Fusr\u002Fbin\u002Fgjs\n$ dpkg --search \u002Fusr\u002Fbin\u002Fgjs\ngjs: \u002Fusr\u002Fbin\u002Fgjs\n\n$ cat gtk.js \nconst Gtk = imports.gi.Gtk;\nGtk.init(null);\n\nlet win = new Gtk.Window();\nwin.connect(\"delete-event\", () => {\n Gtk.main_quit();\n});\nwin.show_all();\nGtk.main();\n$ gjs gtk.js \u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВыше я упомянул про разбиение кода на модули и загрузку их из Javascript. Возникает вопрос, а как в самом модуле определить, запущен ли он как \"main\"-модуль, или загружен из другого модуля? \u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ Python-е есть аутентичная конструкция:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"python\"\u003Eif __name__ == \"__main__\":\n main()\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ Node.js — аналогично:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Eif (require.main === module) {\n main();\n}\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EОфициального ответа на этот вопрос для Gjs\u002FGH я не нашел, но придумал такой прием, которым спешу поделиться с читателем (а че, кто-то дочитал \"досюдова\"? респект!).\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EИтак, хитрый прием основан на анализе текущего стека вызовов, если он состоит из 2х и более строк — значит мы не в main()-модуле:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Eif (\n new Error().stack.split(\u002F\\r\\n|\\r|\\n\u002Fg).filter(line => line.length > 0)\n .length == 1\n) {\n main();\n}\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Ch2 id=\"uborka-za-soboy\"\u003EУборка за собой\u003C\u002Fh2\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EКаждое расширение GNOME Shell имеет доступ ко всем объектам всего GNOME Shell. К примеру, чтобы отобразить кол-во непрочитанных еще уведомлений, доберемся до контейнера с ними в \u003Ccode\u003ENotification Area\u003C\u002Fcode\u003E, расположенного по центру сверху, номер 4 на картинке (нажмите на надпись с текущим временем, она кликабельна в реале, не здесь):\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003E\u003Cimg src=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fpost_images\u002F91e\u002Feec\u002Facc\u002F91eeecaccdc67202335ce4d51d8d90d3.png\" alt=\"Do Not Disturb Time\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw780\u002Fgetpro\u002Fhabr\u002Fpost_images\u002F91e\u002Feec\u002Facc\u002F91eeecaccdc67202335ce4d51d8d90d3.png 780w, https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fpost_images\u002F91e\u002Feec\u002Facc\u002F91eeecaccdc67202335ce4d51d8d90d3.png 781w\" loading=\"lazy\" decode=\"async\"\u002F\u003E\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003E let unseenlist =\n Main.panel.statusArea.dateMenu._messageList._notificationSection._list;\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EМожно узнать, сколько их, непрочитанных уведомлений, подписаться на события добавления и удаления уведомлений:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Elet number = unseenlist.get_n_children();\nunseenlist.connect(\"actor-added\", () => {\n log(\"added!\");\n});\nunseenlist.connect(\"actor-removed\", () => {\n log(\"removed!\");\n});\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EЭто прекрасно, но пользователь, бывает, может решить, что расширение X ему больше не нужно, и нажмет кнопку отключить расширение. Для расширения это равносильно вызову функции disable(), и нужно предпринять все усилия, чтобы выключенное расширение не поломало работающий GH:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Efunction disable() {\n dndButton.destroy();\n}\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ данном случае, помимо того, что удаляем саму кнопку, нужно отписаться от событий \"actor-added\"\u002F\"actor-removed\", пример:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Evar signal = unseenlist.connect(\"actor-added\", () => {\n log(\"added!\");\n});\n\nfunction disable() {\n dndButton.destroy();\n unseenlist.disconnect(signal);\n}\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EЕсли этого не сделать, то код обработчиков будет продолжать вызываться на соответствующего события, пытаться обновлять состояние несуществующей уже кнопки с менюшкой и… GNOME Shell начнет глючить. Ну да, напакостим мы, ругаться будут пользователи, камни полетят в разрабов GNOME Shell и GNOME в целом. Реальная картина, че.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EИтак, GNOME Shell\u002FGjs представляет собой симбиоз двух систем, Glib\u002FGTK и Javascript, и у них разный подход к управлению ресурсами. Glib\u002FGTK требует явного освобождения своих ресурсов (кнопок, таймеров и прочего). Если же объект создан движком Javascript-а, то действуем как обычно (ничего не освобождаем).\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ итоге, как только наше расширение готово, и не \"течет\", можно смело публиковать его на \u003Ca href=\"https:\u002F\u002Fextensions.gnome.org\"\u003Ehttps:\u002F\u002Fextensions.gnome.org\u003C\u002Fa\u003E. \u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Ch2 id=\"rezhim-gnomesessionpresencestatusbusy-i-dbus\"\u003EРежим GnomeSession.PresenceStatus.BUSY и DBus.\u003C\u002Fh2\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EЕсли вы еще не забыли, мы делаем расширение \"Do Not Disturb\", которое выключает показ уведомлений пользователю. \u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ GNOME уже есть флаг, отвечающий за это состояние. После логина пользователя создается процесс gnome-session, в котором этот флаг и находится: это атрибут GsmPresencePrivate.status, см. исходники gnome-session, gnome-session\u002Fgsm-presence.c. Доступ к этому флагу получаем через DBus-интерфейс (такое межпроцессное взаимодействие). \u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EНе только нам, но и самому GH нужна информация об этом флаге, чтобы не показывать уведомления. Это достаточно легко ищется в исходниках GH:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Ethis._presence = new GnomeSession.Presence((proxy, error) => {\n this._onStatusChanged(proxy.status);\n});\n...\nthis._presence.connectSignal('StatusChanged', (proxy, senderName, [status]) => {\n this._onStatusChanged(status);\n});\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ данном случае метод _onStatusChanged есть обработчик, реагирующий на смену состояния. Копируем этот код к себе, адаптируем — с уведомлениями разобрались\u003Csup\u003E\u003Ca href=\"#dopolnitelno\"\u003E4\u003C\u002Fa\u003E\u003C\u002Fsup\u003E, остался звук.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Ch2 id=\"vyklyuchenievklyuchenie-zvuka\"\u003EВыключение\u002Fвключение звука\u003C\u002Fh2\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EБольшинство современных Linux-десктопов управляется PulseAudio, \u003Cdel\u003Eнебезызвестное поделие\u003C\u002Fdel\u003E программа за авторством небезызвестного Lennart Poettering. До сих пор у меня не доходили руки пошерстить код PulseAudio, и я был рад представившейся возможности разобраться в PulseAudio на некотором уровне.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ итоге оказалось, что для mute\u002Funmute достаточно одной утилиты \u003Ccode\u003Epactl\u003C\u002Fcode\u003E, а точнее трех команд на ее основе:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cul\u003E\n\u003Cli\u003E\"pactl info\": узнать \u003Ccode\u003Edefault sink\u003C\u002Fcode\u003E — на какой звуковой выход, если их несколько, подается звук по умолчанию\u003C\u002Fli\u003E\n\u003Cli\u003E\"pactl list sinks\": узнать состояние mute\u002Funmute соответствующего устройства\u003C\u002Fli\u003E\n\u003Cli\u003E\"pactl set-sink-mute %(defaultSink)s %(isMute)s\": для собственно mute\u002Funmute\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EИтак, наша задача состоит в запуске команд\u002Fпроцессов, чтении их вывода stdout и поиске нужных значений по регулярке. Короче, стандартная задача.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ GNOME за создание процессов отвечает базовая библиотека glib, и есть отличная \u003Ca href=\"https:\u002F\u002Fdeveloper.gnome.org\u002Fglib\u002Fstable\u002Fglib-Spawning-Processes.html\"\u003Eдокументация\u003C\u002Fa\u003E по ней. И конечно она на C. А у нас JS. Известно, что пакет Gjs сделал умную, \"интуитивно-понятную\" прослойку между С-API и Javascript. Но все равно понимаешь, что нужны примеры и без гугления не обойтись.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВ итоге, благодаря прекрасному \u003Ca href=\"https:\u002F\u002Fgist.github.com\u002Fbuzztaiki\u002F1487781\u002F74ea93d3a30f20c7f094327db9cb263a6286f6d6\"\u003Egist-у\u003C\u002Fa\u003E получаем работающий код:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Elet resList = GLib.spawn_command_line_sync(cmd);\n\u002F\u002F res = true\u002Ffalse, успех\u002Fпровал запуска процесса\n\u002F\u002F status = int, код выхода процесса\n\u002F\u002F out\u002Ferr = строки, содержащие stdout\u002Fstderr процесса\nlet [res, out, err, status] = resList;\nif (res != true || status != 0) {\n print(\"not ok!\");\n} else {\n \u002F\u002F do something useful\n}\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Ch2 id=\"sohranenie-nastroek-v-reestre\"\u003EСохранение настроек \u003Cdel\u003Eв реестре\u003C\u002Fdel\u003E\u003C\u002Fh2\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EНе, конечно реестра в Linux-е нет. Тут вам не Windows. Есть лучше, называется GSettings (это API), за ним скрывается несколько вариантов реализации, по умолчанию в GNOME используется Dconf. Вот так выглядит GUI-шка для него:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003E\u003Cimg src=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fpost_images\u002F0fe\u002F03c\u002Feca\u002F0fe03ceca6c7fc9338377fc5d0909024.png\" alt=\"dconf-editor\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw780\u002Fgetpro\u002Fhabr\u002Fpost_images\u002F0fe\u002F03c\u002Feca\u002F0fe03ceca6c7fc9338377fc5d0909024.png 780w, https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fpost_images\u002F0fe\u002F03c\u002Feca\u002F0fe03ceca6c7fc9338377fc5d0909024.png 781w\" loading=\"lazy\" decode=\"async\"\u002F\u003E\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003E — Чем это лучше хранения настроек в обычных текстовых файлах? — спросят олдовые и бородатые пользователи Linux-а. Основная фишка GSettings в том, что можно легко подписаться на изменения в настройке, пример:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Econst Gio = imports.gi.Gio;\nsettings = new Gio.Settings({ settings_schema: schemaObj });\nsettings.connect(\"changed::mute-audio\", function() {\n log(\"I see, you changed it!\");\n});\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EЕдинственная пока настройка в нашем \"Do Not Disturb\" — это опция \"mute-audio\", которая позволяет по желанию пользователя выключать или нет звук на время \"тихого часа\".\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Ch2 id=\"i-nemnogo-klassiki-gui-na-gtk\"\u003EИ немного классики, GUI на GTK\u003C\u002Fh2\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EЧтобы красиво показать пользователю настройки нашего расширения (а не лезть грязными лапами в реестр), GH предлагает нам написать GUI-код и положить его в функцию buildPrefsWidget() файла prefs.js. В этом случае напротив нашего расширения в списке \"Installed Extensions\" \u003Ca href=\"https:\u002F\u002Fextensions.gnome.org\u002Flocal\u002F\"\u003Eздесь\u003C\u002Fa\u003E мы увидим дополнительную кнопку \"Configure this extension\", по нажатию которой наша красота и появится.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EДавайте создадим отдельную вкладку About, ведь известно, что без \"Эбаута\", пардон, программа не является полноценной.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EВообще говоря, для построения классического графического интерфейса у GTK есть весь ассортимент строительных блоков, \u003Ccode\u003Eвиджетов\u003C\u002Fcode\u003E, заценить кол-во и качество которых можно \u003Ca href=\"https:\u002F\u002Fdeveloper.gnome.org\u002Fgtk3\u002Fstable\u002F\"\u003Eздесь\u003C\u002Fa\u003E.\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EМы же воспользуемся лишь несколькими из них:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cul\u003E\n\u003Cli\u003EGtk.Notebook — это вкладки, примерно как в браузере\u003C\u002Fli\u003E\n\u003Cli\u003EGtk.VBox — это контейнер для вертикального структурирования списка виджетов\u003C\u002Fli\u003E\n\u003Cli\u003EGtk.Label — это базовый элемент, надпись, с возможностью HTML-форматирования\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\u003Cbr\u002F\u003E\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003Efunction buildPrefsWidget() {\n \u002F\u002F собственно набор вкладок Gtk.Notebook составляет все наше GUI\n let notebook = new Gtk.Notebook();\n ...\n\n \u002F\u002F вкладка About, содержимым вкладки будет VBox c отступом в 10 пикселей,\n \u002F\u002F прям как margin\u002Fpadding на фронте\n let aboutBox = new Gtk.VBox({ border_width: 10 });\n\n \u002F\u002F добавляем вкладку с титулом About\n notebook.append_page(\n aboutBox,\n new Gtk.Label({label: \"About\"}),\n );\n\n \u002F\u002F первым делом вставляем название нашего расширения жирным шрифтом,\n \u002F\u002F и чтоб занял все свободное место, если таковое будет (expand)\n aboutBox.pack_start(\n new Gtk.Label({\n label: \"<b>Do Not Disturb Time<\u002Fb>\",\n use_markup: true,\n }),\n true, \u002F\u002F expand\n true, \u002F\u002F fill\n 0,\n );\n ...\n\n notebook.show_all();\n return notebook;\n}\u003C\u002Fcode\u003E\u003Cdiv class=\"code-explainer\"\u003E\u003Ca href=\"https:\u002F\u002Fsourcecraft.dev\u002F\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"\u003E\u003Cimg style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\u002F\u003E\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003C\u002Fpre\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003EИтоговый скриншот:\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Cp\u003E\u003Cimg src=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fpost_images\u002Ff87\u002F554\u002F787\u002Ff8755478763d159cbb9dfc1518881e60.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw780\u002Fgetpro\u002Fhabr\u002Fpost_images\u002Ff87\u002F554\u002F787\u002Ff8755478763d159cbb9dfc1518881e60.png 780w, https:\u002F\u002Fhabrastorage.org\u002Fr\u002Fw1560\u002Fgetpro\u002Fhabr\u002Fpost_images\u002Ff87\u002F554\u002F787\u002Ff8755478763d159cbb9dfc1518881e60.png 781w\" loading=\"lazy\" decode=\"async\"\u002F\u003E\u003C\u002Fp\u003E\u003Cbr\u002F\u003E\n\u003Ch2 id=\"dopolnitelno\"\u003EДополнительно\u003C\u002Fh2\u003E\u003Cbr\u002F\u003E\n\u003Cdiv class=\"spoiler\"\u003E\u003Cb class=\"spoiler_title\"\u003E1. Режимы работы: поддержка и работа\u003C\u002Fb\u003E\u003Cdiv class=\"spoiler_text\"\u003E\u003Cp\u003EРабота программиста предполагает 2 режима в моем случае:\u003Cbr\u002F\u003E\n1) в режиме поддержки, когда нужно быстро реагировать на события,- почта, Slack, Skype и прочее\u003Cbr\u002F\u003E\n2) в режиме работы, когда жизненно необходимо вырубить уведомления хотя бы на 20 минут, иначе фокус теряется и итоговая производительность труда — ничтожна. Именно для этого полезен режим \"Не беспокоить\".\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E\u003Cbr\u002F\u003E\n\u003Cdiv class=\"spoiler\"\u003E\u003Cb class=\"spoiler_title\"\u003E2. Как выключать звук\u003C\u002Fb\u003E\u003Cdiv class=\"spoiler_text\"\u003E\u003Cp\u003EМожет показаться, что полное выключение звука, mute, это слишком. Действительно, ведь в идеале хочется, чтобы в режиме \"Не беспокоить\" звонки Slack\u002FSkype были слышны, а вот остальные звуки (реальных уведомлений) — нет. Но для этого их нужно как-то различать. Можно, конечно, сделать звуковое API специально для уведомлений (и такое уже есть), только вот всегда найдется программа\u002Fпрограммист, который не задействует такой функционал. Пример — почтовик Mailspring: звуки он просто проигрывает через тег \u003Ccode\u003Eaudio\u003C\u002Fcode\u003E, и их никак не отличишь, допустим, от речи в звонке Slack-а.\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E\u003Cbr\u002F\u003E\n\u003Cdiv class=\"spoiler\"\u003E\u003Cb class=\"spoiler_title\"\u003E3. PanelMenu.Button\u003C\u002Fb\u003E\u003Cdiv class=\"spoiler_text\"\u003E\u003Cp\u003EPanelMenu.Button — это собственно кнопка в панели + всплывающее меню, и можно самому разобраться и создать с нуля, и то, и другое, \u003Cem\u003Eпацаны на раене\u003C\u002Fem\u003E оценят! У меня же была нацеленность на быстрый результат и поэтому я копипастнул код из неофициальной документации.\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E\u003Cbr\u002F\u003E\n\u003Cdiv class=\"spoiler\"\u003E\u003Cb class=\"spoiler_title\"\u003E4. SetStatusRemote()\u003C\u002Fb\u003E\u003Cdiv class=\"spoiler_text\"\u003E\u003Cp\u003EСобственно инициировать смену режима нужно с помощью SetStatusRemote().\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E","tags":[{"titleHtml":"javascript"},{"titleHtml":"GNOME"},{"titleHtml":"Linux"},{"titleHtml":"UI"},{"titleHtml":"производительность труда"}],"metadata":{"stylesUrls":[],"scriptUrls":[],"shareImageUrl":"https:\u002F\u002Fhabr.com\u002Fshare\u002Fpublication\u002F428187\u002F4cbdb571d332f0791f2593550a82eb18\u002F","shareImageWidth":1200,"shareImageHeight":630,"vkShareImageUrl":"https:\u002F\u002Fhabr.com\u002Fshare\u002Fpublication\u002F428187\u002F4cbdb571d332f0791f2593550a82eb18\u002F?format=vk","schemaJsonLd":"{\"@context\":\"http:\\\u002F\\\u002Fschema.org\",\"@type\":\"Article\",\"mainEntityOfPage\":{\"@type\":\"WebPage\",\"@id\":\"https:\\\u002F\\\u002Fhabr.com\\\u002Fru\\\u002Farticles\\\u002F428187\\\u002F\"},\"headline\":\"Как написать расширение для GNOME Shell: режим «Do Not Disturb»\",\"datePublished\":\"2018-11-06T09:14:37+03:00\",\"dateModified\":\"2018-11-06T11:21:00+03:00\",\"author\":{\"@type\":\"Person\",\"name\":\"Илья Муравьев\"},\"publisher\":{\"@type\":\"Organization\",\"name\":\"Habr\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fwebt\\\u002Fa_\\\u002Flk\\\u002F9m\\\u002Fa_lk9mjkccjox-zccjrpfolmkmq.png\"}},\"description\":\"Началось все с переезда на новую версию одного дистрибутива Linux, а там — скандально известный GNOME Shell (GH для краткости), на Javascript. Ну ок, на JS так н...\",\"url\":\"https:\\\u002F\\\u002Fhabr.com\\\u002Fru\\\u002Farticles\\\u002F428187\\\u002F#post-content-body\",\"about\":[\"h_open_source\",\"h_programming\",\"h_linux_dev\",\"f_develop\"],\"image\":[\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fpost_images\\\u002F737\\\u002F175\\\u002F2b5\\\u002F7371752b5fb33b8858bff75857fbfa83.jpg\",\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fpost_images\\\u002F104\\\u002F41d\\\u002F479\\\u002F10441d479062c7540a2743c6fb0b852d.png\",\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fpost_images\\\u002F91e\\\u002Feec\\\u002Facc\\\u002F91eeecaccdc67202335ce4d51d8d90d3.png\",\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fpost_images\\\u002F0fe\\\u002F03c\\\u002Feca\\\u002F0fe03ceca6c7fc9338377fc5d0909024.png\",\"https:\\\u002F\\\u002Fhabrastorage.org\\\u002Fgetpro\\\u002Fhabr\\\u002Fpost_images\\\u002Ff87\\\u002F554\\\u002F787\\\u002Ff8755478763d159cbb9dfc1518881e60.png\"]}","metaDescription":"Началось все с переезда на новую версию одного дистрибутива Linux, а там — скандально известный GNOME Shell (GH для краткости), на Javascript. Ну ок, на JS так на JS, работает — и ладно. Одновременно...","mainImageUrl":null,"amp":false,"customTrackerLinks":[]},"polls":[],"commentsEnabled":{"status":true,"reason":null},"rulesRemindEnabled":false,"votesEnabled":true,"status":"published","plannedPublishTime":null,"checked":null,"hasPinnedComments":false,"format":"tutorial","banner":null,"multiwidget":null,"multiwidgetUuid":null,"readingTime":10,"complexity":null,"isEditorial":false,"flowNew":null,"linkedPostTranslation":null,"hasRegionalRestrictions":false}},"articlesIds":{},"isLoading":false,"pagesCount":{},"route":{},"reasonsList":null,"postReasonsList":null,"view":"cards","oldEditorForm":null,"lastVisitedRoute":{},"ssrCommentsArticleIds":[],"viewedPosts":[],"myFeedFilter":{"complexity":"all","score":"all","types":["articles","posts","news"]},"myFeedIsApplyFilters":false,"myFeedIsForce":false},"me":{"user":null,"uuid":null,"ppgDemanded":false,"karmaResetInfo":{"canReincarnate":null,"wasReincarnated":null,"currentScore":null},"notes":null,"userUpdates":{"feeds":{"newCount":null},"conversationUnreadCount":0,"trackerUnreadCount":0},"features":null},"flowsMenu":{"flowsMenuData":{"whatsNew":{"newAuthors":{"order":7,"items":[{"alias":"Maden4ic","fullname":"Maden4ic","avatarUrl":null},{"alias":"slavacpp","fullname":"0xC0DE1E55","avatarUrl":null},{"alias":"Gim6626","fullname":"Дмитрий Винокуров","avatarUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Favatars\u002F27a\u002Fe3f\u002Fda7\u002F27ae3fda77c3828724b6c0568d17ee16.jpg"}]},"updates":{"order":8,"items":[{"title":"Changelog","url":"https:\u002F\u002Fhabr.com\u002Fen\u002Fdocs\u002Fchangelog\u002F2025\u002F","imageUrl":null},{"title":"Habr Code of Authors","url":"https:\u002F\u002Fhabr.com\u002Fen\u002Fdocs\u002Fauthors\u002Fcodex\u002F","imageUrl":null},{"title":"How it works","url":"https:\u002F\u002Fhabr.com\u002Fen\u002Fdocs\u002Fhelp\u002Frules\u002F","imageUrl":null},{"title":"Corporate blogs","url":"https:\u002F\u002Fhabr.com\u002Fen\u002Fdocs\u002Fcompanies\u002Fcorpblogs\u002F","imageUrl":null}]},"socialNetwork":{"order":1},"thematicChannels":{"order":3},"ourActivities":{"order":4},"partnersActivities":{"order":5},"banner":{"order":6},"ourBlogs":{"order":2}},"flows":{"backend":{"technologies":{"order":1,"items":[{"alias":"go","title":"Go","imageUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fhub\u002Fa10\u002Fc5c\u002F626\u002Fa10c5c62685d9a7d2964993daf6958c3.png"}]},"topics":{"order":2,"items":[]},"topPosts":{"order":3,"items":[],"period":"day"},"topAuthors":{"order":4,"items":[{"alias":"xeovo","fullname":"xeovo","avatarUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Favatars\u002Fe83\u002Fde8\u002Ff9e\u002Fe83de8f9ed182d9287ca9900d263c2f9.png"}]},"topCompanies":{"order":5,"items":[{"alias":"xeovo","title":"Xeovo VPN","imageUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fcompany\u002F864\u002F431\u002F196\u002F864431196b8a78de320f5707f6d8ff33.png"}]},"banner":{"order":6}},"frontend":{"technologies":{"order":1,"items":[{"alias":"javascript","title":"JavaScript","imageUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fhub\u002F2b3\u002F99b\u002F964\u002F2b399b964d456f3ad1bfddc0346b60d4.png"},{"alias":"reactjs","title":"ReactJS","imageUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fhub\u002Fe77\u002F27d\u002F25b\u002Fe7727d25b433a67f0e69acc74d2ba785.png"},{"alias":"nestjs","title":"NestJS","imageUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fhub\u002F820\u002Ff03\u002F364\u002F820f03364cccdb926fd93cfe2438f962.png"}]},"topics":{"order":2,"items":[]},"topPosts":{"order":3,"items":[],"period":"day"},"topAuthors":{"order":4,"items":[{"alias":"aymericzip","fullname":"Aymeric PINEAU","avatarUrl":null}]},"topCompanies":{"order":5,"items":[]},"banner":{"order":6}},"hardware_and_gadgets":{"technologies":{"order":1,"items":[]},"topics":{"order":2,"items":[]},"topPosts":{"order":3,"items":[{"id":"1033642","isCorporative":false,"lang":"en","titleHtml":"Appeal to keyboard makers: Please Stop Adding FN Buttons","postType":"article","hubs":[{"id":"21898","alias":"hardware","type":"collective","title":"Computer hardware","titleHtml":"Computer hardware","isProfiled":false},{"id":"17189","alias":"itstandarts","type":"collective","title":"IT Standards","titleHtml":"IT Standards","isProfiled":true},{"id":"21954","alias":"Peripheral","type":"collective","title":"Periphery","titleHtml":"Periphery","isProfiled":false}],"author":{"alias":"Murz","fullname":"Alexey Murz Korepov","avatarUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Favatars\u002F527\u002Fcd0\u002F606\u002F527cd060631ef496fb52ce16199efca0.jpg"}}],"period":"day"},"topAuthors":{"order":4,"items":[]},"topCompanies":{"order":5,"items":[]},"banner":{"order":6}},"mobile_development":{"technologies":{"order":1,"items":[{"alias":"dart","title":"Dart","imageUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fhub\u002F41e\u002F671\u002F2dd\u002F41e6712dd7d298077553efff8562bd73.png"},{"alias":"flutter","title":"Flutter","imageUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fhub\u002F63b\u002F728\u002Fb76\u002F63b728b76ec18862a5454a684509940b.png"},{"alias":"cpp","title":"C++","imageUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fhub\u002F89a\u002Fc44\u002F09e\u002F89ac4409ea406d835a82383fa53fcda7.png"},{"alias":"qt_software","title":"Qt","imageUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Fhub\u002Fa69\u002F436\u002F043\u002Fa69436043016a35349d05068d584b316.png"}]},"topics":{"order":2,"items":[]},"topPosts":{"order":3,"items":[],"period":"day"},"topAuthors":{"order":4,"items":[{"alias":"Paulik8","fullname":"Paulik8","avatarUrl":null},{"alias":"slavacpp","fullname":"0xC0DE1E55","avatarUrl":null}]},"topCompanies":{"order":5,"items":[]},"banner":{"order":6}},"industrial_engineering":{"technologies":{"order":1,"items":[]},"topics":{"order":2,"items":[]},"topPosts":{"order":3,"items":[],"period":"day"},"topAuthors":{"order":4,"items":[{"alias":"rsashka","fullname":"Александр Рябиков","avatarUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Favatars\u002Fad5\u002Fd5f\u002Feb2\u002Fad5d5feb2fbf547d4d74f65d041cb2c7.jpg"}]},"topCompanies":{"order":5,"items":[]},"banner":{"order":6}}},"events":{"items":[]}},"isMenuVisible":false},"banner":{"isArticleStickyPanelVisible":false,"isArticleStickyPanelAtTheBottom":false,"bannerContainer":null,"isHeaderBannerInView":true,"isSponsorBlockPresent":false,"isHeaderBannerVisible":true,"isPBrandingVisible":false},"refs":{"flowsRefs":[{"id":"2","title":"Бэкенд","alias":"backend"},{"id":"4","title":"Фронтенд","alias":"frontend"},{"id":"6","title":"Мобильная разработка","alias":"mobile_development"},{"id":"8","title":"Системное администрирование","alias":"admin"},{"id":"10","title":"Информационная безопасность","alias":"information_security"},{"id":"12","title":"AI и ML","alias":"ai_and_ml"},{"id":"14","title":"Промышленная инженерия","alias":"industrial_engineering"},{"id":"16","title":"Геймдев","alias":"gamedev"},{"id":"18","title":"Тестирование","alias":"quality_assurance"},{"id":"20","title":"Техническая поддержка","alias":"support"},{"id":"22","title":"Системный и бизнес-анализ","alias":"analytics"},{"id":"24","title":"Дизайн","alias":"design"},{"id":"26","title":"Менеджмент","alias":"management"},{"id":"28","title":"Топ-менеджмент","alias":"top_management"},{"id":"30","title":"Маркетинг и контент","alias":"marketing"},{"id":"34","title":"Продажи","alias":"sales"},{"id":"36","title":"HR","alias":"human_resources"},{"id":"38","title":"Бэк-офис","alias":"back_office"},{"id":"40","title":"Зерокодинг","alias":"zero-code_development"},{"id":"42","title":"Железо и гаджеты","alias":"hardware_and_gadgets"},{"id":"44","title":"DIY","alias":"diy"},{"id":"46","title":"Здоровье","alias":"healthcare"},{"id":"48","title":"Научпоп","alias":"popsci"},{"id":"50","title":"Другое","alias":"other"}]},"promoData":{"isLoading":false,"hasLoaded":false,"featurer":null,"megaposts":null,"promoLinks":null,"promoPosts":null,"sticker":null,"stories":null,"activities":[],"partnerActivities":[],"isPromoDataAvailable":{"featurer":true,"promoPosts":true,"promoLinks":false,"megaposts":false}},"fixedBanner":{"isLoading":false,"viewEventsSent":[],"imagesLoaded":{}},"companies":{"companyRefs":{"__ALIAS_STORE__":true},"companyIds":{},"companyTopIds":[],"companyRouteStatistics":{},"pagesCount":{},"companyProfiles":{"__ALIAS_STORE__":true},"companiesCategories":[],"companiesCategoriesTotalCount":0,"companiesWidgets":{"__ALIAS_STORE__":true},"companiesWorkers":{"__ALIAS_STORE__":true},"companiesFans":{"__ALIAS_STORE__":true},"multiwidgets":{"__ALIAS_STORE__":true},"route":{},"isLoading":false,"companyWorkersLoading":false,"companyFansLoading":false,"multiwidgetLoading":false,"vacancies":{},"companiesGalleries":{"__ALIAS_STORE__":true},"companiesBanners":{"__ALIAS_STORE__":true},"companiesLandingVacancies":{"__ALIAS_STORE__":true},"companiesTechnologies":{"__ALIAS_STORE__":true},"workplaceInfo":null},"ssr":{"error":null,"isDataLoaded":true,"isDataLoading":false},"hubs":{"hubRefs":{"__ALIAS_STORE__":true},"hubIds":{},"hubRouteStats":{},"pagesCount":{},"isLoading":false,"counters":{"__ALIAS_STORE__":true},"route":{"name":"","params":{},"query":{}}},"adblock":{"hasAcceptableAdsFilter":false,"hasAdblock":false},"pullRefresh":{"shouldRefresh":false},"viewport":{"prevScrollY":{},"scrollY":0,"width":0,"pageContentHeight":0},"feedStatistics":{"feedsStatistics":{}},"pageArticleComments":{"lastViewedComment":0,"postId":null,"lastCommentTimestamp":1541571500,"moderated":[],"moderatedIds":[],"commentRoute":"","idempotenceKey":""},"comments":{"articleComments":{"428187":{"refs":{"19327584":{"id":"19327584","parentId":null,"level":0,"timePublished":"2018-11-06T08:03:13+00:00","timeChanged":null,"isSuspended":false,"status":"author_deactivated","score":0,"votesCount":0,"message":"UFO just landed and posted this here","editorVersion":0,"author":null,"isAuthor":false,"isPostAuthor":false,"isNew":false,"isFavorite":false,"isCanEdit":false,"timeEditAllowedTill":null,"children":[],"vote":{"value":null,"isCanVote":false},"votePlus":{"canVote":false,"isChargeEnough":false,"isKarmaEnough":false,"isVotingOver":false,"isPublicationLimitEnough":false},"voteMinus":{"canVote":false,"isChargeEnough":false,"isKarmaEnough":false,"isVotingOver":false,"isPublicationLimitEnough":false},"isPinned":false},"19327766":{"id":"19327766","parentId":null,"level":0,"timePublished":"2018-11-06T08:33:36+00:00","timeChanged":"2018-11-06T08:33:48+00:00","isSuspended":false,"status":"published","score":0,"votesCount":0,"message":"> GNOME hell\u003Cbr\u003E\r\nЭто описка по Фрейду. :) Исправлю на GS","editorVersion":1,"author":{"id":"122452","alias":"ilil","fullname":"Илья Муравьев","avatarUrl":null,"speciality":null,"deleted":false},"isAuthor":false,"isPostAuthor":false,"isNew":false,"isFavorite":false,"isCanEdit":false,"timeEditAllowedTill":null,"children":["19331398"],"vote":null,"votePlus":null,"voteMinus":null,"isPinned":false},"19331398":{"id":"19331398","parentId":"19327766","level":1,"timePublished":"2018-11-06T19:12:14+00:00","timeChanged":null,"isSuspended":false,"status":"published","score":1,"votesCount":1,"message":"Нет-нет, не меняйте — тут всё правильно написано :)","editorVersion":1,"author":{"id":"153025","alias":"densss2","fullname":null,"avatarUrl":"\u002F\u002Fhabrastorage.org\u002Fgetpro\u002Fhabr\u002Favatars\u002Fd87\u002Fa4f\u002Fe9e\u002Fd87a4fe9e50cc1c79c4b011cc1643813.png","speciality":"Пользователь","deleted":false},"isAuthor":false,"isPostAuthor":false,"isNew":false,"isFavorite":false,"isCanEdit":false,"timeEditAllowedTill":null,"children":[],"vote":null,"votePlus":null,"voteMinus":null,"isPinned":false},"19331942":{"id":"19331942","parentId":null,"level":0,"timePublished":"2018-11-06T22:58:48+00:00","timeChanged":null,"isSuspended":false,"status":"published","score":0,"votesCount":0,"message":"Что-то я не понял… Одно криво написанное расширение запросто ломает всё? И это типа развитие?\u003Cbr\u003E\r\nНормально же было раньше, когда всякие аплеты были отдельными процессами, встраивались через XEmbed (кажется так называется расширение X11 для запихивания одного окна в другое) в панель и жили там своей жизнью, не мешая никому, глючить могли сколько угодно.","editorVersion":1,"author":{"id":"58132","alias":"Lsh","fullname":"Lsh","avatarUrl":null,"speciality":"Пользователь","deleted":false},"isAuthor":false,"isPostAuthor":false,"isNew":false,"isFavorite":false,"isCanEdit":false,"timeEditAllowedTill":null,"children":["19332440"],"vote":null,"votePlus":null,"voteMinus":null,"isPinned":false},"19332440":{"id":"19332440","parentId":"19331942","level":1,"timePublished":"2018-11-07T06:18:20+00:00","timeChanged":null,"isSuspended":false,"status":"published","score":0,"votesCount":0,"message":"Конечно, при загрузке расширения от совсем уж косяков есть защита try-catch, пример кода из GH:\u003Cbr\u003E\r\n\u003Cpre\u003E\u003Ccode class=\"javascript\"\u003E try {\n extension.stateObj.enable(); \u002F\u002F <=== запуск расширения\n extension.state = ExtensionState.ENABLED;\n _signals.emit('extension-state-changed', extension);\n return;\n } catch(e) {\n logExtensionError(uuid, e);\n return;\n }\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003Cbr\u003E\r\n. На этом все.\u003Cbr\u003E\r\n\u003Cbr\u003E\r\nС другой стороны, всякие \u003Ci\u003Eпесочницы\u003C\u002Fi\u003E усложняют код или вообще делают некоторые вещи невозможными. Вопрос не в развитии, а в компромиссе между простотой и отказоустойчивостью.","editorVersion":1,"author":{"id":"122452","alias":"ilil","fullname":"Илья Муравьев","avatarUrl":null,"speciality":null,"deleted":false},"isAuthor":false,"isPostAuthor":false,"isNew":false,"isFavorite":false,"isCanEdit":false,"timeEditAllowedTill":null,"children":[],"vote":null,"votePlus":null,"voteMinus":null,"isPinned":false}},"root":["19327584","19327766","19331942"],"cacheKey":"79f95f9e-3284-42c7-a478-77831fce275b"}},"articlePinnedComments":{"428187":{"refs":{},"commentIds":[]}},"searchCommentsResults":null,"pagesCount":null,"commentAccess":{},"scrollParents":{}},"publicationStats":{"statsInfo":{},"statsFunnels":{},"statsGraph":{},"defaultSuggest":{},"suggest":{},"timeTracker":{},"isUserActive":true,"otherPublicationStats":{}}};(function(){var s=document.currentScript||document.scripts[document.scripts.length-1];s.parentNode.removeChild(s);}());
Pull to refresh

Comments 5

UFO landed and left these words here
Нет-нет, не меняйте — тут всё правильно написано :)
Что-то я не понял… Одно криво написанное расширение запросто ломает всё? И это типа развитие?
Нормально же было раньше, когда всякие аплеты были отдельными процессами, встраивались через XEmbed (кажется так называется расширение X11 для запихивания одного окна в другое) в панель и жили там своей жизнью, не мешая никому, глючить могли сколько угодно.
Конечно, при загрузке расширения от совсем уж косяков есть защита try-catch, пример кода из GH:
    try {
        extension.stateObj.enable(); // <=== запуск расширения
        extension.state = ExtensionState.ENABLED;
        _signals.emit('extension-state-changed', extension);
        return;
    } catch(e) {
        logExtensionError(uuid, e);
        return;
    }

. На этом все.

С другой стороны, всякие песочницы усложняют код или вообще делают некоторые вещи невозможными. Вопрос не в развитии, а в компромиссе между простотой и отказоустойчивостью.
Sign up to leave a comment.

Articles