Changing the Activities Button in GNOME Shell 3.

2

Changing the Activities Button in GNOME Shell 3.2
Finnbarr P. Murphy
(fpm@fpmurphy.com) In the original version of the GNOME Shell (3.0) it was very easy to alter the text of the Activities button or add an icon by means of a simple GNOME Shell extension. For example, here is the code for a simple GNOME Shell extension that I published shortly after the release of GNOME 3.0 which enables a user to do just that.

Fo r

This GNOME Shell extension could be easily customized to display either an icon, a text string or both for the Activities button. All you had to do was comment or or uncomment the appropriate lines in the source code. With the release of GNOME 3.2, which included GNOME Shell 3.2, in September 2011, the above code no longer works. Life became much more complicated for GNOME Shell extension developers because of new requirements to support three functions: init, enable and disable when coding a new GNOME Shell extension or upgrading an existing extension to support GNOME Shell 3.2. See this post, which I wrote a couple of months ago, for a detailed description of the changes to the GNOME Shell extension infrastructure. In addition, the JavaScript code relating to the Activities button significantly changed between GNOME Shell 3.0 and 3.2. Here is the relevant code from /usr/share/gnome-shell/js/ui/panel.js with comments removed to reduce the number of lines.

function ActivitiesButton() { this._init.apply(this, arguments);

01-09-2012

pe rs o

const St = imports.gi.St; const Main = imports.ui.main; const Panel = imports.ui.panel; const Gettext = imports.gettext.domain('gnome-shell'); const _ = Gettext.gettext; function main() { let hotCornerButton = Main.panel.button; let box = new St.BoxLayout({ style_class: 'activities_box'}); // change the text string if you want to display different text // for the activities button let label = new St.Label({ text: _("Activities"), style_class: 'activities_text' }); // change the icon_name if you want to display a different icon // the icon must exist in the appropriate directory let logo = new St.Icon({ icon_type: St.IconType.FULLCOLOR, icon_size: hotCornerButton.height, icon_name: 'fedora-logo-icon' }); // comment out this line if you do not want an icon displayed box.add_actor(logo); // comment out this line if you do not want the label displayed box.add_actor(label); Main.panel.button.set_child(box); }

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se
1/15

on

ly

Changing the Activities Button in GNOME Shell 3.2 } ActivitiesButton.prototype = { __proto__: PanelMenu.Button.prototype, _init: function() { PanelMenu.Button.prototype._init.call(this, 0.0); let container = new Shell.GenericContainer(); container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferr edWidth)); container.connect('get-preferred-height', Lang.bind(this, this._containerGetPrefer redHeight)); container.connect('allocate', Lang.bind(this, this._containerAllocate)); this.actor.add_actor(container); this.actor.name = 'panelActivities'; this._label = new St.Label({ text: _("Activities") }); container.add_actor(this._label); this._hotCorner = new Layout.HotCorner(); container.add_actor(this._hotCorner.actor); this.menu.open = Lang.bind(this, this._onMenuOpenRequest); this.menu.close = Lang.bind(this, this._onMenuCloseRequest); this.menu.toggle = Lang.bind(this, this._onMenuToggleRequest); this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent)); this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRel ease)); this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease)) ; Main.overview.connect('showing', Lang.bind(this, function() { this.actor.add_style_pseudo_class('overview'); this._escapeMenuGrab(); })); Main.overview.connect('hiding', Lang.bind(this, function() { this.actor.remove_style_pseudo_class('overview'); this._escapeMenuGrab(); })); this._xdndTimeOut = 0; }, _containerGetPreferredWidth: function(actor, forHeight, alloc) { [alloc.min_size, alloc.natural_size] = this._label.get_preferred_width(forHeight); }, _containerGetPreferredHeight: function(actor, forWidth, alloc) { [alloc.min_size, alloc.natural_size] = this._label.get_preferred_height(forWidth); }, _containerAllocate: function(actor, box, flags) { this._label.allocate(box, flags); let primary = Main.layoutManager.primaryMonitor; let hotBox = new Clutter.ActorBox(); let ok, x, y; if (actor.get_direction() == St.TextDirection.LTR) { [ok, x, y] = actor.transform_stage_point(primary.x, primary.y) } else { [ok, x, y] = actor.transform_stage_point(primary.x + primary.width, primary.y) ; } hotBox.x1 = Math.round(x); hotBox.x2 = hotBox.x1 + this._hotCorner.actor.width; hotBox.y1 = Math.round(y); hotBox.y2 = hotBox.y1 + this._hotCorner.actor.height; this._hotCorner.actor.allocate(hotBox, flags); },

