sidebar: audio device selector module
This commit is contained in:
parent
11c8a14a22
commit
72e349e368
3 changed files with 239 additions and 0 deletions
|
|
@ -507,6 +507,116 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.device-selector {
|
||||||
|
@include lib.spacing(10, true);
|
||||||
|
|
||||||
|
.selector {
|
||||||
|
@include lib.rounded(20);
|
||||||
|
|
||||||
|
background-color: color.change(scheme.$surface1, $alpha: 0.4);
|
||||||
|
padding: lib.s(10) lib.s(15);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: lib.s(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
margin-bottom: lib.s(8);
|
||||||
|
margin-top: lib.s(5);
|
||||||
|
background-color: if(scheme.$light, scheme.$overlay1, scheme.$overlay0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
color: scheme.$subtext0;
|
||||||
|
|
||||||
|
@include lib.spacing(3, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
.device {
|
||||||
|
@include lib.spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
color: scheme.$text;
|
||||||
|
|
||||||
|
@include lib.spacing(10);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: lib.s(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sublabel {
|
||||||
|
color: scheme.$subtext0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
@include lib.element-decel;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: scheme.$subtext1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: scheme.$text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream {
|
||||||
|
@include lib.rounded(20);
|
||||||
|
@include lib.element-decel;
|
||||||
|
|
||||||
|
background-color: color.change(scheme.$surface1, $alpha: 0.4);
|
||||||
|
padding: lib.s(10) lib.s(15);
|
||||||
|
|
||||||
|
@include lib.spacing(5);
|
||||||
|
|
||||||
|
&.playing {
|
||||||
|
background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 50%), $alpha: 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: lib.s(28);
|
||||||
|
margin-right: lib.s(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sublabel {
|
||||||
|
font-size: lib.s(14);
|
||||||
|
color: scheme.$subtext0;
|
||||||
|
}
|
||||||
|
|
||||||
|
trough {
|
||||||
|
@include lib.rounded(10);
|
||||||
|
|
||||||
|
min-width: lib.s(100);
|
||||||
|
min-height: lib.s(10);
|
||||||
|
background-color: color.change(scheme.$error, $alpha: 0.3);
|
||||||
|
|
||||||
|
fill {
|
||||||
|
@include lib.rounded(10);
|
||||||
|
|
||||||
|
background-color: color.change(scheme.$overlay0, $alpha: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
highlight {
|
||||||
|
@include lib.rounded(10);
|
||||||
|
|
||||||
|
background-color: scheme.$subtext1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > button {
|
||||||
|
@include media-button;
|
||||||
|
|
||||||
|
font-size: lib.s(18);
|
||||||
|
min-width: lib.s(20);
|
||||||
|
min-height: lib.s(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.networks {
|
.networks {
|
||||||
.list {
|
.list {
|
||||||
@include lib.spacing(10, true);
|
@include lib.spacing(10, true);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import DeviceSelector from "./modules/deviceselector";
|
||||||
import Media from "./modules/media";
|
import Media from "./modules/media";
|
||||||
import Streams from "./modules/streams";
|
import Streams from "./modules/streams";
|
||||||
|
|
||||||
|
|
@ -6,5 +7,7 @@ export default () => (
|
||||||
<Media />
|
<Media />
|
||||||
<box className="separator" />
|
<box className="separator" />
|
||||||
<Streams />
|
<Streams />
|
||||||
|
<box className="separator" />
|
||||||
|
<DeviceSelector />
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
126
src/modules/sidebar/modules/deviceselector.tsx
Normal file
126
src/modules/sidebar/modules/deviceselector.tsx
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
import { bind, execAsync, Variable, type Binding } from "astal";
|
||||||
|
import { Astal, Gtk } from "astal/gtk3";
|
||||||
|
import AstalWp from "gi://AstalWp";
|
||||||
|
|
||||||
|
const Device = ({
|
||||||
|
input,
|
||||||
|
defaultDevice,
|
||||||
|
showDropdown,
|
||||||
|
device,
|
||||||
|
}: {
|
||||||
|
input?: boolean;
|
||||||
|
defaultDevice: Binding<AstalWp.Endpoint>;
|
||||||
|
showDropdown: Variable<boolean>;
|
||||||
|
device: AstalWp.Endpoint;
|
||||||
|
}) => (
|
||||||
|
<button
|
||||||
|
visible={defaultDevice.get().id !== device.id}
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={() => {
|
||||||
|
execAsync(`wpctl set-default ${device.id}`).catch(console.error);
|
||||||
|
showDropdown.set(false);
|
||||||
|
}}
|
||||||
|
setup={self => {
|
||||||
|
let last: { d: AstalWp.Endpoint; id: number } | null = {
|
||||||
|
d: defaultDevice.get(),
|
||||||
|
id: defaultDevice
|
||||||
|
.get()
|
||||||
|
.connect("notify::id", () => self.set_visible(defaultDevice.get().id !== device.id)),
|
||||||
|
};
|
||||||
|
self.hook(defaultDevice, (_, d) => {
|
||||||
|
last?.d.disconnect(last.id);
|
||||||
|
self.set_visible(d.id !== device.id);
|
||||||
|
last = {
|
||||||
|
d,
|
||||||
|
id: d.connect("notify::id", () => self.set_visible(d.id !== device.id)),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
self.connect("destroy", () => last?.d.disconnect(last.id));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<box className="device">
|
||||||
|
{bind(device, "icon").as(i =>
|
||||||
|
Astal.Icon.lookup_icon(i) ? (
|
||||||
|
<icon className="icon" icon={device.icon} />
|
||||||
|
) : (
|
||||||
|
<label className="icon" label={input ? "mic" : "media_output"} />
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<label label={bind(device, "description")} />
|
||||||
|
</box>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const DefaultDevice = ({ input, device }: { input?: boolean; device: AstalWp.Endpoint }) => (
|
||||||
|
<box className="selected">
|
||||||
|
<label className="icon" label={input ? "mic" : "media_output"} />
|
||||||
|
<box vertical>
|
||||||
|
<label
|
||||||
|
truncate
|
||||||
|
xalign={0}
|
||||||
|
label={bind(device, "description").as(d => (input ? "[In] " : "[Out] ") + (d ?? "Unknown"))}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
xalign={0}
|
||||||
|
className="sublabel"
|
||||||
|
label={bind(device, "volume").as(v => `Volume ${Math.round(v * 100)}%`)}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Selector = ({ input, audio }: { input?: boolean; audio: AstalWp.Audio }) => {
|
||||||
|
const showDropdown = Variable(false);
|
||||||
|
const defaultDevice = bind(audio, input ? "defaultMicrophone" : "defaultSpeaker");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box vertical className="selector">
|
||||||
|
<revealer
|
||||||
|
transitionType={Gtk.RevealerTransitionType.SLIDE_UP}
|
||||||
|
transitionDuration={150}
|
||||||
|
revealChild={bind(showDropdown)}
|
||||||
|
>
|
||||||
|
<box vertical className="list">
|
||||||
|
{bind(audio, input ? "microphones" : "speakers").as(ds =>
|
||||||
|
ds.map(d => (
|
||||||
|
<Device
|
||||||
|
input={input}
|
||||||
|
defaultDevice={defaultDevice}
|
||||||
|
showDropdown={showDropdown}
|
||||||
|
device={d}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
<box className="separator" />
|
||||||
|
</box>
|
||||||
|
</revealer>
|
||||||
|
<button cursor="pointer" onClick={() => showDropdown.set(!showDropdown.get())}>
|
||||||
|
{defaultDevice.as(d => (
|
||||||
|
<DefaultDevice input={input} device={d} />
|
||||||
|
))}
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NoWp = () => (
|
||||||
|
<box vexpand homogeneous>
|
||||||
|
<box vertical valign={Gtk.Align.CENTER}>
|
||||||
|
<label label="Device selector unavailable" />
|
||||||
|
<label className="no-wp-prompt" label="WirePlumber is required for this module" />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const audio = AstalWp.get_default()?.get_audio();
|
||||||
|
|
||||||
|
if (!audio) return <NoWp />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box vertical className="device-selector">
|
||||||
|
<Selector input audio={audio} />
|
||||||
|
<Selector audio={audio} />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue