Changing the Activities Button in GNOME Shell 3 2

15
Changing the Activities Button in GNOME Shell 3.2 Changing the Activities Button in GNOME Shell 3.2 Finnbarr P. Murphy ([email protected]) 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. 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); } 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); For personnal use only 01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 1/15

description

See http://blog.fpmurphy.com/2012/01/changing-the-activities-button-in-gnome-shell-3-2.html for more information and the lastest version of this document

Transcript of Changing the Activities Button in GNOME Shell 3 2

Page 1: Changing the Activities Button in GNOME Shell 3 2

Changing the Activities Button in GNOME Shell 3.2

Changing the Activities Button inGNOME Shell 3.2

Finnbarr P. Murphy([email protected])

In the original version of the GNOME Shell (3.0) it was very easy to alter the text of the Activitiesbutton 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 afterthe release of GNOME 3.0 which enables a user to do just that.

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);}

This GNOME Shell extension could be easily customized to display either an icon, a text string orboth for the Activities button. All you had to do was comment or or uncomment the appropriatelines in the source code.

With the release of GNOME 3.2, which included GNOME Shell 3.2, in September 2011, the abovecode no longer works. Life became much more complicated for GNOME Shell extensiondevelopers because of new requirements to support three functions: init, enable and disable whencoding a new GNOME Shell extension or upgrading an existing extension to support GNOME Shell3.2. See this post, which I wrote a couple of months ago, for a detailed description of the changesto the GNOME Shell extension infrastructure.

In addition, the JavaScript code relating to the Activities button significantly changed betweenGNOME Shell 3.0 and 3.2. Here is the relevant code from /usr/share/gnome-shell/js/ui/panel.jswith comments removed to reduce the number of lines.

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

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 1/15

Page 2: Changing the Activities Button in GNOME Shell 3 2

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._containerGetPreferredWidth)); container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight)); 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._onButtonRelease)); 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); },

Note the use of Shell.GenericContainer and its associated signal handlers. I will return todiscussing 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 abovecode via a GNOME Shell extension in order to change the (text) label displayed, display an iconinstead of a label or display both an icon and a label simultaneously. This post assumes that you

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 2/15

Page 3: Changing the Activities Button in GNOME Shell 3 2

Changing the Activities Button in GNOME Shell 3.2

are familiar with GNOME Shell extension development; it does not attempt to explain the basics ofwriting an extension.

Example 1:

This GNOME Shell extension simply displays an alternative label for the Activities button whenenabled and reverts back to the original label, i.e. Activities, when disabled. Localization supportfor the alternative label is not included but can easily be added.

const Main = imports.ui.main;// Replace this string constant with your textconst 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();}

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

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

const Main = imports.ui.main;// Replace this string constant with your textconst 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

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 3/15

Page 4: Changing the Activities Button in GNOME Shell 3 2

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 waschanged in GNOME Shell 3.2 to use a Shell GenericContainer object. Here is the C source codefor 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 gpointershell_generic_container_allocation_ref (ShellGenericContainerAllocation *alloc){ alloc->_refcount++; return alloc;}static voidshell_generic_container_allocation_unref (ShellGenericContainerAllocation *alloc){ if (--alloc->_refcount == 0) g_slice_free (ShellGenericContainerAllocation, alloc);}static voidshell_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 voidshell_generic_container_get_preferred_width (ClutterActor *actor,

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 4/15

Page 5: Changing the Activities Button in GNOME Shell 3 2

Changing the Activities Button in GNOME Shell 3.2

gfloat for_height, gfloat *min_width_p, gfloat *natural_width_p){ 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 voidshell_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 voidshell_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 voidshell_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 p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 5/15

Page 6: Changing the Activities Button in GNOME Shell 3 2

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 = children->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. */guintshell_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 */gbooleanshell_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. */voidshell_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 voidshell_generic_container_finalize (GObject *object)

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 6/15

Page 7: Changing the Activities Button in GNOME Shell 3 2

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 voidshell_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:

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 7/15

Page 8: Changing the Activities Button in GNOME Shell 3 2

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 voidshell_generic_container_actor_removed (ClutterContainer *container, ClutterActor *actor){ ShellGenericContainerPrivate *priv = SHELL_GENERIC_CONTAINER (container)->priv; g_hash_table_remove (priv->skip_paint, actor);}static voidshell_generic_container_iface_init (ClutterContainerIface *iface){ iface->actor_removed = shell_generic_container_actor_removed;}static voidshell_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);}GTypeshell_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;}

