import { ResourceLabelType } from 'lib/types/bar'; import { GenericResourceData, Postfix } from 'lib/types/customModules/generic'; import { InputHandlerEvents, RunAsyncCommand } from 'lib/types/customModules/utils'; import { ThrottleFn, ThrottleFnCallback } from 'lib/types/utils'; import { Attribute, Child, EventArgs } from 'lib/types/widget'; import { Binding } from 'lib/utils'; import { openMenu } from 'modules/bar/utils'; import options from 'options'; import Gdk from 'types/@girs/gdk-3.0/gdk-3.0'; import { Variable as VariableType } from 'types/variable'; import Button from 'types/widgets/button'; const { scrollSpeed } = options.bar.customModules; const handlePostInputUpdater = (postInputUpdater?: VariableType): void => { if (postInputUpdater !== undefined) { postInputUpdater.value = !postInputUpdater.value; } }; export const runAsyncCommand: RunAsyncCommand = (cmd, events, fn, postInputUpdater?: VariableType): void => { if (cmd.startsWith('menu:')) { const menuName = cmd.split(':')[1].trim().toLowerCase(); openMenu(events.clicked, events.event, `${menuName}menu`); return; } Utils.execAsync(`bash -c "${cmd}"`) .then((output) => { handlePostInputUpdater(postInputUpdater); if (fn !== undefined) { fn(output); } }) .catch((err) => console.error(`Error running command "${cmd}": ${err})`)); }; export function throttle(func: T, limit: number): T { let inThrottle: boolean; return function (this: ThisParameterType, ...args: Parameters) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => { inThrottle = false; }, limit); } } as T; } export const throttledScrollHandler = (interval: number): ThrottleFn => throttle((cmd: string, events: EventArgs, fn: ThrottleFnCallback, postInputUpdater?: VariableType) => { runAsyncCommand(cmd, events, fn, postInputUpdater); }, 200 / interval); const dummyVar = Variable(''); export const inputHandler = ( self: Button, { onPrimaryClick, onSecondaryClick, onMiddleClick, onScrollUp, onScrollDown }: InputHandlerEvents, postInputUpdater?: VariableType, ): void => { const sanitizeInput = (input: VariableType): string => { if (input === undefined) { return ''; } return input.value; }; const updateHandlers = (): void => { const interval = scrollSpeed.value; const throttledHandler = throttledScrollHandler(interval); self.on_primary_click = (clicked: Button, event: Gdk.Event): void => { runAsyncCommand( sanitizeInput(onPrimaryClick?.cmd || dummyVar), { clicked, event }, onPrimaryClick.fn, postInputUpdater, ); }; self.on_secondary_click = (clicked: Button, event: Gdk.Event): void => { runAsyncCommand( sanitizeInput(onSecondaryClick?.cmd || dummyVar), { clicked, event }, onSecondaryClick.fn, postInputUpdater, ); }; self.on_middle_click = (clicked: Button, event: Gdk.Event): void => { runAsyncCommand( sanitizeInput(onMiddleClick?.cmd || dummyVar), { clicked, event }, onMiddleClick.fn, postInputUpdater, ); }; self.on_scroll_up = (clicked: Button, event: Gdk.Event): void => { throttledHandler( sanitizeInput(onScrollUp?.cmd || dummyVar), { clicked, event }, onScrollUp.fn, postInputUpdater, ); }; self.on_scroll_down = (clicked: Button, event: Gdk.Event): void => { throttledHandler( sanitizeInput(onScrollDown?.cmd || dummyVar), { clicked, event }, onScrollDown.fn, postInputUpdater, ); }; }; // Initial setup of event handlers updateHandlers(); const sanitizeVariable = (someVar: VariableType | undefined): Binding => { if (someVar === undefined || typeof someVar.bind !== 'function') { return dummyVar.bind('value'); } return someVar.bind('value'); }; // Re-run the update whenever scrollSpeed changes Utils.merge( [ scrollSpeed.bind('value'), sanitizeVariable(onPrimaryClick), sanitizeVariable(onSecondaryClick), sanitizeVariable(onMiddleClick), sanitizeVariable(onScrollUp), sanitizeVariable(onScrollDown), ], updateHandlers, ); }; export const divide = ([total, used]: number[], round: boolean): number => { const percentageTotal = (used / total) * 100; if (round) { return total > 0 ? Math.round(percentageTotal) : 0; } return total > 0 ? parseFloat(percentageTotal.toFixed(2)) : 0; }; export const formatSizeInKiB = (sizeInBytes: number, round: boolean): number => { const sizeInGiB = sizeInBytes / 1024 ** 1; return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2)); }; export const formatSizeInMiB = (sizeInBytes: number, round: boolean): number => { const sizeInGiB = sizeInBytes / 1024 ** 2; return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2)); }; export const formatSizeInGiB = (sizeInBytes: number, round: boolean): number => { const sizeInGiB = sizeInBytes / 1024 ** 3; return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2)); }; export const formatSizeInTiB = (sizeInBytes: number, round: boolean): number => { const sizeInGiB = sizeInBytes / 1024 ** 4; return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2)); }; export const autoFormatSize = (sizeInBytes: number, round: boolean): number => { // auto convert to GiB, MiB, KiB, TiB, or bytes if (sizeInBytes >= 1024 ** 4) return formatSizeInTiB(sizeInBytes, round); if (sizeInBytes >= 1024 ** 3) return formatSizeInGiB(sizeInBytes, round); if (sizeInBytes >= 1024 ** 2) return formatSizeInMiB(sizeInBytes, round); if (sizeInBytes >= 1024 ** 1) return formatSizeInKiB(sizeInBytes, round); return sizeInBytes; }; export const getPostfix = (sizeInBytes: number): Postfix => { if (sizeInBytes >= 1024 ** 4) return 'TiB'; if (sizeInBytes >= 1024 ** 3) return 'GiB'; if (sizeInBytes >= 1024 ** 2) return 'MiB'; if (sizeInBytes >= 1024 ** 1) return 'KiB'; return 'B'; }; export const renderResourceLabel = (lblType: ResourceLabelType, rmUsg: GenericResourceData, round: boolean): string => { const { used, total, percentage, free } = rmUsg; const formatFunctions = { TiB: formatSizeInTiB, GiB: formatSizeInGiB, MiB: formatSizeInMiB, KiB: formatSizeInKiB, B: (size: number): number => size, }; // Get the data in proper GiB, MiB, KiB, TiB, or bytes const totalSizeFormatted = autoFormatSize(total, round); // get the postfix: one of [TiB, GiB, MiB, KiB, B] const postfix = getPostfix(total); // Determine which format function to use const formatUsed = formatFunctions[postfix] || formatFunctions['B']; const usedSizeFormatted = formatUsed(used, round); if (lblType === 'used/total') { return `${usedSizeFormatted}/${totalSizeFormatted} ${postfix}`; } if (lblType === 'used') { return `${autoFormatSize(used, round)} ${getPostfix(used)}`; } if (lblType === 'free') { return `${autoFormatSize(free, round)} ${getPostfix(free)}`; } return `${percentage}%`; }; export const formatTooltip = (dataType: string, lblTyp: ResourceLabelType): string => { switch (lblTyp) { case 'used': return `Used ${dataType}`; case 'free': return `Free ${dataType}`; case 'used/total': return `Used/Total ${dataType}`; case 'percentage': return `Percentage ${dataType} Usage`; default: return ''; } };