Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cava Module #662

Merged
merged 9 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions scripts/fillThemes.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,11 @@ const main = async () => {
const themeFiles = (await fs.readdir(themesDir)).filter((file) => file.endsWith('.json'));

const specialKeyMappings = {
'theme.bar.menus.menu.bluetooth.scroller.color': 'theme.bar.menus.menu.bluetooth.label.color',
'theme.bar.menus.menu.network.scroller.color': 'theme.bar.menus.menu.network.label.color',
'theme.bar.buttons.modules.cava.text': 'theme.bar.buttons.modules.submap.text',
'theme.bar.buttons.modules.cava.background': 'theme.bar.buttons.modules.submap.background',
'theme.bar.buttons.modules.cava.icon_background': 'theme.bar.buttons.modules.submap.icon_background',
'theme.bar.buttons.modules.cava.icon': 'theme.bar.buttons.modules.submap.icon',
'theme.bar.buttons.modules.cava.border': 'theme.bar.buttons.modules.submap.border',
};

const queue = [...themeFiles].filter(
Expand All @@ -412,6 +415,7 @@ const main = async () => {
);

const processQueue = async () => {
const concurrencyLimit = 5;
while (queue.length > 0) {
const promises = [];
for (let i = 0; i < concurrencyLimit && queue.length > 0; i++) {
Expand Down
2 changes: 2 additions & 0 deletions src/components/bar/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Weather } from '../../components/bar/modules/weather/index';
import { Power } from '../../components/bar/modules/power/index';
import { Hyprsunset } from '../../components/bar/modules/hyprsunset/index';
import { Hypridle } from '../../components/bar/modules/hypridle/index';
import { Cava } from '../../components/bar/modules/cava/index';

export {
Menu,
Expand Down Expand Up @@ -50,4 +51,5 @@ export {
Power,
Hyprsunset,
Hypridle,
Cava,
};
2 changes: 2 additions & 0 deletions src/components/bar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
Power,
Hyprsunset,
Hypridle,
Cava,
} from './exports';

import { WidgetContainer } from './shared/WidgetContainer';
Expand Down Expand Up @@ -62,6 +63,7 @@ const widget = {
power: (): JSX.Element => WidgetContainer(Power()),
hyprsunset: (): JSX.Element => WidgetContainer(Hyprsunset()),
hypridle: (): JSX.Element => WidgetContainer(Hypridle()),
cava: (): JSX.Element => WidgetContainer(Cava()),
};

export const Bar = (() => {
Expand Down
64 changes: 64 additions & 0 deletions src/components/bar/modules/cava/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { bind, Variable } from 'astal';
import { cavaService, mprisService } from 'src/lib/constants/services';
import options from 'src/options';

const {
showActiveOnly,
bars,
autoSensitivity,
lowCutoff,
highCutoff,
noiseReduction,
stereo,
channels,
framerate,
samplerate,
} = options.bar.customModules.cava;

/**
* Initializes a visibility tracker that updates the visibility status based on the active state and the presence of players.
*
* @param isVis - A variable that holds the visibility status.
*/
export function initVisibilityTracker(isVis: Variable<boolean>): void {
Variable.derive([bind(showActiveOnly), bind(mprisService, 'players')], (showActive, players) => {
isVis.set(cavaService !== null && (!showActive || players?.length > 0));
});
}

/**
* Initializes a settings tracker that updates the CAVA service settings based on the provided options.
*/
export function initSettingsTracker(): void {
const cava = cavaService;

if (!cava) {
return;
}

Variable.derive(
[
bind(bars),
bind(channels),
bind(framerate),
bind(samplerate),
bind(autoSensitivity),
bind(lowCutoff),
bind(highCutoff),
bind(noiseReduction),
bind(stereo),
],
(bars, channels, framerate, samplerate, autoSens, lCutoff, hCutoff, noiseRed, isStereo) => {
cava.set_autosens(autoSens);
cava.set_low_cutoff(lCutoff);
cava.set_high_cutoff(hCutoff);
cava.set_noise_reduction(noiseRed);
cava.set_source('auto');
cava.set_stereo(isStereo);
cava.set_bars(bars);
cava.set_channels(channels);
cava.set_framerate(framerate);
cava.set_samplerate(samplerate);
},
);
}
77 changes: 77 additions & 0 deletions src/components/bar/modules/cava/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Variable, bind } from 'astal';
import { Astal } from 'astal/gtk3';
import { cavaService } from 'src/lib/constants/services';
import { BarBoxChild } from 'src/lib/types/bar';
import { Module } from '../../shared/Module';
import { inputHandler } from '../../utils/helpers';
import options from 'src/options';
import { initSettingsTracker, initVisibilityTracker } from './helpers';

const {
icon,
showIcon: label,
showActiveOnly,
barCharacters,
spaceCharacter,
leftClick,
rightClick,
middleClick,
scrollUp,
scrollDown,
} = options.bar.customModules.cava;

const isVis = Variable(!showActiveOnly.get());

initVisibilityTracker(isVis);
initSettingsTracker();

export const Cava = (): BarBoxChild => {
let labelBinding: Variable<string> = Variable('');

if (cavaService) {
labelBinding = Variable.derive(
[bind(cavaService, 'values'), bind(spaceCharacter), bind(barCharacters)],
(values, spacing, blockCharacters) => {
const valueMap = values
.map((v: number) => {
const index = Math.floor(v * blockCharacters.length);
return blockCharacters[Math.min(index, blockCharacters.length - 1)];
})
.join(spacing);
return valueMap;
},
);
}

return Module({
isVis,
label: labelBinding(),
showIconBinding: bind(label),
textIcon: bind(icon),
boxClass: 'cava',
props: {
setup: (self: Astal.Button) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
cmd: scrollUp,
},
onScrollDown: {
cmd: scrollDown,
},
});
},
onDestroy: () => {
labelBinding.drop();
},
},
});
};
37 changes: 37 additions & 0 deletions src/components/bar/settings/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,43 @@ export const CustomModuleSettings = (): JSX.Element => {
<Option opt={options.bar.customModules.hypridle.scrollUp} title="Scroll Up" type="string" />
<Option opt={options.bar.customModules.hypridle.scrollDown} title="Scroll Down" type="string" />

{/* Cava Section */}
<Header title="Cava" />
<Option
opt={options.theme.bar.buttons.modules.cava.enableBorder}
title="Button Border"
type="boolean"
/>
<Option opt={options.bar.customModules.cava.icon} title="Icon" type="string" />
<Option opt={options.bar.customModules.cava.showIcon} title="Show Icon" type="boolean" />
<Option opt={options.theme.bar.buttons.modules.cava.spacing} title="Spacing" type="string" />
<Option opt={options.bar.customModules.cava.barCharacters} title="Bar Characters" type="object" />
<Option opt={options.bar.customModules.cava.spaceCharacter} title="Bar Separator" type="string" />
<Option
opt={options.bar.customModules.cava.showActiveOnly}
title="Auto Hide"
subtitle="Hide if no media detected."
type="boolean"
/>
<Option opt={options.bar.customModules.cava.bars} title="Bars" type="number" />
<Option opt={options.bar.customModules.cava.channels} title="Channels" type="number" />
<Option opt={options.bar.customModules.cava.framerate} title="Framerate" type="number" />
<Option opt={options.bar.customModules.cava.samplerate} title="Sample Rate" type="number" />
<Option
opt={options.bar.customModules.cava.autoSensitivity}
title="Automatic Sensitivity"
type="boolean"
/>
<Option opt={options.bar.customModules.cava.lowCutoff} title="Low Cutoff" type="number" />
<Option opt={options.bar.customModules.cava.highCutoff} title="High Cutoff" type="number" />
<Option opt={options.bar.customModules.cava.noiseReduction} title="Noise Reduction" type="float" />
<Option opt={options.bar.customModules.cava.stereo} title="Stereo" type="boolean" />
<Option opt={options.bar.customModules.cava.leftClick} title="Left Click" type="string" />
<Option opt={options.bar.customModules.cava.rightClick} title="Right Click" type="string" />
<Option opt={options.bar.customModules.cava.middleClick} title="Middle Click" type="string" />
<Option opt={options.bar.customModules.cava.scrollUp} title="Scroll Up" type="string" />
<Option opt={options.bar.customModules.cava.scrollDown} title="Scroll Down" type="string" />

{/* Power Section */}
<Header title="Power" />
<Option
Expand Down
13 changes: 13 additions & 0 deletions src/components/bar/settings/theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,19 @@ export const CustomModuleTheme = (): JSX.Element => {
/>
<Option opt={options.theme.bar.buttons.modules.hypridle.border} title="Border" type="color" />

{/* Cava Module Section */}
<Header title="Cava" />
<Option opt={options.theme.bar.buttons.modules.cava.text} title="Bars" type="color" />
<Option opt={options.theme.bar.buttons.modules.cava.icon} title="Icon" type="color" />
<Option opt={options.theme.bar.buttons.modules.cava.background} title="Label Background" type="color" />
<Option
opt={options.theme.bar.buttons.modules.cava.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.cava.border} title="Border" type="color" />

{/* Power Module Section */}
<Header title="Power" />
<Option opt={options.theme.bar.buttons.modules.power.icon} title="Icon" type="color" />
Expand Down
11 changes: 5 additions & 6 deletions src/components/bar/shared/Module.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import options from 'src/options';

const { style } = options.theme.bar.buttons;

const undefinedVar = Variable(undefined);

export const Module = ({
icon,
textIcon,
Expand All @@ -16,7 +14,8 @@ export const Module = ({
boxClass,
isVis,
props = {},
showLabelBinding = bind(undefinedVar),
showLabelBinding = bind(Variable(true)),
showIconBinding = bind(Variable(true)),
showLabel,
labelHook,
hook,
Expand Down Expand Up @@ -48,12 +47,12 @@ export const Module = ({
);

const componentChildren = Variable.derive(
[showLabelBinding, useTextIcon],
(showLabel: boolean, forceTextIcon: boolean): JSX.Element[] => {
[showLabelBinding, showIconBinding, useTextIcon],
(showLabel: boolean, showIcon: boolean, forceTextIcon: boolean): JSX.Element[] => {
const childrenArray = [];
const iconWidget = getIconWidget(forceTextIcon);

if (iconWidget !== undefined) {
if (showIcon && iconWidget !== undefined) {
childrenArray.push(iconWidget);
}

Expand Down
22 changes: 6 additions & 16 deletions src/components/bar/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import options from 'src/options';
import { Gdk } from 'astal/gtk3';
import { GtkWidget } from 'src/lib/types/widget';
import { onMiddleClick, onPrimaryClick, onSecondaryClick } from 'src/lib/shared/eventHandlers';
import { isScrollDown, isScrollUp } from 'src/lib/utils';

const { scrollSpeed } = options.bar.customModules;

Expand Down Expand Up @@ -45,7 +46,7 @@ export const runAsyncCommand: RunAsyncCommand = (cmd, events, fn, postInputUpdat
return;
}

execAsync(`bash -c "${cmd}"`)
execAsync(['bash', '-c', cmd])
.then((output) => {
handlePostInputUpdater(postInputUpdater);
if (fn !== undefined) {
Expand Down Expand Up @@ -152,29 +153,18 @@ export const inputHandler = (
});

const id = self.connect('scroll-event', (self: GtkWidget, event: Gdk.Event) => {
const [directionSuccess, direction] = event.get_scroll_direction();
const [deltaSuccess, , yScroll] = event.get_scroll_deltas();

const handleScroll = (input?: { cmd: Variable<string>; fn: (output: string) => void }): void => {
if (input) {
throttledHandler(sanitizeInput(input.cmd), { clicked: self, event }, input.fn, postInputUpdater);
}
};

if (directionSuccess) {
if (direction === Gdk.ScrollDirection.UP) {
handleScroll(onScrollUpInput);
} else if (direction === Gdk.ScrollDirection.DOWN) {
handleScroll(onScrollDownInput);
}
if (isScrollUp(event)) {
handleScroll(onScrollUpInput);
}

if (deltaSuccess) {
if (yScroll > 0) {
handleScroll(onScrollUpInput);
} else if (yScroll < 0) {
handleScroll(onScrollDownInput);
}
if (isScrollDown(event)) {
handleScroll(onScrollDownInput);
}
});

Expand Down
3 changes: 3 additions & 0 deletions src/lib/constants/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ export const brightnessService = Brightness.get_default();

import AstalPowerProfiles from 'gi://AstalPowerProfiles?version=0.1';
export const powerProfilesService = AstalPowerProfiles.get_default();

import AstalCava from 'gi://AstalCava';
export const cavaService = AstalCava.get_default();
1 change: 1 addition & 0 deletions src/lib/types/bar.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type BarModule = {
props?: Widget.ButtonProps;
showLabel?: boolean;
showLabelBinding?: Binding;
showIconBinding?: Binding;
hook?: BoxHook;
connection?: Binding<Connectable>;
};
Expand Down
3 changes: 2 additions & 1 deletion src/lib/types/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export type BarModule =
| 'power'
| 'systray'
| 'hypridle'
| 'hyprsunset';
| 'hyprsunset'
| 'cava';

export type BarLayout = {
left: BarModule[];
Expand Down
Loading
Loading