Compare commits
	
		
			2 Commits
		
	
	
		
			gnome-3-22
			...
			wip/loc-ap
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 11db28b216 | ||
|   | 614f37b116 | 
| @@ -7,12 +7,17 @@ const Lang = imports.lang; | |||||||
| const Main = imports.ui.main; | const Main = imports.ui.main; | ||||||
| const PanelMenu = imports.ui.panelMenu; | const PanelMenu = imports.ui.panelMenu; | ||||||
| const PopupMenu = imports.ui.popupMenu; | const PopupMenu = imports.ui.popupMenu; | ||||||
|  | const ModalDialog = imports.ui.modalDialog; | ||||||
| const Shell = imports.gi.Shell; | const Shell = imports.gi.Shell; | ||||||
|  | const St = imports.gi.St; | ||||||
|  |  | ||||||
| const LOCATION_SCHEMA = 'org.gnome.system.location'; | const LOCATION_SCHEMA = 'org.gnome.system.location'; | ||||||
| const MAX_ACCURACY_LEVEL = 'max-accuracy-level'; | const MAX_ACCURACY_LEVEL = 'max-accuracy-level'; | ||||||
| const ENABLED = 'enabled'; | const ENABLED = 'enabled'; | ||||||
|  |  | ||||||
|  | const APP_PERMISSIONS_TABLE = 'desktop'; | ||||||
|  | const APP_PERMISSIONS_ID = 'geolocation'; | ||||||
|  |  | ||||||
| const GeoclueAccuracyLevel = { | const GeoclueAccuracyLevel = { | ||||||
|     NONE: 0, |     NONE: 0, | ||||||
|     COUNTRY: 1, |     COUNTRY: 1, | ||||||
| @@ -46,6 +51,26 @@ var AgentIface = '<node> \ | |||||||
|   </interface> \ |   </interface> \ | ||||||
| </node>'; | </node>'; | ||||||
|  |  | ||||||
|  | var XdgAppIface = '<node> \ | ||||||
|  |   <interface name="org.freedesktop.XdgApp.PermissionStore"> \ | ||||||
|  |     <method name="Lookup"> \ | ||||||
|  |       <arg name="table" type="s" direction="in"/> \ | ||||||
|  |       <arg name="id" type="s" direction="in"/> \ | ||||||
|  |       <arg name="permissions" type="a{sas}" direction="out"/> \ | ||||||
|  |       <arg name="data" type="v" direction="out"/> \ | ||||||
|  |     </method> \ | ||||||
|  |     <method name="Set"> \ | ||||||
|  |       <arg name="table" type="s" direction="in"/> \ | ||||||
|  |       <arg name="create" type="b" direction="in"/> \ | ||||||
|  |       <arg name="id" type="s" direction="in"/> \ | ||||||
|  |       <arg name="app_permissions" type="a{sas}" direction="in"/> \ | ||||||
|  |       <arg name="data" type="v" direction="in"/> \ | ||||||
|  |     </method> \ | ||||||
|  |   </interface> \ | ||||||
|  | </node>'; | ||||||
|  |  | ||||||
|  | const PermissionStore = Gio.DBusProxy.makeProxyWrapper(XdgAppIface); | ||||||
|  |  | ||||||
| const Indicator = new Lang.Class({ | const Indicator = new Lang.Class({ | ||||||
|     Name: 'LocationIndicator', |     Name: 'LocationIndicator', | ||||||
|     Extends: PanelMenu.SystemIndicator, |     Extends: PanelMenu.SystemIndicator, | ||||||
| @@ -83,64 +108,200 @@ const Indicator = new Lang.Class({ | |||||||
|         this._onSessionUpdated(); |         this._onSessionUpdated(); | ||||||
|         this._onMaxAccuracyLevelChanged(); |         this._onMaxAccuracyLevelChanged(); | ||||||
|         this._connectToGeoclue(); |         this._connectToGeoclue(); | ||||||
|  |         this._connectToPermissionStore(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     get MaxAccuracyLevel() { |     get MaxAccuracyLevel() { | ||||||
|         return this._getMaxAccuracyLevel(); |         return this._getMaxAccuracyLevel(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // We (and geoclue) have currently no way to reliably identifying apps so |     AuthorizeAppAsync: function(params, invocation) { | ||||||
|     // for now, lets just authorize all apps as long as they provide a valid |         let [desktop_id, reqAccuracyLevel] = params; | ||||||
|     // desktop ID. We also ensure they don't get more accuracy than global max. |         log("%s is requesting location".format(desktop_id)); | ||||||
|     AuthorizeApp: function(desktop_id, reqAccuracyLevel) { |  | ||||||
|  |         let callback = function(level, timesAllowed) { | ||||||
|  |             if (level >= GeoclueAccuracyLevel.NONE && timesAllowed > 2) { | ||||||
|  |                 log("%s is in store".format(desktop_id)); | ||||||
|  |                 let accuracyLevel = clamp(reqAccuracyLevel, 0, level); | ||||||
|  |                 this._completeAuthorizeApp(desktop_id, | ||||||
|  |                                            accuracyLevel, | ||||||
|  |                                            timesAllowed, | ||||||
|  |                                            invocation); | ||||||
|  |             } else { | ||||||
|  |                 log("%s not in store".format(desktop_id)); | ||||||
|  |                 this._userAuthorizeApp(desktop_id, | ||||||
|  |                                        reqAccuracyLevel, | ||||||
|  |                                        timesAllowed, | ||||||
|  |                                        invocation); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         this._fetchPermissionFromStore(desktop_id, Lang.bind(this, callback); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     _userAuthorizeApp: function(desktopId, reqAccuracyLevel, timesAllowed, invocation) { | ||||||
|         var appSystem = Shell.AppSystem.get_default(); |         var appSystem = Shell.AppSystem.get_default(); | ||||||
|         var app = appSystem.lookup_app(desktop_id + ".desktop"); |         var app = appSystem.lookup_app(desktopId + ".desktop"); | ||||||
|         if (app == null) { |         if (app == null) { | ||||||
|             return [false, 0]; |             this._completeAuthorizeApp(desktopId, | ||||||
|  |                                        GeoclueAccuracyLevel.NONE, | ||||||
|  |                                        timesAllowed, | ||||||
|  |                                        invocation); | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         let allowedAccuracyLevel = clamp(reqAccuracyLevel, 0, this._getMaxAccuracyLevel()); |         var name = app.get_name(); | ||||||
|         return [true, allowedAccuracyLevel]; |         var icon = app.get_app_info().get_icon(); | ||||||
|  |         var reason = app.get_string("X-Geoclue-Reason"); | ||||||
|  |         var allowCallback = function() { | ||||||
|  |             this._completeAuthorizeApp(desktopId, | ||||||
|  |                                        reqAccuracyLevel, | ||||||
|  |                                        timesAllowed, | ||||||
|  |                                        invocation); | ||||||
|  |         }; | ||||||
|  |         var denyCallback = function() { | ||||||
|  |             this._completeAuthorizeApp(desktopId, | ||||||
|  |                                        GeoclueAccuracyLevel.NONE, | ||||||
|  |                                        timesAllowed, | ||||||
|  |                                        invocation); | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         this._showAppAuthDialog(name, | ||||||
|  |                                 reason, | ||||||
|  |                                 icon, | ||||||
|  |                                 Lang.bind(this, allowCallback), | ||||||
|  |                                 Lang.bind(this, denyCallback)); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     _showAppAuthDialog: function(name, reason, icon, allowCallback, denyCallback) { | ||||||
|  |         if (this._dialog == null) | ||||||
|  |             this._dialog = new GeolocationDialog(name, reason, icon); | ||||||
|  |         else | ||||||
|  |             this._dialog.update(name, reason, icon); | ||||||
|  |  | ||||||
|  |         let closedId = this._dialog.connect('closed', function() { | ||||||
|  |             this._dialog.disconnect(closedId); | ||||||
|  |             if (this._dialog.allowed) | ||||||
|  |                 allowCallback (); | ||||||
|  |             else | ||||||
|  |                 denyCallback (); | ||||||
|  |         }.bind(this)); | ||||||
|  |  | ||||||
|  |         this._dialog.open(global.get_current_time ()); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     _completeAuthorizeApp: function(desktopId, | ||||||
|  |                                     accuracyLevel, | ||||||
|  |                                     timesAllowed, | ||||||
|  |                                     invocation) { | ||||||
|  |         if (accuracyLevel == GeoclueAccuracyLevel.NONE) { | ||||||
|  |             invocation.return_value(GLib.Variant.new('(bu)', | ||||||
|  |                                                      [false, accuracyLevel])); | ||||||
|  |             this._saveToPermissionStore(desktopId, accuracyLevel, 0); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let allowedAccuracyLevel = clamp(accuracyLevel, | ||||||
|  |                                          0, | ||||||
|  |                                          this._getMaxAccuracyLevel()); | ||||||
|  |         invocation.return_value(GLib.Variant.new('(bu)', | ||||||
|  |                                                  [true, allowedAccuracyLevel])); | ||||||
|  |  | ||||||
|  |         this._saveToPermissionStore(desktopId, allowedAccuracyLevel, timesAllowed + 1); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     _fetchPermissionFromStore: function(desktopId, callback) { | ||||||
|  |         if (this._permStoreProxy == null) { | ||||||
|  |             callback (-1, 0); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this._permStoreProxy.LookupRemote(APP_PERMISSIONS_TABLE, | ||||||
|  |                                           APP_PERMISSIONS_ID, | ||||||
|  |                                           function(result, error) { | ||||||
|  |             if (error != null) { | ||||||
|  |                 log(error.message); | ||||||
|  |                 callback(-1, 0); | ||||||
|  |  | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             let [permissions, data] = result; | ||||||
|  |             let permission = permissions[desktopId]; | ||||||
|  |             if (permission == null) { | ||||||
|  |                 callback(-1, 0); | ||||||
|  |  | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             let levelStr = permission[0]; | ||||||
|  |             let level = GeoclueAccuracyLevel[levelStr.toUpperCase()] || | ||||||
|  |                         GeoclueAccuracyLevel.NONE; | ||||||
|  |             let timesAllowed = data.get_byte(); | ||||||
|  |  | ||||||
|  |             callback(level, timesAllowed); | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     _saveToPermissionStore: function(desktopId, | ||||||
|  |                                      allowedAccuracyLevel, | ||||||
|  |                                      timesAllowed) { | ||||||
|  |         if (timesAllowed > 2 || this._permStoreProxy == null) | ||||||
|  |             return; | ||||||
|  |  | ||||||
|  |         let levelStr = Object.keys(GeoclueAccuracyLevel)[allowedAccuracyLevel];  | ||||||
|  |         let permission = { desktopId: [levelStr] }; | ||||||
|  |         let permissions = GLib.Variant.new('a{sas}', [permission])); | ||||||
|  |         let data = GLib.Variant.new('y', timesAllowed); | ||||||
|  |  | ||||||
|  |         this._permStoreProxy.SetRemote(APP_PERMISSIONS_TABLE, | ||||||
|  |                                        true, | ||||||
|  |                                        APP_PERMISSIONS_ID, | ||||||
|  |                                        permissions, | ||||||
|  |                                        data, | ||||||
|  |                                        function (result, error) { | ||||||
|  |             log(error.message); | ||||||
|  |         }); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _syncIndicator: function() { |     _syncIndicator: function() { | ||||||
|         if (this._proxy == null) { |         if (this._managerProxy == null) { | ||||||
|             this._indicator.visible = false; |             this._indicator.visible = false; | ||||||
|             this._item.actor.visible = false; |             this._item.actor.visible = false; | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this._indicator.visible = this._proxy.InUse; |         this._indicator.visible = this._managerProxy.InUse; | ||||||
|         this._item.actor.visible = this._indicator.visible; |         this._item.actor.visible = this._indicator.visible; | ||||||
|         this._updateMenuLabels(); |         this._updateMenuLabels(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _connectToGeoclue: function() { |     _connectToGeoclue: function() { | ||||||
|         if (this._proxy != null || this._connecting) |         if (this._managerProxy != null || this._connecting) | ||||||
|             return false; |             return false; | ||||||
|  |  | ||||||
|         this._connecting = true; |         this._connecting = true; | ||||||
|         new GeoclueManager(Gio.DBus.system, |         new GeoclueManager(Gio.DBus.system, | ||||||
|                            'org.freedesktop.GeoClue2', |                            'org.freedesktop.GeoClue2', | ||||||
|                            '/org/freedesktop/GeoClue2/Manager', |                            '/org/freedesktop/GeoClue2/Manager', | ||||||
|                            Lang.bind(this, this._onProxyReady)); |                            Lang.bind(this, this._onManagerProxyReady)); | ||||||
|         return true; |         return true; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _onProxyReady: function(proxy, error) { |     _onManagerProxyReady: function(proxy, error) { | ||||||
|         if (error != null) { |         if (error != null) { | ||||||
|             log(error.message); |             log(error.message); | ||||||
|             this._connecting = false; |             this._connecting = false; | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this._proxy = proxy; |         this._managerProxy = proxy; | ||||||
|         this._propertiesChangedId = this._proxy.connect('g-properties-changed', |         this._propertiesChangedId = this._managerProxy.connect('g-properties-changed', | ||||||
|                                                         Lang.bind(this, this._onGeocluePropsChanged)); |                                                         Lang.bind(this, this._onGeocluePropsChanged)); | ||||||
|  |  | ||||||
|         this._syncIndicator(); |         this._syncIndicator(); | ||||||
|  |  | ||||||
|         this._proxy.AddAgentRemote('gnome-shell', Lang.bind(this, this._onAgentRegistered)); |         this._managerProxy.AddAgentRemote('gnome-shell', Lang.bind(this, this._onAgentRegistered)); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _onAgentRegistered: function(result, error) { |     _onAgentRegistered: function(result, error) { | ||||||
| @@ -153,10 +314,10 @@ const Indicator = new Lang.Class({ | |||||||
|  |  | ||||||
|     _onGeoclueVanished: function() { |     _onGeoclueVanished: function() { | ||||||
|         if (this._propertiesChangedId) { |         if (this._propertiesChangedId) { | ||||||
|             this._proxy.disconnect(this._propertiesChangedId); |             this._managerProxy.disconnect(this._propertiesChangedId); | ||||||
|             this._propertiesChangedId = 0; |             this._propertiesChangedId = 0; | ||||||
|         } |         } | ||||||
|         this._proxy = null; |         this._managerProxy = null; | ||||||
|  |  | ||||||
|         this._syncIndicator(); |         this._syncIndicator(); | ||||||
|     }, |     }, | ||||||
| @@ -211,9 +372,85 @@ const Indicator = new Lang.Class({ | |||||||
|         let unpacked = properties.deep_unpack(); |         let unpacked = properties.deep_unpack(); | ||||||
|         if ("InUse" in unpacked) |         if ("InUse" in unpacked) | ||||||
|             this._syncIndicator(); |             this._syncIndicator(); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     _connectToPermissionStore: function() { | ||||||
|  |         this._permStoreProxy = null; | ||||||
|  |         new PermissionStore(Gio.DBus.session, | ||||||
|  |                            'org.freedesktop.XdgApp', | ||||||
|  |                            '/org/freedesktop/XdgApp/PermissionStore', | ||||||
|  |                            Lang.bind(this, this._onPermStoreProxyReady)); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     _onPermStoreProxyReady: function(proxy, error) { | ||||||
|  |         if (error != null) { | ||||||
|  |             log(error.message); | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         this._permStoreProxy = proxy; | ||||||
|  |     }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| function clamp(value, min, max) { | function clamp(value, min, max) { | ||||||
|     return Math.max(min, Math.min(max, value)); |     return Math.max(min, Math.min(max, value)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | const GeolocationDialog = new Lang.Class({ | ||||||
|  |     Name: 'GeolocationDialog', | ||||||
|  |     Extends: ModalDialog.ModalDialog, | ||||||
|  |  | ||||||
|  |     // FIXME: Would be nice to show the application icon too | ||||||
|  |     _init: function(name, reason, icon) { | ||||||
|  |         this.parent({ destroyOnClose: false }); | ||||||
|  |  | ||||||
|  |         let text = _("'%s' is requesting access to location data.").format (name); | ||||||
|  |         this._label = new St.Label({ style_class: 'prompt-dialog-description', | ||||||
|  |                                      text: text }); | ||||||
|  |  | ||||||
|  |         this.contentLayout.add(this._label, {}); | ||||||
|  |  | ||||||
|  |         if (reason != null) { | ||||||
|  |             this._reasonLabel = new St.Label({ style_class: 'prompt-dialog-description', | ||||||
|  |                                                text: reason }); | ||||||
|  |             this.contentLayout.add(this._reasonLabel, {}); | ||||||
|  |         } else { | ||||||
|  |             this._reasonLabel = null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this._allowButton = this.addButton({ label: _("Confirm"), | ||||||
|  |                                              action: this._onAllowClicked.bind(this), | ||||||
|  |                                              default: false }, | ||||||
|  |                                            { expand: true, x_fill: false, x_align: St.Align.END }); | ||||||
|  |         this._denyButton = this.addButton({ label: _("Cancel"), | ||||||
|  |                                             action: this._onDisallowClicked.bind(this), | ||||||
|  |                                             default: true }, | ||||||
|  |                                           { expand: true, x_fill: false, x_align: St.Align.START }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     update: function(name, reason, icon) { | ||||||
|  |         let text = _("'%s' is requesting access to location data.").format (name); | ||||||
|  |         this._label.text = text; | ||||||
|  |  | ||||||
|  |         if (this._reasonLabel != null) { | ||||||
|  |             this.contentLayout.remove(this._reasonLabel, {}); | ||||||
|  |             this._reasonLabel = null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (reason != null) { | ||||||
|  |             this._reasonLabel = new St.Label({ style_class: 'prompt-dialog-description', | ||||||
|  |                                                text: reason }); | ||||||
|  |             this.contentLayout.add(this._reasonLabel, {}); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     _onAllowClicked: function() { | ||||||
|  |         this.allowed = true; | ||||||
|  |         this.close(); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     _onDisallowClicked: function() { | ||||||
|  |         this.allowed = false; | ||||||
|  |         this.close(); | ||||||
|  |     } | ||||||
|  | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user