diff --git a/template/calfbox/py/metadata.py b/template/calfbox/py/metadata.py index cf6a4d4..f088ec0 100644 --- a/template/calfbox/py/metadata.py +++ b/template/calfbox/py/metadata.py @@ -7,6 +7,8 @@ This file implements the JackIO Python side of Jack Medata as described here: """ +import os.path + #get_thing from calfbox._cbox2 import do_cmd @@ -91,9 +93,73 @@ class Metadata: for port, index in pDict.items(): Metadata.set_port_order(port, index) - + @staticmethod def set_pretty_name(port, name): """port is the portname as string including a client name System:out_1 Name however is just the port name, without a client.""" Metadata.set_property(port, "http://jackaudio.org/metadata/pretty-name", name) + + + @staticmethod + def _set_icon_name(port, freeDeskopIconName): + """Internal function used in set_icon_small and set_icon_large""" + if not os.path.splitext(freeDeskopIconName)[0] == freeDeskopIconName: + raise ValueEror(f"Icon name must not have a file extension. Expected {os.path.splitext(freeDeskopIconName)[0]} but was {freeDeskopIconName}") + + if not os.path.basename(freeDeskopIconName) == freeDeskopIconName: + raise ValueError(f"Icon name must not be path. Expected {os.path.basename(freeDeskopIconName)} but was {freeDeskopIconName}") + + self.set_property(port, "http://jackaudio.org/metadata/icon-name", freeDeskopIconName) + + + @staticmethod + def set_icon_small(port, freeDeskopIconName, base64png): + """ A value with a MIME type of "image/png;base64" that is an encoding of an + NxN (with 32 < N <= 128) image to be used when displaying a visual representation of that + client or port. + + The name of the icon for the subject (typically client). + This is used for looking up icons on the system, possibly with many sizes or themes. Icons + should be searched for according to the freedesktop Icon + Theme Specification: + https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + + The small icon of our JACK client. + Setting icons to ports seems to be technically possible, but this is not the function + for port-icons. + + This function does not check if base64png has the correct format. + + This also sets the name of the icon according to freedesktop specs. + The name is the basename without extension like so: + /usr/share/icons/hicolor/32x32/apps/patroneo.png -> "patroneo" + """ + + self.set_property(port, "http://jackaudio.org/metadata/icon-small", base64png, jackPropertyType="image/png;base64") + self._set_icon_name(port, freeDeskopIconName) + + @staticmethod + def set_icon_large(base64png): + """ A value with a MIME type of "image/png;base64" that is an encoding of an + NxN (with N <=32) image to be used when displaying a visual representation of that client + or port. + + The name of the icon for the subject (typically client). + This is used for looking up icons on the system, possibly with many sizes or themes. Icons + should be searched for according to the freedesktop Icon + Theme Specification: + https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + + The large icon of our JACK client. + Setting icons to ports seems to be technically possible, but this is not the function + for port-icons. + + This function does not check if base64png has the correct format. + + This also sets the name of the icon according to freedesktop specs. + The name is the basename without extension like so: + /usr/share/icons/hicolor/32x32/apps/patroneo.png -> "patroneo" + """ + self.set_property(port, "http://jackaudio.org/metadata/icon-large", base64png, jackPropertyType="image/png;base64") + self._set_icon_name(port, freeDeskopIconName) diff --git a/template/documentation/build.py b/template/documentation/build.py index f07fff2..4d7902f 100644 --- a/template/documentation/build.py +++ b/template/documentation/build.py @@ -28,6 +28,7 @@ from os import getcwd import os.path assert os.path.exists(os.path.join(getcwd(), __file__)), (getcwd(), __file__) import datetime +import base64 #Readme @@ -64,28 +65,28 @@ with open ("../../documentation/index.adoc", "w") as w: #print ("Built /documentation/index.adoc. You still need to run /documentation/build-documentation.sh manually") #print ("Built /documentation/index.adoc") -#Documentation +#Documentation METADATA["supportedLanguages"].update({"English":""}) for language in METADATA["supportedLanguages"].keys(): - - language = language.lower() - + + language = language.lower() + try: with open(f"{language}.adoc.template", "r") as r: - template = r.read() + template = r.read() except: continue #language not yet supported as manual - + for key, value in METADATA.items(): #all strings if type(value) is str: - template = template.replace(f"<{key}>", value) + template = template.replace(f"<{key}>", value) if language == "english": - template = template.replace("", "== Introduction\n\n" + METADATA["description"]) - + template = template.replace("", "== Introduction\n\n" + METADATA["description"]) - with open (f"../../documentation/{language}.part.adoc", "r") as clientPart: + + with open (f"../../documentation/{language}.part.adoc", "r") as clientPart: template = template.replace("", clientPart.read()) with open (f"../../documentation/{language}.adoc", "w") as w: @@ -115,11 +116,11 @@ settings and save directories. Other modes of operations, mostly for testing, are: -Run without session management and save in /tmp. +Run without session management and save in /tmp. {METADATA["shortName"]} --save /tmp Run without audio and midi. Skips all JACK checks. Used to just look at the GUI, e.g. to make screenshots - {METADATA["shortName"]} --mute + {METADATA["shortName"]} --mute [see also] The full documentation for {METADATA["name"]} is maintained as a multi-lingual html site to your systems doc-dir. @@ -136,5 +137,21 @@ command = f"help2man ../../{METADATA['shortName']} --no-info --include ../../doc subprocess.run(command, capture_output=True, text=True, shell=True) + + +#Convert 32x32 and 128x128 icons to base64 utf8 strings for jack metadata +txt_icon_32_base64_utf8 = os.path.join("../../engine/resources/", "icon_32_base64_utf8.txt") +with open (txt_icon_32_base64_utf8, "w") as icon32txt: + with open("../../desktop/images/32x32.png", "rb") as icon32png: + icon32txt.write(base64.b64encode(icon32png.read()).decode('utf-8')) + +txt_icon_128_base64_utf8 = os.path.join("../../engine/resources/", "icon_128_base64_utf8.txt") +with open (txt_icon_128_base64_utf8, "w") as icon128txt: + with open("../../desktop/images/128x128.png", "rb") as icon128png: + icon128txt.write(base64.b64encode(icon128png.read()).decode('utf-8')) + + +#Done. Tell the world: + #print ("Built /documentation. You still need to run /documentation/build-documentation.sh manually") print ("Built template part of documentation.") diff --git a/template/engine/session.py b/template/engine/session.py index 4da4803..3d2801d 100644 --- a/template/engine/session.py +++ b/template/engine/session.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python3 +#/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net ) @@ -27,6 +27,7 @@ import os.path import json import atexit from sys import exit as sysexit +import base64 #for jack metadata icon #Third Party Modules from calfbox import cbox @@ -34,6 +35,7 @@ from calfbox import cbox #Our Template Modules from .history import History from .duration import DB, DL, D1, D2, D4, D8, D16, D32, D64, D128, MAXIMUM_TICK_DURATION +from ..start import PATHS #User Data from engine.config import * @@ -114,6 +116,9 @@ class Session(object): logger.error("Will not load or save because: " + e.__repr__()) if not self.data: self.data = Data(parentSession = self) + + self.sendJackMetadataIcon() + logger.info("New/Open session complete") def openFromJson(self, absoluteJsonFilePath): @@ -183,3 +188,27 @@ class Session(object): logger.info("@atexit: Calfbox Audio stopped ") cbox.shutdown_engine() logger.info("@atexit: Calfbox Engine shutdown ") + + + def sendJackMetadataIcon(self): + """Convert our icon to base64 UTF-8 and send it to jack metadata. + Actually, the icon is already encoded in our codebase so we don't have to locate the file + on the users disk when installed + + Sent once at the end of self.nsm_openOrNewCallback + """ + #testData = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAAAAABWESUoAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAgcAAAIHASw6dZwAAAAHdElNRQfkBBEHMwXdauI0AAABrElEQVQ4y4XTv0vbQRzG8fc3Jv5WqqUKRvjaaKE6pE5RBFHBdlIQSp3yB3QouDh0q05K0UUzuZQMLdh/wFKhKEIERR1EOwlVxGhaUyqRSox5HBI1F5N423Gv4z6fu+fgwaGsEXqaObsPTtx2QXDVT21B8IEq/hQA3x1NY4QygcMoOOx3fX3Ej7xdJPqYVRBf3iPGGZa2sLbzgKWiZ2fSZTn+3CDiLtmQpCGK1nOB5CAzkqRF8MZzgI8MJVOyAybug1WXHZUkxffmoexnNvhnu0KS9PddNQC+RBYYYVKSfnmsDv/rJ8CcCY5LfFeS/nvbdiVdjEJT0gDTLEjSVMVRamMPrBjgVWVCkpobWgd2JCkAIwZw90rSPkDjuaQ96DRe87cNEAY43AQaLQ5TK2lgxQBanIDVABTXEDWAOwLweNIBbz0AcZxGHt6UnkmStma+SZLC0GwU+YlZI3qfYdAAsZr600zQDQHzqqd4eX63HoSKsAkuu+m6jftaGbzPfu7j59ROpCr9Ug3tsTSwULqbyPAyTu8L18XSAdSt2ekLyshkIlB/07rnNtcPf/5rhItvET2iDPMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDQtMTdUMDc6NTE6MDUrMDA6MDAudbalAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA0LTE3VDA3OjUxOjA1KzAwOjAwXygOGQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" + + logger.info("Sending program icons to jack metadata") + + icon_32_base64_utf8 = os.path.join(PATHS["share"], "icon_32_base64_utf8.txt") + assert os.path.exists(icon_32_base64_utf8), icon_32_base64_utf8 + with open(icon_32_base64_utf8, "r") as fSmall: + cbox.JackIO.Metadata.set_icon_small(fSmall.read()) + + icon_128_base64_utf8 = os.path.join(PATHS["share"], "icon_128_base64_utf8.txt") + assert os.path.exists(icon_128_base64_utf8), icon_128_base64_utf8 + with open(icon_128_base64_utf8, "r") as fLarge: + cbox.JackIO.Metadata.set_icon_large(fLarge.read()) + + cbox.JackIO.Metadata.set_icon_name(METADATA["shortName"]) diff --git a/template/start.py b/template/start.py index 2fe80f2..819f3d2 100644 --- a/template/start.py +++ b/template/start.py @@ -50,11 +50,11 @@ parser.add_argument("-V", "--verbose", action='store_true', help="(Development) args = parser.parse_args() import logging -if args.verbose: - logging.basicConfig(level=logging.INFO) #development +if args.verbose: #development + logging.basicConfig(level=logging.INFO, format='[' + METADATA["shortName"] + '] %(levelname)s %(asctime)s %(name)s: %(message)s',) #logging.getLogger().setLevel(logging.INFO) #development -else: - logging.basicConfig(level=logging.ERROR) #production +else: #production + logging.basicConfig(level=logging.ERROR, format='[' + METADATA["shortName"] + '] %(levelname)s %(asctime)s %(name)s: %(message)s',) #logging.getLogger().setLevel(logging.ERROR) #production logger = logging.getLogger(__name__)