-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathextension.js
executable file
·331 lines (291 loc) · 12.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
321
322
323
324
325
326
327
328
329
330
331
const Me = imports.misc.extensionUtils.getCurrentExtension();
// Import utils from another file
const ExtensionUtils = imports.misc.extensionUtils;
const Meta = ExtensionUtils.getCurrentExtension();
const Utils = Meta.imports.utils;
// For the GET Requests
const Soup = imports.gi.Soup;
/* Import St because is the library that allow you to create UI elements */
const St = imports.gi.St;
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
// MainLoop for updating the time every X seconds.
const Mainloop = imports.mainloop;
/*
Import Main because is the instance of the class that have all the UI elements
and we have to add to the Main instance our UI elements
*/
const Main = imports.ui.main;
/* Import tweener to do the animations of the UI elements */
// const Tweener = imports.ui.tweener;
const Tweener = imports.tweener.tweener;
let BASE_URL = "http://192.168.0.105:8080"; // Raspberry-Pi server URL
/*
TV-Power-Switch Variables
*/
/* Global variables for use as button to click (button) and a text label. */
let text, button, tvStatusText, tvSwitchURL;
let icon;
let currentTvStatus;
const PlayTV = 'tv-play-blue.png'; //'tv-play.svg';
const PauseTV = 'tv-shut-blue.png';
let new_icon;
let refreshTimeout;
/*
Weather-Related Variables / Endpoints
*/
const weatherStatsURL = `${BASE_URL}/api/livingroom/get-weather-stats/`; // URL for both the temp and humidity
const tvStatusURL = `${BASE_URL}/get-tv-status/`
let currentStats; // a dictionary with 2 keys (temperature and humidity).
let weatherStatsPanel;
let weatherStatsPanelText;
/*
Function to call when the label is opacity 0%, as the label remains as a
UI element, but not visible, we have to delete it explicitily. So since
the label reaches 0% of opacity we remove it from Main instance.
*/
function _hideText() {
Main.uiGroup.remove_actor(text);
text = null;
}
/*
=====================================================================================
==================================== REFRESHERS =====================================
=====================================================================================
*/
function _refreshWeatherStats() {
_getWeatherStats();
try {
const temperature = currentStats.temperature;
const humidity = currentStats.humidity;
weatherStatsPanelText.text = `${temperature}°C|${humidity}%`;
} catch (error) {
logError(error);
}
return false; // will execute this function only once and abort.
}
function _refreshTVStatus() {
/*
The purpose of this function is to refresh the icons and the text
for the current tv status.
This is needed because the tv status can change from more than 5 different
sources and we have to sync all of these.
The above is achieved by pinging the /api/check-tv-status-changed/ endpoint of my
rpi. This will return true if the status has changed and false otherwise. If true,
we are going to set the new paths accordingly and update the icon.
*/
currentTvStatus = Utils.getCurrentTvStatus(tvStatusURL); // this changes both new_icon and tv_is_open
if(currentTvStatus !== -1) { // if no error occurred
_setTvPaths();
} else {
log("tv-switch-gnome-shell-extension: getCurrentTvStatus returned an invalid code...");
disable();
throw new Error("tv-switch-gnome-shell-extension: getCurrentTvStatus returned an invalid code...");
}
let tvStatusChanged = Utils.sendRequest(`${BASE_URL}/api/check-tv-status-changed/${currentTvStatus}/`, 'GET');
if(tvStatusChanged === false){ // means an error occurred in sendRequest
return true; // do not change anything
}
// Check if the status has changed (otherwise there is no need to re-write the data)
if (tvStatusChanged.changed) {
log("CHANGE DETECTED...")
// update the icon
currentTvStatus = Number(tvStatusChanged.current_status);
_setTvPaths(); // with the new status
// Update icon
icon.gicon = Gio.icon_new_for_string(`${Me.path}/icons/${new_icon}`);
// Send a notification only when someone else opened/closed the tv
// Maybe check this if you want to include more:
// https://stackoverflow.com/questions/32923811/set-notification-icon-in-gnome-shell-3-16-custom-extension
Main.notify(_("TV " + (currentTvStatus===1 ? "OPENED" : "CLOSED")));
}
return true; // in order to run the function forever (resource intensive..)
}
/*
=====================================================================================
=========================== WEATHER STATION (EXPERIMENTAL) ==========================
=====================================================================================
*/
function _getWeatherStats() {
/*
Returns a JS Object with temp (temperature) and humidity as retrieved from the server.
This hasn't been tested yet since the corresponding endpoints are not live.
*/
currentStats = Utils.sendRequest(weatherStatsURL, 'GET');
if(currentStats === false){
currentStats = {
temperature: '-',
humidity: '-'
};
} else if (currentStats === -1) {
log("tv-switch-gnome-shell-extension: sendRequest returned an invalid code for updating the weather statistics...");
disable();
throw new Error("tv-switch-gnome-shell-extension: sendRequest returned an invalid code for updating the weather statistics...");
}
// else {
// currentStats = {
// temperature: 0,
// humidity: 0
// }
// }
log(`Current Weather Stats: ${currentStats.temperature}C|${currentStats.humidity}%`)
}
/*
=====================================================================================
================================= CHANGE SPEC PATHS =================================
=====================================================================================
*/
function _setTvPaths() {
if (currentTvStatus === 1) { // the tv is open
new_icon = PauseTV; // so that the icon in the top bar will shut the TV
tvStatusText="TV Status: ON";
tvSwitchURL=`${BASE_URL}/api/livingroom/turn-off-tv/`; // turn-on-led is the endpoint for turning on the relay
} else {
new_icon = PlayTV; // to open the TV
tvStatusText="TV Status: OFF";
tvSwitchURL=`${BASE_URL}/api/livingroom/turn-on-tv/`; // turn-on-led is the endpoint for turning on the relay
}
}
/*
=====================================================================================
=============================== SET THE CURRENT STATUS ==============================
=====================================================================================
*/
// main handler
async function _changeStatus() {
// Handle request
// let urlStatusData = {currentStatus: 1};
let urlStatusData = Utils.sendRequest(tvSwitchURL, 'GET');
if (urlStatusData === false) {
tvStatusText = "Failed";
} else if (urlStatusData === -1) {
log("tv-switch-gnome-shell-extension: sendRequest returned an invalid code...");
disable();
throw new Error("tv-switch-gnome-shell-extension: sendRequest returned an invalid code...");
} else {
currentTvStatus = urlStatusData.currentStatus;
// Change icon/text/url based on the request
_setTvPaths();
}
/*
If text not already present, we create a new UI element, using ST library, that allows us
to create UI elements of gnome-shell.
*/
if (!text) {
text = new St.Label({
style_class: 'tv-status-label',
text: `${tvStatusText}`
});
Main.uiGroup.add_actor(text);
}
text.opacity = 255;
/*
We have to choose the monitor we want to display the hello world label. Since in gnome-shell
always has a primary monitor, we use it(the main monitor)
*/
let monitor = Main.layoutManager.primaryMonitor;
/*
We change the position of the text to the center of the monitor.
*/
text.set_position(monitor.x + Math.floor(monitor.width / 2 - text.width / 2),
monitor.y + Math.floor(monitor.height / 2 - text.height / 2));
// Update icon
icon.gicon = Gio.icon_new_for_string(`${Me.path}/icons/${new_icon}`);
/*
And using tweener for the animations, we indicate to tweener that we want
to go to opacity 0%, in 2 seconds, with the type of transition easeOutQuad, and,
when this animation has completed, we execute our function _hideText.
*/
await Tweener.addTween(text, {
opacity: 0,
time: 2,
transition: 'easeOutQuad',
onComplete: _hideText
});
}
/*
This is the init function, here we have to put our code to initialize our extension.
we have to be careful with init(), enable() and disable() and do the right things here.
*/
function init() {
// Add the tv state/switch button in the panel
button = new St.Bin({ style_class: 'panel-button',
reactive: true,
can_focus: true,
x_expand: true,
y_expand: false,
track_hover: true });
// Add the temperature in the panel
weatherStatsPanel = new St.Bin({
style_class : "panel-button",
reactive : true,
can_focus : true,
track_hover : true,
height : 30,
});
}
/*
We have to write here our main extension code and the things that actually make works the extension(Add ui elements, signals, etc).
*/
function enable() {
/**
* ===== TV Station Area ======
*/
// We create an icon with the system-status-icon icon and give it the name "system-run"
currentTvStatus = Utils.getCurrentTvStatus(tvStatusURL); // this changes both new_icon and tv_is_open
if(currentTvStatus !== -1) { // if no error occurred
_setTvPaths();
} else {
log("tv-switch-gnome-shell-extension: getCurrentTvStatus returned an invalid code...");
disable();
throw new Error("tv-switch-gnome-shell-extension: getCurrentTvStatus returned an invalid code...");
}
icon = new St.Icon({ style_class: 'system-status-icon' });
// TODO: Get current tv-switch status and show the corresponding image
// log("GOT NEW ICON PATH: " + new_icon);
icon.gicon = Gio.icon_new_for_string(`${Me.path}/icons/${new_icon}`);
/*
We put as a child of the button the icon, so, in the structure of actors we have the
icon inside the button that is a container.
*/
button.set_child(icon);
/*
We connect the actor signal "button-press-event" from the button to the funcion _changeStatus. In this manner,
when we press the button, this signal is emitted, and we captured it and execute the _changeStatus function.
You can see all signals in the clutter reference(because we are using St that implements actors from clutter, and
this signals comes from the actor class)
*/
button.connect('button-press-event', _changeStatus);
Main.panel._rightBox.insert_child_at_index(button, 0);
/**
* ===== Weather Area ======
*/
weatherStatsPanelText = new St.Label({
text : "-°C",
y_align: Clutter.ActorAlign.CENTER,
});
_refreshWeatherStats();
weatherStatsPanel.set_child(weatherStatsPanelText);
weatherStatsPanel.connect("button-press-event", () => {
_refreshWeatherStats();
});
// Change the tv status and the weather stats every X seconds (check function for comments)
refreshTimeout = Mainloop.timeout_add_seconds(10, () => {
_refreshTVStatus();
_refreshWeatherStats();
}
);
Main.panel._rightBox.insert_child_at_index(weatherStatsPanel, 1);
}
/*
We have to delete all conections and things from our extensions, to let the system how it is before our extension. So
We have to unconnect the signals we connect, we have to delete all UI elements we created, etc.
*/
function disable() {
// We remove the button from the right panel
Main.panel._rightBox.remove_child(button);
Main.panel._rightBox.remove_child(weatherStatsPanel);
// TODO: Not sure if the timeout_add_seconds function stops refresing when disable is called. Check it.
// remove mainloop
Mainloop.source_remove(refreshTimeout);
}