#!/usr/bin/env python from subprocess import Popen, PIPE, STDOUT from configparser import ConfigParser, DuplicateSectionError from re import search, IGNORECASE from justpy import Div, WebPage, SetRoute, justpy def process(args): try: # with shell=True args can be a string # detached process https://stackoverflow.com/a/65900355/992129 start_new_session # https://docs.python.org/3/library/subprocess.html#popen-constructor result = Popen(args, stdout=PIPE, stderr=STDOUT, shell=True, start_new_session=True) return result.stdout.read().decode("utf-8").rstrip() except Exception as e: print(f"{e} failed!") def volume(name): return process(f'pamixer --get-volume --sink "{name}"') def volume_decrease(name): return process(f'pamixer --get-volume --sink "{name}" --decrease 5') def volume_increase(name): return process(f'pamixer --get-volume --sink "{name}" --increase 5') class Button(Div): command = None def __init__(self, **kwargs): super().__init__(**kwargs) self.classes = "bg-gray-800 hover:bg-gray-700 w-20 h-20 m-2 p-1 rounded-lg font-bold flex items-center text-center justify-center select-none" if self.command is not None: def click(self, msg): print(self.command) # string works only with shell if isinstance(self.command, (list)): # e.g.: [['pkill', 'ArdourGUI'], ['systemctl', '--user', 'restart', 'pipewire', 'pipewire-pulse'], ['ardour6', '-n', 'productive-pipewire']] if isinstance(self.command[0], (list)): [process(i) for i in self.command] else: # e.g.: ['pkill', 'ArdourGUI'] process(self.command) else: # e.g.: 'pkill ArdourGUI' process(self.command) self.on('click', click) class ButtonSound(Div): div = None name = None description = None volume = None def __init__(self, **kwargs): super().__init__(**kwargs) self.classes = "grid-rows-2" self.div = Div(classes="flex") Button(inner_html=f'{self.description}
- 5%', click=self.decrease, a=self.div) Button(inner_html=f'{self.description}
+ 5%', click=self.increase, a=self.div) self.add(self.div) self.volume = Div(text=f"Volume: {volume(self.name)}%", classes="text-center -mt-2", a=self) async def decrease(self, msg): self.volume.text = f'Volume: {volume_decrease(self.name)}%' async def increase(self, msg): self.volume.text = f'Volume: {volume_increase(self.name)}%' @SetRoute('/') def application(): wp = WebPage(title="ControlDeck", body_classes="bg-gray-900") wp.head_html = '' # div = Div(classes="flex flex-wrap", a=wp) # ButtonSound(name="Stream_sink", description="Stream sink", a=div) # div2 = Div(classes="flex flex-wrap", a=wp) # Button(text="Sleep", command='systemctl suspend', a=div2) config = ConfigParser(strict=False) volume_dict = {} button_dict = {} try: config.read('controldeck.conf') except Exception as e: print(f"{e}") #print(config.sections()) for i in config.sections(): iname = None iname = search("^([0-9]*.?)volume", i, flags=IGNORECASE) if iname is not None: id = iname.group(1)[:-1] # remove dot try: volume_dict[id] += [{'description': i[iname.end(0)+1:], 'name': config.get(i, 'name', fallback=None)}] except KeyError: volume_dict[id] = [{'description': i[iname.end(0)+1:], 'name': config.get(i, 'name', fallback=None)}] iname = search("^([0-9]*.?)button", i, flags=IGNORECASE) if iname is not None: id = iname.group(1)[:-1] # remove dot try: button_dict[id] += [{'text': i[iname.end(0)+1:], 'command': config.get(i, 'command', fallback=None)}] except KeyError: button_dict[id] = [{'text': i[iname.end(0)+1:], 'command': config.get(i, 'command', fallback=None)}] for i in volume_dict: for j in volume_dict[i]: if 'div'+i not in vars(): vars()['div'+i] = Div(classes="flex flex-wrap", a=wp) ButtonSound(name=j['name'], description=j['description'], a=eval('div'+i)) for i in button_dict: for j in button_dict[i]: if 'div'+i not in vars(): vars()['div'+i] = Div(classes="flex flex-wrap", a=wp) Button(text=j['text'], command=j['command'], a=eval('div'+i)) if not wp.components: # config not found or empty, therefore insert an empty div to not get an error Div(text="add elements in controldeck.conf", classes="flex flex-wrap", a=wp) return wp if __name__ == '__main__': justpy()