diff --git a/README.md b/README.md
index c05af98..d2a5c9e 100644
--- a/README.md
+++ b/README.md
@@ -171,14 +171,14 @@ Alternatively, if you're using NixOS and/or Home-Manager, you can setup AGS usin
outputs = { self, nixpkgs, ... }@inputs:
let
# ...
- system = "x86_64-linux"; # change to whatever your system should be.
+ system = "x86_64-linux"; # change to whatever your system should be.
pkgs = import nixpkgs {
- inherit system;
- # ...
- overlays = [
+ inherit system;
+ # ...
+ overlays = [
inputs.hyprpanel.overlay
- ];
- };
+ ];
+ };
in {
# ...
}
diff --git a/flake.lock b/flake.lock
index a161abe..695fe36 100644
--- a/flake.lock
+++ b/flake.lock
@@ -8,11 +8,11 @@
]
},
"locked": {
- "lastModified": 1735485506,
- "narHash": "sha256-7CWr3Q83KnGiLUn0oaboafLMOXQ0X9/fjFRVY1xopbM=",
+ "lastModified": 1736090999,
+ "narHash": "sha256-B5CJuHqfJrzPa7tObK0H9669/EClSHpa/P7B9EuvElU=",
"owner": "aylur",
"repo": "ags",
- "rev": "251d39413543264361898b02035775aa3e46fe52",
+ "rev": "5527c3c07d92c11e04e7fd99d58429493dba7e3c",
"type": "github"
},
"original": {
@@ -44,11 +44,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1735291276,
- "narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=",
+ "lastModified": 1736344531,
+ "narHash": "sha256-8YVQ9ZbSfuUk2bUf2KRj60NRraLPKPS0Q4QFTbc+c2c=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "634fd46801442d760e09493a794c4f15db2d0cbb",
+ "rev": "bffc22eb12172e6db3c5dde9e3e5628f8e3e7912",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 3110342..9bdd585 100644
--- a/flake.nix
+++ b/flake.nix
@@ -33,6 +33,7 @@
battery
bluetooth
mpris
+ cava
network
notifd
powerprofiles
diff --git a/scripts/fillThemes.js b/scripts/fillThemes.js
index 2d8e3fe..f95876b 100644
--- a/scripts/fillThemes.js
+++ b/scripts/fillThemes.js
@@ -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(
@@ -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++) {
diff --git a/src/components/bar/exports.ts b/src/components/bar/exports.ts
index 13da913..fc4f9b5 100644
--- a/src/components/bar/exports.ts
+++ b/src/components/bar/exports.ts
@@ -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,
@@ -50,4 +51,5 @@ export {
Power,
Hyprsunset,
Hypridle,
+ Cava,
};
diff --git a/src/components/bar/index.tsx b/src/components/bar/index.tsx
index aed8c48..e770c04 100644
--- a/src/components/bar/index.tsx
+++ b/src/components/bar/index.tsx
@@ -24,6 +24,7 @@ import {
Power,
Hyprsunset,
Hypridle,
+ Cava,
} from './exports';
import { WidgetContainer } from './shared/WidgetContainer';
@@ -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 = (() => {
diff --git a/src/components/bar/modules/bluetooth/index.tsx b/src/components/bar/modules/bluetooth/index.tsx
index 473bddc..7f55d80 100644
--- a/src/components/bar/modules/bluetooth/index.tsx
+++ b/src/components/bar/modules/bluetooth/index.tsx
@@ -11,11 +11,11 @@ import { Astal } from 'astal/gtk3';
const { rightClick, middleClick, scrollDown, scrollUp } = options.bar.bluetooth;
const Bluetooth = (): BarBoxChild => {
- const btIcon = (isPowered: boolean): JSX.Element => (
+ const BluetoothIcon = ({ isPowered }: BluetoothIconProps): JSX.Element => (
);
- const btText = (isPowered: boolean, devices: AstalBluetooth.Device[]): JSX.Element => {
+ const BluetoothLabel = ({ isPowered, devices }: BluetoothLabelProps): JSX.Element => {
const connectDevices = devices.filter((device) => device.connected);
const label =
@@ -39,11 +39,17 @@ const Bluetooth = (): BarBoxChild => {
const componentBinding = Variable.derive(
[bind(options.bar.bluetooth.label), bind(bluetoothService, 'isPowered'), bind(bluetoothService, 'devices')],
- (showLabel: boolean, isPowered: boolean, devices: AstalBluetooth.Device[]): JSX.Element[] => {
+ (showLabel: boolean, isPowered: boolean, devices: AstalBluetooth.Device[]): JSX.Element => {
if (showLabel) {
- return [btIcon(isPowered), btText(isPowered, devices)];
+ return (
+
+
+
+
+ );
}
- return [btIcon(isPowered)];
+
+ return ;
},
);
@@ -101,4 +107,13 @@ const Bluetooth = (): BarBoxChild => {
};
};
+interface BluetoothIconProps {
+ isPowered: boolean;
+}
+
+interface BluetoothLabelProps {
+ isPowered: boolean;
+ devices: AstalBluetooth.Device[];
+}
+
export { Bluetooth };
diff --git a/src/components/bar/modules/cava/helpers.ts b/src/components/bar/modules/cava/helpers.ts
new file mode 100644
index 0000000..66c45db
--- /dev/null
+++ b/src/components/bar/modules/cava/helpers.ts
@@ -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): 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);
+ },
+ );
+}
diff --git a/src/components/bar/modules/cava/index.tsx b/src/components/bar/modules/cava/index.tsx
new file mode 100644
index 0000000..063127a
--- /dev/null
+++ b/src/components/bar/modules/cava/index.tsx
@@ -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 = 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();
+ },
+ },
+ });
+};
diff --git a/src/components/bar/modules/clock/index.tsx b/src/components/bar/modules/clock/index.tsx
index f490dff..11bd6a0 100644
--- a/src/components/bar/modules/clock/index.tsx
+++ b/src/components/bar/modules/clock/index.tsx
@@ -13,8 +13,8 @@ const { style } = options.theme.bar.buttons;
const time = Variable.derive([systemTime, format], (c, f) => c.format(f) || '');
const Clock = (): BarBoxChild => {
- const clockTime = ;
- const clockIcon = ;
+ const ClockTime = (): JSX.Element => ;
+ const ClockIcon = (): JSX.Element => ;
const componentClassName = Variable.derive(
[bind(style), bind(showIcon), bind(showTime)],
@@ -31,11 +31,16 @@ const Clock = (): BarBoxChild => {
const componentChildren = Variable.derive([bind(showIcon), bind(showTime)], (shIcn, shTm) => {
if (shIcn && !shTm) {
- return [clockIcon];
+ return ;
} else if (shTm && !shIcn) {
- return [clockTime];
+ return ;
}
- return [clockIcon, clockTime];
+ return (
+
+
+
+
+ );
});
const component = (
diff --git a/src/components/bar/modules/network/index.tsx b/src/components/bar/modules/network/index.tsx
index d7cd3c4..9537716 100644
--- a/src/components/bar/modules/network/index.tsx
+++ b/src/components/bar/modules/network/index.tsx
@@ -23,7 +23,7 @@ const Network = (): BarBoxChild => {
},
);
- const networkIcon = ;
+ const NetworkIcon = (): JSX.Element => ;
const networkLabel = Variable.derive(
[
@@ -66,8 +66,6 @@ const Network = (): BarBoxChild => {
},
);
- const componentChildren = [networkIcon, networkLabel()];
-
const component = (
{
componentClassName.drop();
}}
>
- {componentChildren}
+
+ {networkLabel()}
);
diff --git a/src/components/bar/modules/notifications/index.tsx b/src/components/bar/modules/notifications/index.tsx
index 3731a22..aa91f55 100644
--- a/src/components/bar/modules/notifications/index.tsx
+++ b/src/components/bar/modules/notifications/index.tsx
@@ -44,7 +44,7 @@ export const Notifications = (): BarBoxChild => {
) => {
const filteredNotifications = filterNotifications(notif, ignoredNotifs);
- const notifIcon = (
+ const NotifIcon = (): JSX.Element => (