Fo r

Note the use of Shell.GenericContainer and its associated signal handlers. I will return to discussing this object and these signal handlers later in the post. The remainder of this post describes by means of a number of examples how to modify the above code via a GNOME Shell extension in order to change the (text) label displayed, display an icon instead of a label or display both an icon and a label simultaneously. This post assumes that you

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

2/15

Changing the Activities Button in GNOME Shell 3.2

are familiar with GNOME Shell extension development; it does not attempt to explain the basics of writing an extension. Example 1: This GNOME Shell extension simply displays an alternative label for the Activities button when enabled and reverts back to the original label, i.e. Activities, when disabled. Localization support for the alternative label is not included but can easily be added.

Some people prefer the non-prototypical way of writing a GNOME Shell extension. If you are one of those people, here is what this extension looks like when written to provide the three mandatory functions:

Fo r

const Main = imports.ui.main; // Replace this string constant with your text const ACTIVITIES_BUTTON_TEXT = "Change Me"; let _label; let _originalText; function init() { _label = Main.panel._activitiesButton._label; _originalText = _label.get_text(); } function enable() { _label.set_text(ACTIVITIES_BUTTON_TEXT); } function disable() { _label.set_text(this._originalText); }

Example 2: Things get more complicated when you want to add an icon to the Activities button or replace the

01-09-2012

pe rs o

As you can see, the code for this GNOME Shell extension is still fairly simple. When the extension is installed, _init is invoked. When the extension is enabled, enable is invoked and the label on the Activities button is changed to the contents of the ACTIVITIES_BUTTON_TEXT constant. When the extension is disabled, the original label, i.e. Activities, is displayed.

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

const Main = imports.ui.main; // Replace this string constant with your text const ACTIVITIES_BUTTON_TEXT = "Change Me"; function ChangeActivitiesButtonText() { this._init(); } ChangeActivitiesButtonText.prototype = { _init: function() { this._label = Main.panel._activitiesButton._label; this._originalText = this._label.get_text(); }, enable: function() { this._label.set_text(ACTIVITIES_BUTTON_TEXT); }, disable: function() { this._label.set_text(this._originalText); } }; function init(extensionMeta) { return new ChangeActivitiesButtonText(); }

lu

se
3/15

on

ly

Changing the Activities Button in GNOME Shell 3.2

text with an icon. This is due to the fact the the code for implementing the Activities button was changed in GNOME Shell 3.2 to use a Shell GenericContainer object. Here is the C source code for this object.

