From a4f5fb5917d49a14372bca6fd953afb92298c9ad Mon Sep 17 00:00:00 2001 From: Jas Singh Date: Sat, 23 Nov 2024 03:55:00 -0800 Subject: [PATCH] Refactor Polling Mechanism: Implement Class-Based Poller with Start/Stop Control (#528) * custom module updates to class based. * Finish poller logic. * Use composition for pollers * Rename poller * Handle recorder polling. * Fix quotes in bash command * Remove logs --- customModules/PollVar.ts | 77 --------------- customModules/cpu/index.ts | 11 ++- customModules/cputemp/helpers.ts | 8 +- customModules/cputemp/index.ts | 8 +- customModules/hypridle/helpers.ts | 2 +- customModules/hypridle/index.ts | 11 ++- customModules/hyprsunset/helpers.ts | 2 +- customModules/hyprsunset/index.ts | 6 +- customModules/netstat/index.ts | 12 ++- customModules/ram/index.ts | 13 ++- customModules/storage/index.ts | 14 ++- customModules/updates/index.ts | 10 +- globals.d.ts | 1 - globals/utilities.ts | 2 - lib/poller/BashPoller.ts | 90 +++++++++++++++++ lib/poller/FunctionPoller.ts | 86 +++++++++++++++++ lib/poller/Poller.ts | 107 +++++++++++++++++++++ lib/types/customModules/generic.d.ts | 2 +- lib/types/options.d.ts | 32 +++++- lib/utils.ts | 25 ++++- modules/bar/Bar.ts | 39 +------- modules/menus/dashboard/shortcuts/index.ts | 33 ++++--- options.ts | 4 +- services/Cpu.ts | 12 ++- services/Ram.ts | 11 ++- services/Storage.ts | 11 ++- 26 files changed, 460 insertions(+), 169 deletions(-) delete mode 100644 customModules/PollVar.ts create mode 100644 lib/poller/BashPoller.ts create mode 100644 lib/poller/FunctionPoller.ts create mode 100644 lib/poller/Poller.ts diff --git a/customModules/PollVar.ts b/customModules/PollVar.ts deleted file mode 100644 index 2381e89..0000000 --- a/customModules/PollVar.ts +++ /dev/null @@ -1,77 +0,0 @@ -import GLib from 'gi://GLib?version=2.0'; -import { GenericFunction } from 'lib/types/customModules/generic'; -import { Bind } from 'lib/types/variable'; -import { Variable as VariableType } from 'types/variable'; - -/** - * @param {VariableType} targetVariable - The Variable to update with the function's result. - * @param {Array} trackers - Array of trackers to watch. - * @param {Bind} pollingInterval - The polling interval in milliseconds. - * @param {GenericFunction} someFunc - The function to execute at each interval, which updates the Variable. - * @param {...P} params - Parameters to pass to someFunc. - */ -export const pollVariable = >( - targetVariable: VariableType, - trackers: Array, - pollingInterval: Bind, - someFunc: F, - ...params: P -): void => { - let intervalInstance: number | null = null; - - const intervalFn = (pollIntrvl: number): void => { - if (intervalInstance !== null) { - GLib.source_remove(intervalInstance); - } - - intervalInstance = Utils.interval(pollIntrvl, () => { - targetVariable.value = someFunc(...params); - }); - }; - - Utils.merge([pollingInterval, ...trackers], (pollIntrvl: number) => { - intervalFn(pollIntrvl); - }); -}; - -/** - * @param {VariableType} targetVariable - The Variable to update with the result of the command. - * @param {Array} trackers - Array of trackers to watch. - * @param {Bind} pollingInterval - The polling interval in milliseconds. - * @param {string} someCommand - The bash command to execute. - * @param {GenericFunction} someFunc - The function to execute after processing the command result; - * with the first argument being the result of the command execution. - * @param {...P} params - Additional parameters to pass to someFunc. - */ -export const pollVariableBash = >( - targetVariable: VariableType, - trackers: Array, - pollingInterval: Bind, - someCommand: string, - someFunc: F, - ...params: P -): void => { - let intervalInstance: number | null = null; - - const intervalFn = (pollIntrvl: number): void => { - if (intervalInstance !== null) { - GLib.source_remove(intervalInstance); - } - - intervalInstance = Utils.interval(pollIntrvl, () => { - Utils.execAsync(`bash -c "${someCommand}"`) - .then((res: string) => { - try { - targetVariable.value = someFunc(res, ...params); - } catch (error) { - console.warn(`An error occurred when running interval bash function: ${error}`); - } - }) - .catch((err) => console.error(`Error running command "${someCommand}": ${err}`)); - }); - }; - - Utils.merge([pollingInterval, ...trackers], (pollIntrvl: number) => { - intervalFn(pollIntrvl); - }); -}; diff --git a/customModules/cpu/index.ts b/customModules/cpu/index.ts index a3848e4..c5e5c19 100644 --- a/customModules/cpu/index.ts +++ b/customModules/cpu/index.ts @@ -1,16 +1,14 @@ -import options from 'options'; - -// Module initializer import { module } from '../module'; +import options from 'options'; import Button from 'types/widgets/button'; // Utility Methods import { inputHandler } from 'customModules/utils'; import { computeCPU } from './computeCPU'; -import { pollVariable } from 'customModules/PollVar'; import { BarBoxChild } from 'lib/types/bar'; import { Attribute, Child } from 'lib/types/widget'; +import { FunctionPoller } from 'lib/poller/FunctionPoller'; // All the user configurable options for the cpu module that are needed const { label, round, leftClick, rightClick, middleClick, scrollUp, scrollDown, pollingInterval, icon } = @@ -18,7 +16,8 @@ const { label, round, leftClick, rightClick, middleClick, scrollUp, scrollDown, export const cpuUsage = Variable(0); -pollVariable( +// Instantiate the Poller class for CPU usage polling +const cpuPoller = new FunctionPoller( // Variable to poll and update with the result of the function passed in cpuUsage, // Variables that should trigger the polling function to update when they change @@ -29,6 +28,8 @@ pollVariable( computeCPU, ); +cpuPoller.initialize('cpu'); + export const Cpu = (): BarBoxChild => { const renderLabel = (cpuUsg: number, rnd: boolean): string => { return rnd ? `${Math.round(cpuUsg)}%` : `${cpuUsg.toFixed(2)}%`; diff --git a/customModules/cputemp/helpers.ts b/customModules/cputemp/helpers.ts index 1663698..1d451ce 100644 --- a/customModules/cputemp/helpers.ts +++ b/customModules/cputemp/helpers.ts @@ -2,14 +2,14 @@ import GLib from 'gi://GLib?version=2.0'; import { convertCelsiusToFahrenheit } from 'globals/weather'; import { UnitType } from 'lib/types/weather'; import options from 'options'; -import { Variable } from 'types/variable'; +import { Variable as VariableType } from 'types/variable'; const { sensor } = options.bar.customModules.cpuTemp; /** * Retrieves the current CPU temperature. * @returns CPU temperature in degrees Celsius */ -export const getCPUTemperature = (round: Variable, unit: Variable): number => { +export const getCPUTemperature = (round: VariableType, unit: VariableType): number => { try { if (sensor.value.length === 0) { return 0; @@ -23,13 +23,13 @@ export const getCPUTemperature = (round: Variable, unit: Variable, VariableType]>( // Variable to poll and update with the result of the function passed in cpuTemp, // Variables that should trigger the polling function to update when they change @@ -43,6 +45,8 @@ pollVariable( unit, ); +cpuTempPoller.initialize('cputemp'); + export const CpuTemp = (): BarBoxChild => { const cpuTempModule = module({ textIcon: icon.bind('value'), diff --git a/customModules/hypridle/helpers.ts b/customModules/hypridle/helpers.ts index bfa223a..d147dd4 100644 --- a/customModules/hypridle/helpers.ts +++ b/customModules/hypridle/helpers.ts @@ -1,6 +1,6 @@ import { Variable as TVariable } from 'types/variable'; -export const isActiveCommand = `bash -c "pgrep -x "hypridle" > /dev/null && echo "yes" || echo "no""`; +export const isActiveCommand = `bash -c "pgrep -x 'hypridle' &>/dev/null && echo 'yes' || echo 'no'"`; export const isActive = Variable(false); diff --git a/customModules/hypridle/index.ts b/customModules/hypridle/index.ts index 8895531..38bf7ce 100644 --- a/customModules/hypridle/index.ts +++ b/customModules/hypridle/index.ts @@ -5,8 +5,8 @@ import { inputHandler, throttleInput } from 'customModules/utils'; import Button from 'types/widgets/button'; import { Attribute, Child } from 'lib/types/widget'; import { BarBoxChild } from 'lib/types/bar'; -import { pollVariable } from 'customModules/PollVar'; import { checkIdleStatus, isActive, toggleIdle } from './helpers'; +import { FunctionPoller } from 'lib/poller/FunctionPoller'; const { label, pollingInterval, onIcon, offIcon, onLabel, offLabel, rightClick, middleClick, scrollUp, scrollDown } = options.bar.customModules.hypridle; @@ -15,7 +15,14 @@ const dummyVar = Variable(undefined); checkIdleStatus(); -pollVariable(dummyVar, [], pollingInterval.bind('value'), checkIdleStatus); +const idleStatusPoller = new FunctionPoller( + dummyVar, + [], + pollingInterval.bind('value'), + checkIdleStatus, +); + +idleStatusPoller.initialize('hypridle'); const throttledToggleIdle = throttleInput(() => toggleIdle(isActive), 1000); diff --git a/customModules/hyprsunset/helpers.ts b/customModules/hyprsunset/helpers.ts index 4b2bc42..2a62586 100644 --- a/customModules/hyprsunset/helpers.ts +++ b/customModules/hyprsunset/helpers.ts @@ -4,7 +4,7 @@ import { Variable as TVariable } from 'types/variable'; const { temperature } = options.bar.customModules.hyprsunset; -export const isActiveCommand = `bash -c "pgrep -x "hyprsunset" > /dev/null && echo "yes" || echo "no""`; +export const isActiveCommand = `bash -c "pgrep -x 'hyprsunset' > /dev/null && echo 'yes' || echo 'no'"`; export const isActive = Variable(false); diff --git a/customModules/hyprsunset/index.ts b/customModules/hyprsunset/index.ts index 799d38d..30e3742 100644 --- a/customModules/hyprsunset/index.ts +++ b/customModules/hyprsunset/index.ts @@ -5,8 +5,8 @@ import { inputHandler, throttleInput } from 'customModules/utils'; import Button from 'types/widgets/button'; import { Attribute, Child } from 'lib/types/widget'; import { BarBoxChild } from 'lib/types/bar'; -import { pollVariable } from 'customModules/PollVar'; import { checkSunsetStatus, isActive, toggleSunset } from './helpers'; +import { FunctionPoller } from 'lib/poller/FunctionPoller'; const { label, @@ -26,7 +26,9 @@ const dummyVar = Variable(undefined); checkSunsetStatus(); -pollVariable(dummyVar, [], pollingInterval.bind('value'), checkSunsetStatus); +const sunsetPoller = new FunctionPoller(dummyVar, [], pollingInterval.bind('value'), checkSunsetStatus); + +sunsetPoller.initialize('hyprsunset'); const throttledToggleSunset = throttleInput(() => toggleSunset(isActive), 1000); diff --git a/customModules/netstat/index.ts b/customModules/netstat/index.ts index 1672db8..120662e 100644 --- a/customModules/netstat/index.ts +++ b/customModules/netstat/index.ts @@ -3,13 +3,14 @@ import options from 'options'; import { module } from '../module'; import { inputHandler } from 'customModules/utils'; import { computeNetwork } from './computeNetwork'; -import { BarBoxChild, NetstatLabelType } from 'lib/types/bar'; +import { BarBoxChild, NetstatLabelType, RateUnit } from 'lib/types/bar'; import Button from 'types/widgets/button'; import { NetworkResourceData } from 'lib/types/customModules/network'; import { NETWORK_LABEL_TYPES } from 'lib/types/defaults/bar'; import { GET_DEFAULT_NETSTAT_DATA } from 'lib/types/defaults/netstat'; -import { pollVariable } from 'customModules/PollVar'; import { Attribute, Child } from 'lib/types/widget'; +import { FunctionPoller } from 'lib/poller/FunctionPoller'; +import { Variable as TVariable } from 'types/variable'; const { label, @@ -27,7 +28,10 @@ const { export const networkUsage = Variable(GET_DEFAULT_NETSTAT_DATA(rateUnit.value)); -pollVariable( +const netstatPoller = new FunctionPoller< + NetworkResourceData, + [round: TVariable, interfaceNameVar: TVariable, dataType: TVariable] +>( // Variable to poll and update with the result of the function passed in networkUsage, // Variables that should trigger the polling function to update when they change @@ -48,6 +52,8 @@ pollVariable( rateUnit, ); +netstatPoller.initialize('netstat'); + export const Netstat = (): BarBoxChild => { const renderNetworkLabel = (lblType: NetstatLabelType, network: NetworkResourceData): string => { switch (lblType) { diff --git a/customModules/ram/index.ts b/customModules/ram/index.ts index b651aaa..2b380f6 100644 --- a/customModules/ram/index.ts +++ b/customModules/ram/index.ts @@ -16,8 +16,9 @@ import { BarBoxChild, ResourceLabelType } from 'lib/types/bar'; // Global Constants import { LABEL_TYPES } from 'lib/types/defaults/bar'; -import { pollVariable } from 'customModules/PollVar'; import { Attribute, Child } from 'lib/types/widget'; +import { FunctionPoller } from 'lib/poller/FunctionPoller'; +import { Variable as TVariable } from 'types/variable'; // All the user configurable options for the ram module that are needed const { label, labelType, round, leftClick, rightClick, middleClick, pollingInterval, icon } = @@ -26,7 +27,15 @@ const { label, labelType, round, leftClick, rightClick, middleClick, pollingInte const defaultRamData: GenericResourceData = { total: 0, used: 0, percentage: 0, free: 0 }; const ramUsage = Variable(defaultRamData); -pollVariable(ramUsage, [round.bind('value')], pollingInterval.bind('value'), calculateRamUsage, round); +const ramPoller = new FunctionPoller]>( + ramUsage, + [round.bind('value')], + pollingInterval.bind('value'), + calculateRamUsage, + round, +); + +ramPoller.initialize('ram'); export const Ram = (): BarBoxChild => { const ramModule = module({ diff --git a/customModules/storage/index.ts b/customModules/storage/index.ts index 9cfeb85..af91233 100644 --- a/customModules/storage/index.ts +++ b/customModules/storage/index.ts @@ -1,14 +1,14 @@ import options from 'options'; import { module } from '../module'; - import { formatTooltip, inputHandler, renderResourceLabel } from 'customModules/utils'; import { computeStorage } from './computeStorage'; import { BarBoxChild, ResourceLabelType } from 'lib/types/bar'; import { GenericResourceData } from 'lib/types/customModules/generic'; import Button from 'types/widgets/button'; import { LABEL_TYPES } from 'lib/types/defaults/bar'; -import { pollVariable } from 'customModules/PollVar'; import { Attribute, Child } from 'lib/types/widget'; +import { FunctionPoller } from 'lib/poller/FunctionPoller'; +import { Variable as TVariable } from 'types/variable'; const { label, labelType, icon, round, leftClick, rightClick, middleClick, pollingInterval } = options.bar.customModules.storage; @@ -17,7 +17,15 @@ const defaultStorageData = { total: 0, used: 0, percentage: 0, free: 0 }; const storageUsage = Variable(defaultStorageData); -pollVariable(storageUsage, [round.bind('value')], pollingInterval.bind('value'), computeStorage, round); +const storagePoller = new FunctionPoller]>( + storageUsage, + [round.bind('value')], + pollingInterval.bind('value'), + computeStorage, + round, +); + +storagePoller.initialize('storage'); export const Storage = (): BarBoxChild => { const storageModule = module({ diff --git a/customModules/updates/index.ts b/customModules/updates/index.ts index ea65183..5180dc7 100644 --- a/customModules/updates/index.ts +++ b/customModules/updates/index.ts @@ -3,10 +3,10 @@ import { module } from '../module'; import { inputHandler } from 'customModules/utils'; import Button from 'types/widgets/button'; -import { Variable as VariableType } from 'types/variable'; -import { pollVariableBash } from 'customModules/PollVar'; +import { Variable as TVariable } from 'types/variable'; import { Attribute, Child } from 'lib/types/widget'; import { BarBoxChild } from 'lib/types/bar'; +import { BashPoller } from 'lib/poller/BashPoller'; const { updateCommand, @@ -21,7 +21,7 @@ const { scrollDown, } = options.bar.customModules.updates; -const pendingUpdates: VariableType = Variable('0'); +const pendingUpdates: TVariable = Variable('0'); const postInputUpdater = Variable(true); const processUpdateCount = (updateCount: string): string => { @@ -29,7 +29,7 @@ const processUpdateCount = (updateCount: string): string => { return `${updateCount.padStart(2, '0')}`; }; -pollVariableBash( +const updatesPoller = new BashPoller( pendingUpdates, [padZero.bind('value'), postInputUpdater.bind('value')], pollingInterval.bind('value'), @@ -37,6 +37,8 @@ pollVariableBash( processUpdateCount, ); +updatesPoller.initialize('updates'); + export const Updates = (): BarBoxChild => { const updatesModule = module({ textIcon: icon.bind('value'), diff --git a/globals.d.ts b/globals.d.ts index 5abad72..17f1e70 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -1,4 +1,3 @@ -// globals.d.ts /* eslint-disable no-var */ import { Options, Variable as VariableType } from 'types/variable'; diff --git a/globals/utilities.ts b/globals/utilities.ts index 5b03e3d..cf767e1 100644 --- a/globals/utilities.ts +++ b/globals/utilities.ts @@ -10,8 +10,6 @@ globalThis.isWindowVisible = (windowName: string): boolean => { }; globalThis.setLayout = (layout: string): string => { - console.log(layout); - try { const layoutJson = JSON.parse(layout); const { layouts } = options.bar; diff --git a/lib/poller/BashPoller.ts b/lib/poller/BashPoller.ts new file mode 100644 index 0000000..c928ba7 --- /dev/null +++ b/lib/poller/BashPoller.ts @@ -0,0 +1,90 @@ +import { Variable as VariableType } from 'types/variable'; +import { Bind } from 'lib/types/variable'; +import { GenericFunction } from 'lib/types/customModules/generic'; +import { BarModule } from 'lib/types/options'; +import { Poller } from './Poller'; + +/** + * A class that manages polling of a variable by executing a bash command at specified intervals. + */ +export class BashPoller { + private poller: Poller; + + 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 params - Additional parameters for the polling function. + * + * @example + * + * ```ts + * //##################### EXAMPLE ########################## + * const updatesPoller = new BashPoller( + * pendingUpdates, + * [padZero.bind('value'), postInputUpdater.bind('value')], + * pollingInterval.bind('value'), + * updateCommand.value, + * processUpdateCount, + * ); + * //####################################################### + * + * ``` + */ + constructor( + private targetVariable: VariableType, + private trackers: Bind[], + private pollingInterval: Bind, + private updateCommand: string, + private pollingFunction: GenericFunction, + ...params: Parameters + ) { + this.params = params; + + this.poller = new Poller(this.pollingInterval, this.trackers, this.execute); + } + + /** + * Executes the bash command specified in the updateCommand property. + * + * The result of the command is processed by the pollingFunction and + * assigned to the targetVariable. + */ + public execute = async (): Promise => { + try { + const res = await Utils.execAsync(`bash -c "${this.updateCommand}"`); + this.targetVariable.value = await this.pollingFunction(res, ...this.params); + } catch (error) { + console.error(`Error executing bash command "${this.updateCommand}":`, error); + } + }; + + /** + * Starts the polling process. + */ + public start(): void { + this.poller.start(); + } + + /** + * Stops the polling process. + */ + public stop(): void { + this.poller.stop(); + } + + /** + * Initializes the poller with the specified module. + * + * @param moduleName - The name of the module to initialize. + */ + public initialize(moduleName?: BarModule): void { + this.poller.initialize(moduleName); + } +} diff --git a/lib/poller/FunctionPoller.ts b/lib/poller/FunctionPoller.ts new file mode 100644 index 0000000..eb684d1 --- /dev/null +++ b/lib/poller/FunctionPoller.ts @@ -0,0 +1,86 @@ +import { Variable as VariableType } from 'types/variable'; +import { Bind } from 'lib/types/variable'; +import { GenericFunction } from 'lib/types/customModules/generic'; +import { BarModule } from 'lib/types/options'; +import { Poller } from './Poller'; + +/** + * A class that manages polling of a variable by executing a generic function at specified intervals. + */ +export class FunctionPoller { + private poller: Poller; + + 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 params - Additional parameters for the polling function. + * + * @example + * + * ```ts + * //##################### EXAMPLE ########################## + * const cpuPoller = new FunctionPoller( + * cpuUsage, + * [round.bind('value')], + * pollingInterval.bind('value'), + * computeCPU, + * ); + * //####################################################### + * + * ``` + */ + constructor( + private targetVariable: VariableType, + private trackers: Bind[], + private pollingInterval: Bind, + private pollingFunction: GenericFunction, + ...params: Parameters + ) { + this.params = params; + + this.poller = new Poller(this.pollingInterval, this.trackers, this.execute); + } + + /** + * Executes the polling function with the provided parameters. + * + * The result of the function is assigned to the target variable. + */ + private execute = async (): Promise => { + try { + const result = await this.pollingFunction(...this.params); + this.targetVariable.value = result; + } catch (error) { + console.error('Error executing polling function:', error); + } + }; + + /** + * Starts the polling process. + */ + public start(): void { + this.poller.start(); + } + + /** + * Stops the polling process. + */ + public stop(): void { + this.poller.stop(); + } + + /** + * Initializes the poller with the specified module. + * + * @param moduleName - The name of the module to initialize. + */ + public initialize(moduleName?: BarModule): void { + this.poller.initialize(moduleName); + } +} diff --git a/lib/poller/Poller.ts b/lib/poller/Poller.ts new file mode 100644 index 0000000..e28f5e2 --- /dev/null +++ b/lib/poller/Poller.ts @@ -0,0 +1,107 @@ +import GLib from 'gi://GLib?version=2.0'; +import { Bind } from 'lib/types/variable'; +import { BarModule } from 'lib/types/options'; +import { getLayoutItems } from 'lib/utils'; + +const { layouts } = options.bar; + +/** + * A class that manages the polling lifecycle, including interval management and execution state. + */ +export class Poller { + private intervalInstance: number | null = null; + private isExecuting: boolean = false; + private pollingFunction: () => Promise; + + /** + * Creates an instance of Poller. + * @param pollingInterval - The interval at which polling occurs. + * @param trackers - An array of trackers to monitor. + * @param pollingFunction - The function to execute during each poll. + */ + constructor( + private pollingInterval: Bind, + private trackers: Bind[], + pollingFunction: () => Promise, + ) { + this.pollingFunction = pollingFunction; + } + + /** + * Starts the polling process by setting up the interval. + */ + public start(): void { + Utils.merge([this.pollingInterval, ...this.trackers], (intervalMs: number) => { + this.executePolling(intervalMs); + }); + } + + /** + * Stops the polling process and cleans up resources. + */ + public stop(): void { + if (this.intervalInstance !== null) { + GLib.source_remove(this.intervalInstance); + this.intervalInstance = null; + } + } + + /** + * Initializes the polling based on module usage. + * + * If not module is provided then we can safely assume that we want + * to always run the pollig interval. + * + * @param moduleName - The name of the module to initialize. + */ + public initialize(moduleName?: BarModule): void { + if (moduleName === undefined) { + return this.start(); + } + + const initialModules = getLayoutItems(); + + if (initialModules.includes(moduleName)) { + this.start(); + } else { + this.stop(); + } + + layouts.connect('changed', () => { + const usedModules = getLayoutItems(); + + if (usedModules.includes(moduleName)) { + this.start(); + } else { + this.stop(); + } + }); + } + + /** + * Executes the polling function at the specified interval. + * + * @param intervalMs - The polling interval in milliseconds. + */ + private executePolling(intervalMs: number): void { + if (this.intervalInstance !== null) { + GLib.source_remove(this.intervalInstance); + } + + this.intervalInstance = Utils.interval(intervalMs, async () => { + if (this.isExecuting) { + return; + } + + this.isExecuting = true; + + try { + await this.pollingFunction(); + } catch (error) { + console.error('Error during polling execution:', error); + } finally { + this.isExecuting = false; + } + }); + } +} diff --git a/lib/types/customModules/generic.d.ts b/lib/types/customModules/generic.d.ts index 22610e2..385a0fb 100644 --- a/lib/types/customModules/generic.d.ts +++ b/lib/types/customModules/generic.d.ts @@ -1,4 +1,4 @@ -export type GenericFunction = (...args: P) => T; +export type GenericFunction = (...args: Parameters) => Promise | Value; export type GenericResourceMetrics = { total: number; diff --git a/lib/types/options.d.ts b/lib/types/options.d.ts index 131f423..d16d6e3 100644 --- a/lib/types/options.d.ts +++ b/lib/types/options.d.ts @@ -15,8 +15,38 @@ export type RecursiveOptionsObject = { }; export type BarLocation = 'top' | 'bottom'; +export type BarModule = + | 'battery' + | 'dashboard' + | 'workspaces' + | 'windowtitle' + | 'media' + | 'notifications' + | 'volume' + | 'network' + | 'bluetooth' + | 'clock' + | 'ram' + | 'cpu' + | 'cputemp' + | 'storage' + | 'netstat' + | 'kbinput' + | 'updates' + | 'submap' + | 'weather' + | 'power' + | 'systray' + | 'hypridle' + | 'hyprsunset'; + export type BarLayout = { - [key: string]: Layout; + left: BarModule[]; + middle: BarModule[]; + right: BarModule[]; +}; +export type BarLayouts = { + [key: string]: BarLayout; }; export type Unit = 'imperial' | 'metric'; diff --git a/lib/utils.ts b/lib/utils.ts index cee751d..cdb8128 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { type Application } from 'types/service/applications'; -import { NotificationAnchor } from './types/options'; +import { BarModule, NotificationAnchor } from './types/options'; import { OSDAnchor } from 'lib/types/options'; import icons, { substitutes } from './icons'; import Gtk from 'gi://Gtk?version=3.0'; @@ -18,6 +18,29 @@ import options from 'options'; export type Binding = import('types/service').Binding; +/** + * Retrieves all unique layout items from the bar options. + * + * @returns An array of unique layout items. + */ +export const getLayoutItems = (): BarModule[] => { + const { layouts } = options.bar; + + const itemsInLayout: BarModule[] = []; + + Object.keys(layouts.value).forEach((monitor) => { + const leftItems = layouts.value[monitor].left; + const rightItems = layouts.value[monitor].right; + const middleItems = layouts.value[monitor].middle; + + itemsInLayout.push(...leftItems); + itemsInLayout.push(...middleItems); + itemsInLayout.push(...rightItems); + }); + + return [...new Set(itemsInLayout)]; +}; + /** * @returns substitute icon || name || fallback icon */ diff --git a/modules/bar/Bar.ts b/modules/bar/Bar.ts index adc7eef..d1751f9 100644 --- a/modules/bar/Bar.ts +++ b/modules/bar/Bar.ts @@ -35,7 +35,7 @@ import Button from 'types/widgets/button.js'; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0.js'; import './SideEffects'; -import { BarLayout, WindowLayer } from 'lib/types/options.js'; +import { BarLayout, BarLayouts, WindowLayer } from 'lib/types/options.js'; import { Attribute, Child } from 'lib/types/widget.js'; import Window from 'types/widgets/window.js'; @@ -43,40 +43,7 @@ const { layouts } = options.bar; const { location } = options.theme.bar; const { location: borderLocation } = options.theme.bar.border; -export type BarWidget = keyof typeof widget; - -type Section = - | 'battery' - | 'dashboard' - | 'workspaces' - | 'windowtitle' - | 'media' - | 'notifications' - | 'volume' - | 'network' - | 'bluetooth' - | 'clock' - | 'ram' - | 'cpu' - | 'cputemp' - | 'storage' - | 'netstat' - | 'kbinput' - | 'updates' - | 'submap' - | 'weather' - | 'power' - | 'systray' - | 'hypridle' - | 'hyprsunset'; - -type Layout = { - left: Section[]; - middle: Section[]; - right: Section[]; -}; - -const getLayoutForMonitor = (monitor: number, layouts: BarLayout): Layout => { +const getLayoutForMonitor = (monitor: number, layouts: BarLayouts): BarLayout => { const matchingKey = Object.keys(layouts).find((key) => key === monitor.toString()); const wildcard = Object.keys(layouts).find((key) => key === '*'); @@ -95,7 +62,7 @@ const getLayoutForMonitor = (monitor: number, layouts: BarLayout): Layout => { }; }; -const isLayoutEmpty = (layout: Layout): boolean => { +const isLayoutEmpty = (layout: BarLayout): boolean => { const isLeftSectionEmpty = !Array.isArray(layout.left) || layout.left.length === 0; const isRightSectionEmpty = !Array.isArray(layout.right) || layout.right.length === 0; const isMiddleSectionEmpty = !Array.isArray(layout.middle) || layout.middle.length === 0; diff --git a/modules/menus/dashboard/shortcuts/index.ts b/modules/menus/dashboard/shortcuts/index.ts index 5b4f49c..a0d294f 100644 --- a/modules/menus/dashboard/shortcuts/index.ts +++ b/modules/menus/dashboard/shortcuts/index.ts @@ -1,4 +1,5 @@ const hyprland = await Service.import('hyprland'); +import { BashPoller } from 'lib/poller/BashPoller'; import { Attribute, BoxWidget, Child } from 'lib/types/widget'; import options from 'options'; import { Variable as VarType } from 'types/variable'; @@ -9,18 +10,26 @@ import Label from 'types/widgets/label'; const { left, right } = options.menus.dashboard.shortcuts; const Shortcuts = (): BoxWidget => { - const isRecording = Variable(false, { - poll: [ - 1000, - `${App.configDir}/services/screen_record.sh status`, - (out): boolean => { - if (out === 'recording') { - return true; - } - return false; - }, - ], - }); + const pollingInterval = Variable(1000); + const isRecording = Variable(false); + + const handleRecorder = (commandOutput: string): boolean => { + if (commandOutput === 'recording') { + return true; + } + return false; + }; + + const recordingPoller = new BashPoller( + isRecording, + [], + pollingInterval.bind('value'), + `${App.configDir}/services/screen_record.sh status`, + handleRecorder, + ); + + recordingPoller.initialize(); + const handleClick = (action: string, tOut: number = 250): void => { App.closeWindow('dashboardmenu'); diff --git a/options.ts b/options.ts index c30f889..2021c3b 100644 --- a/options.ts +++ b/options.ts @@ -4,7 +4,7 @@ import { KbLabelType } from 'lib/types/customModules/kbLayout'; import { ActiveWsIndicator, BarButtonStyles, - BarLayout, + BarLayouts, BarLocation, BluetoothBatteryState, BorderLocation, @@ -872,7 +872,7 @@ const options = mkOptions(OPTIONS, { bar: { scrollSpeed: opt(5), - layouts: opt({ + layouts: opt({ '1': { left: ['dashboard', 'workspaces', 'windowtitle'], middle: ['media'], diff --git a/services/Cpu.ts b/services/Cpu.ts index 9a4c555..ea45e83 100644 --- a/services/Cpu.ts +++ b/services/Cpu.ts @@ -2,8 +2,7 @@ // @ts-expect-error: This import is a special directive that tells the compiler to use the GTop library import GTop from 'gi://GTop'; - -import { pollVariable } from 'customModules/PollVar'; +import { FunctionPoller } from 'lib/poller/FunctionPoller'; class Cpu { private updateFrequency = Variable(2000); @@ -15,7 +14,14 @@ class Cpu { GTop.glibtop_get_cpu(this.previousCpuData); this.calculateUsage = this.calculateUsage.bind(this); - pollVariable(this.cpu, [], this.updateFrequency.bind('value'), this.calculateUsage); + const cpuPoller = new FunctionPoller( + this.cpu, + [], + this.updateFrequency.bind('value'), + this.calculateUsage, + ); + + cpuPoller.start(); } public calculateUsage(): number { diff --git a/services/Ram.ts b/services/Ram.ts index 2454c02..338ffe6 100644 --- a/services/Ram.ts +++ b/services/Ram.ts @@ -2,7 +2,7 @@ const GLib = imports.gi.GLib; -import { pollVariable } from 'customModules/PollVar'; +import { FunctionPoller } from 'lib/poller/FunctionPoller'; import { GenericResourceData } from 'lib/types/customModules/generic'; class Ram { @@ -13,7 +13,14 @@ class Ram { constructor() { this.calculateUsage = this.calculateUsage.bind(this); - pollVariable(this.ram, [], this.updateFrequency.bind('value'), this.calculateUsage); + const ramPoller = new FunctionPoller( + this.ram, + [], + this.updateFrequency.bind('value'), + this.calculateUsage, + ); + + ramPoller.start(); } public calculateUsage(): GenericResourceData { diff --git a/services/Storage.ts b/services/Storage.ts index 466d5ec..538261b 100644 --- a/services/Storage.ts +++ b/services/Storage.ts @@ -3,7 +3,7 @@ // @ts-expect-error: This import is a special directive that tells the compiler to use the GTop library import GTop from 'gi://GTop'; -import { pollVariable } from 'customModules/PollVar'; +import { FunctionPoller } from 'lib/poller/FunctionPoller'; import { GenericResourceData } from 'lib/types/customModules/generic'; class Storage { @@ -14,7 +14,14 @@ class Storage { constructor() { this.calculateUsage = this.calculateUsage.bind(this); - pollVariable(this.storage, [], this.updateFrequency.bind('value'), this.calculateUsage); + const storagePoller = new FunctionPoller( + this.storage, + [], + this.updateFrequency.bind('value'), + this.calculateUsage, + ); + + storagePoller.start(); } public calculateUsage(): GenericResourceData {