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:
Jas Singh
2025-05-11 23:01:55 -07:00
committed by GitHub
parent 0c82ce9704
commit 2bb1449fb6
275 changed files with 4363 additions and 2505 deletions

View File

@@ -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));
}
});
}

View File

@@ -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',
});

View File

@@ -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.*)$');
});
};

View File

@@ -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,
);

View File

@@ -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,

View File

@@ -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;
}
}
}
},
});
}

View 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
View 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;
}
}

View 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
View 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 };

View File

@@ -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 };

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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;
};

View File

@@ -0,0 +1,8 @@
export type MediaTags = {
title: string;
artists: string;
artist: string;
album: string;
name: string;
identity: string;
};

View File

@@ -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>;
};

View File

@@ -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];

View File

@@ -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>;

View File

@@ -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;
};

View File

@@ -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'];

View File

@@ -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` };
};

View 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` };
};

View File

@@ -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;
}

View File

@@ -1,4 +0,0 @@
export type MousePos = {
source: string;
pos: number[];
};

View File

@@ -1,3 +1,5 @@
import { Process } from 'astal';
export type GPUStatProcess = {
username: string;
command: string;

View File

@@ -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;

View File

@@ -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 = '󰤩' | '󰤨' | '󰤪' | '󰤨' | '󰤩' | '󰤮' | '󰤨' | '󰤥' | '󰤢' | '󰤟' | '󰤯';

View File

@@ -6,7 +6,7 @@ export interface NotificationArgs {
iconName?: string;
id?: number;
summary?: string;
urgency?: Urgency;
urgency?: string;
category?: string;
timeout?: number;
transient?: boolean;

View File

@@ -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;
};

View 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>;
};

View File

@@ -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';

View File

@@ -0,0 +1 @@
export type ProfileType = 'balanced' | 'power-saver' | 'performance';

View File

@@ -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,

View File

@@ -1 +0,0 @@
export type Bind = OriginalBinding<GObject.Object, keyof Props<GObject.Object>, unknown>;

View File

@@ -1,3 +0,0 @@
export type VolumeIcons = {
[index: number]: string;
};

View File

@@ -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>;
};
}

View File

@@ -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;
}
/**