summaryrefslogtreecommitdiff
path: root/raveos-theme/gnome/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-theme/gnome/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js')
-rw-r--r--raveos-theme/gnome/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js280
1 files changed, 280 insertions, 0 deletions
diff --git a/raveos-theme/gnome/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js b/raveos-theme/gnome/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js
new file mode 100644
index 0000000..d992b1a
--- /dev/null
+++ b/raveos-theme/gnome/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js
@@ -0,0 +1,280 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+import {
+ Atk,
+ Clutter,
+ Gio,
+ GLib,
+ St,
+} from './dependencies/gi.js';
+
+import {PopupMenu} from './dependencies/shell/ui.js';
+
+import {Utils} from './imports.js';
+
+// Dbusmenu features not (yet) supported:
+//
+// * The CHILD_DISPLAY property
+//
+// This seems to have only one possible value in the Dbusmenu API, so
+// there's little point in depending on it--the code in libdbusmenu sets it
+// if and only if an item has children, so for our purposes it's simpler
+// and more intuitive to just check children.length. (This does ignore the
+// possibility of a program not using libdbusmenu and setting CHILD_DISPLAY
+// independently, perhaps to indicate that an childless menu item should
+// nevertheless be displayed like a submenu.)
+//
+// * Children more than two levels deep
+//
+// PopupMenu doesn't seem to support submenus in submenus.
+//
+// * Shortcut keys
+//
+// If these keys are supposed to be installed as global shortcuts, we'd
+// have to query these aggressively and not wait for the DBus menu to be
+// mapped to a popup menu. A shortcut key that only works once the popup
+// menu is open and has key focus is possibly of marginal value.
+
+/**
+ *
+ */
+export async function haveDBusMenu() {
+ try {
+ const {default: DBusMenu} = await import('gi://Dbusmenu');
+ return DBusMenu;
+ } catch (e) {
+ log(`Failed to import DBusMenu, quicklists are not available: ${e}`);
+ return null;
+ }
+}
+
+const DBusMenu = await haveDBusMenu();
+
+/**
+ * @param dbusmenuItem
+ * @param deep
+ */
+export function makePopupMenuItem(dbusmenuItem, deep) {
+ // These are the only properties guaranteed to be available when the root
+ // item is first announced. Other properties might be loaded already, but
+ // be sure to connect to Dbusmenu.MENUITEM_SIGNAL_PROPERTY_CHANGED to get
+ // the most up-to-date values in case they aren't.
+ const itemType = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_TYPE);
+ const label = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_LABEL);
+ const visible = dbusmenuItem.property_get_bool(DBusMenu.MENUITEM_PROP_VISIBLE);
+ const enabled = dbusmenuItem.property_get_bool(DBusMenu.MENUITEM_PROP_ENABLED);
+ const accessibleDesc = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_ACCESSIBLE_DESC);
+ // const childDisplay = dbusmenuItem.property_get(Dbusmenu.MENUITEM_PROP_CHILD_DISPLAY);
+
+ let item;
+ const signalsHandler = new Utils.GlobalSignalsHandler();
+ const wantIcon = itemType === DBusMenu.CLIENT_TYPES_IMAGE;
+
+ // If the basic type of the menu item needs to change, call this.
+ const recreateItem = () => {
+ const newItem = makePopupMenuItem(dbusmenuItem, deep);
+ const parentMenu = item._parent;
+ parentMenu.addMenuItem(newItem);
+ // Reminder: Clutter thinks of later entries in the child list as
+ // "above" earlier ones, so "above" here means "below" in terms of the
+ // menu's vertical order.
+ parentMenu.actor.set_child_above_sibling(newItem.actor, item.actor);
+ if (newItem.menu)
+ parentMenu.actor.set_child_above_sibling(newItem.menu.actor, newItem.actor);
+
+ parentMenu.actor.remove_child(item.actor);
+ item.destroy();
+ item = null;
+ };
+
+ const updateDisposition = () => {
+ const disposition = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_DISPOSITION);
+ let iconName = null;
+ switch (disposition) {
+ case DBusMenu.MENUITEM_DISPOSITION_ALERT:
+ case DBusMenu.MENUITEM_DISPOSITION_WARNING:
+ iconName = 'dialog-warning-symbolic';
+ break;
+ case DBusMenu.MENUITEM_DISPOSITION_INFORMATIVE:
+ iconName = 'dialog-information-symbolic';
+ break;
+ }
+ if (iconName) {
+ item._dispositionIcon = new St.Icon({
+ icon_name: iconName,
+ style_class: 'popup-menu-icon',
+ y_align: Clutter.ActorAlign.CENTER,
+ y_expand: true,
+ });
+ let expander;
+ for (let child = item.label.get_next_sibling(); ; child = child.get_next_sibling()) {
+ if (!child) {
+ expander = new St.Bin({
+ style_class: 'popup-menu-item-expander',
+ x_expand: true,
+ });
+ item.actor.add_child(expander);
+ break;
+ } else if (child instanceof St.Widget &&
+ child.has_style_class_name('popup-menu-item-expander')) {
+ expander = child;
+ break;
+ }
+ }
+ item.actor.insert_child_above(item._dispositionIcon, expander);
+ } else if (item._dispositionIcon) {
+ item.actor.remove_child(item._dispositionIcon);
+ item._dispositionIcon = null;
+ }
+ };
+
+ const updateIcon = () => {
+ if (!wantIcon)
+ return;
+
+ const iconData = dbusmenuItem.property_get_byte_array(DBusMenu.MENUITEM_PROP_ICON_DATA);
+ const iconName = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_ICON_NAME);
+ if (iconName)
+ item.icon.icon_name = iconName;
+ else if (iconData.length)
+ item.icon.gicon = Gio.BytesIcon.new(iconData);
+ };
+
+ const updateOrnament = () => {
+ const toggleType = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_TOGGLE_TYPE);
+ switch (toggleType) {
+ case DBusMenu.MENUITEM_TOGGLE_CHECK:
+ item.actor.accessible_role = Atk.Role.CHECK_MENU_ITEM;
+ break;
+ case DBusMenu.MENUITEM_TOGGLE_RADIO:
+ item.actor.accessible_role = Atk.Role.RADIO_MENU_ITEM;
+ break;
+ default:
+ item.actor.accessible_role = Atk.Role.MENU_ITEM;
+ }
+ let ornament = PopupMenu.Ornament.NONE;
+ const state = dbusmenuItem.property_get_int(DBusMenu.MENUITEM_PROP_TOGGLE_STATE);
+ if (state === DBusMenu.MENUITEM_TOGGLE_STATE_UNKNOWN) {
+ // PopupMenu doesn't natively support an "unknown" ornament, but we
+ // can hack one in:
+ item.setOrnament(ornament);
+ item.actor.add_accessible_state(Atk.StateType.INDETERMINATE);
+ item._ornamentLabel.text = '\u2501';
+ item.actor.remove_style_pseudo_class('checked');
+ } else {
+ item.actor.remove_accessible_state(Atk.StateType.INDETERMINATE);
+ if (state === DBusMenu.MENUITEM_TOGGLE_STATE_CHECKED) {
+ if (toggleType === DBusMenu.MENUITEM_TOGGLE_CHECK)
+ ornament = PopupMenu.Ornament.CHECK;
+ else if (toggleType === DBusMenu.MENUITEM_TOGGLE_RADIO)
+ ornament = PopupMenu.Ornament.DOT;
+
+ item.actor.add_style_pseudo_class('checked');
+ } else {
+ item.actor.remove_style_pseudo_class('checked');
+ }
+ item.setOrnament(ornament);
+ }
+ };
+
+ const onPropertyChanged = (_, name, value) => {
+ // `value` is null when a property is cleared, so handle those cases
+ // with sensible defaults.
+ switch (name) {
+ case DBusMenu.MENUITEM_PROP_TYPE:
+ recreateItem();
+ break;
+ case DBusMenu.MENUITEM_PROP_ENABLED:
+ item.setSensitive(value ? value.unpack() : false);
+ break;
+ case DBusMenu.MENUITEM_PROP_LABEL:
+ item.label.text = value ? value.unpack() : '';
+ break;
+ case DBusMenu.MENUITEM_PROP_VISIBLE:
+ item.actor.visible = value ? value.unpack() : false;
+ break;
+ case DBusMenu.MENUITEM_PROP_DISPOSITION:
+ updateDisposition();
+ break;
+ case DBusMenu.MENUITEM_PROP_ACCESSIBLE_DESC:
+ item.actor.get_accessible().accessible_description = value && value.unpack() || '';
+ break;
+ case DBusMenu.MENUITEM_PROP_ICON_DATA:
+ case DBusMenu.MENUITEM_PROP_ICON_NAME:
+ updateIcon();
+ break;
+ case DBusMenu.MENUITEM_PROP_TOGGLE_TYPE:
+ case DBusMenu.MENUITEM_PROP_TOGGLE_STATE:
+ updateOrnament();
+ break;
+ }
+ };
+
+
+ // Start actually building the menu item.
+ const children = dbusmenuItem.get_children();
+ if (children.length && !deep) {
+ // Make a submenu.
+ item = new PopupMenu.PopupSubMenuMenuItem(label, wantIcon);
+ const updateChildren = () => {
+ const itemChildren = dbusmenuItem.get_children();
+ if (!itemChildren.length) {
+ recreateItem();
+ return;
+ }
+
+ item.menu.removeAll();
+ itemChildren.forEach(remoteChild =>
+ item.menu.addMenuItem(makePopupMenuItem(remoteChild, true)));
+ };
+ updateChildren();
+ signalsHandler.add(
+ [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_ADDED, updateChildren],
+ [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_MOVED, updateChildren],
+ [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_REMOVED, updateChildren]);
+ } else {
+ // Don't make a submenu.
+ if (!deep) {
+ // We only have the potential to get a submenu if we aren't deep.
+ signalsHandler.add(
+ [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_ADDED, recreateItem],
+ [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_MOVED, recreateItem],
+ [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_REMOVED, recreateItem]);
+ }
+
+ if (itemType === DBusMenu.CLIENT_TYPES_SEPARATOR) {
+ item = new PopupMenu.PopupSeparatorMenuItem();
+ } else if (wantIcon) {
+ item = new PopupMenu.PopupImageMenuItem(label, null);
+ item.icon = item._icon;
+ } else {
+ item = new PopupMenu.PopupMenuItem(label);
+ }
+ }
+
+ // Set common initial properties.
+ item.actor.visible = visible;
+ item.setSensitive(enabled);
+ if (accessibleDesc)
+ item.actor.get_accessible().accessible_description = accessibleDesc;
+
+ updateDisposition();
+ updateIcon();
+ updateOrnament();
+
+ // Prevent an initial resize flicker.
+ if (wantIcon)
+ item.icon.icon_size = 16;
+
+
+ signalsHandler.add(dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_PROPERTY_CHANGED, onPropertyChanged);
+
+ // Connections on item will be lost when item is disposed; there's no need
+ // to add them to signalsHandler.
+ item.connect('activate', () =>
+ dbusmenuItem.handle_event(DBusMenu.MENUITEM_EVENT_ACTIVATED,
+ new GLib.Variant('i', 0), Math.floor(Date.now() / 1000)));
+ item.connect('destroy', () => signalsHandler.destroy());
+
+ return item;
+}