mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-02-23 23:54:13 +08:00
317 lines
8 KiB
TypeScript
317 lines
8 KiB
TypeScript
import temp from 'temp';
|
|
import os from 'os';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import { execSync } from 'child_process';
|
|
import ini from 'ini';
|
|
|
|
const Context = {
|
|
ACTIONS: 'actions',
|
|
ANIMATIONS: 'animations',
|
|
APPLICATIONS: 'applications',
|
|
CATEGORIES: 'categories',
|
|
DEVICES: 'devices',
|
|
EMBLEMS: 'emblems',
|
|
EMOTES: 'emotes',
|
|
INTERNATIONAL: 'international',
|
|
MIMETYPES: 'mimetypes',
|
|
PANEL: 'panel',
|
|
PLACES: 'places',
|
|
STATUS: 'status',
|
|
};
|
|
|
|
const HOME = os.homedir();
|
|
|
|
const ICON_THEME_PATHS = [
|
|
path.join(HOME, '.local/share/icons'),
|
|
path.join(HOME, '.icons'),
|
|
'/usr/share/icons',
|
|
'/usr/local/share/icons',
|
|
];
|
|
|
|
const DESKTOP = process.env.XDG_CURRENT_DESKTOP;
|
|
|
|
const ICON_EXTENSION = ['.png', '.svg'];
|
|
|
|
/**
|
|
* Create a gsettings command string for icons themes
|
|
*
|
|
* @returns {string} command
|
|
* @private
|
|
*/
|
|
function __getGsettingsIconThemeCMD(): string {
|
|
const path = __getDesktopSettingsPath();
|
|
const key = 'icon-theme';
|
|
return path != null ? `gsettings get ${path} ${key}` : null;
|
|
}
|
|
|
|
/**
|
|
* Create a gsettings command string for themes
|
|
*
|
|
* @returns {string} command
|
|
* @private
|
|
*/
|
|
function __getGsettingsGtkThemeCMD(): string {
|
|
const path = __getDesktopSettingsPath();
|
|
const key = 'gtk-theme';
|
|
return path != null ? `gsettings ${path} ${key}` : null;
|
|
}
|
|
|
|
/**
|
|
* Returns the path for the gsettings command. Currently only GNOME and MATE are supported
|
|
*
|
|
* @returns {*}
|
|
* @private
|
|
*/
|
|
function __getDesktopSettingsPath(): string {
|
|
switch (DESKTOP) {
|
|
case 'MATE':
|
|
return 'org.mate.interface';
|
|
case 'GNOME':
|
|
case 'Budgie:GNOME':
|
|
return 'org.gnome.desktop.interface';
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute a command and return the string result
|
|
*
|
|
* @param {string} cmd to execute
|
|
* @returns {string} result of the command
|
|
* @private
|
|
*/
|
|
function __exec(cmd: string): string {
|
|
try {
|
|
return cmd == null
|
|
? null
|
|
: execSync(cmd)
|
|
.toString()
|
|
.trim()
|
|
.replace(/'/g, '');
|
|
} catch (error) {
|
|
console.warn(error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the current icon theme
|
|
*
|
|
* @returns {string} name of the icon
|
|
*/
|
|
function getIconThemeName(): string {
|
|
return __exec(__getGsettingsIconThemeCMD());
|
|
}
|
|
|
|
/**
|
|
* Get the current GTK theme
|
|
*
|
|
* @returns {string} name of the theme
|
|
*/
|
|
function getThemeName(): string {
|
|
return __exec(__getGsettingsGtkThemeCMD());
|
|
}
|
|
|
|
/**
|
|
* Parse the icon themes index.theme file
|
|
*
|
|
* @param {string} themePath path to the themes root
|
|
* @returns {object} parsed ini file
|
|
* @private
|
|
*/
|
|
function __parseIconTheme(themePath: string): object {
|
|
const themeIndex = path.join(themePath, 'index.theme');
|
|
if (fs.existsSync(themeIndex)) {
|
|
return ini.parse(fs.readFileSync(themeIndex, 'utf-8'));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find the path to the icon theme and parse it
|
|
*
|
|
* @param {string} themeName to parse
|
|
* @returns {IconTheme} containing the parsed index.theme file and path to the theme
|
|
*/
|
|
function getIconTheme(themeName): IconTheme {
|
|
if (themeName != null) {
|
|
for (const themesPath of ICON_THEME_PATHS) {
|
|
const themePath = path.join(themesPath, themeName);
|
|
const parsed = __parseIconTheme(themePath);
|
|
if (parsed != null) {
|
|
return {
|
|
themePath: themePath,
|
|
data: parsed,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
type IconTheme = {
|
|
themePath: string;
|
|
data: object;
|
|
};
|
|
|
|
/**
|
|
* Get all possible icon paths
|
|
*
|
|
* @param {IconTheme} theme data
|
|
* @param {string} iconName name of the icon
|
|
* @param {number} size of the icon
|
|
* @param {string|string[]} contexts of the icon
|
|
* @param {1|2} [scale=1] of the icon
|
|
* @returns {string[]} with all possibilities of the icon in one theme
|
|
* @private
|
|
*/
|
|
function __getAllIconPaths(
|
|
theme: IconTheme,
|
|
iconName: string,
|
|
size: number,
|
|
contexts: string | string[],
|
|
scale: 1 | 2 = 1
|
|
): string[] {
|
|
const icons = [];
|
|
|
|
if (!(contexts instanceof Array)) {
|
|
contexts = [contexts];
|
|
}
|
|
|
|
for (const [sectionName, section] of Object.entries(theme.data)) {
|
|
if (sectionName !== 'Icon Theme') {
|
|
const _context = (section.Context || '').toLowerCase();
|
|
const _size = parseInt(section.Size, 10);
|
|
const _minSize = parseInt(section.MinSize || section.Size, 10);
|
|
const _maxSize = parseInt(section.MaxSize || section.Size, 10);
|
|
const _scale = parseInt(section.Scale || 1, 10);
|
|
|
|
if (
|
|
contexts.indexOf(_context) > -1 &&
|
|
(_size === size || (_minSize <= size && size <= _maxSize)) &&
|
|
(scale == null || scale === _scale)
|
|
) {
|
|
const iconDir = path.join(theme.themePath, sectionName);
|
|
for (const extension of ICON_EXTENSION) {
|
|
const iconPath = path.join(iconDir, iconName + extension);
|
|
if (fs.existsSync(iconPath)) {
|
|
icons.push(iconPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return icons;
|
|
}
|
|
|
|
/**
|
|
* Get the icon from a theme
|
|
*
|
|
* @param {IconTheme} theme name
|
|
* @param {string} iconName name of the icon
|
|
* @param {number} size of the icon
|
|
* @param {string|string[]} contexts to search in
|
|
* @param {1|2} [scale=1] of the icon
|
|
* @returns {string} path to the icon or null
|
|
*/
|
|
function getIconFromTheme(
|
|
theme: IconTheme,
|
|
iconName: string,
|
|
size: number,
|
|
contexts: string | string[],
|
|
scale: 1 | 2 = 1
|
|
) {
|
|
const icons = __getAllIconPaths(theme, iconName, size, contexts, scale);
|
|
for (let path of icons) {
|
|
if (fs.existsSync(path)) {
|
|
return fs.realpathSync(path);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the absolute path to the icon. If the icon is not found in the theme itself, the inherited
|
|
* themes will be searched
|
|
*
|
|
* @param {string} iconName to search for
|
|
* @param {number} size of the icon
|
|
* @param {array|string} context the icons context
|
|
* @param {1|2} [scale=1] 1 = normal, 2 = HiDPI version
|
|
* @returns {string} absolute path of the icon
|
|
*/
|
|
function getIconPath(iconName: string, size: number, context: string | string[], scale: 1 | 2 = 1) {
|
|
let defaultTheme = getIconTheme(getIconThemeName());
|
|
if (defaultTheme != null) {
|
|
let inherits = defaultTheme.data['Icon Theme']['Inherits'].split(',');
|
|
|
|
let icon = getIconFromTheme(defaultTheme, iconName, size, context, scale);
|
|
|
|
if (icon !== null) {
|
|
return icon;
|
|
}
|
|
|
|
// in case the icon was not found in the theme, we search the inherited themes
|
|
for (let key of inherits) {
|
|
let inheritsTheme = getIconTheme(inherits[key]);
|
|
icon = getIconFromTheme(inheritsTheme, iconName, size, context);
|
|
if (icon !== null) {
|
|
return icon;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Return an icon from the current icon theme
|
|
*
|
|
* @param {string} iconName name of the icon you want to search for (i.e. mailspring)
|
|
* @param {number} [size=22] size of the icon, if no exact size is found, the next possible one will be chosen
|
|
* @param {array|string} [context=Context.APPLICATIONS] icon context to search in, defaults to APPLICATIONS
|
|
* @param {number} [scale=2] icon scale, defaults to HiDPI version
|
|
* @returns {string} path to the icon
|
|
*/
|
|
function getIcon(
|
|
iconName,
|
|
size = 22,
|
|
context: string | string[] = [Context.APPLICATIONS],
|
|
scale: 1 | 2 = 2
|
|
) {
|
|
if (process.platform !== 'linux') {
|
|
throw Error('getIcon only works on linux');
|
|
}
|
|
|
|
return getIconPath(iconName, size, context, scale);
|
|
}
|
|
|
|
/**
|
|
* Convert any icon to a png using ImageMagick. If ImageMagick is not present the icon cannot be
|
|
* converted.
|
|
*
|
|
* @param {string} iconName to name the tmp file
|
|
* @param {string} iconPath to the original icon to be converted
|
|
* @returns {string} path to the converted tmp file
|
|
*/
|
|
function convertToPNG(iconName: string, iconPath: string) {
|
|
try {
|
|
const version = execSync('convert --version')
|
|
.toString()
|
|
.trim();
|
|
if (!version) {
|
|
console.warn('Cannot find ImageMagick');
|
|
return null;
|
|
}
|
|
const tmpFile = temp.openSync({ prefix: iconName, suffix: '.png' });
|
|
execSync(`convert ${iconPath} -transparent white ${tmpFile.path}`);
|
|
return tmpFile.path;
|
|
} catch (error) {
|
|
console.warn(error);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export { convertToPNG, getIcon, getIconThemeName, getThemeName, Context };
|