!7 srecord AudioSink(S description, S volume, S baseVolume, bool mute, int index) {} cmodule ListAudioSinks > DynObjectTable { //transient SimpleLiveValue lvVolume = floatLiveValue(); // for selected sink transient JTextField tfVolume; start { set fieldsInOrder; dontPersist(); doEveryAndNow(10.0, r updateList); } void updateList enter { LS lines = lines(backtick("pactl list sinks")); new Matches m; new L list; for i over lines: if (swic(lines.get(i), "Sink #")) { int idx = parseFirstInt(lines.get(i)); AudioSink as = nu(AudioSink, index := idx, description := lines.get(i)); for (int j = i+1; j < l(lines); j++) { S line = lines.get(j); if (!startsWithSpace(line)) break; line = trim(line); if (swic_trim(line, "Description:", m)) as.description = m.rest(); else if (swic_trim(line, "Base Volume:", m)) as.baseVolume = m.rest(); else if (swic_trim(line, "Volume:", m)) as.volume = iround(parseFirstInt(m.rest())*100.0/65536) + "%"; else if (swic_trim(line, "Mute:", m)) as.mute = isYes(m.rest()); } list.add(as); } setData(list); } visualize { JComponent c = super.visualize(); onTableSelectionChanged(table, r { setText(tfVolume, selected() == null ? "" : firstIntAsString(selected().volume)) }); tfVolume = jTextField(); onEnter(tfVolume, r { if (selected() == null) ret; backtickToConsole("pactl set-sink-volume " + selected().index + " " + iround(parseFirstInt(gtt(tfVolume))/100.0*65536)); updateList(); }); ret centerAndEastWithMargins(c, vstackWithSpacing( tableDependentButton_extraCondition(table, "Mute", rThread { muteSink(selected()) }, func -> bool { !selected().mute }), tableDependentButton_extraCondition(table, "Unmute", rThread { unmuteSink(selected()) }, func -> bool { selected().mute }), jlabel("Volume:"), tfVolume , jbutton("Export as text", r { showText("Audio sink settings", exportAsText()) }) )); } // API S getSinkName(int index) { ret getString(objectWhere(data, +index), 'description); } AudioSink findSinkByName(S name) { ret objectWhereIC(data, description := name); } void muteSink(AudioSink sink) { if (sink == null) ret; backtickToConsole("pactl set-sink-mute " + sink.index + " 1"); updateList(); } void unmuteSink(AudioSink sink) { if (sink == null) ret; backtickToConsole("pactl set-sink-mute " + sink.index + " 0"); updateList(); } bool isSinkMuted(AudioSink sink) { ret sink != null && sink.mute; } S exportAsText() { ret linesMap(data(), func(AudioSink sink) -> S { (sink.mute ? "Muted audio sink" : "Audio sink") + " " + quote(sink.description) + " has volume " + parseFirstInt(sink.volume) + "." }); } }