One way to work around the Shell GenericContainer issue is to use CSS to restyle the ActivitiesButton 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';

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 8/15

Page 9: Changing the Activities Button in GNOME Shell 3 2

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

/* 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;}

The problems with this approach are (1) you cannot display both text and an icon on the Activitiesbutton, and (2) there is potential for the styling of the GNOME Shell to become confused due tomultiple stylesheets in multiple GNOME Shell extensions. This is a known problem in the GNOMEShell. It is something that can easily occur with badly written Shell extensions that makeunwarranted assumptions about what they can and cannot do.

The better approach is to understand how Shell GenericContainer works. Probably the mostimportant thing to understand about this object is that you cannot allocate a height or width whenyou are creating an instance of this object. Instead you have to use three separate signals asshown in the following example:

// create the new shell generic container objectlet container = new Shell.GenericContainer();

// set up the three signalscontainer.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth));container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeigh

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 9/15

Page 10: Changing the Activities Button in GNOME Shell 3 2

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); ....},

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

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.jsActivitiesButtonIcon.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._containerGetPreferredWidth)); container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight)); 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));

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 10/15

Page 11: Changing the Activities Button in GNOME Shell 3 2

Changing the Activities Button in GNOME Shell 3.2

this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRelease)); 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, actor)); }, _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() {

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 11/15

Page 12: Changing the Activities Button in GNOME Shell 3 2

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();}

In GNOME Shell 3.0, we could have used monkey patching to patch the ActivitiesButton prototypecode in /usr/share/gnome-shell/js/ui/panel.js. This is no longer possible in GNOME Shell 3.2 due toa change in when GNOME Shell extensions are loaded and enabled. Hence the need to provide acomplete ActivitiesButton prototype in the extension.

This code can be used to display any icon that is available in the usual places on a Linux platformas 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 aSVG 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

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 12/15

Page 13: Changing the Activities Button in GNOME Shell 3 2

Changing the Activities Button in GNOME Shell 3.2

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

const ACTIVITIES_BUTTON_ICON_FILENAME = 'activities_button_icon.svg'; function ActivitiesButtonIcon() { this._init.apply(this, arguments);}// ----- most of this code came straight from panel.jsActivitiesButtonIcon.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);}

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 wherethe extension code is located is one component of the available metadata. In the above example,this path is retrieved using extensionMeta.path.

Example 4:

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

r pers

onna

l use

only

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 13/15

Page 14: Changing the Activities Button in GNOME Shell 3 2

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(forHeight); }, _containerGetPreferredHeight: function(actor, forWidth, alloc) { [alloc.min_size, alloc.natural_size] = this._boxIconText.get_preferred_height(forWidth); }, _containerAllocate: function(actor, box, flags) { this._boxIconText.allocate(box, flags); ...

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

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

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 14/15

Page 15: Changing the Activities Button in GNOME Shell 3 2

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 smalleritalicized font. There is no reason that such support could not be added to the Example 1 GNOMEShell 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 toenable you to develop your own GNOME 3.2 Shell extension to customize your Activities button inwhatever manner that you like. All of the above examples can be downloaded from my GNOMEShell Extensions webpage as working extensions.

P.S. Please feel free to email me with your suggestions of additional topics relating to extendingthe GNOME Shell that you would like me to write about.

For p

erson

nal u

se on

ly

01-09-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 15/15