/* jshint moz: true, multistr: true */
/* global imports */
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const GLib = imports.gi.GLib;
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const St = imports.gi.St;
const Main = imports.ui.main;
const AccuracyLevel = {
COUNTRY: 1,
CITY: 4,
NEIGHBORHOOD: 5,
STREET: 6,
EXACT: 8
};
const API_URL = 'http://api.open-notify.org/iss-now.json';
// The minimal distance in km for icon to be made visible
const SOUGHT_DISTANCE = 500;
const CHECK_INTERVAL = 5 * 60; // how often (in sec) the check for ISS is done
const ManagerIface = '<node> \
<interface name="org.freedesktop.GeoClue2.Manager"> \
<method name="GetClient"> \
<arg type="o" name="client" direction="out"/> \
</method> \
</interface> \
</node>';
const ManagerProxy = Gio.DBusProxy.makeProxyWrapper(ManagerIface);
const ClientInterface = '<node> \
<interface name="org.freedesktop.GeoClue2.Client"> \
<property name="Location" type="o" access="read"/> \
<property name="DesktopId" type="s" access="readwrite"/> \
<property name="RequestedAccuracyLevel" type="u" access="readwrite"/> \
<property name="DistanceThreshold" type="u" access="readwrite"/> \
<property name="Active" type="b" access="read"/> \
<method name="Start"/> \
<method name="Stop"/> \
<signal name="LocationUpdated"> \
<arg name="old" type="o"/> \
<arg name="new" type="o"/> \
</signal> \
</interface> \
</node>';
const ClientProxy = Gio.DBusProxy.makeProxyWrapper(ClientInterface);
const LocationInterface = '<node> \
<interface name="org.freedesktop.GeoClue2.Location"> \
<property name="Latitude" type="d" access="read"/> \
<property name="Longitude" type="d" access="read"/> \
<property name="Accuracy" type="d" access="read"/> \
<property name="Description" type="s" access="read"/> \
</interface> \
</node>';
const LocationProxy = Gio.DBusProxy.makeProxyWrapper(LocationInterface);
// global variables
let client;
const ISS_Above = new Lang.Class({
Name: 'ISS_Above',
Extends: GObject.Object,
Signals: {
'processed_data': {
param_types: [
// GObject.TYPE_INT
]
}
},
_init: function(params) {
this.parent();
this.managerProxy = new ManagerProxy(Gio.DBus.system,
'org.freedesktop.GeoClue2', '/org/freedesktop/GeoClue2/Manager');
[this.clientAddr] = this.managerProxy.GetClientSync();
this.current_location = null;
this.iss_coords = null;
this.button = null;
this.label = null;
this.task_queue = 2; // number of async process to complete
print('_init, task_queue = ' + this.task_queue);
this.timeout_source = null;
this.clientProxy = new ClientProxy(Gio.DBus.system,
'org.freedesktop.GeoClue2', this.clientAddr);
this.clientProxy.DesktopId = 'gnome-shell-ISS_Above';
this.clientProxy.DistanceThreshold = 10000;
this.clientProxy.RequestedAccuracyLevel = AccuracyLevel.EXACT;
this.clientProxy.connectSignal('LocationUpdated',
Lang.bind(this, this.onLocationUpdated));
this.clientProxy.StartRemote();
this.connect('processed_data', () => this.processResult());
},
getISScoords: function (cb_process) {
let iss_api = Gio.file_new_for_uri(API_URL);
iss_api.load_contents_async(null, (iss_api, result) => {
this.iss_coords = JSON.parse(iss_api.load_contents_finish(result)[1]);
print('this.iss_coords = ' + this.iss_coords.toSource());
print('cb_process = ' + cb_process);
if (cb_process !== undefined) {
cb_process();
}
else {
print('emitting "processed_data" signal');
this.emit('processed_data');
}
});
},
run: function() {
this.getISScoords(() => {
print('run: processing new coords');
this.task_queue = 1;
print('run, task_queue = ' + this.task_queue);
this.processResult();
});
return GLib.SOURCE_CONTINUE;
},
onLocationUpdated: function (proxy, sender, [oldPath, newPath]) {
let geoclueLocation = new LocationProxy(Gio.DBus.system,
"org.freedesktop.GeoClue2",
newPath);
// Yes, I take both null and undefined
if (geoclueLocation.Latitude != null) {
this.current_location = {
Latitude: geoclueLocation.Latitude,
Longitude: geoclueLocation.Longitude,
Accuracy: geoclueLocation.Accuracy,
Description: geoclueLocation.Description
};
}
this.emit('processed_data');
},
/**
* Calculate distance in km of two geographical points
*
* @param lat1 Number latitude of the first point
* @param long1 Number longitude of the first point
* @param lat2 Number latitude of the second point
* @param long2 Number longitude of the second point
* @return Number distance in km
*
* Described on https://en.wikipedia.org/wiki/Haversine_formula
*/
get_distance: function(lat1, long1, lat2, long2) {
// Mean radius of the Earth in km
const EARTH_R = 6371;
const PI_180 = Math.PI / 180;
function haversin(fi) {
return (1 - Math.cos(fi)) / 2;
}
lat1 = lat1 * PI_180;
long1 = long1 * PI_180;
lat2 = lat2 * PI_180;
long2 = long2 * PI_180;
let dist = 2 * EARTH_R *
Math.asin(Math.sqrt(haversin(lat2 - lat1) +
Math.cos(lat1) * Math.cos(lat2) * haversin(long2 - long1)));
let test_haversin = haversin(dist / EARTH_R);
if (test_haversin < 1) {
return dist.toFixed(3);
}
else {
throw new Error('haversine(d/r) cannot be over 1, but it is ' +
test_haversin);
}
},
processResult: function() {
print('processResult, start: task_queue = ' + this.task_queue);
this.task_queue--;
print('processResult, deciding: task_queue = ' + this.task_queue);
if (this.task_queue === 0) {
print('processResult: our location: ' +
this.current_location.toSource());
print('processResult: iss coordinates: ' +
this.iss_coords.toSource());
let dist = this.get_distance(this.current_location.Latitude,
this.current_location.Longitude,
this.iss_coords.iss_position.latitude,
this.iss_coords.iss_position.longitude);
print('ISS is ' + dist + ' km away.');
// https://developer.gnome.org/clutter/stable/ClutterActor.html
if (dist < SOUGHT_DISTANCE) {
// Make the label text completely opaque
// this.label.set_opacity(255);
this.label.set_background_color(
Clutter.Color.get_static(Clutter.StaticColor.RED));
print('Make label opaque');
}
else {
// Make the label text completely transparent
// this.label.set_opacity(0);
this.label.set_background_color(
Clutter.Color.get_static(Clutter.StaticColor.GREEN));
print('Make label transparent');
}
}
return false;
}
});
// END of ISS_Above object definition
function init() {
}
function enable() {
print('ENABLING ISS Above');
let button = new St.Bin({ style_class: 'panel-button',
reactive: true,
can_focus: true,
x_fill: true,
y_fill: false,
track_hover: true });
// http://blog.fpmurphy.com/2011/04/replace-gnome-shell-activities-text-string-with-icon.html
let label = new St.Label({ text: 'ISS' });
print('button is ' + button);
button.set_child(label);
label.set_opacity(0);
Main.panel._rightBox.insert_child_at_index(button, 0);
client = new ISS_Above();
client.getISScoords();
client.button = button;
client.label = label;
client.timeout_source = GLib.timeout_add_seconds(
CHECK_INTERVAL, () => client.run());
}
function disable() {
print('DISABLING ISS Above');
Main.panel._rightBox.remove_child(client.button);
GLib.Source.destroy(client.timeout_source);
}