hyprpanel
This commit is contained in:
1 parent
10080a83ee
commit
5151ac24da
322 files changed
+38276
No files matched your search
@@ -0,0 +1,75 @@
|
||||
const audio = await Service.import('audio');
|
||||
import { getIcon } from '../utils.js';
|
||||
import Box from 'types/widgets/box.js';
|
||||
import { Attribute, Child } from 'lib/types/widget.js';
|
||||
|
||||
const renderActiveInput = (): Box<Child, Attribute>[] => {
|
||||
return [
|
||||
Widget.Box({
|
||||
class_name: 'menu-slider-container input',
|
||||
children: [
|
||||
Widget.Button({
|
||||
vexpand: false,
|
||||
vpack: 'end',
|
||||
setup: (self) => {
|
||||
const updateClass = (): void => {
|
||||
const mic = audio.microphone;
|
||||
const className = `menu-active-button input ${mic.is_muted ? 'muted' : ''}`;
|
||||
self.class_name = className;
|
||||
};
|
||||
|
||||
self.hook(audio.microphone, updateClass, 'notify::is-muted');
|
||||
},
|
||||
on_primary_click: () => (audio.microphone.is_muted = !audio.microphone.is_muted),
|
||||
child: Widget.Icon({
|
||||
class_name: 'menu-active-icon input',
|
||||
setup: (self) => {
|
||||
const updateIcon = (): void => {
|
||||
const isMicMuted =
|
||||
audio.microphone.is_muted !== null ? audio.microphone.is_muted : true;
|
||||
|
||||
if (audio.microphone.volume > 0) {
|
||||
self.icon = getIcon(audio.microphone.volume, isMicMuted)['mic'];
|
||||
} else {
|
||||
self.icon = getIcon(100, true)['mic'];
|
||||
}
|
||||
};
|
||||
self.hook(audio.microphone, updateIcon, 'notify::volume');
|
||||
self.hook(audio.microphone, updateIcon, 'notify::is-muted');
|
||||
},
|
||||
}),
|
||||
}),
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
class_name: 'menu-active input',
|
||||
hpack: 'start',
|
||||
truncate: 'end',
|
||||
wrap: true,
|
||||
label: audio.bind('microphone').as((v) => {
|
||||
return v.description === null ? 'No input device found...' : v.description;
|
||||
}),
|
||||
}),
|
||||
Widget.Slider({
|
||||
value: audio.microphone.bind('volume').as((v) => v),
|
||||
class_name: 'menu-active-slider menu-slider inputs',
|
||||
draw_value: false,
|
||||
hexpand: true,
|
||||
min: 0,
|
||||
max: 1,
|
||||
onChange: ({ value }) => (audio.microphone.volume = value),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Widget.Label({
|
||||
class_name: 'menu-active-percentage input',
|
||||
vpack: 'end',
|
||||
label: audio.microphone.bind('volume').as((v) => `${Math.round(v * 100)}%`),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
||||
export { renderActiveInput };
|
||||
@@ -0,0 +1,68 @@
|
||||
const audio = await Service.import('audio');
|
||||
import { getIcon } from '../utils.js';
|
||||
import Box from 'types/widgets/box.js';
|
||||
import { Attribute, Child } from 'lib/types/widget.js';
|
||||
|
||||
const renderActivePlayback = (): Box<Child, Attribute>[] => {
|
||||
return [
|
||||
Widget.Box({
|
||||
class_name: 'menu-slider-container playback',
|
||||
children: [
|
||||
Widget.Button({
|
||||
vexpand: false,
|
||||
vpack: 'end',
|
||||
setup: (self) => {
|
||||
const updateClass = (): void => {
|
||||
const spkr = audio.speaker;
|
||||
const className = `menu-active-button playback ${spkr.is_muted ? 'muted' : ''}`;
|
||||
self.class_name = className;
|
||||
};
|
||||
|
||||
self.hook(audio.speaker, updateClass, 'notify::is-muted');
|
||||
},
|
||||
on_primary_click: () => (audio.speaker.is_muted = !audio.speaker.is_muted),
|
||||
child: Widget.Icon({
|
||||
class_name: 'menu-active-icon playback',
|
||||
setup: (self) => {
|
||||
const updateIcon = (): void => {
|
||||
const isSpeakerMuted = audio.speaker.is_muted !== null ? audio.speaker.is_muted : true;
|
||||
self.icon = getIcon(audio.speaker.volume, isSpeakerMuted)['spkr'];
|
||||
};
|
||||
self.hook(audio.speaker, updateIcon, 'notify::volume');
|
||||
self.hook(audio.speaker, updateIcon, 'notify::is-muted');
|
||||
},
|
||||
}),
|
||||
}),
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
class_name: 'menu-active playback',
|
||||
hpack: 'start',
|
||||
truncate: 'end',
|
||||
expand: true,
|
||||
wrap: true,
|
||||
label: audio.bind('speaker').as((v) => v.description || ''),
|
||||
}),
|
||||
Widget.Slider({
|
||||
value: audio['speaker'].bind('volume'),
|
||||
class_name: 'menu-active-slider menu-slider playback',
|
||||
draw_value: false,
|
||||
hexpand: true,
|
||||
min: 0,
|
||||
max: 1,
|
||||
onChange: ({ value }) => (audio.speaker.volume = value),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Widget.Label({
|
||||
vpack: 'end',
|
||||
class_name: 'menu-active-percentage playback',
|
||||
label: audio.speaker.bind('volume').as((v) => `${Math.round(v * 100)}%`),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
||||
export { renderActivePlayback };
|
||||
@@ -0,0 +1,41 @@
|
||||
import { renderActiveInput } from './SelectedInput.js';
|
||||
import { renderActivePlayback } from './SelectedPlayback.js';
|
||||
import Box from 'types/widgets/box.js';
|
||||
import { Attribute, Child } from 'lib/types/widget.js';
|
||||
|
||||
const activeDevices = (): Box<Child, Attribute> => {
|
||||
return Widget.Box({
|
||||
class_name: 'menu-section-container volume',
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
class_name: 'menu-label-container volume selected',
|
||||
hpack: 'fill',
|
||||
child: Widget.Label({
|
||||
class_name: 'menu-label audio volume',
|
||||
hexpand: true,
|
||||
hpack: 'start',
|
||||
label: 'Volume',
|
||||
}),
|
||||
}),
|
||||
Widget.Box({
|
||||
class_name: 'menu-items-section selected',
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
class_name: 'menu-active-container playback',
|
||||
vertical: true,
|
||||
children: renderActivePlayback(),
|
||||
}),
|
||||
Widget.Box({
|
||||
class_name: 'menu-active-container input',
|
||||
vertical: true,
|
||||
children: renderActiveInput(),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export { activeDevices };
|
||||
@@ -0,0 +1,66 @@
|
||||
const audio = await Service.import('audio');
|
||||
import { InputDevices } from 'lib/types/audio';
|
||||
import { Stream } from 'types/service/audio';
|
||||
|
||||
const renderInputDevices = (inputDevices: Stream[]): InputDevices => {
|
||||
if (inputDevices.length === 0) {
|
||||
return [
|
||||
Widget.Button({
|
||||
class_name: `menu-unfound-button input`,
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Box({
|
||||
hpack: 'start',
|
||||
children: [
|
||||
Widget.Label({
|
||||
class_name: 'menu-button-name input',
|
||||
label: 'No input devices found...',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
];
|
||||
}
|
||||
return inputDevices.map((device) => {
|
||||
return Widget.Button({
|
||||
on_primary_click: () => (audio.microphone = device),
|
||||
class_name: `menu-button audio input ${device}`,
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Box({
|
||||
hpack: 'start',
|
||||
children: [
|
||||
Widget.Label({
|
||||
wrap: true,
|
||||
class_name: audio.microphone
|
||||
.bind('description')
|
||||
.as((v) =>
|
||||
device.description === v
|
||||
? 'menu-button-icon active input txt-icon'
|
||||
: 'menu-button-icon input txt-icon',
|
||||
),
|
||||
label: '',
|
||||
}),
|
||||
Widget.Label({
|
||||
truncate: 'end',
|
||||
wrap: true,
|
||||
class_name: audio.microphone
|
||||
.bind('description')
|
||||
.as((v) =>
|
||||
device.description === v
|
||||
? 'menu-button-name active input'
|
||||
: 'menu-button-name input',
|
||||
),
|
||||
label: device.description,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export { renderInputDevices };
|
||||
@@ -0,0 +1,60 @@
|
||||
const audio = await Service.import('audio');
|
||||
import { PlaybackDevices } from 'lib/types/audio';
|
||||
import { Stream } from 'types/service/audio';
|
||||
|
||||
const renderPlaybacks = (playbackDevices: Stream[]): PlaybackDevices => {
|
||||
return playbackDevices.map((device) => {
|
||||
if (device.description === 'Dummy Output') {
|
||||
return Widget.Box({
|
||||
class_name: 'menu-unfound-button playback',
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Label({
|
||||
class_name: 'menu-button-name playback',
|
||||
label: 'No playback devices found...',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
return Widget.Button({
|
||||
class_name: `menu-button audio playback ${device}`,
|
||||
on_primary_click: () => (audio.speaker = device),
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Box({
|
||||
hpack: 'start',
|
||||
children: [
|
||||
Widget.Label({
|
||||
truncate: 'end',
|
||||
wrap: true,
|
||||
class_name: audio.speaker
|
||||
.bind('description')
|
||||
.as((v) =>
|
||||
device.description === v
|
||||
? 'menu-button-icon active playback txt-icon'
|
||||
: 'menu-button-icon playback txt-icon',
|
||||
),
|
||||
label: '',
|
||||
}),
|
||||
Widget.Label({
|
||||
truncate: 'end',
|
||||
wrap: true,
|
||||
class_name: audio.speaker
|
||||
.bind('description')
|
||||
.as((v) =>
|
||||
device.description === v
|
||||
? 'menu-button-name active playback'
|
||||
: 'menu-button-name playback',
|
||||
),
|
||||
label: device.description,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export { renderPlaybacks };
|
||||
@@ -0,0 +1,72 @@
|
||||
const audio = await Service.import('audio');
|
||||
import { BoxWidget } from 'lib/types/widget.js';
|
||||
import { renderInputDevices } from './InputDevices.js';
|
||||
import { renderPlaybacks } from './PlaybackDevices.js';
|
||||
|
||||
const availableDevices = (): BoxWidget => {
|
||||
return Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
class_name: 'menu-section-container playback',
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
class_name: 'menu-label-container playback',
|
||||
hpack: 'fill',
|
||||
child: Widget.Label({
|
||||
class_name: 'menu-label audio playback',
|
||||
hexpand: true,
|
||||
hpack: 'start',
|
||||
label: 'Playback Devices',
|
||||
}),
|
||||
}),
|
||||
Widget.Box({
|
||||
class_name: 'menu-items-section playback',
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
class_name: 'menu-container playback',
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: audio.bind('speakers').as((v) => renderPlaybacks(v)),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Widget.Box({
|
||||
class_name: 'menu-label-container input',
|
||||
hpack: 'fill',
|
||||
child: Widget.Label({
|
||||
class_name: 'menu-label audio input',
|
||||
hexpand: true,
|
||||
hpack: 'start',
|
||||
label: 'Input Devices',
|
||||
}),
|
||||
}),
|
||||
Widget.Box({
|
||||
class_name: 'menu-items-section input',
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
class_name: 'menu-container input',
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: audio.bind('microphones').as((v) => renderInputDevices(v)),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export { availableDevices };
|
||||
@@ -0,0 +1,25 @@
|
||||
import Window from 'types/widgets/window.js';
|
||||
import DropdownMenu from '../shared/dropdown/index.js';
|
||||
import { activeDevices } from './active/index.js';
|
||||
import { availableDevices } from './available/index.js';
|
||||
import { Attribute, Child } from 'lib/types/widget.js';
|
||||
import options from 'options.js';
|
||||
|
||||
export default (): Window<Child, Attribute> => {
|
||||
return DropdownMenu({
|
||||
name: 'audiomenu',
|
||||
transition: options.menus.transition.bind('value'),
|
||||
child: Widget.Box({
|
||||
class_name: 'menu-items audio',
|
||||
hpack: 'fill',
|
||||
hexpand: true,
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
hexpand: true,
|
||||
class_name: 'menu-items-container audio',
|
||||
children: [activeDevices(), availableDevices()],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
const speakerIcons = {
|
||||
101: 'audio-volume-overamplified-symbolic',
|
||||
66: 'audio-volume-high-symbolic',
|
||||
34: 'audio-volume-medium-symbolic',
|
||||
1: 'audio-volume-low-symbolic',
|
||||
0: 'audio-volume-muted-symbolic',
|
||||
} as const;
|
||||
|
||||
const inputIcons = {
|
||||
101: 'microphone-sensitivity-high-symbolic',
|
||||
66: 'microphone-sensitivity-high-symbolic',
|
||||
34: 'microphone-sensitivity-medium-symbolic',
|
||||
1: 'microphone-sensitivity-low-symbolic',
|
||||
0: 'microphone-disabled-symbolic',
|
||||
};
|
||||
|
||||
type IconVolumes = keyof typeof speakerIcons;
|
||||
|
||||
const getIcon = (audioVol: number, isMuted: boolean): Record<string, string> => {
|
||||
const thresholds: IconVolumes[] = [101, 66, 34, 1, 0];
|
||||
const icon = isMuted ? 0 : thresholds.find((threshold) => threshold <= audioVol * 100) || 0;
|
||||
|
||||
return {
|
||||
spkr: speakerIcons[icon],
|
||||
mic: inputIcons[icon],
|
||||
};
|
||||
};
|
||||
|
||||
export { getIcon };
|
||||
Reference in new issue
Block a user