forked from mheine/gnome-shell-spotify-label
-
Notifications
You must be signed in to change notification settings - Fork 0
/
extension.js
320 lines (257 loc) · 10.3 KB
/
extension.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
const St = imports.gi.St;
const Main = imports.ui.main;
const Soup = imports.gi.Soup;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Clutter = imports.gi.Clutter;
const PanelMenu = imports.ui.panelMenu;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
//"User-defined" constants. If you've stumbled upon this extension, these values are the most likely you'd like to change.
let LEFT_PADDING, RIGHT_PADDING, MAX_STRING_LENGTH, REFRESH_RATE, FRIENDLY_GREETING, ARTIST_FIRST, EXTENSION_PLACE, EXTENSION_INDEX, TOGGLE_WINDOW, gschema, lastExtensionPlace, lastExtensionIndex;
var settings, onLeftPaddingChanged, onRightPaddingChanged, onExtensionPlaceChanged, onExtensionIndexChanged, onToggleModeChanged;
let _httpSession;
let spMenu;
const SpotifyLabel = new Lang.Class({
Name: 'SpotifyLabel',
Extends: PanelMenu.Button,
_init: function (settings) {
this.parent(0.0, "Spotify Label", false);
this.settings = settings;
this.buttonText = new St.Label({
text: _("Loading..."),
style: "padding-left: " + this.settings.get_int('left-padding') + "px;"
+ "padding-right: " + this.settings.get_int('right-padding') + "px; ",
y_align: Clutter.ActorAlign.CENTER,
x_align: Clutter.ActorAlign.FILL
});
// Listen for update of padding in settings
onLeftPaddingChanged = this.settings.connect(
'changed::left-padding',
this._leftPaddingChanged.bind(this)
);
onRightPaddingChanged = this.settings.connect(
'changed::right-padding',
this._rightPaddingChanged.bind(this)
);
// Listen for changes in the toggle feature
onToggleModeChanged = this.settings.connect(
'changed::toggle-window',
this._toggleModeChanged.bind(this)
);
this._toggleModeChanged(); // checks and connects the toggle button
// Create a new layout, add the text and add the actor to the layout
let topBox = new St.BoxLayout();
topBox.add(this.buttonText);
this.actor.add_actor(topBox);
//Place the actor/label at the "end" (rightmost) position within the left box
children = Main.panel._leftBox.get_children();
Main.panel._leftBox.insert_child_at_index(this.actor, children.length)
this._refresh();
},
// Update padding of this.buttonText according to new value set in settings
_leftPaddingChanged: function() {
this.buttonText.set_style("padding-left: " + this.settings.get_int('left-padding') + "px; "
+ "padding-right: " + this.settings.get_int('right-padding') + "px; ");
},
_rightPaddingChanged: function() {
this.buttonText.set_style("padding-left: " + this.settings.get_int('left-padding') + "px; "
+ "padding-right: " + this.settings.get_int('right-padding') + "px; ");
},
// Update labelEventListener if toggle mode changes
_toggleModeChanged: function () {
spotifyWindow = nonSpotifyWindow = null;
if (settings.get_boolean('toggle-window')) {
this.toggleModeID = this.actor.connect('button-press-event', toggleWindow);
} else {
this.actor.disconnect(this.toggleModeID);
}
},
//Defind the refreshing function and set the timeout in seconds
_refresh: function () {
this._loadData(this._refreshUI);
this._removeTimeout();
this._timeout = Mainloop.timeout_add_seconds(this.settings.get_int('refresh-rate'), Lang.bind(this, this._refresh));
return true;
},
_loadData: function () {
let [res, out, err, status] = [];
try {
//Use GLib to send a dbus request with the expectation of receiving an MPRIS v2 response.
[res, out, err, status] = GLib.spawn_command_line_sync("dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:org.mpris.MediaPlayer2.Player string:Metadata");
}
catch(err) {
this._refreshUI("Error. Please check system logs.");
global.log("spotifylabel: res: " + res + " -- status: " + status + " -- err:" + err);
return;
}
var labelstring = parseSpotifyData(out.toString());
this._refreshUI(labelstring);
},
_refreshUI: function (data) {
let txt = data.toString();
this.buttonText.set_text(txt);
},
_removeTimeout: function () {
if (this._timeout) {
Mainloop.source_remove(this._timeout);
this._timeout = null;
}
},
stop: function () {
if (_httpSession !== undefined)
_httpSession.abort();
_httpSession = undefined;
if (this._timeout)
Mainloop.source_remove(this._timeout);
this._timeout = undefined;
this.menu.removeAll();
}
}
);
function init() {
}
function enable() {
// Load schema
gschema = Gio.SettingsSchemaSource.new_from_directory(
Me.dir.get_child('schemas').get_path(),
Gio.SettingsSchemaSource.get_default(),
false
);
// Load settings
settings = new Gio.Settings({
settings_schema: gschema.lookup('org.gnome.shell.extensions.spotifylabel', true)
});
// Mandatory for removing the spMenu from the correct location
this.lastExtensionPlace = settings.get_string('extension-place');
this.lastExtensionIndex = settings.get_int('extension-index');
onExtensionPlaceChanged = this.settings.connect(
'changed::extension-place',
this.onExtensionLocationChanged.bind(this)
);
onExtensionIndexChanged = this.settings.connect(
'changed::extension-index',
this.onExtensionLocationChanged.bind(this)
);
let spotifyWindow, nonSpotifyWindow; // used by the switcher - greyed out in most editors
spMenu = new SpotifyLabel(settings);
Main.panel.addToStatusArea('sp-indicator', spMenu, settings.get_int('extension-index'), settings.get_string('extension-place'));
}
function disable() {
this.settings.disconnect(onLeftPaddingChanged);
this.settings.disconnect(onRightPaddingChanged);
this.settings.disconnect(onExtensionPlaceChanged);
this.settings.disconnect(onExtensionIndexChanged);
this.settings.disconnect(onToggleModeChanged);
spMenu.stop();
spMenu.destroy();
}
// Removes spMenu from correct location and then adds it to new one
function onExtensionLocationChanged (settings, key) {
const newExtensionPlace = this.settings.get_string('extension-place');
const newExtensionIndex = this.settings.get_int('extension-index');
if (this.lastExtensionPlace !== newExtensionPlace
|| this.lastExtensionIndex !== newExtensionIndex) {
switch (this.lastExtensionPlace) {
case 'left':
Main.panel._leftBox.remove_actor(spMenu.container);
break;
case 'center':
Main.panel._centerBox.remove_actor(spMenu.container);
break;
default:
Main.panel._rightBox.remove_actor(spMenu.container);
}
this.lastExtensionPlace = newExtensionPlace;
this.lastExtensionIndex = newExtensionIndex;
switch (newExtensionPlace) {
case 'left':
Main.panel._leftBox.insert_child_at_index(spMenu.container, newExtensionIndex);
break;
case 'center':
Main.panel._centerBox.insert_child_at_index(spMenu.container, newExtensionIndex);
break;
default:
Main.panel._rightBox.insert_child_at_index(spMenu.container, newExtensionIndex);
}
}
}
//Spotify uses MIPRIS v2, and as such the metadata fields are prefixed by 'xesam'
//We use this info to set our limits,and assume the data is properly escaped within quotes.
function parseSpotifyData(data) {
if(!data)
return createGreeting()
var titleBlock = data.substring(data.indexOf("xesam:title"));
var title = titleBlock.split("\"")[2]
var artistBlock = data.substring(data.indexOf("xesam:artist"));
var artist = artistBlock.split("\"")[2]
//If the delimited '-' is in the title, we assume that it's remix, and encapsulate the end in brackets.
if(title.includes("-"))
title = title.replace("- ", "(") + ")";
//If the name of either string is too long, cut off and add '...'
if (artist.length > this.settings.get_int('max-string-length'))
artist = artist.substring(0, this.settings.get_int('max-string-length')) + "...";
if (title.length > this.settings.get_int('max-string-length'))
title = title.substring(0, this.settings.get_int('max-string-length')) + "...";
if (title.includes("xesam") || artist.includes("xesam"))
return "Loading..."
if (this.settings.get_boolean('artist-first')) {
return (artist + " - " + title);
}
return (title + " - " + artist);
}
function toggleWindow() {
if (spotifyWindow && spotifyWindow.has_focus()){ // Spotify is focused
if (nonSpotifyWindow)
Main.activateWindow(nonSpotifyWindow);
// else do nothing
} else { // Spotify not focused, first press, multiple Spotify windows - all cases
nonSpotifyWindow = spotifyWindow = null; // nonSpotifyWindow changes OR another spotifyWindow is active
let windowActors = global.get_window_actors();
for (let windowActor of windowActors) {
if (typeof windowActor.get_meta_window === "function") {
if (windowActor.get_meta_window().get_wm_class() === 'Spotify')
spotifyWindow = windowActor.get_meta_window();
else if (windowActor.get_meta_window().has_focus())
nonSpotifyWindow = windowActor.get_meta_window();
if (spotifyWindow && nonSpotifyWindow) // both found
break;
}
}
Main.activateWindow(spotifyWindow); // switch to Spotify
}
}
let genres = ["DnB", "Synthwave", "Dubstep", "Pop", "House", "Hardstyle", "Rock", "8-bit", "Classical", "Electro"]
let currentGenre = genres[Math.floor(Math.random() * genres.length)];
let genreChanged = false;
function createGreeting() {
if (!this.settings.get_boolean('friendly-greeting'))
return ""
var current_hour = new Date().getHours();
if(new Date().getMinutes() % 5 == 0 && !genreChanged) {
currentGenre = genres[Math.floor(Math.random() * genres.length)];
genreChanged = true;
}
else if(new Date().getMinutes() % 5 != 0)
genreChanged = false;
if (current_hour < 4)
return "A bit of late night coding and " + currentGenre + " music?";
else if (current_hour < 7)
return "You're up early, get at 'em!"
else if (current_hour < 10)
return "Start the day properly with some " + currentGenre + " music?";
else if (current_hour < 12)
return "What's todays soundtrack? A bit of " + currentGenre + "?";
else if (current_hour == 12)
return "" + currentGenre + " music and bit of lunch?";
else if (current_hour < 15)
return "Is that " + currentGenre + " music on the radio? Let's go!";
else if (current_hour < 18)
return "Isn't work progressing nicely with some " + currentGenre + " music?";
else if (current_hour < 21)
return "Free time and " + currentGenre + "? Name a better duo.";
else
return "Can " + currentGenre + " be considered a lullaby? Sure!"
}