Files
Burn-My-Windows/src/utils.js
T
2025-03-27 20:07:01 +01:00

207 lines
7.7 KiB
JavaScript

//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< //
// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ //
// |__/ //
//////////////////////////////////////////////////////////////////////////////////////////
// SPDX-FileCopyrightText: Simon Schneegans <code@simonschneegans.de>
// SPDX-License-Identifier: GPL-3.0-or-later
'use strict';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
// We import some modules optionally. This file is used in the preferences process as well
// as in the GNOME Shell process. Some modules are only available or required in one of
// these processes.
const GdkPixbuf = await importInShellOnly('gi://GdkPixbuf');
const Clutter = await importInShellOnly('gi://Clutter');
const Cogl = await importInShellOnly('gi://Cogl');
const St = await importInShellOnly('gi://St');
// We import the Config module. This is done differently in the GNOME Shell process and in
// the preferences process.
const Config = await importConfig();
// Returns the given argument, except for "alpha", "beta", and "rc". In these cases -3,
// -2, and -1 are returned respectively.
function toNumericVersion(x) {
switch (x) {
case 'alpha':
return -3;
case 'beta':
return -2;
case 'rc':
return -1;
}
return x;
}
const [GS_MAJOR, GS_MINOR] = Config.PACKAGE_VERSION.split('.').map(toNumericVersion);
// This method returns true if the current GNOME Shell version matches the given
// arguments.
export function shellVersionIs(major, minor) {
return GS_MAJOR == major && GS_MINOR == toNumericVersion(minor);
}
// This method returns true if the current GNOME Shell version is at least as high as the
// given arguments. Supports "alpha" and "beta" for the minor version number.
export function shellVersionIsAtLeast(major, minor = 0) {
if (GS_MAJOR > major) {
return true;
}
if (GS_MAJOR == major) {
return GS_MINOR >= toNumericVersion(minor);
}
return false;
}
// This method can be used to import the Config module.
export async function importConfig() {
if (typeof global === 'undefined') {
return (await import('resource:///org/gnome/Shell/Extensions/js/misc/config.js'));
}
return (await import('resource:///org/gnome/shell/misc/config.js'));
}
// This method can be used to write a message to GNOME Shell's log. This is enhances
// the standard log() functionality by prepending the extension's name and the location
// where the message was logged. As the extensions name is part of the location, you
// can more effectively watch the log output of GNOME Shell:
// journalctl -f -o cat | grep -E 'burn-my-windows|'
export function debug(message) {
const stack = new Error().stack.split('\n');
// Remove debug() function call from stack.
stack.shift();
// Find the index of the extension directory in the stack entry. We do not want to
// print the entire absolute file path.
const extensionRoot = stack[0].indexOf('burn-my-windows@schneegans.github.com');
console.log('[' + stack[0].slice(extensionRoot) + '] ' + message);
}
// This method can be used to import a module in the GNOME Shell process only. This
// is useful if you want to use a module in extension.js, but not in the preferences
// process. This method returns null if it is called in the preferences process.
export async function importInShellOnly(module) {
if (typeof global !== 'undefined') {
return (await import(module)).default;
}
return null;
}
// This method can be used to import a module in the preferences process only. The same as
// importInShellOnly(), but the other way around.
export async function importInPrefsOnly(module) {
if (typeof global === 'undefined') {
return (await import(module)).default;
}
return null;
}
// This method can be used to import gettext. This is done differently in the
// GNOME Shell process and in the preferences process.
export async function importGettext() {
if (typeof global === 'undefined') {
return (await import('resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'))
.gettext;
}
return (await import('resource:///org/gnome/shell/extensions/extension.js')).gettext;
}
// Reads the contents of a file contained in the global resources archive. The data
// is returned as a string.
export function getStringResource(path) {
const data = Gio.resources_lookup_data(path, 0);
return new TextDecoder().decode(data.get_data());
}
// Reads the contents of an image file contained in the global resources archive. The data
// is returned as a St.ImageContent.
export function getImageResource(path, premultiplied = false) {
const data = GdkPixbuf.Pixbuf.new_from_resource(path);
const texture = St.ImageContent.new_with_preferred_size(data.width, data.height);
const format = data.has_alpha ?
(premultiplied ? Cogl.PixelFormat.RGBA_8888_PRE : Cogl.PixelFormat.RGBA_8888) :
Cogl.PixelFormat.RGB_888;
// https://gitlab.gnome.org/GNOME/gnome-shell/-/commit/44b84e458a22046fedb85701ea25ad08ecc0d43f
if (shellVersionIsAtLeast(48, 'beta')) {
texture.set_data(global.stage.context.get_backend().get_cogl_context(),
data.get_pixels(), format, data.width, data.height, data.rowstride);
} else {
texture.set_data(data.get_pixels(), format, data.width, data.height, data.rowstride);
}
return texture;
}
// Executes a command asynchronously and returns the output from 'stdout' on success or
// throw an error with output from 'stderr' on failure. If given, input will be passed to
// 'stdin' and cancellable can be used to stop the process before it finishes.
// This is directly taken from here:
// https://gjs.guide/guides/gio/subprocesses.html#complete-examples
export async function executeCommand(argv, input = null, cancellable = null) {
let cancelId = 0;
let flags = (Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE);
if (input !== null) flags |= Gio.SubprocessFlags.STDIN_PIPE;
let proc = new Gio.Subprocess({argv: argv, flags: flags});
proc.init(cancellable);
if (cancellable instanceof Gio.Cancellable) {
cancelId = cancellable.connect(() => proc.force_exit());
}
return new Promise((resolve, reject) => {
proc.communicate_utf8_async(input, null, (proc, res) => {
try {
let [, stdout, stderr] = proc.communicate_utf8_finish(res);
let status = proc.get_exit_status();
if (status !== 0) {
throw new Gio.IOErrorEnum({
code: Gio.io_error_from_errno(status),
message: stderr ? stderr.trim() : GLib.strerror(status)
});
}
resolve(stdout.trim());
} catch (e) {
reject(e);
} finally {
if (cancelId > 0) {
cancellable.disconnect(cancelId);
}
}
});
});
}
// Converts a hex, rgb, or rgba CSS-like color string to four numbers
// representing rgba values.
export function parseColor(string) {
let color;
if (shellVersionIsAtLeast(47, 'alpha')) {
color = Cogl.Color.from_string(string)[1];
} else {
color = Clutter.Color.from_string(string)[1];
}
return [color.red / 255, color.green / 255, color.blue / 255, color.alpha / 255];
}