/** * SECTION:shell-generic-container * @short_description: A container class with signals for allocation * * #ShellGenericContainer is mainly a workaround for the current * lack of GObject subclassing + vfunc overrides in gjs. We * implement the container interface, but proxy the virtual functions * into signals, which gjs can catch. * * #ShellGenericContainer is an #StWidget, and automatically takes its * borders and padding into account during size request and allocation. */ #include "config.h" #include "shell-generic-container.h" #include <clutter/clutter.h> #include <gtk/gtk.h> #include <girepository.h> static void shell_generic_container_iface_init (ClutterContainerIface *iface); G_DEFINE_TYPE_WITH_CODE(ShellGenericContainer, shell_generic_container, ST_TYPE_CONTAINER, G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER, shell_generic_container_iface_init)); struct _ShellGenericContainerPrivate { GHashTable *skip_paint; }; /* Signals */ enum { GET_PREFERRED_WIDTH, GET_PREFERRED_HEIGHT, ALLOCATE, LAST_SIGNAL }; static guint shell_generic_container_signals [LAST_SIGNAL] = { 0 }; static gpointer shell_generic_container_allocation_ref (ShellGenericContainerAllocation *alloc) { alloc->_refcount++; return alloc; } static void shell_generic_container_allocation_unref (ShellGenericContainerAllocation *alloc) { if (--alloc->_refcount == 0) g_slice_free (ShellGenericContainerAllocation, alloc); } static void shell_generic_container_allocate (ClutterActor *self, const ClutterActorBox *box, ClutterAllocationFlags flags) { StThemeNode *theme_node; ClutterActorBox content_box; CLUTTER_ACTOR_CLASS (shell_generic_container_parent_class)->allocate (self, box, flags); theme_node = st_widget_get_theme_node (ST_WIDGET (self)); st_theme_node_get_content_box (theme_node, box, &amp;content_box); g_signal_emit (G_OBJECT (self), shell_generic_container_signals[ALLOCATE], 0, &amp;content_box, flags); } static void shell_generic_container_get_preferred_width (ClutterActor *actor,

Fo r

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

4/15

Changing the Activities Button in GNOME Shell 3.2 gfloat gfloat gfloat { ShellGenericContainerAllocation *alloc = g_slice_new0 (ShellGenericContainerAllocation); StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); st_theme_node_adjust_for_height (theme_node, &amp;for_height); alloc->_refcount = 1; g_signal_emit (G_OBJECT (actor), shell_generic_container_signals[GET_PREFERRED_WIDTH], 0, for_height, alloc); if (min_width_p) *min_width_p = alloc->min_size; if (natural_width_p) *natural_width_p = alloc->natural_size; shell_generic_container_allocation_unref (alloc); st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p); } static void shell_generic_container_get_preferred_height (ClutterActor *actor, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p) { ShellGenericContainerAllocation *alloc = g_slice_new0 (ShellGenericContainerAllocation); StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); st_theme_node_adjust_for_width (theme_node, &amp;for_width); alloc->_refcount = 1; g_signal_emit (G_OBJECT (actor), shell_generic_container_signals[GET_PREFERRED_HEIGHT], 0, for_width, alloc); if (min_height_p) *min_height_p = alloc->min_size; if (natural_height_p) *natural_height_p = alloc->natural_size; shell_generic_container_allocation_unref (alloc); st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p); } static void shell_generic_container_paint (ClutterActor *actor) { ShellGenericContainer *self = (ShellGenericContainer*) actor; GList *iter, *children; CLUTTER_ACTOR_CLASS (shell_generic_container_parent_class)->paint (actor); children = st_container_get_children_list (ST_CONTAINER (actor)); for (iter = children; iter; iter = iter->next) { ClutterActor *child = iter->data; if (g_hash_table_lookup (self->priv->skip_paint, child)) continue; clutter_actor_paint (child); } } static void shell_generic_container_pick (ClutterActor *actor, const ClutterColor *color) { ShellGenericContainer *self = (ShellGenericContainer*) actor; GList *iter, *children; CLUTTER_ACTOR_CLASS (shell_generic_container_parent_class)->pick (actor, color); children = st_container_get_children_list (ST_CONTAINER (actor)); for (iter = children; iter; iter = iter->next) { ClutterActor *child = iter->data; if (g_hash_table_lookup (self->priv->skip_paint, child)) continue; clutter_actor_paint (child); } } for_height, *min_width_p, *natural_width_p)

Fo r

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

5/15

