Files
TheaninovOS/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Volume.js

159 lines
4.4 KiB
JavaScript

import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import icons from "../../icons.js";
import FontIcon from "../../misc/FontIcon.js";
import { getAudioTypeIcon } from "../../utils.js";
import { Arrow } from "../ToggleButton.js";
import { Menu } from "../ToggleButton.js";
/** @param {'speaker' | 'microphone'=} type */
const VolumeIndicator = (type = "speaker") =>
Widget.Button({
on_clicked: () => (Audio[type].is_muted = !Audio[type].is_muted),
child: Widget.Icon().hook(Audio[type], (icon) => {
icon.icon =
type === "speaker"
? getAudioTypeIcon(Audio[type].icon_name || "")
: icons.audio.mic.high;
icon.tooltip_text = `Volume ${Math.floor(Audio[type].volume * 100)}%`;
}),
});
/** @param {'speaker' | 'microphone'=} type */
const VolumeSlider = (type = "speaker") =>
Widget.Slider({
hexpand: true,
draw_value: false,
on_change: ({ value }) => (Audio[type].volume = value),
setup: (self) =>
self.hook(Audio[type], () => {
self.value = Audio[type].volume || 0;
}),
});
export const Volume = () =>
Widget.Box({
children: [
VolumeIndicator("speaker"),
VolumeSlider("speaker"),
Widget.Box({
vpack: "center",
child: Arrow("sink-selector"),
}),
Widget.Box({
vpack: "center",
child: Arrow("app-mixer"),
visible: Audio.bind("apps").transform((a) => a.length > 0),
}),
],
});
export const Microhone = () =>
Widget.Box({
class_name: "slider horizontal",
visible: Audio.bind("recorders").transform((a) => a.length > 0),
children: [VolumeIndicator("microphone"), VolumeSlider("microphone")],
});
/** @param {import('types/service/audio').Stream} stream */
const MixerItem = (stream) =>
Widget.Box({
hexpand: true,
class_name: "mixer-item horizontal",
children: [
Widget.Icon({
tooltip_text: stream.bind("name").transform((n) => n || ""),
icon: stream.bind("name").transform((n) => {
return Utils.lookUpIcon(n || "") ? n || "" : icons.mpris.fallback;
}),
}),
Widget.Box({
vertical: true,
children: [
Widget.Label({
xalign: 0,
truncate: "end",
label: stream.bind("description").transform((d) => d || ""),
}),
Widget.Slider({
hexpand: true,
draw_value: false,
value: stream.bind("volume"),
on_change: ({ value }) => (stream.volume = value),
}),
],
}),
Widget.Label({
xalign: 1,
label: stream.bind("volume").transform((v) => `${Math.floor(v * 100)}`),
}),
],
});
/** @param {import('types/service/audio').Stream} stream */
const SinkItem = (stream) =>
Widget.Button({
hexpand: true,
on_clicked: () => (Audio.speaker = stream),
child: Widget.Box({
children: [
Widget.Icon({
icon: getAudioTypeIcon(stream.icon_name || ""),
tooltip_text: stream.icon_name,
}),
Widget.Label(
(stream.description || "").split(" ").slice(0, 4).join(" "),
),
Widget.Icon({
icon: icons.ui.tick,
hexpand: true,
hpack: "end",
visible: Audio.speaker
.bind("stream")
.transform((s) => s === stream.stream),
}),
],
}),
});
const SettingsButton = () =>
Widget.Button({
on_clicked: () => Utils.execAsync("pavucontrol"),
hexpand: true,
child: Widget.Box({
children: [Widget.Icon(icons.ui.settings), Widget.Label("Settings")],
}),
});
export const AppMixer = () =>
Menu({
name: "app-mixer",
icon: FontIcon(icons.audio.mixer),
title: Widget.Label("App Mixer"),
content: [
Widget.Box({
vertical: true,
children: Audio.bind("apps").transform((a) => a.map(MixerItem)),
}),
Widget.Separator(),
SettingsButton(),
],
});
export const SinkSelector = () =>
Menu({
name: "sink-selector",
icon: Widget.Icon(icons.audio.type.headset),
title: Widget.Label("Sink Selector"),
content: [
Widget.Box({
vertical: true,
children: Audio.bind("speakers").transform((a) => a.map(SinkItem)),
}),
Widget.Separator(),
SettingsButton(),
],
});