hyprpanel

This commit is contained in:
wt committed 2024-10-23 21:12:15 +07:00
1 parent 10080a83ee
commit 5151ac24da
322 files changed
+38276

No files matched your search

+77
View File
@@ -0,0 +1,77 @@
import GLib from 'gi://GLib?version=2.0';
import { GenericFunction } from 'lib/types/customModules/generic';
import { Bind } from 'lib/types/variable';
import { Variable as VariableType } from 'types/variable';
/**
* @param {VariableType<T>} targetVariable - The Variable to update with the function's result.
* @param {Array<Bind>} trackers - Array of trackers to watch.
* @param {Bind} pollingInterval - The polling interval in milliseconds.
* @param {GenericFunction<T, P>} someFunc - The function to execute at each interval, which updates the Variable.
* @param {...P} params - Parameters to pass to someFunc.
*/
export const pollVariable = <T, P extends unknown[], F extends GenericFunction<T, P>>(
targetVariable: VariableType<T>,
trackers: Array<Bind>,
pollingInterval: Bind,
someFunc: F,
...params: P
): void => {
let intervalInstance: number | null = null;
const intervalFn = (pollIntrvl: number): void => {
if (intervalInstance !== null) {
GLib.source_remove(intervalInstance);
}
intervalInstance = Utils.interval(pollIntrvl, () => {
targetVariable.value = someFunc(...params);
});
};
Utils.merge([pollingInterval, ...trackers], (pollIntrvl: number) => {
intervalFn(pollIntrvl);
});
};
/**
* @param {VariableType<T>} targetVariable - The Variable to update with the result of the command.
* @param {Array<Bind>} trackers - Array of trackers to watch.
* @param {Bind} pollingInterval - The polling interval in milliseconds.
* @param {string} someCommand - The bash command to execute.
* @param {GenericFunction<T, [unknown, ...P]>} someFunc - The function to execute after processing the command result;
* with the first argument being the result of the command execution.
* @param {...P} params - Additional parameters to pass to someFunc.
*/
export const pollVariableBash = <T, P extends unknown[], F extends GenericFunction<T, [string, ...P]>>(
targetVariable: VariableType<T>,
trackers: Array<Bind>,
pollingInterval: Bind,
someCommand: string,
someFunc: F,
...params: P
): void => {
let intervalInstance: number | null = null;
const intervalFn = (pollIntrvl: number): void => {
if (intervalInstance !== null) {
GLib.source_remove(intervalInstance);
}
intervalInstance = Utils.interval(pollIntrvl, () => {
Utils.execAsync(`bash -c "${someCommand}"`)
.then((res: string) => {
try {
targetVariable.value = someFunc(res, ...params);
} catch (error) {
console.warn(`An error occurred when running interval bash function: ${error}`);
}
})
.catch((err) => console.error(`Error running command "${someCommand}": ${err}`));
});
};
Utils.merge([pollingInterval, ...trackers], (pollIntrvl: number) => {
intervalFn(pollIntrvl);
});
};
+595
View File
@@ -0,0 +1,595 @@
import { Option } from 'widget/settings/shared/Option';
import { Header } from 'widget/settings/shared/Header';
import options from 'options';
import Scrollable from 'types/widgets/scrollable';
import { Attribute, GtkWidget } from 'lib/types/widget';
export const CustomModuleSettings = (): Scrollable<GtkWidget, Attribute> =>
Widget.Scrollable({
vscroll: 'automatic',
hscroll: 'automatic',
class_name: 'menu-theme-page customModules paged-container',
child: Widget.Box({
class_name: 'menu-theme-page paged-container',
vertical: true,
children: [
/*
************************************
* GENERAL *
************************************
*/
Header('General'),
Option({
opt: options.bar.customModules.scrollSpeed,
title: 'Scrolling Speed',
type: 'number',
}),
/*
************************************
* RAM *
************************************
*/
Header('RAM'),
Option({
opt: options.theme.bar.buttons.modules.ram.enableBorder,
title: 'Button Border',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.ram.icon,
title: 'Ram Icon',
type: 'string',
}),
Option({
opt: options.bar.customModules.ram.label,
title: 'Show Label',
type: 'boolean',
}),
Option({
opt: options.theme.bar.buttons.modules.ram.spacing,
title: 'Spacing',
type: 'string',
}),
Option({
opt: options.bar.customModules.ram.labelType,
title: 'Label Type',
type: 'enum',
enums: ['used/total', 'used', 'free', 'percentage'],
}),
Option({
opt: options.bar.customModules.ram.round,
title: 'Round',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.ram.pollingInterval,
title: 'Polling Interval',
type: 'number',
min: 100,
max: 60 * 24 * 1000,
increment: 1000,
}),
Option({
opt: options.bar.customModules.ram.leftClick,
title: 'Left Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.ram.rightClick,
title: 'Right Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.ram.middleClick,
title: 'Middle Click',
type: 'string',
}),
/*
************************************
* CPU *
************************************
*/
Header('CPU'),
Option({
opt: options.theme.bar.buttons.modules.cpu.enableBorder,
title: 'Button Border',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.cpu.icon,
title: 'Cpu Icon',
type: 'string',
}),
Option({
opt: options.bar.customModules.cpu.label,
title: 'Show Label',
type: 'boolean',
}),
Option({
opt: options.theme.bar.buttons.modules.cpu.spacing,
title: 'Spacing',
type: 'string',
}),
Option({
opt: options.bar.customModules.cpu.round,
title: 'Round',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.cpu.pollingInterval,
title: 'Polling Interval',
type: 'number',
min: 100,
max: 60 * 24 * 1000,
increment: 1000,
}),
Option({
opt: options.bar.customModules.cpu.leftClick,
title: 'Left Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.cpu.rightClick,
title: 'Right Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.cpu.middleClick,
title: 'Middle Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.cpu.scrollUp,
title: 'Scroll Up',
type: 'string',
}),
Option({
opt: options.bar.customModules.cpu.scrollDown,
title: 'Scroll Down',
type: 'string',
}),
/*
************************************
* STORAGE *
************************************
*/
Header('Storage'),
Option({
opt: options.theme.bar.buttons.modules.storage.enableBorder,
title: 'Button Border',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.storage.icon,
title: 'Storage Icon',
type: 'string',
}),
Option({
opt: options.bar.customModules.storage.label,
title: 'Show Label',
type: 'boolean',
}),
Option({
opt: options.theme.bar.buttons.modules.storage.spacing,
title: 'Spacing',
type: 'string',
}),
Option({
opt: options.bar.customModules.storage.labelType,
title: 'Label Type',
type: 'enum',
enums: ['used/total', 'used', 'free', 'percentage'],
}),
Option({
opt: options.bar.customModules.storage.round,
title: 'Round',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.storage.pollingInterval,
title: 'Polling Interval',
type: 'number',
min: 100,
max: 60 * 24 * 1000,
increment: 1000,
}),
Option({
opt: options.bar.customModules.storage.leftClick,
title: 'Left Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.storage.rightClick,
title: 'Right Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.storage.middleClick,
title: 'Middle Click',
type: 'string',
}),
/*
************************************
* NETSTAT *
************************************
*/
Header('Netstat'),
Option({
opt: options.theme.bar.buttons.modules.netstat.enableBorder,
title: 'Button Border',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.netstat.networkInterface,
title: 'Network Interface',
subtitle:
"Name of the network interface to poll.\nHINT: Get list of interfaces with 'cat /proc/net/dev'",
type: 'string',
}),
Option({
opt: options.bar.customModules.netstat.icon,
title: 'Netstat Icon',
type: 'string',
}),
Option({
opt: options.bar.customModules.netstat.label,
title: 'Show Label',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.netstat.rateUnit,
title: 'Rate Unit',
type: 'enum',
enums: ['GiB', 'MiB', 'KiB', 'auto'],
}),
Option({
opt: options.theme.bar.buttons.modules.netstat.spacing,
title: 'Spacing',
type: 'string',
}),
Option({
opt: options.bar.customModules.netstat.labelType,
title: 'Label Type',
type: 'enum',
enums: ['full', 'in', 'out'],
}),
Option({
opt: options.bar.customModules.netstat.round,
title: 'Round',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.netstat.pollingInterval,
title: 'Polling Interval',
type: 'number',
min: 100,
max: 60 * 24 * 1000,
increment: 1000,
}),
Option({
opt: options.bar.customModules.netstat.leftClick,
title: 'Left Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.netstat.rightClick,
title: 'Right Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.netstat.middleClick,
title: 'Middle Click',
type: 'string',
}),
/*
************************************
* KEYBOARD LAYOUT *
************************************
*/
Header('Keyboard Layout'),
Option({
opt: options.theme.bar.buttons.modules.kbLayout.enableBorder,
title: 'Button Border',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.kbLayout.icon,
title: 'Keyboard Layout Icon',
type: 'string',
}),
Option({
opt: options.bar.customModules.kbLayout.label,
title: 'Show Label',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.kbLayout.labelType,
title: 'Label Type',
type: 'enum',
enums: ['layout', 'code'],
}),
Option({
opt: options.theme.bar.buttons.modules.kbLayout.spacing,
title: 'Spacing',
type: 'string',
}),
Option({
opt: options.bar.customModules.kbLayout.leftClick,
title: 'Left Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.kbLayout.rightClick,
title: 'Right Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.kbLayout.middleClick,
title: 'Middle Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.kbLayout.scrollUp,
title: 'Scroll Up',
type: 'string',
}),
Option({
opt: options.bar.customModules.kbLayout.scrollDown,
title: 'Scroll Down',
type: 'string',
}),
/*
************************************
* UPDATES *
************************************
*/
Header('Updates'),
Option({
opt: options.theme.bar.buttons.modules.updates.enableBorder,
title: 'Button Border',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.updates.updateCommand,
title: 'Check Updates Command',
type: 'string',
}),
Option({
opt: options.bar.customModules.updates.icon,
title: 'Updates Icon',
type: 'string',
}),
Option({
opt: options.bar.customModules.updates.label,
title: 'Show Label',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.updates.padZero,
title: 'Pad with 0',
type: 'boolean',
}),
Option({
opt: options.theme.bar.buttons.modules.updates.spacing,
title: 'Spacing',
type: 'string',
}),
Option({
opt: options.bar.customModules.updates.pollingInterval,
title: 'Polling Interval',
type: 'number',
subtitle: "WARNING: Be careful of your package manager's rate limit.",
min: 100,
max: 60 * 24 * 1000,
increment: 1000,
}),
Option({
opt: options.bar.customModules.updates.leftClick,
title: 'Left Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.updates.rightClick,
title: 'Right Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.updates.middleClick,
title: 'Middle Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.updates.scrollUp,
title: 'Scroll Up',
type: 'string',
}),
Option({
opt: options.bar.customModules.updates.scrollDown,
title: 'Scroll Down',
type: 'string',
}),
/*
************************************
* SUBMAP *
************************************
*/
Header('Submap'),
Option({
opt: options.theme.bar.buttons.modules.submap.enableBorder,
title: 'Button Border',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.submap.showSubmapName,
title: 'Show Submap Name',
subtitle:
'When enabled, the name of the current submap will be displayed' +
' instead of the Submap Enabled or Disabled text.',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.submap.enabledIcon,
title: 'Enabled Icon',
type: 'string',
}),
Option({
opt: options.bar.customModules.submap.disabledIcon,
title: 'Disabled Icon',
type: 'string',
}),
Option({
opt: options.bar.customModules.submap.enabledText,
title: 'Enabled Text',
type: 'string',
}),
Option({
opt: options.bar.customModules.submap.disabledText,
title: 'Disabled Text',
type: 'string',
}),
Option({
opt: options.bar.customModules.submap.label,
title: 'Show Label',
type: 'boolean',
}),
Option({
opt: options.theme.bar.buttons.modules.submap.spacing,
title: 'Spacing',
type: 'string',
}),
Option({
opt: options.bar.customModules.submap.leftClick,
title: 'Left Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.submap.rightClick,
title: 'Right Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.submap.middleClick,
title: 'Middle Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.submap.scrollUp,
title: 'Scroll Up',
type: 'string',
}),
Option({
opt: options.bar.customModules.submap.scrollDown,
title: 'Scroll Down',
type: 'string',
}),
/*
************************************
* WEATHER *
************************************
*/
Header('Weather'),
Option({
opt: options.theme.bar.buttons.modules.weather.enableBorder,
title: 'Button Border',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.weather.label,
title: 'Show Label',
type: 'boolean',
}),
Option({
opt: options.bar.customModules.weather.unit,
title: 'Units',
type: 'enum',
enums: ['imperial', 'metric'],
}),
Option({
opt: options.theme.bar.buttons.modules.weather.spacing,
title: 'Spacing',
type: 'string',
}),
Option({
opt: options.bar.customModules.weather.leftClick,
title: 'Left Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.weather.rightClick,
title: 'Right Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.weather.middleClick,
title: 'Middle Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.weather.scrollUp,
title: 'Scroll Up',
type: 'string',
}),
Option({
opt: options.bar.customModules.weather.scrollDown,
title: 'Scroll Down',
type: 'string',
}),
/*
************************************
* POWER *
************************************
*/
Header('Power'),
Option({
opt: options.theme.bar.buttons.modules.power.enableBorder,
title: 'Button Border',
type: 'boolean',
}),
Option({
opt: options.theme.bar.buttons.modules.power.spacing,
title: 'Spacing',
type: 'string',
}),
Option({
opt: options.bar.customModules.power.icon,
title: 'Power Button Icon',
type: 'string',
}),
Option({
opt: options.bar.customModules.power.leftClick,
title: 'Left Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.power.rightClick,
title: 'Right Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.power.middleClick,
title: 'Middle Click',
type: 'string',
}),
Option({
opt: options.bar.customModules.power.scrollUp,
title: 'Scroll Up',
type: 'string',
}),
Option({
opt: options.bar.customModules.power.scrollDown,
title: 'Scroll Down',
type: 'string',
}),
],
}),
});
@@ -0,0 +1,20 @@
// @ts-expect-error: This import is a special directive that tells the compiler to use the GTop library
import GTop from 'gi://GTop';
let previousCpuData = new GTop.glibtop_cpu();
GTop.glibtop_get_cpu(previousCpuData);
export const computeCPU = (): number => {
const currentCpuData = new GTop.glibtop_cpu();
GTop.glibtop_get_cpu(currentCpuData);
// Calculate the differences from the previous to current data
const totalDiff = currentCpuData.total - previousCpuData.total;
const idleDiff = currentCpuData.idle - previousCpuData.idle;
const cpuUsagePercentage = totalDiff > 0 ? ((totalDiff - idleDiff) / totalDiff) * 100 : 0;
previousCpuData = currentCpuData;
return cpuUsagePercentage;
};
+69
View File
@@ -0,0 +1,69 @@
import options from 'options';
// Module initializer
import { module } from '../module';
import Button from 'types/widgets/button';
// Utility Methods
import { inputHandler } from 'customModules/utils';
import { computeCPU } from './computeCPU';
import { pollVariable } from 'customModules/PollVar';
import { BarBoxChild } from 'lib/types/bar';
import { Attribute, Child } from 'lib/types/widget';
// All the user configurable options for the cpu module that are needed
const { label, round, leftClick, rightClick, middleClick, scrollUp, scrollDown, pollingInterval, icon } =
options.bar.customModules.cpu;
export const cpuUsage = Variable(0);
pollVariable(
// Variable to poll and update with the result of the function passed in
cpuUsage,
// Variables that should trigger the polling function to update when they change
[round.bind('value')],
// Interval at which to poll
pollingInterval.bind('value'),
// Function to execute to get the network data
computeCPU,
);
export const Cpu = (): BarBoxChild => {
const renderLabel = (cpuUsg: number, rnd: boolean): string => {
return rnd ? `${Math.round(cpuUsg)}%` : `${cpuUsg.toFixed(2)}%`;
};
const cpuModule = module({
textIcon: icon.bind('value'),
label: Utils.merge([cpuUsage.bind('value'), round.bind('value')], (cpuUsg, rnd) => {
return renderLabel(cpuUsg, rnd);
}),
tooltipText: 'CPU',
boxClass: 'cpu',
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Child, Attribute>) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
cmd: scrollUp,
},
onScrollDown: {
cmd: scrollDown,
},
});
},
},
});
return cpuModule;
};
@@ -0,0 +1,28 @@
import {
HyprctlDeviceLayout,
HyprctlKeyboard,
KbLabelType,
LayoutKeys,
LayoutValues,
} from 'lib/types/customModules/kbLayout';
import { layoutMap } from './layouts';
export const getKeyboardLayout = (obj: string, format: KbLabelType): LayoutKeys | LayoutValues => {
const hyprctlDevices: HyprctlDeviceLayout = JSON.parse(obj);
const keyboards = hyprctlDevices['keyboards'];
if (keyboards.length === 0) {
return format === 'code' ? 'Unknown' : 'Unknown Layout';
}
let mainKb = keyboards.find((kb: HyprctlKeyboard) => kb.main);
if (!mainKb) {
mainKb = keyboards[keyboards.length - 1];
}
const layout: LayoutKeys = mainKb['active_keymap'] as LayoutKeys;
const foundLayout: LayoutValues = layoutMap[layout];
return format === 'code' ? foundLayout || layout : layout;
};
@@ -0,0 +1,73 @@
const hyprland = await Service.import('hyprland');
import options from 'options';
import { module } from '../module';
import { inputHandler } from 'customModules/utils';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from 'types/widgets/button';
import Label from 'types/widgets/label';
import { getKeyboardLayout } from './getLayout';
import { BarBoxChild } from 'lib/types/bar';
import { Attribute, Child } from 'lib/types/widget';
const { label, labelType, icon, leftClick, rightClick, middleClick, scrollUp, scrollDown } =
options.bar.customModules.kbLayout;
export const KbInput = (): BarBoxChild => {
const keyboardModule = module({
textIcon: icon.bind('value'),
tooltipText: '',
labelHook: (self: Label<Gtk.Widget>): void => {
self.hook(
hyprland,
() => {
Utils.execAsync('hyprctl devices -j')
.then((obj) => {
self.label = getKeyboardLayout(obj, labelType.value);
})
.catch((err) => {
console.error(err);
});
},
'keyboard-layout',
);
self.hook(labelType, () => {
Utils.execAsync('hyprctl devices -j')
.then((obj) => {
self.label = getKeyboardLayout(obj, labelType.value);
})
.catch((err) => {
console.error(err);
});
});
},
boxClass: 'kblayout',
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Child, Attribute>) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
cmd: scrollUp,
},
onScrollDown: {
cmd: scrollDown,
},
});
},
},
});
return keyboardModule;
};
@@ -0,0 +1,585 @@
export const layoutMap = {
'Abkhazian (Russia)': 'RU (Ab)',
Akan: 'GH (Akan)',
Albanian: 'AL',
'Albanian (Plisi)': 'AL (Plisi)',
'Albanian (Veqilharxhi)': 'AL (Veqilharxhi)',
Amharic: 'ET',
Arabic: 'ARA',
'Arabic (Algeria)': 'DZ (Ar)',
'Arabic (AZERTY, Eastern Arabic numerals)': 'ARA (Azerty Digits)',
'Arabic (AZERTY)': 'ARA (Azerty)',
'Arabic (Buckwalter)': 'ARA (Buckwalter)',
'Arabic (Eastern Arabic numerals)': 'ARA (Digits)',
'Arabic (Macintosh)': 'ARA (Mac)',
'Arabic (Morocco)': 'MA',
'Arabic (OLPC)': 'ARA (Olpc)',
'Arabic (Pakistan)': 'PK (Ara)',
'Arabic (QWERTY, Eastern Arabic numerals)': 'ARA (Qwerty Digits)',
'Arabic (QWERTY)': 'ARA (Qwerty)',
'Arabic (Syria)': 'SY',
Armenian: 'AM',
'Armenian (alt. eastern)': 'AM (Eastern-Alt)',
'Armenian (alt. phonetic)': 'AM (Phonetic-Alt)',
'Armenian (eastern)': 'AM (Eastern)',
'Armenian (phonetic)': 'AM (Phonetic)',
'Armenian (western)': 'AM (Western)',
'Asturian (Spain, with bottom-dot H and L)': 'ES (Ast)',
Avatime: 'GH (Avn)',
Azerbaijani: 'AZ',
'Azerbaijani (Cyrillic)': 'AZ (Cyrillic)',
'Azerbaijani (Iran)': 'IR (Azb)',
Bambara: 'ML',
Bangla: 'BD',
'Bangla (India, Baishakhi InScript)': 'IN (Ben Inscript)',
'Bangla (India, Baishakhi)': 'IN (Ben Baishakhi)',
'Bangla (India, Bornona)': 'IN (Ben Bornona)',
'Bangla (India, Gitanjali)': 'IN (Ben Gitanjali)',
'Bangla (India, Probhat)': 'IN (Ben Probhat)',
'Bangla (India)': 'IN (Ben)',
'Bangla (Probhat)': 'BD (Probhat)',
Bashkirian: 'RU (Bak)',
Belarusian: 'BY',
'Belarusian (intl.)': 'BY (Intl)',
'Belarusian (Latin)': 'BY (Latin)',
'Belarusian (legacy)': 'BY (Legacy)',
'Belarusian (phonetic)': 'BY (Phonetic)',
Belgian: 'BE',
'Belgian (alt.)': 'BE (Oss)',
'Belgian (ISO, alt.)': 'BE (Iso-Alternate)',
'Belgian (Latin-9 only, alt.)': 'BE (Oss Latin9)',
'Belgian (no dead keys)': 'BE (Nodeadkeys)',
'Belgian (Wang 724 AZERTY)': 'BE (Wang)',
'Berber (Algeria, Latin)': 'DZ',
'Berber (Algeria, Tifinagh)': 'DZ (Ber)',
'Berber (Morocco, Tifinagh alt.)': 'MA (Tifinagh-Alt)',
'Berber (Morocco, Tifinagh extended phonetic)': 'MA (Tifinagh-Extended-Phonetic)',
'Berber (Morocco, Tifinagh extended)': 'MA (Tifinagh-Extended)',
'Berber (Morocco, Tifinagh phonetic, alt.)': 'MA (Tifinagh-Alt-Phonetic)',
'Berber (Morocco, Tifinagh phonetic)': 'MA (Tifinagh-Phonetic)',
'Berber (Morocco, Tifinagh)': 'MA (Tifinagh)',
Bosnian: 'BA',
'Bosnian (US, with Bosnian digraphs)': 'BA (Unicodeus)',
'Bosnian (US)': 'BA (Us)',
'Bosnian (with Bosnian digraphs)': 'BA (Unicode)',
'Bosnian (with guillemets)': 'BA (Alternatequotes)',
Braille: 'BRAI',
'Braille (left-handed inverted thumb)': 'BRAI (Left Hand Invert)',
'Braille (left-handed)': 'BRAI (Left Hand)',
'Braille (right-handed inverted thumb)': 'BRAI (Right Hand Invert)',
'Braille (right-handed)': 'BRAI (Right Hand)',
'Breton (France)': 'FR (Bre)',
Bulgarian: 'BG',
'Bulgarian (enhanced)': 'BG (Bekl)',
'Bulgarian (new phonetic)': 'BG (Bas Phonetic)',
'Bulgarian (traditional phonetic)': 'BG (Phonetic)',
Burmese: 'MM',
'Burmese Zawgyi': 'MM (Zawgyi)',
'Cameroon (AZERTY, intl.)': 'CM (Azerty)',
'Cameroon (Dvorak, intl.)': 'CM (Dvorak)',
'Cameroon Multilingual (QWERTY, intl.)': 'CM (Qwerty)',
'Canadian (CSA)': 'CA (Multix)',
'Catalan (Spain, with middle-dot L)': 'ES (Cat)',
Cherokee: 'US (Chr)',
Chinese: 'CN',
Chuvash: 'RU (Cv)',
'Chuvash (Latin)': 'RU (Cv Latin)',
CloGaelach: 'IE (CloGaelach)',
'Crimean Tatar (Turkish Alt-Q)': 'UA (Crh Alt)',
'Crimean Tatar (Turkish F)': 'UA (Crh F)',
'Crimean Tatar (Turkish Q)': 'UA (Crh)',
Croatian: 'HR',
'Croatian (US, with Croatian digraphs)': 'HR (Unicodeus)',
'Croatian (US)': 'HR (Us)',
'Croatian (with Croatian digraphs)': 'HR (Unicode)',
'Croatian (with guillemets)': 'HR (Alternatequotes)',
Czech: 'CZ',
'Czech (QWERTY, extended backslash)': 'CZ (Qwerty Bksl)',
'Czech (QWERTY, Macintosh)': 'CZ (Qwerty-Mac)',
'Czech (QWERTY)': 'CZ (Qwerty)',
'Czech (UCW, only accented letters)': 'CZ (Ucw)',
'Czech (US, Dvorak, UCW support)': 'CZ (Dvorak-Ucw)',
'Czech (with <\\|> key)': 'CZ (Bksl)',
Danish: 'DK',
'Danish (Dvorak)': 'DK (Dvorak)',
'Danish (Macintosh, no dead keys)': 'DK (Mac Nodeadkeys)',
'Danish (Macintosh)': 'DK (Mac)',
'Danish (no dead keys)': 'DK (Nodeadkeys)',
'Danish (Windows)': 'DK (Winkeys)',
Dari: 'AF',
'Dari (Afghanistan, OLPC)': 'AF (Fa-Olpc)',
Dhivehi: 'MV',
Dutch: 'NL',
'Dutch (Macintosh)': 'NL (Mac)',
'Dutch (standard)': 'NL (Std)',
'Dutch (US)': 'NL (Us)',
Dzongkha: 'BT',
'English (Australian)': 'AU',
'English (Cameroon)': 'CM',
'English (Canada)': 'CA (Eng)',
'English (classic Dvorak)': 'US (Dvorak-Classic)',
'English (Colemak-DH ISO)': 'US (Colemak Dh Iso)',
'English (Colemak-DH)': 'US (Colemak Dh)',
'English (Colemak)': 'US (Colemak)',
'English (Dvorak, alt. intl.)': 'US (Dvorak-Alt-Intl)',
'English (Dvorak, intl., with dead keys)': 'US (Dvorak-Intl)',
'English (Dvorak, left-handed)': 'US (Dvorak-L)',
'English (Dvorak, Macintosh)': 'US (Dvorak-Mac)',
'English (Dvorak, right-handed)': 'US (Dvorak-R)',
'English (Dvorak)': 'US (Dvorak)',
'English (Ghana, GILLBT)': 'GH (Gillbt)',
'English (Ghana, multilingual)': 'GH (Generic)',
'English (Ghana)': 'GH',
'English (India, with rupee)': 'IN (Eng)',
'English (intl., with AltGr dead keys)': 'US (Altgr-Intl)',
'English (Macintosh)': 'US (Mac)',
'English (Mali, US, intl.)': 'ML (Us-Intl)',
'English (Mali, US, Macintosh)': 'ML (Us-Mac)',
'English (Nigeria)': 'NG',
'English (Norman)': 'US (Norman)',
'English (programmer Dvorak)': 'US (Dvp)',
'English (South Africa)': 'ZA',
'English (the divide/multiply toggle the layout)': 'US (Olpc2)',
'English (UK, Colemak-DH)': 'GB (Colemak Dh)',
'English (UK, Colemak)': 'GB (Colemak)',
'English (UK, Dvorak, with UK punctuation)': 'GB (Dvorakukp)',
'English (UK, Dvorak)': 'GB (Dvorak)',
'English (UK, extended, Windows)': 'GB (Extd)',
'English (UK, intl., with dead keys)': 'GB (Intl)',
'English (UK, Macintosh, intl.)': 'GB (Mac Intl)',
'English (UK, Macintosh)': 'GB (Mac)',
'English (UK)': 'GB',
'English (US, alt. intl.)': 'US (Alt-Intl)',
'English (US, euro on 5)': 'US (Euro)',
'English (US, intl., with dead keys)': 'US (Intl)',
'English (US, Symbolic)': 'US (Symbolic)',
'English (US)': 'US',
'English (Workman, intl., with dead keys)': 'US (Workman-Intl)',
'English (Workman)': 'US (Workman)',
Esperanto: 'EPO',
'Esperanto (Brazil, Nativo)': 'BR (Nativo-Epo)',
'Esperanto (legacy)': 'EPO (Legacy)',
'Esperanto (Portugal, Nativo)': 'PT (Nativo-Epo)',
Estonian: 'EE',
'Estonian (Dvorak)': 'EE (Dvorak)',
'Estonian (no dead keys)': 'EE (Nodeadkeys)',
'Estonian (US)': 'EE (Us)',
Ewe: 'GH (Ewe)',
Faroese: 'FO',
'Faroese (no dead keys)': 'FO (Nodeadkeys)',
Filipino: 'PH',
'Filipino (Capewell-Dvorak, Baybayin)': 'PH (Capewell-Dvorak-Bay)',
'Filipino (Capewell-Dvorak, Latin)': 'PH (Capewell-Dvorak)',
'Filipino (Capewell-QWERF 2006, Baybayin)': 'PH (Capewell-Qwerf2k6-Bay)',
'Filipino (Capewell-QWERF 2006, Latin)': 'PH (Capewell-Qwerf2k6)',
'Filipino (Colemak, Baybayin)': 'PH (Colemak-Bay)',
'Filipino (Colemak, Latin)': 'PH (Colemak)',
'Filipino (Dvorak, Baybayin)': 'PH (Dvorak-Bay)',
'Filipino (Dvorak, Latin)': 'PH (Dvorak)',
'Filipino (QWERTY, Baybayin)': 'PH (Qwerty-Bay)',
Finnish: 'FI',
'Finnish (classic, no dead keys)': 'FI (Nodeadkeys)',
'Finnish (classic)': 'FI (Classic)',
'Finnish (Macintosh)': 'FI (Mac)',
'Finnish (Windows)': 'FI (Winkeys)',
French: 'FR',
'French (alt., Latin-9 only)': 'FR (Oss Latin9)',
'French (alt., no dead keys)': 'FR (Oss Nodeadkeys)',
'French (alt.)': 'FR (Oss)',
'French (AZERTY, AFNOR)': 'FR (Afnor)',
'French (AZERTY)': 'FR (Azerty)',
'French (BEPO, AFNOR)': 'FR (Bepo Afnor)',
'French (BEPO, Latin-9 only)': 'FR (Bepo Latin9)',
'French (BEPO)': 'FR (Bepo)',
'French (Cameroon)': 'CM (French)',
'French (Canada, Dvorak)': 'CA (Fr-Dvorak)',
'French (Canada, legacy)': 'CA (Fr-Legacy)',
'French (Canada)': 'CA',
'French (Democratic Republic of the Congo)': 'CD',
'French (Dvorak)': 'FR (Dvorak)',
'French (legacy, alt., no dead keys)': 'FR (Latin9 Nodeadkeys)',
'French (legacy, alt.)': 'FR (Latin9)',
'French (Macintosh)': 'FR (Mac)',
'French (Mali, alt.)': 'ML (Fr-Oss)',
'French (Morocco)': 'MA (French)',
'French (no dead keys)': 'FR (Nodeadkeys)',
'French (Switzerland, Macintosh)': 'CH (Fr Mac)',
'French (Switzerland, no dead keys)': 'CH (Fr Nodeadkeys)',
'French (Switzerland)': 'CH (Fr)',
'French (Togo)': 'TG',
'French (US)': 'FR (Us)',
'Friulian (Italy)': 'IT (Fur)',
Fula: 'GH (Fula)',
Ga: 'GH (Ga)',
Georgian: 'GE',
'Georgian (ergonomic)': 'GE (Ergonomic)',
'Georgian (France, AZERTY Tskapo)': 'FR (Geo)',
'Georgian (Italy)': 'IT (Geo)',
'Georgian (MESS)': 'GE (Mess)',
German: 'DE',
'German (Austria, Macintosh)': 'AT (Mac)',
'German (Austria, no dead keys)': 'AT (Nodeadkeys)',
'German (Austria)': 'AT',
'German (dead acute)': 'DE (Deadacute)',
'German (dead grave acute)': 'DE (Deadgraveacute)',
'German (dead tilde)': 'DE (Deadtilde)',
'German (Dvorak)': 'DE (Dvorak)',
'German (E1)': 'DE (E1)',
'German (E2)': 'DE (E2)',
'German (Macintosh, no dead keys)': 'DE (Mac Nodeadkeys)',
'German (Macintosh)': 'DE (Mac)',
'German (Neo 2)': 'DE (Neo)',
'German (no dead keys)': 'DE (Nodeadkeys)',
'German (QWERTY)': 'DE (Qwerty)',
'German (Switzerland, legacy)': 'CH (Legacy)',
'German (Switzerland, Macintosh)': 'CH (De Mac)',
'German (Switzerland, no dead keys)': 'CH (De Nodeadkeys)',
'German (Switzerland)': 'CH',
'German (T3)': 'DE (T3)',
'German (US)': 'DE (Us)',
Greek: 'GR',
'Greek (extended)': 'GR (Extended)',
'Greek (no dead keys)': 'GR (Nodeadkeys)',
'Greek (polytonic)': 'GR (Polytonic)',
'Greek (simple)': 'GR (Simple)',
Gujarati: 'IN (Guj)',
'Hanyu Pinyin Letters (with AltGr dead keys)': 'CN (Altgr-Pinyin)',
'Hausa (Ghana)': 'GH (Hausa)',
'Hausa (Nigeria)': 'NG (Hausa)',
Hawaiian: 'US (Haw)',
Hebrew: 'IL',
'Hebrew (Biblical, Tiro)': 'IL (Biblical)',
'Hebrew (lyx)': 'IL (Lyx)',
'Hebrew (phonetic)': 'IL (Phonetic)',
'Hindi (Bolnagri)': 'IN (Bolnagri)',
'Hindi (KaGaPa, phonetic)': 'IN (Hin-Kagapa)',
'Hindi (Wx)': 'IN (Hin-Wx)',
Hungarian: 'HU',
'Hungarian (no dead keys)': 'HU (Nodeadkeys)',
'Hungarian (QWERTY, 101-key, comma, dead keys)': 'HU (101 Qwerty Comma Dead)',
'Hungarian (QWERTY, 101-key, comma, no dead keys)': 'HU (101 Qwerty Comma Nodead)',
'Hungarian (QWERTY, 101-key, dot, dead keys)': 'HU (101 Qwerty Dot Dead)',
'Hungarian (QWERTY, 101-key, dot, no dead keys)': 'HU (101 Qwerty Dot Nodead)',
'Hungarian (QWERTY, 102-key, comma, dead keys)': 'HU (102 Qwerty Comma Dead)',
'Hungarian (QWERTY, 102-key, comma, no dead keys)': 'HU (102 Qwerty Comma Nodead)',
'Hungarian (QWERTY, 102-key, dot, dead keys)': 'HU (102 Qwerty Dot Dead)',
'Hungarian (QWERTY, 102-key, dot, no dead keys)': 'HU (102 Qwerty Dot Nodead)',
'Hungarian (QWERTY)': 'HU (Qwerty)',
'Hungarian (QWERTZ, 101-key, comma, dead keys)': 'HU (101 Qwertz Comma Dead)',
'Hungarian (QWERTZ, 101-key, comma, no dead keys)': 'HU (101 Qwertz Comma Nodead)',
'Hungarian (QWERTZ, 101-key, dot, dead keys)': 'HU (101 Qwertz Dot Dead)',
'Hungarian (QWERTZ, 101-key, dot, no dead keys)': 'HU (101 Qwertz Dot Nodead)',
'Hungarian (QWERTZ, 102-key, comma, dead keys)': 'HU (102 Qwertz Comma Dead)',
'Hungarian (QWERTZ, 102-key, comma, no dead keys)': 'HU (102 Qwertz Comma Nodead)',
'Hungarian (QWERTZ, 102-key, dot, dead keys)': 'HU (102 Qwertz Dot Dead)',
'Hungarian (QWERTZ, 102-key, dot, no dead keys)': 'HU (102 Qwertz Dot Nodead)',
'Hungarian (standard)': 'HU (Standard)',
Icelandic: 'IS',
'Icelandic (Dvorak)': 'IS (Dvorak)',
'Icelandic (Macintosh, legacy)': 'IS (Mac Legacy)',
'Icelandic (Macintosh)': 'IS (Mac)',
Igbo: 'NG (Igbo)',
Indian: 'IN',
'Indic IPA': 'IN (Iipa)',
'Indonesian (Arab Melayu, extended phonetic)': 'ID (Melayu-Phoneticx)',
'Indonesian (Arab Melayu, phonetic)': 'ID (Melayu-Phonetic)',
'Indonesian (Arab Pegon, phonetic)': 'ID (Pegon-Phonetic)',
'Indonesian (Latin)': 'ID',
Inuktitut: 'CA (Ike)',
Iraqi: 'IQ',
Irish: 'IE',
'Irish (UnicodeExpert)': 'IE (UnicodeExpert)',
Italian: 'IT',
'Italian (IBM 142)': 'IT (Ibm)',
'Italian (intl., with dead keys)': 'IT (Intl)',
'Italian (Macintosh)': 'IT (Mac)',
'Italian (no dead keys)': 'IT (Nodeadkeys)',
'Italian (US)': 'IT (Us)',
'Italian (Windows)': 'IT (Winkeys)',
Japanese: 'JP',
'Japanese (Dvorak)': 'JP (Dvorak)',
'Japanese (Kana 86)': 'JP (Kana86)',
'Japanese (Kana)': 'JP (Kana)',
'Japanese (Macintosh)': 'JP (Mac)',
'Japanese (OADG 109A)': 'JP (OADG109A)',
Javanese: 'ID (Javanese)',
'Kabyle (AZERTY, with dead keys)': 'DZ (Azerty-Deadkeys)',
'Kabyle (QWERTY, UK, with dead keys)': 'DZ (Qwerty-Gb-Deadkeys)',
'Kabyle (QWERTY, US, with dead keys)': 'DZ (Qwerty-Us-Deadkeys)',
Kalmyk: 'RU (Xal)',
Kannada: 'IN (Kan)',
'Kannada (KaGaPa, phonetic)': 'IN (Kan-Kagapa)',
Kashubian: 'PL (Csb)',
Kazakh: 'KZ',
'Kazakh (extended)': 'KZ (Ext)',
'Kazakh (Latin)': 'KZ (Latin)',
'Kazakh (with Russian)': 'KZ (Kazrus)',
'Khmer (Cambodia)': 'KH',
Kikuyu: 'KE (Kik)',
Komi: 'RU (Kom)',
Korean: 'KR',
'Korean (101/104-key compatible)': 'KR (Kr104)',
'Kurdish (Iran, Arabic-Latin)': 'IR (Ku Ara)',
'Kurdish (Iran, F)': 'IR (Ku F)',
'Kurdish (Iran, Latin Alt-Q)': 'IR (Ku Alt)',
'Kurdish (Iran, Latin Q)': 'IR (Ku)',
'Kurdish (Iraq, Arabic-Latin)': 'IQ (Ku Ara)',
'Kurdish (Iraq, F)': 'IQ (Ku F)',
'Kurdish (Iraq, Latin Alt-Q)': 'IQ (Ku Alt)',
'Kurdish (Iraq, Latin Q)': 'IQ (Ku)',
'Kurdish (Syria, F)': 'SY (Ku F)',
'Kurdish (Syria, Latin Alt-Q)': 'SY (Ku Alt)',
'Kurdish (Syria, Latin Q)': 'SY (Ku)',
'Kurdish (Turkey, F)': 'TR (Ku F)',
'Kurdish (Turkey, Latin Alt-Q)': 'TR (Ku Alt)',
'Kurdish (Turkey, Latin Q)': 'TR (Ku)',
Kyrgyz: 'KG',
'Kyrgyz (phonetic)': 'KG (Phonetic)',
Lao: 'LA',
'Lao (STEA)': 'LA (Stea)',
Latvian: 'LV',
'Latvian (adapted)': 'LV (Adapted)',
'Latvian (apostrophe)': 'LV (Apostrophe)',
'Latvian (ergonomic, ŪGJRMV)': 'LV (Ergonomic)',
'Latvian (F)': 'LV (Fkey)',
'Latvian (modern)': 'LV (Modern)',
'Latvian (tilde)': 'LV (Tilde)',
Lithuanian: 'LT',
'Lithuanian (IBM LST 1205-92)': 'LT (Ibm)',
'Lithuanian (LEKP)': 'LT (Lekp)',
'Lithuanian (LEKPa)': 'LT (Lekpa)',
'Lithuanian (Ratise)': 'LT (Ratise)',
'Lithuanian (standard)': 'LT (Std)',
'Lithuanian (US)': 'LT (Us)',
'Lower Sorbian': 'DE (Dsb)',
'Lower Sorbian (QWERTZ)': 'DE (Dsb Qwertz)',
Macedonian: 'MK',
'Macedonian (no dead keys)': 'MK (Nodeadkeys)',
'Malay (Jawi, Arabic Keyboard)': 'MY',
'Malay (Jawi, phonetic)': 'MY (Phonetic)',
Malayalam: 'IN (Mal)',
'Malayalam (enhanced InScript, with rupee)': 'IN (Mal Enhanced)',
'Malayalam (Lalitha)': 'IN (Mal Lalitha)',
Maltese: 'MT',
'Maltese (UK, with AltGr overrides)': 'MT (Alt-Gb)',
'Maltese (US, with AltGr overrides)': 'MT (Alt-Us)',
'Maltese (US)': 'MT (Us)',
'Manipuri (Eeyek)': 'IN (Eeyek)',
Maori: 'MAO',
'Marathi (enhanced InScript)': 'IN (Marathi)',
'Marathi (KaGaPa, phonetic)': 'IN (Mar-Kagapa)',
Mari: 'RU (Chm)',
Mmuock: 'CM (Mmuock)',
Moldavian: 'MD',
'Moldavian (Gagauz)': 'MD (Gag)',
Mon: 'MM (Mnw)',
'Mon (A1)': 'MM (Mnw-A1)',
Mongolian: 'MN',
'Mongolian (Bichig)': 'CN (Mon Trad)',
'Mongolian (Galik)': 'CN (Mon Trad Galik)',
'Mongolian (Manchu Galik)': 'CN (Mon Manchu Galik)',
'Mongolian (Manchu)': 'CN (Mon Trad Manchu)',
'Mongolian (Todo Galik)': 'CN (Mon Todo Galik)',
'Mongolian (Todo)': 'CN (Mon Trad Todo)',
'Mongolian (Xibe)': 'CN (Mon Trad Xibe)',
Montenegrin: 'ME',
'Montenegrin (Cyrillic, with guillemets)': 'ME (Cyrillicalternatequotes)',
'Montenegrin (Cyrillic, ZE and ZHE swapped)': 'ME (Cyrillicyz)',
'Montenegrin (Cyrillic)': 'ME (Cyrillic)',
'Montenegrin (Latin, QWERTY)': 'ME (Latinyz)',
'Montenegrin (Latin, Unicode, QWERTY)': 'ME (Latinunicodeyz)',
'Montenegrin (Latin, Unicode)': 'ME (Latinunicode)',
'Montenegrin (Latin, with guillemets)': 'ME (Latinalternatequotes)',
"N'Ko (AZERTY)": 'GN',
Nepali: 'NP',
'Northern Saami (Finland)': 'FI (Smi)',
'Northern Saami (Norway, no dead keys)': 'NO (Smi Nodeadkeys)',
'Northern Saami (Norway)': 'NO (Smi)',
'Northern Saami (Sweden)': 'SE (Smi)',
Norwegian: 'NO',
'Norwegian (Colemak)': 'NO (Colemak)',
'Norwegian (Dvorak)': 'NO (Dvorak)',
'Norwegian (Macintosh, no dead keys)': 'NO (Mac Nodeadkeys)',
'Norwegian (Macintosh)': 'NO (Mac)',
'Norwegian (no dead keys)': 'NO (Nodeadkeys)',
'Norwegian (Windows)': 'NO (Winkeys)',
Occitan: 'FR (Oci)',
Ogham: 'IE (Ogam)',
'Ogham (IS434)': 'IE (Ogam Is434)',
'Ol Chiki': 'IN (Olck)',
'Old Turkic': 'TR (Otk)',
'Old Turkic (F)': 'TR (Otkf)',
Oriya: 'IN (Ori)',
'Oriya (Bolnagri)': 'IN (Ori-Bolnagri)',
'Oriya (Wx)': 'IN (Ori-Wx)',
'Ossetian (Georgia)': 'GE (Os)',
'Ossetian (legacy)': 'RU (Os Legacy)',
'Ossetian (Windows)': 'RU (Os Winkeys)',
'Ottoman (F)': 'TR (Otf)',
'Ottoman (Q)': 'TR (Ot)',
'Pannonian Rusyn': 'RS (Rue)',
Pashto: 'AF (Ps)',
'Pashto (Afghanistan, OLPC)': 'AF (Ps-Olpc)',
Persian: 'IR',
'Persian (with Persian keypad)': 'IR (Pes Keypad)',
Polish: 'PL',
'Polish (British keyboard)': 'GB (Pl)',
'Polish (Dvorak, with Polish quotes on key 1)': 'PL (Dvorak Altquotes)',
'Polish (Dvorak, with Polish quotes on quotemark key)': 'PL (Dvorak Quotes)',
'Polish (Dvorak)': 'PL (Dvorak)',
'Polish (legacy)': 'PL (Legacy)',
'Polish (programmer Dvorak)': 'PL (Dvp)',
'Polish (QWERTZ)': 'PL (Qwertz)',
Portuguese: 'PT',
'Portuguese (Brazil, Dvorak)': 'BR (Dvorak)',
'Portuguese (Brazil, IBM/Lenovo ThinkPad)': 'BR (Thinkpad)',
'Portuguese (Brazil, Nativo for US keyboards)': 'BR (Nativo-Us)',
'Portuguese (Brazil, Nativo)': 'BR (Nativo)',
'Portuguese (Brazil, no dead keys)': 'BR (Nodeadkeys)',
'Portuguese (Brazil)': 'BR',
'Portuguese (Macintosh, no dead keys)': 'PT (Mac Nodeadkeys)',
'Portuguese (Macintosh)': 'PT (Mac)',
'Portuguese (Nativo for US keyboards)': 'PT (Nativo-Us)',
'Portuguese (Nativo)': 'PT (Nativo)',
'Portuguese (no dead keys)': 'PT (Nodeadkeys)',
'Punjabi (Gurmukhi Jhelum)': 'IN (Jhelum)',
'Punjabi (Gurmukhi)': 'IN (Guru)',
Romanian: 'RO',
'Romanian (Germany, no dead keys)': 'DE (Ro Nodeadkeys)',
'Romanian (Germany)': 'DE (Ro)',
'Romanian (standard)': 'RO (Std)',
'Romanian (Windows)': 'RO (Winkeys)',
Russian: 'RU',
'Russian (Belarus)': 'BY (Ru)',
'Russian (Czech, phonetic)': 'CZ (Rus)',
'Russian (DOS)': 'RU (Dos)',
'Russian (engineering, EN)': 'RU (Ruchey En)',
'Russian (engineering, RU)': 'RU (Ruchey Ru)',
'Russian (Georgia)': 'GE (Ru)',
'Russian (Germany, phonetic)': 'DE (Ru)',
'Russian (Kazakhstan, with Kazakh)': 'KZ (Ruskaz)',
'Russian (legacy)': 'RU (Legacy)',
'Russian (Macintosh)': 'RU (Mac)',
'Russian (phonetic, AZERTY)': 'RU (Phonetic Azerty)',
'Russian (phonetic, Dvorak)': 'RU (Phonetic Dvorak)',
'Russian (phonetic, French)': 'RU (Phonetic Fr)',
'Russian (phonetic, Windows)': 'RU (Phonetic Winkeys)',
'Russian (phonetic, YAZHERTY)': 'RU (Phonetic YAZHERTY)',
'Russian (phonetic)': 'RU (Phonetic)',
'Russian (Poland, phonetic Dvorak)': 'PL (Ru Phonetic Dvorak)',
'Russian (Sweden, phonetic, no dead keys)': 'SE (Rus Nodeadkeys)',
'Russian (Sweden, phonetic)': 'SE (Rus)',
'Russian (typewriter, legacy)': 'RU (Typewriter-Legacy)',
'Russian (typewriter)': 'RU (Typewriter)',
'Russian (Ukraine, standard RSTU)': 'UA (Rstu Ru)',
'Russian (US, phonetic)': 'US (Rus)',
'Saisiyat (Taiwan)': 'TW (Saisiyat)',
Samogitian: 'LT (Sgs)',
'Sanskrit (KaGaPa, phonetic)': 'IN (San-Kagapa)',
'Scottish Gaelic': 'GB (Gla)',
Serbian: 'RS',
'Serbian (Cyrillic, with guillemets)': 'RS (Alternatequotes)',
'Serbian (Cyrillic, ZE and ZHE swapped)': 'RS (Yz)',
'Serbian (Latin, QWERTY)': 'RS (Latinyz)',
'Serbian (Latin, Unicode, QWERTY)': 'RS (Latinunicodeyz)',
'Serbian (Latin, Unicode)': 'RS (Latinunicode)',
'Serbian (Latin, with guillemets)': 'RS (Latinalternatequotes)',
'Serbian (Latin)': 'RS (Latin)',
'Serbian (Russia)': 'RU (Srp)',
'Serbo-Croatian (US)': 'US (Hbs)',
Shan: 'MM (Shn)',
'Shan (Zawgyi Tai)': 'MM (Zgt)',
Sicilian: 'IT (Scn)',
Silesian: 'PL (Szl)',
Sindhi: 'PK (Snd)',
'Sinhala (phonetic)': 'LK',
'Sinhala (US)': 'LK (Us)',
Slovak: 'SK',
'Slovak (extended backslash)': 'SK (Bksl)',
'Slovak (QWERTY, extended backslash)': 'SK (Qwerty Bksl)',
'Slovak (QWERTY)': 'SK (Qwerty)',
Slovenian: 'SI',
'Slovenian (US)': 'SI (Us)',
'Slovenian (with guillemets)': 'SI (Alternatequotes)',
Spanish: 'ES',
'Spanish (dead tilde)': 'ES (Deadtilde)',
'Spanish (Dvorak)': 'ES (Dvorak)',
'Spanish (Latin American, Colemak)': 'LATAM (Colemak)',
'Spanish (Latin American, dead tilde)': 'LATAM (Deadtilde)',
'Spanish (Latin American, Dvorak)': 'LATAM (Dvorak)',
'Spanish (Latin American, no dead keys)': 'LATAM (Nodeadkeys)',
'Spanish (Latin American)': 'LATAM',
'Spanish (Macintosh)': 'ES (Mac)',
'Spanish (no dead keys)': 'ES (Nodeadkeys)',
'Spanish (Windows)': 'ES (Winkeys)',
'Swahili (Kenya)': 'KE',
'Swahili (Tanzania)': 'TZ',
Swedish: 'SE',
'Swedish (Dvorak, intl.)': 'SE (Us Dvorak)',
'Swedish (Dvorak)': 'SE (Dvorak)',
'Swedish (Macintosh)': 'SE (Mac)',
'Swedish (no dead keys)': 'SE (Nodeadkeys)',
'Swedish (Svdvorak)': 'SE (Svdvorak)',
'Swedish (US)': 'SE (Us)',
'Swedish Sign Language': 'SE (Swl)',
Syriac: 'SY (Syc)',
'Syriac (phonetic)': 'SY (Syc Phonetic)',
Taiwanese: 'TW',
'Taiwanese (indigenous)': 'TW (Indigenous)',
Tajik: 'TJ',
'Tajik (legacy)': 'TJ (Legacy)',
'Tamil (InScript, with Arabic numerals)': 'IN (Tam)',
'Tamil (InScript, with Tamil numerals)': 'IN (Tam Tamilnumbers)',
"Tamil (Sri Lanka, TamilNet '99, TAB encoding)": 'LK (Tam TAB)',
"Tamil (Sri Lanka, TamilNet '99)": 'LK (Tam Unicode)',
"Tamil (TamilNet '99 with Tamil numerals)": 'IN (Tamilnet Tamilnumbers)',
"Tamil (TamilNet '99, TAB encoding)": 'IN (Tamilnet TAB)',
"Tamil (TamilNet '99, TSCII encoding)": 'IN (Tamilnet TSCII)',
"Tamil (TamilNet '99)": 'IN (Tamilnet)',
Tarifit: 'MA (Rif)',
Tatar: 'RU (Tt)',
Telugu: 'IN (Tel)',
'Telugu (KaGaPa, phonetic)': 'IN (Tel-Kagapa)',
'Telugu (Sarala)': 'IN (Tel-Sarala)',
Thai: 'TH',
'Thai (Pattachote)': 'TH (Pat)',
'Thai (TIS-820.2538)': 'TH (Tis)',
Tibetan: 'CN (Tib)',
'Tibetan (with ASCII numerals)': 'CN (Tib Asciinum)',
Tswana: 'BW',
Turkish: 'TR',
'Turkish (Alt-Q)': 'TR (Alt)',
'Turkish (E)': 'TR (E)',
'Turkish (F)': 'TR (F)',
'Turkish (Germany)': 'DE (Tr)',
'Turkish (intl., with dead keys)': 'TR (Intl)',
Turkmen: 'TM',
'Turkmen (Alt-Q)': 'TM (Alt)',
Udmurt: 'RU (Udm)',
Ukrainian: 'UA',
'Ukrainian (homophonic)': 'UA (Homophonic)',
'Ukrainian (legacy)': 'UA (Legacy)',
'Ukrainian (macOS)': 'UA (MacOS)',
'Ukrainian (phonetic)': 'UA (Phonetic)',
'Ukrainian (standard RSTU)': 'UA (Rstu)',
'Ukrainian (typewriter)': 'UA (Typewriter)',
'Ukrainian (Windows)': 'UA (Winkeys)',
'Urdu (alt. phonetic)': 'IN (Urd-Phonetic3)',
'Urdu (Pakistan, CRULP)': 'PK (Urd-Crulp)',
'Urdu (Pakistan, NLA)': 'PK (Urd-Nla)',
'Urdu (Pakistan)': 'PK',
'Urdu (phonetic)': 'IN (Urd-Phonetic)',
'Urdu (Windows)': 'IN (Urd-Winkeys)',
Uyghur: 'CN (Ug)',
Uzbek: 'UZ',
'Uzbek (Afghanistan, OLPC)': 'AF (Uz-Olpc)',
'Uzbek (Afghanistan)': 'AF (Uz)',
'Uzbek (Latin)': 'UZ (Latin)',
Vietnamese: 'VN',
'Vietnamese (France)': 'VN (Fr)',
'Vietnamese (US)': 'VN (Us)',
Wolof: 'SN',
Yakut: 'RU (Sah)',
Yoruba: 'NG (Yoruba)',
'Unknown Layout': 'Unknown',
} as const;
+84
View File
@@ -0,0 +1,84 @@
import { BarBoxChild, Module } from 'lib/types/bar';
import { BarButtonStyles } from 'lib/types/options';
import { Bind } from 'lib/types/variable';
import { GtkWidget } from 'lib/types/widget';
import options from 'options';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
const { style } = options.theme.bar.buttons;
const undefinedVar = Variable(undefined);
export const module = ({
icon,
textIcon,
label,
tooltipText,
boxClass,
props = {},
showLabelBinding = undefinedVar.bind('value'),
showLabel,
labelHook,
hook,
}: Module): BarBoxChild => {
const getIconWidget = (): GtkWidget | undefined => {
let iconWidget: Gtk.Widget | undefined;
if (icon !== undefined) {
iconWidget = Widget.Icon({
class_name: `txt-icon bar-button-icon module-icon ${boxClass}`,
icon: icon,
}) as unknown as Gtk.Widget;
} else if (textIcon !== undefined) {
iconWidget = Widget.Label({
class_name: `txt-icon bar-button-icon module-icon ${boxClass}`,
label: textIcon,
}) as unknown as Gtk.Widget;
}
return iconWidget;
};
return {
component: Widget.Box({
className: Utils.merge(
[style.bind('value'), showLabelBinding],
(style: BarButtonStyles, shwLabel: boolean) => {
const shouldShowLabel = shwLabel || showLabel;
const styleMap = {
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `${boxClass} ${styleMap[style]} ${!shouldShowLabel ? 'no-label' : ''}`;
},
),
tooltip_text: tooltipText,
children: Utils.merge([showLabelBinding], (showLabelBinding): Gtk.Widget[] => {
const childrenArray: Gtk.Widget[] = [];
const iconWidget = getIconWidget();
if (iconWidget !== undefined) {
childrenArray.push(iconWidget);
}
if (showLabelBinding) {
childrenArray.push(
Widget.Label({
class_name: `bar-button-label module-label ${boxClass}`,
label: label,
setup: labelHook,
}) as unknown as Gtk.Widget,
);
}
return childrenArray;
}) as Bind,
setup: hook,
}),
tooltip_text: tooltipText,
isVisible: true,
boxClass,
props,
};
};
@@ -0,0 +1,110 @@
import GLib from 'gi://GLib';
import { Variable as VariableType } from 'types/variable';
import { NetworkResourceData } from 'lib/types/customModules/network';
import { GET_DEFAULT_NETSTAT_DATA } from 'lib/types/defaults/netstat';
import { RateUnit } from 'lib/types/bar';
let previousNetUsage = { rx: 0, tx: 0, time: 0 };
const formatRate = (rate: number, type: string, round: boolean): string => {
const fixed = round ? 0 : 2;
switch (true) {
case type === 'KiB':
return `${(rate / 1e3).toFixed(fixed)} KiB/s`;
case type === 'MiB':
return `${(rate / 1e6).toFixed(fixed)} MiB/s`;
case type === 'GiB':
return `${(rate / 1e9).toFixed(fixed)} GiB/s`;
case rate >= 1e9:
return `${(rate / 1e9).toFixed(fixed)} GiB/s`;
case rate >= 1e6:
return `${(rate / 1e6).toFixed(fixed)} MiB/s`;
case rate >= 1e3:
return `${(rate / 1e3).toFixed(fixed)} KiB/s`;
default:
return `${rate.toFixed(fixed)} bytes/s`;
}
};
interface NetworkUsage {
name: string;
rx: number;
tx: number;
}
const parseInterfaceData = (line: string): NetworkUsage | null => {
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine.startsWith('Inter-') || trimmedLine.startsWith('face')) {
return null;
}
const [iface, rx, , , , , , , , tx] = trimmedLine.split(/\s+/);
const rxValue = parseInt(rx, 10);
const txValue = parseInt(tx, 10);
const cleanedIface = iface.replace(':', '');
return { name: cleanedIface, rx: rxValue, tx: txValue };
};
const isValidInterface = (iface: NetworkUsage | null, interfaceName: string): boolean => {
if (!iface) return false;
if (interfaceName) return iface.name === interfaceName;
return iface.name !== 'lo' && iface.rx > 0 && iface.tx > 0;
};
const getNetworkUsage = (interfaceName: string = ''): NetworkUsage => {
const [success, data] = GLib.file_get_contents('/proc/net/dev');
if (!success) {
console.error('Failed to read /proc/net/dev');
return { name: '', rx: 0, tx: 0 };
}
const lines = new TextDecoder('utf-8').decode(data).split('\n');
for (const line of lines) {
const iface = parseInterfaceData(line);
if (isValidInterface(iface, interfaceName)) {
return iface!;
}
}
return { name: '', rx: 0, tx: 0 };
};
export const computeNetwork = (
round: VariableType<boolean>,
interfaceNameVar: VariableType<string>,
dataType: VariableType<RateUnit>,
): NetworkResourceData => {
const rateUnit = dataType.value;
const interfaceName = interfaceNameVar ? interfaceNameVar.value : '';
const DEFAULT_NETSTAT_DATA = GET_DEFAULT_NETSTAT_DATA(rateUnit);
try {
const { rx, tx, name } = getNetworkUsage(interfaceName);
const currentTime = Date.now();
if (!name) {
return DEFAULT_NETSTAT_DATA;
}
if (previousNetUsage.time === 0) {
previousNetUsage = { rx, tx, time: currentTime };
return DEFAULT_NETSTAT_DATA;
}
const timeDiff = Math.max((currentTime - previousNetUsage.time) / 1000, 1);
const rxRate = (rx - previousNetUsage.rx) / timeDiff;
const txRate = (tx - previousNetUsage.tx) / timeDiff;
previousNetUsage = { rx, tx, time: currentTime };
return {
in: formatRate(rxRate, rateUnit, round.value),
out: formatRate(txRate, rateUnit, round.value),
};
} catch (error) {
console.error('Error calculating network usage:', error);
return DEFAULT_NETSTAT_DATA;
}
};
@@ -0,0 +1,105 @@
import options from 'options';
import { module } from '../module';
import { inputHandler } from 'customModules/utils';
import { computeNetwork } from './computeNetwork';
import { BarBoxChild, NetstatLabelType } from 'lib/types/bar';
import Button from 'types/widgets/button';
import { NetworkResourceData } from 'lib/types/customModules/network';
import { NETWORK_LABEL_TYPES } from 'lib/types/defaults/bar';
import { GET_DEFAULT_NETSTAT_DATA } from 'lib/types/defaults/netstat';
import { pollVariable } from 'customModules/PollVar';
import { Attribute, Child } from 'lib/types/widget';
const {
label,
labelType,
networkInterface,
rateUnit,
icon,
round,
leftClick,
rightClick,
middleClick,
pollingInterval,
} = options.bar.customModules.netstat;
export const networkUsage = Variable<NetworkResourceData>(GET_DEFAULT_NETSTAT_DATA(rateUnit.value));
pollVariable(
// Variable to poll and update with the result of the function passed in
networkUsage,
// Variables that should trigger the polling function to update when they change
[rateUnit.bind('value'), networkInterface.bind('value'), round.bind('value')],
// Interval at which to poll
pollingInterval.bind('value'),
// Function to execute to get the network data
computeNetwork,
// Optional parameters to pass to the function
// round is a boolean that determines whether to round the values
round,
// Optional parameters to pass to the function
// networkInterface is the interface name to filter the data
networkInterface,
// Optional parameters to pass to the function
// rateUnit is the unit to display the data in
// e.g. KiB, MiB, GiB, etc.
rateUnit,
);
export const Netstat = (): BarBoxChild => {
const renderNetworkLabel = (lblType: NetstatLabelType, network: NetworkResourceData): string => {
switch (lblType) {
case 'in':
return `↓ ${network.in}`;
case 'out':
return `↑ ${network.out}`;
default:
return `↓ ${network.in} ↑ ${network.out}`;
}
};
const netstatModule = module({
textIcon: icon.bind('value'),
label: Utils.merge(
[networkUsage.bind('value'), labelType.bind('value')],
(network: NetworkResourceData, lblTyp: NetstatLabelType) => renderNetworkLabel(lblTyp, network),
),
tooltipText: labelType.bind('value').as((lblTyp) => {
return lblTyp === 'full' ? 'Ingress / Egress' : lblTyp === 'in' ? 'Ingress' : 'Egress';
}),
boxClass: 'netstat',
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Child, Attribute>) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
fn: () => {
labelType.value = NETWORK_LABEL_TYPES[
(NETWORK_LABEL_TYPES.indexOf(labelType.value) + 1) % NETWORK_LABEL_TYPES.length
] as NetstatLabelType;
},
},
onScrollDown: {
fn: () => {
labelType.value = NETWORK_LABEL_TYPES[
(NETWORK_LABEL_TYPES.indexOf(labelType.value) - 1 + NETWORK_LABEL_TYPES.length) %
NETWORK_LABEL_TYPES.length
] as NetstatLabelType;
},
},
});
},
},
});
return netstatModule;
};
@@ -0,0 +1,40 @@
import options from 'options';
import { module } from '../module';
import { inputHandler } from 'customModules/utils';
import Button from 'types/widgets/button';
import { Attribute, Child } from 'lib/types/widget';
import { BarBoxChild } from 'lib/types/bar';
const { icon, leftClick, rightClick, middleClick, scrollUp, scrollDown } = options.bar.customModules.power;
export const Power = (): BarBoxChild => {
const powerModule = module({
tooltipText: 'Power Menu',
textIcon: icon.bind('value'),
boxClass: 'powermodule',
props: {
setup: (self: Button<Child, Attribute>) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
cmd: scrollUp,
},
onScrollDown: {
cmd: scrollDown,
},
});
},
},
});
return powerModule;
};
@@ -0,0 +1,40 @@
const GLib = imports.gi.GLib;
import { divide } from 'customModules/utils';
import { GenericResourceData } from 'lib/types/customModules/generic';
import { Variable as VariableType } from 'types/variable';
export const calculateRamUsage = (round: VariableType<boolean>): GenericResourceData => {
try {
const [success, meminfoBytes] = GLib.file_get_contents('/proc/meminfo');
if (!success || !meminfoBytes) {
throw new Error('Failed to read /proc/meminfo or file content is null.');
}
const meminfo = new TextDecoder('utf-8').decode(meminfoBytes);
const totalMatch = meminfo.match(/MemTotal:\s+(\d+)/);
const availableMatch = meminfo.match(/MemAvailable:\s+(\d+)/);
if (!totalMatch || !availableMatch) {
throw new Error('Failed to parse /proc/meminfo for memory values.');
}
const totalRamInBytes = parseInt(totalMatch[1], 10) * 1024;
const availableRamInBytes = parseInt(availableMatch[1], 10) * 1024;
let usedRam = totalRamInBytes - availableRamInBytes;
usedRam = isNaN(usedRam) || usedRam < 0 ? 0 : usedRam;
return {
percentage: divide([totalRamInBytes, usedRam], round.value),
total: totalRamInBytes,
used: usedRam,
free: availableRamInBytes,
};
} catch (error) {
console.error('Error calculating RAM usage:', error);
return { total: 0, used: 0, percentage: 0, free: 0 };
}
};
+79
View File
@@ -0,0 +1,79 @@
import options from 'options';
// Module initializer
import { module } from '../module';
// Types
import { GenericResourceData } from 'lib/types/customModules/generic';
import Button from 'types/widgets/button';
// Helper Methods
import { calculateRamUsage } from './computeRam';
// Utility Methods
import { formatTooltip, inputHandler, renderResourceLabel } from 'customModules/utils';
import { BarBoxChild, ResourceLabelType } from 'lib/types/bar';
// Global Constants
import { LABEL_TYPES } from 'lib/types/defaults/bar';
import { pollVariable } from 'customModules/PollVar';
import { Attribute, Child } from 'lib/types/widget';
// All the user configurable options for the ram module that are needed
const { label, labelType, round, leftClick, rightClick, middleClick, pollingInterval, icon } =
options.bar.customModules.ram;
const defaultRamData: GenericResourceData = { total: 0, used: 0, percentage: 0, free: 0 };
const ramUsage = Variable<GenericResourceData>(defaultRamData);
pollVariable(ramUsage, [round.bind('value')], pollingInterval.bind('value'), calculateRamUsage, round);
export const Ram = (): BarBoxChild => {
const ramModule = module({
textIcon: icon.bind('value'),
label: Utils.merge(
[ramUsage.bind('value'), labelType.bind('value'), round.bind('value')],
(rmUsg: GenericResourceData, lblTyp: ResourceLabelType, round: boolean) => {
const returnValue = renderResourceLabel(lblTyp, rmUsg, round);
return returnValue;
},
),
tooltipText: labelType.bind('value').as((lblTyp) => {
return formatTooltip('RAM', lblTyp);
}),
boxClass: 'ram',
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Child, Attribute>) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
fn: () => {
labelType.value = LABEL_TYPES[
(LABEL_TYPES.indexOf(labelType.value) + 1) % LABEL_TYPES.length
] as ResourceLabelType;
},
},
onScrollDown: {
fn: () => {
labelType.value = LABEL_TYPES[
(LABEL_TYPES.indexOf(labelType.value) - 1 + LABEL_TYPES.length) % LABEL_TYPES.length
] as ResourceLabelType;
},
},
});
},
},
});
return ramModule;
};
@@ -0,0 +1,28 @@
// @ts-expect-error is a special directive that tells the compiler to use the GTop library
import GTop from 'gi://GTop';
import { divide } from 'customModules/utils';
import { Variable as VariableType } from 'types/variable';
import { GenericResourceData } from 'lib/types/customModules/generic';
export const computeStorage = (round: VariableType<boolean>): GenericResourceData => {
try {
const currentFsUsage = new GTop.glibtop_fsusage();
GTop.glibtop_get_fsusage(currentFsUsage, '/');
const total = currentFsUsage.blocks * currentFsUsage.block_size;
const available = currentFsUsage.bavail * currentFsUsage.block_size;
const used = total - available;
return {
total,
used,
free: available,
percentage: divide([total, used], round.value),
};
} catch (error) {
console.error('Error calculating RAM usage:', error);
return { total: 0, used: 0, percentage: 0, free: 0 };
}
};
@@ -0,0 +1,68 @@
import options from 'options';
import { module } from '../module';
import { formatTooltip, inputHandler, renderResourceLabel } from 'customModules/utils';
import { computeStorage } from './computeStorage';
import { BarBoxChild, ResourceLabelType } from 'lib/types/bar';
import { GenericResourceData } from 'lib/types/customModules/generic';
import Button from 'types/widgets/button';
import { LABEL_TYPES } from 'lib/types/defaults/bar';
import { pollVariable } from 'customModules/PollVar';
import { Attribute, Child } from 'lib/types/widget';
const { label, labelType, icon, round, leftClick, rightClick, middleClick, pollingInterval } =
options.bar.customModules.storage;
const defaultStorageData = { total: 0, used: 0, percentage: 0, free: 0 };
const storageUsage = Variable<GenericResourceData>(defaultStorageData);
pollVariable(storageUsage, [round.bind('value')], pollingInterval.bind('value'), computeStorage, round);
export const Storage = (): BarBoxChild => {
const storageModule = module({
textIcon: icon.bind('value'),
label: Utils.merge(
[storageUsage.bind('value'), labelType.bind('value'), round.bind('value')],
(storage: GenericResourceData, lblTyp: ResourceLabelType, round: boolean) => {
return renderResourceLabel(lblTyp, storage, round);
},
),
tooltipText: labelType.bind('value').as((lblTyp) => {
return formatTooltip('Storage', lblTyp);
}),
boxClass: 'storage',
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Child, Attribute>) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
fn: () => {
labelType.value = LABEL_TYPES[
(LABEL_TYPES.indexOf(labelType.value) + 1) % LABEL_TYPES.length
] as ResourceLabelType;
},
},
onScrollDown: {
fn: () => {
labelType.value = LABEL_TYPES[
(LABEL_TYPES.indexOf(labelType.value) - 1 + LABEL_TYPES.length) % LABEL_TYPES.length
] as ResourceLabelType;
},
},
});
},
},
});
return storageModule;
};
@@ -0,0 +1,20 @@
import { Variable } from 'types/variable';
const hyprland = await Service.import('hyprland');
export const isSubmapEnabled = (submap: string, enabled: string, disabled: string): string => {
return submap !== 'default' ? enabled : disabled;
};
export const getInitialSubmap = (submapStatus: Variable<string>): void => {
let submap = hyprland.message('submap');
const newLineCarriage = /\n/g;
submap = submap.replace(newLineCarriage, '');
if (submap === 'unknown request') {
submap = 'default';
}
submapStatus.value = submap;
};
+101
View File
@@ -0,0 +1,101 @@
const hyprland = await Service.import('hyprland');
import options from 'options';
import { module } from '../module';
import { inputHandler } from 'customModules/utils';
import Button from 'types/widgets/button';
import { Variable as VariableType } from 'types/variable';
import { Attribute, Child } from 'lib/types/widget';
import { BarBoxChild } from 'lib/types/bar';
import { capitalizeFirstLetter } from 'lib/utils';
import { getInitialSubmap, isSubmapEnabled } from './helpers';
const {
label,
showSubmapName,
enabledIcon,
disabledIcon,
enabledText,
disabledText,
leftClick,
rightClick,
middleClick,
scrollUp,
scrollDown,
} = options.bar.customModules.submap;
const submapStatus: VariableType<string> = Variable('default');
hyprland.connect('submap', (_, currentSubmap) => {
if (currentSubmap.length === 0) {
submapStatus.value = 'default';
} else {
submapStatus.value = currentSubmap;
}
});
getInitialSubmap(submapStatus);
export const Submap = (): BarBoxChild => {
const submapModule = module({
textIcon: Utils.merge(
[submapStatus.bind('value'), enabledIcon.bind('value'), disabledIcon.bind('value')],
(status, enabled, disabled) => {
return isSubmapEnabled(status, enabled, disabled);
},
),
tooltipText: Utils.merge(
[
submapStatus.bind('value'),
enabledText.bind('value'),
disabledText.bind('value'),
showSubmapName.bind('value'),
],
(status, enabled, disabled, showSmName) => {
if (showSmName) {
return capitalizeFirstLetter(status);
}
return isSubmapEnabled(status, enabled, disabled);
},
),
boxClass: 'submap',
label: Utils.merge(
[
submapStatus.bind('value'),
enabledText.bind('value'),
disabledText.bind('value'),
showSubmapName.bind('value'),
],
(status, enabled, disabled, showSmName) => {
if (showSmName) {
return capitalizeFirstLetter(status);
}
return isSubmapEnabled(status, enabled, disabled);
},
),
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Child, Attribute>) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
cmd: scrollUp,
},
onScrollDown: {
cmd: scrollDown,
},
});
},
},
});
return submapModule;
};
+171
View File
@@ -0,0 +1,171 @@
import { Option } from 'widget/settings/shared/Option';
import { Header } from 'widget/settings/shared/Header';
import options from 'options';
import Scrollable from 'types/widgets/scrollable';
import { Attribute, GtkWidget } from 'lib/types/widget';
export const CustomModuleTheme = (): Scrollable<GtkWidget, Attribute> => {
return Widget.Scrollable({
vscroll: 'automatic',
hscroll: 'automatic',
class_name: 'menu-theme-page customModules paged-container',
child: Widget.Box({
class_name: 'bar-theme-page paged-container',
vertical: true,
children: [
Header('RAM'),
Option({ opt: options.theme.bar.buttons.modules.ram.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.ram.icon, title: 'Icon', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.ram.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.ram.icon_background,
title: 'Icon Background',
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Option({ opt: options.theme.bar.buttons.modules.ram.border, title: 'Border', type: 'color' }),
Header('CPU'),
Option({ opt: options.theme.bar.buttons.modules.cpu.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.cpu.icon, title: 'Icon', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.cpu.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.cpu.icon_background,
title: 'Icon Background',
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Option({ opt: options.theme.bar.buttons.modules.cpu.border, title: 'Border', type: 'color' }),
Header('Storage'),
Option({ opt: options.theme.bar.buttons.modules.storage.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.storage.icon, title: 'Icon', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.storage.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.storage.icon_background,
title: 'Icon Background',
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Option({ opt: options.theme.bar.buttons.modules.storage.border, title: 'Border', type: 'color' }),
Header('Netstat'),
Option({ opt: options.theme.bar.buttons.modules.netstat.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.netstat.icon, title: 'Icon', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.netstat.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.netstat.icon_background,
title: 'Icon Background',
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Option({ opt: options.theme.bar.buttons.modules.netstat.border, title: 'Border', type: 'color' }),
Header('Keyboard Layout'),
Option({ opt: options.theme.bar.buttons.modules.kbLayout.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.kbLayout.icon, title: 'Icon', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.kbLayout.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.kbLayout.icon_background,
title: 'Icon Background',
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Option({ opt: options.theme.bar.buttons.modules.kbLayout.border, title: 'Border', type: 'color' }),
Header('Updates'),
Option({ opt: options.theme.bar.buttons.modules.updates.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.updates.icon, title: 'Icon', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.updates.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.updates.icon_background,
title: 'Icon Background',
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Option({ opt: options.theme.bar.buttons.modules.updates.border, title: 'Border', type: 'color' }),
Header('Submap'),
Option({ opt: options.theme.bar.buttons.modules.submap.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.submap.icon, title: 'Icon', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.submap.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.submap.icon_background,
title: 'Icon Background',
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Option({ opt: options.theme.bar.buttons.modules.submap.border, title: 'Border', type: 'color' }),
Header('Weather'),
Option({ opt: options.theme.bar.buttons.modules.weather.icon, title: 'Icon', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.weather.text, title: 'Text', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.weather.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.weather.icon_background,
title: 'Icon Background',
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Option({ opt: options.theme.bar.buttons.modules.weather.border, title: 'Border', type: 'color' }),
Header('Power'),
Option({ opt: options.theme.bar.buttons.modules.power.icon, title: 'Icon', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.power.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.power.icon_background,
title: 'Icon Background',
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Option({ opt: options.theme.bar.buttons.modules.power.border, title: 'Border', type: 'color' }),
],
}),
});
};
@@ -0,0 +1,70 @@
import options from 'options';
import { module } from '../module';
import { inputHandler } from 'customModules/utils';
import Button from 'types/widgets/button';
import { Variable as VariableType } from 'types/variable';
import { pollVariableBash } from 'customModules/PollVar';
import { Attribute, Child } from 'lib/types/widget';
import { BarBoxChild } from 'lib/types/bar';
const {
updateCommand,
label,
padZero,
pollingInterval,
icon,
leftClick,
rightClick,
middleClick,
scrollUp,
scrollDown,
} = options.bar.customModules.updates;
const pendingUpdates: VariableType<string> = Variable(' 0');
const processUpdateCount = (updateCount: string): string => {
if (!padZero.value) return updateCount;
return `${updateCount.padStart(2, '0')}`;
};
pollVariableBash(
pendingUpdates,
[padZero.bind('value')],
pollingInterval.bind('value'),
updateCommand.value,
processUpdateCount,
);
export const Updates = (): BarBoxChild => {
const updatesModule = module({
textIcon: icon.bind('value'),
tooltipText: pendingUpdates.bind('value').as((v) => `${v} updates available`),
boxClass: 'updates',
label: pendingUpdates.bind('value'),
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Child, Attribute>) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
cmd: scrollUp,
},
onScrollDown: {
cmd: scrollDown,
},
});
},
},
});
return updatesModule;
};
+197
View File
@@ -0,0 +1,197 @@
import { ResourceLabelType } from 'lib/types/bar';
import { GenericResourceData, Postfix } from 'lib/types/customModules/generic';
import { InputHandlerEvents, RunAsyncCommand } from 'lib/types/customModules/utils';
import { ThrottleFn, ThrottleFnCallback } from 'lib/types/utils';
import { Attribute, Child, EventArgs } from 'lib/types/widget';
import { Binding } from 'lib/utils';
import { openMenu } from 'modules/bar/utils';
import options from 'options';
import Gdk from 'types/@girs/gdk-3.0/gdk-3.0';
import { Variable as VariableType } from 'types/variable';
import Button from 'types/widgets/button';
const { scrollSpeed } = options.bar.customModules;
export const runAsyncCommand: RunAsyncCommand = (cmd, events, fn): void => {
if (cmd.startsWith('menu:')) {
const menuName = cmd.split(':')[1].trim().toLowerCase();
openMenu(events.clicked, events.event, `${menuName}menu`);
return;
}
Utils.execAsync(`bash -c "${cmd}"`)
.then((output) => {
if (fn !== undefined) {
fn(output);
}
})
.catch((err) => console.error(`Error running command "${cmd}": ${err})`));
};
export function throttle<T extends ThrottleFn>(func: T, limit: number): T {
let inThrottle: boolean;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
} as T;
}
export const throttledScrollHandler = (interval: number): ThrottleFn =>
throttle((cmd: string, events: EventArgs, fn: ThrottleFnCallback) => {
runAsyncCommand(cmd, events, fn);
}, 200 / interval);
const dummyVar = Variable('');
export const inputHandler = (
self: Button<Child, Attribute>,
{ onPrimaryClick, onSecondaryClick, onMiddleClick, onScrollUp, onScrollDown }: InputHandlerEvents,
): void => {
const sanitizeInput = (input: VariableType<string>): string => {
if (input === undefined) {
return '';
}
return input.value;
};
const updateHandlers = (): void => {
const interval = scrollSpeed.value;
const throttledHandler = throttledScrollHandler(interval);
self.on_primary_click = (clicked: Button<Child, Attribute>, event: Gdk.Event): void =>
runAsyncCommand(sanitizeInput(onPrimaryClick?.cmd || dummyVar), { clicked, event }, onPrimaryClick.fn);
self.on_secondary_click = (clicked: Button<Child, Attribute>, event: Gdk.Event): void =>
runAsyncCommand(sanitizeInput(onSecondaryClick?.cmd || dummyVar), { clicked, event }, onSecondaryClick.fn);
self.on_middle_click = (clicked: Button<Child, Attribute>, event: Gdk.Event): void =>
runAsyncCommand(sanitizeInput(onMiddleClick?.cmd || dummyVar), { clicked, event }, onMiddleClick.fn);
self.on_scroll_up = (clicked: Button<Child, Attribute>, event: Gdk.Event): void =>
throttledHandler(sanitizeInput(onScrollUp?.cmd || dummyVar), { clicked, event }, onScrollUp.fn);
self.on_scroll_down = (clicked: Button<Child, Attribute>, event: Gdk.Event): void =>
throttledHandler(sanitizeInput(onScrollDown?.cmd || dummyVar), { clicked, event }, onScrollDown.fn);
};
// Initial setup of event handlers
updateHandlers();
const sanitizeVariable = (someVar: VariableType<string> | undefined): Binding<string> => {
if (someVar === undefined || typeof someVar.bind !== 'function') {
return dummyVar.bind('value');
}
return someVar.bind('value');
};
// Re-run the update whenever scrollSpeed changes
Utils.merge(
[
scrollSpeed.bind('value'),
sanitizeVariable(onPrimaryClick),
sanitizeVariable(onSecondaryClick),
sanitizeVariable(onMiddleClick),
sanitizeVariable(onScrollUp),
sanitizeVariable(onScrollDown),
],
updateHandlers,
);
};
export const divide = ([total, used]: number[], round: boolean): number => {
const percentageTotal = (used / total) * 100;
if (round) {
return total > 0 ? Math.round(percentageTotal) : 0;
}
return total > 0 ? parseFloat(percentageTotal.toFixed(2)) : 0;
};
export const formatSizeInKiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / 1024 ** 1;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
};
export const formatSizeInMiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / 1024 ** 2;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
};
export const formatSizeInGiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / 1024 ** 3;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
};
export const formatSizeInTiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / 1024 ** 4;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
};
export const autoFormatSize = (sizeInBytes: number, round: boolean): number => {
// auto convert to GiB, MiB, KiB, TiB, or bytes
if (sizeInBytes >= 1024 ** 4) return formatSizeInTiB(sizeInBytes, round);
if (sizeInBytes >= 1024 ** 3) return formatSizeInGiB(sizeInBytes, round);
if (sizeInBytes >= 1024 ** 2) return formatSizeInMiB(sizeInBytes, round);
if (sizeInBytes >= 1024 ** 1) return formatSizeInKiB(sizeInBytes, round);
return sizeInBytes;
};
export const getPostfix = (sizeInBytes: number): Postfix => {
if (sizeInBytes >= 1024 ** 4) return 'TiB';
if (sizeInBytes >= 1024 ** 3) return 'GiB';
if (sizeInBytes >= 1024 ** 2) return 'MiB';
if (sizeInBytes >= 1024 ** 1) return 'KiB';
return 'B';
};
export const renderResourceLabel = (lblType: ResourceLabelType, rmUsg: GenericResourceData, round: boolean): string => {
const { used, total, percentage, free } = rmUsg;
const formatFunctions = {
TiB: formatSizeInTiB,
GiB: formatSizeInGiB,
MiB: formatSizeInMiB,
KiB: formatSizeInKiB,
B: (size: number): number => size,
};
// Get them datas in proper GiB, MiB, KiB, TiB, or bytes
const totalSizeFormatted = autoFormatSize(total, round);
// get the postfix: one of [TiB, GiB, MiB, KiB, B]
const postfix = getPostfix(total);
// Determine which format function to use
const formatUsed = formatFunctions[postfix] || formatFunctions['B'];
const usedSizeFormatted = formatUsed(used, round);
if (lblType === 'used/total') {
return `${usedSizeFormatted}/${totalSizeFormatted} ${postfix}`;
}
if (lblType === 'used') {
return `${autoFormatSize(used, round)} ${getPostfix(used)}`;
}
if (lblType === 'free') {
return `${autoFormatSize(free, round)} ${getPostfix(free)}`;
}
return `${percentage}%`;
};
export const formatTooltip = (dataType: string, lblTyp: ResourceLabelType): string => {
switch (lblTyp) {
case 'used':
return `Used ${dataType}`;
case 'free':
return `Free ${dataType}`;
case 'used/total':
return `Used/Total ${dataType}`;
case 'percentage':
return `Percentage ${dataType} Usage`;
default:
return '';
}
};
@@ -0,0 +1,52 @@
import options from 'options';
import { module } from '../module';
import { inputHandler } from 'customModules/utils';
import Button from 'types/widgets/button';
import { getWeatherStatusTextIcon, globalWeatherVar } from 'globals/weather';
import { Attribute, Child } from 'lib/types/widget';
import { BarBoxChild } from 'lib/types/bar';
const { label, unit, leftClick, rightClick, middleClick, scrollUp, scrollDown } = options.bar.customModules.weather;
export const Weather = (): BarBoxChild => {
const weatherModule = module({
textIcon: Utils.merge([globalWeatherVar.bind('value')], (wthr) => {
const weatherStatusIcon = getWeatherStatusTextIcon(wthr);
return weatherStatusIcon;
}),
tooltipText: globalWeatherVar.bind('value').as((v) => `Weather Status: ${v.current.condition.text}`),
boxClass: 'weather-custom',
label: Utils.merge([globalWeatherVar.bind('value'), unit.bind('value')], (wthr, unt) => {
if (unt === 'imperial') {
return `${Math.ceil(wthr.current.temp_f)}° F`;
} else {
return `${Math.ceil(wthr.current.temp_c)}° C`;
}
}),
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Child, Attribute>) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
cmd: scrollUp,
},
onScrollDown: {
cmd: scrollDown,
},
});
},
},
});
return weatherModule;
};