dot/.local/share/gnome-shell/extensions/gsconnect@andyholmes.github.io/service/__init__.js

325 lines
8.7 KiB
JavaScript

'use strict';
const Gdk = imports.gi.Gdk;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const ByteArray = imports.byteArray;
/**
* Check if we're in a Wayland session (mostly for input synthesis)
* https://wiki.gnome.org/Accessibility/Wayland#Bugs.2FIssues_We_Must_Address
*/
window._WAYLAND = GLib.getenv('XDG_SESSION_TYPE') === 'wayland';
window.HAVE_REMOTEINPUT = GLib.getenv('GDMSESSION') !== 'ubuntu-wayland';
/**
* A custom debug function that logs at LEVEL_MESSAGE to avoid the need for env
* variables to be set.
*
* @param {Error|string} message - A string or Error to log
* @param {string} [prefix] - An optional prefix for the warning
*/
const _debugFunc = function(message, prefix = null) {
let caller;
if (message.stack) {
caller = message.stack.split('\n')[0];
message = `${message.message}\n${message.stack}`;
} else {
message = JSON.stringify(message, null, 2);
caller = (new Error()).stack.split('\n')[1];
}
// Prepend prefix
message = (prefix) ? `${prefix}: ${message}` : message;
// Cleanup the stack
let [, func, file, line] = caller.match(/([^@]*)@([^:]*):([^:]*)/);
let script = file.replace(gsconnect.extdatadir, '');
GLib.log_structured('GSConnect', GLib.LogLevelFlags.LEVEL_MESSAGE, {
'MESSAGE': `[${script}:${func}:${line}]: ${message}`,
'SYSLOG_IDENTIFIER': 'org.gnome.Shell.Extensions.GSConnect',
'CODE_FILE': file,
'CODE_FUNC': func,
'CODE_LINE': line
});
};
// Swap the function out for a no-op anonymous function for speed
window.debug = gsconnect.settings.get_boolean('debug') ? _debugFunc : () => {};
gsconnect.settings.connect('changed::debug', (settings) => {
window.debug = settings.get_boolean('debug') ? _debugFunc : () => {};
});
/**
* Convenience function for loading JSON from a file
*
* @param {Gio.File|string} file - A Gio.File or path to a JSON file
* @param {boolean} sync - Default is %false, if %true load synchronously
* @return {object} - The parsed object
*/
JSON.load = function (file, sync = false) {
if (typeof file === 'string') {
file = Gio.File.new_for_path(file);
}
if (sync) {
let contents = file.load_contents(null)[1];
return JSON.parse(ByteArray.toString(contents));
} else {
return new Promise((resolve, reject) => {
file.load_contents_async(null, (file, res) => {
try {
let contents = file.load_contents_finish(res)[1];
resolve(JSON.parse(ByteArray.toString(contents)));
} catch (e) {
reject(e);
}
});
});
}
};
/**
* Convenience function for dumping JSON to a file
*
* @param {Gio.File|string} file - A Gio.File or file path
* @param {object} obj - The object to write to disk
* @param {boolean} sync - Default is %false, if %true load synchronously
*/
JSON.dump = function (obj, file, sync = false) {
if (typeof file === 'string') {
file = Gio.File.new_for_path(file);
}
if (sync) {
file.replace_contents(
JSON.stringify(obj, null, 2),
null,
false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
null
);
} else {
return new Promise((resolve, reject) => {
file.replace_contents_bytes_async(
new GLib.Bytes(JSON.stringify(obj, null, 2)),
null,
false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
null,
(file, res) => {
try {
file.replace_contents_finish(res);
resolve();
} catch (e) {
reject(e);
}
}
);
});
}
};
/**
* Idle Promise
*
* @param {number} priority - The priority of the idle source
*/
Promise.idle = function(priority) {
return new Promise(resolve => GLib.idle_add(priority, resolve));
};
/**
* Timeout Promise
*
* @param {number} priority - The priority of the timeout source
* @param {number} interval - Delay in milliseconds before resolving
*/
Promise.timeout = function(priority = GLib.PRIORITY_DEFAULT, interval = 100) {
return new Promise(resolve => GLib.timeout_add(priority, interval, resolve));
};
/**
* A simple (for now) pre-comparison sanitizer for phone numbers
* See: https://github.com/KDE/kdeconnect-kde/blob/master/smsapp/conversationlistmodel.cpp#L200-L210
*
* @return {string} - Return the string stripped of leading 0, and ' ()-+'
*/
String.prototype.toPhoneNumber = function() {
let strippedNumber = this.replace(/^0*|[ ()+-]/g, '');
if (strippedNumber.length)
return strippedNumber;
return this;
};
/**
* A simple equality check for phone numbers based on `toPhoneNumber()`
*
* @param {string} number - A phone number string to compare
* @return {boolean} - If `this` and @number are equivalent phone numbers
*/
String.prototype.equalsPhoneNumber = function(number) {
let a = this.toPhoneNumber();
let b = number.toPhoneNumber();
return (a.endsWith(b) || b.endsWith(a));
};
/**
* An implementation of `rm -rf` in Gio
*/
Gio.File.rm_rf = function(file) {
try {
if (typeof file === 'string') {
file = Gio.File.new_for_path(file);
}
try {
let iter = file.enumerate_children(
'standard::name',
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
null
);
let info;
while ((info = iter.next_file(null))) {
Gio.File.rm_rf(iter.get_child(info));
}
iter.close(null);
} catch (e) {
// Silence errors
}
file.delete(null);
} catch (e) {
// Silence errors
}
};
/**
* Extend GLib.Variant with a static method to recursively pack a variant
*
* @param {*} [obj] - May be a GLib.Variant, Array, standard Object or literal.
*/
function _full_pack(obj) {
let packed;
let type = typeof obj;
switch (true) {
case (obj instanceof GLib.Variant):
return obj;
case (type === 'string'):
return GLib.Variant.new('s', obj);
case (type === 'number'):
return GLib.Variant.new('d', obj);
case (type === 'boolean'):
return GLib.Variant.new('b', obj);
case (obj instanceof Uint8Array):
return GLib.Variant.new('ay', obj);
case (obj === null):
return GLib.Variant.new('mv', null);
case (typeof obj.map === 'function'):
return GLib.Variant.new(
'av',
obj.filter(e => e !== undefined).map(e => _full_pack(e))
);
case (obj instanceof Gio.Icon):
return obj.serialize();
case (type === 'object'):
packed = {};
for (let [key, val] of Object.entries(obj)) {
if (val !== undefined) {
packed[key] = _full_pack(val);
}
}
return GLib.Variant.new('a{sv}', packed);
default:
throw Error(`Unsupported type '${type}': ${obj}`);
}
}
GLib.Variant.full_pack = _full_pack;
/**
* Extend GLib.Variant with a method to recursively deepUnpack() a variant
*
* TODO: this is duplicated in components/dbus.js and it probably shouldn't be,
* but dbus.js can stand on it's own if it is...
*
* @param {*} [obj] - May be a GLib.Variant, Array, standard Object or literal.
*/
function _full_unpack(obj) {
obj = (obj === undefined) ? this : obj;
let unpacked;
switch (true) {
case (obj === null):
return obj;
case (obj instanceof GLib.Variant):
return _full_unpack(obj.deepUnpack());
case (obj instanceof Uint8Array):
return obj;
case (typeof obj.map === 'function'):
return obj.map(e => _full_unpack(e));
case (typeof obj === 'object'):
unpacked = {};
for (let [key, value] of Object.entries(obj)) {
// Try to detect and deserialize GIcons
try {
if (key === 'icon' && value.get_type_string() === '(sv)') {
unpacked[key] = Gio.Icon.deserialize(value);
} else {
unpacked[key] = _full_unpack(value);
}
} catch (e) {
unpacked[key] = _full_unpack(value);
}
}
return unpacked;
default:
return obj;
}
}
GLib.Variant.prototype.full_unpack = _full_unpack;