[gnome] Update extensions
This commit is contained in:
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* This file is part of the Forge extension for GNOME
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import { production } from "./settings.js";
|
||||
|
||||
export class Logger {
|
||||
static #settings;
|
||||
|
||||
static LOG_LEVELS = {
|
||||
OFF: 0,
|
||||
FATAL: 1,
|
||||
ERROR: 2,
|
||||
WARN: 3,
|
||||
INFO: 4,
|
||||
DEBUG: 5,
|
||||
TRACE: 6,
|
||||
ALL: 7,
|
||||
};
|
||||
|
||||
static init(settings) {
|
||||
this.#settings = settings;
|
||||
}
|
||||
|
||||
static get #level() {
|
||||
if (this.#settings?.get_boolean?.("logging-enabled")) {
|
||||
return production
|
||||
? Logger.LOG_LEVELS.OFF
|
||||
: this.#settings?.get_uint?.("log-level") ?? Logger.LOG_LEVELS.OFF;
|
||||
}
|
||||
return Logger.LOG_LEVELS.OFF;
|
||||
}
|
||||
|
||||
// TODO: use console.* methods
|
||||
static format(msg, ...params) {
|
||||
return params.reduce((acc, val) => acc.replace("{}", val), msg);
|
||||
}
|
||||
|
||||
static fatal(...args) {
|
||||
if (this.#level > Logger.LOG_LEVELS.OFF) log(`[Forge] [FATAL]`, ...args);
|
||||
}
|
||||
|
||||
static error(...args) {
|
||||
if (this.#level > Logger.LOG_LEVELS.FATAL) log(`[Forge] [ERROR]`, ...args);
|
||||
}
|
||||
|
||||
static warn(...args) {
|
||||
if (this.#level > Logger.LOG_LEVELS.ERROR) log(`[Forge] [WARN]`, ...args);
|
||||
}
|
||||
|
||||
static info(...args) {
|
||||
if (this.#level > Logger.LOG_LEVELS.WARN) log(`[Forge] [INFO]`, ...args);
|
||||
}
|
||||
|
||||
static debug(...args) {
|
||||
if (this.#level > Logger.LOG_LEVELS.INFO) log(`[Forge] [DEBUG]`, ...args);
|
||||
}
|
||||
|
||||
static trace(...args) {
|
||||
if (this.#level > Logger.LOG_LEVELS.DEBUG) log(`[Forge] [TRACE]`, ...args);
|
||||
}
|
||||
|
||||
static log(...args) {
|
||||
if (this.#level > Logger.LOG_LEVELS.OFF) log(`[Forge] [LOG]`, ...args);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* This file is part of the Forge Window Manager extension for Gnome 3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Gnome imports
|
||||
import Gio from "gi://Gio";
|
||||
import GLib from "gi://GLib";
|
||||
import GObject from "gi://GObject";
|
||||
|
||||
import { Logger } from "./logger.js";
|
||||
|
||||
// Dev or Prod mode, see Makefile:debug
|
||||
export const production = true;
|
||||
|
||||
export class ConfigManager extends GObject.Object {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
#confDir = GLib.get_user_config_dir();
|
||||
|
||||
constructor({ dir }) {
|
||||
super();
|
||||
this.extensionPath = dir.get_path();
|
||||
}
|
||||
|
||||
get confDir() {
|
||||
return `${this.#confDir}/forge`;
|
||||
}
|
||||
|
||||
get defaultStylesheetFile() {
|
||||
const defaultStylesheet = GLib.build_filenamev([this.extensionPath, `stylesheet.css`]);
|
||||
|
||||
Logger.trace(`default-stylesheet: ${defaultStylesheet}`);
|
||||
|
||||
const defaultStylesheetFile = Gio.File.new_for_path(defaultStylesheet);
|
||||
if (defaultStylesheetFile.query_exists(null)) {
|
||||
return defaultStylesheetFile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
get stylesheetFile() {
|
||||
const profileSettingPath = `${this.confDir}/stylesheet/forge`;
|
||||
const settingFile = "stylesheet.css";
|
||||
const defaultSettingFile = this.defaultStylesheetFile;
|
||||
return this.loadFile(profileSettingPath, settingFile, defaultSettingFile);
|
||||
}
|
||||
|
||||
get defaultWindowConfigFile() {
|
||||
const defaultWindowConfig = GLib.build_filenamev([
|
||||
this.extensionPath,
|
||||
`config`,
|
||||
`windows.json`,
|
||||
]);
|
||||
|
||||
Logger.trace(`default-window-config: ${defaultWindowConfig}`);
|
||||
const defaultWindowConfigFile = Gio.File.new_for_path(defaultWindowConfig);
|
||||
|
||||
if (defaultWindowConfigFile.query_exists(null)) {
|
||||
return defaultWindowConfigFile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
get windowConfigFile() {
|
||||
const profileSettingPath = `${this.confDir}/config`;
|
||||
const settingFile = "windows.json";
|
||||
const defaultSettingFile = this.defaultWindowConfigFile;
|
||||
return this.loadFile(profileSettingPath, settingFile, defaultSettingFile);
|
||||
}
|
||||
|
||||
loadFile(path, file, defaultFile) {
|
||||
const customSetting = GLib.build_filenamev([path, file]);
|
||||
Logger.trace(`custom-setting-file: ${customSetting}`);
|
||||
|
||||
const customSettingFile = Gio.File.new_for_path(customSetting);
|
||||
if (customSettingFile.query_exists(null)) {
|
||||
return customSettingFile;
|
||||
} else {
|
||||
const profileCustomSettingDir = Gio.File.new_for_path(path);
|
||||
if (!profileCustomSettingDir.query_exists(null)) {
|
||||
if (profileCustomSettingDir.make_directory_with_parents(null)) {
|
||||
const createdStream = customSettingFile.create(Gio.FileCreateFlags.NONE, null);
|
||||
const defaultContents = this.loadFileContents(defaultFile);
|
||||
Logger.trace(defaultContents);
|
||||
createdStream.write_all(defaultContents, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
loadFileContents(configFile) {
|
||||
let [success, contents] = configFile.load_contents(null);
|
||||
if (success) {
|
||||
const stringContents = imports.byteArray.toString(contents);
|
||||
return stringContents;
|
||||
}
|
||||
}
|
||||
|
||||
get windowProps() {
|
||||
let windowConfigFile = this.windowConfigFile;
|
||||
let windowProps = null;
|
||||
if (!windowConfigFile || !production) {
|
||||
windowConfigFile = this.defaultWindowConfigFile;
|
||||
}
|
||||
|
||||
let [success, contents] = windowConfigFile.load_contents(null);
|
||||
if (success) {
|
||||
const windowConfigContents = imports.byteArray.toString(contents);
|
||||
Logger.trace(`${windowConfigContents}`);
|
||||
windowProps = JSON.parse(windowConfigContents);
|
||||
}
|
||||
return windowProps;
|
||||
}
|
||||
|
||||
set windowProps(props) {
|
||||
let windowConfigFile = this.windowConfigFile;
|
||||
if (!windowConfigFile || !production) {
|
||||
windowConfigFile = this.defaultWindowConfigFile;
|
||||
}
|
||||
|
||||
let windowConfigContents = JSON.stringify(props, null, 4);
|
||||
|
||||
const PERMISSIONS_MODE = 0o744;
|
||||
|
||||
if (GLib.mkdir_with_parents(windowConfigFile.get_parent().get_path(), PERMISSIONS_MODE) === 0) {
|
||||
let [_, _tag] = windowConfigFile.replace_contents(
|
||||
windowConfigContents,
|
||||
null,
|
||||
false,
|
||||
Gio.FileCreateFlags.REPLACE_DESTINATION,
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,279 @@
|
||||
/*
|
||||
* This file is part of the Forge extension for GNOME
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Gnome imports
|
||||
import Gio from "gi://Gio";
|
||||
import GLib from "gi://GLib";
|
||||
import GObject from "gi://GObject";
|
||||
|
||||
// Application imports
|
||||
import { stringify, parse } from "../css/index.js";
|
||||
import { production } from "./settings.js";
|
||||
|
||||
export class ThemeManagerBase extends GObject.Object {
|
||||
static {
|
||||
GObject.registerClass(this);
|
||||
}
|
||||
|
||||
constructor({ configMgr, settings }) {
|
||||
super();
|
||||
this.configMgr = configMgr;
|
||||
this.settings = settings;
|
||||
this._importCss();
|
||||
this.defaultPalette = this.getDefaultPalette();
|
||||
|
||||
// A random number to denote an update on the css, usually the possible next version
|
||||
// in extensions.gnome.org
|
||||
// TODO: need to research the most effective way to bring in CSS updates
|
||||
// since the schema css-last-update might be triggered when there is a
|
||||
// code change on the schema unrelated to css updates.
|
||||
// For now tagging works. See @this.patchCss() and @this._needUpdate().
|
||||
this.cssTag = 37;
|
||||
|
||||
// TODO: should the patchCss() call be done here?
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
addPx(value) {
|
||||
return `${value}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
removePx(value) {
|
||||
return value.replace("px", "");
|
||||
}
|
||||
|
||||
getDefaultPalette() {
|
||||
return {
|
||||
tiled: this.getDefaults("tiled"),
|
||||
split: this.getDefaults("split"),
|
||||
floated: this.getDefaults("floated"),
|
||||
stacked: this.getDefaults("stacked"),
|
||||
tabbed: this.getDefaults("tabbed"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The scheme name is in between the CSS selector name
|
||||
* E.g. window-tiled-color should return `tiled`
|
||||
* @param {string} selector
|
||||
*/
|
||||
getColorSchemeBySelector(selector) {
|
||||
if (!selector.includes("-")) return null;
|
||||
let firstDash = selector.indexOf("-");
|
||||
let secondDash = selector.indexOf("-", firstDash + 1);
|
||||
const scheme = selector.substr(firstDash + 1, secondDash - firstDash - 1);
|
||||
return scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} color
|
||||
*/
|
||||
getDefaults(color) {
|
||||
return {
|
||||
color: this.getCssProperty(`.${color}`, "color").value,
|
||||
"border-width": this.removePx(this.getCssProperty(`.${color}`, "border-width").value),
|
||||
opacity: this.getCssProperty(`.${color}`, "opacity").value,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} selector
|
||||
*/
|
||||
getCssRule(selector) {
|
||||
if (this.cssAst) {
|
||||
const rules = this.cssAst.stylesheet.rules;
|
||||
// return only the first match, Forge CSS authors should make sure class names are unique :)
|
||||
const matchRules = rules.filter((r) => r.selectors.filter((s) => s === selector).length > 0);
|
||||
return matchRules.length > 0 ? matchRules[0] : {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {string} propertyName
|
||||
*/
|
||||
getCssProperty(selector, propertyName) {
|
||||
const cssRule = this.getCssRule(selector);
|
||||
|
||||
if (cssRule) {
|
||||
const matchDeclarations = cssRule.declarations.filter((d) => d.property === propertyName);
|
||||
return matchDeclarations.length > 0 ? matchDeclarations[0] : {};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {string} propertyName
|
||||
* @param {string} propertyValue
|
||||
*/
|
||||
setCssProperty(selector, propertyName, propertyValue) {
|
||||
const cssProperty = this.getCssProperty(selector, propertyName);
|
||||
if (cssProperty) {
|
||||
cssProperty.value = propertyValue;
|
||||
this._updateCss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AST for stylesheet.css
|
||||
*/
|
||||
_importCss() {
|
||||
let cssFile = this.configMgr.stylesheetFile;
|
||||
if (!cssFile || !production) {
|
||||
cssFile = this.configMgr.defaultStylesheetFile;
|
||||
}
|
||||
|
||||
let [success, contents] = cssFile.load_contents(null);
|
||||
if (success) {
|
||||
const cssContents = new TextDecoder().decode(contents);
|
||||
this.cssAst = parse(cssContents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the AST back to stylesheet.css and reloads the theme
|
||||
*/
|
||||
_updateCss() {
|
||||
if (!this.cssAst) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cssFile = this.configMgr.stylesheetFile;
|
||||
if (!cssFile || !production) {
|
||||
cssFile = this.configMgr.defaultStylesheetFile;
|
||||
}
|
||||
|
||||
const cssContents = stringify(this.cssAst);
|
||||
const PERMISSIONS_MODE = 0o744;
|
||||
|
||||
if (GLib.mkdir_with_parents(cssFile.get_parent().get_path(), PERMISSIONS_MODE) === 0) {
|
||||
let [success, _tag] = cssFile.replace_contents(
|
||||
cssContents,
|
||||
null,
|
||||
false,
|
||||
Gio.FileCreateFlags.REPLACE_DESTINATION,
|
||||
null
|
||||
);
|
||||
if (success) {
|
||||
this.reloadStylesheet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BREAKING: Patches the CSS by overriding the $HOME/.config stylesheet
|
||||
* at the moment.
|
||||
*
|
||||
* TODO: work needed to consolidate the existing config stylesheet and
|
||||
* when the extension default stylesheet gets an update.
|
||||
*/
|
||||
patchCss() {
|
||||
if (this._needUpdate()) {
|
||||
let originalCss = this.configMgr.defaultStylesheetFile;
|
||||
let configCss = this.configMgr.stylesheetFile;
|
||||
let copyConfigCss = Gio.File.new_for_path(this.configMgr.stylesheetFileName + ".bak");
|
||||
let backupFine = configCss.copy(copyConfigCss, Gio.FileCopyFlags.OVERWRITE, null, null);
|
||||
let copyFine = originalCss.copy(configCss, Gio.FileCopyFlags.OVERWRITE, null, null);
|
||||
if (backupFine && copyFine) {
|
||||
this.settings.set_uint("css-last-update", this.cssTag);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Credits: ExtensionSystem.js:_callExtensionEnable()
|
||||
*/
|
||||
reloadStylesheet() {
|
||||
throw new Error("Must implement reloadStylesheet");
|
||||
}
|
||||
|
||||
_needUpdate() {
|
||||
let cssTag = this.cssTag;
|
||||
return this.settings.get_uint("css-last-update") !== cssTag;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Credits: Color Space conversion functions from CSS Tricks
|
||||
* https://css-tricks.com/converting-color-spaces-in-javascript/
|
||||
*/
|
||||
export function RGBAToHexA(rgba) {
|
||||
let sep = rgba.indexOf(",") > -1 ? "," : " ";
|
||||
rgba = rgba.substr(5).split(")")[0].split(sep);
|
||||
|
||||
// Strip the slash if using space-separated syntax
|
||||
if (rgba.indexOf("/") > -1) rgba.splice(3, 1);
|
||||
|
||||
for (let R in rgba) {
|
||||
let r = rgba[R];
|
||||
if (r.indexOf("%") > -1) {
|
||||
let p = r.substr(0, r.length - 1) / 100;
|
||||
|
||||
if (R < 3) {
|
||||
rgba[R] = Math.round(p * 255);
|
||||
} else {
|
||||
rgba[R] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
let r = (+rgba[0]).toString(16),
|
||||
g = (+rgba[1]).toString(16),
|
||||
b = (+rgba[2]).toString(16),
|
||||
a = Math.round(+rgba[3] * 255).toString(16);
|
||||
|
||||
if (r.length == 1) r = "0" + r;
|
||||
if (g.length == 1) g = "0" + g;
|
||||
if (b.length == 1) b = "0" + b;
|
||||
if (a.length == 1) a = "0" + a;
|
||||
|
||||
return "#" + r + g + b + a;
|
||||
}
|
||||
|
||||
export function hexAToRGBA(h) {
|
||||
let r = 0,
|
||||
g = 0,
|
||||
b = 0,
|
||||
a = 1;
|
||||
|
||||
if (h.length == 5) {
|
||||
r = "0x" + h[1] + h[1];
|
||||
g = "0x" + h[2] + h[2];
|
||||
b = "0x" + h[3] + h[3];
|
||||
a = "0x" + h[4] + h[4];
|
||||
} else if (h.length == 9) {
|
||||
r = "0x" + h[1] + h[2];
|
||||
g = "0x" + h[3] + h[4];
|
||||
b = "0x" + h[5] + h[6];
|
||||
a = "0x" + h[7] + h[8];
|
||||
}
|
||||
a = +(a / 255).toFixed(3);
|
||||
|
||||
return "rgba(" + +r + "," + +g + "," + +b + "," + a + ")";
|
||||
}
|
||||
Reference in New Issue
Block a user