Changing the Activities Button in GNOME Shell 3.2 static GList * shell_generic_container_get_focus_chain (StContainer *container) { ShellGenericContainer *self = SHELL_GENERIC_CONTAINER (container); GList *children, *focus_chain; focus_chain = NULL; for (children = st_container_get_children_list (container); children; children = childre n->next) { ClutterActor *child = children->data; if (CLUTTER_ACTOR_IS_VISIBLE (child) &amp;&amp; !shell_generic_container_get_skip_paint (self, child)) focus_chain = g_list_prepend (focus_chain, child); } return g_list_reverse (focus_chain); } /** * shell_generic_container_get_n_skip_paint: * @self: A #ShellGenericContainer * * Returns: Number of children which will not be painted. */ guint shell_generic_container_get_n_skip_paint (ShellGenericContainer *self) { return g_hash_table_size (self->priv->skip_paint); } /** * shell_generic_container_get_skip_paint: * @self: A #ShellGenericContainer * @child: Child #ClutterActor * * Gets whether or not @actor is skipped when painting. * * Return value: %TRUE or %FALSE */ gboolean shell_generic_container_get_skip_paint (ShellGenericContainer *self, ClutterActor *child) { return g_hash_table_lookup (self->priv->skip_paint, child) != NULL; } /** * shell_generic_container_set_skip_paint: * @self: A #ShellGenericContainer * @child: Child #ClutterActor * @skip: %TRUE if we should skip painting * * Set whether or not we should skip painting @actor. Workaround for * lack of gjs ability to override _paint vfunc. */ void shell_generic_container_set_skip_paint (ShellGenericContainer *self, ClutterActor *child, gboolean skip) { gboolean currently_skipping; currently_skipping = g_hash_table_lookup (self->priv->skip_paint, child) != NULL; if (!!skip == currently_skipping) return; if (!skip) g_hash_table_remove (self->priv->skip_paint, child); else g_hash_table_insert (self->priv->skip_paint, child, child); clutter_actor_queue_redraw (CLUTTER_ACTOR (self)); } static void shell_generic_container_finalize (GObject *object)

Fo r

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

6/15

Changing the Activities Button in GNOME Shell 3.2 { ShellGenericContainer *self = (ShellGenericContainer*) object; g_hash_table_destroy (self->priv->skip_paint); G_OBJECT_CLASS (shell_generic_container_parent_class)->finalize (object); } static void shell_generic_container_class_init (ShellGenericContainerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); StContainerClass *container_class = ST_CONTAINER_CLASS (klass); gobject_class->finalize = shell_generic_container_finalize; actor_class->get_preferred_width = shell_generic_container_get_preferred_width; actor_class->get_preferred_height = shell_generic_container_get_preferred_height; actor_class->allocate = shell_generic_container_allocate; actor_class->paint = shell_generic_container_paint; actor_class->pick = shell_generic_container_pick; container_class->get_focus_chain = shell_generic_container_get_focus_chain; /** * ShellGenericContainer::get-preferred-width: * @self: the #ShellGenericContainer * @for_height: as in clutter_actor_get_preferred_width() * @alloc: a #ShellGenericContainerAllocation to be filled in * * Emitted when clutter_actor_get_preferred_width() is called * on @self. You should fill in the fields of @alloc with the * your minimum and natural widths. #ShellGenericContainer * will deal with taking its borders and padding into account * for you. * * @alloc's fields are initialized to 0, so unless you have a fixed * width specified (via #ClutterActor:width or CSS), you must * connect to this signal and fill in the values. */ shell_generic_container_signals[GET_PREFERRED_WIDTH] = g_signal_new ("get-preferred-width", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, gi_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_FLOAT, SHELL_TYPE_GENERIC_CONTAINER_ALLOCATION); /** * ShellGenericContainer::get-preferred-height: * @self: the #ShellGenericContainer * @for_width: as in clutter_actor_get_preferred_height() * @alloc: a #ShellGenericContainerAllocation to be filled in * * Emitted when clutter_actor_get_preferred_height() is called * on @self. You should fill in the fields of @alloc with the * your minimum and natural heights. #ShellGenericContainer * will deal with taking its borders and padding into account * for you. * * @alloc's fields are initialized to 0, so unless you have a fixed * height specified (via #ClutterActor:height or CSS), you must * connect to this signal and fill in the values. */ shell_generic_container_signals[GET_PREFERRED_HEIGHT] = g_signal_new ("get-preferred-height", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, gi_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_FLOAT, SHELL_TYPE_GENERIC_CONTAINER_ALLOCATION); /** * ShellGenericContainer::allocate:

Fo r

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

7/15

Changing the Activities Button in GNOME Shell 3.2 * @self: the #ShellGenericContainer * @box: @self's content box * @flags: the allocation flags. * * Emitted when @self is allocated, after chaining up to the parent * allocate method. * * Note that @box is @self's content box (qv * st_theme_node_get_content_box()), NOT its allocation. */ shell_generic_container_signals[ALLOCATE] = g_signal_new ("allocate", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, gi_cclosure_marshal_generic, G_TYPE_NONE, 2, CLUTTER_TYPE_ACTOR_BOX, CLUTTER_TYPE_ALLOCATION_FLAGS); g_type_class_add_private (gobject_class, sizeof (ShellGenericContainerPrivate)); } static void shell_generic_container_actor_removed (ClutterContainer *container, ClutterActor *actor) { ShellGenericContainerPrivate *priv = SHELL_GENERIC_CONTAINER (container)->priv; g_hash_table_remove (priv->skip_paint, actor); } static void shell_generic_container_iface_init (ClutterContainerIface *iface) { iface->actor_removed = shell_generic_container_actor_removed; } static void shell_generic_container_init (ShellGenericContainer *area) { area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, SHELL_TYPE_GENERIC_CONTAINER, ShellGenericContainerPrivate); area->priv->skip_paint = g_hash_table_new (NULL, NULL); } GType shell_generic_container_allocation_get_type (void) { static GType gtype = G_TYPE_INVALID; if (gtype == G_TYPE_INVALID) { gtype = g_boxed_type_register_static ("ShellGenericContainerAllocation", (GBoxedCopyFunc)shell_generic_container_allocation_ref, (GBoxedFreeFunc)shell_generic_container_allocation_unref); } return gtype; }

