/* 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 = ' \ \ \ \ \ \ '; const ManagerProxy = Gio.DBusProxy.makeProxyWrapper(ManagerIface); const ClientInterface = ' \ \ \ \ \ \ \ \ \ \ \ \ \ \ '; const ClientProxy = Gio.DBusProxy.makeProxyWrapper(ClientInterface); const LocationInterface = ' \ \ \ \ \ \ \ '; 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); }