add source volume slider, add gui config flags, add gui menu
This commit is contained in:
@@ -3,6 +3,12 @@
|
||||
HTML style powered by Quasar
|
||||
|
||||
NOTE: currently buttons only updated on page reload
|
||||
|
||||
Icon string
|
||||
https://quasar.dev/vue-components/icon#webfont-usagehttps://quasar.dev/vue-components/icon#webfont-usage
|
||||
for example:
|
||||
without prefix uses material-icons https://fonts.google.com/icons?icon.set=Material+Icons
|
||||
"fas fa-" uses fontawesome-v5 https://fontawesome.com/icons
|
||||
"""
|
||||
|
||||
import sys
|
||||
@@ -321,7 +327,7 @@ class Slider(Div):
|
||||
class Volume(Div):
|
||||
# class variables
|
||||
data = {} # pulseaudio info for all sinks
|
||||
icon_muted = 'volume_mute' # default icon for muted state, 'volume_off' better for disabled or not found?
|
||||
icon_muted = 'volume_mute' # default icon for muted state, 'volume_off' better for disabled?
|
||||
icon_unmuted = 'volume_up' # default icon for unmuted state
|
||||
last_update = 0 # used for updates. init set to zero so the 1st diff is large to go into update at startup
|
||||
|
||||
@@ -330,8 +336,8 @@ class Volume(Div):
|
||||
self.slider = None # for handle methods to access slider
|
||||
|
||||
# default **kwargs
|
||||
self.wtype = 'sink' # sink (loudspeaker) or sink-input (app output)
|
||||
self.name = '' # pulseaudio sink name
|
||||
self.wtype = 'sink' # sink (loudspeaker), source (microphone) or sink-input (app output)
|
||||
self.name = '' # pulseaudio sink or source name
|
||||
self.description = '' # badge name
|
||||
|
||||
super().__init__(**kwargs)
|
||||
@@ -342,6 +348,11 @@ class Volume(Div):
|
||||
if self.wtype == 'sink':
|
||||
cmdl_toggle = 'pactl set-sink-mute {name} toggle'
|
||||
cmdl_value = 'pactl set-sink-volume {name} {value}%'
|
||||
if self.wtype == 'source':
|
||||
cmdl_toggle = 'pactl set-source-mute {name} toggle'
|
||||
cmdl_value = 'pactl set-source-volume {name} {value}%'
|
||||
self.icon_muted = 'mic_none' # default icon for muted state, 'mic_off' better for disabled?
|
||||
self.icon_unmuted = 'mic' # default icon for unmuted state
|
||||
elif self.wtype == 'sink-input':
|
||||
cmdl_toggle = 'pactl set-sink-input-mute {name} toggle'
|
||||
cmdl_value = 'pactl set-sink-input-volume {name} {value}%'
|
||||
@@ -539,7 +550,7 @@ def widget_load(config) -> dict:
|
||||
# TODO: empty using label class, like an alias?
|
||||
args = [{'widget-class': 'Empty',
|
||||
'type': wid_type}]
|
||||
if wid_type == 'label':
|
||||
elif wid_type == 'label':
|
||||
args = [{'widget-class': Label,
|
||||
'type': wid_type,
|
||||
'text': wid_name}]
|
||||
@@ -574,6 +585,13 @@ def widget_load(config) -> dict:
|
||||
'name': wid_name,
|
||||
'description': config.get(i, 'description', fallback=''),
|
||||
}]
|
||||
elif wid_type == 'source':
|
||||
# volume sliders
|
||||
args = [{'widget-class': Volume,
|
||||
'type': wid_type,
|
||||
'name': wid_name,
|
||||
'description': config.get(i, 'description', fallback=''),
|
||||
}]
|
||||
elif wid_type == 'sink-inputs':
|
||||
# multiple volume sliders
|
||||
args = [{'widget-class': VolumeGroup,
|
||||
|
||||
@@ -3,7 +3,7 @@ import sys
|
||||
import os
|
||||
import argparse
|
||||
from tkinter import Tk, messagebox
|
||||
from webview import create_window, start
|
||||
import webview
|
||||
from controldeck import config_load, process
|
||||
import threading
|
||||
import time
|
||||
@@ -28,41 +28,50 @@ def main(args, pid=-1):
|
||||
port = config.get('default', 'port', fallback='8000')
|
||||
url = f"http://{host}:{port}/?gui&pid={str(pid)}"
|
||||
try:
|
||||
width = int(config.get('gui', 'width', fallback=800))
|
||||
width = config.getint('gui', 'width', fallback=800)
|
||||
except ValueError as e:
|
||||
print(f"{e}")
|
||||
width = 800
|
||||
print(f"Error width: {e}. fallback to: {width}")
|
||||
try:
|
||||
height = int(config.get('gui', 'height', fallback=600))
|
||||
height = config.getint('gui', 'height', fallback=600)
|
||||
except ValueError as e:
|
||||
print(f"{e}")
|
||||
width = 600
|
||||
print(f"Error height: {e}. fallback to {width}")
|
||||
try:
|
||||
x = int(config.get('gui', 'x', fallback=''))
|
||||
x = config.getint('gui', 'x', fallback='')
|
||||
except ValueError as e:
|
||||
print(f"{e}")
|
||||
x = None
|
||||
print(f"Error x: {e}. fallback to {x}")
|
||||
try:
|
||||
y = int(config.get('gui', 'y', fallback=''))
|
||||
y = config.getint('gui', 'y', fallback='')
|
||||
except ValueError as e:
|
||||
print(f"{e}")
|
||||
y = None
|
||||
print(f"Error y: {e}. fallback to: {y}")
|
||||
resizable = config.get('gui', 'resizable', fallback='True').title() == 'True'
|
||||
fullscreen = config.get('gui', 'fullscreen', fallback='False').title() == 'True'
|
||||
try:
|
||||
min_width = int(config.get('gui', 'min_width', fallback=200))
|
||||
min_width = config.getint('gui', 'min_width', fallback=200)
|
||||
except ValueError as e:
|
||||
print(f"{e}")
|
||||
min_width = 200
|
||||
print(f"Error min_width: {e}. fallback to: {min_width}")
|
||||
try:
|
||||
min_height = int(config.get('gui', 'min_height', fallback=100))
|
||||
min_height = config.getint('gui', 'min_height', fallback=100)
|
||||
except ValueError as e:
|
||||
print(f"{e}")
|
||||
min_width = 100
|
||||
min_height = 100
|
||||
print(f"Error min_height: {e}. fallback to: {min_height}")
|
||||
min_size = (min_width, min_height)
|
||||
frameless = config.get('gui', 'frameless', fallback='False').title() == 'True'
|
||||
minimized = config.get('gui', 'minimized', fallback='False').title() == 'True'
|
||||
maximized = config.get('gui', 'maximized', fallback='False').title() == 'True'
|
||||
on_top = config.get('gui', 'always_on_top', fallback='False').title() == 'True'
|
||||
confirm_close = config.get('gui', 'confirm_close', fallback='False').title() == 'True'
|
||||
transparent = config.get('gui', 'transparent', fallback='True').title() == 'True'
|
||||
gui_type = config.get('gui', 'gui_type', fallback=None)
|
||||
gui_type = gui_type if gui_type != "" else None
|
||||
menu = config.get('gui', 'menu', fallback='True').title() == 'True'
|
||||
if args.debug:
|
||||
print(f"config file [default]: {config.items('default')}")
|
||||
print(f"config file [gui]: {config.items('gui')}")
|
||||
|
||||
#controldeck_process = process("ps --no-headers -C controldeck")
|
||||
controldeck_process = process("ps --no-headers -C controldeck || ps aux | grep -e 'python.*controldeck.py' | grep -v grep", shell=True, output=True)
|
||||
@@ -86,29 +95,101 @@ def main(args, pid=-1):
|
||||
|
||||
sys.exit(2)
|
||||
|
||||
create_window("ControlDeck",
|
||||
url=url,
|
||||
html=None,
|
||||
js_api=None,
|
||||
width=width,
|
||||
height=height,
|
||||
x=x,
|
||||
y=y,
|
||||
resizable=resizable,
|
||||
fullscreen=fullscreen,
|
||||
min_size=min_size,
|
||||
hidden=False,
|
||||
frameless=frameless,
|
||||
easy_drag=True,
|
||||
minimized=minimized,
|
||||
on_top=on_top,
|
||||
confirm_close=False,
|
||||
background_color='#000000',
|
||||
transparent=True,
|
||||
text_select=False)
|
||||
window = webview.create_window(
|
||||
title="ControlDeck",
|
||||
url=url,
|
||||
html=None,
|
||||
js_api=None,
|
||||
width=width,
|
||||
height=height,
|
||||
x=x,
|
||||
y=y,
|
||||
screen=None,
|
||||
resizable=resizable,
|
||||
fullscreen=fullscreen,
|
||||
min_size=min_size,
|
||||
hidden=False,
|
||||
frameless=frameless,
|
||||
easy_drag=True,
|
||||
focus=True,
|
||||
minimized=minimized,
|
||||
maximized=maximized,
|
||||
on_top=on_top,
|
||||
confirm_close=confirm_close,
|
||||
background_color='#000000',
|
||||
transparent=transparent, # TODO: bug in qt; menu bar is transparent
|
||||
text_select=False,
|
||||
zoomable=False, # zoom via js
|
||||
draggable=False,
|
||||
vibrancy=False,
|
||||
localization=None,
|
||||
)
|
||||
x = threading.Thread(target=thread_function, args=(1,))
|
||||
x.start()
|
||||
start()
|
||||
|
||||
def menu_reload():
|
||||
window = webview.active_window()
|
||||
if window:
|
||||
url = window.get_current_url()
|
||||
window.load_url(url)
|
||||
print(window.get_current_url())
|
||||
print(window)
|
||||
print(dir(window))
|
||||
def menu_zoomin():
|
||||
window = webview.active_window()
|
||||
if window:
|
||||
zoom = window.evaluate_js('document.documentElement.style.zoom')
|
||||
if zoom == "":
|
||||
zoom = 1.0
|
||||
else:
|
||||
zoom = float(zoom)
|
||||
zoom += 0.1
|
||||
print(f"zoom-in: {zoom}")
|
||||
window.evaluate_js(f'document.documentElement.style.zoom = {zoom}')
|
||||
print(f"set document.documentElement.style.zoom = {zoom}")
|
||||
def menu_zoomout():
|
||||
window = webview.active_window()
|
||||
if window:
|
||||
zoom = window.evaluate_js('document.documentElement.style.zoom')
|
||||
if zoom == "":
|
||||
zoom = 1.0
|
||||
else:
|
||||
zoom = float(zoom)
|
||||
zoom -= 0.1
|
||||
print(f"zoom-out: {zoom}")
|
||||
window.evaluate_js(f'document.documentElement.style.zoom = {zoom}')
|
||||
print(f"set document.documentElement.style.zoom = {zoom}")
|
||||
def menu_zoomreset():
|
||||
window = webview.active_window()
|
||||
if window:
|
||||
zoom = 1.0
|
||||
print(f"zoom-reset: {zoom}")
|
||||
window.evaluate_js(f'document.documentElement.style.zoom = {zoom}')
|
||||
print(f"set document.documentElement.style.zoom = {zoom}")
|
||||
menu_items = []
|
||||
if menu:
|
||||
menu_items = [webview.menu.Menu(
|
||||
'Main', [
|
||||
webview.menu.MenuAction('Reload', menu_reload),
|
||||
webview.menu.MenuAction('zoom +', menu_zoomin),
|
||||
webview.menu.MenuAction('zoom -', menu_zoomout),
|
||||
webview.menu.MenuAction('zoom reset', menu_zoomreset),
|
||||
]
|
||||
)]
|
||||
# TODO: zoom reset on reload (both from menu and within justpy)
|
||||
# TODO: add zoom in config
|
||||
# TODO: move zoom logic to justpy but then it is fix for all,
|
||||
# maybe better a zoom argument in url address
|
||||
|
||||
def win_func(window):
|
||||
print(window.get_current_url())
|
||||
webview.start(
|
||||
func=win_func,
|
||||
args=window,
|
||||
gui=gui_type, # TODO: bug in qt; any menu action is always the last action
|
||||
debug=args.debug,
|
||||
menu=menu_items,
|
||||
)
|
||||
|
||||
def cli():
|
||||
parser = argparse.ArgumentParser(
|
||||
|
||||
@@ -1,59 +1,66 @@
|
||||
# Examples:
|
||||
#
|
||||
# [N.volume.NAME]
|
||||
# name = sink_name
|
||||
# color-fg = hex color code
|
||||
# color-bg = hex color code
|
||||
#
|
||||
# : N. optional number to specify group/row
|
||||
# : NAME id, name of the button
|
||||
# : name sink name, see name with either:
|
||||
# pactl list sinks short
|
||||
# pamixer --list-sinks
|
||||
# : color-bg background color
|
||||
# : color-bg forground color
|
||||
#
|
||||
# [N.button.NAME]
|
||||
# text-alt = name
|
||||
# color-fg = hex color code
|
||||
# color-bg = hex color code
|
||||
# command = shell command
|
||||
# second command
|
||||
# ...
|
||||
# command-alt = shell command ...
|
||||
# state = normal state
|
||||
# state-command = shell command ...
|
||||
# icon = Font Awesome
|
||||
# icon-alt = Font Awesom
|
||||
# image = path to svg file
|
||||
# image-alt = path to svg file
|
||||
#
|
||||
# : N. optional group/row specification
|
||||
# : NAME id, name of the button
|
||||
# : text-alt optional alternative button text
|
||||
# : color-bg background color
|
||||
# : color-bg forground color
|
||||
# : command command(s) to run
|
||||
# : command-alt optional back-switch command(s) to run
|
||||
# : state string to define the normal state
|
||||
# : state-command command to get the state
|
||||
# : icon use icon instead of NAME (Font Awesome), e.g.: fas fa-play
|
||||
# : icon-alt optional alternative icon
|
||||
# : image absolute path to svg file
|
||||
# : image-alt optional alternative image
|
||||
#
|
||||
# [N.empty.NAME]
|
||||
#
|
||||
# [TAB:N.empty.NAME]
|
||||
# : TAB optional tab name to specify tab group
|
||||
# : N. optional number to specify group/row
|
||||
# : NAME id of the empty spot
|
||||
#
|
||||
# [TAB:N.label.NAME]
|
||||
# : TAB optional tab name to specify tab group
|
||||
# : N. optional number to specify group/row
|
||||
# : NAME id, name of the label
|
||||
#
|
||||
# [TAB:N.sink.NAME]
|
||||
# : TAB optional tab name to specify tab group
|
||||
# : N. optional number to specify group/row
|
||||
# : NAME sink id name, see name with either:
|
||||
# pactl list sinks short
|
||||
# pamixer --list-sinks
|
||||
# description = text for the sink
|
||||
#
|
||||
# [TAB:N.source.NAME]
|
||||
# : TAB optional tab name to specify tab group
|
||||
# : N. optional number to specify group/row
|
||||
# : NAME source id name, see name with either:
|
||||
# pactl list sources short
|
||||
# pamixer --list-sources
|
||||
# description = text for the source
|
||||
#
|
||||
# [TAB:N.sink-inputs]
|
||||
# : TAB optional tab name to specify tab group
|
||||
# : N. optional number to specify group/row
|
||||
#
|
||||
# [TAB:N.button.NAME]
|
||||
# : TAB optional tab name to specify tab group
|
||||
# : N. optional group/row specification
|
||||
# : NAME id, name of the button
|
||||
# text-alt = optional alternative burron text
|
||||
# color-fg = foreground color as hex color code, e.g. \#ff0000
|
||||
# color-bg = background color as hex color code, e.g. \#ff0000
|
||||
# command = command(s) to run, seperated by new lines: shell command
|
||||
# second command ...
|
||||
# command-alt = optinal back-switch command(s) to run: shell command ...
|
||||
# state-alt = string to define the alternative state (pressed)
|
||||
# state-command = command to get the state: shell command ...
|
||||
# icon = add icon in front of NAME (Font Awesome), e.g. fas fa-play
|
||||
# icon-alt = optional alternative icon
|
||||
# image = absolte path to image file (svg, png)
|
||||
# image-alt = optional alternative absolue path to image file
|
||||
|
||||
# [TAB:N.slider.NAME]
|
||||
# : TAB optional tab name to specify tab group
|
||||
# : N. optional group/row specification
|
||||
# : NAME id, name of the button
|
||||
# description = text for the slider
|
||||
|
||||
[default]
|
||||
host = 0.0.0.0
|
||||
port = 8000
|
||||
# status = False
|
||||
# volume-decrease-icon = fas fa-volume-down
|
||||
# volume-increase-icon = fas fa-volume-up
|
||||
# volume-mute-icon = fas fa-volume-off
|
||||
# volume-mute-icon-alt = fas fa-volume-mute
|
||||
# volume-mute-icon-alt =
|
||||
# volume-decrease-image =
|
||||
# volume-increase-image =
|
||||
# volume-mute-image =
|
||||
@@ -68,7 +75,6 @@
|
||||
# mic-mute-image-alt =
|
||||
|
||||
[gui]
|
||||
url = http://0.0.0.0:8000
|
||||
width = 800
|
||||
height = 600
|
||||
# x and y specifying the window coordinate (empty = centered)
|
||||
@@ -80,7 +86,13 @@ min_width = 200
|
||||
min_height = 100
|
||||
frameless = False
|
||||
minimized = False
|
||||
maximized = False
|
||||
always_on_top = False
|
||||
confirm_close = False
|
||||
transparent = True
|
||||
# gui_type: qt, gtk, cef, mshtml, edgechromium. or set env PYWEBVIEW_GUI
|
||||
gui_type =
|
||||
menu = True
|
||||
|
||||
[4.button.Test]
|
||||
command = notify-send -a foo baz
|
||||
|
||||
Reference in New Issue
Block a user