Fo r

One way to work around the Shell GenericContainer issue is to use CSS to restyle the Activities Button to display an icon instead of the default label as shown below:

const St = imports.gi.St; const Shell = imports.gi.Shell; const Main = imports.ui.main; function ThemeActivitiesButton(meta) { this._init(meta) } ThemeActivitiesButton.prototype = { _init: function(meta) { this._defaultStylesheet = Main._defaultCssStylesheet; this._patchStylesheet = meta.path + '/activitiesbutton.css';

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

8/15

Changing the Activities Button in GNOME Shell 3.2 this._themeContext = St.ThemeContext.get_for_stage(global.stage); }, enable: function() { let theme = new St.Theme ({ application_stylesheet: this._patchStylesheet, theme_stylesheet: this._defaultStylesheet }); try { this._themeContext.set_theme(theme); } catch (e) { global.logError('Stylesheet parse error: ' + e); } }, disable: function() { let theme = new St.Theme ({ theme_stylesheet: this._defaultStylesheet }); try { this._themeContext.set_theme(theme); } catch (e) { global.logError('Stylesheet parse error: ' + e); } } }; function init(meta) { return new ThemeActivitiesButton(meta); }

Here is the corresponding stylesheet.css

Fo r

The problems with this approach are (1) you cannot display both text and an icon on the Activities button, and (2) there is potential for the styling of the GNOME Shell to become confused due to multiple stylesheets in multiple GNOME Shell extensions. This is a known problem in the GNOME Shell. It is something that can easily occur with badly written Shell extensions that make unwarranted assumptions about what they can and cannot do. The better approach is to understand how Shell GenericContainer works. Probably the most important thing to understand about this object is that you cannot allocate a height or width when you are creating an instance of this object. Instead you have to use three separate signals as shown in the following example:

// create the new shell generic container object let container = new Shell.GenericContainer(); // set up the three signals container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth) ); container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeigh

01-09-2012

