diff --git a/Manual.org b/Manual.org index 1527d2b..9457218 100644 --- a/Manual.org +++ b/Manual.org @@ -6028,104 +6028,7 @@ This systemd unit for the user session starts (and stops) kodi. #+INCLUDE: "roles/kodi/templates/kodi.service.j2" src conf **** set-kodi-diplay This is a version-dependent script to force KODI to use the display set by the environment variable ~DISPLAY~. The following Version is intended for KODI 18. -#+BEGIN_SRC jinja2 :tangle roles/kodi/templates/set-kodi-display.j2 :padline no -#!/usr/bin/env python3 -""" -{{ ansible_managed }} - -This Script changes the monitor in KODI's guisettings.xml to the wanted output -according to the DISPLAY environment variable. It works with KODI 18 (not KODI 17!). -""" - -import os -import sys -import subprocess -import xml.etree.ElementTree as ET - - -GUISETTINGS = '/var/lib/vdr/.kodi/userdata/guisettings.xml' -CACHE_DIR = '/var/lib/vdr/.kodi/.display_cache' -VIDEOSCREEN_TEMPLATE = """ - {} -""" - - -def get_output_names(): - """ - get display name from xrandr output for given DISPLAY environment variable - """ - xrandr_output = [ - l for l in subprocess.check_output( - ["xrandr"], - env={"DISPLAY": os.environ["DISPLAY"]} - ).decode("utf-8").splitlines() - ] - return [l.split()[0] for l in xrandr_output if " connected " in l] - - -def parse_template(template_path, template, output=""): - """read videoscreen settings from backup or create a stub file""" - try: - xml_tree = ET.parse(template_path) - except FileNotFoundError: - print("{} not found, creating stub file".format(template_path)) - xml_template = ET.fromstring(template.format(output)) - xml_tree = ET.ElementTree(xml_template) - finally: - xml_tree.write(template_path) - return xml_tree - - -def backup_videoscreen(): - """parse guisettings.xml for display name an backup videoscreen data""" - tree = parse_template(GUISETTINGS, VIDEOSCREEN_TEMPLATE, "Default") - root = tree.getroot() - videoscreen = root.find("./setting[@id='videoscreen.monitor']") - output = videoscreen.text - xml_path = os.path.join(CACHE_DIR, '{}-videodevice.xml'.format(output)) - base_tree = ET.fromstring('') - xml_tree = ET.ElementTree(base_tree) - backup_root = xml_tree.getroot() - backup_root.insert(0, videoscreen) - xml_tree.write(xml_path) - print("written backup for {} to {}".format(output, xml_path)) - - -def change_videoscreen(output, new_videoscreen): - """change videoscreen node to content of backup file""" - tree = parse_template(GUISETTINGS, VIDEOSCREEN_TEMPLATE, output) - root = tree.getroot() - - videoscreen = root.find('./setting[@id="videoscreen.monitor"]') - if videoscreen is not None: - videoscreen.text = new_videoscreen.text - else: - videoscreen = root.find("./settings") - root.insert(0, new_videoscreen) - tree.write(GUISETTINGS) - return tree - -if __name__ == '__main__': - output = get_output_names()[0] - if not output: - sys.exit("Error: no screen name found") - try: - os.makedirs(CACHE_DIR, exist_ok=True) - except PermissionError: - sys.exit("Error: insufficient permissions to create cachedir {}".format( - CACHE_DIR)) - try: - backup_videoscreen() - except FileNotFoundError: - print("{} does not exist".format(GUISETTINGS)) - except Exception as e: - print("Could not backup videoscreen.monitor:", str(e)) - xml_path = os.path.join(CACHE_DIR, '{}-videodevice.xml'.format(output)) - videodir_xml = parse_template(xml_path, VIDEOSCREEN_TEMPLATE, output) - videodir_root = videodir_xml.getroot() - new_videoscreen = videodir_root.find("./setting[@id='videoscreen.monitor']") - guisettings_xml = change_videoscreen(output, new_videoscreen) -#+END_SRC +#+INCLUDE: "roles/kodi/templates/set-kodi-display.j2" src python *** files :PROPERTIES: :ID: 58c5c693-bd24-420a-bfed-79771e8e0d47 diff --git a/roles/kodi/tasks/install-kodi.yml b/roles/kodi/tasks/install-kodi.yml index b964d03..cc29500 100644 --- a/roles/kodi/tasks/install-kodi.yml +++ b/roles/kodi/tasks/install-kodi.yml @@ -5,6 +5,7 @@ name: - kodi - kodi-pvr-vdr-vnsi + - python3-lxml state: present install_recommends: no diff --git a/roles/kodi/templates/set-kodi-display.j2 b/roles/kodi/templates/set-kodi-display.j2 index dc8fbec..147e908 100644 --- a/roles/kodi/templates/set-kodi-display.j2 +++ b/roles/kodi/templates/set-kodi-display.j2 @@ -4,40 +4,64 @@ This Script changes the monitor in KODI's guisettings.xml to the wanted output according to the DISPLAY environment variable. It works with KODI 18 (not KODI 17!). + +In order to change the display we need to modify the settings/videoscreen nodes. + +Basic algorithm: + - get the current videoscreen.monitor + - check if it needs to be changed + - create a backup of the videoscreen nodes in /var/lib/vdr/.kodi/.display_cache/{CONNETOR}-videoscreen.xml + - check if there is an existing backup for the new CONNECTOR + - parse the backup of the videoscreen nodes + - replace the videoscreen nodes with the backup data """ +import copy import os import sys import subprocess -import xml.etree.ElementTree as ET +from lxml import etree as ET GUISETTINGS = '/var/lib/vdr/.kodi/userdata/guisettings.xml' CACHE_DIR = '/var/lib/vdr/.kodi/.display_cache' VIDEOSCREEN_TEMPLATE = """ - {} + {} """ -def get_output_names(): +def create_cache_dir(): + try: + os.makedirs(CACHE_DIR, exist_ok=True) + except PermissionError: + sys.exit(f"Error: insufficient permissions to create cachedir {CACHE_DIR}") + except Exception as e: + sys.exit(f"Unexpected Error when trying to create {CACHE_DIR}:", e) + + + +def get_output_name(): """ get display name from xrandr output for given DISPLAY environment variable """ - xrandr_output = [ - l for l in subprocess.check_output( - ["xrandr"], - env={"DISPLAY": os.environ["DISPLAY"]} - ).decode("utf-8").splitlines() - ] - return [l.split()[0] for l in xrandr_output if " connected " in l] + try: + xrandr_output = [ + l for l in subprocess.check_output( + ["xrandr"], + env={"DISPLAY": os.environ["DISPLAY"]} + ).decode("utf-8").splitlines() + ] + return next(l.split()[0] for l in xrandr_output if " connected " in l) + except Exception as e: + sys.exit("could not determine output name", e) def parse_template(template_path, template, output=""): """read videoscreen settings from backup or create a stub file""" try: xml_tree = ET.parse(template_path) - except FileNotFoundError: - print("{} not found, creating stub file".format(template_path)) + except OSError: + print(f"{template_path} not found, creating stub file", file=sys.stderr) xml_template = ET.fromstring(template.format(output)) xml_tree = ET.ElementTree(xml_template) finally: @@ -45,52 +69,49 @@ def parse_template(template_path, template, output=""): return xml_tree -def backup_videoscreen(): - """parse guisettings.xml for display name an backup videoscreen data""" - tree = parse_template(GUISETTINGS, VIDEOSCREEN_TEMPLATE, "Default") - root = tree.getroot() - videoscreen = root.find("./setting[@id='videoscreen.monitor']") - output = videoscreen.text - xml_path = os.path.join(CACHE_DIR, '{}-videodevice.xml'.format(output)) +def main(output): + guisettings = parse_template(GUISETTINGS, VIDEOSCREEN_TEMPLATE, "Default") + + # parse guisettings Etree for display name an backup videoscreen data + root = guisettings.getroot() + old_output = root.find("./setting[@id='videoscreen.monitor']").text + if old_output == output: + print("no changes necessary, exiting", file=sys.stderr) + sys.exit() + + # create a minimal guisettings etree + xml_path = os.path.join(CACHE_DIR, f'{old_output}-videoscreen.xml') base_tree = ET.fromstring('') xml_tree = ET.ElementTree(base_tree) backup_root = xml_tree.getroot() - backup_root.insert(0, videoscreen) + + # copy videoscreen elements to backup etree + videoscreen_elements = root.xpath( + "./setting[starts-with(@id, 'videoscreen.')]") + for element in videoscreen_elements: + backup_root.append(copy.deepcopy(element)) + element.getparent().remove(element) xml_tree.write(xml_path) - print("written backup for {} to {}".format(output, xml_path)) + print(f"written backup for {old_output} to {xml_path}", file=sys.stderr) - -def change_videoscreen(output, new_videoscreen): - """change videoscreen node to content of backup file""" - tree = parse_template(GUISETTINGS, VIDEOSCREEN_TEMPLATE, output) - root = tree.getroot() - - videoscreen = root.find('./setting[@id="videoscreen.monitor"]') - if videoscreen is not None: - videoscreen.text = new_videoscreen.text - else: - videoscreen = root.find("./settings") - root.insert(0, new_videoscreen) - tree.write(GUISETTINGS) - return tree - -if __name__ == '__main__': - output = get_output_names()[0] - if not output: - sys.exit("Error: no screen name found") - try: - os.makedirs(CACHE_DIR, exist_ok=True) - except PermissionError: - sys.exit("Error: insufficient permissions to create cachedir {}".format( - CACHE_DIR)) - try: - backup_videoscreen() - except FileNotFoundError: - print("{} does not exist".format(GUISETTINGS)) - except Exception as e: - print("Could not backup videoscreen.monitor:", str(e)) - xml_path = os.path.join(CACHE_DIR, '{}-videodevice.xml'.format(output)) + # change videoscreen node to content of backup file + xml_path = os.path.join(CACHE_DIR, f'{output}-videoscreen.xml') videodir_xml = parse_template(xml_path, VIDEOSCREEN_TEMPLATE, output) videodir_root = videodir_xml.getroot() - new_videoscreen = videodir_root.find("./setting[@id='videoscreen.monitor']") - guisettings_xml = change_videoscreen(output, new_videoscreen) + videoscreen = videodir_root.find("./setting[@id='videoscreen.monitor']") + # copy videoscreen.* elements from Backup + videoscreen_elements = videodir_root.xpath( + "./setting[starts-with(@id, 'videoscreen.')]") + for element in videoscreen_elements: + new_element = copy.deepcopy(element) + root.append(new_element) + guisettings.write(GUISETTINGS) + + +if __name__ == '__main__': + create_cache_dir() + output = get_output_name() + try: + main(output) + except Exception as e: + print("Could not change videoscreen.* settings:", str(e), file=sys.stderr)