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


                                      
                                   
                             
                          

                             
 






                       
 

                                                          



                                                                              



































                                                                           
 
                   
           
 
                                  

                          
 




                           
     
    
 
                           
                  
 

                                                                       
 
                                                          
 

                                 

                       










                                                                  
                                                               

    
                                       
                                                

                                                                            



                                     
                                    



       
                   
                             
                          
                           
       
                                

    

















                                                                       
     






































                                                                     


                                







                                                                     
                                       

            
                                         

       
               



                                     

                 


                   
                              
                                                        




                                             



                                                     




                                                        

                         
 
                                                   

                                        


                    
                               
                                                   
                                            
 
/* jshint moz: true, multistr: true */
/* global imports */
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const GLib = imports.gi.GLib;
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 = 1000;
const CHECK_INTERVAL = 2 * 60; // how often (in sec) the check for ISS is done
const VISIBLE_TEXT = 'ISS';
const INVISIBLE_TEXT = '___';

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
    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]);
      if (cb_process !== undefined) {
        cb_process();
      }
      else {
        this.emit('processed_data');
      }
    });
  },

  run: function() {
    this.getISScoords(() => {
      this.task_queue = 1;
      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() {
    this.task_queue--;

    if (this.task_queue === 0) {
      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) {
        this.label.text = VISIBLE_TEXT;
      }
      else {
        this.label.text = INVISIBLE_TEXT;
      }
    }
  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 });
  let label = new St.Label({ text: INVISIBLE_TEXT });
  button.child = label;
  button.width = 30;
  button.height = 21;

  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(
    GLib.PRIORITY_DEFAULT,
    CHECK_INTERVAL, () => client.run());
}

function disable() {
  print('DISABLING ISS Above');
  Main.panel._rightBox.remove_child(client.button);
  GLib.source_remove(client.timeout_source);
}