summaryrefslogblamecommitdiffstats
path: root/extension.js
blob: e3284e75b8269b7426b3cf780fc47cc1e8a5fa83 (plain) (tree)
1
2
3
4
5
6
7
8
9





                                      

                             
 






                       
 







































                                                                              
 










                                     
     
    
 

                           
 

                                                                       
 
                                                          
 















                                                                        














                                                                            







                                          
























































































                                                                       

                 


                   






















                                                                                               


                    




                                                
 
/* 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 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 text, button, label, client;

const ISS_Above = new GObject.Class({
  Name: 'ISS_Above',
  Extends: GObject.Object,
  Signals: {
    'processed_data': {
      param_types: [
        // GObject.TYPE_INT
      ]
    }
  },

  _init: function(params) {
    this.parent(params);

    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.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));
  },

  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');
      }
    });
  },

  run: function() {
    this.getISScoords(function () {
      print('run: processing new coords');
      this.task_queue = 1;
      this.processResult();
    });
  },

  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() {
}

function enable() {
  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() {
  print('DISABLING ISS Above');
  Main.panel._rightBox.remove_child(button);
  Mainloop.source_remove(client.timeout_source);

  Mainloop.quit();
}