pe rs o

/* activitiesbutton.png should be 24x24 PNG icon */ #panelActivities { border: none; background-image: url("activitiesbutton.png"); background-position: 4 0; width: 24px; height: 24px; padding-left: 12px; padding-right: 0px; color: rgba(0,0,0,0.0); transition-duration: 100; } #panelActivities:hover { border-image: url("activitiesbutton-border.svg") 10 10 0 2; }

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se
9/15

on

ly

Changing the Activities Button in GNOME Shell 3.2 t)); container.connect('allocate', Lang.bind(this, this._containerAllocate)); // signal code _containerGetPreferredWidth: function(actor, forHeight, alloc) { [alloc.min_size, alloc.natural_size] = this._label.get_preferred_width(forHeight); }, _containerGetPreferredHeight: function(actor, forWidth, alloc) { [alloc.min_size, alloc.natural_size] = this._label.get_preferred_height(forWidth); }, _containerAllocate: function(actor, box, flags) { this._label.allocate(box, flags); .... },

const Clutter = imports.gi.Clutter; const Shell = imports.gi.Shell; const St = imports.gi.St; const Layout = imports.ui.layout; const PanelMenu = imports.ui.panelMenu; const Main = imports.ui.main; const Lang = imports.lang; const Mainloop = imports.mainloop; const Signals = imports.signals; // ------------ change to suit ----------const ACTIVITIES_BUTTON_ICON_SIZE = 20; const ACTIVITIES_BUTTON_ICON_NAME = 'fedora-logo-icon'; function ActivitiesButtonIcon() { this._init.apply(this, arguments); } // ----- most of this code came straight from panel.js ActivitiesButtonIcon.prototype = { __proto__: PanelMenu.Button.prototype, _init: function() { PanelMenu.Button.prototype._init.call(this, 0.0); this.actor.name = 'panelActivities'; let container = new Shell.GenericContainer(); container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferr edWidth)); container.connect('get-preferred-height', Lang.bind(this, this._containerGetPrefer redHeight)); container.connect('allocate', Lang.bind(this, this._containerAllocate)); this.actor.add_actor(container); // ---------------- icon code ----------------this._iconBox = new St.Bin({ width: ACTIVITIES_BUTTON_ICON_SIZE, height: ACTIVITIES_BUTTON_ICON_SIZE, x_fill: true, y_fill: true }); this._logo = new St.Icon({ icon_type: St.IconType.FULLCOLOR, icon_size: ACTIVITIES_BUTTON_ICON_SIZE, icon_name: ACTIVITIES_BUTTON_ICON_NAME }); this._iconBox.child = this._logo; container.add_actor(this._iconBox); this._hotCorner = new Layout.HotCorner(); container.add_actor(this._hotCorner.actor); // Hack up our menu... this.menu.open = Lang.bind(this, this._onMenuOpenRequest); this.menu.close = Lang.bind(this, this._onMenuCloseRequest); this.menu.toggle = Lang.bind(this, this._onMenuToggleRequest); this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent));

Fo r

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

Here is a complete extension.js which displays the Fedora Project logo on the Activities button when enabled. All the remaining examples in this post are based on this code.

ly

10/15

