50aa15dec9
When transitioning from gnome-panel to gnome-shell in 3.0 we lost the ability to summon the wisdom of the mythical fish. This patch restores this, for the few adepts that are aware of the magical incantation. (Not as configurable as the original one, but it's an easter egg after all...) https://bugzilla.gnome.org/show_bug.cgi?id=666606
211 lines
7.2 KiB
JavaScript
211 lines
7.2 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
const GdkPixbuf = imports.gi.GdkPixbuf;
|
|
const GLib = imports.gi.GLib;
|
|
const Lang = imports.lang;
|
|
const Shell = imports.gi.Shell;
|
|
const Signals = imports.signals;
|
|
const St = imports.gi.St;
|
|
|
|
const IconGrid = imports.ui.iconGrid;
|
|
const Main = imports.ui.main;
|
|
const Search = imports.ui.search;
|
|
|
|
// we could make these gsettings
|
|
const FISH_NAME = 'wanda';
|
|
const FISH_SPEED = 300;
|
|
const FISH_COMMAND = 'fortune';
|
|
|
|
const GNOME_PANEL_PIXMAPDIR = '../gnome-panel/pixmaps';
|
|
const FISH_GROUP = 'Fish Animation';
|
|
|
|
const MAGIC_FISH_KEY = 'free the fish';
|
|
|
|
const WandaIcon = new Lang.Class({
|
|
Name: 'WandaIcon',
|
|
Extends: IconGrid.BaseIcon,
|
|
|
|
_init : function(fish, label, params) {
|
|
this._fish = fish;
|
|
let file = GLib.build_filenamev([global.datadir, GNOME_PANEL_PIXMAPDIR, fish + '.fish']);
|
|
|
|
if (GLib.file_test(file, GLib.FileTest.EXISTS)) {
|
|
this._keyfile = new GLib.KeyFile();
|
|
this._keyfile.load_from_file(file, GLib.KeyFileFlags.NONE);
|
|
|
|
this._imageFile = GLib.build_filenamev([global.datadir, GNOME_PANEL_PIXMAPDIR,
|
|
this._keyfile.get_string(FISH_GROUP, 'image')]);
|
|
|
|
let tmpPixbuf = GdkPixbuf.Pixbuf.new_from_file(this._imageFile);
|
|
|
|
this._imgHeight = tmpPixbuf.height;
|
|
this._imgWidth = tmpPixbuf.width / this._keyfile.get_integer(FISH_GROUP, 'frames');
|
|
} else {
|
|
this._imageFile = null;
|
|
}
|
|
|
|
this.parent(label, params);
|
|
},
|
|
|
|
createIcon: function(iconSize) {
|
|
if (this._animations)
|
|
this._animations.destroy();
|
|
|
|
if (!this._imageFile) {
|
|
return new St.Icon({ icon_name: 'face-smile',
|
|
icon_type: St.IconType.FULLCOLOR,
|
|
icon_size: iconSize
|
|
});
|
|
}
|
|
|
|
this._animations = St.TextureCache.get_default().load_sliced_image(this._imageFile, this._imgWidth, this._imgHeight);
|
|
this._animations.connect('destroy', Lang.bind(this, function() {
|
|
if (this._timeoutId)
|
|
GLib.source_remove(this._timeoutId);
|
|
this._timeoutId = 0;
|
|
this._animations = null;
|
|
}));
|
|
this._animations.connect('notify::mapped', Lang.bind(this, function() {
|
|
if (this._animations.mapped && !this._timeoutId) {
|
|
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, FISH_SPEED, Lang.bind(this, this._update));
|
|
|
|
this._i = 0;
|
|
this._update();
|
|
} else if (!this._animations.mapped && this._timeoutId) {
|
|
GLib.source_remove(this._timeoutId);
|
|
this._timeoutId = 0;
|
|
}
|
|
}));
|
|
|
|
this._i = 0;
|
|
|
|
return this._animations;
|
|
},
|
|
|
|
_update: function() {
|
|
let n = this._animations.get_n_children();
|
|
if (n == 0) {
|
|
return true;
|
|
}
|
|
|
|
this._animations.get_nth_child(this._i).hide();
|
|
this._i = (this._i + 1) % n;
|
|
this._animations.get_nth_child(this._i).show();
|
|
|
|
return true;
|
|
},
|
|
});
|
|
|
|
const WandaIconBin = new Lang.Class({
|
|
Name: 'WandaIconBin',
|
|
|
|
_init: function(fish, label, params) {
|
|
this.actor = new St.Bin({ style_class: 'search-result-content',
|
|
reactive: true,
|
|
track_hover: true });
|
|
this.icon = new WandaIcon(fish, label, params);
|
|
|
|
this.actor.child = this.icon.actor;
|
|
this.actor.label_actor = this.icon.label;
|
|
},
|
|
});
|
|
|
|
const FortuneDialog = new Lang.Class({
|
|
Name: 'FortuneDialog',
|
|
|
|
_init: function(name, command) {
|
|
let text;
|
|
|
|
try {
|
|
let [res, stdout, stderr, status] = GLib.spawn_command_line_sync(command);
|
|
text = String.fromCharCode.apply(null, stdout);
|
|
} catch(e) {
|
|
text = _("Sorry, no wisdom for you today:\n%s").format(e.message);
|
|
}
|
|
|
|
this._title = new St.Label({ style_class: 'polkit-dialog-headline',
|
|
text: _("%s the Oracle says").format(name) });
|
|
this._label = new St.Label({ style_class: 'polkit-dialog-description',
|
|
text: text });
|
|
this._label.clutter_text.line_wrap = true;
|
|
|
|
this._box = new St.BoxLayout({ vertical: true,
|
|
style_class: 'polkit-dialog' // this is just to force a reasonable width
|
|
});
|
|
this._box.add(this._title, { align: St.Align.MIDDLE });
|
|
this._box.add(this._label, { expand: true });
|
|
|
|
this._button = new St.Button({ button_mask: St.ButtonMask.ONE,
|
|
style_class: 'modal-dialog',
|
|
reactive: true });
|
|
this._button.connect('clicked', Lang.bind(this, this.destroy));
|
|
this._button.child = this._box;
|
|
|
|
let monitor = Main.layoutManager.primaryMonitor;
|
|
|
|
Main.layoutManager.addChrome(this._button);
|
|
this._button.set_position(Math.floor(monitor.width / 2 - this._button.width / 2),
|
|
Math.floor(monitor.height / 2 - this._button.height / 2));
|
|
|
|
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 10, Lang.bind(this, this.destroy));
|
|
},
|
|
|
|
destroy: function() {
|
|
this._button.destroy();
|
|
}
|
|
});
|
|
|
|
function capitalize(str) {
|
|
return str[0].toUpperCase() + str.substring(1, str.length);
|
|
}
|
|
|
|
const WandaSearchProvider = new Lang.Class({
|
|
Name: 'WandaSearchProvider',
|
|
Extends: Search.SearchProvider,
|
|
|
|
_init: function() {
|
|
this.parent(_("Your favorite Easter Egg"));
|
|
},
|
|
|
|
getResultMeta: function(fish) {
|
|
return { 'id': fish,
|
|
'name': capitalize(fish),
|
|
'createIcon': function(iconSize) {
|
|
// for DND only (maybe could be improved)
|
|
// DON'T use St.Icon here, it crashes the shell
|
|
// (dnd.js code assumes it can query the actor size
|
|
// without parenting it, while StWidget accesses
|
|
// StThemeNode in get_preferred_width/height, which
|
|
// triggers an assertion failure)
|
|
return St.TextureCache.get_default().load_icon_name(null,
|
|
'face-smile',
|
|
St.IconType.FULLCOLOR,
|
|
iconSize);
|
|
}
|
|
};
|
|
},
|
|
|
|
getInitialResultSet: function(terms) {
|
|
if (terms.join(' ') == MAGIC_FISH_KEY) {
|
|
return [ FISH_NAME ];
|
|
}
|
|
return [];
|
|
},
|
|
|
|
getSubsearchResultSet: function(previousResults, terms) {
|
|
return this.getInitialResultSet(terms);
|
|
},
|
|
|
|
activateResult: function(fish, params) {
|
|
if (this._dialog)
|
|
this._dialog.destroy();
|
|
this._dialog = new FortuneDialog(capitalize(fish), FISH_COMMAND);
|
|
},
|
|
|
|
createResultActor: function (resultMeta, terms) {
|
|
let icon = new WandaIconBin(resultMeta.id, resultMeta.name);
|
|
return icon.actor;
|
|
}
|
|
});
|