diff options
Diffstat (limited to 'extension.js')
-rw-r--r-- | extension.js | 260 |
1 files changed, 226 insertions, 34 deletions
diff --git a/extension.js b/extension.js index 260aef7..bc89de1 100644 --- a/extension.js +++ b/extension.js @@ -1,53 +1,245 @@ - +/* jshint moz: true, multistr: true */ +/* global imports */ +const Gio = imports.gi.Gio; +const Mainloop = imports.mainloop; +const GObject = imports.gi.GObject; +const Lang = imports.lang; const St = imports.gi.St; const Main = imports.ui.main; -const Tweener = imports.ui.tweener; -let text, button; +const AccuracyLevel = { + COUNTRY: 1, + CITY: 4, + NEIGHBORHOOD: 5, + STREET: 6, + EXACT: 8 +}; -function _hideHello() { - Main.uiGroup.remove_actor(text); - text = null; -} +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); -function _showHello() { - if (!text) { - text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" }); - Main.uiGroup.add_actor(text); +// global variables +let text, button, label, client; + +const ISS_Above = new GObject.Class({ + Name: 'ISS_Above', + Extends: GObject.Object, + Signals: { + 'processed_data': { + param_types: [ + // GObject.TYPE_INT + ] } + }, - text.opacity = 255; + _init: function(params) { + this.parent(params); - let monitor = Main.layoutManager.primaryMonitor; + this.managerProxy = new ManagerProxy(Gio.DBus.system, + 'org.freedesktop.GeoClue2', '/org/freedesktop/GeoClue2/Manager'); - text.set_position(monitor.x + Math.floor(monitor.width / 2 - text.width / 2), - monitor.y + Math.floor(monitor.height / 2 - text.height / 2)); + [this.clientAddr] = this.managerProxy.GetClientSync(); - Tweener.addTween(text, - { opacity: 0, - time: 2, - transition: 'easeOutQuad', - onComplete: _hideHello }); -} + this.current_location = null; + this.iss_coords = null; + this.task_queue = 2; // number of async process to complete + 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', Lang.bind(this, this.processResult)); + }, + + run: function() { + this.getISScoords(function () { + print('run: processing new coords'); + this.task_queue = 1; + this.processResult(); + }); + }, + + getISScoords: function (cb_process) { + let self = this; + let iss_api = Gio.file_new_for_uri(API_URL); + iss_api.load_contents_async(null, function(iss_api, result) { + self.iss_coords = JSON.parse(iss_api.load_contents_finish(result)[1]); + print('self.iss_coords = ' + self.iss_coords.toSource()); + if (cb_process !== undefined) { + cb_process(); + } + else { + self.emit('processed_data'); + } + }); + }, + + 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--; + + 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 + label.set_opacity(255); + print('Make label opaque'); + } + else { + // Make the label text completely transparent + label.set_opacity(0); + print('Make label transparent'); + } + } + } +}); + +// END of ISS_Above object definition function init() { - button = new St.Bin({ style_class: 'panel-button', - reactive: true, - can_focus: true, - x_fill: true, - y_fill: false, - track_hover: true }); - let icon = new St.Icon({ icon_name: 'system-run-symbolic', - style_class: 'system-status-icon' }); - - button.set_child(icon); - button.connect('button-press-event', _showHello); } function enable() { - Main.panel._rightBox.insert_child_at_index(button, 0); + print('ENABLING ISS Above'); + 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 + 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.timeout_source = Mainloop.timeout_add_seconds( + CHECK_INTERVAL, client.run); + + Mainloop.run(); } function disable() { - Main.panel._rightBox.remove_child(button); + print('DISABLING ISS Above'); + Main.panel._rightBox.remove_child(button); + Mainloop.source_remove(client.timeout_source); + + Mainloop.quit(); } |