Changing the Activities Button in GNOME Shell 3.2 this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRel ease)); this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease)) ; Main.overview.connect('showing', Lang.bind(this, function() { this.actor.add_style_pseudo_class('overview'); this._escapeMenuGrab(); })); Main.overview.connect('hiding', Lang.bind(this, function() { this.actor.remove_style_pseudo_class('overview'); this._escapeMenuGrab(); })); this._xdndTimeOut = 0; }, _containerGetPreferredWidth: function(actor, forHeight, alloc) { [alloc.min_size, alloc.natural_size] = this._iconBox.get_preferred_width(forHeight) ; }, _containerGetPreferredHeight: function(actor, forWidth, alloc) { [alloc.min_size, alloc.natural_size] = this._iconBox.get_preferred_height(forWidth) ; }, _containerAllocate: function(actor, box, flags) { this._iconBox.allocate(box, flags); let primary = Main.layoutManager.primaryMonitor; let hotBox = new Clutter.ActorBox(); let ok, x, y; if (actor.get_direction() == St.TextDirection.LTR) { [ok, x, y] = actor.transform_stage_point(primary.x, primary.y) } else { [ok, x, y] = actor.transform_stage_point(primary.x + primary.width, primary.y) } hotBox.x1 = Math.round(x); hotBox.x2 = hotBox.x1 + this._hotCorner.actor.width; hotBox.y1 = Math.round(y); hotBox.y2 = hotBox.y1 + this._hotCorner.actor.height; this._hotCorner.actor.allocate(hotBox, flags); }, handleDragOver: function(source, actor, x, y, time) { if (source != Main.xdndHandler) return; if (this._xdndTimeOut != 0) Mainloop.source_remove(this._xdndTimeOut); this._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT, Lang.bind(this, this._xdndShowOverview, a ctor)); }, _escapeMenuGrab: function() { if (this.menu.isOpen) this.menu.close(); }, _onCapturedEvent: function(actor, event) { if (event.type() == Clutter.EventType.BUTTON_PRESS) { if (!this._hotCorner.shouldToggleOverviewOnClick()) return true; } return false; }, _onMenuOpenRequest: function() { this.menu.isOpen = true; this.menu.emit('open-state-changed', true); }, _onMenuCloseRequest: function() { this.menu.isOpen = false; this.menu.emit('open-state-changed', false); }, _onMenuToggleRequest: function() {

Fo r

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

;

lu

se

on

ly

11/15

Changing the Activities Button in GNOME Shell 3.2 this.menu.isOpen = !this.menu.isOpen; this.menu.emit('open-state-changed', this.menu.isOpen); }, _onButtonRelease: function() { if (this.menu.isOpen) { this.menu.close(); Main.overview.toggle(); } }, _onKeyRelease: function(actor, event) { let symbol = event.get_key_symbol(); if (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_space) { if (this.menu.isOpen) this.menu.close(); Main.overview.toggle(); } }, _xdndShowOverview: function(actor) { let [x, y, mask] = global.get_pointer(); let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y); if (pickedActor == this.actor) { if (!Main.overview.visible &amp;&amp; !Main.overview.animationInProgress) { Main.overview.showTemporarily(); Main.overview.beginItemDrag(actor); } } Mainloop.source_remove(this._xdndTimeOut); this._xdndTimeOut = 0; } }; function ChangeActivitiesButton() { this._init(); } ChangeActivitiesButton.prototype = { _init: function() { this._myActivitiesButton = new ActivitiesButtonIcon(); this._orgActivitiesButton = Main.panel._activitiesButton; }, enable: function() { Main.panel._leftBox.remove_actor(this._orgActivitiesButton.actor); Main.panel._leftBox.insert_actor(this._myActivitiesButton.actor, 0); }, disable: function() { Main.panel._leftBox.remove_actor(this._myActivitiesButton.actor); Main.panel._leftBox.insert_actor(this._orgActivitiesButton.actor, 0); } }; function init(extensionMeta) { return new ChangeActivitiesButton(); }

Fo r

In GNOME Shell 3.0, we could have used monkey patching to patch the ActivitiesButton prototype code in /usr/share/gnome-shell/js/ui/panel.js. This is no longer possible in GNOME Shell 3.2 due to a change in when GNOME Shell extensions are loaded and enabled. Hence the need to provide a complete ActivitiesButton prototype in the extension. This code can be used to display any icon that is available in the usual places on a Linux platform as specified by the XDG Base Directory Specification. You can also place a custom icon in $HOME/.icons and it will displayed by the GNOME Shell extension. The above example uses a SVG for the icon but other filetypes are accepted. Example 3: In this example, our new GNOME Shell extension from Example 2 is modified so that a file

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

12/15

Changing the Activities Button in GNOME Shell 3.2

