Compare commits
2 Commits
citadel
...
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user