192 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			192 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { bind } from "astal";
 | 
						|
import { Gtk } from "astal/gtk4";
 | 
						|
import AstalWp from "gi://AstalWp";
 | 
						|
 | 
						|
const wp = AstalWp.get_default()!;
 | 
						|
 | 
						|
const AudioModule = () => {
 | 
						|
    const setVolumeSpeaker = (volume: number) => {
 | 
						|
        wp.defaultSpeaker.set_volume(volume / 100);
 | 
						|
    };
 | 
						|
 | 
						|
    const setVolumeMicrophone = (volume: number) => {
 | 
						|
        wp.defaultMicrophone.set_volume(volume / 100);
 | 
						|
    };
 | 
						|
 | 
						|
    const speakerSelector = SinkSelectPopover(AstalWp.MediaClass.AUDIO_SINK);
 | 
						|
    const micSelector = SinkSelectPopover(AstalWp.MediaClass.AUDIO_SOURCE);
 | 
						|
 | 
						|
    return (
 | 
						|
        <box cssClasses={["audio-box"]} vertical>
 | 
						|
            <box hexpand vexpand>
 | 
						|
                <button
 | 
						|
                    onClicked={() =>
 | 
						|
                        wp.defaultSpeaker.set_mute(
 | 
						|
                            !wp.defaultSpeaker.get_mute(),
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    tooltipText={"Mute audio output"}
 | 
						|
                    child={
 | 
						|
                        <image
 | 
						|
                            iconName={bind(wp.defaultSpeaker, "volumeIcon")}
 | 
						|
                            marginEnd={3}
 | 
						|
                        ></image>
 | 
						|
                    }
 | 
						|
                ></button>
 | 
						|
                <label
 | 
						|
                    label={bind(wp.defaultSpeaker, "volume").as(
 | 
						|
                        v => Math.round(100 * v) + "%",
 | 
						|
                    )}
 | 
						|
                ></label>
 | 
						|
                <slider
 | 
						|
                    value={bind(wp.defaultSpeaker, "volume").as(v => 100 * v)}
 | 
						|
                    max={100}
 | 
						|
                    min={0}
 | 
						|
                    step={1}
 | 
						|
                    hexpand
 | 
						|
                    vexpand
 | 
						|
                    onChangeValue={self => setVolumeSpeaker(self.value)}
 | 
						|
                ></slider>
 | 
						|
                <button
 | 
						|
                    cssClasses={["sink-select-button"]}
 | 
						|
                    tooltipText={"Pick audio output"}
 | 
						|
                    child={
 | 
						|
                        <box>
 | 
						|
                            <image iconName={"speaker-symbolic"}></image>
 | 
						|
                            {speakerSelector}
 | 
						|
                        </box>
 | 
						|
                    }
 | 
						|
                    onClicked={() => speakerSelector.popup()}
 | 
						|
                ></button>
 | 
						|
            </box>
 | 
						|
            <box hexpand vexpand>
 | 
						|
                <button
 | 
						|
                    onClicked={() =>
 | 
						|
                        wp.defaultMicrophone.set_mute(
 | 
						|
                            !wp.defaultMicrophone.get_mute(),
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    tooltipText={"Mute audio input"}
 | 
						|
                    child={
 | 
						|
                        <image
 | 
						|
                            iconName={bind(wp.defaultMicrophone, "volumeIcon")}
 | 
						|
                            marginEnd={3}
 | 
						|
                        ></image>
 | 
						|
                    }
 | 
						|
                ></button>
 | 
						|
                <label
 | 
						|
                    label={bind(wp.defaultMicrophone, "volume").as(
 | 
						|
                        v => Math.round(100 * v) + "%",
 | 
						|
                    )}
 | 
						|
                ></label>
 | 
						|
                <slider
 | 
						|
                    value={bind(wp.defaultMicrophone, "volume").as(
 | 
						|
                        v => 100 * v,
 | 
						|
                    )}
 | 
						|
                    max={100}
 | 
						|
                    min={0}
 | 
						|
                    step={1}
 | 
						|
                    hexpand
 | 
						|
                    vexpand
 | 
						|
                    onChangeValue={self => setVolumeMicrophone(self.value)}
 | 
						|
                ></slider>
 | 
						|
                <button
 | 
						|
                    cssClasses={["sink-select-button"]}
 | 
						|
                    tooltipText={"Select audio input"}
 | 
						|
                    child={
 | 
						|
                        <box>
 | 
						|
                            <image iconName={"microphone"}></image>
 | 
						|
                            {micSelector}
 | 
						|
                        </box>
 | 
						|
                    }
 | 
						|
                    onClicked={() => micSelector.popup()}
 | 
						|
                ></button>
 | 
						|
            </box>
 | 
						|
        </box>
 | 
						|
    );
 | 
						|
};
 | 
						|
 | 
						|
const SinkPicker = (type: AstalWp.MediaClass) => {
 | 
						|
    const devices = bind(wp, "nodes");
 | 
						|
    wp.connect("ready", () => {
 | 
						|
        const dev = wp.get_nodes()!
 | 
						|
        for (let i = 0; i < dev.length; i++) {
 | 
						|
            const d = dev[i];
 | 
						|
        }
 | 
						|
    })
 | 
						|
 | 
						|
    return (
 | 
						|
        <box vertical>
 | 
						|
            <label
 | 
						|
                label={`Available Audio ${type === AstalWp.MediaClass.AUDIO_SINK ? "Output" : type === AstalWp.MediaClass.AUDIO_SOURCE ? "Input" : ""} Devices`}
 | 
						|
                cssClasses={[ 'title-2' ]}
 | 
						|
            ></label>
 | 
						|
            <Gtk.Separator marginBottom={5} marginTop={3}></Gtk.Separator>
 | 
						|
            <box vertical cssClasses={["sink-picker"]}>
 | 
						|
                {devices.as(d => {
 | 
						|
                    return d.map(device => {
 | 
						|
                        if (device.get_media_class() !== type) {
 | 
						|
                            return <box cssClasses={[ 'empty' ]}></box>;
 | 
						|
                        }
 | 
						|
                        return (
 | 
						|
                            <button
 | 
						|
                                cssClasses={bind(device, "id").as(id => {
 | 
						|
                                    if (
 | 
						|
                                        id ===
 | 
						|
                                        (type ===
 | 
						|
                                        AstalWp.MediaClass.AUDIO_SINK
 | 
						|
                                            ? wp.defaultSpeaker.id
 | 
						|
                                            : type ===
 | 
						|
                                                AstalWp.MediaClass
 | 
						|
                                                    .AUDIO_SOURCE
 | 
						|
                                              ? wp.defaultMicrophone.id
 | 
						|
                                              : "")
 | 
						|
                                    ) {
 | 
						|
                                        return [
 | 
						|
                                            "sink-option",
 | 
						|
                                            "currently-selected-sink-option",
 | 
						|
                                        ];
 | 
						|
                                    } else {
 | 
						|
                                        return ["sink-option"];
 | 
						|
                                    }
 | 
						|
                                })}
 | 
						|
                                child={
 | 
						|
                                    <box halign={Gtk.Align.START}>
 | 
						|
                                        <image
 | 
						|
                                            iconName={bind(device, "icon").as(
 | 
						|
                                                icon => icon,
 | 
						|
                                            )}
 | 
						|
                                            marginEnd={3}
 | 
						|
                                        ></image>
 | 
						|
                                        <label
 | 
						|
                                            label={bind(
 | 
						|
                                                device,
 | 
						|
                                                "description",
 | 
						|
                                            ).as(t => t ?? "")}
 | 
						|
                                        ></label>
 | 
						|
                                    </box>
 | 
						|
                                }
 | 
						|
                                onClicked={() => {
 | 
						|
                                    device.set_is_default(true);
 | 
						|
                                }}
 | 
						|
                            ></button>
 | 
						|
                        );
 | 
						|
                    });
 | 
						|
                })}
 | 
						|
            </box>
 | 
						|
        </box>
 | 
						|
    );
 | 
						|
};
 | 
						|
 | 
						|
const SinkSelectPopover = (type: AstalWp.MediaClass) => {
 | 
						|
    const popover = new Gtk.Popover();
 | 
						|
 | 
						|
    popover.set_child(SinkPicker(type));
 | 
						|
 | 
						|
    return popover;
 | 
						|
};
 | 
						|
 | 
						|
export default {
 | 
						|
    AudioModule,
 | 
						|
};
 |