aboutsummaryrefslogtreecommitdiff
path: root/inter.py
diff options
context:
space:
mode:
Diffstat (limited to 'inter.py')
-rw-r--r--inter.py369
1 files changed, 313 insertions, 56 deletions
diff --git a/inter.py b/inter.py
index 4a4cd5b..c316663 100644
--- a/inter.py
+++ b/inter.py
@@ -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