!7 srecord AudioSource(S description, S volume, S baseVolume, bool mute, int index) {} module ListAudioSources > DynObjectTable { transient SimpleLiveValue lvVolume = floatLiveValue(); // for selected source start { set fieldsInOrder; dontPersist(); doEvery(0.0, 10.0, r updateList); } void updateList enter { LS lines = lines(backtick("pactl list sources")); new Matches m; new L list; for i over lines: if (swic(lines.get(i), "Source #")) { int idx = parseFirstInt(lines.get(i)); AudioSource as = nu(AudioSource, 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 = m.rest(); 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 { lvVolume.set(getVolume(selected())) }); lvVolume.onChange(r { AudioSource as = selected(); if (as != null && getVolume(as) != lvVolume!) { int intVal = iround(lvVolume!*65536); as.volume = str(intVal); setData_force(data); backtickToConsole("pactl set-source-volume " + as.index + " " + intVal); } }); ret centerAndSouthWithMargins( centerAndEastWithMargin(c, vstackWithSpacing( tableDependentButton_extraCondition(table, "Mute", rThread { backtickToConsole("pactl set-source-mute " + selected().index + " 1"); updateList(); }, func -> bool { !selected().mute }), tableDependentButton_extraCondition(table, "Unmute", rThread { backtickToConsole("pactl set-source-mute " + selected().index + " 0"); updateList(); }, func -> bool { selected().mute }), )), jLiveValueSlider(lvVolume)); } float getVolume(AudioSource as) { ret as == null ? 0 : parseFirstInt(as.volume)/65536f; } }