summaryrefslogtreecommitdiff
path: root/modules/overrides/GLib.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/overrides/GLib.js')
-rw-r--r--modules/overrides/GLib.js507
1 files changed, 507 insertions, 0 deletions
diff --git a/modules/overrides/GLib.js b/modules/overrides/GLib.js
new file mode 100644
index 00000000..312377a8
--- /dev/null
+++ b/modules/overrides/GLib.js
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2011 Giovanni Campagna
+
+const {GLib} = import.meta.importSync('gi');
+
+const SIMPLE_TYPES = ['b', 'y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd', 's', 'o', 'g'];
+
+function _readSingleType(signature, forceSimple) {
+ let char = signature.shift();
+ let isSimple = false;
+
+ if (!SIMPLE_TYPES.includes(char)) {
+ if (forceSimple)
+ throw new TypeError('Invalid GVariant signature (a simple type was expected)');
+ } else {
+ isSimple = true;
+ }
+
+ if (char === 'm' || char === 'a')
+ return [char].concat(_readSingleType(signature, false));
+ if (char === '{') {
+ let key = _readSingleType(signature, true);
+ let val = _readSingleType(signature, false);
+ let close = signature.shift();
+ if (close !== '}')
+ throw new TypeError('Invalid GVariant signature for type DICT_ENTRY (expected "}"');
+ return [char].concat(key, val, close);
+ }
+ if (char === '(') {
+ let res = [char];
+ while (true) {
+ if (signature.length === 0)
+ throw new TypeError('Invalid GVariant signature for type TUPLE (expected ")")');
+ let next = signature[0];
+ if (next === ')') {
+ signature.shift();
+ return res.concat(next);
+ }
+ let el = _readSingleType(signature);
+ res = res.concat(el);
+ }
+ }
+
+ // Valid types are simple types, arrays, maybes, tuples, dictionary entries and variants
+ if (!isSimple && char !== 'v')
+ throw new TypeError(`Invalid GVariant signature (${char} is not a valid type)`);
+
+ return [char];
+}
+
+function _packVariant(signature, value) {
+ if (signature.length === 0)
+ throw new TypeError('GVariant signature cannot be empty');
+
+ let char = signature.shift();
+ switch (char) {
+ case 'b':
+ return GLib.Variant.new_boolean(value);
+ case 'y':
+ return GLib.Variant.new_byte(value);
+ case 'n':
+ return GLib.Variant.new_int16(value);
+ case 'q':
+ return GLib.Variant.new_uint16(value);
+ case 'i':
+ return GLib.Variant.new_int32(value);
+ case 'u':
+ return GLib.Variant.new_uint32(value);
+ case 'x':
+ return GLib.Variant.new_int64(value);
+ case 't':
+ return GLib.Variant.new_uint64(value);
+ case 'h':
+ return GLib.Variant.new_handle(value);
+ case 'd':
+ return GLib.Variant.new_double(value);
+ case 's':
+ return GLib.Variant.new_string(value);
+ case 'o':
+ return GLib.Variant.new_object_path(value);
+ case 'g':
+ return GLib.Variant.new_signature(value);
+ case 'v':
+ return GLib.Variant.new_variant(value);
+ case 'm':
+ if (value !== null) {
+ return GLib.Variant.new_maybe(null, _packVariant(signature, value));
+ } else {
+ return GLib.Variant.new_maybe(new GLib.VariantType(
+ _readSingleType(signature, false).join('')), null);
+ }
+ case 'a': {
+ let arrayType = _readSingleType(signature, false);
+ if (arrayType[0] === 's') {
+ // special case for array of strings
+ return GLib.Variant.new_strv(value);
+ }
+ if (arrayType[0] === 'y') {
+ // special case for array of bytes
+ let bytes;
+ if (typeof value === 'string') {
+ let encoder = new TextEncoder();
+ let byteArray = encoder.encode(value);
+ if (byteArray[byteArray.length - 1] !== 0)
+ byteArray = Uint8Array.of(...byteArray, 0);
+ bytes = new GLib.Bytes(byteArray);
+ } else {
+ bytes = new GLib.Bytes(value);
+ }
+ return GLib.Variant.new_from_bytes(new GLib.VariantType('ay'),
+ bytes, true);
+ }
+
+ let arrayValue = [];
+ if (arrayType[0] === '{') {
+ // special case for dictionaries
+ for (let key in value) {
+ let copy = [].concat(arrayType);
+ let child = _packVariant(copy, [key, value[key]]);
+ arrayValue.push(child);
+ }
+ } else {
+ for (let i = 0; i < value.length; i++) {
+ let copy = [].concat(arrayType);
+ let child = _packVariant(copy, value[i]);
+ arrayValue.push(child);
+ }
+ }
+ return GLib.Variant.new_array(new GLib.VariantType(arrayType.join('')), arrayValue);
+ }
+
+ case '(': {
+ let children = [];
+ for (let i = 0; i < value.length; i++) {
+ let next = signature[0];
+ if (next === ')')
+ break;
+ children.push(_packVariant(signature, value[i]));
+ }
+
+ if (signature[0] !== ')')
+ throw new TypeError('Invalid GVariant signature for type TUPLE (expected ")")');
+ signature.shift();
+ return GLib.Variant.new_tuple(children);
+ }
+ case '{': {
+ let key = _packVariant(signature, value[0]);
+ let child = _packVariant(signature, value[1]);
+
+ if (signature[0] !== '}')
+ throw new TypeError('Invalid GVariant signature for type DICT_ENTRY (expected "}")');
+ signature.shift();
+
+ return GLib.Variant.new_dict_entry(key, child);
+ }
+ default:
+ throw new TypeError(`Invalid GVariant signature (unexpected character ${char})`);
+ }
+}
+
+function _unpackVariant(variant, deep, recursive = false) {
+ switch (String.fromCharCode(variant.classify())) {
+ case 'b':
+ return variant.get_boolean();
+ case 'y':
+ return variant.get_byte();
+ case 'n':
+ return variant.get_int16();
+ case 'q':
+ return variant.get_uint16();
+ case 'i':
+ return variant.get_int32();
+ case 'u':
+ return variant.get_uint32();
+ case 'x':
+ return variant.get_int64();
+ case 't':
+ return variant.get_uint64();
+ case 'h':
+ return variant.get_handle();
+ case 'd':
+ return variant.get_double();
+ case 'o':
+ case 'g':
+ case 's':
+ // g_variant_get_string has length as out argument
+ return variant.get_string()[0];
+ case 'v': {
+ const ret = variant.get_variant();
+ if (deep && recursive && ret instanceof GLib.Variant)
+ return _unpackVariant(ret, deep, recursive);
+ return ret;
+ }
+ case 'm': {
+ let val = variant.get_maybe();
+ if (deep && val)
+ return _unpackVariant(val, deep, recursive);
+ else
+ return val;
+ }
+ case 'a':
+ if (variant.is_of_type(new GLib.VariantType('a{?*}'))) {
+ // special case containers
+ let ret = {};
+ let nElements = variant.n_children();
+ for (let i = 0; i < nElements; i++) {
+ // always unpack the dictionary entry, and always unpack
+ // the key (or it cannot be added as a key)
+ let val = _unpackVariant(variant.get_child_value(i), deep,
+ recursive);
+ let key;
+ if (!deep)
+ key = _unpackVariant(val[0], true);
+ else
+ key = val[0];
+ ret[key] = val[1];
+ }
+ return ret;
+ }
+ if (variant.is_of_type(new GLib.VariantType('ay'))) {
+ // special case byte arrays
+ return variant.get_data_as_bytes().toArray();
+ }
+
+ // fall through
+ case '(':
+ case '{': {
+ let ret = [];
+ let nElements = variant.n_children();
+ for (let i = 0; i < nElements; i++) {
+ let val = variant.get_child_value(i);
+ if (deep)
+ ret.push(_unpackVariant(val, deep, recursive));
+ else
+ ret.push(val);
+ }
+ return ret;
+ }
+ }
+
+ throw new Error('Assertion failure: this code should not be reached');
+}
+
+function _notIntrospectableError(funcName, replacement) {
+ return new Error(`${funcName} is not introspectable. Use ${replacement} instead.`);
+}
+
+/**
+ * @param funcName
+ * @param replacement
+ */
+function _warnNotIntrospectable(funcName, replacement) {
+ logError(_notIntrospectableError(funcName, replacement));
+}
+
+function _escapeCharacterSetChars(char) {
+ if ('-^]\\'.includes(char))
+ return `\\${char}`;
+ return char;
+}
+
+(function () {
+ // For convenience in property min or max values, since GLib.MAXINT64 and
+ // friends will log a warning when used
+ GLib.MAXINT64_BIGINT = 0x7fff_ffff_ffff_ffffn;
+ GLib.MININT64_BIGINT = -GLib.MAXINT64_BIGINT - 1n;
+ GLib.MAXUINT64_BIGINT = 0xffff_ffff_ffff_ffffn;
+
+ // small HACK: we add a matches() method to standard Errors so that
+ // you can do "if (e.matches(Ns.FooError, Ns.FooError.SOME_CODE))"
+ // without checking instanceof
+ Error.prototype.matches = function () {
+ return false;
+ };
+
+ // Guard against domains that aren't valid quarks and would lead
+ // to a crash
+ const quarkToString = GLib.quark_to_string;
+ const realNewLiteral = GLib.Error.new_literal;
+ GLib.Error.new_literal = function (domain, code, message) {
+ if (quarkToString(domain) === null)
+ throw new TypeError(`Error.new_literal: ${domain} is not a valid domain`);
+ return realNewLiteral(domain, code, message);
+ };
+
+ GLib.Variant._new_internal = function (sig, value) {
+ let signature = Array.prototype.slice.call(sig);
+
+ let variant = _packVariant(signature, value);
+ if (signature.length !== 0)
+ throw new TypeError('Invalid GVariant signature (more than one single complete type)');
+
+ return variant;
+ };
+
+ // Deprecate version of new GLib.Variant()
+ GLib.Variant.new = function (sig, value) {
+ return new GLib.Variant(sig, value);
+ };
+ GLib.Variant.prototype.unpack = function () {
+ return _unpackVariant(this, false);
+ };
+ GLib.Variant.prototype.deepUnpack = function () {
+ return _unpackVariant(this, true);
+ };
+ // backwards compatibility alias
+ GLib.Variant.prototype.deep_unpack = GLib.Variant.prototype.deepUnpack;
+
+ // Note: discards type information, if the variant contains any 'v' types
+ GLib.Variant.prototype.recursiveUnpack = function () {
+ return _unpackVariant(this, true, true);
+ };
+
+ GLib.Variant.prototype.toString = function () {
+ return `[object variant of type "${GLib.get_type_string()}"]`;
+ };
+
+ GLib.Bytes.prototype.toArray = function () {
+ return imports._byteArrayNative.fromGBytes(this);
+ };
+
+ GLib.log_structured =
+ /**
+ * @param {string} logDomain
+ * @param {GLib.LogLevelFlags} logLevel
+ * @param {Record<string, unknown>} stringFields
+ * @returns {void}
+ */
+ function log_structured(logDomain, logLevel, stringFields) {
+ /** @type {Record<string, GLib.Variant>} */
+ let fields = {};
+
+ for (let key in stringFields) {
+ const field = stringFields[key];
+
+ if (field instanceof Uint8Array) {
+ fields[key] = new GLib.Variant('ay', field);
+ } else if (typeof field === 'string') {
+ fields[key] = new GLib.Variant('s', field);
+ } else if (field instanceof GLib.Variant) {
+ // GLib.log_variant converts all Variants that are
+ // not 'ay' or 's' type to strings by printing
+ // them.
+ //
+ // https://gitlab.gnome.org/GNOME/glib/-/blob/a380bfdf93cb3bfd3cd4caedc0127c4e5717545b/glib/gmessages.c#L1894
+ fields[key] = field;
+ } else {
+ throw new TypeError(`Unsupported value ${field}, log_structured supports GLib.Variant, Uint8Array, and string values.`);
+ }
+ }
+
+ GLib.log_variant(logDomain, logLevel, new GLib.Variant('a{sv}', fields));
+ };
+
+ // GjsPrivate depends on GLib so we cannot import it
+ // before GLib is fully resolved.
+
+ GLib.log_set_writer_func_variant = function (...args) {
+ const {log_set_writer_func} = gi.GjsPrivate;
+
+ log_set_writer_func(...args);
+ };
+
+ GLib.log_set_writer_default = function (...args) {
+ const {log_set_writer_default} = gi.GjsPrivate;
+
+ log_set_writer_default(...args);
+ };
+
+ GLib.log_set_writer_func = function (writer_func) {
+ const {log_set_writer_func} = imports.gi.GjsPrivate;
+
+ if (typeof writer_func !== 'function') {
+ log_set_writer_func(writer_func);
+ } else {
+ log_set_writer_func(function (logLevel, stringFields) {
+ const stringFieldsObj = {...stringFields.recursiveUnpack()};
+ return writer_func(logLevel, stringFieldsObj);
+ });
+ }
+ };
+
+ GLib.VariantDict.prototype.lookup = function (key, variantType = null, deep = false) {
+ if (typeof variantType === 'string')
+ variantType = new GLib.VariantType(variantType);
+
+ const variant = this.lookup_value(key, variantType);
+ if (variant === null)
+ return null;
+ return _unpackVariant(variant, deep);
+ };
+
+ // Prevent user code from calling GLib string manipulation functions that
+ // return the same string that was passed in. These can't be annotated
+ // properly, and will mostly crash.
+ // Here we provide approximate implementations of the functions so that if
+ // they had happened to work in the past, they will continue working, but
+ // log a stack trace and a suggestion of what to use instead.
+ // Exceptions are thrown instead for GLib.stpcpy() of which the return value
+ // is useless anyway and GLib.ascii_formatd() which is too complicated to
+ // implement here.
+
+ GLib.stpcpy = function () {
+ throw _notIntrospectableError('GLib.stpcpy()', 'the + operator');
+ };
+
+ GLib.strstr_len = function (haystack, len, needle) {
+ _warnNotIntrospectable('GLib.strstr_len()', 'String.indexOf()');
+ let searchString = haystack;
+ if (len !== -1)
+ searchString = searchString.slice(0, len);
+ const index = searchString.indexOf(needle);
+ if (index === -1)
+ return null;
+ return haystack.slice(index);
+ };
+
+ GLib.strrstr = function (haystack, needle) {
+ _warnNotIntrospectable('GLib.strrstr()', 'String.lastIndexOf()');
+ const index = haystack.lastIndexOf(needle);
+ if (index === -1)
+ return null;
+ return haystack.slice(index);
+ };
+
+ GLib.strrstr_len = function (haystack, len, needle) {
+ _warnNotIntrospectable('GLib.strrstr_len()', 'String.lastIndexOf()');
+ let searchString = haystack;
+ if (len !== -1)
+ searchString = searchString.slice(0, len);
+ const index = searchString.lastIndexOf(needle);
+ if (index === -1)
+ return null;
+ return haystack.slice(index);
+ };
+
+ GLib.strup = function (string) {
+ _warnNotIntrospectable('GLib.strup()',
+ 'String.toUpperCase() or GLib.ascii_strup()');
+ return string.toUpperCase();
+ };
+
+ GLib.strdown = function (string) {
+ _warnNotIntrospectable('GLib.strdown()',
+ 'String.toLowerCase() or GLib.ascii_strdown()');
+ return string.toLowerCase();
+ };
+
+ GLib.strreverse = function (string) {
+ _warnNotIntrospectable('GLib.strreverse()',
+ 'Array.reverse() and String.join()');
+ return [...string].reverse().join('');
+ };
+
+ GLib.ascii_dtostr = function (unused, len, number) {
+ _warnNotIntrospectable('GLib.ascii_dtostr()', 'JS string conversion');
+ return `${number}`.slice(0, len);
+ };
+
+ GLib.ascii_formatd = function () {
+ throw _notIntrospectableError('GLib.ascii_formatd()',
+ 'Number.toExponential() and string interpolation');
+ };
+
+ GLib.strchug = function (string) {
+ _warnNotIntrospectable('GLib.strchug()', 'String.trimStart()');
+ return string.trimStart();
+ };
+
+ GLib.strchomp = function (string) {
+ _warnNotIntrospectable('GLib.strchomp()', 'String.trimEnd()');
+ return string.trimEnd();
+ };
+
+ // g_strstrip() is a macro and therefore doesn't even appear in the GIR
+ // file, but we may as well include it here since it's trivial
+ GLib.strstrip = function (string) {
+ _warnNotIntrospectable('GLib.strstrip()', 'String.trim()');
+ return string.trim();
+ };
+
+ GLib.strdelimit = function (string, delimiters, newDelimiter) {
+ _warnNotIntrospectable('GLib.strdelimit()', 'String.replace()');
+
+ if (delimiters === null)
+ delimiters = GLib.STR_DELIMITERS;
+ if (typeof newDelimiter === 'number')
+ newDelimiter = String.fromCharCode(newDelimiter);
+
+ const delimiterChars = delimiters.split('');
+ const escapedDelimiterChars = delimiterChars.map(_escapeCharacterSetChars);
+ const delimiterRegex = new RegExp(`[${escapedDelimiterChars.join('')}]`, 'g');
+ return string.replace(delimiterRegex, newDelimiter);
+ };
+
+ GLib.strcanon = function (string, validChars, substitutor) {
+ _warnNotIntrospectable('GLib.strcanon()', 'String.replace()');
+
+ if (typeof substitutor === 'number')
+ substitutor = String.fromCharCode(substitutor);
+
+ const validArray = validChars.split('');
+ const escapedValidArray = validArray.map(_escapeCharacterSetChars);
+ const invalidRegex = new RegExp(`[^${escapedValidArray.join('')}]`, 'g');
+ return string.replace(invalidRegex, substitutor);
+ };
+})();