containing an icon is read and icon contained therein is displayed on the Activities button.

In the above example, the icon file must be located in the same directory as the extension code. Certain metadata is passed to a GNOME Shell extension when it is loaded. The full path to where the extension code is located is one component of the available metadata. In the above example, this path is retrieved using extensionMeta.path.

Fo r

Example 4:

In this final example, our new GNOME Shell extension from Example 2 is modified so that both an icon and text are displayed.

01-09-2012

pe rs o

const ACTIVITIES_BUTTON_ICON_FILENAME = 'activities_button_icon.svg'; function ActivitiesButtonIcon() { this._init.apply(this, arguments); } // ----- most of this code came straight from panel.js ActivitiesButtonIcon.prototype = { __proto__: PanelMenu.Button.prototype, _init: function(iconpath) { PanelMenu.Button.prototype._init.call(this, 0.0); this.actor.name = 'panelActivities'; // ---------------- icon code ----------------let textureCache = St.TextureCache.get_default(); this._iconActivitiesButton = textureCache.load_uri_async("file://" + iconpath + "/" + ACTIVITIES_BUTTON_ICON_FILENAME, -1, -1); this._iconBox = new St.Bin({ style_class: 'activities-button-icon', child: this._iconActivitiesButton }); container.add_actor(this._iconBox); .... }, _containerGetPreferredWidth: function(actor, forHeight, alloc) { [alloc.min_size, alloc.natural_size] = this._iconBox.get_preferred_width(forHeight) ; }, _containerGetPreferredHeight: function(actor, forWidth, alloc) { [alloc.min_size, alloc.natural_size] = this._iconBox.get_preferred_height(forWidth) ; }, _containerAllocate: function(actor, box, flags) { this._iconBox.allocate(box, flags); ... }, ... }; function init(extensionMeta) { return new ChangeActivitiesButton(extensionMeta.path); }

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

13/15

Changing the Activities Button in GNOME Shell 3.2

Here are the relevant code changes:

// ---------------- icon and text code ----------------let textureCache = St.TextureCache.get_default(); this._iconActivitiesButton = textureCache.load_uri_async("file://" + iconpath + "/" + ACTIVITIES_BUTTON_ICON_FILENAME, -1, -1); this._boxIconText = new St.BoxLayout({ vertical: false }); this._boxIconText.add_actor( new St.Bin({ style_class: 'activities-button-icon', child: this._iconActivitiesButton })); this._boxIconText.add_actor(new St.Label({ text: _("Activities"), style_class: 'activities-button-text' }) ); container.add_actor(this._boxIconText); .... _containerGetPreferredWidth: function(actor, forHeight, alloc) { [alloc.min_size, alloc.natural_size] = this._boxIconText.get_preferred_width(forHe ight); }, _containerGetPreferredHeight: function(actor, forWidth, alloc) { [alloc.min_size, alloc.natural_size] = this._boxIconText.get_preferred_height(forW idth); }, _containerAllocate: function(actor, box, flags) { this._boxIconText.allocate(box, flags); ...

Fo r

Support for an extra CSS ruleset, activities-button-text, was added to allow the Activities button label to be styled.

.activities-button-text { font-style: italic; font-size: 75%; }

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se
14/15

on

ly

Changing the Activities Button in GNOME Shell 3.2

If you examine the above screenshot, you will see that the Activities label is displayed in a smaller italicized font. There is no reason that such support could not be added to the Example 1 GNOME Shell extension. I will leave that as an exercise for you to do. Well, the above examples should have provided you with all the necessary knowledge and skills to enable you to develop your own GNOME 3.2 Shell extension to customize your Activities button in whatever manner that you like. All of the above examples can be downloaded from my GNOME Shell Extensions webpage as working extensions. P.S. Please feel free to email me with your suggestions of additional topics relating to extending the GNOME Shell that you would like me to write about.

Fo r

01-09-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se
15/15

on

ly

Sign up to vote on this title
UsefulNot useful