diff options
Diffstat (limited to 'inter.py')
-rw-r--r-- | inter.py | 369 |
1 files changed, 313 insertions, 56 deletions
@@ -1,56 +1,313 @@ -from collections import namedtuple -import pacmd - -Sink = namedtuple("Sink", - ["name", # string - "description", # string - "index", # int - "state", # string - "muted", # bool - "volume"]) # string -SinkInput = namedtuple("SinkInput", - ["name", # string - "driver", # string - "sink"]) # int - -def list_sinks(): - res = pacmd.list_sinks() - assert len(res.sections) == 1 - assert res.sections[0].name == "sink" - - ret = [] - for item in res.sections[0].items: - muted = item.children["muted"].value - if muted == "yes": muted = True - elif muted == "no": muted = False - else: assert False - - sink = Sink( - item.children["name"].value, - item.children["properties"].children["device.description"].value, - item.index, - item.children["state"].value, - muted, - item.children["volume"].value) - ret.append(sink) - - return ret - -def list_sink_inputs(): - res = pacmd.list_sink_inputs() - assert len(res.sections) == 1 - assert res.sections[0].name == "input" - - ret = [] - for item in res.sections[0].items: - name = item.children["properties"].children["media.name"].value - if "application.process.binary" in item.children["properties"].children: - name += " (" + item.children["properties"].children["application.process.binary"].value + ")" - - si = SinkInput( - name, - item.children["driver"].value, - int(item.children["sink"].value.split()[0])) - ret.append(si) - - return ret +import sys, os, atexit +import pacmd, pa +try: + sys.path.append(os.path.dirname(os.path.realpath(__file__))) + import termio as T +except Exception as e: + print("Place termio.py and libtermio.so from the github.com/tomsmeding/termio project in this directory") + print(e) + sys.exit(1) + +__all__ = ["start", "end", "mainloop", "update"] + +inps = None +sinks = None +sel = (0, 0) +prompt_y = 0 # updated by redraw() + +MENU_MAIN, MENU_VOLUME, NUM_MENUS = range(3) +menu = MENU_MAIN + +menutext = [0] * NUM_MENUS +menuopts = [0] * NUM_MENUS + +menutext[MENU_MAIN] = "" +menuopts[MENU_MAIN] = [ + ("(q)uit", [0,1]), + ("(v)olume", [0,1]), + ("(d)efault", [1]), + ("(m)ove", [0]) +] + +menutext[MENU_VOLUME] = "Change volume: <- / -> (alt: 1%) (m)ute [q/esc: return]" +menuopts[MENU_VOLUME] = [] + + +def start(): + T.initscreen() + atexit.register(T.endscreen) + T.initkeyboard(False) + atexit.register(T.endkeyboard) + + redraw() + +def end(): + T.endkeyboard() + atexit.unregister(T.endkeyboard) + T.endscreen() + atexit.unregister(T.endscreen) + +def mainloop(): + global sel, menu + while True: + update() + if len([inps, sinks][sel[0]]) == 0 and len([sinks, inps][sel[0]]) != 0: + sel = (1 - sel[0], sel[1]) + redraw() + + key = T.tgetkey() + + if menu != MENU_MAIN and (key == T.KEY_ESC or key == ord('q')): + menu = MENU_MAIN + + elif menu == MENU_MAIN: + if key == T.KEY_DOWN: + if sel[0] == 0 and sel[1] >= len(inps) - 1: + if len(sinks) > 0: + sel = (1, 0) + elif sel[0] == 1 and sel[1] >= len(sinks) - 1: + pass + else: + sel = (sel[0], sel[1] + 1) + + elif key == T.KEY_UP: + if sel[0] == 0 and sel[1] == 0: + pass + elif sel[0] == 1 and sel[1] == 0: + if len(inps) > 0: + sel = (0, len(inps) - 1) + else: + sel = (sel[0], sel[1] - 1) + + elif key == ord('q'): + break + + elif key == ord('v'): + thing = get_selected() + kind = "sink" if type(thing) == pa.Sink else "input" + vol = thing.volume() + if vol[0] != vol[1]: + show_message( + "Warning: current volume of this " + kind + " is asymmetric!") + + menu = MENU_VOLUME + + elif sel[0] == 1 and key == ord('d'): + wrap_pacmd(lambda: sinks[sel[1]].set_default()) + + elif sel[0] == 0 and key == ord('m'): + idx = show_prompt( + "Enter sink number to link input {} to" + .format(inps[sel[1]].index())) + + if idx is None: + continue + + try: + idx = int(idx) + except: + show_message("Invalid number!") + continue + + for i in range(len(sinks)): + if sinks[i].index() == idx: + wrap_pacmd(inps[sel[1]].move_to_sink(idx)) + break + else: + show_message("No sink found with that index!") + + elif menu == MENU_VOLUME: + thing = [inps, sinks][sel[0]][sel[1]] + + incr = 0 + if key == T.KEY_RIGHT: + incr = 0.05 + elif key == T.KEY_LEFT: + incr = -0.05 + elif key == T.KEY_ALT + T.KEY_RIGHT: + incr = 0.01 + elif key == T.KEY_ALT + T.KEY_LEFT: + incr = -0.01 + elif key == ord('m'): + thing.set_muted(not thing.muted()) + continue + else: + T.bel() + continue + + vol = sum(thing.volume()) / 2 + vol = min(1, max(0, vol + incr)) + wrap_pacmd(lambda: thing.set_volume(vol)) + + else: + assert False + +def get_selected(): + return [inps, sinks][sel[0]][sel[1]] + +def wrap_pacmd(lam): + try: + lam() + except pacmd.PacmdError as e: + show_message("An error occurred:\n" + str(e)) + +def show_message(msg): + sz = T.gettermsize() + T.fillrect(0, prompt_y, sz.w, sz.h - prompt_y, ' ') + T.moveto(0, prompt_y) + T.tprint("! " + msg + "\n[press return]") + T.redraw() + + while True: + key = T.tgetkey() + if key == T.KEY_CR or key == T.KEY_LF: + break + + T.fillrect(0, prompt_y, sz.w, sz.h - prompt_y, ' ') + +def show_prompt(msg): + sz = T.gettermsize() + T.fillrect(0, prompt_y, sz.w, sz.h - prompt_y, ' ') + T.moveto(0, prompt_y) + T.tprint(msg + "\n> ") + T.redraw() + + line = T.tgetline() + + T.fillrect(0, prompt_y, sz.w, sz.h - prompt_y, ' ') + return line + +def redraw(): + global prompt_y + if inps is None: + update() + + sz = T.gettermsize() + T.fillrect(0, 0, sz.w, sz.h, ' ') + + T.setstyle(T.Style(9, 9, False, False)) + + y = 0 + T.moveto(0, y) + T.setbold(True) + T.tprint("Sink Inputs") + T.setbold(False) + y += 1 + + for i, inp in enumerate(inps): + T.moveto(4, y) + print_input(inp, sel[0] == 0 and sel[1] == i) + y += 1 + + y += 1 + T.moveto(0, y) + T.setbold(True) + T.tprint("Sinks") + T.setbold(False) + y += 1 + + for i, sink in enumerate(sinks): + T.moveto(4, y) + print_sink(sink, sel[0] == 1 and sel[1] == i) + y += 1 + + y += 2 + + prompt_y = y + + T.moveto(0, y) + T.tprint(menutext[menu]) + first = True + for (text, groups) in menuopts[menu]: + if sel[0] in groups: + if first: first = False + else: T.tprint(" ") + T.tprint(text) + # if sel[0] not in groups: + # T.setfg(6) + # T.tprint(text) + # T.setfg(9) + + y += 1 + + T.moveto(0, y) + T.tprint("> ") + + T.redraw() + +def print_prefix(thing, selected): + if selected: + T.setbold(True) + T.tprint("> ") + T.setbold(False) + else: + T.tprint(" ") + +def fmt_volume(vol): + res = ["", ""] + for i in range(2): + res[i] = str(round(100 * vol[i])) + "%" + if vol[0] == vol[1]: + return res[0] + else: + return res[0] + "/" + res[1] + +def print_volume(thing): + T.setfg(6) + T.tprint("(vol: {}{})".format( + fmt_volume(thing.volume()), + " (MUTED)" if thing.muted() else "")) + T.setfg(9) + +def print_input(inp, selected): + print_prefix(inp, selected) + + T.setfg(3) + T.tprint("[{}]".format(inp.index())) + T.setfg(9) + + T.tprint(" ") + T.tprint(inp.name()) + + T.tprint(" ") + print_volume(inp) + + T.setfg(3) + T.tprint(" -> ") + T.tprint("[{}]".format(inp.sink())) + T.setfg(9) + +def print_sink(sink, selected): + print_prefix(sink, selected) + + T.setfg(3) + T.tprint("[{}]".format(sink.index())) + T.setfg(9) + + T.tprint(" ") + T.tprint(sink.name()) + + T.tprint(" ") + print_volume(sink) + + if sink.default(): + T.tprint(" ") + T.setbold(True) + T.tprint("[default]") + T.setbold(False) + +def update(): + global inps, sinks + + inps = pa.list_sink_inputs() + sinks = pa.list_sinks() + redraw() + + +orig_excepthook = sys.excepthook +def excepthook(exctype, value, traceback): + T.endkeyboard() + T.endscreen() + orig_excepthook(exctype, value, traceback) + +sys.excepthook = excepthook |