added my Recipes
@@ -0,0 +1,3 @@
|
||||
BOARD: STM32MP13
|
||||
LOGO: pictures/ST20578_Label_OpenSTlinux_V.png
|
||||
INFO: <span font='9' color='#FFFFFFFF'><b>STM32MP13x Board</b></span>|<span font='7' color='#FFFFFFFF'>Arm® Cortex®-A7</span>
|
||||
@@ -0,0 +1,4 @@
|
||||
BOARD: STM32MP15
|
||||
LOGO: pictures/ST20578_Label_OpenSTlinux_V.png
|
||||
INFO: <span font='14' color='#FFFFFFFF'><b>STM32MP15x Board</b></span>|<span font='10' color='#FFFFFFFF'>Dual Arm® Cortex®-A7</span>|<span font='10' color='#FFFFFFFF'>+</span>|<span font='10' color='#FFFFFFFF'>Copro Arm® Cortex®-M4</span>
|
||||
|
||||
861
meta-st/meta-st-openstlinux/recipes-samples/demo/demo-launcher/demo_launcher.py
Executable file
@@ -0,0 +1,861 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Copyright (c) 2019 STMicroelectronics. All rights reserved.
|
||||
#
|
||||
# This software component is licensed by ST under BSD 3-Clause license,
|
||||
# the "License"; You may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at:
|
||||
# opensource.org/licenses/BSD-3-Clause
|
||||
|
||||
# to debug this script:
|
||||
# python3 -m pdb ./demo_launcher.py
|
||||
#
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import GdkPixbuf
|
||||
from gi.repository import Pango
|
||||
|
||||
import yaml
|
||||
|
||||
import subprocess
|
||||
import random
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
import socket
|
||||
import fcntl
|
||||
import struct
|
||||
import string
|
||||
import random
|
||||
from collections import deque
|
||||
from time import sleep, time
|
||||
import threading
|
||||
|
||||
import importlib
|
||||
#
|
||||
# For simulating UI on PC , please use
|
||||
# the variable SIMULATE = 1
|
||||
# If SIMULATE = 1 then
|
||||
# the picture/icon must be present on pictures directory
|
||||
#
|
||||
SIMULATE = 0
|
||||
|
||||
|
||||
if SIMULATE > 0:
|
||||
#DEMO_PATH = os.environ['HOME']+"/Desktop/launcher"
|
||||
DEMO_PATH = "./"
|
||||
else:
|
||||
DEMO_PATH = "/usr/local/demo"
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Managment of lock file to have only excution of this script as same time
|
||||
lock = threading.Lock()
|
||||
|
||||
lock_handle = None
|
||||
lock_file_path = '/tmp/demo_launcher.lock'
|
||||
|
||||
def file_is_locked(file_path):
|
||||
global lock_handle
|
||||
lock_handle= open(file_path, 'w')
|
||||
try:
|
||||
fcntl.lockf(lock_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
return False
|
||||
except IOError:
|
||||
return True
|
||||
|
||||
def file_lock_remove(file_path):
|
||||
try:
|
||||
os.remove(lock_file_path)
|
||||
except Exception as exc:
|
||||
print("Signal handler Exception: ", exc)
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
def detroy_quit_application(widget):
|
||||
file_lock_remove(lock_file_path)
|
||||
Gtk.main_quit()
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
# CONSTANT VALUES
|
||||
#
|
||||
SIMULATE_SCREEN_SIZE_WIDTH = 800
|
||||
SIMULATE_SCREEN_SIZE_HEIGHT = 480
|
||||
#SIMULATE_SCREEN_SIZE_WIDTH = 480
|
||||
#SIMULATE_SCREEN_SIZE_HEIGHT = 272
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
ICON_SIZE_1080 = 260
|
||||
ICON_SIZE_720 = 180
|
||||
ICON_SIZE_480 = 128
|
||||
ICON_SIZE_272 = 64
|
||||
|
||||
# return format:
|
||||
# [ icon_size, font_size, logo_size, exit_size, column_spacing, row_spacing ]
|
||||
SIZES_ID_ICON_SIZE = 0
|
||||
SIZES_ID_FONT_SIZE = 1
|
||||
SIZES_ID_LOGO_SIZE = 2
|
||||
SIZES_ID_EXIT_SIZE = 3
|
||||
SIZES_ID_COLUMN_SPACING = 4
|
||||
SIZES_ID_ROW_SPACING = 5
|
||||
def get_sizes_from_screen_size(width, height):
|
||||
minsize = min(width, height)
|
||||
icon_size = None
|
||||
font_size = None
|
||||
logo_size = None
|
||||
exit_size = None
|
||||
column_spacing = None
|
||||
row_spacing = None
|
||||
if minsize == 720:
|
||||
icon_size = ICON_SIZE_720
|
||||
font_size = 15
|
||||
logo_size = 160
|
||||
exit_size = 50
|
||||
column_spacing = 20
|
||||
row_spacing = 20
|
||||
elif minsize == 480:
|
||||
icon_size = ICON_SIZE_480
|
||||
font_size = 15
|
||||
logo_size = 160
|
||||
exit_size = 50
|
||||
column_spacing = 10
|
||||
row_spacing = 10
|
||||
elif minsize == 272:
|
||||
icon_size = ICON_SIZE_272
|
||||
font_size = 8
|
||||
logo_size = 60
|
||||
exit_size = 25
|
||||
column_spacing = 5
|
||||
row_spacing = 5
|
||||
elif minsize == 600:
|
||||
icon_size = ICON_SIZE_720
|
||||
font_size = 15
|
||||
logo_size = 160
|
||||
exit_size = 50
|
||||
column_spacing = 20
|
||||
row_spacing = 20
|
||||
elif minsize >= 1080:
|
||||
icon_size = ICON_SIZE_1080
|
||||
font_size = 32
|
||||
logo_size = 260
|
||||
exit_size = 50
|
||||
column_spacing = 20
|
||||
row_spacing = 20
|
||||
return [icon_size, font_size, logo_size, exit_size, column_spacing, row_spacing]
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
# Back video view
|
||||
class BackVideoWindow(Gtk.Dialog):
|
||||
def __init__(self, parent):
|
||||
Gtk.Dialog.__init__(self, "Wifi", parent, 0)
|
||||
self.previous_click_time=time()
|
||||
self.maximize()
|
||||
self.set_decorated(False)
|
||||
self.set_name("backed_bg")
|
||||
self.show_all()
|
||||
|
||||
# Info view
|
||||
class InfoWindow(Gtk.Dialog):
|
||||
def __init__(self, parent):
|
||||
Gtk.Dialog.__init__(self, "Wifi", parent, 0)
|
||||
self.previous_click_time=time()
|
||||
self.maximize()
|
||||
self.set_decorated(False)
|
||||
self.set_name("backed_bg")
|
||||
try:
|
||||
self.font_size = parent.font_size
|
||||
except:
|
||||
print("DEBUG take default font size")
|
||||
self.font_size = 15
|
||||
|
||||
mainvbox = self.get_content_area()
|
||||
|
||||
page_info = Gtk.VBox()
|
||||
page_info.set_border_width(10)
|
||||
|
||||
title = Gtk.Label()
|
||||
title.set_markup("<span font='%d' color='#FFFFFFFF'><b>About the application</b></span>" % (self.font_size+5))
|
||||
page_info.add(title)
|
||||
|
||||
label1 = Gtk.Label()
|
||||
label1.set_markup("<span font='%d' color='#FFFFFFFF'>\n\nTo get control of video playback and camera preview,\nSimple tap: pause/resume\nDouble tap: exit from demos\n\nAI demo: draw character on touchscreen to launch action</span>" % self.font_size)
|
||||
label1.set_justify(Gtk.Justification.LEFT)
|
||||
page_info.add(label1)
|
||||
|
||||
mainvbox.pack_start(page_info, False, False, 3)
|
||||
self.connect("button-release-event", self.on_page_press_event)
|
||||
self.show_all()
|
||||
|
||||
def on_page_press_event(self, widget, event):
|
||||
self.click_time = time()
|
||||
print(self.click_time - self.previous_click_time)
|
||||
# TODO : a fake click is observed, workaround hereafter
|
||||
if (self.click_time - self.previous_click_time) < 0.01:
|
||||
self.previous_click_time = self.click_time
|
||||
elif (self.click_time - self.previous_click_time) < 0.3:
|
||||
print ("double click")
|
||||
self.destroy()
|
||||
else:
|
||||
print ("simple click")
|
||||
self.previous_click_time = self.click_time
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
def _load_image_eventBox(parent, filename, label_text1, label_text2, scale_w, scale_h, font_size):
|
||||
# Create box for xpm and label
|
||||
box = Gtk.VBox(homogeneous=False, spacing=0)
|
||||
# Create an eventBox
|
||||
eventBox = Gtk.EventBox()
|
||||
# Now on to the image stuff
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
||||
filename=filename,
|
||||
width=scale_w,
|
||||
height=scale_h,
|
||||
preserve_aspect_ratio=True)
|
||||
image = Gtk.Image.new_from_pixbuf(pixbuf)
|
||||
|
||||
label = Gtk.Label()
|
||||
label.set_markup("<span font='%d' color='#39A9DCFF'>%s\n</span>"
|
||||
"<span font='%d' color='#002052FF'>%s</span>" %
|
||||
(font_size, label_text1, font_size, label_text2))
|
||||
label.set_justify(Gtk.Justification.CENTER)
|
||||
label.set_line_wrap(True)
|
||||
|
||||
# Pack the pixmap and label into the box
|
||||
box.pack_start(image, True, False, 0)
|
||||
box.pack_start(label, True, False, 0)
|
||||
|
||||
# Add the image to the eventBox
|
||||
eventBox.add(box)
|
||||
|
||||
return eventBox
|
||||
|
||||
def _load_image_Box(parent, mp1filename, infofilename, label_text, scale_w, scale_h):
|
||||
box = Gtk.VBox(homogeneous=False, spacing=0)
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
||||
filename=mp1filename,
|
||||
width=scale_w,
|
||||
height=scale_h,
|
||||
preserve_aspect_ratio=True)
|
||||
image = Gtk.Image.new_from_pixbuf(pixbuf)
|
||||
|
||||
# Create a label for the button
|
||||
label0 = Gtk.Label() #for padding
|
||||
label1 = Gtk.Label()
|
||||
label1.set_markup("%s\n" % label_text)
|
||||
label1.set_justify(Gtk.Justification.CENTER)
|
||||
label1.set_line_wrap(True)
|
||||
|
||||
eventBox = Gtk.EventBox()
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
||||
filename=infofilename,
|
||||
width=scale_w,
|
||||
height=(scale_h/4),
|
||||
preserve_aspect_ratio=True)
|
||||
info = Gtk.Image.new_from_pixbuf(pixbuf)
|
||||
eventBox.add(info)
|
||||
eventBox.connect("button_release_event", parent.info_event)
|
||||
eventBox.connect("button_press_event", parent.highlight_eventBox)
|
||||
|
||||
label3 = Gtk.Label()
|
||||
label3.set_markup("<span font='10' color='#FFFFFFFF'><b>Python GTK launcher</b></span>\n")
|
||||
label3.set_justify(Gtk.Justification.CENTER)
|
||||
label3.set_line_wrap(True)
|
||||
|
||||
# Pack the pixmap and label into the box
|
||||
box.pack_start(label0, True, False, 0)
|
||||
box.pack_start(image, True, False, 0)
|
||||
box.pack_start(label1, True, False, 0)
|
||||
box.pack_start(eventBox, True, False, 0)
|
||||
box.pack_start(label3, True, False, 0)
|
||||
|
||||
return box
|
||||
|
||||
def _load_image_on_button(parent, filename, label_text, scale_w, scale_h):
|
||||
# Create box for xpm and label
|
||||
box = Gtk.HBox(homogeneous=False, spacing=0)
|
||||
box.set_border_width(2)
|
||||
# print("[DEBUG] image: %s " % filename)
|
||||
# Now on to the image stuff
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
||||
filename=filename,
|
||||
width=scale_w,
|
||||
height=scale_h,
|
||||
preserve_aspect_ratio=True)
|
||||
image = Gtk.Image.new_from_pixbuf(pixbuf)
|
||||
|
||||
# Create a label for the button
|
||||
label = Gtk.Label.new(label_text)
|
||||
|
||||
# Pack the pixmap and label into the box
|
||||
box.pack_start(image, True, False, 3)
|
||||
|
||||
image.show()
|
||||
label.show()
|
||||
return box
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
def read_board_compatibility_name():
|
||||
if SIMULATE > 0:
|
||||
return "all"
|
||||
else:
|
||||
try:
|
||||
with open("/proc/device-tree/compatible") as fp:
|
||||
string = fp.read()
|
||||
return string.split(',')[-1].rstrip('\x00')
|
||||
except:
|
||||
return "all"
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
BOARD_CONFIG_ID_BOARD = 0
|
||||
BOARD_CONFIG_ID_LOGO = 1
|
||||
BOARD_CONFIG_ID_INFO_TEXT = 2
|
||||
def read_configuration_board_file(search_path):
|
||||
board_list = []
|
||||
yaml_configuration = None
|
||||
board_compatibility_name = read_board_compatibility_name()
|
||||
print("[DEBUG] compatiblity name ", read_board_compatibility_name())
|
||||
configuration_found = None
|
||||
for file in sorted(os.listdir(search_path)):
|
||||
if board_compatibility_name.find(file) > -1:
|
||||
configuration_found = file
|
||||
#print("DEBUG: found board configuration file: ", file)
|
||||
if configuration_found and os.path.isfile(os.path.join(search_path, configuration_found)):
|
||||
print("[DEBUG] read configuration box for ", configuration_found)
|
||||
with open(os.path.join(search_path, configuration_found)) as fp:
|
||||
yaml_configuration = yaml.load(fp, Loader=yaml.FullLoader)
|
||||
|
||||
# board name
|
||||
if yaml_configuration and yaml_configuration["BOARD"]:
|
||||
board_list.append(yaml_configuration["BOARD"])
|
||||
else:
|
||||
board_list.append('STM32MP')
|
||||
# logo to used
|
||||
if yaml_configuration and yaml_configuration["LOGO"]:
|
||||
board_list.append(yaml_configuration["LOGO"])
|
||||
else:
|
||||
board_list.append('pictures/ST20578_Label_OpenSTlinux_V.png')
|
||||
# info text to display
|
||||
if yaml_configuration and yaml_configuration["INFO"]:
|
||||
info = '\n'.join(yaml_configuration["INFO"].split('|'))
|
||||
board_list.append(info)
|
||||
else:
|
||||
board_list.append("<span font='14' color='#FFFFFFFF'><b>STM32MP BOARD</b></span>")
|
||||
return board_list
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
def import_module_by_name(module_name):
|
||||
''' module example:0application.netdata.netdata
|
||||
(corresponding to application/netdata/netdata.py file)
|
||||
'''
|
||||
try:
|
||||
print("[DEBUG] module_name=>%s<" % module_name)
|
||||
imported = importlib.import_module(module_name)
|
||||
except Exception as e:
|
||||
print("Module Load, error: ", e)
|
||||
return None
|
||||
return imported
|
||||
|
||||
class ApplicationButton():
|
||||
def __init__(self, parent, yaml_file, icon_size, font_size):
|
||||
self.event_box = None
|
||||
self.yaml_configuration = None
|
||||
self.icon_size = icon_size
|
||||
self.font_size = font_size
|
||||
self._parent = parent
|
||||
self._compatible = True
|
||||
|
||||
with open(yaml_file) as fp:
|
||||
self.yaml_configuration = yaml.load(fp, Loader=yaml.FullLoader)
|
||||
#print(self.yaml_configuration)
|
||||
#print("Name ", self.yaml_configuration["Application"]["Name"])
|
||||
|
||||
if self.yaml_configuration:
|
||||
# check board if it's compatible
|
||||
if (self._is_compatible(self.yaml_configuration["Application"]["Board"])):
|
||||
self._compatible = True
|
||||
self.event_box = _load_image_eventBox(self, "%s/%s" % (DEMO_PATH, self.yaml_configuration["Application"]["Icon"]),
|
||||
self.yaml_configuration["Application"]["Name"],
|
||||
self.yaml_configuration["Application"]["Description"],
|
||||
-1, self.icon_size, self.font_size)
|
||||
if (self.yaml_configuration["Application"]["Type"].rstrip() == "script"):
|
||||
self.event_box.connect("button_release_event", self.script_handle)
|
||||
self.event_box.connect("button_press_event", self._parent.highlight_eventBox)
|
||||
elif (self.yaml_configuration["Application"]["Type"].rstrip() == "python"):
|
||||
self.event_box.connect("button_release_event", self.python_start)
|
||||
self.event_box.connect("button_press_event", self._parent.highlight_eventBox)
|
||||
else:
|
||||
self._compatible = False
|
||||
print(" %s NOT compatible" % self.yaml_configuration["Application"]["Name"])
|
||||
|
||||
|
||||
def is_exist(self, data):
|
||||
try:
|
||||
#print("[DEBUG][is_exist] ", data)
|
||||
if (data):
|
||||
for masterkey in data:
|
||||
#print("[DEBUG][is_exist] key available: ", masterkey)
|
||||
if masterkey == "Exist":
|
||||
for key in data["Exist"]:
|
||||
#print("[DEBUG][is_exist] key detected: %s" % key)
|
||||
if key == "File" and len(data["Exist"]["File"].rstrip()):
|
||||
if (os.path.exists(data["Exist"]["File"].rstrip())):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
elif (key == "Command" and len(data["Exist"]["Command"].rstrip())):
|
||||
retcode = subprocess.call(data["Exist"]["Command"].rstrip(), shell=True);
|
||||
if (int(retcode) == 0):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
except:
|
||||
print("is_exist exception return true")
|
||||
return True
|
||||
|
||||
def exist_MSG_present(self, data):
|
||||
try:
|
||||
#print("[DEBUG][is_exist] ", data)
|
||||
if (data):
|
||||
for masterkey in data:
|
||||
#print("[DEBUG][is_exist] key available: ", masterkey)
|
||||
if masterkey == "Exist":
|
||||
for key in data["Exist"]:
|
||||
#print("[DEBUG][is_exist] key detected: %s" % key)
|
||||
if key == "Msg_false" and len(data["Exist"]["Msg_false"].rstrip()):
|
||||
return True
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def is_compatible(self):
|
||||
return self._compatible
|
||||
def _is_compatible(self, data):
|
||||
board_compatibility_name = read_board_compatibility_name()
|
||||
try:
|
||||
if (data):
|
||||
for key in data:
|
||||
if key == "List" and len(data["List"].rstrip()):
|
||||
#print("[DEBUG] List<", data["List"], "> %s" % board_compatibility_name, " ")
|
||||
if data["List"].find('all') > -1:
|
||||
return True
|
||||
for b in data["List"].split():
|
||||
#print("[DEBUG] test for List <", b, "> %s" % board_compatibility_name, " " , board_compatibility_name.find(b) )
|
||||
if board_compatibility_name.find(b) > -1:
|
||||
return True
|
||||
return False
|
||||
elif key == "NotList" and len(data["NotList"].rstrip()):
|
||||
#print("[DEBUG] NotList<", data["NotList"], "> %s" % board_compatibility_name, " "))
|
||||
for b in data["NotList"].split():
|
||||
#print("[DEBUG] test for Not List <", b, "> %s" % board_compatibility_name, " " , board_compatibility_name.find(b) )
|
||||
if board_compatibility_name.find(b) > -1:
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
except Exception as e:
|
||||
print("is_compatible exception return true ", e)
|
||||
return True
|
||||
return True
|
||||
|
||||
def get_event_box(self):
|
||||
return self.event_box
|
||||
|
||||
def python_start(self, widget, event):
|
||||
print("Python module =>", self.yaml_configuration["Application"]["Python"]["Module"], "<<<")
|
||||
if (self.is_exist(self.yaml_configuration["Application"]["Python"])):
|
||||
if (self.yaml_configuration["Application"]["Python"]["Module"] and
|
||||
len(self.yaml_configuration["Application"]["Python"]["Module"].rstrip()) > 0):
|
||||
module_imported = import_module_by_name(self.yaml_configuration["Application"]["Python"]["Module"].rstrip())
|
||||
if (module_imported):
|
||||
print("[Python_event start]")
|
||||
module_imported.create_subdialogwindow(self._parent)
|
||||
print("[Python_event stop]\n")
|
||||
widget.set_name("transparent_bg")
|
||||
self._parent.button_exit.show()
|
||||
elif (self.exist_MSG_present(self.yaml_configuration["Application"]["Python"])):
|
||||
print("[WARNING] %s not detected\n" % self.yaml_configuration["Application"]["Python"]["Exist"]["Msg_false"])
|
||||
self._parent.display_message("<span font='15' color='#FFFFFFFF'>%s\n</span>" % self.yaml_configuration["Application"]["Python"]["Exist"]["Msg_false"])
|
||||
widget.set_name("transparent_bg")
|
||||
self._parent.button_exit.show()
|
||||
|
||||
def script_start(self):
|
||||
global lock
|
||||
with lock:
|
||||
print("Lock Acquired")
|
||||
backscript_window = BackVideoWindow(self._parent)
|
||||
backscript_window.show_all()
|
||||
|
||||
print("[DEBUG][ApplicationButton][script_handle]:")
|
||||
print(" Name: ", self.yaml_configuration["Application"]["Name"])
|
||||
print(" Start script: ", self.yaml_configuration["Application"]["Script"]["Start"])
|
||||
|
||||
cmd = [os.path.join(DEMO_PATH,self.yaml_configuration["Application"]["Script"]["Start"])]
|
||||
subprocess.run(cmd, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
|
||||
backscript_window.destroy()
|
||||
print("Lock Released")
|
||||
|
||||
def script_handle(self, widget, event):
|
||||
if (self.is_exist(self.yaml_configuration["Application"]["Script"])):
|
||||
print("Acquiring lock")
|
||||
self.script_start()
|
||||
|
||||
elif (self.exist_MSG_present(self.yaml_configuration["Application"]["Script"])):
|
||||
print("[WARNING] %s not detected\n" % self.yaml_configuration["Application"]["Script"]["Exist"]["Msg_false"])
|
||||
self._parent.display_message("<span font='15' color='#FFFFFFFF'>%s\n</span>" % self.yaml_configuration["Application"]["Script"]["Exist"]["Msg_false"])
|
||||
|
||||
print("[script_event stop]\n")
|
||||
widget.set_name("transparent_bg")
|
||||
self._parent.button_exit.show()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
def gtk_style():
|
||||
css = b"""
|
||||
|
||||
.widget .grid .label {
|
||||
background-color: rgba (100%, 100%, 100%, 1.0);
|
||||
}
|
||||
.textview {
|
||||
color: gray;
|
||||
}
|
||||
#normal_bg {
|
||||
background-color: rgba (100%, 100%, 100%, 1.0);
|
||||
}
|
||||
|
||||
#transparent_bg {
|
||||
background-color: rgba (0%, 0%, 0%, 0.0);
|
||||
}
|
||||
#highlight_bg {
|
||||
background-color: rgba (0%, 0%, 0%, 0.1);
|
||||
}
|
||||
#logo_bg {
|
||||
background-color: #03244b;
|
||||
}
|
||||
#backed_bg {
|
||||
background-color: rgba (31%, 32%, 31%, 0.8);
|
||||
}
|
||||
|
||||
"""
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_data(css)
|
||||
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
Gdk.Screen.get_default(),
|
||||
style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
class MainUIWindow(Gtk.Window):
|
||||
def __init__(self):
|
||||
Gtk.Window.__init__(self, title="Demo Launcher")
|
||||
self.set_decorated(False)
|
||||
gtk_style()
|
||||
if SIMULATE > 0:
|
||||
self.screen_width = SIMULATE_SCREEN_SIZE_WIDTH
|
||||
self.screen_height = SIMULATE_SCREEN_SIZE_HEIGHT
|
||||
else:
|
||||
#self.fullscreen()
|
||||
self.maximize()
|
||||
try:
|
||||
display = Gdk.Display.get_default()
|
||||
monitor = display.get_primary_monitor()
|
||||
geometry = monitor.get_geometry()
|
||||
scale_factor = monitor.get_scale_factor()
|
||||
self.screen_width = scale_factor * geometry.width
|
||||
self.screen_height = scale_factor * geometry.height
|
||||
except:
|
||||
self.screen_width = self.get_screen().get_width()
|
||||
self.screen_height = self.get_screen().get_height()
|
||||
|
||||
self.board_name = "STM32MP board"
|
||||
|
||||
self.set_default_size(self.screen_width, self.screen_height)
|
||||
print("[DEBUG] screen size: %dx%d" % (self.screen_width, self.screen_height))
|
||||
self.set_position(Gtk.WindowPosition.CENTER)
|
||||
self.connect('destroy', detroy_quit_application)
|
||||
|
||||
self.previous_click_time=time()
|
||||
|
||||
self.application_path = os.path.join(DEMO_PATH,"./application/")
|
||||
self.board_path = os.path.join(DEMO_PATH,"./board/")
|
||||
|
||||
self.board_configuration = read_configuration_board_file(self.board_path)
|
||||
|
||||
sizes = get_sizes_from_screen_size(self.screen_width, self.screen_height)
|
||||
self.icon_size = sizes[SIZES_ID_ICON_SIZE]
|
||||
self.font_size = sizes[SIZES_ID_FONT_SIZE]
|
||||
self.logo_size = sizes[SIZES_ID_LOGO_SIZE]
|
||||
self.exit_size = sizes[SIZES_ID_EXIT_SIZE]
|
||||
self.column_spacing = sizes[SIZES_ID_COLUMN_SPACING]
|
||||
self.row_spacing = sizes[SIZES_ID_ROW_SPACING]
|
||||
|
||||
# page for basic information
|
||||
self.create_page_icon_autodetected()
|
||||
|
||||
def display_message(self, message):
|
||||
dialog = Gtk.Dialog("Error", self, 0, (Gtk.STOCK_OK, Gtk.ResponseType.OK))
|
||||
dialog.set_decorated(False)
|
||||
width, height = self.get_size()
|
||||
dialog.set_default_size(width, height)
|
||||
dialog.set_name("backed_bg")
|
||||
|
||||
label0 = Gtk.Label() #for padding
|
||||
|
||||
label1 = Gtk.Label()
|
||||
label1.set_markup(message)
|
||||
label1.set_justify(Gtk.Justification.CENTER)
|
||||
label1.set_line_wrap(True)
|
||||
|
||||
label2 = Gtk.Label() #for padding
|
||||
|
||||
# Create a centering alignment object
|
||||
align = Gtk.Alignment()
|
||||
align.set(0.5, 0, 0, 0)
|
||||
|
||||
dialog.vbox.pack_start(label0, True, False, 0)
|
||||
dialog.vbox.pack_start(label1, True, True, 0)
|
||||
dialog.vbox.pack_start(align, True, True, 0)
|
||||
dialog.vbox.pack_start(label2, True, False, 0)
|
||||
|
||||
dialog.action_area.reparent(align)
|
||||
dialog.show_all()
|
||||
|
||||
dialog.run()
|
||||
print("INFO dialog closed")
|
||||
|
||||
dialog.destroy()
|
||||
|
||||
|
||||
def info_event(self, widget, event):
|
||||
print("[info_event start]");
|
||||
info_window = InfoWindow(self)
|
||||
info_window.show_all()
|
||||
response = info_window.run()
|
||||
info_window.destroy()
|
||||
print("[info_event stop]\n");
|
||||
widget.set_name("transparent_bg")
|
||||
self.button_exit.show()
|
||||
|
||||
|
||||
# Button event of main screen
|
||||
def highlight_eventBox(self, widget, event):
|
||||
''' highlight the eventBox widget '''
|
||||
print("[highlight_eventBox start]")
|
||||
widget.set_name("highlight_bg")
|
||||
self.button_exit.hide()
|
||||
print("[highlight_eventBox stop]\n")
|
||||
|
||||
def create_page_icon_autodetected(self):
|
||||
self.yaml_application_list = None
|
||||
self.application_list = []
|
||||
self.application_eventbox_list = []
|
||||
self.application_start_previous = 0
|
||||
self.application_start_next = 0
|
||||
self.application_end = 0
|
||||
|
||||
self.page_main = Gtk.HBox(homogeneous=False, spacing=0)
|
||||
self.page_main.set_border_width(0)
|
||||
|
||||
# create a grid of icon
|
||||
self.icon_grid = Gtk.Grid(column_homogeneous=True, row_homogeneous=True)
|
||||
self.icon_grid.set_column_spacing(self.column_spacing)
|
||||
self.icon_grid.set_row_spacing(self.row_spacing)
|
||||
|
||||
# STM32MP1 Logo and info area
|
||||
info_box_text = self.board_configuration[BOARD_CONFIG_ID_INFO_TEXT]
|
||||
info_box_logo = self.board_configuration[BOARD_CONFIG_ID_LOGO]
|
||||
self.logo_info_area = _load_image_Box(self, "%s/%s" % (DEMO_PATH,info_box_logo), "%s/pictures/ST13340_Info_white.png" % DEMO_PATH, info_box_text, -1, self.logo_size)
|
||||
self.logo_info_area.set_name("logo_bg")
|
||||
self.icon_grid.attach(self.logo_info_area, 3, 0, 1, 2)
|
||||
|
||||
self.back_box = self.create_eventbox_back_next(1)
|
||||
self.next_box = self.create_eventbox_back_next(0)
|
||||
|
||||
number_of_application = 0
|
||||
for file in sorted(os.listdir(self.application_path)):
|
||||
if os.path.isfile(os.path.join(self.application_path, file)) and file.endswith(".yaml"):
|
||||
print("[DEBUG] create event box for ", file)
|
||||
application_button = ApplicationButton(self, os.path.join(self.application_path, file), self.icon_size, self.font_size)
|
||||
if application_button.is_compatible():
|
||||
self.application_list.append(os.path.join(self.application_path, file))
|
||||
self.application_eventbox_list.append(application_button.get_event_box())
|
||||
number_of_application = number_of_application + 1
|
||||
print("[DEBUG] there is %d application(s) detected " % number_of_application)
|
||||
if number_of_application == 0:
|
||||
self.set_default_size(self.screen_width, self.screen_height)
|
||||
self.display_message("<span font='15' color='#FFFFFFFF'>There is no application detected\n</span>")
|
||||
self.destroy()
|
||||
|
||||
self.application_end = len(self.application_list)
|
||||
|
||||
#print("[DEBUG] application list:\n", self.application_list)
|
||||
self.create_page_icon_by_page(0)
|
||||
self.page_main.add(self.icon_grid)
|
||||
|
||||
overlay = Gtk.Overlay()
|
||||
overlay.add(self.page_main)
|
||||
self.button_exit = Gtk.Button()
|
||||
self.button_exit.connect("clicked", detroy_quit_application)
|
||||
self.button_exit_image = _load_image_on_button(self, "%s/pictures/close_70x70_white.png" % DEMO_PATH, "Exit", -1, self.exit_size)
|
||||
self.button_exit.set_halign(Gtk.Align.END)
|
||||
self.button_exit.set_valign(Gtk.Align.START)
|
||||
self.button_exit.add(self.button_exit_image)
|
||||
self.button_exit.set_relief(Gtk.ReliefStyle.NONE)
|
||||
overlay.add_overlay(self.button_exit)
|
||||
self.add(overlay)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def create_page_icon_by_page(self, app_start):
|
||||
'''
|
||||
--------------------------------------------------------------
|
||||
| 0,0: app1 | 1,0: app2 | 2,0: app2 | 3,0: information |
|
||||
--------------------------------------------------------------
|
||||
| 0,1: app1 | 1,1: app2 | 2,1: app2 | 3,1: information |
|
||||
--------------------------------------------------------------
|
||||
'''
|
||||
for ind in range(0,self.application_end):
|
||||
if (self.application_eventbox_list[ind]):
|
||||
self.icon_grid.remove(self.application_eventbox_list[ind])
|
||||
self.icon_grid.remove(self.back_box)
|
||||
self.icon_grid.remove(self.next_box)
|
||||
|
||||
#print("[ICON DEBUG] app_start ", app_start)
|
||||
# calculate next and previous
|
||||
if app_start > 0:
|
||||
if (app_start % 5) == 0:
|
||||
self.application_start_previous = app_start - 5
|
||||
else:
|
||||
self.application_start_previous = app_start - 4
|
||||
if self.application_start_previous < 0:
|
||||
self.application_start_previous = 0
|
||||
self.application_start_next = app_start + 4
|
||||
else:
|
||||
self.application_start_previous = 0
|
||||
self.application_start_next = 5
|
||||
#print("[ICON DEBUG] previous ", self.application_start_previous)
|
||||
#print("[ICON DEBUG] next ", self.application_start_next)
|
||||
|
||||
if app_start != 0:
|
||||
''' add previous button '''
|
||||
index = app_start
|
||||
# 0, 0
|
||||
self.icon_grid.attach(self.back_box, 0, 0, 1, 1)
|
||||
# 1, 0
|
||||
if self.application_eventbox_list[index]:
|
||||
self.icon_grid.attach(self.application_eventbox_list[index], 1, 0, 1, 1)
|
||||
index = index + 1
|
||||
else:
|
||||
index = app_start
|
||||
self.application_start_previous = app_start - 4
|
||||
if self.application_start_previous < 0:
|
||||
self.application_start_previous = 0
|
||||
# 0, 0
|
||||
if self.application_eventbox_list[index]:
|
||||
self.icon_grid.attach(self.application_eventbox_list[index], 0, 0, 1, 1)
|
||||
index = index + 1
|
||||
# 1, 0
|
||||
if (index < self.application_end) and self.application_eventbox_list[index]:
|
||||
self.icon_grid.attach(self.application_eventbox_list[index], 1, 0, 1, 1)
|
||||
else:
|
||||
self.icon_grid.show_all()
|
||||
return
|
||||
index = index + 1
|
||||
# 2, 0
|
||||
if (index < self.application_end) and self.application_eventbox_list[index]:
|
||||
self.icon_grid.attach(self.application_eventbox_list[index], 2, 0, 1, 1)
|
||||
else:
|
||||
self.icon_grid.show_all()
|
||||
return
|
||||
index = index + 1
|
||||
# 0, 1
|
||||
if (index < self.application_end) and self.application_eventbox_list[index]:
|
||||
self.icon_grid.attach(self.application_eventbox_list[index], 0, 1, 1, 1)
|
||||
else:
|
||||
self.icon_grid.show_all()
|
||||
return
|
||||
index = index + 1
|
||||
# 1, 1
|
||||
if (index < self.application_end) and self.application_eventbox_list[index]:
|
||||
self.icon_grid.attach(self.application_eventbox_list[index], 1, 1, 1, 1)
|
||||
else:
|
||||
self.icon_grid.show_all()
|
||||
return
|
||||
index = index + 1
|
||||
# 2, 1
|
||||
if ((index+1) < self.application_end) and self.application_eventbox_list[index]:
|
||||
''' add next button '''
|
||||
self.icon_grid.attach(self.next_box, 2, 1, 1, 1)
|
||||
else:
|
||||
if (index < self.application_end) and self.application_eventbox_list[index]:
|
||||
self.icon_grid.attach(self.application_eventbox_list[index], 2, 1, 1, 1)
|
||||
self.icon_grid.show_all()
|
||||
|
||||
|
||||
def create_eventbox_back_next(self,back):
|
||||
if back > 0:
|
||||
back_eventbox = _load_image_eventBox(self, "%s/pictures/ST10261_back_button_medium_grey.png" % DEMO_PATH,
|
||||
"BACK", "menu", -1, self.icon_size, self.font_size)
|
||||
back_eventbox.connect("button_release_event", self.on_back_menu_event)
|
||||
back_eventbox.connect("button_press_event", self.highlight_eventBox)
|
||||
return back_eventbox
|
||||
else:
|
||||
next_eventbox = _load_image_eventBox(self, "%s/pictures/ST10261_play_button_medium_grey.png" % DEMO_PATH,
|
||||
"NEXT", "menu", -1, self.icon_size, self.font_size)
|
||||
next_eventbox.connect("button_release_event", self.on_next_menu_event)
|
||||
next_eventbox.connect("button_press_event", self.highlight_eventBox)
|
||||
return next_eventbox
|
||||
|
||||
def on_back_menu_event(self, widget, event):
|
||||
self.create_page_icon_by_page(self.application_start_previous)
|
||||
widget.set_name("normal_bg")
|
||||
widget.set_name("transparent_bg")
|
||||
self.button_exit.show()
|
||||
def on_next_menu_event(self, widget, event):
|
||||
self.create_page_icon_by_page(self.application_start_next)
|
||||
widget.set_name("normal_bg")
|
||||
widget.set_name("transparent_bg")
|
||||
self.button_exit.show()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Signal handler
|
||||
def demo_signal_handler(signum, frame):
|
||||
file_lock_remove(lock_file_path)
|
||||
sys.exit(0)
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
# Main
|
||||
if __name__ == "__main__":
|
||||
# add signal to catch CRTL+C
|
||||
import signal
|
||||
signal.signal(signal.SIGINT, demo_signal_handler)
|
||||
|
||||
if file_is_locked(lock_file_path):
|
||||
print("[ERROR] another instance is running exiting now\n")
|
||||
exit(0)
|
||||
try:
|
||||
win = MainUIWindow()
|
||||
win.connect("delete-event", Gtk.main_quit)
|
||||
win.show_all()
|
||||
Gtk.main()
|
||||
except Exception as exc:
|
||||
print("Main Exception: ", exc)
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
Summary:
|
||||
=======
|
||||
1. Purpose
|
||||
2. Yaml description for each application
|
||||
3. File tree
|
||||
4. Example of application description
|
||||
|
||||
1. Purpose:
|
||||
-----------
|
||||
Propose a generic way to descibe an application and easily add it on the
|
||||
demo_launcher.
|
||||
To permit a better and dynamic management of application, each application are
|
||||
described on a yaml file.
|
||||
|
||||
2. Yaml description for each application:
|
||||
-----------------------------------------
|
||||
Format:
|
||||
Application:
|
||||
Name: <name of application>
|
||||
Description: <description of application>
|
||||
Icon: <icon of application>
|
||||
Type: <script|python>
|
||||
Board:
|
||||
<List|NotList>: <all|list of chip>
|
||||
Script:
|
||||
Exist:
|
||||
<File|Command>: <file or command to verify>
|
||||
Msg_false: <Message to display if <File|Command> are not true
|
||||
Start: <script or application to launch application>
|
||||
Python:
|
||||
Exist:
|
||||
<File|Command>: <file or command to verify>
|
||||
Msg_false: <Message to display if <File|Command> are not true
|
||||
Module: <Python module name to load>
|
||||
Action:
|
||||
button_release_event: <python_start|script_management>
|
||||
button_press_event: highlight_eventBox
|
||||
|
||||
Explaination:
|
||||
- Name: name of application displayed on demo_launcher
|
||||
- Description: description of application displayed on demo_launcher
|
||||
- Icon: icon of application displayed on demo_launcher
|
||||
- Board: define which board the application are compatible
|
||||
List: list of chip/soc the application are compatible
|
||||
NotList: List of chip/soc the application are NOT compatible
|
||||
- Type: Type of script to launch the application,
|
||||
type available:
|
||||
* "script": shell script or application (without parameters) to execute
|
||||
* "python": python script to load for launching application
|
||||
This two type have a specific declaration available: Script, Python
|
||||
- Script: it'a s section for describe the script (shell or application) to
|
||||
launch the application.
|
||||
This section have several sub section:
|
||||
Exist: verify some requirement before to launch start command
|
||||
Start: command to start the application
|
||||
|
||||
Exist section:
|
||||
File: <verify presence of specific file>
|
||||
Command: <command to execute, if return are Ok then we can launch start command>
|
||||
|
||||
- Python: it'a s section for describe the python script to load for accessing
|
||||
to application functionality. The python script must have the function
|
||||
"create_subdialogwindow(<parent window>)"
|
||||
This section have several sub section:
|
||||
Exist: verify some requirement before to launch start command
|
||||
Module: Python module name to load, it's corresponding to path and scirpt
|
||||
name.
|
||||
ex.:
|
||||
path = application/netdata/netdata.py
|
||||
module name = application.netdata.netdata
|
||||
Tips: you need to add an empty file name "__init__.py" on each sub
|
||||
directory to permit to launch the python module
|
||||
|
||||
Exist section:
|
||||
File: <verify presence of specific file>
|
||||
Command: <command to execute, if return are Ok then we can launch start command>
|
||||
|
||||
|
||||
3. File Tree:
|
||||
------------
|
||||
application/
|
||||
├── 000-netdata.yaml
|
||||
├── 010-camera.yaml
|
||||
├── 020-video.yaml
|
||||
├── 030-3d_cube.yaml
|
||||
├── 040-m4_ai.yaml
|
||||
├── 060-bluetooth_audio_output.yaml
|
||||
├── 3d_cube
|
||||
│ ├── bin
|
||||
│ │ └── launch_cube_3D.sh
|
||||
│ └── pictures
|
||||
│ └── ST153_cube_purple.png
|
||||
├── bluetooth
|
||||
│ ├── bluetooth_audio.py
|
||||
│ ├── __init__.py
|
||||
│ ├── pictures
|
||||
│ │ └── ST11012_bluetooth_speaker_light_green.png
|
||||
│ └── wrap_blctl.py
|
||||
├── camera
|
||||
│ ├── bin
|
||||
│ │ └── launch_camera_preview.sh
|
||||
│ ├── pictures
|
||||
│ │ └── ST1077_webcam_dark_blue.png
|
||||
│ └── shaders
|
||||
│ └── edge_InvertLuma.fs
|
||||
├── __init__.py
|
||||
├── m4_ai
|
||||
│ ├── bin
|
||||
│ │ └── launch_AI.sh
|
||||
│ └── pictures
|
||||
│ └── ST7079_AI_neural_pink.png
|
||||
├── netdata
|
||||
│ ├── bin
|
||||
│ │ └── build_qrcode.sh
|
||||
│ ├── __init__.py
|
||||
│ ├── netdata.py
|
||||
│ └── pictures
|
||||
│ └── netdata-icon-192x192.png
|
||||
└── video
|
||||
├── bin
|
||||
│ └── launch_video.sh
|
||||
└── pictures
|
||||
└── Video_playback_logo.pn
|
||||
4. Example of application:
|
||||
-----------------------
|
||||
Example 1:
|
||||
Application:
|
||||
Name: 3D Pict
|
||||
Description: GPU with picture
|
||||
Icon: application/3d_cube/pictures/ST153_cube_purple.png
|
||||
Board:
|
||||
NotList: stm32mp151
|
||||
Type: script
|
||||
Script:
|
||||
Exist:
|
||||
File: /dev/galcore
|
||||
Msg_false: No GPU capabilities to run 3D GPU demo
|
||||
Start: application/3d_cube/bin/launch_cube_3D.sh
|
||||
Action:
|
||||
button_release_event: script_management
|
||||
button_press_event: highlight_eventBox
|
||||
|
||||
Example 2:
|
||||
Application:
|
||||
Name: Camera
|
||||
Description: shader
|
||||
Icon: application/camera/pictures/ST1077_webcam_dark_blue.png
|
||||
Board:
|
||||
List: all
|
||||
Type: script
|
||||
Script:
|
||||
Exist:
|
||||
File: /dev/video0
|
||||
Msg_false: Webcam is not connected,
|
||||
/dev/video0 doesn't exist
|
||||
Start: application/camera/bin/launch_camera_shader.sh
|
||||
Action:
|
||||
button_release_event: script_management
|
||||
button_press_event: highlight_eventBox
|
||||
|
||||
Example 3:
|
||||
Application:
|
||||
Name: Bluetooth
|
||||
Description: speaker
|
||||
Icon: application/bluetooth/pictures/ST11012_bluetooth_speaker_light_green.png
|
||||
Type: python
|
||||
Board:
|
||||
List: stm32mp157 stm32mp153
|
||||
Python:
|
||||
Exist:
|
||||
Command: hciconfig hci0 up
|
||||
Msg_false: Please connect a bluetooth controller on the board
|
||||
Module: application.bluetooth.bluetooth_audio
|
||||
Action:
|
||||
button_release_event: python_start
|
||||
button_press_event: highlight_eventBox
|
||||
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 800 B |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,48 @@
|
||||
#!/bin/sh
|
||||
source /etc/profile.d/weston_profile.sh
|
||||
source /etc/profile.d/pulse_profile.sh
|
||||
|
||||
# wait pulseaudio starting
|
||||
while [ 1 ]; do
|
||||
if [ $(pgrep pulseaudio | wc -l) -ge 1 ]; then
|
||||
break;
|
||||
else
|
||||
sleep 1;
|
||||
fi
|
||||
done
|
||||
|
||||
# this magic line permit to create the link to pulseaudio
|
||||
script -qc 'su -l weston -c "source /etc/profile.d/pulse_profile.sh;pactl info; pactl list sinks"'
|
||||
|
||||
while [ 1 ]; do
|
||||
pactl info
|
||||
if [ $? -eq 0 ]; then
|
||||
break;
|
||||
else
|
||||
sleep 1;
|
||||
fi
|
||||
done
|
||||
if [ -f /usr/bin/pulseaudio_hdmi_switch.sh ]; then
|
||||
/usr/bin/pulseaudio_hdmi_switch.sh
|
||||
else
|
||||
pactl info; pactl list sinks
|
||||
|
||||
cards=`pactl list cards | egrep -i 'alsa.card_name' | sed 's/ //g'| sed 's/alsa.card_name=\"//g'| sed 's/\"//g'`
|
||||
index=0
|
||||
for i in $cards;
|
||||
do
|
||||
found=`echo $i | grep -n STM32MP | wc -l`
|
||||
if [ $found -eq 1 ];
|
||||
then
|
||||
pactl set-card-profile $index output:analog-stereo
|
||||
fi
|
||||
index=$((index+1))
|
||||
done
|
||||
fi
|
||||
# force pulseaudio sink and source to be on SUSPENDED state (cf: pactl list sinks)
|
||||
pactl suspend-source 0
|
||||
for sink in $(pactl list short sinks | awk '{ print $1 }'); do
|
||||
pactl suspend-sink $sink
|
||||
done
|
||||
|
||||
/usr/local/demo/demo_launcher.py
|
||||