linux-presets/gui/gnome/autocustom-gnome-macos/res/extensions/dash-to-dock@micxgx.gmail.com/utils.js

696 lines
19 KiB
JavaScript
Executable File

import {
Clutter,
GLib,
Gio,
GObject,
Meta,
Shell,
St,
} from './dependencies/gi.js';
import {
Docking,
} from './imports.js';
const {_gi: Gi} = imports;
export const SignalsHandlerFlags = Object.freeze({
NONE: 0,
CONNECT_AFTER: 1,
});
const GENERIC_KEY = Symbol('generic');
/**
* Simplify global signals and function injections handling
* abstract class
*/
const BasicHandler = class DashToDockBasicHandler {
static get genericKey() {
return GENERIC_KEY;
}
constructor(parentObject) {
this._storage = Object.create(null);
if (parentObject) {
if (!(parentObject.connect instanceof Function))
throw new TypeError('Not a valid parent object');
if (!(parentObject instanceof GObject.Object) ||
GObject.signal_lookup('destroy', parentObject.constructor.$gtype)) {
this._parentObject = parentObject;
this._destroyId = parentObject.connect('destroy', () => this.destroy());
}
}
}
add(...args) {
// Convert arguments object to array, concatenate with generic
// Call addWithLabel with ags as if they were passed arguments
this.addWithLabel(GENERIC_KEY, ...args);
}
clear() {
Object.getOwnPropertySymbols(this._storage).forEach(label =>
this.removeWithLabel(label));
}
destroy() {
this._parentObject?.disconnect(this._destroyId);
this._parentObject = null;
this.clear();
}
block() {
Object.getOwnPropertySymbols(this._storage).forEach(label =>
this.blockWithLabel(label));
}
unblock() {
Object.getOwnPropertySymbols(this._storage).forEach(label =>
this.unblockWithLabel(label));
}
addWithLabel(label, ...args) {
if (typeof label !== 'symbol')
throw new Error(`Invalid label ${label}, must be a symbol`);
let argsArray = [...args];
if (argsArray.every(arg => !Array.isArray(arg)))
argsArray = [argsArray];
if (this._storage[label] === undefined)
this._storage[label] = [];
// Skip first element of the arguments
for (const argArray of argsArray) {
if (argArray.length < 3)
throw new Error('Unexpected number of arguments');
const item = this._storage[label];
try {
item.push(this._create(...argArray));
} catch (e) {
logError(e);
}
}
}
removeWithLabel(label) {
this._storage[label]?.reverse().forEach(item => this._remove(item));
delete this._storage[label];
}
blockWithLabel(label) {
(this._storage[label] || []).forEach(item => this._block(item));
}
unblockWithLabel(label) {
(this._storage[label] || []).forEach(item => this._unblock(item));
}
// Virtual methods to be implemented by subclass
/**
* Create single element to be stored in the storage structure
*
* @param _object
* @param _element
* @param _callback
*/
_create(_object, _element, _callback) {
throw new GObject.NotImplementedError(`_create in ${this.constructor.name}`);
}
/**
* Correctly delete single element
*
* @param _item
*/
_remove(_item) {
throw new GObject.NotImplementedError(`_remove in ${this.constructor.name}`);
}
/**
* Block single element
*
* @param _item
*/
_block(_item) {
throw new GObject.NotImplementedError(`_block in ${this.constructor.name}`);
}
/**
* Unblock single element
*
* @param _item
*/
_unblock(_item) {
throw new GObject.NotImplementedError(`_unblock in ${this.constructor.name}`);
}
};
/**
* Manage global signals
*/
export class GlobalSignalsHandler extends BasicHandler {
_create(object, event, callback, flags = SignalsHandlerFlags.NONE) {
if (!object)
throw new Error('Impossible to connect to an invalid object');
const after = flags === SignalsHandlerFlags.CONNECT_AFTER;
const connector = after ? object.connect_after : object.connect;
if (!connector) {
throw new Error(`Requested to connect to signal '${event}', ` +
`but no implementation for 'connect${after ? '_after' : ''}' ` +
`found in ${object.constructor.name}`);
}
const id = connector.call(object, event, callback);
if (event === 'destroy' && object === this._parentObject) {
this._parentObject.disconnect(this._destroyId);
this._destroyId =
this._parentObject.connect('destroy', () => this.destroy());
}
return [object, id];
}
_remove(item) {
const [object, id] = item;
object.disconnect(id);
}
_block(item) {
const [object, id] = item;
if (object instanceof GObject.Object)
GObject.Object.prototype.block_signal_handler.call(object, id);
}
_unblock(item) {
const [object, id] = item;
if (object instanceof GObject.Object)
GObject.Object.prototype.unblock_signal_handler.call(object, id);
}
}
/**
* Color manipulation utilities
*/
export class ColorUtils {
// Darken or brigthen color by a fraction dlum
// Each rgb value is modified by the same fraction.
// Return "#rrggbb" string
static ColorLuminance(r, g, b, dlum) {
let rgbString = '#';
rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(r * (1 + dlum), 0), 255)), 2);
rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(g * (1 + dlum), 0), 255)), 2);
rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(b * (1 + dlum), 0), 255)), 2);
return rgbString;
}
// Convert decimal to an hexadecimal string adding the desired padding
static _decimalToHex(d, padding) {
let hex = d.toString(16);
while (hex.length < padding)
hex = `0${hex}`;
return hex;
}
// Convert hsv ([0-1, 0-1, 0-1]) to rgb ([0-255, 0-255, 0-255]).
// Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV
// here with h = [0,1] instead of [0, 360]
// Accept either (h,s,v) independently or {h:h, s:s, v:v} object.
// Return {r:r, g:g, b:b} object.
static HSVtoRGB(h, s, v) {
if (arguments.length === 1)
({s, v, h} = h);
let r, g, b;
const c = v * s;
const h1 = h * 6;
const x = c * (1 - Math.abs(h1 % 2 - 1));
const m = v - c;
if (h1 <= 1) {
r = c + m;
g = x + m;
b = m;
} else if (h1 <= 2) {
r = x + m;
g = c + m;
b = m;
} else if (h1 <= 3) {
r = m;
g = c + m;
b = x + m;
} else if (h1 <= 4) {
r = m;
g = x + m;
b = c + m;
} else if (h1 <= 5) {
r = x + m;
g = m;
b = c + m;
} else {
r = c + m;
g = m;
b = x + m;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
};
}
// Convert rgb ([0-255, 0-255, 0-255]) to hsv ([0-1, 0-1, 0-1]).
// Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV
// here with h = [0,1] instead of [0, 360]
// Accept either (r,g,b) independently or {r:r, g:g, b:b} object.
// Return {h:h, s:s, v:v} object.
static RGBtoHSV(r, g, b) {
if (arguments.length === 1)
({r, g, b} = r);
let h, s;
const M = Math.max(r, g, b);
const m = Math.min(r, g, b);
const c = M - m;
if (c === 0)
h = 0;
else if (M === r)
h = ((g - b) / c) % 6;
else if (M === g)
h = (b - r) / c + 2;
else
h = (r - g) / c + 4;
h /= 6;
const v = M / 255;
if (M !== 0)
s = c / M;
else
s = 0;
return {
h,
s,
v,
};
}
}
/**
* Manage function injection: both instances and prototype can be overridden
* and restored
*/
export class InjectionsHandler extends BasicHandler {
_create(object, name, injectedFunction) {
const original = object[name];
if (!(original instanceof Function))
throw new Error(`Virtual function ${name}() is not available for ${object}`);
object[name] = function (...args) {
return injectedFunction.call(this, original, ...args);
};
return [object, name, original];
}
_remove(item) {
const [object, name, original] = item;
object[name] = original;
}
}
/**
* Manage vfunction injection: both instances and prototype can be overridden
* and restored
*/
export class VFuncInjectionsHandler extends BasicHandler {
_create(prototype, name, injectedFunction) {
const original = prototype[`vfunc_${name}`];
if (!(original instanceof Function))
throw new Error(`Virtual function ${name} is not available for ${prototype}`);
this._replaceVFunc(prototype, name, injectedFunction);
return [prototype, name];
}
_remove(item) {
const [prototype, name] = item;
const originalVFunc = prototype[`vfunc_${name}`];
try {
// This may fail if trying to reset to a never-overridden vfunc
// as gjs doesn't consider it a function, even if it's true that
// originalVFunc instanceof Function.
this._replaceVFunc(prototype, name, originalVFunc);
} catch {
try {
this._replaceVFunc(prototype, name, function (...args) {
// eslint-disable-next-line no-invalid-this
return originalVFunc.call(this, ...args);
});
} catch (e) {
logError(e, `Removing vfunc_${name}`);
}
}
}
_replaceVFunc(prototype, name, func) {
if (Gi.gobject_prototype_symbol && Gi.gobject_prototype_symbol in prototype)
prototype = prototype[Gi.gobject_prototype_symbol];
return prototype[Gi.hook_up_vfunc_symbol](name, func);
}
}
/**
* Manage properties injection: both instances and prototype can be overridden
* and restored
*/
export class PropertyInjectionsHandler extends BasicHandler {
_create(instance, name, injectedPropertyDescriptor) {
if (!(name in instance))
throw new Error(`Object ${instance} has no '${name}' property`);
const {prototype} = instance.constructor;
const originalPropertyDescriptor = Object.getOwnPropertyDescriptor(prototype, name) ??
Object.getOwnPropertyDescriptor(instance, name);
Object.defineProperty(instance, name, {
...originalPropertyDescriptor,
...injectedPropertyDescriptor,
...{configurable: true},
});
return [instance, name, originalPropertyDescriptor];
}
_remove(item) {
const [instance, name, originalPropertyDescriptor] = item;
if (originalPropertyDescriptor)
Object.defineProperty(instance, name, originalPropertyDescriptor);
else
delete instance[name];
}
}
/**
* Return the actual position reverseing left and right in rtl
*/
export function getPosition() {
const position = Docking.DockManager.settings.dockPosition;
if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL) {
if (position === St.Side.LEFT)
return St.Side.RIGHT;
else if (position === St.Side.RIGHT)
return St.Side.LEFT;
}
return position;
}
/**
* @param cr
* @param x
* @param y
* @param width
* @param height
* @param isRoundLeft
* @param isRoundRight
* @param stroke
* @param fill
*/
export function drawRoundedLine(cr, x, y, width, height, isRoundLeft, isRoundRight, stroke, fill) {
if (height > width) {
y += Math.floor((height - width) / 2.0);
height = width;
}
height = 2.0 * Math.floor(height / 2.0);
const leftRadius = isRoundLeft ? height / 2.0 : 0.0;
const rightRadius = isRoundRight ? height / 2.0 : 0.0;
cr.moveTo(x + width - rightRadius, y);
cr.lineTo(x + leftRadius, y);
if (isRoundLeft)
cr.arcNegative(x + leftRadius, y + leftRadius, leftRadius, -Math.PI / 2, Math.PI / 2);
else
cr.lineTo(x, y + height);
cr.lineTo(x + width - rightRadius, y + height);
if (isRoundRight)
cr.arcNegative(x + width - rightRadius, y + rightRadius, rightRadius, Math.PI / 2, -Math.PI / 2);
else
cr.lineTo(x + width, y);
cr.closePath();
if (fill) {
cr.setSource(fill);
cr.fillPreserve();
}
if (stroke)
cr.setSource(stroke);
cr.stroke();
}
/**
* Convert a signal handler with n value parameters (that is, excluding the
* signal source parameter) to an array of n handlers that are each responsible
* for receiving one of the n values and calling the original handler with the
* most up-to-date arguments.
*
* @param handler
*/
export function splitHandler(handler) {
if (handler.length > 30)
throw new Error('too many parameters');
const count = handler.length - 1;
let missingValueBits = (1 << count) - 1;
const values = Array.from({length: count});
return values.map((_ignored, i) => {
const mask = ~(1 << i);
return (obj, value) => {
values[i] = value;
missingValueBits &= mask;
if (missingValueBits === 0)
handler(obj, ...values);
};
});
}
/**
* Construct a map of gtk application window object paths to MetaWindows.
*/
export function getWindowsByObjectPath() {
const windowsByObjectPath = new Map();
const {workspaceManager} = global;
const workspaces = [...new Array(workspaceManager.nWorkspaces)].map(
(_c, i) => workspaceManager.get_workspace_by_index(i));
workspaces.forEach(ws => {
ws.list_windows().forEach(w => {
const path = w.get_gtk_window_object_path();
if (path)
windowsByObjectPath.set(path, w);
});
});
return windowsByObjectPath;
}
/**
* Re-implements shell_app_compare so that can be used to resort running apps
*
* @param appA
* @param appB
*/
export function shellAppCompare(appA, appB) {
if (appA.state !== appB.state) {
if (appA.state === Shell.AppState.RUNNING)
return -1;
return 1;
}
const windowsA = appA.get_windows();
const windowsB = appB.get_windows();
const isMinimized = windows => !windows.some(w => w.showing_on_its_workspace());
const minimizedB = isMinimized(windowsB);
if (isMinimized(windowsA) !== minimizedB) {
if (minimizedB)
return -1;
return 1;
}
if (appA.state === Shell.AppState.RUNNING) {
if (windowsA.length && !windowsB.length)
return -1;
else if (!windowsA.length && windowsB.length)
return 1;
const lastUserTime = windows =>
Math.max(...windows.map(w => w.get_user_time()));
return lastUserTime(windowsB) - lastUserTime(windowsA);
}
return 0;
}
/**
* Re-implements shell_app_compare_windows
*
* @param winA
* @param winB
*/
export function shellWindowsCompare(winA, winB) {
const activeWorkspace = global.workspaceManager.get_active_workspace();
const wsA = winA.get_workspace() === activeWorkspace;
const wsB = winB.get_workspace() === activeWorkspace;
if (wsA && !wsB)
return -1;
else if (!wsA && wsB)
return 1;
const visA = winA.showing_on_its_workspace();
const visB = winB.showing_on_its_workspace();
if (visA && !visB)
return -1;
else if (!visA && visB)
return 1;
return winB.get_user_time() - winA.get_user_time();
}
export const CancellableChild = GObject.registerClass({
Properties: {
'parent': GObject.ParamSpec.object(
'parent', 'parent', 'parent',
GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
Gio.Cancellable.$gtype),
},
},
class CancellableChild extends Gio.Cancellable {
_init(parent) {
if (parent && !(parent instanceof Gio.Cancellable))
throw TypeError('Not a valid cancellable');
super._init({parent});
if (parent?.is_cancelled()) {
this.cancel();
return;
}
this._connectToParent();
}
_connectToParent() {
this._connectId = this.parent?.connect(() => {
this._realCancel();
if (this._disconnectIdle)
return;
this._disconnectIdle = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
delete this._disconnectIdle;
this._disconnectFromParent();
return GLib.SOURCE_REMOVE;
});
});
}
_disconnectFromParent() {
if (this._connectId && !this._disconnectIdle) {
this.parent.disconnect(this._connectId);
delete this._connectId;
}
}
_realCancel() {
Gio.Cancellable.prototype.cancel.call(this);
}
cancel() {
this._disconnectFromParent();
this._realCancel();
}
});
/**
*
*/
export function getMonitorManager() {
return global.backend.get_monitor_manager?.() ?? Meta.MonitorManager.get();
}
/**
* @param laterType
* @param callback
*/
export function laterAdd(laterType, callback) {
return global.compositor?.get_laters?.().add(laterType, callback) ??
Meta.later_add(laterType, callback);
}
/**
* @param id
*/
export function laterRemove(id) {
if (global.compositor?.get_laters)
global.compositor?.get_laters().remove(id);
else
Meta.later_remove(id);
}
/**
* Up to Gnome Shell 45, the Cairo Context object didn't export the
* `setSourceColor()` method, so Clutter included a function call for
* that, written in C. In Gnome Shell 46, the method was finally exported,
* so that function was removed.
*
* This function is, thus, required for Gnome Shell 45 compatibility.
*
* @param {*} cr A cairo context
* @param {*} sourceColor The new color for source
*/
export function cairoSetSourceColor(cr, sourceColor) {
if (Clutter.cairo_set_source_color)
Clutter.cairo_set_source_color(cr, sourceColor);
else
cr.setSourceColor(sourceColor);
}
/**
* Specifies if the system supports extended barriers. This function
* is required for Gnome Shell 45 compatibility, which used
* `global.display.supports_extended_barriers`. Gnome Shell 46 moved
* that into global.backend.capabilities.
*
* @returns True if the system supports extended barriers.
*/
export function supportsExtendedBarriers() {
if (global.display.supports_extended_barriers)
return global.display.supports_extended_barriers();
return !!(global.backend.capabilities & Meta.BackendCapabilities.BARRIERS);
}
export function addActor(element, actor) {
if (element.add_actor)
element.add_actor(actor);
else
element.add_child(actor);
}