Added workspaces, window titles, volume, bluetooth, systray and date/time modules to the panel
This commit is contained in:
261
modules/volume/volume.js
Normal file
261
modules/volume/volume.js
Normal file
@@ -0,0 +1,261 @@
|
||||
// const audio = await Service.import("audio");
|
||||
//
|
||||
// /** @param {'speaker' | 'microphone'} type */
|
||||
// const VolumeSlider = (type = "speaker") =>
|
||||
// Widget.Slider({
|
||||
// hexpand: true,
|
||||
// drawValue: false,
|
||||
// onChange: ({ value }) => (audio[type].volume = value),
|
||||
// value: audio[type].bind("volume"),
|
||||
// });
|
||||
//
|
||||
// const speakerSlider = VolumeSlider("speaker");
|
||||
// const micSlider = VolumeSlider("microphone");
|
||||
//
|
||||
// const VolumeCtl = () => {
|
||||
// const volCtlLabel = Widget.Label({
|
||||
// class_name: "volCtlLabel",
|
||||
// label: "Volume",
|
||||
// });
|
||||
//
|
||||
// const volSliderBox = Widget.Box(
|
||||
// { class_name: "volumeSliderBox" },
|
||||
// speakerSlider,
|
||||
// );
|
||||
//
|
||||
// return Widget.Box(
|
||||
// {
|
||||
// class_name: "volumeCtlContainer",
|
||||
// vertical: true,
|
||||
// css: 'min-width: 100px'
|
||||
// },
|
||||
// volCtlLabel,
|
||||
// volSliderBox,
|
||||
// );
|
||||
// };
|
||||
//
|
||||
// export const Volume = () => {
|
||||
// return Widget.Box({
|
||||
// child: VolumeCtl(),
|
||||
// });
|
||||
// };
|
||||
import icons from "../icons/index.js";
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import {execAsync} from "resource:///com/github/Aylur/ags/utils.js";
|
||||
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
|
||||
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
|
||||
import Menu from "../menu/menu.js";
|
||||
|
||||
/** @param {string} type */
|
||||
const sorm = (type) => type === "sink" ? "speaker" : "microphone";
|
||||
/** @param {string} type */
|
||||
const sorms = (type) => type === "sink" ? "speakers" : "microphones";
|
||||
/** @param {string | null} item
|
||||
* @param {string} type */
|
||||
const iconSubstitute = (item, type) => {
|
||||
const microphoneSubstitutes = {
|
||||
"audio-headset-analog-usb": "audio-headset-symbolic",
|
||||
"audio-headset-bluetooth": "audio-headphones-symbolic",
|
||||
"audio-card-analog-usb": "audio-input-microphone-symbolic",
|
||||
"audio-card-analog-pci": "audio-input-microphone-symbolic",
|
||||
"audio-card-analog": "audio-input-microphone-symbolic",
|
||||
"camera-web-analog-usb": "camera-web-symbolic"
|
||||
};
|
||||
const substitues = {
|
||||
"audio-headset-bluetooth": "audio-headphones-symbolic",
|
||||
"audio-card-analog-usb": "audio-speakers-symbolic",
|
||||
"audio-card-analog-pci": "audio-speakers-symbolic",
|
||||
"audio-card-analog": "audio-speakers-symbolic",
|
||||
"audio-headset-analog-usb": "audio-headset-symbolic"
|
||||
};
|
||||
|
||||
if (type === "sink") {
|
||||
return substitues[item] || item;
|
||||
}
|
||||
return microphoneSubstitutes[item] || item;
|
||||
};
|
||||
|
||||
/** @param {import('types/service/audio').Stream} stream */
|
||||
const streamIconSubstiture = stream => {
|
||||
const subs = {
|
||||
"spotify": "spotify",
|
||||
"Firefox": "firefox",
|
||||
};
|
||||
return subs[stream.name] || stream.icon_name;
|
||||
};
|
||||
|
||||
/** @param {string} type */
|
||||
const TypeIndicator = (type = "sink") => Widget.Button({
|
||||
on_clicked: () => execAsync(`pactl set-${type}-mute @DEFAULT_${type.toUpperCase()}@ toggle`),
|
||||
child: Widget.Icon()
|
||||
.hook(Audio, icon => {
|
||||
if (Audio[sorm(type)])
|
||||
// @ts-ignore
|
||||
icon.icon = iconSubstitute(Audio[sorm(type)].icon_name, type);
|
||||
}, sorm(type) + "-changed")
|
||||
});
|
||||
|
||||
/** @param {string} type */
|
||||
const PercentLabel = (type = "sink") => Widget.Label({
|
||||
class_name: "audio-volume-label",
|
||||
})
|
||||
.hook(Audio, label => {
|
||||
if (Audio[sorm(type)])
|
||||
// @ts-ignore
|
||||
label.label = `${Math.floor(Audio[sorm(type)].volume * 100)}%`;
|
||||
}, sorm(type) + "-changed");
|
||||
|
||||
/** @param {string} type */
|
||||
const VolumeSlider = (type = "sink") => Widget.Slider({
|
||||
hexpand: true,
|
||||
draw_value: false,
|
||||
// @ts-ignore
|
||||
on_change: ({value}) => Audio[sorm(type)].volume = value,
|
||||
})
|
||||
.hook(Audio, slider => {
|
||||
if (!Audio[sorm(type)])
|
||||
return;
|
||||
|
||||
// @ts-ignore
|
||||
slider.sensitive = !Audio[sorm(type)].is_muted;
|
||||
// @ts-ignore
|
||||
slider.value = Audio[sorm(type)].volume;
|
||||
}, sorm(type) + "-changed");
|
||||
|
||||
/** @param {string} type */
|
||||
export const Volume = (type = "sink") => Widget.Box({
|
||||
class_name: "audio-volume-box",
|
||||
children: [
|
||||
TypeIndicator(type),
|
||||
VolumeSlider(type),
|
||||
PercentLabel(type)
|
||||
],
|
||||
});
|
||||
|
||||
/** @param {import('types/service/audio').Stream} stream */
|
||||
const MixerItem = stream => Widget.EventBox({
|
||||
on_primary_click: () => stream.is_muted = !stream.is_muted,
|
||||
on_scroll_up: () => stream.volume += 0.03,
|
||||
on_scroll_down: () => stream.volume -= 0.03,
|
||||
child: Widget.Box({
|
||||
hexpand: true,
|
||||
class_name: "mixer-item",
|
||||
children: [
|
||||
Widget.Icon({
|
||||
icon: stream.bind("icon_name").transform(() => streamIconSubstiture(stream)),
|
||||
tooltip_text: stream.bind("name").transform(name => name || "")
|
||||
}),
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
vpack: "center",
|
||||
children: [
|
||||
Widget.Box({
|
||||
children: [
|
||||
Widget.Label({
|
||||
xalign: 0,
|
||||
hexpand: true,
|
||||
class_name: "mixer-item-title",
|
||||
truncate: "end",
|
||||
label: stream.bind("description").transform(desc => desc || ""),
|
||||
}),
|
||||
Widget.Label({
|
||||
xalign: 0,
|
||||
class_name: "mixer-item-volume",
|
||||
label: stream.bind("volume").transform(volume => `${Math.floor(volume * 100)}%`)
|
||||
}),
|
||||
]
|
||||
}),
|
||||
Widget.Slider({
|
||||
hexpand: true,
|
||||
class_name: "mixer-item-slider",
|
||||
draw_value: false,
|
||||
value: stream.bind("volume"),
|
||||
on_change: ({value}) => {
|
||||
stream.volume = value;
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @returns {function(import('types/service/audio').Stream): import('types/widgets/button').default}
|
||||
*/
|
||||
const SinkItem = (type) => stream => Widget.Button({
|
||||
on_clicked: () => Audio[sorm(type)] = stream,
|
||||
child: Widget.Box({
|
||||
spacing: 5,
|
||||
children: [
|
||||
Widget.Icon({
|
||||
icon: iconSubstitute(stream.icon_name, type),
|
||||
tooltip_text: stream.icon_name,
|
||||
}),
|
||||
Widget.Label(stream.description?.split(" ").slice(0, 4).join(" ")),
|
||||
Widget.Icon({
|
||||
icon: icons.tick,
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
}).hook(Audio, icon => {
|
||||
icon.visible = Audio[sorm(type)] === stream;
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
/** @param {number} tab */
|
||||
const SettingsButton = (tab = 0) => Widget.Button({
|
||||
on_clicked: () => Hyprland.sendMessage("dispatch exec pavucontrol -t " + tab),
|
||||
child: Widget.Icon(icons.settings),
|
||||
});
|
||||
|
||||
export const AppMixer = () => Menu({
|
||||
title: "App Mixer",
|
||||
icon: icons.audio.mixer,
|
||||
content: Widget.Box({
|
||||
class_name: "app-mixer",
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({vertical: true})
|
||||
.hook(Audio, box => {
|
||||
box.children = Audio.apps.map(MixerItem);
|
||||
}, "notify::apps")
|
||||
],
|
||||
}),
|
||||
headerChild: SettingsButton(1),
|
||||
});
|
||||
|
||||
export const SinkSelector = (type = "sink") => Menu({
|
||||
title: type + " Selector",
|
||||
icon: type === "sink" ? icons.audio.type.headset : icons.audio.mic.unmuted,
|
||||
content: Widget.Box({
|
||||
class_name: "sink-selector",
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({vertical: true})
|
||||
.hook(Audio, box => {
|
||||
box.children = Array.from(Audio[sorms(type)].values()).map(SinkItem(type));
|
||||
}, "stream-added")
|
||||
.hook(Audio, box => {
|
||||
box.children = Array.from(Audio[sorms(type)].values()).map(SinkItem(type));
|
||||
}, "stream-removed")
|
||||
],
|
||||
}),
|
||||
headerChild: SettingsButton(type === "sink" ? 3 : 4),
|
||||
});
|
||||
|
||||
const AudioContent = () => Widget.Box({
|
||||
vertical: true,
|
||||
class_name: "qs-page",
|
||||
children: [
|
||||
Volume("sink"),
|
||||
Volume("source"),
|
||||
SinkSelector("sink"),
|
||||
SinkSelector("source"),
|
||||
AppMixer(),
|
||||
]
|
||||
});
|
||||
|
||||
export default AudioContent;
|
||||
Reference in New Issue
Block a user