From 38bbcf96efe3372c688e5b8d6410206cf0de7ea0 Mon Sep 17 00:00:00 2001 From: Jas Singh Date: Tue, 24 Dec 2024 14:46:37 -0800 Subject: [PATCH] Added a command to adjust volume that respects hyprpanel settings and made bar module settings for click events apply immediately. (#619) --- scripts/hyprpanel_launcher.sh.in | 2 +- src/cli/commander/Parser.ts | 145 ++++++++++++------ src/cli/commander/commands/utility/index.ts | 31 ++++ src/components/bar/modules/battery/index.tsx | 53 ++++--- .../bar/modules/bluetooth/index.tsx | 52 ++++--- src/components/bar/modules/clock/index.tsx | 52 ++++--- src/components/bar/modules/media/index.tsx | 68 +++++--- src/components/bar/modules/menu/index.tsx | 52 ++++--- src/components/bar/modules/network/index.tsx | 53 ++++--- .../bar/modules/notifications/index.tsx | 52 ++++--- src/components/bar/modules/volume/index.tsx | 52 ++++--- .../bar/modules/window_title/index.tsx | 52 ++++--- .../menus/audio/active/sliderItem/Slider.tsx | 3 +- .../calendar/weather/temperature/index.tsx | 79 ++++++---- .../settings/pages/config/bar/index.tsx | 2 + src/lib/icons/icons2.ts | 1 + src/lib/icons/weather.ts | 1 + src/options.ts | 6 +- 18 files changed, 495 insertions(+), 261 deletions(-) diff --git a/scripts/hyprpanel_launcher.sh.in b/scripts/hyprpanel_launcher.sh.in index 122d53c..85260dd 100644 --- a/scripts/hyprpanel_launcher.sh.in +++ b/scripts/hyprpanel_launcher.sh.in @@ -5,5 +5,5 @@ export HYPRPANEL_DATADIR="@DATADIR@" if [ "$#" -eq 0 ]; then exec gjs -m "@DATADIR@/hyprpanel.js" else - exec astal -i hyprpanel "$@" + exec astal -i hyprpanel "$*" fi diff --git a/src/cli/commander/Parser.ts b/src/cli/commander/Parser.ts index 9645b0a..78be34b 100644 --- a/src/cli/commander/Parser.ts +++ b/src/cli/commander/Parser.ts @@ -2,13 +2,11 @@ import { CommandRegistry } from './Registry'; import { Command, ParsedCommand } from './types'; /** - * The CommandParser is responsible for parsing the input string into a command and its positional arguments. - * It does not handle flags, only positional arguments. + * Parses an input string into a command and its positional arguments. * - * Expected command format: + * Expected format: * astal arg1 arg2 arg3... * - * The parser: * 1. Tokenizes the input. * 2. Identifies the command by the first token. * 3. Parses positional arguments based on the command definition. @@ -19,23 +17,25 @@ export class CommandParser { private registry: CommandRegistry; /** - * Creates an instance of CommandParser. + * Constructs a CommandParser with the provided command registry. * - * @param registry - The command registry to use. + * @param registry - The command registry containing available commands. */ constructor(registry: CommandRegistry) { this.registry = registry; } /** - * Parses the input string into a ParsedCommand object. + * Parses the entire input string, returning the matching command and its arguments. * - * @param input - The input string to parse. - * @returns The parsed command and its arguments. - * @throws If no command is provided or the command is unknown. + * @param input - The raw input string to parse. + * @returns A parsed command object, including the command and its arguments. + * @throws If no command token is found. + * @throws If the command token is not registered. */ parse(input: string): ParsedCommand { const tokens = this.tokenize(input); + if (tokens.length === 0) { throw new Error('No command provided.'); } @@ -51,10 +51,10 @@ export class CommandParser { } /** - * Tokenizes the input string into an array of tokens. + * Splits the input string into tokens, respecting quotes. * - * @param input - The input string to tokenize. - * @returns The array of tokens. + * @param input - The raw input string to break into tokens. + * @returns An array of tokens. */ private tokenize(input: string): string[] { const regex = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g; @@ -63,78 +63,131 @@ export class CommandParser { } /** - * Strips quotes from the beginning and end of a string. + * Removes surrounding quotes from a single token, if they exist. * - * @param str - The string to strip quotes from. - * @returns The string without quotes. + * @param str - The token from which to strip leading or trailing quotes. + * @returns The token without its outer quotes. */ private stripQuotes(str: string): string { return str.replace(/^["'](.+(?=["']$))["']$/, '$1'); } /** - * Parses the positional arguments for a command. + * Parses the array of tokens into arguments based on the command's argument definitions. * - * @param command - The command definition. - * @param tokens - The array of argument tokens. - * @returns The parsed arguments. - * @throws If there are too many arguments or a required argument is missing. + * @param command - The command whose arguments are being parsed. + * @param tokens - The list of tokens extracted from the input. + * @returns An object mapping argument names to their parsed values. + * @throws If required arguments are missing. + * @throws If there are too many tokens for the command definition. */ private parseArgs(command: Command, tokens: string[]): Record { const args: Record = {}; - const argDefs = command.args; + let currentIndex = 0; - if (tokens.length > argDefs.length) { - throw new Error(`Too many arguments for command "${command.name}". Expected at most ${argDefs.length}.`); - } - - argDefs.forEach((argDef, index) => { - const value = tokens[index]; - if (value === undefined) { + for (const argDef of command.args) { + if (currentIndex >= tokens.length) { if (argDef.required) { throw new Error(`Missing required argument: "${argDef.name}".`); } if (argDef.default !== undefined) { args[argDef.name] = argDef.default; } - return; + continue; } - args[argDef.name] = this.convertType(value, argDef.type); - }); + if (argDef.type === 'object') { + const { objectValue, nextIndex } = this.parseObjectTokens(tokens, currentIndex); + args[argDef.name] = objectValue; + currentIndex = nextIndex; + } else { + const value = tokens[currentIndex]; + currentIndex++; + args[argDef.name] = this.convertType(value, argDef.type); + } + } + + if (currentIndex < tokens.length) { + throw new Error( + `Too many arguments for command "${command.name}". Expected at most ${command.args.length}.`, + ); + } return args; } /** - * Converts a string value to the specified type. + * Accumulates tokens until braces are balanced to form a valid JSON string, + * then parses the result. * - * @param value - The value to convert. - * @param type - The type to convert to. - * @returns The converted value. - * @throws If the value cannot be converted to the specified type. + * @param tokens - The list of tokens extracted from the input. + * @param startIndex - The token index from which to begin JSON parsing. + * @returns An object containing the parsed JSON object and the next token index. + * @throws If the reconstructed JSON is invalid. */ - private convertType( - value: string, - type: 'string' | 'number' | 'boolean' | 'object', - ): string | number | boolean | Record { + private parseObjectTokens(tokens: string[], startIndex: number): { objectValue: unknown; nextIndex: number } { + let braceCount = 0; + let started = false; + const objectTokens: string[] = []; + let currentIndex = startIndex; + + while (currentIndex < tokens.length) { + const token = tokens[currentIndex]; + currentIndex++; + + for (const char of token) { + if (char === '{') braceCount++; + if (char === '}') braceCount--; + } + + objectTokens.push(token); + + // Once we've started and braceCount returns to 0, we assume the object is complete + if (started && braceCount === 0) break; + if (token.includes('{')) started = true; + } + + const objectString = objectTokens.join(' '); + let parsed: unknown; + try { + parsed = JSON.parse(objectString); + } catch { + throw new Error(`Invalid JSON object: "${objectString}".`); + } + + return { objectValue: parsed, nextIndex: currentIndex }; + } + + /** + * Converts a single token to the specified argument type. + * + * @param value - The raw token to be converted. + * @param type - The expected argument type. + * @returns The converted value. + * @throws If the token cannot be converted to the expected type. + */ + private convertType(value: string, type: 'string' | 'number' | 'boolean' | 'object'): unknown { switch (type) { - case 'number': + case 'number': { const num = Number(value); if (isNaN(num)) { throw new Error(`Expected a number but got "${value}".`); } return num; - case 'boolean': - if (value.toLowerCase() === 'true') return true; - if (value.toLowerCase() === 'false') return false; + } + case 'boolean': { + const lower = value.toLowerCase(); + if (lower === 'true') return true; + if (lower === 'false') return false; throw new Error(`Expected a boolean (true/false) but got "${value}".`); - case 'object': + } + case 'object': { try { return JSON.parse(value); } catch { throw new Error(`Invalid JSON object: "${value}".`); } + } case 'string': default: return value; diff --git a/src/cli/commander/commands/utility/index.ts b/src/cli/commander/commands/utility/index.ts index 353a8d5..2fb9181 100644 --- a/src/cli/commander/commands/utility/index.ts +++ b/src/cli/commander/commands/utility/index.ts @@ -2,6 +2,7 @@ import { errorHandler } from 'src/lib/utils'; import { Command } from '../../types'; import { execAsync, Gio, GLib } from 'astal'; import { checkDependencies } from './checkDependencies'; +import { audioService } from 'src/lib/constants/services'; export const utilityCommands: Command[] = [ { @@ -33,6 +34,36 @@ export const utilityCommands: Command[] = [ } }, }, + { + name: 'adjustVolume', + aliases: ['vol'], + description: 'Adjusts the volume of the default audio output device.', + category: 'Utility', + args: [ + { + name: 'volume', + description: 'A positive or negative number to adjust the volume by.', + type: 'number', + required: true, + }, + ], + handler: (args: Record): number => { + try { + const speaker = audioService.defaultSpeaker; + const volumeInput = Number(args['volume']) / 100; + + if (options.menus.volume.raiseMaximumVolume.get()) { + speaker.set_volume(Math.min(speaker.volume + volumeInput, 1.5)); + } else { + speaker.set_volume(Math.min(speaker.volume + volumeInput, 1)); + } + + return Math.round((speaker.volume + volumeInput) * 100); + } catch (error) { + errorHandler(error); + } + }, + }, { name: 'migrateConfig', aliases: ['mcfg'], diff --git a/src/components/bar/modules/battery/index.tsx b/src/components/bar/modules/battery/index.tsx index 7382d58..202d2fb 100644 --- a/src/components/bar/modules/battery/index.tsx +++ b/src/components/bar/modules/battery/index.tsx @@ -7,7 +7,6 @@ import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/util import Variable from 'astal/variable'; import { bind } from 'astal/binding.js'; import AstalBattery from 'gi://AstalBattery?version=0.1'; -import { useHook } from 'src/lib/shared/hookHandler'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers'; import { getBatteryIcon } from './helpers'; @@ -102,29 +101,43 @@ const BatteryLabel = (): BarBoxChild => { boxClass: 'battery', props: { setup: (self: Astal.Button): void => { - useHook(self, options.bar.scrollSpeed, () => { - const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); + let disconnectFunctions: (() => void)[] = []; - const disconnectPrimary = onPrimaryClick(self, (clicked, event) => { - openMenu(clicked, event, 'energymenu'); - }); + Variable.derive( + [ + bind(rightClick), + bind(middleClick), + bind(scrollUp), + bind(scrollDown), + bind(options.bar.scrollSpeed), + ], + () => { + disconnectFunctions.forEach((disconnect) => disconnect()); + disconnectFunctions = []; - const disconnectSecondary = onSecondaryClick(self, (clicked, event) => { - runAsyncCommand(rightClick.get(), { clicked, event }); - }); + const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); - const disconnectMiddle = onMiddleClick(self, (clicked, event) => { - runAsyncCommand(middleClick.get(), { clicked, event }); - }); + disconnectFunctions.push( + onPrimaryClick(self, (clicked, event) => { + openMenu(clicked, event, 'energymenu'); + }), + ); - const disconnectScroll = onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get()); - return (): void => { - disconnectPrimary(); - disconnectSecondary(); - disconnectMiddle(); - disconnectScroll(); - }; - }); + disconnectFunctions.push( + onSecondaryClick(self, (clicked, event) => { + runAsyncCommand(rightClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push( + onMiddleClick(self, (clicked, event) => { + runAsyncCommand(middleClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get())); + }, + ); }, }, }; diff --git a/src/components/bar/modules/bluetooth/index.tsx b/src/components/bar/modules/bluetooth/index.tsx index 3da04cd..a1287a6 100644 --- a/src/components/bar/modules/bluetooth/index.tsx +++ b/src/components/bar/modules/bluetooth/index.tsx @@ -5,7 +5,6 @@ import { BarBoxChild } from 'src/lib/types/bar.js'; import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js'; import { bind } from 'astal/binding.js'; import Variable from 'astal/variable.js'; -import { useHook } from 'src/lib/shared/hookHandler.js'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js'; import AstalBluetooth from 'gi://AstalBluetooth?version=0.1'; import { Astal } from 'astal/gtk3'; @@ -57,30 +56,43 @@ const Bluetooth = (): BarBoxChild => { boxClass: 'bluetooth', props: { setup: (self: Astal.Button): void => { - useHook(self, options.bar.scrollSpeed, () => { - const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); + let disconnectFunctions: (() => void)[] = []; - const disconnectPrimary = onPrimaryClick(self, (clicked, event) => { - openMenu(clicked, event, 'bluetoothmenu'); - }); + Variable.derive( + [ + bind(rightClick), + bind(middleClick), + bind(scrollUp), + bind(scrollDown), + bind(options.bar.scrollSpeed), + ], + () => { + disconnectFunctions.forEach((disconnect) => disconnect()); + disconnectFunctions = []; - const disconnectSecondary = onSecondaryClick(self, (clicked, event) => { - runAsyncCommand(rightClick.get(), { clicked, event }); - }); + const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); - const disconnectMiddle = onMiddleClick(self, (clicked, event) => { - runAsyncCommand(middleClick.get(), { clicked, event }); - }); + disconnectFunctions.push( + onPrimaryClick(self, (clicked, event) => { + openMenu(clicked, event, 'bluetoothmenu'); + }), + ); - const disconnectScroll = onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get()); + disconnectFunctions.push( + onSecondaryClick(self, (clicked, event) => { + runAsyncCommand(rightClick.get(), { clicked, event }); + }), + ); - return (): void => { - disconnectPrimary(); - disconnectSecondary(); - disconnectMiddle(); - disconnectScroll(); - }; - }); + disconnectFunctions.push( + onMiddleClick(self, (clicked, event) => { + runAsyncCommand(middleClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get())); + }, + ); }, onDestroy: (): void => { componentClassName.drop(); diff --git a/src/components/bar/modules/clock/index.tsx b/src/components/bar/modules/clock/index.tsx index 15bf44f..f490dff 100644 --- a/src/components/bar/modules/clock/index.tsx +++ b/src/components/bar/modules/clock/index.tsx @@ -3,7 +3,6 @@ import options from 'src/options'; import { BarBoxChild } from 'src/lib/types/bar.js'; import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js'; import { bind, Variable } from 'astal'; -import { useHook } from 'src/lib/shared/hookHandler'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers'; import { Astal } from 'astal/gtk3'; import { systemTime } from 'src/globals/time'; @@ -57,30 +56,43 @@ const Clock = (): BarBoxChild => { boxClass: 'clock', props: { setup: (self: Astal.Button): void => { - useHook(self, options.bar.scrollSpeed, () => { - const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); + let disconnectFunctions: (() => void)[] = []; - const disconnectPrimary = onPrimaryClick(self, (clicked, event) => { - openMenu(clicked, event, 'calendarmenu'); - }); + Variable.derive( + [ + bind(rightClick), + bind(middleClick), + bind(scrollUp), + bind(scrollDown), + bind(options.bar.scrollSpeed), + ], + () => { + disconnectFunctions.forEach((disconnect) => disconnect()); + disconnectFunctions = []; - const disconnectSecondary = onSecondaryClick(self, (clicked, event) => { - runAsyncCommand(rightClick.get(), { clicked, event }); - }); + const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); - const disconnectMiddle = onMiddleClick(self, (clicked, event) => { - runAsyncCommand(middleClick.get(), { clicked, event }); - }); + disconnectFunctions.push( + onPrimaryClick(self, (clicked, event) => { + openMenu(clicked, event, 'calendarmenu'); + }), + ); - const disconnectScroll = onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get()); + disconnectFunctions.push( + onSecondaryClick(self, (clicked, event) => { + runAsyncCommand(rightClick.get(), { clicked, event }); + }), + ); - return (): void => { - disconnectPrimary(); - disconnectSecondary(); - disconnectMiddle(); - disconnectScroll(); - }; - }); + disconnectFunctions.push( + onMiddleClick(self, (clicked, event) => { + runAsyncCommand(middleClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get())); + }, + ); }, }, }; diff --git a/src/components/bar/modules/media/index.tsx b/src/components/bar/modules/media/index.tsx index 7fd1a7d..09fdfc9 100644 --- a/src/components/bar/modules/media/index.tsx +++ b/src/components/bar/modules/media/index.tsx @@ -1,18 +1,26 @@ import { openMenu } from '../../utils/menu.js'; import options from 'src/options.js'; -import { runAsyncCommand } from 'src/components/bar/utils/helpers.js'; +import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js'; import { generateMediaLabel } from './helpers/index.js'; -import { useHook } from 'src/lib/shared/hookHandler.js'; import { mprisService } from 'src/lib/constants/services.js'; import Variable from 'astal/variable.js'; -import { onMiddleClick, onPrimaryClick, onSecondaryClick } from 'src/lib/shared/eventHandlers.js'; +import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js'; import { bind } from 'astal/binding.js'; import { BarBoxChild } from 'src/lib/types/bar.js'; import { Astal } from 'astal/gtk3'; import { activePlayer, mediaAlbum, mediaArtist, mediaTitle } from 'src/globals/media.js'; -const { truncation, truncation_size, show_label, show_active_only, rightClick, middleClick, format } = - options.bar.media; +const { + truncation, + truncation_size, + show_label, + show_active_only, + rightClick, + middleClick, + scrollUp, + scrollDown, + format, +} = options.bar.media; const isVis = Variable(!show_active_only.get()); @@ -71,25 +79,43 @@ const Media = (): BarBoxChild => { boxClass: 'media', props: { setup: (self: Astal.Button): void => { - useHook(self, options.bar.scrollSpeed, () => { - const disconnectPrimary = onPrimaryClick(self, (clicked, event) => { - openMenu(clicked, event, 'mediamenu'); - }); + let disconnectFunctions: (() => void)[] = []; - const disconnectSecondary = onSecondaryClick(self, (clicked, event) => { - runAsyncCommand(rightClick.get(), { clicked, event }); - }); + Variable.derive( + [ + bind(rightClick), + bind(middleClick), + bind(scrollUp), + bind(scrollDown), + bind(options.bar.scrollSpeed), + ], + () => { + disconnectFunctions.forEach((disconnect) => disconnect()); + disconnectFunctions = []; - const disconnectMiddle = onMiddleClick(self, (clicked, event) => { - runAsyncCommand(middleClick.get(), { clicked, event }); - }); + const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); - return (): void => { - disconnectPrimary(); - disconnectSecondary(); - disconnectMiddle(); - }; - }); + disconnectFunctions.push( + onPrimaryClick(self, (clicked, event) => { + openMenu(clicked, event, 'mediamenu'); + }), + ); + + disconnectFunctions.push( + onSecondaryClick(self, (clicked, event) => { + runAsyncCommand(rightClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push( + onMiddleClick(self, (clicked, event) => { + runAsyncCommand(middleClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get())); + }, + ); }, }, }; diff --git a/src/components/bar/modules/menu/index.tsx b/src/components/bar/modules/menu/index.tsx index f6a7c1f..1278d51 100644 --- a/src/components/bar/modules/menu/index.tsx +++ b/src/components/bar/modules/menu/index.tsx @@ -5,7 +5,6 @@ import { getDistroIcon } from '../../../../lib/utils.js'; import { bind } from 'astal/binding.js'; import Variable from 'astal/variable.js'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js'; -import { useHook } from 'src/lib/shared/hookHandler.js'; // Ensure correct import import { BarBoxChild } from 'src/lib/types/bar.js'; import { Astal } from 'astal/gtk3'; @@ -43,30 +42,43 @@ const Menu = (): BarBoxChild => { boxClass: 'dashboard', props: { setup: (self: Astal.Button): void => { - useHook(self, options.bar.scrollSpeed, () => { - const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); + let disconnectFunctions: (() => void)[] = []; - const disconnectPrimary = onPrimaryClick(self, (clicked, event) => { - openMenu(clicked, event, 'dashboardmenu'); - }); + Variable.derive( + [ + bind(rightClick), + bind(middleClick), + bind(scrollUp), + bind(scrollDown), + bind(options.bar.scrollSpeed), + ], + () => { + disconnectFunctions.forEach((disconnect) => disconnect()); + disconnectFunctions = []; - const disconnectSecondary = onSecondaryClick(self, (clicked, event) => { - runAsyncCommand(rightClick.get(), { clicked, event }); - }); + const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); - const disconnectMiddle = onMiddleClick(self, (clicked, event) => { - runAsyncCommand(middleClick.get(), { clicked, event }); - }); + disconnectFunctions.push( + onPrimaryClick(self, (clicked, event) => { + openMenu(clicked, event, 'dashboardmenu'); + }), + ); - const disconnectScroll = onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get()); + disconnectFunctions.push( + onSecondaryClick(self, (clicked, event) => { + runAsyncCommand(rightClick.get(), { clicked, event }); + }), + ); - return (): void => { - disconnectPrimary(); - disconnectSecondary(); - disconnectMiddle(); - disconnectScroll(); - }; - }); + disconnectFunctions.push( + onMiddleClick(self, (clicked, event) => { + runAsyncCommand(middleClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get())); + }, + ); }, }, }; diff --git a/src/components/bar/modules/network/index.tsx b/src/components/bar/modules/network/index.tsx index 7c27fff..d7cd3c4 100644 --- a/src/components/bar/modules/network/index.tsx +++ b/src/components/bar/modules/network/index.tsx @@ -6,7 +6,6 @@ import { bind, Variable } from 'astal'; import { onPrimaryClick, onSecondaryClick, onMiddleClick, onScroll } from 'src/lib/shared/eventHandlers'; import { Astal, Gtk } from 'astal/gtk3'; import AstalNetwork from 'gi://AstalNetwork?version=0.1'; -import { useHook } from 'src/lib/shared/hookHandler'; import { BarBoxChild } from 'src/lib/types/bar.js'; import { formatWifiInfo, wiredIcon, wirelessIcon } from './helpers'; @@ -90,29 +89,43 @@ const Network = (): BarBoxChild => { boxClass: 'network', props: { setup: (self: Astal.Button): void => { - useHook(self, options.bar.scrollSpeed, () => { - const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); + let disconnectFunctions: (() => void)[] = []; - const disconnectPrimary = onPrimaryClick(self, (clicked, event) => { - openMenu(clicked, event, 'networkmenu'); - }); + Variable.derive( + [ + bind(rightClick), + bind(middleClick), + bind(scrollUp), + bind(scrollDown), + bind(options.bar.scrollSpeed), + ], + () => { + disconnectFunctions.forEach((disconnect) => disconnect()); + disconnectFunctions = []; - const disconnectSecondary = onSecondaryClick(self, (clicked, event) => { - runAsyncCommand(rightClick.get(), { clicked, event }); - }); + const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); - const disconnectMiddle = onMiddleClick(self, (clicked, event) => { - runAsyncCommand(middleClick.get(), { clicked, event }); - }); + disconnectFunctions.push( + onPrimaryClick(self, (clicked, event) => { + openMenu(clicked, event, 'networkmenu'); + }), + ); - const disconnectScroll = onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get()); - return (): void => { - disconnectPrimary(); - disconnectSecondary(); - disconnectMiddle(); - disconnectScroll(); - }; - }); + disconnectFunctions.push( + onSecondaryClick(self, (clicked, event) => { + runAsyncCommand(rightClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push( + onMiddleClick(self, (clicked, event) => { + runAsyncCommand(middleClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get())); + }, + ); }, }, }; diff --git a/src/components/bar/modules/notifications/index.tsx b/src/components/bar/modules/notifications/index.tsx index 5397875..3731a22 100644 --- a/src/components/bar/modules/notifications/index.tsx +++ b/src/components/bar/modules/notifications/index.tsx @@ -6,7 +6,6 @@ import { filterNotifications } from 'src/lib/shared/notifications.js'; import { BarBoxChild } from 'src/lib/types/bar.js'; import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js'; import { bind, Variable } from 'astal'; -import { useHook } from 'src/lib/shared/hookHandler'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers'; const { show_total, rightClick, middleClick, scrollUp, scrollDown, hideCountWhenZero } = options.bar.notifications; @@ -85,30 +84,43 @@ export const Notifications = (): BarBoxChild => { boxClass: 'notifications', props: { setup: (self: Astal.Button): void => { - useHook(self, options.bar.scrollSpeed, () => { - const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); + let disconnectFunctions: (() => void)[] = []; - const disconnectPrimary = onPrimaryClick(self, (clicked, event) => { - openMenu(clicked, event, 'notificationsmenu'); - }); + Variable.derive( + [ + bind(rightClick), + bind(middleClick), + bind(scrollUp), + bind(scrollDown), + bind(options.bar.scrollSpeed), + ], + () => { + disconnectFunctions.forEach((disconnect) => disconnect()); + disconnectFunctions = []; - const disconnectSecondary = onSecondaryClick(self, (clicked, event) => { - runAsyncCommand(rightClick.get(), { clicked, event }); - }); + const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); - const disconnectMiddle = onMiddleClick(self, (clicked, event) => { - runAsyncCommand(middleClick.get(), { clicked, event }); - }); + disconnectFunctions.push( + onPrimaryClick(self, (clicked, event) => { + openMenu(clicked, event, 'notificationsmenu'); + }), + ); - const disconnectScroll = onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get()); + disconnectFunctions.push( + onSecondaryClick(self, (clicked, event) => { + runAsyncCommand(rightClick.get(), { clicked, event }); + }), + ); - return (): void => { - disconnectPrimary(); - disconnectSecondary(); - disconnectMiddle(); - disconnectScroll(); - }; - }); + disconnectFunctions.push( + onMiddleClick(self, (clicked, event) => { + runAsyncCommand(middleClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get())); + }, + ); }, }, }; diff --git a/src/components/bar/modules/volume/index.tsx b/src/components/bar/modules/volume/index.tsx index 7b2a930..96a8dad 100644 --- a/src/components/bar/modules/volume/index.tsx +++ b/src/components/bar/modules/volume/index.tsx @@ -4,7 +4,6 @@ import options from 'src/options'; import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js'; import Variable from 'astal/variable.js'; import { bind } from 'astal/binding.js'; -import { useHook } from 'src/lib/shared/hookHandler.js'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js'; import { getIcon } from './helpers/index.js'; import { BarBoxChild } from 'src/lib/types/bar.js'; @@ -77,30 +76,43 @@ const Volume = (): BarBoxChild => { boxClass: 'volume', props: { setup: (self: Astal.Button): void => { - useHook(self, options.bar.scrollSpeed, () => { - const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); + let disconnectFunctions: (() => void)[] = []; - const disconnectPrimary = onPrimaryClick(self, (clicked, event) => { - openMenu(clicked, event, 'audiomenu'); - }); + Variable.derive( + [ + bind(rightClick), + bind(middleClick), + bind(scrollUp), + bind(scrollDown), + bind(options.bar.scrollSpeed), + ], + () => { + disconnectFunctions.forEach((disconnect) => disconnect()); + disconnectFunctions = []; - const disconnectSecondary = onSecondaryClick(self, (clicked, event) => { - runAsyncCommand(rightClick.get(), { clicked, event }); - }); + const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); - const disconnectMiddle = onMiddleClick(self, (clicked, event) => { - runAsyncCommand(middleClick.get(), { clicked, event }); - }); + disconnectFunctions.push( + onPrimaryClick(self, (clicked, event) => { + openMenu(clicked, event, 'audiomenu'); + }), + ); - const disconnectScroll = onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get()); + disconnectFunctions.push( + onSecondaryClick(self, (clicked, event) => { + runAsyncCommand(rightClick.get(), { clicked, event }); + }), + ); - return (): void => { - disconnectPrimary(); - disconnectSecondary(); - disconnectMiddle(); - disconnectScroll(); - }; - }); + disconnectFunctions.push( + onMiddleClick(self, (clicked, event) => { + runAsyncCommand(middleClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get())); + }, + ); }, }, }; diff --git a/src/components/bar/modules/window_title/index.tsx b/src/components/bar/modules/window_title/index.tsx index 33e266c..687286d 100644 --- a/src/components/bar/modules/window_title/index.tsx +++ b/src/components/bar/modules/window_title/index.tsx @@ -3,7 +3,6 @@ import { BarBoxChild } from 'src/lib/types/bar'; import options from 'src/options'; import { hyprlandService } from 'src/lib/constants/services'; import AstalHyprland from 'gi://AstalHyprland?version=0.1'; -import { useHook } from 'src/lib/shared/hookHandler'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers'; import { bind, Variable } from 'astal'; import { getTitle, getWindowMatch, truncateTitle } from './helpers/title'; @@ -81,30 +80,43 @@ const ClientTitle = (): BarBoxChild => { boxClass: 'windowtitle', props: { setup: (self: Astal.Button): void => { - useHook(self, options.bar.scrollSpeed, () => { - const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); + let disconnectFunctions: (() => void)[] = []; - const disconnectPrimary = onPrimaryClick(self, (clicked, event) => { - runAsyncCommand(leftClick.get(), { clicked, event }); - }); + Variable.derive( + [ + bind(rightClick), + bind(middleClick), + bind(scrollUp), + bind(scrollDown), + bind(options.bar.scrollSpeed), + ], + () => { + disconnectFunctions.forEach((disconnect) => disconnect()); + disconnectFunctions = []; - const disconnectSecondary = onSecondaryClick(self, (clicked, event) => { - runAsyncCommand(rightClick.get(), { clicked, event }); - }); + const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get()); - const disconnectMiddle = onMiddleClick(self, (clicked, event) => { - runAsyncCommand(middleClick.get(), { clicked, event }); - }); + disconnectFunctions.push( + onPrimaryClick(self, (clicked, event) => { + runAsyncCommand(leftClick.get(), { clicked, event }); + }), + ); - const disconnectScroll = onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get()); + disconnectFunctions.push( + onSecondaryClick(self, (clicked, event) => { + runAsyncCommand(rightClick.get(), { clicked, event }); + }), + ); - return (): void => { - disconnectPrimary(); - disconnectSecondary(); - disconnectMiddle(); - disconnectScroll(); - }; - }); + disconnectFunctions.push( + onMiddleClick(self, (clicked, event) => { + runAsyncCommand(middleClick.get(), { clicked, event }); + }), + ); + + disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get())); + }, + ); }, }, }; diff --git a/src/components/menus/audio/active/sliderItem/Slider.tsx b/src/components/menus/audio/active/sliderItem/Slider.tsx index 07e50f1..5464913 100644 --- a/src/components/menus/audio/active/sliderItem/Slider.tsx +++ b/src/components/menus/audio/active/sliderItem/Slider.tsx @@ -36,7 +36,8 @@ export const Slider = ({ device, type }: SliderProps): JSX.Element => { self.connect('scroll-event', (_, event: Gdk.Event) => { if (isScrollUp(event)) { const newVolume = device.volume + 0.05; - device.set_volume(Math.min(newVolume, 1)); + const minVolume = raiseMaximumVolume.get() ? 1.5 : 1; + device.set_volume(Math.min(newVolume, minVolume)); } if (isScrollDown(event)) { diff --git a/src/components/menus/calendar/weather/temperature/index.tsx b/src/components/menus/calendar/weather/temperature/index.tsx index 43bb626..96470c6 100644 --- a/src/components/menus/calendar/weather/temperature/index.tsx +++ b/src/components/menus/calendar/weather/temperature/index.tsx @@ -5,45 +5,64 @@ import { Gtk } from 'astal/gtk3'; import { bind, Variable } from 'astal'; const { unit } = options.menus.clock.weather; -export const TodayTemperature = (): JSX.Element => { +const WeatherStatus = (): JSX.Element => { + return ( + + + ); +}; + +const Temperature = (): JSX.Element => { const labelBinding = Variable.derive([bind(globalWeatherVar), bind(unit)], getTemperature); + + const TemperatureLabel = (): JSX.Element => { + return