Fix: An issue that would cause Matugen colors to not apply. (#929)
* Eslint updates * linter fixes * Type fixes * More type fixes * Fix isvis * More type fixes * Type Fixes * Consolidate logic to manage options * Linter fixes * Package lock update * Update configs * Version checker * Debug pipeline * Package lock update * Update ci * Strict check * Revert ci * Eslint * Remove rule since it causes issues in CI * Actual matugen fix
This commit is contained in:
@@ -28,7 +28,7 @@ function setBarVisibility(monitorId: number, isVisible: boolean): void {
|
||||
* @param client - The Hyprland client that gained focus
|
||||
*/
|
||||
function handleFullscreenClientVisibility(client: AstalHyprland.Client): void {
|
||||
if (!client) {
|
||||
if (client === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ function handleFullscreenClientVisibility(client: AstalHyprland.Client): void {
|
||||
|
||||
Variable.derive([bind(fullscreenBinding)], (isFullScreen) => {
|
||||
if (autoHide.get() === 'fullscreen') {
|
||||
setBarVisibility(client.monitor.id, !isFullScreen);
|
||||
setBarVisibility(client.monitor.id, !Boolean(isFullScreen));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import AstalBattery from 'gi://AstalBattery?version=0.1';
|
||||
import icons from '../icons/icons';
|
||||
import { Notify } from '../utils';
|
||||
import options from 'src/options';
|
||||
|
||||
const batteryService = AstalBattery.get_default();
|
||||
const {
|
||||
lowBatteryThreshold,
|
||||
lowBatteryNotification,
|
||||
lowBatteryNotificationText,
|
||||
lowBatteryNotificationTitle,
|
||||
} = options.menus.power;
|
||||
|
||||
export function warnOnLowBattery(): void {
|
||||
let sentLowNotification = false;
|
||||
let sentHalfLowNotification = false;
|
||||
|
||||
batteryService.connect('notify::charging', () => {
|
||||
// Reset it when the battery is put to charge
|
||||
if (batteryService.charging) {
|
||||
sentLowNotification = false;
|
||||
sentHalfLowNotification = false;
|
||||
@@ -17,24 +23,21 @@ export function warnOnLowBattery(): void {
|
||||
});
|
||||
|
||||
batteryService.connect('notify::percentage', () => {
|
||||
const { lowBatteryThreshold, lowBatteryNotification, lowBatteryNotificationText, lowBatteryNotificationTitle } =
|
||||
options.menus.power;
|
||||
|
||||
if (!lowBatteryNotification.get() || batteryService.charging) {
|
||||
if (lowBatteryNotification.get() === undefined || batteryService.charging) {
|
||||
return;
|
||||
}
|
||||
|
||||
// batteryService.percentage will be a double in between 0 and 1, so we multiply it by 100 to convert it to the percentage
|
||||
const batteryPercentage = Math.floor(batteryService.percentage * 100);
|
||||
const lowThreshold = lowBatteryThreshold.get();
|
||||
|
||||
// To avoid double notifications, we check each of the thresholds and set the correct `sentNotification`, but then
|
||||
// combine them into one notification only
|
||||
// Avoid double notification
|
||||
let sendNotification = false;
|
||||
|
||||
if (!sentLowNotification && batteryPercentage <= lowThreshold) {
|
||||
sentLowNotification = true;
|
||||
sendNotification = true;
|
||||
}
|
||||
|
||||
if (!sentHalfLowNotification && batteryPercentage <= lowThreshold / 2) {
|
||||
sentHalfLowNotification = true;
|
||||
sendNotification = true;
|
||||
@@ -42,8 +45,12 @@ export function warnOnLowBattery(): void {
|
||||
|
||||
if (sendNotification) {
|
||||
Notify({
|
||||
summary: lowBatteryNotificationTitle.get().replaceAll('$POWER_LEVEL', batteryPercentage.toString()),
|
||||
body: lowBatteryNotificationText.get().replaceAll('$POWER_LEVEL', batteryPercentage.toString()),
|
||||
summary: lowBatteryNotificationTitle
|
||||
.get()
|
||||
.replaceAll('$POWER_LEVEL', batteryPercentage.toString()),
|
||||
body: lowBatteryNotificationText
|
||||
.get()
|
||||
.replaceAll('$POWER_LEVEL', batteryPercentage.toString()),
|
||||
iconName: icons.ui.warning,
|
||||
urgency: 'critical',
|
||||
});
|
||||
|
||||
@@ -3,18 +3,18 @@ import AstalHyprland from 'gi://AstalHyprland?version=0.1';
|
||||
const hyprlandService = AstalHyprland.get_default();
|
||||
|
||||
const floatSettingsDialog = (): void => {
|
||||
hyprlandService.message(`keyword windowrulev2 float, title:^(hyprpanel-settings)$`);
|
||||
hyprlandService.message('keyword windowrulev2 float, title:^(hyprpanel-settings)$');
|
||||
|
||||
hyprlandService.connect('config-reloaded', () => {
|
||||
hyprlandService.message(`keyword windowrulev2 float, title:^(hyprpanel-settings)$`);
|
||||
hyprlandService.message('keyword windowrulev2 float, title:^(hyprpanel-settings)$');
|
||||
});
|
||||
};
|
||||
|
||||
const floatFilePicker = (): void => {
|
||||
hyprlandService.message(`keyword windowrulev2 float, title:^((Save|Import) Hyprpanel.*)$`);
|
||||
hyprlandService.message('keyword windowrulev2 float, title:^((Save|Import) Hyprpanel.*)$');
|
||||
|
||||
hyprlandService.connect('config-reloaded', () => {
|
||||
hyprlandService.message(`keyword windowrulev2 float, title:^((Save|Import) Hyprpanel.*)$`);
|
||||
hyprlandService.message('keyword windowrulev2 float, title:^((Save|Import) Hyprpanel.*)$');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -124,14 +124,16 @@ const overrides = {
|
||||
*/
|
||||
export const defaultApplicationIconMap = defaultWindowTitleMap.reduce(
|
||||
(iconMapAccumulator: Record<string, string>, windowTitles) => {
|
||||
const currentIconMap = iconMapAccumulator;
|
||||
|
||||
const appName: string = windowTitles[0];
|
||||
const appIcon: string = windowTitles[1];
|
||||
|
||||
if (!(appName in iconMapAccumulator)) {
|
||||
iconMapAccumulator[appName] = appIcon;
|
||||
if (!(appName in currentIconMap)) {
|
||||
currentIconMap[appName] = appIcon;
|
||||
}
|
||||
|
||||
return iconMapAccumulator;
|
||||
return currentIconMap;
|
||||
},
|
||||
overrides,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { DropdownMenuList } from '../types/options';
|
||||
import { DropdownMenuList } from '../options/options.types';
|
||||
|
||||
export const StackTransitionMap = {
|
||||
none: Gtk.StackTransitionType.NONE,
|
||||
|
||||
@@ -1,355 +0,0 @@
|
||||
import { MkOptionsResult } from './types/options';
|
||||
import Variable from 'astal/variable';
|
||||
import { monitorFile, readFile, writeFile } from 'astal/file';
|
||||
import { errorHandler, Notify } from './utils';
|
||||
import { ensureDirectory } from './session';
|
||||
import icons from './icons/icons';
|
||||
|
||||
type OptProps = {
|
||||
persistent?: boolean;
|
||||
};
|
||||
|
||||
type WriteDiskProps = {
|
||||
writeDisk?: boolean;
|
||||
};
|
||||
|
||||
export class Opt<T = unknown> extends Variable<T> {
|
||||
/**
|
||||
* The initial value set when the `Opt` is created.
|
||||
*/
|
||||
public readonly initial: T;
|
||||
|
||||
/**
|
||||
* Indicates whether this option should remain unchanged even when reset operations occur.
|
||||
*/
|
||||
public readonly persistent: boolean;
|
||||
|
||||
private _id = '';
|
||||
|
||||
/**
|
||||
* Creates an instance of `Opt`.
|
||||
*
|
||||
* @param {T} initial - The initial value of the option.
|
||||
* @param {OptProps} [props={}] - Additional properties for the option.
|
||||
*/
|
||||
constructor(initial: T, { persistent = false }: OptProps = {}) {
|
||||
super(initial);
|
||||
this.initial = initial;
|
||||
this.persistent = persistent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the current value to a JSON-compatible string.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
toJSON(): string {
|
||||
return `opt:${JSON.stringify(this.get())}`;
|
||||
}
|
||||
|
||||
public get value(): T {
|
||||
return this.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the current value of the option.
|
||||
*/
|
||||
public set value(val: T) {
|
||||
this.set(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the unique ID of the option.
|
||||
*/
|
||||
public get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the unique ID of the option.
|
||||
*/
|
||||
public set id(newId: string) {
|
||||
this._id = newId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this option based on the provided configuration, if available.
|
||||
*
|
||||
* @param config - The configuration.
|
||||
*/
|
||||
public init(config: Record<string, unknown>): void {
|
||||
const value = _findVal(config, this._id.split('.'));
|
||||
|
||||
if (value !== undefined) {
|
||||
this.set(value as T, { writeDisk: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given configuration value and write it to disk, if specified.
|
||||
*
|
||||
* @param value - The new value.
|
||||
* @param writeDisk - Whether to write the changes to disk. Defaults to true.
|
||||
*/
|
||||
public set = (value: T, { writeDisk = true }: WriteDiskProps = {}): void => {
|
||||
if (value === this.get()) {
|
||||
// If nothing actually changed, exit quick
|
||||
return;
|
||||
}
|
||||
|
||||
super.set(value);
|
||||
|
||||
if (writeDisk) {
|
||||
const raw = readFile(CONFIG_FILE);
|
||||
let config: Record<string, unknown> = {};
|
||||
if (raw && raw.trim() !== '') {
|
||||
try {
|
||||
config = JSON.parse(raw) as Record<string, unknown>;
|
||||
} catch (error) {
|
||||
// Last thing we want is to reset someones entire config
|
||||
// so notify them instead
|
||||
console.error(`Failed to load config file: ${error}`);
|
||||
Notify({
|
||||
summary: 'Failed to load config file',
|
||||
body: `${error}`,
|
||||
iconName: icons.ui.warning,
|
||||
});
|
||||
|
||||
errorHandler(error);
|
||||
}
|
||||
}
|
||||
config[this._id] = value;
|
||||
writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets the value of this option to its initial value if not persistent and if it differs from the current value.
|
||||
*
|
||||
* @param writeDisk - Whether to write the changes to disk. Defaults to true.
|
||||
* @returns Returns the option's ID if reset occurred, otherwise undefined.
|
||||
*/
|
||||
public reset(writeDiskProps: WriteDiskProps = {}): string | undefined {
|
||||
if (this.persistent) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let currentValue: string | T = this.get();
|
||||
currentValue = typeof currentValue === 'object' ? JSON.stringify(currentValue) : currentValue;
|
||||
let initialValue: string | T = this.initial;
|
||||
initialValue = typeof initialValue === 'object' ? JSON.stringify(initialValue) : initialValue;
|
||||
|
||||
if (currentValue !== initialValue) {
|
||||
this.set(this.initial, writeDiskProps);
|
||||
return this._id;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function _findVal(obj: Record<string, unknown>, path: string[]): unknown | undefined {
|
||||
const top = path.shift();
|
||||
|
||||
if (!top) {
|
||||
// The path is empty, so this is our value.
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (typeof obj !== 'object') {
|
||||
// Not an array, not an object, but we need to go deeper.
|
||||
// This is invalid, so return.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mergedPath = [top, ...path].join('.');
|
||||
|
||||
if (mergedPath in obj) {
|
||||
// The key exists on this level with dot-notation, so we return that.
|
||||
return obj[mergedPath];
|
||||
}
|
||||
|
||||
if (top in obj) {
|
||||
// The value exists but we are not there yet, so we recurse.
|
||||
return _findVal(obj[top] as Record<string, unknown>, path);
|
||||
}
|
||||
|
||||
// Key does not exist :(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an `Opt` instance with the given initial value and properties.
|
||||
* @template T
|
||||
* @param initial - The initial value.
|
||||
* @param [props] - Additional properties.
|
||||
*/
|
||||
export function opt<T>(initial: T, props?: OptProps): Opt<T> {
|
||||
return new Opt(initial, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively traverses the provided object to extract all `Opt` instances, assigning IDs to each.
|
||||
*
|
||||
* @param optionsObj - The object containing `Opt` instances.
|
||||
* @param [path=''] - The current path (used internally).
|
||||
* @param [arr=[]] - The accumulator array for found `Opt` instances.
|
||||
* @returns An array of all found `Opt` instances.
|
||||
*/
|
||||
function getOptions(optionsObj: Record<string, unknown>, path = '', arr: Opt[] = []): Opt[] {
|
||||
try {
|
||||
for (const key in optionsObj) {
|
||||
const value = optionsObj[key];
|
||||
const id = path ? `${path}.${key}` : key;
|
||||
|
||||
if (value instanceof Variable) {
|
||||
const optValue = value as Opt;
|
||||
optValue.id = id;
|
||||
arr.push(optValue);
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
getOptions(value as Record<string, unknown>, id, arr);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
} catch (error) {
|
||||
errorHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and initializes options from a given object structure. The returned object
|
||||
* includes methods to reset values, reset theme colors, and handle dependencies.
|
||||
*
|
||||
* @template T extends object
|
||||
* @param optionsObj - The object containing nested `Opt` instances.
|
||||
* @returns The original object extended with additional methods for handling options.
|
||||
*/
|
||||
export function mkOptions<T extends object>(optionsObj: T): T & MkOptionsResult {
|
||||
ensureDirectory(CONFIG_FILE.split('/').slice(0, -1).join('/'));
|
||||
|
||||
const rawConfig = readFile(CONFIG_FILE);
|
||||
|
||||
let config: Record<string, unknown> = {};
|
||||
if (rawConfig && rawConfig.trim() !== '') {
|
||||
try {
|
||||
config = JSON.parse(rawConfig) as Record<string, unknown>;
|
||||
} catch (error) {
|
||||
Notify({
|
||||
summary: 'Failed to load config file',
|
||||
body: `${error}`,
|
||||
iconName: icons.ui.warning,
|
||||
});
|
||||
// Continue with a broken config, the user has
|
||||
// been warned
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the config options
|
||||
const allOptions = getOptions(optionsObj as Record<string, unknown>);
|
||||
for (let i = 0; i < allOptions.length; i++) {
|
||||
allOptions[i].init(config);
|
||||
}
|
||||
|
||||
// Setup a file monitor to allow live config edit preview from outside
|
||||
// the config menu
|
||||
const debounceTimeMs = 200;
|
||||
let lastEventTime = Date.now();
|
||||
monitorFile(CONFIG_FILE, () => {
|
||||
if (Date.now() - lastEventTime < debounceTimeMs) {
|
||||
return;
|
||||
}
|
||||
lastEventTime = Date.now();
|
||||
|
||||
let newConfig: Record<string, unknown> = {};
|
||||
|
||||
const rawConfig = readFile(CONFIG_FILE);
|
||||
if (rawConfig && rawConfig.trim() !== '') {
|
||||
try {
|
||||
newConfig = JSON.parse(rawConfig) as Record<string, unknown>;
|
||||
} catch (error) {
|
||||
console.error(`Error loading configuration file: ${error}`);
|
||||
Notify({
|
||||
summary: 'Loading configuration file failed',
|
||||
body: `${error}`,
|
||||
iconName: icons.ui.warning,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < allOptions.length; i++) {
|
||||
const opt = allOptions[i];
|
||||
const newVal = _findVal(newConfig, opt.id.split('.'));
|
||||
|
||||
if (newVal === undefined) {
|
||||
// Set the variable but don't write it back to the file,
|
||||
// as we are getting it from there
|
||||
opt.reset({ writeDisk: false });
|
||||
continue;
|
||||
}
|
||||
|
||||
const oldVal = opt.get();
|
||||
if (newVal !== oldVal) {
|
||||
// Set the variable but don't write it back to the file,
|
||||
// as we are getting it from there
|
||||
opt.set(newVal, { writeDisk: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A simple sleep utility.
|
||||
*
|
||||
* @param [ms=0] - Milliseconds to sleep.
|
||||
*/
|
||||
function sleep(ms = 0): Promise<T> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all options to their initial values if possible.
|
||||
*
|
||||
* @param opts - Array of all option instances.
|
||||
* @returns IDs of all reset options.
|
||||
*/
|
||||
async function resetAll(opts: Opt[]): Promise<string[]> {
|
||||
const results: string[] = [];
|
||||
for (let i = 0; i < opts.length; i++) {
|
||||
const id = opts[i].reset();
|
||||
|
||||
if (id) {
|
||||
results.push(id);
|
||||
await sleep(50);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
return Object.assign(optionsObj, {
|
||||
array: (): Opt[] => allOptions,
|
||||
async reset(): Promise<string> {
|
||||
const ids = await resetAll(allOptions);
|
||||
|
||||
return ids.join('\n');
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a callback that fires when any option whose ID starts with any of the given dependencies changes.
|
||||
*
|
||||
* @param deps - An array of dependency prefixes.
|
||||
* @param callback - The callback function to execute on changes.
|
||||
*/
|
||||
handler(deps: string[], callback: () => void): void {
|
||||
for (let i = 0; i < allOptions.length; i++) {
|
||||
const opt = allOptions[i];
|
||||
|
||||
for (let j = 0; j < deps.length; j++) {
|
||||
if (opt.id.startsWith(deps[j])) {
|
||||
opt.subscribe(callback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
169
src/lib/options/ConfigManager.ts
Normal file
169
src/lib/options/ConfigManager.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { readFile, writeFile, monitorFile } from 'astal/file';
|
||||
import { errorHandler, Notify } from '../utils';
|
||||
import { ensureDirectory } from '../session';
|
||||
import icons from '../icons/icons';
|
||||
|
||||
/**
|
||||
* Manages configuration file operations including reading, writing, and change monitoring
|
||||
*
|
||||
* The ConfigManager centralizes all configuration persistence operations and provides
|
||||
* utilities for working with nested configuration structures.
|
||||
*/
|
||||
export class ConfigManager {
|
||||
private _configPath: string;
|
||||
private _changeCallbacks: Array<() => void> = [];
|
||||
|
||||
/**
|
||||
* Creates a new configuration manager for a specific config file
|
||||
*
|
||||
* @param configPath - Path to the configuration file to manage
|
||||
*/
|
||||
constructor(configPath: string) {
|
||||
this._configPath = configPath;
|
||||
this._ensureConfigDirectory();
|
||||
this._setupConfigMonitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single option in the configuration file
|
||||
*
|
||||
* @param id - Dot-notation path of the option to update
|
||||
* @param value - New value to store for the option
|
||||
*/
|
||||
public updateOption(id: string, value: unknown): void {
|
||||
const config = this.readConfig();
|
||||
config[id] = value;
|
||||
this.writeConfig(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a value from a nested object using a path
|
||||
*
|
||||
* @param dataObject - The object to search within
|
||||
* @param path - Dot-notation path or array of path segments
|
||||
* @returns The value at the specified path or undefined if not found
|
||||
*/
|
||||
public getNestedValue(dataObject: Record<string, unknown>, path: string | string[]): unknown {
|
||||
const pathArray = typeof path === 'string' ? path.split('.') : path;
|
||||
return this._findValueByPath(dataObject, pathArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the current configuration from disk
|
||||
*
|
||||
* @returns The parsed configuration object or an empty object if the file doesn't exist
|
||||
*/
|
||||
public readConfig(): Record<string, unknown> {
|
||||
const raw = readFile(this._configPath);
|
||||
if (!raw || raw.trim() === '') {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch (error) {
|
||||
this._handleConfigError(error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes configuration to disk
|
||||
*
|
||||
* @param config - The configuration object to serialize and save
|
||||
*/
|
||||
public writeConfig(config: Record<string, unknown>): void {
|
||||
writeFile(this._configPath, JSON.stringify(config, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be called when the config file changes
|
||||
*
|
||||
* @param callback - Function to execute when config file changes are detected
|
||||
*/
|
||||
public onConfigChanged(callback: () => void): void {
|
||||
this._changeCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively navigates an object to find a value at the specified path
|
||||
*
|
||||
* @param currentObject - The object currently being traversed
|
||||
* @param pathKeys - Remaining path segments to navigate
|
||||
* @returns The value at the path or undefined if not found
|
||||
*/
|
||||
private _findValueByPath(currentObject: Record<string, unknown>, pathKeys: string[]): unknown {
|
||||
const currentKey = pathKeys.shift();
|
||||
if (currentKey === undefined) {
|
||||
return currentObject;
|
||||
}
|
||||
if (!this._isObject(currentObject)) {
|
||||
return;
|
||||
}
|
||||
const propertyPath = [currentKey, ...pathKeys].join('.');
|
||||
if (propertyPath in currentObject) {
|
||||
return currentObject[propertyPath];
|
||||
}
|
||||
if (!(currentKey in currentObject)) {
|
||||
return;
|
||||
}
|
||||
const currentKeyValue = currentObject[currentKey];
|
||||
if (!this._isObject(currentKeyValue)) {
|
||||
return;
|
||||
}
|
||||
return this._findValueByPath(currentKeyValue, pathKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the directory for the config file exists
|
||||
*/
|
||||
private _ensureConfigDirectory(): void {
|
||||
ensureDirectory(this._configPath.split('/').slice(0, -1).join('/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up file monitoring to detect external changes to the config file
|
||||
*/
|
||||
private _setupConfigMonitor(): void {
|
||||
const debounceTimeMs = 200;
|
||||
let lastEventTime = Date.now();
|
||||
monitorFile(this._configPath, () => {
|
||||
if (Date.now() - lastEventTime < debounceTimeMs) {
|
||||
return;
|
||||
}
|
||||
lastEventTime = Date.now();
|
||||
this._notifyConfigChanged();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all registered callbacks about config file changes
|
||||
*/
|
||||
private _notifyConfigChanged(): void {
|
||||
this._changeCallbacks.forEach((callback) => callback());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles configuration parsing errors with appropriate logging and notification
|
||||
*
|
||||
* @param error - The error that occurred during config parsing
|
||||
*/
|
||||
private _handleConfigError(error: unknown): void {
|
||||
console.error(`Failed to load config file: ${error}`);
|
||||
Notify({
|
||||
summary: 'Failed to load config file',
|
||||
body: `${error}`,
|
||||
iconName: icons.ui.warning,
|
||||
});
|
||||
errorHandler(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard that checks if a value is a non-null object
|
||||
*
|
||||
* @param value - The value to check
|
||||
* @returns True if the value is a non-null object
|
||||
*/
|
||||
private _isObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
}
|
||||
98
src/lib/options/Opt.ts
Normal file
98
src/lib/options/Opt.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import Variable from 'astal/variable';
|
||||
import { ConfigManager } from './ConfigManager';
|
||||
|
||||
/**
|
||||
* Properties that can be passed when creating an option
|
||||
*/
|
||||
export interface OptProps {
|
||||
persistent?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for set operations
|
||||
*/
|
||||
export interface WriteOptions {
|
||||
writeDisk?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A managed application option with persistence capabilities
|
||||
*/
|
||||
export class Opt<T = unknown> extends Variable<T> {
|
||||
public readonly initial: T;
|
||||
public readonly persistent: boolean;
|
||||
private _id = '';
|
||||
private _configManager: ConfigManager;
|
||||
|
||||
constructor(initial: T, configManager: ConfigManager, { persistent = false }: OptProps = {}) {
|
||||
super(initial);
|
||||
this.initial = initial;
|
||||
this.persistent = persistent;
|
||||
this._configManager = configManager;
|
||||
}
|
||||
|
||||
public toJSON(): string {
|
||||
return `opt:${JSON.stringify(this.get())}`;
|
||||
}
|
||||
|
||||
public get value(): T {
|
||||
return this.get();
|
||||
}
|
||||
|
||||
public set value(val: T) {
|
||||
this.set(val);
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public set id(newId: string) {
|
||||
this._id = newId;
|
||||
}
|
||||
|
||||
public init(config: Record<string, unknown>): void {
|
||||
const value = this._configManager.getNestedValue(config, this._id);
|
||||
|
||||
if (value !== undefined) {
|
||||
this.set(value as T, { writeDisk: false });
|
||||
}
|
||||
}
|
||||
|
||||
public set = (value: T, { writeDisk = true }: WriteOptions = {}): void => {
|
||||
if (value === this.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.set(value);
|
||||
|
||||
if (writeDisk) {
|
||||
this._configManager.updateOption(this._id, value);
|
||||
}
|
||||
};
|
||||
|
||||
public reset(writeOptions: WriteOptions = {}): string | undefined {
|
||||
if (this.persistent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasChanged = this._hasChangedFromInitial();
|
||||
|
||||
if (hasChanged) {
|
||||
this.set(this.initial, writeOptions);
|
||||
return this._id;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private _hasChangedFromInitial(): boolean {
|
||||
let currentValue: string | T = this.get();
|
||||
currentValue = typeof currentValue === 'object' ? JSON.stringify(currentValue) : currentValue;
|
||||
|
||||
let initialValue: string | T = this.initial;
|
||||
initialValue = typeof initialValue === 'object' ? JSON.stringify(initialValue) : initialValue;
|
||||
|
||||
return currentValue !== initialValue;
|
||||
}
|
||||
}
|
||||
186
src/lib/options/OptionRegistry.ts
Normal file
186
src/lib/options/OptionRegistry.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import { Opt } from './Opt';
|
||||
import { ConfigManager } from './ConfigManager';
|
||||
import { MkOptionsResult, OptionsObject } from './options.types';
|
||||
import { errorHandler } from '../utils';
|
||||
|
||||
/**
|
||||
* Creates and manages a registry of application options
|
||||
*
|
||||
* Provides functionality to collect, initialize, reset, and track options throughout
|
||||
* the application. Handles configuration synchronization and dependency-based subscriptions.
|
||||
*/
|
||||
export class OptionRegistry<T extends OptionsObject> {
|
||||
private _options: Opt[] = [];
|
||||
private _optionsObj: T;
|
||||
private _configManager: ConfigManager;
|
||||
|
||||
/**
|
||||
* Creates a new option registry
|
||||
*
|
||||
* @param optionsObj - The object containing option definitions
|
||||
* @param configManager - The configuration manager to handle persistence
|
||||
*/
|
||||
constructor(optionsObj: T, configManager: ConfigManager) {
|
||||
this._optionsObj = optionsObj;
|
||||
this._configManager = configManager;
|
||||
this._initializeOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered options as an array
|
||||
*/
|
||||
public toArray(): Opt[] {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all options to their initial values
|
||||
*
|
||||
* @returns Newline-separated list of IDs for options that were reset
|
||||
*/
|
||||
public async reset(): Promise<string> {
|
||||
const results = await this._resetAllOptions(this._options);
|
||||
return results.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback for options matching the provided dependency prefixes
|
||||
*
|
||||
* @param optionsToWatch - Array of option ID prefixes to watch
|
||||
* @param callback - Function to call when matching options change
|
||||
*/
|
||||
public handler(optionsToWatch: string[], callback: () => void): void {
|
||||
optionsToWatch.forEach((prefix) => {
|
||||
const matchingOptions = this._options.filter((opt) => opt.id.startsWith(prefix));
|
||||
|
||||
matchingOptions.forEach((opt) => opt.subscribe(callback));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates options based on changes to the config file
|
||||
*
|
||||
* Synchronizes in-memory option values with the current state of the config file
|
||||
*/
|
||||
public handleConfigFileChange(): void {
|
||||
const newConfig = this._configManager.readConfig();
|
||||
|
||||
for (const opt of this._options) {
|
||||
const newVal = this._configManager.getNestedValue(newConfig, opt.id);
|
||||
|
||||
if (newVal === undefined) {
|
||||
opt.reset({ writeDisk: false });
|
||||
continue;
|
||||
}
|
||||
|
||||
const oldVal = opt.get();
|
||||
if (newVal !== oldVal) {
|
||||
opt.set(newVal, { writeDisk: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the enhanced options object with additional methods
|
||||
*
|
||||
* @returns The original options object enhanced with registry methods
|
||||
*/
|
||||
public createEnhancedOptions(): T & MkOptionsResult {
|
||||
return Object.assign(this._optionsObj, {
|
||||
toArray: this.toArray.bind(this),
|
||||
reset: this.reset.bind(this),
|
||||
handler: this.handler.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the option registry by collecting options and setting up monitoring
|
||||
*/
|
||||
private _initializeOptions(): void {
|
||||
this._options = this._collectOptions(this._optionsObj);
|
||||
this._initializeFromConfig();
|
||||
|
||||
this._configManager.onConfigChanged(() => {
|
||||
this.handleConfigFileChange();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes option values from the saved configuration
|
||||
*/
|
||||
private _initializeFromConfig(): void {
|
||||
const config = this._configManager.readConfig();
|
||||
|
||||
for (const opt of this._options) {
|
||||
opt.init(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively collects all option instances from an object structure
|
||||
*
|
||||
* @param sourceObject - The object to search for options
|
||||
* @param path - Current path in the object hierarchy
|
||||
* @returns Array of found option instances
|
||||
*/
|
||||
private _collectOptions(sourceObject: Record<string, unknown>, path = ''): Opt[] {
|
||||
const result: Opt[] = [];
|
||||
|
||||
try {
|
||||
for (const key in sourceObject) {
|
||||
const value = sourceObject[key];
|
||||
const id = path ? `${path}.${key}` : key;
|
||||
|
||||
if (value instanceof Opt) {
|
||||
value.id = id;
|
||||
result.push(value);
|
||||
} else if (this._isNestedObject(value)) {
|
||||
result.push(...this._collectOptions(value, id));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all options to their initial values with a delay between operations
|
||||
*
|
||||
* @param opts - Array of options to reset
|
||||
* @returns Array of IDs for options that were reset
|
||||
*/
|
||||
private async _resetAllOptions(opts: Opt[]): Promise<string[]> {
|
||||
const results: string[] = [];
|
||||
|
||||
for (const opt of opts) {
|
||||
const id = opt.reset();
|
||||
|
||||
if (id !== undefined) {
|
||||
results.push(id);
|
||||
await this._sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple promise-based sleep function
|
||||
*
|
||||
* @param ms - Milliseconds to sleep
|
||||
*/
|
||||
private _sleep(ms = 0): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if a value is a non-null object that can be traversed
|
||||
*
|
||||
* @param value - The value to check
|
||||
*/
|
||||
private _isNestedObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
}
|
||||
25
src/lib/options/index.ts
Normal file
25
src/lib/options/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ConfigManager } from './ConfigManager';
|
||||
import { Opt, OptProps } from './Opt';
|
||||
import { OptionRegistry } from './OptionRegistry';
|
||||
import { MkOptionsResult, OptionsObject } from './options.types';
|
||||
|
||||
const CONFIG_PATH = CONFIG_FILE;
|
||||
|
||||
const configManager = new ConfigManager(CONFIG_PATH);
|
||||
|
||||
/**
|
||||
* Creates an option with the specified initial value
|
||||
*/
|
||||
export function opt<T>(initial: T, props?: OptProps): Opt<T> {
|
||||
return new Opt(initial, configManager, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and initializes an options management system
|
||||
*/
|
||||
export function mkOptions<T extends OptionsObject>(optionsObj: T): T & MkOptionsResult {
|
||||
const registry = new OptionRegistry(optionsObj, configManager);
|
||||
return registry.createEnhancedOptions();
|
||||
}
|
||||
|
||||
export { Opt, OptProps, ConfigManager, OptionRegistry };
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Opt } from 'src/lib/option';
|
||||
import { Variable } from 'types/variable';
|
||||
import { defaultColorMap } from './defaults/options';
|
||||
import { Astal } from 'astal/gtk3';
|
||||
import { dropdownMenuList } from '../constants/options';
|
||||
import { FontStyle } from 'src/components/settings/shared/inputs/font/utils';
|
||||
import { Variable } from 'astal';
|
||||
import { defaultColorMap } from '../types/defaults/options.types';
|
||||
import { LabelSettingProps } from 'src/components/settings/shared/Label';
|
||||
import { Opt } from './Opt';
|
||||
|
||||
export type MkOptionsResult = {
|
||||
array: () => Opt[];
|
||||
export interface MkOptionsResult {
|
||||
toArray: () => Opt[];
|
||||
reset: () => Promise<string>;
|
||||
handler: (deps: string[], callback: () => void) => void;
|
||||
};
|
||||
handler: (optionsToWatch: string[], callback: () => void) => void;
|
||||
}
|
||||
|
||||
export type RecursiveOptionsObject = {
|
||||
[key: string]:
|
||||
@@ -22,6 +23,8 @@ export type RecursiveOptionsObject = {
|
||||
| Variable<boolean>;
|
||||
};
|
||||
|
||||
export type OptionsObject = Record<string, unknown>;
|
||||
|
||||
export type BarLocation = 'top' | 'bottom';
|
||||
export type AutoHide = 'never' | 'fullscreen' | 'single-window';
|
||||
export type BarModule =
|
||||
@@ -70,7 +73,15 @@ export type NotificationAnchor =
|
||||
| 'bottom left'
|
||||
| 'left'
|
||||
| 'right';
|
||||
export type OSDAnchor = 'top left' | 'top' | 'top right' | 'right' | 'bottom right' | 'bottom' | 'bottom left' | 'left';
|
||||
export type OSDAnchor =
|
||||
| 'top left'
|
||||
| 'top'
|
||||
| 'top right'
|
||||
| 'right'
|
||||
| 'bottom right'
|
||||
| 'bottom'
|
||||
| 'bottom left'
|
||||
| 'left';
|
||||
export type BarButtonStyles = 'default' | 'split' | 'wave' | 'wave2';
|
||||
|
||||
export type ThemeExportData = {
|
||||
@@ -101,7 +112,7 @@ export interface RowProps<T> {
|
||||
min?: number;
|
||||
disabledBinding?: Variable<boolean>;
|
||||
exportData?: ThemeExportData;
|
||||
subtitle?: string | VarType<string> | Opt;
|
||||
subtitle?: LabelSettingProps['subtitle'];
|
||||
subtitleLink?: string;
|
||||
dependencies?: string[];
|
||||
increment?: number;
|
||||
@@ -196,9 +207,9 @@ export type MatugenVariation = {
|
||||
base: HexColor;
|
||||
mantle: HexColor;
|
||||
crust: HexColor;
|
||||
notifications_closer: HexColor;
|
||||
notifications_background: HexColor;
|
||||
dashboard_btn_text: HexColor;
|
||||
notifications_closer?: HexColor;
|
||||
notifications_background?: HexColor;
|
||||
dashboard_btn_text?: HexColor;
|
||||
red2: HexColor;
|
||||
peach2: HexColor;
|
||||
pink2: HexColor;
|
||||
@@ -230,9 +241,6 @@ export type MatugenVariation = {
|
||||
yellow3: HexColor;
|
||||
maroon3: HexColor;
|
||||
crust3: HexColor;
|
||||
notifications_closer?: HexColor;
|
||||
notifications_background?: HexColor;
|
||||
dashboard_btn_text?: HexColor;
|
||||
};
|
||||
export type MatugenScheme =
|
||||
| 'content'
|
||||
@@ -244,6 +252,8 @@ export type MatugenScheme =
|
||||
| 'rainbow'
|
||||
| 'tonal-spot';
|
||||
|
||||
export type MatugenTheme = 'light' | 'dark';
|
||||
|
||||
export type MatugenVariations =
|
||||
| 'standard_1'
|
||||
| 'standard_2'
|
||||
@@ -255,8 +265,6 @@ export type MatugenVariations =
|
||||
| 'vivid_2'
|
||||
| 'vivid_3';
|
||||
|
||||
type MatugenTheme = 'light' | 'dark';
|
||||
|
||||
export type ColorMapKey = keyof typeof defaultColorMap;
|
||||
export type ColorMapValue = (typeof defaultColorMap)[ColorMapKey];
|
||||
|
||||
@@ -264,7 +272,15 @@ export type ScalingPriority = 'gdk' | 'hyprland' | 'both';
|
||||
|
||||
export type BluetoothBatteryState = 'paired' | 'connected' | 'always';
|
||||
|
||||
export type BorderLocation = 'none' | 'top' | 'right' | 'bottom' | 'left' | 'horizontal' | 'vertical' | 'full';
|
||||
export type BorderLocation =
|
||||
| 'none'
|
||||
| 'top'
|
||||
| 'right'
|
||||
| 'bottom'
|
||||
| 'left'
|
||||
| 'horizontal'
|
||||
| 'vertical'
|
||||
| 'full';
|
||||
|
||||
export type PositionAnchor = { [key: string]: Astal.WindowAnchor };
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import { Bind } from 'src/lib/types/variable';
|
||||
import { GenericFunction } from 'src/lib/types/customModules/generic';
|
||||
import { BarModule } from 'src/lib/types/options';
|
||||
import { GenericFunction } from '../types/customModules/generic.types';
|
||||
import { BarModule } from '../options/options.types';
|
||||
import { Poller } from './Poller';
|
||||
import { execAsync, Variable } from 'astal';
|
||||
import { Binding, execAsync, Variable } from 'astal';
|
||||
|
||||
/**
|
||||
* A class that manages polling of a variable by executing a bash command at specified intervals.
|
||||
*/
|
||||
export class BashPoller<Value, Parameters extends unknown[]> {
|
||||
private poller: Poller;
|
||||
private _poller: Poller;
|
||||
|
||||
private params: Parameters;
|
||||
private _params: Parameters;
|
||||
|
||||
/**
|
||||
* Creates an instance of BashPoller.
|
||||
*
|
||||
* @param targetVariable - The target variable to poll.
|
||||
* @param trackers - An array of trackers to monitor.
|
||||
* @param pollingInterval - The interval at which polling occurs.
|
||||
* @param updateCommand - The command to update the target variable.
|
||||
* @param pollingFunction - The function to execute during each poll.
|
||||
* @param _targetVariable - The target variable to poll.
|
||||
* @param _trackers - An array of trackers to monitor.
|
||||
* @param _pollingInterval - The interval at which polling occurs.
|
||||
* @param _updateCommand - The command to update the target variable.
|
||||
* @param _pollingFunction - The function to execute during each poll.
|
||||
* @param params - Additional parameters for the polling function.
|
||||
*
|
||||
* @example
|
||||
@@ -38,16 +37,16 @@ export class BashPoller<Value, Parameters extends unknown[]> {
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
private targetVariable: Variable<Value>,
|
||||
private trackers: Bind[],
|
||||
private pollingInterval: Bind,
|
||||
private updateCommand: string,
|
||||
private pollingFunction: GenericFunction<Value, [string, ...Parameters]>,
|
||||
private _targetVariable: Variable<Value>,
|
||||
private _trackers: Binding<unknown>[],
|
||||
private _pollingInterval: Binding<number>,
|
||||
private _updateCommand: string,
|
||||
private _pollingFunction: GenericFunction<Value, [string, ...Parameters]>,
|
||||
...params: Parameters
|
||||
) {
|
||||
this.params = params;
|
||||
this._params = params;
|
||||
|
||||
this.poller = new Poller(this.pollingInterval, this.trackers, this.execute);
|
||||
this._poller = new Poller(this._pollingInterval, this._trackers, this.execute);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,10 +57,10 @@ export class BashPoller<Value, Parameters extends unknown[]> {
|
||||
*/
|
||||
public execute = async (): Promise<void> => {
|
||||
try {
|
||||
const res = await execAsync(`bash -c "${this.updateCommand}"`);
|
||||
this.targetVariable.set(await this.pollingFunction(res, ...this.params));
|
||||
const res = await execAsync(`bash -c "${this._updateCommand}"`);
|
||||
this._targetVariable.set(await this._pollingFunction(res, ...this._params));
|
||||
} catch (error) {
|
||||
console.error(`Error executing bash command "${this.updateCommand}":`, error);
|
||||
console.error(`Error executing bash command "${this._updateCommand}":`, error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -69,14 +68,14 @@ export class BashPoller<Value, Parameters extends unknown[]> {
|
||||
* Starts the polling process.
|
||||
*/
|
||||
public start(): void {
|
||||
this.poller.start();
|
||||
this._poller.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the polling process.
|
||||
*/
|
||||
public stop(): void {
|
||||
this.poller.stop();
|
||||
this._poller.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,6 +84,6 @@ export class BashPoller<Value, Parameters extends unknown[]> {
|
||||
* @param moduleName - The name of the module to initialize.
|
||||
*/
|
||||
public initialize(moduleName?: BarModule): void {
|
||||
this.poller.initialize(moduleName);
|
||||
this._poller.initialize(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import { Bind } from 'src/lib/types/variable';
|
||||
import { GenericFunction } from 'src/lib/types/customModules/generic';
|
||||
import { BarModule } from 'src/lib/types/options';
|
||||
import { GenericFunction } from '../types/customModules/generic.types';
|
||||
import { BarModule } from '../options/options.types';
|
||||
import { Poller } from './Poller';
|
||||
import { Variable } from 'astal';
|
||||
import { Binding, Variable } from 'astal';
|
||||
|
||||
/**
|
||||
* A class that manages polling of a variable by executing a generic function at specified intervals.
|
||||
*/
|
||||
export class FunctionPoller<Value, Parameters extends unknown[] = []> {
|
||||
private poller: Poller;
|
||||
private _poller: Poller;
|
||||
|
||||
private params: Parameters;
|
||||
private _params: Parameters;
|
||||
|
||||
/**
|
||||
* Creates an instance of FunctionPoller.
|
||||
*
|
||||
* @param targetVariable - The target variable to poll.
|
||||
* @param trackers - An array of trackers to monitor.
|
||||
* @param pollingInterval - The interval at which polling occurs.
|
||||
* @param pollingFunction - The function to execute during each poll.
|
||||
* @param _targetVariable - The target variable to poll.
|
||||
* @param _trackers - An array of trackers to monitor.
|
||||
* @param _pollingInterval - The interval at which polling occurs.
|
||||
* @param _pollingFunction - The function to execute during each poll.
|
||||
* @param params - Additional parameters for the polling function.
|
||||
*
|
||||
* @example
|
||||
@@ -36,15 +35,15 @@ export class FunctionPoller<Value, Parameters extends unknown[] = []> {
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
private targetVariable: Variable<Value>,
|
||||
private trackers: Bind[],
|
||||
private pollingInterval: Bind,
|
||||
private pollingFunction: GenericFunction<Value, Parameters>,
|
||||
private _targetVariable: Variable<Value>,
|
||||
private _trackers: Binding<unknown>[],
|
||||
private _pollingInterval: Binding<number>,
|
||||
private _pollingFunction: GenericFunction<Value, Parameters>,
|
||||
...params: Parameters
|
||||
) {
|
||||
this.params = params;
|
||||
this._params = params;
|
||||
|
||||
this.poller = new Poller(this.pollingInterval, this.trackers, this.execute);
|
||||
this._poller = new Poller(this._pollingInterval, this._trackers, this._execute);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,10 +51,10 @@ export class FunctionPoller<Value, Parameters extends unknown[] = []> {
|
||||
*
|
||||
* The result of the function is assigned to the target variable.
|
||||
*/
|
||||
private execute = async (): Promise<void> => {
|
||||
private _execute = async (): Promise<void> => {
|
||||
try {
|
||||
const result = await this.pollingFunction(...this.params);
|
||||
this.targetVariable.set(result);
|
||||
const result = await this._pollingFunction(...this._params);
|
||||
this._targetVariable.set(result);
|
||||
} catch (error) {
|
||||
console.error('Error executing polling function:', error);
|
||||
}
|
||||
@@ -65,14 +64,14 @@ export class FunctionPoller<Value, Parameters extends unknown[] = []> {
|
||||
* Starts the polling process.
|
||||
*/
|
||||
public start(): void {
|
||||
this.poller.start();
|
||||
this._poller.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the polling process.
|
||||
*/
|
||||
public stop(): void {
|
||||
this.poller.stop();
|
||||
this._poller.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,6 +80,6 @@ export class FunctionPoller<Value, Parameters extends unknown[] = []> {
|
||||
* @param moduleName - The name of the module to initialize.
|
||||
*/
|
||||
public initialize(moduleName?: BarModule): void {
|
||||
this.poller.initialize(moduleName);
|
||||
this._poller.initialize(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Bind } from 'src/lib/types/variable';
|
||||
import { BarModule } from 'src/lib/types/options';
|
||||
import { getLayoutItems } from 'src/lib/utils';
|
||||
import { AstalIO, interval, Variable } from 'astal';
|
||||
import { AstalIO, Binding, interval, Variable } from 'astal';
|
||||
import options from 'src/options';
|
||||
import { BarModule } from '../options/options.types';
|
||||
|
||||
const { layouts } = options.bar;
|
||||
|
||||
@@ -20,8 +20,8 @@ export class Poller {
|
||||
* @param pollingFunction - The function to execute during each poll.
|
||||
*/
|
||||
constructor(
|
||||
private _pollingInterval: Bind,
|
||||
private _trackers: Bind[],
|
||||
private _pollingInterval: Binding<number>,
|
||||
private _trackers: Binding<unknown>[],
|
||||
pollingFunction: () => Promise<void>,
|
||||
) {
|
||||
this._pollingFunction = pollingFunction;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { GtkWidget } from 'src/lib/types/widget.js';
|
||||
import { Gdk } from 'astal/gtk3';
|
||||
import { ThrottleFn } from '../types/utils';
|
||||
import { ThrottleFn } from '../types/utils.types';
|
||||
import { GtkWidget } from '../types/widget.types';
|
||||
|
||||
/**
|
||||
* Connects a primary click handler and returns a disconnect function.
|
||||
*/
|
||||
export function onPrimaryClick(widget: GtkWidget, handler: (self: GtkWidget, event: Gdk.Event) => void): () => void {
|
||||
export function onPrimaryClick(
|
||||
widget: GtkWidget,
|
||||
handler: (self: GtkWidget, event: Gdk.Event) => void,
|
||||
): () => void {
|
||||
const id = widget.connect('button-press-event', (self: GtkWidget, event: Gdk.Event) => {
|
||||
const eventButton = event.get_button()[1];
|
||||
if (eventButton === Gdk.BUTTON_PRIMARY) {
|
||||
@@ -18,7 +21,10 @@ export function onPrimaryClick(widget: GtkWidget, handler: (self: GtkWidget, eve
|
||||
/**
|
||||
* Connects a secondary click handler and returns a disconnect function.
|
||||
*/
|
||||
export function onSecondaryClick(widget: GtkWidget, handler: (self: GtkWidget, event: Gdk.Event) => void): () => void {
|
||||
export function onSecondaryClick(
|
||||
widget: GtkWidget,
|
||||
handler: (self: GtkWidget, event: Gdk.Event) => void,
|
||||
): () => void {
|
||||
const id = widget.connect('button-press-event', (self: GtkWidget, event: Gdk.Event) => {
|
||||
const eventButton = event.get_button()[1];
|
||||
if (eventButton === Gdk.BUTTON_SECONDARY) {
|
||||
@@ -31,7 +37,10 @@ export function onSecondaryClick(widget: GtkWidget, handler: (self: GtkWidget, e
|
||||
/**
|
||||
* Connects a middle click handler and returns a disconnect function.
|
||||
*/
|
||||
export function onMiddleClick(widget: GtkWidget, handler: (self: GtkWidget, event: Gdk.Event) => void): () => void {
|
||||
export function onMiddleClick(
|
||||
widget: GtkWidget,
|
||||
handler: (self: GtkWidget, event: Gdk.Event) => void,
|
||||
): () => void {
|
||||
const id = widget.connect('button-press-event', (self: GtkWidget, event: Gdk.Event) => {
|
||||
const eventButton = event.get_button()[1];
|
||||
if (eventButton === Gdk.BUTTON_MIDDLE) {
|
||||
|
||||
@@ -35,7 +35,7 @@ export function useHook(
|
||||
};
|
||||
|
||||
const hookIntoTarget = (): void => {
|
||||
if (signal && isConnectable(hookTarget)) {
|
||||
if (signal !== undefined && isConnectable(hookTarget)) {
|
||||
passedWidget.hook(hookTarget, signal, executeSetup);
|
||||
} else if (isSubscribable(hookTarget)) {
|
||||
passedWidget.hook(hookTarget, executeSetup);
|
||||
|
||||
@@ -23,7 +23,8 @@ export const getCurrentPlayer = (
|
||||
const playerStillExists = mprisPlayers.some((p) => activePlayer.bus_name === p.bus_name);
|
||||
|
||||
const nextPlayerUp = mprisPlayers.sort(
|
||||
(a: AstalMpris.Player, b: AstalMpris.Player) => statusOrder[a.playbackStatus] - statusOrder[b.playbackStatus],
|
||||
(a: AstalMpris.Player, b: AstalMpris.Player) =>
|
||||
statusOrder[a.playbackStatus] - statusOrder[b.playbackStatus],
|
||||
)[0];
|
||||
|
||||
if (isPlaying || !playerStillExists) {
|
||||
|
||||
11
src/lib/types/astal-extensions.d.ts
vendored
11
src/lib/types/astal-extensions.d.ts
vendored
@@ -1,11 +0,0 @@
|
||||
import { Gdk } from 'astal/gtk3';
|
||||
|
||||
declare module 'astal/gtk3' {
|
||||
interface EventButton extends Gdk.Event {
|
||||
get_root_coords(): [number, number];
|
||||
}
|
||||
|
||||
interface EventScroll extends Gdk.Event {
|
||||
direction: Gdk.ScrollDirection;
|
||||
}
|
||||
}
|
||||
14
src/lib/types/audio.d.ts
vendored
14
src/lib/types/audio.d.ts
vendored
@@ -1,14 +0,0 @@
|
||||
export type InputDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
|
||||
|
||||
type DummyDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
|
||||
type RealPlaybackDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
|
||||
export type PlaybackDevices = DummyDevices | RealPlaybackDevices;
|
||||
|
||||
export type MediaTags = {
|
||||
title: string;
|
||||
artists: string;
|
||||
artist: string;
|
||||
album: string;
|
||||
name: string;
|
||||
identity: string;
|
||||
};
|
||||
8
src/lib/types/audio.types.ts
Normal file
8
src/lib/types/audio.types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type MediaTags = {
|
||||
title: string;
|
||||
artists: string;
|
||||
artist: string;
|
||||
album: string;
|
||||
name: string;
|
||||
identity: string;
|
||||
};
|
||||
@@ -1,28 +1,22 @@
|
||||
import { Variable } from 'types/variable';
|
||||
import Box from 'types/widgets/box';
|
||||
import Button, { ButtonProps } from 'types/widgets/button';
|
||||
import Label from 'types/widgets/label';
|
||||
import { Attribute, Child } from './widget';
|
||||
import { Widget } from 'astal/gtk3';
|
||||
import { Binding } from 'astal';
|
||||
import { Binding, Variable } from 'astal';
|
||||
import { Connectable } from 'astal/binding';
|
||||
import { CustomBarModuleStyle } from 'src/components/bar/custom_modules/types';
|
||||
import { BoxWidget } from './widget.types';
|
||||
import { Label } from 'astal/gtk3/widget';
|
||||
|
||||
export type BarBoxChild = {
|
||||
component: JSX.Element;
|
||||
isVisible?: boolean;
|
||||
isVis?: Variable<boolean>;
|
||||
isVis?: Binding<boolean>;
|
||||
isBox?: boolean;
|
||||
boxClass: string;
|
||||
tooltip_text?: string | Binding<string>;
|
||||
} & ({ isBox: true; props: Widget.EventBoxProps } | { isBox?: false; props: Widget.ButtonProps });
|
||||
|
||||
export type SelfButton = Button<Child, Attribute>;
|
||||
export type BoxHook = (self: BoxWidget) => void;
|
||||
export type LabelHook = (self: Label) => void;
|
||||
|
||||
export type BoxHook = (self: Box<Gtk.Widget, Gtk.Widget>) => void;
|
||||
export type LabelHook = (self: Label<Gtk.Widget>) => void;
|
||||
|
||||
export type BarModule = {
|
||||
export type BarModuleProps = {
|
||||
icon?: string | Binding<string>;
|
||||
textIcon?: string | Binding<string>;
|
||||
useTextIcon?: Binding<boolean>;
|
||||
@@ -32,11 +26,11 @@ export type BarModule = {
|
||||
boundLabel?: string;
|
||||
tooltipText?: string | Binding<string>;
|
||||
boxClass: string;
|
||||
isVis?: Variable<boolean>;
|
||||
isVis?: Binding<boolean>;
|
||||
props?: Widget.ButtonProps;
|
||||
showLabel?: boolean;
|
||||
showLabelBinding?: Binding;
|
||||
showIconBinding?: Binding;
|
||||
showLabelBinding?: Binding<boolean>;
|
||||
showIconBinding?: Binding<boolean>;
|
||||
hook?: BoxHook;
|
||||
connection?: Binding<Connectable>;
|
||||
};
|
||||
@@ -1,5 +1,3 @@
|
||||
import { layoutMap } from 'src/components/bar/modules/kblayout/layouts';
|
||||
|
||||
export type KbLabelType = 'layout' | 'code';
|
||||
|
||||
export type HyprctlKeyboard = {
|
||||
@@ -27,6 +25,3 @@ export type HyprctlDeviceLayout = {
|
||||
touch: unknown[];
|
||||
switches: unknown[];
|
||||
};
|
||||
|
||||
export type LayoutKeys = keyof typeof layoutMap;
|
||||
export type LayoutValues = (typeof layoutMap)[LayoutKeys];
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Variable } from 'astal';
|
||||
import { Opt } from 'src/lib/option';
|
||||
import { Binding } from 'src/lib/utils';
|
||||
import { EventArgs } from '../widget.types';
|
||||
import { Opt } from 'src/lib/options';
|
||||
|
||||
export type InputHandlerEventArgs = {
|
||||
cmd?: Opt<string> | Variable<string>;
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Variable } from 'astal';
|
||||
|
||||
export type ShortcutFixed = {
|
||||
tooltip: string;
|
||||
command: string;
|
||||
@@ -6,9 +8,9 @@ export type ShortcutFixed = {
|
||||
};
|
||||
|
||||
export type ShortcutVariable = {
|
||||
tooltip: VarType<string>;
|
||||
command: VarType<string>;
|
||||
icon: VarType<string>;
|
||||
tooltip: Variable<string>;
|
||||
command: Variable<string>;
|
||||
icon: Variable<string>;
|
||||
configurable?: true;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Astal } from 'astal/gtk3';
|
||||
import { NetstatLabelType, ResourceLabelType } from '../bar';
|
||||
import { BarLocation } from '../options';
|
||||
import { NetstatLabelType, ResourceLabelType } from '../bar.types';
|
||||
import { BarLocation } from '../../options/options.types';
|
||||
|
||||
export const LABEL_TYPES: ResourceLabelType[] = ['used/total', 'used', 'free', 'percentage'];
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { RateUnit } from '../bar';
|
||||
import { NetworkResourceData } from '../customModules/network';
|
||||
|
||||
export const GET_DEFAULT_NETSTAT_DATA = (dataType: RateUnit): NetworkResourceData => {
|
||||
if (dataType === 'auto') {
|
||||
return { in: `0 Kib/s`, out: `0 Kib/s` };
|
||||
}
|
||||
|
||||
return { in: `0 ${dataType}/s`, out: `0 ${dataType}/s` };
|
||||
};
|
||||
10
src/lib/types/defaults/netstat.types.ts
Normal file
10
src/lib/types/defaults/netstat.types.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { RateUnit } from '../bar.types';
|
||||
import { NetworkResourceData } from '../customModules/network.types';
|
||||
|
||||
export const getDefaultNetstatData = (dataType: RateUnit): NetworkResourceData => {
|
||||
if (dataType === 'auto') {
|
||||
return { in: `0 Kib/s`, out: `0 Kib/s` };
|
||||
}
|
||||
|
||||
return { in: `0 ${dataType}/s`, out: `0 ${dataType}/s` };
|
||||
};
|
||||
@@ -1,14 +1,13 @@
|
||||
import { GtkWidget, Transition } from './widget';
|
||||
import { Astal, Gtk } from 'astal/gtk3';
|
||||
import { WindowProps } from 'astal/gtk3/widget';
|
||||
import { Opt } from '../option';
|
||||
import { Binding } from 'astal';
|
||||
import { WindowProps } from 'astal/gtk3/widget';
|
||||
|
||||
export interface DropdownMenuProps extends WindowProps {
|
||||
export interface DropdownMenuProps {
|
||||
name: string;
|
||||
child?: JSX.Element | JSX.Element[];
|
||||
child?: JSX.Element | JSX.Element[] | Binding<JSX.Element | undefined>;
|
||||
layout?: string;
|
||||
transition?: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>;
|
||||
exclusivity?: Astal.Exclusivity;
|
||||
fixed?: boolean;
|
||||
onDestroy?: () => void;
|
||||
}
|
||||
4
src/lib/types/globals.d.ts
vendored
4
src/lib/types/globals.d.ts
vendored
@@ -1,4 +0,0 @@
|
||||
export type MousePos = {
|
||||
source: string;
|
||||
pos: number[];
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Process } from 'astal';
|
||||
|
||||
export type GPUStatProcess = {
|
||||
username: string;
|
||||
command: string;
|
||||
@@ -1,5 +1,4 @@
|
||||
import AstalMpris from 'gi://AstalMpris?version=0.1';
|
||||
import icons2 from '../icons/icons2';
|
||||
|
||||
export type PlaybackIconMap = {
|
||||
[key in AstalMpris.PlaybackStatus]: string;
|
||||
@@ -1,5 +1,3 @@
|
||||
import { WIFI_STATUS_MAP } from 'src/globals/network';
|
||||
|
||||
export type AccessPoint = {
|
||||
bssid: string | null;
|
||||
address: string | null;
|
||||
@@ -11,6 +9,4 @@ export type AccessPoint = {
|
||||
iconName: string | undefined;
|
||||
};
|
||||
|
||||
export type WifiStatus = keyof typeof WIFI_STATUS_MAP;
|
||||
|
||||
export type WifiIcon = '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '';
|
||||
@@ -6,7 +6,7 @@ export interface NotificationArgs {
|
||||
iconName?: string;
|
||||
id?: number;
|
||||
summary?: string;
|
||||
urgency?: Urgency;
|
||||
urgency?: string;
|
||||
category?: string;
|
||||
timeout?: number;
|
||||
transient?: boolean;
|
||||
52
src/lib/types/popupwindow.d.ts
vendored
52
src/lib/types/popupwindow.d.ts
vendored
@@ -1,52 +0,0 @@
|
||||
import { Widget } from 'types/widgets/widget';
|
||||
import { WindowProps } from 'types/widgets/window';
|
||||
import { Transition } from './widget';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
|
||||
export type PopupWindowProps = {
|
||||
name: string;
|
||||
child?: JSX.Element | JSX.Element[];
|
||||
layout?: Layouts;
|
||||
transition?: Transition | Binding<Transition>;
|
||||
exclusivity?: Exclusivity;
|
||||
} & WindowProps;
|
||||
|
||||
export type LayoutFunction = (
|
||||
name: string,
|
||||
child: Widget,
|
||||
transition: Gtk.RevealerTransitionType,
|
||||
) => {
|
||||
center: () => Widget;
|
||||
top: () => Widget;
|
||||
'top-right': () => Widget;
|
||||
'top-center': () => Widget;
|
||||
'top-left': () => Widget;
|
||||
'bottom-left': () => Widget;
|
||||
'bottom-center': () => Widget;
|
||||
'bottom-right': () => Widget;
|
||||
};
|
||||
export type Layouts =
|
||||
| 'center'
|
||||
| 'top'
|
||||
| 'top-right'
|
||||
| 'top-center'
|
||||
| 'top-left'
|
||||
| 'bottom-left'
|
||||
| 'bottom-center'
|
||||
| 'bottom-right';
|
||||
|
||||
export type Opts = {
|
||||
className: string;
|
||||
vexpand: boolean;
|
||||
};
|
||||
|
||||
export type PaddingProps = {
|
||||
name: string;
|
||||
opts?: Opts;
|
||||
};
|
||||
|
||||
export type PopupRevealerProps = {
|
||||
name: string;
|
||||
child: GtkWidget;
|
||||
transition: Gtk.RevealerTransitionType;
|
||||
};
|
||||
51
src/lib/types/popupwindow.types.ts
Normal file
51
src/lib/types/popupwindow.types.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Astal, Gtk } from 'astal/gtk3';
|
||||
import { Binding } from 'astal';
|
||||
import { WindowProps } from 'astal/gtk3/widget';
|
||||
|
||||
export interface PopupWindowProps extends WindowProps {
|
||||
name: string;
|
||||
child?: JSX.Element;
|
||||
layout?: Layouts;
|
||||
transition?: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>;
|
||||
exclusivity?: Astal.Exclusivity;
|
||||
}
|
||||
|
||||
export type LayoutFunction = (
|
||||
name: string,
|
||||
child: JSX.Element,
|
||||
transition: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>,
|
||||
) => {
|
||||
center: () => JSX.Element;
|
||||
top: () => JSX.Element;
|
||||
'top-right': () => JSX.Element;
|
||||
'top-center': () => JSX.Element;
|
||||
'top-left': () => JSX.Element;
|
||||
'bottom-left': () => JSX.Element;
|
||||
'bottom-center': () => JSX.Element;
|
||||
'bottom-right': () => JSX.Element;
|
||||
};
|
||||
export type Layouts =
|
||||
| 'center'
|
||||
| 'top'
|
||||
| 'top-right'
|
||||
| 'top-center'
|
||||
| 'top-left'
|
||||
| 'bottom-left'
|
||||
| 'bottom-center'
|
||||
| 'bottom-right';
|
||||
|
||||
export type Opts = {
|
||||
className: string;
|
||||
vexpand: boolean;
|
||||
};
|
||||
|
||||
export type PaddingProps = {
|
||||
name: string;
|
||||
opts?: Opts;
|
||||
};
|
||||
|
||||
export type PopupRevealerProps = {
|
||||
name: string;
|
||||
child: JSX.Element;
|
||||
transition: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>;
|
||||
};
|
||||
9
src/lib/types/powerprofiles.d.ts
vendored
9
src/lib/types/powerprofiles.d.ts
vendored
@@ -1,9 +0,0 @@
|
||||
import PowerProfiles from 'types/service/powerprofiles.js';
|
||||
|
||||
export type PowerProfiles = InstanceType<typeof PowerProfiles>;
|
||||
export type PowerProfile = 'power-saver' | 'balanced' | 'performance';
|
||||
export type PowerProfileObject = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export type ProfileType = 'balanced' | 'power-saver' | 'performance';
|
||||
1
src/lib/types/powerprofiles.types.ts
Normal file
1
src/lib/types/powerprofiles.types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type ProfileType = 'balanced' | 'power-saver' | 'performance';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EventArgs } from './eventArgs';
|
||||
import { Variable } from 'types/variable';
|
||||
import { Variable } from 'astal';
|
||||
import { EventArgs } from './widget.types';
|
||||
|
||||
export type ThrottleFn = (
|
||||
cmd: string,
|
||||
1
src/lib/types/variable.d.ts
vendored
1
src/lib/types/variable.d.ts
vendored
@@ -1 +0,0 @@
|
||||
export type Bind = OriginalBinding<GObject.Object, keyof Props<GObject.Object>, unknown>;
|
||||
3
src/lib/types/volume.d.ts
vendored
3
src/lib/types/volume.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
export type VolumeIcons = {
|
||||
[index: number]: string;
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Gdk, Gtk } from 'astal/gtk3';
|
||||
import Box from 'types/widgets/box';
|
||||
import { Binding } from 'astal';
|
||||
import { Astal, Gdk, Gtk } from 'astal/gtk3';
|
||||
|
||||
export type Exclusivity = 'normal' | 'ignore' | 'exclusive';
|
||||
export type Anchor = 'left' | 'right' | 'top' | 'down';
|
||||
export type Transition = 'none' | 'crossfade' | 'slide_right' | 'slide_left' | 'slide_up' | 'slide_down';
|
||||
|
||||
@@ -17,19 +16,13 @@ export type Layouts =
|
||||
|
||||
export type Attribute = unknown;
|
||||
export type Child = Gtk.Widget;
|
||||
export type GtkWidget = Gtk.Widget;
|
||||
export type BoxWidget = Box<GtkWidget, Child>;
|
||||
export type BoxWidget = Gtk.Box;
|
||||
export type GdkEvent = Gdk.Event;
|
||||
|
||||
export type GButton = Gtk.Button;
|
||||
export type GBox = Gtk.Box;
|
||||
export type GLabel = Gtk.Label;
|
||||
export type GCenterBox = Gtk.Box;
|
||||
|
||||
export type EventHandler<Self> = (self: Self, event: Gdk.Event) => boolean | unknown;
|
||||
export type EventArgs = {
|
||||
clicked: GtkWidget;
|
||||
event: Gdk.EventButton | Gdk.EventScroll;
|
||||
event: Gdk.Event;
|
||||
};
|
||||
|
||||
export interface WidgetProps {
|
||||
@@ -48,7 +41,7 @@ export interface GtkWidgetExtended extends Gtk.Widget {
|
||||
isVisible?: boolean;
|
||||
boxClass?: string;
|
||||
isVis?: {
|
||||
bind: (key: string) => Bind;
|
||||
bind: (key: string) => Binding<boolean>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { BarModule, NotificationAnchor, PositionAnchor } from './types/options';
|
||||
import { OSDAnchor } from './types/options';
|
||||
import icons, { substitutes } from './icons/icons';
|
||||
import { BarModule, NotificationAnchor, PositionAnchor } from './options/options.types';
|
||||
import { OSDAnchor } from './options/options.types';
|
||||
import icons from './icons/icons';
|
||||
import GLib from 'gi://GLib?version=2.0';
|
||||
import GdkPixbuf from 'gi://GdkPixbuf';
|
||||
import { NotificationArgs } from './types/notification';
|
||||
import { NotificationArgs } from './types/notification.types';
|
||||
import { namedColors } from './constants/colors';
|
||||
import { distroIcons } from './constants/distro';
|
||||
import { distro } from './variables';
|
||||
import options from '../options';
|
||||
import { Astal, Gdk, Gtk } from 'astal/gtk3';
|
||||
import AstalApps from 'gi://AstalApps?version=0.1';
|
||||
import { exec, execAsync } from 'astal/process';
|
||||
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
|
||||
import { Primitive } from './types/utils';
|
||||
import { Primitive } from './types/utils.types';
|
||||
|
||||
const notifdService = AstalNotifd.get_default();
|
||||
|
||||
@@ -56,7 +55,7 @@ export function errorHandler(error: unknown): never {
|
||||
* @returns The Gtk.IconInfo object if the icon is found, or null if not found.
|
||||
*/
|
||||
export function lookUpIcon(name?: string, size = 16): Gtk.IconInfo | null {
|
||||
if (!name) return null;
|
||||
if (name === undefined) return null;
|
||||
|
||||
return Gtk.IconTheme.get_default().lookup_icon(name, size, Gtk.IconLookupFlags.USE_BUILTIN);
|
||||
}
|
||||
@@ -87,37 +86,6 @@ export function getLayoutItems(): BarModule[] {
|
||||
return [...new Set(itemsInLayout)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the appropriate icon based on the provided name and fallback.
|
||||
*
|
||||
* This function returns a substitute icon if available, the original name if it exists as a file, or a fallback icon.
|
||||
* It also logs a message if no substitute icon is found.
|
||||
*
|
||||
* @param name The name of the icon to look up.
|
||||
* @param fallback The fallback icon to use if the name is not found. Defaults to `icons.missing`.
|
||||
*
|
||||
* @returns The icon name or the fallback icon.
|
||||
*/
|
||||
export function icon(name: string | null, fallback = icons.missing): string {
|
||||
const validateSubstitute = (name: string): name is keyof typeof substitutes => name in substitutes;
|
||||
|
||||
if (!name) return fallback || '';
|
||||
|
||||
if (GLib.file_test(name, GLib.FileTest.EXISTS)) return name;
|
||||
|
||||
let icon: string = name;
|
||||
|
||||
if (validateSubstitute(name)) {
|
||||
icon = substitutes[name];
|
||||
}
|
||||
|
||||
if (lookUpIcon(icon)) return icon;
|
||||
|
||||
console.log(`No icon substitute "${icon}" for "${name}", fallback: "${fallback}"`);
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a bash command asynchronously.
|
||||
*
|
||||
@@ -130,8 +98,8 @@ export function icon(name: string | null, fallback = icons.missing): string {
|
||||
* @returns A promise that resolves to the command output as a string.
|
||||
*/
|
||||
export async function bash(strings: TemplateStringsArray | string, ...values: unknown[]): Promise<string> {
|
||||
const cmd =
|
||||
typeof strings === 'string' ? strings : strings.flatMap((str, i) => str + `${values[i] ?? ''}`).join('');
|
||||
const stringsIsString = typeof strings === 'string';
|
||||
const cmd = stringsIsString ? strings : strings.flatMap((str, i) => str + `${values[i] ?? ''}`).join('');
|
||||
|
||||
return execAsync(['bash', '-c', cmd]).catch((err) => {
|
||||
console.error(cmd, err);
|
||||
@@ -167,7 +135,7 @@ export async function sh(cmd: string | string[]): Promise<string> {
|
||||
* @returns An array of JSX elements, one for each monitor.
|
||||
*/
|
||||
export async function forMonitors(widget: (monitor: number) => Promise<JSX.Element>): Promise<JSX.Element[]> {
|
||||
const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
|
||||
const n = Gdk.Display.get_default()?.get_n_monitors() ?? 1;
|
||||
|
||||
return Promise.all(range(n, 0).map(widget));
|
||||
}
|
||||
@@ -219,24 +187,6 @@ export function dependencies(...bins: string[]): boolean {
|
||||
return missing.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches an application in a detached process.
|
||||
*
|
||||
* This function runs the specified application executable in the background using a bash command.
|
||||
* It also increments the application's frequency counter.
|
||||
*
|
||||
* @param app The application to launch.
|
||||
*/
|
||||
export function launchApp(app: AstalApps.Application): void {
|
||||
const exe = app.executable
|
||||
.split(/\s+/)
|
||||
.filter((str) => !str.startsWith('%') && !str.startsWith('@'))
|
||||
.join(' ');
|
||||
|
||||
bash(`${exe} &`);
|
||||
app.frequency += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided filepath is a valid image.
|
||||
*
|
||||
@@ -294,13 +244,13 @@ export function Notify(notifPayload: NotificationArgs): void {
|
||||
|
||||
let command = 'notify-send';
|
||||
command += ` "${notifPayload.summary} "`;
|
||||
if (notifPayload.body) command += ` "${notifPayload.body}" `;
|
||||
if (notifPayload.appName) command += ` -a "${notifPayload.appName}"`;
|
||||
if (notifPayload.iconName) command += ` -i "${notifPayload.iconName}"`;
|
||||
if (notifPayload.urgency) command += ` -u "${notifPayload.urgency}"`;
|
||||
if (notifPayload.body !== undefined) command += ` "${notifPayload.body}" `;
|
||||
if (notifPayload.appName !== undefined) command += ` -a "${notifPayload.appName}"`;
|
||||
if (notifPayload.iconName !== undefined) command += ` -i "${notifPayload.iconName}"`;
|
||||
if (notifPayload.urgency !== undefined) command += ` -u "${notifPayload.urgency}"`;
|
||||
if (notifPayload.timeout !== undefined) command += ` -t ${notifPayload.timeout}`;
|
||||
if (notifPayload.category) command += ` -c "${notifPayload.category}"`;
|
||||
if (notifPayload.transient) command += ` -e`;
|
||||
if (notifPayload.category !== undefined) command += ` -c "${notifPayload.category}"`;
|
||||
if (notifPayload.transient !== undefined) command += ' -e';
|
||||
if (notifPayload.id !== undefined) command += ` -r ${notifPayload.id}`;
|
||||
|
||||
execAsync(command)
|
||||
@@ -331,7 +281,7 @@ export function getPosition(pos: NotificationAnchor | OSDAnchor): Astal.WindowAn
|
||||
left: Astal.WindowAnchor.LEFT,
|
||||
};
|
||||
|
||||
return positionMap[pos] || Astal.WindowAnchor.TOP;
|
||||
return positionMap[pos] ?? Astal.WindowAnchor.TOP;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user