Compare commits

28 Commits
v1.0 ... master

Author SHA1 Message Date
eeb18679e6 add option to debug command output 2024-01-01 01:52:19 +01:00
2046adfc79 add update page button to update button states only, delay button update state to let process to finish, overwrite button text if attr description is defined, empty and label class inherit from tile class, process with callback option 2024-01-01 00:11:17 +01:00
bb8709bdf5 fix systemd unit file 2023-12-28 19:10:25 +01:00
212a6ab426 update systemd service unit to wait for env DISPLAY and XAUTHORITY 2023-12-28 19:00:03 +01:00
2622d514c4 fix cast of command which might not be a number 2023-12-28 15:42:01 +01:00
b8d94b271f fix slider config cast 2023-12-24 01:47:57 +01:00
7c3cd04c1b working slider 2023-12-24 00:34:46 +01:00
6d502ea67e fix source sliders 2023-12-23 16:10:20 +01:00
165879eb0d fix color buttons 2023-12-23 15:18:14 +01:00
ed851b0eed add source volume slider, add gui config flags, add gui menu 2023-12-23 14:36:36 +01:00
6abaf515ae fix config 2023-12-21 13:26:55 +01:00
b972354e97 first step for slider class 2023-12-19 21:43:21 +01:00
dec5642e26 fix volume input sinks to handle also mono streams 2023-12-10 04:19:05 +01:00
310d396407 fix static dir 2023-12-09 12:50:16 +01:00
4594b55bf4 change colors 2023-12-09 09:29:32 +01:00
d1581e866f add label class, move empty buton to empty label 2023-10-18 20:32:51 +02:00
c50b5e1ce9 command output 2023-05-29 19:35:55 +02:00
bbda532246 move test button to debug 2023-01-16 13:02:07 +01:00
ead2b06eca cli args for host and port 2023-01-16 12:51:54 +01:00
db0a3b1655 fix app name 2022-11-20 18:24:45 +01:00
180f571018 fix json load of string including an stderr output 2022-11-08 19:43:15 +01:00
707453a513 fix json load of string including an stderr output 2022-11-08 19:39:42 +01:00
21c91b0b9c fix justpy config overwrite 2022-11-06 15:51:47 +01:00
b32a63d9b4 fix config file variable 2022-10-30 16:55:18 +01:00
a9636a4112 fix static directory 2022-10-30 16:45:24 +01:00
18af7036bb comment out xdg ref and profile 2022-10-03 02:24:49 +02:00
310d31e98d fix controldeck entry 2022-10-01 13:35:39 +02:00
6a3995f69a change style to Quasar 2022-10-01 13:16:19 +02:00
8 changed files with 1440 additions and 601 deletions

8
README
View File

@@ -1,7 +1,15 @@
Install Install
Requirements: Requirements:
- controldeck
- python package justpy, the framework: pip install justpy --upgrade
- (optionally) for volume buttons: libpulse - (optionally) for volume buttons: libpulse
- controldeck-gui
- render engine
- GTK: python-gobject, python-cairo, webkit2gtk>=2.22
- QT: qt5-webkit, python-qtpy
- QT5: python-pyqt5-webengine, python-pyqt5
- QT: pyside2, pyside6
local: local:
./setup.sh ./setup.sh

View File

@@ -1 +1 @@
2021.08.24 2022.10.01

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ import sys
import os import os
import argparse import argparse
from tkinter import Tk, messagebox from tkinter import Tk, messagebox
from webview import create_window, start import webview
from controldeck import config_load, process from controldeck import config_load, process
import threading import threading
import time import time
@@ -14,20 +14,73 @@ def thread_function(name):
# print("Thread %s: finishing", name) # print("Thread %s: finishing", name)
# p = process("xdotool search --name 'ControlDeck'") # p = process("xdotool search --name 'ControlDeck'")
# intersection of ControlDeck window name and empty classname # intersection of ControlDeck window name and empty classname
p = process("comm -12 <(xdotool search --name 'ControlDeck' | sort) <(xdotool search --classname '^$' | sort)") p = process("comm -12 <(xdotool search --name 'ControlDeck' | sort) <(xdotool search --classname '^$' | sort)", shell=True)
if p: if p:
# print(p) # print(p)
# process("xdotool search --name 'ControlDeck' set_window --class 'controldeck'", output=False) # process("xdotool search --name 'ControlDeck' set_window --class 'controldeck'", output=False)
# process("xdotool search --name 'ControlDeck' set_window --classname 'controldeck' --class 'ControlDeck' windowunmap windowmap", output=False) # will find to many wrong ids # process("xdotool search --name 'ControlDeck' set_window --classname 'controldeck' --class 'ControlDeck' windowunmap windowmap", output=False) # will find to many wrong ids
process(f"xdotool set_window --classname 'controldeck' --class 'ControlDeck' {p} windowunmap {p} windowmap {p}", output=False) process(f"xdotool set_window --classname 'controldeck' --class 'ControlDeck' {p} windowunmap {p} windowmap {p}", shell=True, output=False)
time.sleep(0.1) time.sleep(0.1)
def main(args, pid=-1): def main(args, pid=-1):
config = config_load(conf=args.config)
host = config.get('default', 'host', fallback='0.0.0.0')
port = config.get('default', 'port', fallback='8000')
url = f"http://{host}:{port}/?gui&pid={str(pid)}"
try:
width = config.getint('gui', 'width', fallback=800)
except ValueError as e:
width = 800
print(f"Error width: {e}. fallback to: {width}")
try:
height = config.getint('gui', 'height', fallback=600)
except ValueError as e:
width = 600
print(f"Error height: {e}. fallback to {width}")
try:
x = config.getint('gui', 'x', fallback='')
except ValueError as e:
x = None
print(f"Error x: {e}. fallback to {x}")
try:
y = config.getint('gui', 'y', fallback='')
except ValueError as 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 = config.getint('gui', 'min_width', fallback=200)
except ValueError as e:
min_width = 200
print(f"Error min_width: {e}. fallback to: {min_width}")
try:
min_height = config.getint('gui', 'min_height', fallback=100)
except ValueError as e:
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")
controldeck_process = process("ps --no-headers -C controldeck || ps aux | grep -e 'python.*controldeck.py' | grep -v grep") controldeck_process = process("ps --no-headers -C controldeck || ps aux | grep -e 'python.*controldeck.py' | grep -v grep", shell=True, output=True)
if args.start and controldeck_process == "": if args.start and controldeck_process == "":
process("controldeck &", output=False) cmd = "controldeck"
cmd += " --config={args.config}" if args.config else ""
print(cmd)
process(cmd, shell=True, output=False)
elif controldeck_process == "": elif controldeck_process == "":
# cli output # cli output
@@ -42,46 +95,8 @@ def main(args, pid=-1):
sys.exit(2) sys.exit(2)
config = config_load(conf=args.config) window = webview.create_window(
url = config.get('gui', 'url', fallback='http://0.0.0.0:8000') + "/?gui&pid=" + str(pid) title="ControlDeck",
try:
width = int(config.get('gui', 'width', fallback=800))
except ValueError as e:
print(f"{e}")
width = 800
try:
height = int(config.get('gui', 'height', fallback=600))
except ValueError as e:
print(f"{e}")
width = 600
try:
x = int(config.get('gui', 'x', fallback=''))
except ValueError as e:
print(f"{e}")
x = None
try:
y = int(config.get('gui', 'y', fallback=''))
except ValueError as e:
print(f"{e}")
y = None
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))
except ValueError as e:
print(f"{e}")
min_width = 200
try:
min_height = int(config.get('gui', 'min_height', fallback=100))
except ValueError as e:
print(f"{e}")
min_width = 100
min_size = (min_width, min_height)
frameless = config.get('gui', 'frameless', fallback='False').title() == 'True'
minimized = config.get('gui', 'minimized', fallback='False').title() == 'True'
on_top = config.get('gui', 'always_on_top', fallback='False').title() == 'True'
create_window("ControlDeck",
url=url, url=url,
html=None, html=None,
js_api=None, js_api=None,
@@ -89,21 +104,92 @@ def main(args, pid=-1):
height=height, height=height,
x=x, x=x,
y=y, y=y,
screen=None,
resizable=resizable, resizable=resizable,
fullscreen=fullscreen, fullscreen=fullscreen,
min_size=min_size, min_size=min_size,
hidden=False, hidden=False,
frameless=frameless, frameless=frameless,
easy_drag=True, easy_drag=True,
focus=True,
minimized=minimized, minimized=minimized,
maximized=maximized,
on_top=on_top, on_top=on_top,
confirm_close=False, confirm_close=confirm_close,
background_color='#000000', background_color='#000000',
transparent=True, transparent=transparent, # TODO: bug in qt; menu bar is transparent
text_select=False) text_select=False,
zoomable=False, # zoom via js
draggable=False,
vibrancy=False,
localization=None,
)
x = threading.Thread(target=thread_function, args=(1,)) x = threading.Thread(target=thread_function, args=(1,))
x.start() 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(): def cli():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(

View File

@@ -1,16 +1,22 @@
[Unit] [Unit]
Description=ControlDeck Description=ControlDeck
ConditionFileIsExecutable=/usr/bin/controldeck ConditionFileIsExecutable=/usr/bin/controldeck
After=network.target After=systemd-user-sessions.service getty@tty1.service plymouth-quit.service systemd-logind.service network.target
PartOf=graphical-session.target
[Service] [Service]
Environment=PYTHONUNBUFFERED=1 Environment=PYTHONUNBUFFERED=1
# add a pause to assure /etc/X11/xinit/xinitrc.d/50-systemd-user.sh to run
# this will add DISPLAY and XAUTHORITY env
# ExecStartPre=/bin/sleep 5
ExecStartPre=/bin/sh -c '(while test ! -v DISPLAY -o ! -v XAUTHORITY; do echo "wait for DISPLAY and XAUTHORITY"; sleep 2; done; echo "DISPLAY and XAUTHORITY found");'
# allowed time for the start
TimeoutStartSec=30 TimeoutStartSec=30
ExecStartPre=/bin/sh -c 'source /etc/profile' ExecStart=/usr/bin/controldeck -D
ExecStart=/usr/bin/controldeck
Restart=on-failure Restart=on-failure
RestartSec=4 RestartSec=4
StandardOutput=journal StandardOutput=journal
[Install] [Install]
WantedBy=default.target #WantedBy=default.target
WantedBy=graphical-session.target

View File

@@ -6,8 +6,8 @@ After=network.target
[Service] [Service]
Environment=PYTHONUNBUFFERED=1 Environment=PYTHONUNBUFFERED=1
TimeoutStartSec=30 TimeoutStartSec=30
ExecStartPre=/bin/sh -c 'source /etc/profile' #ExecStartPre=/bin/sh -c 'source /etc/profile'
ExecStart=%h/.local/bin/controldeck ExecStart=%h/.local/bin/controldeck -D
Restart=on-failure Restart=on-failure
RestartSec=4 RestartSec=4
StandardOutput=journal StandardOutput=journal

View File

@@ -1,59 +1,73 @@
# Examples: # Examples:
# #
# [N.volume.NAME] # [TAB:N.empty.NAME]
# name = sink_name # : TAB optional tab name to specify tab group
# color-fg = hex color code
# color-bg = hex color code
#
# : N. optional number to specify group/row # : N. optional number to specify group/row
# : NAME id, name of the button # : NAME id of the empty spot
# : name sink name, see name with either: #
# [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 # pactl list sinks short
# pamixer --list-sinks # pamixer --list-sinks
# : color-bg background color # description = text for the sink
# : color-bg forground color
# #
# [N.button.NAME] # [TAB:N.source.NAME]
# text-alt = name # : TAB optional tab name to specify tab group
# color-fg = hex color code # : N. optional number to specify group/row
# color-bg = hex color code # : NAME source id name, see name with either:
# command = shell command # pactl list sources short
# second command # pamixer --list-sources
# ... # description = text for the source
# 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
# #
# [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 # : N. optional group/row specification
# : NAME id, name of the button # : NAME id, name of the button
# : text-alt optional alternative button text # text-alt = optional alternative burron text
# : color-bg background color # color-fg = foreground color as hex color code, e.g. #aa5500
# : color-bg forground color # color-bg = background color as hex color code, e.g. #0055aa
# : command command(s) to run # command = command(s) to run, seperated by new lines: shell command
# : command-alt optional back-switch command(s) to run # second command ...
# : state string to define the normal state # command-alt = optinal back-switch command(s) to run: shell command ...
# : state-command command to get the state # state-alt = string to define the alternative state (pressed)
# : icon use icon instead of NAME (Font Awesome), e.g.: fas fa-play # state-command = command to get the state: shell command ...
# : icon-alt optional alternative icon # icon = add icon in front of NAME, e.g. fas fa-play
# : image absolute path to svg file # icon-alt = optional alternative icon
# : image-alt optional alternative image # image = absolte path to image file (svg, png)
# # image-alt = optional alternative absolue path to image file
# [N.empty.NAME]
# # [TAB:N.slider.NAME]
# : N. optional number to specify group/row # : TAB optional tab name to specify tab group
# : N. optional group/row specification
# : NAME id, name of the button # : NAME id, name of the button
# description = text for the slider
# icon = add icon in front of slider, e.g. tune
# min = minimum int value, e.g. 0
# max = maximum int value, e.g. 100
# step = step size, e.g. 1
# state-command = command to get the state: shell command
# command = command to run to get the value, using {value} in the command to
# interpolate the value: shell command
[default] [default]
host = 0.0.0.0
port = 8000
# status = False # status = False
# volume-decrease-icon = fas fa-volume-down # volume-decrease-icon = fas fa-volume-down
# volume-increase-icon = fas fa-volume-up # volume-increase-icon = fas fa-volume-up
# volume-mute-icon = fas fa-volume-off # volume-mute-icon = fas fa-volume-off
# volume-mute-icon-alt = fas fa-volume-mute # volume-mute-icon-alt = fas fa-volume-mute
# volume-mute-icon-alt =
# volume-decrease-image = # volume-decrease-image =
# volume-increase-image = # volume-increase-image =
# volume-mute-image = # volume-mute-image =
@@ -68,7 +82,6 @@
# mic-mute-image-alt = # mic-mute-image-alt =
[gui] [gui]
url = http://0.0.0.0:8000
width = 800 width = 800
height = 600 height = 600
# x and y specifying the window coordinate (empty = centered) # x and y specifying the window coordinate (empty = centered)
@@ -80,7 +93,13 @@ min_width = 200
min_height = 100 min_height = 100
frameless = False frameless = False
minimized = False minimized = False
maximized = False
always_on_top = 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] [4.button.Test]
command = notify-send -a foo baz command = notify-send -a foo baz

View File

@@ -6,12 +6,11 @@ version = file: VERSION
install_requires = install_requires =
justpy justpy
pywebview pywebview
cairosvg
py_modules = py_modules =
controldeck controldeck
controldeck_gui controldeck_gui
[options.entry_points] [options.entry_points]
console_scripts = console_scripts =
controldeck = controldeck:main controldeck = controldeck:cli
controldeck-gui = controldeck_gui:cli controldeck-gui = controldeck_gui:cli