Ansible Playbooks for yaVDR 0.7

Inhaltsverzeichnis

1 User Stories

1.1 yavdr-full

A User wants to install yaVDR without customization and relies on full automation.

After installing a supported Ubuntu Server version and (if needed) setting up the network connection, the install script is downloaded and started.

The install script adds the ansible PPA, installs ansible and other basic dependencies and runs the automated installation process.

Several roles are used to tie everything together:

yavdr-common
installs the basic set of packages needed and preconfigures the system (package sources, package manager settings, entires for network ports, directory structure etc.). Also collects information about the system
autoinstall-ubuntu-drivers
uses Ubuntu’s automatic detection for additional drivers (e.g. proprietary nvidia driver, microcode updates, VirtualBox guest additions)
vdr
install the vdr package and required plugins
pulseaudio
install and preconfigure pulseaudio
yavdr-network
configure WOL, add nfs client capabilites, install avahi-linker
samba-server
install and preconfigure a samba server
nfs-server
install nfs-kernelserver and preconfigure nfs exports (and avahi-announcements)
autoinstall-satip
install vdr-plugin-satip if a Sat>IP server has been found
autoinstall-targavfd
install vdr-plugin-targavfd if display is connected
autoinstall-imonlcd
install vdr-plugin-imonlcd if a matchind display is connected
yavdr-xorg
install and configure a systemd user session with a running X-server. A frontend script will manage VDR’s frontend and other applications like KODI. Also choose the best possible output plugin.
grub-config
update grub configuration based on executed roles

2 Introduction

One of the major problems we faced with customized Ubuntu ISO files as installation media for prior yaVDR versions has been the limited hardware support and the time consuming process to create and update them. An interesting alternative to this approach is to enable the user to choose the installation medium by himself, so point releases, kernel versions and additional drivers can be chosen deliberately. After the basic setup is complete (and a working internet connection is available), a fully customizable install script completes the yaVDR installation.

3 Installing and configuring yaVDR with Ansible

This is an experimental feature which allows to set up a yaVDR installation based on a normal Ubuntu Server 16.04.x installation using Ansible.

This Manual is written in org-mode for Emacs and can rewrite the complete ansible configuration if you call org-babel-tangle from within emacs.

To use this playbook on a Ubuntu Server Installation you need to run the following commands:

sudo apt-get install git
git clone https://github.com/yavdr/yavdr-ansible.git
cd yavdr-ansible
git checkout bionic
sudo ./install-yavdr.sh

We use a callback to generate tags for all roles autmatically:

[defaults]
callback_plugins = plugins/callbacks
callback_whitelist = auto_tags
ansible_managed = *** ANSIBLE MANAGED FILE ***
  template: {file}

3.1 Install scripts for local usage

set -e
if (( $EUID != 0 )); then
    echo "This script must be run using sudo -H or as root"
    exit
fi

# update packages
apt update
apt -y install software-properties-common
add-apt-repository -y ppa:ansible/ansible-2.7

# install required packages
apt-get -y install --no-install-recommends ansible python-jmespath

# TODO: run ansible on local host
ansible-playbook yavdr07.yml -b -i 'localhost_inventory' --connection=local --tags="all"
set -e
if (( $EUID != 0 )); then
    echo "This script must be run using sudo -H or as root"
    exit
fi

# update packages
apt update
apt -y install software-properties-common
add-apt-repository -y ppa:ansible/ansible-2.7

# install required packages
apt-get -y install --no-install-recommends ansible python-jmespath

# TODO: run ansible on local host
ansible-playbook yavdr07-headless.yml -b -i 'localhost_inventory' --connection=local --tags="all"

4 Playbooks

4.1 yavdr07.yml

The yavdr07.yml playbook sets up a fully-featured yaVDR installation:

---
# file: yavdr07.yml
# this playbook sets up a complete yaVDR 0.7 installation

- name: set up yaVDR
  hosts: all
  become: true
  roles:
     - yavdr-common               # install and configure the basic system
     - collect-facts              # query system facts
     - autoinstall-ubuntu-drivers # use ubuntu-drivers to install proprietary dirvers
                                  # (e.g. nvidia, virtualbox)
     # - nvidia-experimental       # install very recent nvidia dirver from ppa:graphics-drivers/ppa
     - vdr                        # install vdr and related packages
     - autoinstall-virtualbox-guest
     - yavdr-network              # enable network client capabilities
     - samba-install              # install samba server
     - samba-config               # configure samba server
     - nfs-server                 # install nfs server
     - pulseaudio                 # install pulseaudio
     - yavdr-xorg                 # graphical session
     - yavdr-remote               # remote configuration files, services and scripts
     - yavdr-desktop              # openbox session with yavdr frontend script
     - autoinstall-satip          # install vdr-plugin-satip if a Sat>IP server has been found
     - autoinstall-targavfd       # install vdr-plugin-targavfd if display is connected
     - autoinstall-imonlcd        # install vdr-plugin-imonlcd if a matchind display is connected
     - autoinstall-pvr350         # install vdr-plugin-pvr350 if a matching card is detected
     - autoinstall-hauppauge-pvr  # install vdr-plugin-pvrinput if a matching card is found
     - autoinstall-dvbsddevice    # install vdr-plugin-dvbsddevice if a matching card is detected
     - autoinstall-hardware-irmp  # install yavdr-hardware-irmp if a matching usb device is detected
     - autoinstall-atric-usb      # preconfigure lircd for Atric IR-WakeupUSB receiver
     - autoinstall-yausbir        # preconfigure lircd for yaUsbIR receiver
     - autoinstall-dvbsky-firmware # download and install required firmware files for dvbsky cards
     - autoinstall-firmware       # download and install firmware files for dvb devices
     #- serial-ir                  # configure a serial port for "homebrew" ir receivers (e.g. Atric V5)
     #- epgd                       # install vdr-epg-daemon (and vdr-plugin-epg2vdr)
     - kodi
     - dvd                        # set up packages and a udev rule to allow kodi and other players
                                  # to play and eject optical media
     - wakeup                     # set up wakeup methods for rtc etc.
     - grub-config                # configure grub

  handlers:
    - import_tasks: handlers/main.yml

4.2 yavdr07-headless.yml

For a headless server installation yavdr07-headless.yml is a good choice

---
# file: yavdr07-headless.yml
# this playbook set up a headless yaVDR 0.7 installation

- name: set up a headless yaVDR server
  hosts: all
  become: true
  roles:
    - yavdr-common
    - collect-facts              # query system facts
    - vdr
    - yavdr-network
    - samba-install
    - samba-config
    - nfs-server
    - grub-config
    - autoinstall-satip          # install vdr-plugin-satip if a Sat>IP server has been found
    - autoinstall-pvr350         # install vdr-plugin-pvr350 if a matching card is detected
    - autoinstall-hauppauge-pvr  # install vdr-plugin-pvrinput if a matching card is found
    - autoinstall-dvbsddevice    # install vdr-plugin-dvbsddevice if a matching card is detected
    - autoinstall-dvbsky-firmware # download and install required firmware files for dvbsky cards
    - autoinstall-firmware       # download and install firmware files for dvb devices
    #- epgd                      # install vdr-epg-daemon
    - wakeup
  tags:
    - always
  handlers:
    - import_tasks: handlers/main.yml

5 Hosts

This playbook can either be used to run the installation on the localhost or any other PC in the network that can be accessed via ssh. Simply add the host names or IP addresses to the hosts file in the respective section:

[local]
localhost connection=local

6 Group Variables

6.1 PPAs

---
# file: group_vars/all

branch: experimental
ppa_owner: 'ppa:yavdr'
# add the following PPAs
repositories:
  - '{{ ppa_owner }}/{{branch}}-main'
  - '{{ ppa_owner }}/{{branch}}-vdr'
  - '{{ ppa_owner }}/{{branch}}-kodi'
  #- '{{ ppa_owner }}/{{branch}}-yavdr'

6.2 VDR user, directories, special configuration and plugins

# properties of the user vdr and vdr-related options
# NOTE: user name, uid and confdir must match the values set by the vdr package
vdr:
  user: vdr
  group: vdr
  uid: 666
  gid: 666
  home: /var/lib/vdr
  etc_confdir: /etc/vdr
  confdir: /var/lib/vdr
  recdir: /srv/vdr/video
  hide_first_recording_level: false
  safe_dirnames: true  # escape characters (useful for windows clients and FAT/NTFS file systems)
  override_vdr_charset: false

# copy channels.conf from a local file
# vdr_channels_conf: /path/to/channels.conf

# download channels.conf from a given url (supports HTTP(S) and FTP)
# vdr_channels_conf_url: http://example.com/vdr/channels.conf

# add the vdr plugins you want to install
vdr_plugins:
  - vdr-plugin-devstatus
  - vdr-plugin-markad
  - vdr-plugin-softhddevice-vpp

# IP (range) filter for vdr and plugins (this must be an array):
# vdr_allowed_hosts:
#   - 192.168.0.0/24
#
# hosts and subnets for svdrphosts.conf (overrides vdr_allowed_hosts):
# vdr_svdrphosts:
#   - 192.168.0.0/24
#
# hosts and subnets for allowed_hosts.conf of xineliboutput (overrides vdr_allowed_hosts):
# xineliboutput_allowed_hosts:
#   - 192.168.0.0/24
#
# hosts and subnets for allowed_hosts.conf of vnsiserver (overrides vdr_allowed_hosts):
# vnsiserver_allowed_hosts:
#   - 192.168.0.0/24
#
# hosts and subnets for streamdevhosts.conf (overrides vdr_allowed_hosts):
# streamdev_server_allowed_hosts:
#   - 192.168.0.0/24

6.3 Media directories

# dictionary of directories for (shared) files. Automatically exported via NFS and Samba if those roles are enabled
media_dirs:
  audio: /srv/audio
  video: /srv/video
  pictures: /srv/picture
  files: /srv/files
  backups: /srv/backups
  recordings: '{{ vdr.recdir }}'

6.4 NFS

nfs:
  insecure: false  # set to true for OS X clients or if you plan to use libnfs as unprivileged user (e.g. KODI)

6.5 Samba

samba:
  workgroup: YAVDR
  windows_compatible: '{{ vdr.safe_dirnames }}'  # set to true to disable unix extensions, enable follow symlinks and wide links

6.6 Additional packages

# additional packages you want to install
extra_packages:
  - bpython
  - bpython3
  - htop
  - tree
  - vim
  - w-scan
  - t2scan
  - plymouth-theme-yavdr-logo
  - vdr-addon-lifeguard-ng
  - vdrpbd

6.7 System pre-configuration

frontend: vdr

# vdr shutdown command - SHUTDOWNCMD variable in /etc/default/vdr
# for standby use "/bin/systemctl suspend"
vdr_shutdown_command: poweroff

#system:
#  shutdown: poweroff
wakeup_method: acpiwakeup
grub:
  timeout: 0
  boot_options: quiet splash

6.8 Serial IR

# Serial device to configure for a homebrew receiver.
# Choose either ttyS0 (COM1) or ttyS1 (COM2)
# Also ensure that the role serial-ir is enabled in yavdr07.yml 
serial_ir_device: ttyS0

7 Roles

7.1 install-dependencies

7.1.1 tasks

  1. main.yml
    ---
    
    - name: apt | install packages
      apt:
        name: '{{ packages }}'
        state: present
        install_recommends: no
        cache_valid_time: 60
    

7.2 nvidia experimental drivers

install nvidia-396 from ppa:graphics-drivers/ppa

7.2.1 tasks

  1. main.yml
    ---
    
    - name: add ppa:graphics-drivers/ppa
      apt_repository:
          repo: 'ppa:graphics-drivers/ppa'
          state: present
          update_cache: yes
      tags:
        - ppa
    
    - name: install nvidia-396 and other required packages
      apt:
        name:
          - nvidia-396
          - nvidia-settings
        state: present
        install_recommends: no
      tags:
        - packages
    

7.3 yavdr-common

This role is used to set up a basic yaVDR installation. It creates the directories, installs the vdr and other useful packages.

7.3.1 default variables

This section is for reference only, please use the files in global_vars for customizations.

---
# file: roles/yavdr-common/defaults/main.yml
  1. Repositories

    You can set a list of package repositories which provide the necessary packages. Feel free to use own PPAs if you need special customization to the VDR and it’s plugins.

    branch: unstable
    repositories:
      - 'ppa:yavdr/main'
      - 'ppa:yavdr/unstable-main'
      - 'ppa:yavdr/{{branch}}-vdr'
      - 'ppa:yavdr/{{branch}}-kodi'
      - 'ppa:yavdr/{{branch}}-yavdr'
    
  2. Drivers

    Automatically installed drivers can be very useful, but if you know you need a certain driver, you can simply set it’s value to true. If you don’t want a driver to be installed, set it’s value to false.

    drivers:
      sundtek: auto
      ddvb-dkms: auto
    
  3. Additional Packages

    Add additional packages you would like to have on your installation to this list

    extra_packages:
      - vim
      - tree
      - w-scan
    
  4. VDR

    This section allows you to set the recording directory, the user and group that runs the vdr and it’s home directory.

    user
    the vdr user name
    group
    the main group for the user vdr
    uid
    the user id for the user vdr
    gid
    the group id for the group vdr
    home
    the home directory for the user vdr
    recdir
    the recording directory used by VDR
    hide_first_recording_level
    let vdr hide the first directory level of it’s recording directory so the content of multiple directories is shown merged together
    safe_dirnames
    replace special characters which are not compatible with Windows file systems and Samba shares
    override_vdr_charset
    workaround for channels with weird EPG encodings, e.g. Sky
    vdr:
        user: vdr
        group: vdr
        uid: 666
        gid: 666
        home: /var/lib/vdr
        recdir: /srv/vdr/video
        hide_first_recording_level: false
        safe_dirnames: true
        override_vdr_charset: false
    vdr_allowed_hosts: []
    

7.3.2 tasks

yavdr-common executes the following tasks:

  1. main.yml
    ---
    # file: roles/yavdr-common/tasks/main.yml 
    
    - name: basic installation
      block:
            - import_tasks: configure_apt.yml
            - import_tasks: configure_system.yml
            - import_tasks: create_directories.yml
            - import_tasks: create_sudoers.yml
            - import_tasks: standby_support.yml
      tags: [install]
    
    1. Disable default installation of recommended packages

      This task prevents apt to automatically install all recommended dependencies for packages:

      ---
      
      - name: apt | prevent automatic installation of recommended packages
        template:
          src: templates/90-norecommends.j2
          dest: /etc/apt/apt.conf.d/90norecommends
      
      
    2. Set up package repositories
      - name: add PPAs
        apt_repository:
            repo: '{{ item }}'
            state: present
            update_cache: no
        with_items: '{{ repositories }}'
        tags:
          - ppa
      
      - name: run apt-get dist-upgrade
        apt:
            upgrade: dist
            update_cache: yes
      
    3. Use bash instead of dash
      - name: use bash instead of dash
        shell: |
          echo "set dash/sh false" | debconf-communicate
          dpkg-reconfigure -f noninteractive dash
      
    4. create user vdr
    5. Disable release-upgrade notifications
      1. SOMEDAY move from lineinfile to template
        - name: disable release-upgrade notifications
          lineinfile:
            dest: /etc/update-manager/release-upgrades
            backrefs: yes
            state: present
            regexp: '^(Prompt=).*$'
            line: '\1never'
          ignore_errors: yes
          with_first_found:
            - files:
              - /etc/update-manager/release-upgrades
              skip: true
        
        
    6. Install essential packages
      - name: apt | install basic packages
        apt:
          name:
            - anacron
            - acl
            - at
            - bash-completion
            #- biosdevname  # caution: may change device names after a minimal installation!
            - debconf-utils
            - linux-firmware
            - psmisc
            - python-jmespath
            - python-kmodpy
            - python-requests
            - python-usb
            - python3-usb
            - software-properties-common
            - ssh
            - wget
            - wpasupplicant
            - usbutils
            - xfsprogs
          state: present
          install_recommends: no
      
      
    7. Install additional packages (user defined)
      - name: apt | install extra packages
        apt:
          name: '{{ extra_packages }}'
          state: present
          install_recommends: no
            
      
    8. create directories
    9. create sudoers drop-in for vdr
    10. basic scripts for standby support

      Stop vdr before entering suspend and unload dvb modules, reverse this operation on resume

7.3.3 templates

{{ ansible_managed | comment('c') }}
// Recommends are as of now still abused in many packages
APT::Install-Recommends "0";
APT::Install-Suggests "0";
  1. yavdr sudoers drop-in

    Allow the vdr user to restart vdr.service and reboot the system

    {{ vdr.user }} ALL=NOPASSWD: /bin/systemctl --no-block restart vdr.service
    {{ vdr.user }} ALL=NOPASSWD: /bin/systemctl --no-block reboot
    
  2. suspend hooks
    #!/bin/bash
    {{ ansible_managed | comment }}
    
    case $1 in
      pre)
        /bin/systemctl stop vdr
        /usr/local/bin/module-helper -u dvb_core
        ;;
      post)
        # reload rc-core keytables
        [ -x /usr/bin/ir-keytable ] && {
            for remote in $(ir-keytable 2>&1 | grep rc/rc | egrep -o "rc[0-9]{1,}")
            do 
                ir-keytable -a /etc/rc_maps.cfg --sysdev $remote
            done
        }
    
        /usr/local/bin/module-helper -r
        /bin/systemctl start vdr
        ;;
    esac
    
    
  3. module-helper
    #!/usr/bin/env python3
    # {{ ansible_managed | comment }}
    # based on http://www.e-tobi.net/blog/files/module-helper
    # http://www.e-tobi.net/blog/2010/11/06/squeeze-vdr-teil-9-suspend-to-ram
    # ported to python3 by Alexander Grothe
    #
    # This script resolves linux kernel module dependencies automatically, so only
    # the base module has to be specified (e.g. dvb_core)
    
    import argparse
    import subprocess
    import pickle
    
    dependency_map = {}
    
    
    def find_dependencies(module, dependencies=[]):
        dependencies.append(module)
        if module in dependency_map:
            for dependency in dependency_map[module]:
                find_dependencies(dependency, dependencies)
        return dependencies
    
    
    def build_module_dependency_map():
        with subprocess.Popen(['lsmod'],
                              stdout=subprocess.PIPE,
                              universal_newlines=True
                              ) as p:
            for line in p.stdout:
                values = line.split()
                if len(values) > 3:
                    #print("module {} depends on {}".format(values[0], values[3]))
                    dependency_map[values[0]] = values[3].split(',')
    
    
    def run_command_on_module_list(command, module_list):
        for module in module_list:
            cmd = [command, module]
            subprocess.call(cmd)
    
    
    def create_argparser():
        parser = argparse.ArgumentParser(description="load or unload modules")
        group = parser.add_mutually_exclusive_group()
        group.add_argument('-u', '--unload', metavar='MODULE', nargs='+',
                           help='unload modules')
        group.add_argument('-r', '--reload', action='store_true',
                           help='reload modules')
        parser.add_argument('-t', '--temp-file', nargs='?',
                            default='/tmp/modules.list',
                            help='''store names of unloaded modules in a file,
                                  default location is /tmp/modules.list''')
        return parser
    
    if __name__ == '__main__':
        parser = create_argparser()
        args = parser.parse_args()
        if args.unload:
            build_module_dependency_map()
            all_modules = []
            for module in args.unload:
                all_modules.extend(find_dependencies(module))
            all_modules.reverse()
            try:
                with open(args.temp_file, 'wb') as f:
                    pickle.dump(all_modules, f)
                run_command_on_module_list('rmmod', all_modules)
            except Exception as e:
                print(e)
                pass
        elif args.reload:
            with open(args.temp_file, 'rb') as f:
                all_modules = pickle.load(f)
            run_command_on_module_list('modprobe', all_modules)
        else:
            parser.print_help()
    

7.4 collect facts about the system with custom modules

7.4.1 variables


7.4.2 tasks

  1. main.yml
    - name: get information about usb and pci hardware and loaded kernel modules
      hardware_facts:
        usb: True
        pci: True
        modules: True
        gpus: True
        acpi_power_modes: True
      tags:
        - always
    
    - debug:
        var: '{{ item }}'
        verbosity: 1
      with_items:
        - usb
        - pci
        - gpus
        - modules
        - acpi_power_modes
        - nvidia_detected
        - intel_detected
        - amd_detected
        - virtualbox_detected
    
    - name: get detailed PCI device information
      pci_facts:
      tags:
        - always
    
    - debug:
        var: pci_devices
        verbosity: 1
    
    - name: known vdr output plugins
      set_fact:
        vdr_output_plugins:    
          - softhddevice
          - xineliboutput
          - xine
    

7.5 vdr

7.5.1 tasks

  1. install the basic vdr packages
    ---
    # file: roles/vdr/tasks/main.yml
    
    - name: apt | install basic vdr packages
      apt:
        name:
          - vdr
          - vdrctl
          - vdr-plugin-dbus2vdr
        state: present
        install_recommends: no
    
  2. Add svdrp/svdrp-disc to /etc/services
    - name: add svdrp and svdrp-disc to /etc/services
      lineinfile:
        dest: /etc/services
        state: present
        line: "{{ item }}"
      with_items:
        - "svdrp            6419/tcp"
        - "svdrp-disc       6419/udp"
    
  3. Set up the recording directory for the vdr user
    - name: create vdr recdir
      file:
        state: directory
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
        mode: 0775
        dest: '{{ vdr.recdir }}'
    
    - name: set option to use hide-first-recording-level patch
      blockinfile:
        dest: /etc/vdr/conf.d/04-vdr-hide-first-recordinglevel.conf
        create: true
        state: "{{ 'present' if vdr.hide_first_recording_level else 'absent' }}"
        block: |
          [vdr]
          --hide-first-recording-level
    
    - name: create local dir in recdir
      file:
        state: directory
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
        mode: '0775'
        dest: '{{ vdr.recdir }}/local'
      when:
        vdr.hide_first_recording_level
    
    # TODO: set recdir, user etc. in /etc/vdr/conf.d/
    
  4. Install additional vdr plugins

    The additional plugins to install can be set in the variable {{vdr_plugins}} in the group variables

    - name: apt | install additional vdr plugins
      apt:
        name: '{{ vdr_plugins | default([]) }}'
        state: present
        install_recommends: no
      notify: [ 'Restart VDR' ]
    
  5. copy vdr configuration files (if they don’t exist yet)
    - name: ensure vdr is stopped
      systemd:
        name: vdr.service
        state: stopped
      notify: [ 'Start VDR' ]
    
    - name: "vdr configuration | copy remote.conf if it doesn't exist yet"
      copy:
        src: files/remote.conf
        dest: '{{ vdr.confdir }}/remote.conf'
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
        mode: 0644
        force: no
    
    - name: "vdr configuration | copy keymacros.conf"
      copy:
        src: files/keymacros.conf
        dest: '{{ vdr.etc_confdir }}/keymacros.conf'
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
        mode: 0644
        force: no
    
    - name: "vdr configuration | copy channels.conf if it doesn't exist yet"
      copy:
        src: '{{ vdr_channels_conf }}'
        dest: '{{ vdr.confdir }}/channels.conf'
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
        mode: 0644
        force: no
      when: vdr_channels_conf is defined
    
    - name: "vdr configuration | download channels.conf if it doesn't exist yet"
      get_url:
        url: '{{ vdr_channels_conf_url }}'
        dest: '{{ vdr.confdir }}/channels.conf'
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
        mode: 0644
        force: no
      when: vdr_channels_conf is not defined and vdr_channels_conf_url is defined
    
    - name: "vdr configuration | svdrphosts.conf"
      template:
        src: templates/svdrphosts.conf.j2
        dest: '{{ vdr.etc_confdir }}/svdrphosts.conf'
        mode: 0644
      vars:
        svdrphosts: '{{ vdr_svdrphosts | default(vdr_allowed_hosts) }}'
    
    - name: "vdr configuration | /etc/default/vdr"
      template:
        src: templates/default_vdr.j2
        dest: /etc/default/vdr
    
    
  6. plugin preconfiguration

    Set up IP resp. IP range based access for VDR plugins

    1. xineliboutput
      - name: ensure directory '/etc/vdr/xineliboutput' exists
        file:
          state: directory
          mode: 0775
          dest: '/etc/vdr/plugins/xineliboutput'
        
      - name: "vdr configuration | allowed_hosts.conf for xineliboutput"
        template:
          src: templates/generic_allowed_hosts.conf.j2
          dest: '/etc/vdr/plugins/{{ vdr_service }}/allowed_hosts.conf'
          mode: 0644
        vars:
          allowed_hosts: '{{ xineliboutput_allowed_hosts | default(vdr_allowed_hosts) }}'
          vdr_service: xineliboutput
      
      - name: "vdr configuration | xineliboutput.conf host settings"
        template:
          src: templates/xineliboutput.conf.j2
          dest: /etc/vdr/conf.avail/xineliboutput.conf
          mode: 0644
        vars:
          allowed_hosts: '{{ xineliboutput_allowed_hosts | default(vdr_allowed_hosts) }}'
      
      
    2. vnsiserver
      - name: ensure directory '/etc/vdr/plugins/vnsiserver' exists
        file:
          state: directory
          mode: 0775
          dest: '/etc/vdr/plugins/vnsiserver'
        
      - name: "vdr configuration | allowed_hosts.conf for vnsiserver"
        template:
          src: templates/generic_allowed_hosts.conf.j2
          dest: '/etc/vdr/plugins/{{ vdr_service }}/allowed_hosts.conf'
          mode: 0644
        vars:
          allowed_hosts: '{{ vnsiserver_allowed_hosts | default(vdr_allowed_hosts) }}'
          vdr_service: vnsiserver
      
      
    3. streamdev-server
      - name: ensure directory '/etc/vdr/plugins' exists
        file:
          state: directory
          mode: 0775
          dest: '/etc/vdr/plugins/'
        
      - name: "vdr configuration | streamdevhosts.conf for streamdev-server"
        template:
          src: templates/generic_allowed_hosts.conf.j2
          dest: '/etc/vdr/plugins/streamdevhosts.conf'
          mode: 0644
        vars:
          allowed_hosts: '{{ streamdev_server_allowed_hosts | default(vdr_allowed_hosts) }}'
          vdr_service: streamdev-server
      
      
  7. start vdr after network-online.target
    - name: create directory for vdr.service systemd drop-in files
      file:
        dest: '{{ item }}'
        state: directory
      with_items:
        - "/etc/systemd/system/vdr.service.d/"
    
    - name: systemd-drop-in | start vdr.service after network-online.target
      template:
        src: templates/systemd/network-online.j2
        dest: /etc/systemd/system/vdr.service.d/network-online.conf
    
    

7.5.2 templates

  1. Systemd Drop-in to start vdr.service after network-online.target
    {{ansible_managed | comment }}
    [Unit]
    After=network-online.target
    
  2. /etc/default/vdr
    {{ ansible_managed | comment }}
    # /etc/default/vdr
    #
    # See also /usr/share/doc/vdr/README.Debian.gz
    #
    
    SHUTDOWNCMD="{{ vdr_shutdown_command }}"
    
  3. svdrphosts.conf
    {{ ansible_managed | comment }}
    #
    # svdrphosts    This file describes a number of host addresses that
    #               are allowed to connect to the SVDRP port of the Video
    #               Disk Recorder (VDR) running on this system.
    # Syntax:
    #
    # IP-Address[/Netmask]
    #
    # Examples:
    # 192.168.100.0/24     # any host on the local net
    # 204.152.189.113      # a specific host
    # 0.0.0.0/0            # any host on any net (USE THIS WITH CARE!)
    
    127.0.0.1              # always accept localhost
    {% for host_or_subnet in svdrphosts %}
    {{ host_or_subnet }}
    {% endfor %}
    
    
  4. allowed_hosts.conf style template
    {{ ansible_managed | comment }}
    # This file describes a number of host addresses that
    # are allowed to connect to the {{ vdr_service }}
    # running with the Video Disk Recorder (VDR) on this system.
    #
    # Syntax:
    #
    # IP-Address[/Netmask]
    #
    # Examples:
    #
    # 192.168.100.0/24     # any host on the local net
    # 204.152.189.113      # a specific host
    {% if vdr_service == 'streamdev-server' %}
    # 239.255.0.0/16       # uncomment for IGMP multicast streaming
    {% endif %}
    # 0.0.0.0/0            # any host on any net (USE THIS WITH CARE!)
    
    127.0.0.1              # always accept localhost
    {% for host_or_subnet in allowed_hosts %}
    {{ host_or_subnet }}
    {% endfor %}
    
  5. xineliboutput.conf template
    #
    # Command line parameters for vdr-plugin-xineliboutput
    #
    # For more details see:
    #   - /usr/share/doc/vdr-plugin-xineliboutput/README.Debian
    #   - `vdr --help -Pxineliboutput`
    #   - /usr/share/doc/vdr-plugin-xineliboutput/README
    #
    [xineliboutput]
    --local=none
    --primary
    --remote={{ "" if allowed_hosts else "127.0.0.1" }}:37890
    --truecolor
    

7.5.3 files

  1. remote.conf
    LIRC.Up KEY_UP
    LIRC.Down KEY_DOWN
    LIRC.Menu KEY_MENU
    LIRC.Ok KEY_OK
    LIRC.Back KEY_ESC
    LIRC.Left KEY_LEFT
    LIRC.Right KEY_RIGHT
    LIRC.Red KEY_RED
    LIRC.Green KEY_GREEN
    LIRC.Yellow KEY_YELLOW
    LIRC.Blue KEY_BLUE
    LIRC.0 KEY_0
    LIRC.1 KEY_1
    LIRC.2 KEY_2
    LIRC.3 KEY_3
    LIRC.4 KEY_4
    LIRC.5 KEY_5
    LIRC.6 KEY_6
    LIRC.7 KEY_7
    LIRC.8 KEY_8
    LIRC.9 KEY_9
    LIRC.Info KEY_INFO
    LIRC.Play KEY_PLAY
    LIRC.Pause KEY_PAUSE
    LIRC.Play/Pause KEY_PLAYPAUSE
    LIRC.Stop KEY_STOP
    LIRC.Record KEY_RECORD
    LIRC.FastFwd KEY_FASTFORWARD
    LIRC.FastRew KEY_REWIND
    LIRC.Next KEY_NEXT
    LIRC.Prev KEY_BACK
    LIRC.Power KEY_POWER2
    LIRC.Channel+ KEY_CHANNELUP
    LIRC.Channel- KEY_CHANNELDOWN
    LIRC.PrevChannel KEY_PREVIOUS
    LIRC.Volume+ KEY_VOLUMEUP
    LIRC.Volume- KEY_VOLUMEDOWN
    LIRC.Mute KEY_MUTE
    LIRC.Subtitles KEY_SUBTITLE
    LIRC.Schedule KEY_EPG
    LIRC.Channels KEY_CHANNEL
    LIRC.Commands KEY_FAVORITES
    LIRC.Audio KEY_MODE
    LIRC.Timers KEY_TIME
    LIRC.Recordings KEY_PVR
    LIRC.Setup KEY_SETUP
    LIRC.User0 KEY_TEXT
    LIRC.User1 KEY_PROG1
    LIRC.User2 KEY_PROG2
    LIRC.User3 KEY_PROG3
    LIRC.User4 KEY_PROG4
    LIRC.User5 KEY_AUDIO
    LIRC.User6 KEY_VIDEO
    LIRC.User7 KEY_IMAGES
    LIRC.User8 KEY_FN
    LIRC.User9 KEY_SCREEN
    
    XKeySym.Up         Up
    XKeySym.Down       Down
    XKeySym.Menu       Home
    XKeySym.Menu       Tab
    XKeySym.Menu       KP_Home
    XKeySym.Ok         Return
    XKeySym.Ok         KP_Enter
    XKeySym.Back       BackSpace
    XKeySym.Back       KP_Separator
    XKeySym.Left       Left
    XKeySym.Right      Right
    XKeySym.Up         KP_Up
    XKeySym.Down       KP_Down
    XKeySym.Left       KP_Left
    XKeySym.Right      KP_Right
    XKeySym.Red        F1
    XKeySym.Green      F2
    XKeySym.Yellow     F3
    XKeySym.Blue       F4
    XKeySym.0          0
    XKeySym.1          1
    XKeySym.2          2
    XKeySym.3          3
    XKeySym.4          4
    XKeySym.5          5
    XKeySym.6          6
    XKeySym.7          7
    XKeySym.8          8
    XKeySym.9          9
    XKeySym.0          KP_0
    XKeySym.1          KP_1
    XKeySym.2          KP_2
    XKeySym.3          KP_3
    XKeySym.4          KP_4
    XKeySym.5          KP_5
    XKeySym.6          KP_6
    XKeySym.7          KP_7
    XKeySym.8          KP_8
    XKeySym.9          KP_9
    XKeySym.Info       End
    XKeySym.Info       KP_End
    XKeySym.Pause      F9
    XKeySym.FastFwd    F6
    XKeySym.FastRew    F5
    XKeySym.Power      Pause
    XKeySym.Volume+    F12
    XKeySym.Volume-    F11
    XKeySym.Volume+    KP_Add
    XKeySym.Volume-    KP_Subtract
    XKeySym.Mute       F10
    XKeySym.User1      F5
    XKeySym.User2      F6
    XKeySym.User3      F7
    XKeySym.User4      F8
    XKeySym.User5      Print
    XKeySym.User6      Scroll_Lock
    XKeySym.User7      Insert
    XKeySym.User8      KP_Divide
    XKeySym.User9      KP_Multiply
    XKeySym.Audio      Menu
    XKeySym.Channel+   Prior
    XKeySym.Channel-   Next
    XKeySym.Channel+   KP_Prior
    XKeySym.Channel-   KP_Next
    XKeySym.Volume+    XF86AudioRaiseVolume
    XKeySym.Volume-    XF86AudioLowerVolume
    XKeySym.Mute       XF86AudioMute
    XKeySym.Stop       XF86AudioStop
    XKeySym.Play/Pause  XF86AudioPlay
    XKeySym.Prev       XF86AudioPrev
    XKeySym.Next       XF86AudioNext
    
  2. keymacros.conf
    # Remote control key macros for VDR
    #
    # Format:
    #
    # macrokey  key1 key2 key3...
    # macrokey  @plugin key1 key2 key3...
    #
    # See man vdr(5)
    
    Red       Recordings
    Green     Schedule
    Yellow    Info
    Blue      Timers
    User0     @osdteletext
    
    
    

7.6 STARTED yavdr-network

7.6.1 default variables

install_avahi: true

7.6.2 tasks

---
# this playbook sets up network services for a yaVDR installation

- name: apt | install packages for network services
  apt:
      name:
          - avahi-daemon
          - avahi-utils
          #- biosdevname # caution: this may change device names after a minimal installation!
          - ethtool
          - nfs-common
          - vdr-addon-avahi-linker
          - wakeonlan
      state: present
      install_recommends: no

# Does this really work? We need a way to check if an interface supports WOL - Python Skript?
# - name: check WOL capabilities of network interfaces
#   shell: 'ethtool {{ item }} | grep -Po "(?<=Supports\sWake-on:\s).*$"'
#   register: wol
#   with_items: '{% for interface in ansible_interfaces if interface != 'lo' and interface != 'bond0' %}'

- name: restart autofs if running
  systemd:
    name: autofs
    state: restarted
    enabled: yes
    masked: no

- name: ensure autofs is running
  systemd:
    name: autofs
    state: started
    enabled: yes
    masked: no

- name: restart avahi-linker if running
  systemd:
    name: avahi-linker
    state: restarted
    enabled: yes
    masked: no

- name: start avahi-linker
  systemd:
    name: avahi-linker
    state: started
    enabled: yes
    masked: no

- name: enable and start additional services for avahi-linker
  systemd:
    name: '{{ item }}'
    state: started
    enabled: yes
    masked: no
  with_items:
    - vdr-net-monitor
    - vdr-update-monitor
    - prevent-umount-on-pause

7.7 vdr-plugin-menuorg

7.7.1 tasks

---
# file: roles/vdr-plugin-menuorg/tasks/main.yml

- name: apt | install vdr-plugin-menuorg and yavdr-i18n
  apt:
    name:
      - vdr-plugin-menuorg 
      - yavdr-i18n
    state: present
    install_recommends: no

- name: create /var/lib/vdr/plugins/menuorg.xml
  template:
    src: templates/menuorg.xml.j2
    dest: /var/lib/vdr/plugins/menuorg.xml

7.7.2 templates

<?xml version="1.0" encoding="UTF-8"?>                                                                                                                      
{{ ansible_managed | comment('xml') }}
<!--
This is the config file for the Menuorg plug-in.
See /usr/share/doc/vdr-plugin-menurg for examples and a full
description of the config file format.
-->
<menus>
    <system name="Schedule" />
    <system name="Channels" />
    <system name="Timers" />
    <system name="Recordings" />
    <plugin name="tvguide" />
    <command name="Kodi" execute="frontend-dbus-send switchto kodi" />
    <plugin name="desktop" />
    <menu name="{{ 'VDR Plugins' | translate }}">
        <plugin name="epgsearchonly" />
        <plugin name="quickepgsearch" />
        <plugin name="conflictcheckonly" />
        <plugin name="markad" />
        <plugin name="recsearch" />
        <plugin name="undelete" />
        <plugin name="radio" />
        <plugin name="osd2web" />
    </menu>
    <menu name="{{ 'System' | translate }}">
         <menu name="{{ 'Commands' | translate }}">
               <command name="{{ "Safely remove usb mass storage" | translate }}" confirm="yes" execute="/usr/bin/vdr-mounter --unmount-all &amp;> /dev/null" />
               <command name="{{ "Update vdr recordings list" | translate }}" execute="/usr/bin/vdr-dbus-send /Recordings recording.Update &amp;> /dev/null " />
               <command name="{{ "Restart VDR" |translate }}" confirm="yes" execute="sudo /sbin/initctl restart vdr" />
               <command name="{{ "Reboot system"| translate }}" confirm="yes" execute="/usr/bin/at now -M -f /usr/bin/vdr-reboot" />
               <command name="{{ "Shutdown system" | translate }}" execute="/usr/bin/lircd2uinput-send KEY_POWER2 &amp;> /dev/null &amp; " />
         </menu>
         <system name="Setup" />
         <plugin name="femon" />
         <plugin name="devstatus" />
         <plugin name="epg2vdr" />
         <plugin name="scraper2vdr" />
         <plugin name="sndctl" />
         <plugin name="suspendoutput" />
         <plugin name="recstatus" />
         <plugin name="dynamite" />
         <plugin name="noepgmenu" />
         <plugin name="systeminfo" />
         <plugin name="filebrowser" />
         <plugin name="pin" />
         <plugin name="pulsecontrol" />
         <plugin name="softhddevice" />
         </menu>
    <system name="Commands" />
</menus>

7.8 nfs-server

7.8.1 TODO avahi-services für NFS beim Stoppen des nfs-kernel-servers depublizieren, beim Start wieder announcen

7.8.2 tasks

---

- name: install nfs server packages
  apt:
    name:
      - nfs-kernel-server
      - nfs-common
    state: present
    install_recommends: no

- name: create /etc/exports
  template:
    src: templates/nfs-exports.j2
    dest: /etc/exports
  notify: [ 'Restart NFS Kernel Server' ]

- name: populate /etc/avahi/services
  template:
    src: templates/avahi/service.j2
    dest: '/etc/avahi/services/yavdr-{{ item.key }}.service'
  with_dict: '{{ media_dirs }}'

7.8.3 templates

  1. /etc/exports
    /srv *(rw,fsid=0,sync,no_subtree_check,all_squash,anongid={{ vdr.gid }},anonuid={{ vdr.uid }})
    {% for name, path in media_dirs.items() | list %}
    {{ path }} *(rw,fsid={{ loop.index }},sync,no_subtree_check,all_squash,anongid={{ vdr.gid }},anonuid={{ vdr.uid }}{{ ',insecure' if nfs.insecure else '' }})
    {% endfor %}
    
  2. avahi services
    <?xml version="1.0" standalone='no'?>
    <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
    <service-group>
    <name replace-wildcards="yes">{{ item.key|capitalize }} on %h</name> ## Name
    <service>
           <type>_nfs._tcp</type>
           <port>2049</port>
           <txt-record>path={{ item.value }}</txt-record> ## path to shared Folder
           <txt-record>subtype={{ 'vdr' if item.key == 'recordings' else item.key }}</txt-record> ## subtype
    </service>
    </service-group>
    

7.9 yavdr-remote

The role yavdr-remote sets up the foundation for using eventlircd, lircd2uinput and the pre-configuration for remote receivers which can be detected by udev.

7.9.1 default variables

lircd0_socket: /var/run/lirc/lircd0

7.9.2 tasks

---
# This role is used to set up the yaVDR remote control configuration.

- name: apt | install packages for remote support
  apt:
    name:
      - yavdr-remote
      - lirc
    state: present
  tags:
    - packages

- name: add systemd drop-in for lircd to use lircd2uinput
  block:
    - name: create directory /etc/systemd/system/lircd.service.d/
      file:
        path: /etc/systemd/system/lircd.service.d/
        state: directory
    - name: expand template for /etc/systemd/system/lircd.service.d/lircd2uinput.conf
      template:
        src: templates/lircd.service.d/lircd2uinput.conf.j2
        dest: /etc/systemd/system/lircd.service.d/lircd2uinput.conf
  tags:
    - config

- name: stop, mask and disable lircd.socket, lircd.service, lircmd.service and lircd-uinput.service # (the default lirc configuration conflicts with eventlircd)
  systemd:
    name: '{{ item }}'
    enabled: no
    state: stopped
    masked: yes
  with_items:
    - lircd.service
    - lircd.socket
    - lircmd.service
    - lircd-uinput.service
  ignore_errors: yes
  tags:
    - systemd

- name: apt | install eventlircd and lircd2uinput
  apt:
    name:
      - eventlircd
      - lircd2uinput
    state: present
  tags:
    - packages

- name: configure vdr to read from a lircd-compatible socket
  template:
    src: templates/03-vdr-lirc.conf.j2
    dest: /etc/vdr/conf.d/03-vdr-lirc.conf
  notify: ['Restart VDR']
  tags:
    - config

- name: expand template for /etc/rc_maps.cfg
  template:
    src: templates/rc_maps.cfg.j2
    dest: /etc/rc_maps.cfg
  notify: ['Trigger Udev']
  tags:
    - config

7.9.3 templates

  1. Systemd Drop-in to start vdr with lirc support
    {% if frontend != 'kodi' %}
    [vdr]
    --lirc=/var/run/lirc/lircd
    {% endif %}
    
  2. Systemd Drop-in to start lircd2uinput with lircd
    [Service]
    ExecStartPost=/usr/bin/lircd2uinput-add /var/run/lirc/lircd0
    ExecStopPost=/usr/bin/lircd2uinput-remove /var/run/lirc/lircd0
    
  3. create /etc/rc_maps.cfg
    {{ ansible_managed | comment }}
    #
    # Keymaps table
    #
    # This table creates an association between a keycode file and a kernel
    # driver. It can be used to automatically override a keycode definition.
    #
    # Although not yet tested, it is mented to be added at udev.
    #
    # To use, you just need to run:
    #       ./ir-keytable -a
    #
    # Or, if the remote is not the first device:
    #       ./ir-keytable -a -s rc1         # for RC at rc1
    #
    
    # Format:
    #       driver - name of the driver provided via uevent - use * for any driver
    #       table -  RC keymap table, provided via uevent - use * for any table
    #       file - file name. If directory is not specified, it will default to
    #               /etc/rc_keymaps.
    # For example:
    # driver        table                           file
    # cx8800        *                               ./keycodes/rc5_hauppauge_new
    # *             rc-avermedia-m135a-rm-jx        ./keycodes/kworld_315u
    # saa7134       rc-avermedia-m135a-rm-jx        ./keycodes/keycodes/nec_terratec_cinergy_xs
    # em28xx        *                               ./keycodes/kworld_315u
    # *             *                               ./keycodes/rc5_hauppauge_new
    
    # Table to automatically load the rc maps for the bundled IR's provided with the
    # devices supported by the linux kernel
    
    #driver table                    file
    ite-cir     rc-rc6-mce          /lib/udev/rc_keymaps/rc-rc6-mce
    nuvoton-cir rc-rc6-mce          /lib/udev/rc_keymaps/rc-rc6-mce
    serial_ir   rc-rc6-mce          /lib/udev/rc_keymaps/rc-rc6-mce
    mceusb      rc-rc6-mce          /lib/udev/rc_keymaps/HOPLOrc6
    
    # Hauppauge PVR 350
    ir-kbd-i2c  rc-hauppauge        /lib/udev/rc_keymaps/rc-hauppauge
    
    # imon-pad
    imon        rc-imon-mce         /lib/udev/rc_keymaps/rc-imon-mce
    imon        rc-imon-pad         /lib/udev/rc_keymaps/rc-imon-pad
    
    # TT-1500/1501
    budget_ci   rc-tt-1500          /lib/udev/rc_keymaps/rc-tt-1501
    
    
    # Skystarhd2
    mantis_core * /lib/udev/rc_keymaps/skystarhd2
    
    # Medion X10
    ati_remote    rc-medion-x10              /lib/udev/rc_keymaps/rc-medion-x10
    ati_remote    rc-medion-x10-or2x         /lib/udev/rc_keymaps/rc-medion-x10-or2x
    ati_remote    rc-medion-x10-digitainer   /lib/udev/rc_keymaps/rc-medion-x10-digitainer
    
    # other devices (which still mostly need a customized keymap for yaVDR)
    *       rc-adstech-dvb-t-pci     adstech_dvb_t_pci
    *       rc-alink-dtu-m           alink_dtu_m
    *       rc-anysee                anysee
    *       rc-apac-viewcomp         apac_viewcomp
    *       rc-asus-pc39             asus_pc39
    *       rc-asus-ps3-100          asus_ps3_100
    *       rc-ati-tv-wonder-hd-600  ati_tv_wonder_hd_600
    *       rc-ati-x10               ati_x10
    *       rc-avermedia-a16d        avermedia_a16d
    *       rc-avermedia-cardbus     avermedia_cardbus
    *       rc-avermedia-dvbt        avermedia_dvbt
    *       rc-avermedia-m135a       avermedia_m135a
    *       rc-avermedia-m733a-rm-k6 avermedia_m733a_rm_k6
    *       rc-avermedia-rm-ks       avermedia_rm_ks
    *       rc-avermedia             avermedia
    *       rc-avertv-303            avertv_303
    *       rc-azurewave-ad-tu700    azurewave_ad_tu700
    *       rc-behold-columbus       behold_columbus
    *       rc-behold                behold
    *       rc-budget-ci-old         budget_ci_old
    *       rc-cec                   cec
    *       rc-cinergy-1400          cinergy_1400
    *       rc-cinergy               cinergy
    *       rc-delock-61959          delock_61959
    *       rc-dib0700-nec           dib0700_nec
    *       rc-dib0700-rc5           dib0700_rc5
    *       rc-digitalnow-tinytwin   digitalnow_tinytwin
    *       rc-digittrade            digittrade
    *       rc-dm1105-nec            dm1105_nec
    *       rc-dntv-live-dvb-t       dntv_live_dvb_t
    *       rc-dntv-live-dvbt-pro    dntv_live_dvbt_pro
    *       rc-dtt200u               dtt200u
    *       rc-dvbsky                dvbsky
    *       rc-em-terratec           em_terratec
    *       rc-encore-enltv-fm53     encore_enltv_fm53
    *       rc-encore-enltv          encore_enltv
    *       rc-encore-enltv2         encore_enltv2
    *       rc-evga-indtube          evga_indtube
    *       rc-eztv                  eztv
    *       rc-flydvb                flydvb
    *       rc-flyvideo              flyvideo
    *       rc-fusionhdtv-mce        fusionhdtv_mce
    *       rc-gadmei-rm008z         gadmei_rm008z
    *       rc-genius-tvgo-a11mce    genius_tvgo_a11mce
    *       rc-gotview7135           gotview7135
    *       rc-hauppauge             /lib/udev/rc_keymaps/rc-hauppauge
    *       rc-imon-mce              /lib/udev/rc_keymaps/rc-imon-mce
    *       rc-imon-pad              /lib/udev/rc_keymaps/rc-imon-pad
    *       rc-iodata-bctv7e         iodata_bctv7e
    *       rc-it913x-v1             it913x_v1
    *       rc-it913x-v2             it913x_v2
    *       rc-kaiomy                kaiomy
    *       rc-kworld-315u           kworld_315u
    *       rc-kworld-pc150u         kworld_pc150u
    *       rc-kworld-plus-tv-analog kworld_plus_tv_analog
    *       rc-leadtek-y04g0051      leadtek_y04g0051
    *       rc-lme2510               lme2510
    *       rc-manli                 manli
    #*      rc-medion-x10-digitainer medion_x10_digitainer
    #*      rc-medion-x10-or2x       medion_x10_or2x
    #*      rc-medion-x10            medion_x10
    *       rc-msi-digivox-ii        msi_digivox_ii
    *       rc-msi-digivox-iii       msi_digivox_iii
    *       rc-msi-tvanywhere-plus   msi_tvanywhere_plus
    *       rc-msi-tvanywhere        msi_tvanywhere
    *       rc-nebula                nebula
    *       rc-nec-terratec-cinergy-xs nec_terratec_cinergy_xs
    *       rc-norwood               norwood
    *       rc-npgtech               npgtech
    *       rc-pctv-sedna            pctv_sedna
    *       rc-pinnacle-color        pinnacle_color
    *       rc-pinnacle-grey         pinnacle_grey
    *       rc-pinnacle-pctv-hd      pinnacle_pctv_hd
    *       rc-pixelview-002t        pixelview_002t
    *       rc-pixelview-mk12        pixelview_mk12
    *       rc-pixelview-new         pixelview_new
    *       rc-pixelview             pixelview
    *       rc-powercolor-real-angel powercolor_real_angel
    *       rc-proteus-2309          proteus_2309
    *       rc-purpletv              purpletv
    *       rc-pv951                 pv951
    *       rc-rc6-mce               /lib/udev/rc_keymaps/rc-rc6-mce
    *       rc-real-audio-220-32-keys real_audio_220_32_keys
    *       rc-reddo                 reddo
    *       rc-snapstream-firefly    snapstream_firefly
    *       rc-streamzap             streamzap
    *       rc-su3000                su3000
    *       rc-tbs-nec               tbs_nec
    *       rc-technisat-ts35        technisat_ts35
    *       rc-technisat-usb2        technisat_usb2
    *       rc-terratec-cinergy-c-pci terratec_cinergy_c_pci
    *       rc-terratec-cinergy-s2-hd terratec_cinergy_s2_hd
    *       rc-terratec-cinergy-xs   terratec_cinergy_xs
    *       rc-terratec-slim-2       terratec_slim_2
    *       rc-terratec-slim         terratec_slim
    *       rc-tevii-nec             tevii_nec
    *       rc-tivo                  tivo
    *       rc-total-media-in-hand-02 total_media_in_hand_02
    *       rc-total-media-in-hand   total_media_in_hand
    *       rc-trekstor              trekstor
    *       rc-tt-1500          /lib/udev/rc_keymaps/rc-tt-1500
    *       rc-twinhan-dtv-cab-ci    twinhan_dtv_cab_ci
    *       rc-twinhan1027           twinhan_vp1027_dvbs
    *       rc-videomate-k100        videomate_k100
    *       rc-videomate-s350        videomate_s350
    *       rc-videomate-tv-pvr      videomate_tv_pvr
    *       rc-winfast-usbii-deluxe  winfast_usbii_deluxe
    *       rc-winfast               winfast
    # *     *                        a800                 # found in a800.c
    # *     *                        af9005               # found in af9005-remote.c
    # *     *                        cinergyt2            # found in cinergyT2-core.c
    # *     *                        dvico_mce            # found in cxusb.c
    # *     *                        dvico_portable       # found in cxusb.c
    # *     *                        d680_dmb             # found in cxusb.c
    # *     *                        dibusb               # found in dibusb-common.c
    # *     *                        digitv               # found in digitv.c
    # *     *                        megasky              # found in m920x.c
    # *     *                        tvwalkerir-kbd-i2c   # found in m920x.c
    # *     *                        pinnacle310e         # found in m920x.c
    # *     *                        haupp                # found in nova-t-usb2.c
    # *     *                        opera1               # found in opera1.c
    # *     *                        vp702x               # found in vp702x.c
    # *     *                        vp7045               # found in vp7045.c
    
    

7.10 pulseaudio

7.10.1 tasks

---

- name: apt | install pulseaudio, pavucontrol and vdr-plugin-pulsecontrol
  apt:
    name:
      - pulseaudio
      - pavucontrol
      - vdr-plugin-pulsecontrol
    state: present
    install_recommends: no

- name: create /etc/asound.conf with pulseaudio as default device
  template:
    src: templates/alsa/asound.conf.j2
    dest: /etc/asound.conf

7.10.2 templates

# Use PulseAudio by default
pcm.!default {
  type pulse
  fallback "sysdefault"
  hint {
    show on
    description "Default ALSA Output (currently PulseAudio Sound Server)"
  }
}

ctl.!default {
  type pulse
  fallback "sysdefault"
}

7.11 yavdr-xorg

7.11.1 About the GUI session

The X-Server is started by using the two systemd units xlogin@.service and x@.service provided by the package xlogin. The former is enabled (and started) for the vdr user - which results (using the default settings for the user vdr with the uid 666) in the activation of xlogin@vdr.service when reaching the graphical.target. To simplify starting and stopping the X-server and the desktop session a yavdr-xorg.service is provided by the package yavdr-xorg, which depends on the two units mentioned before.

x@vt7.service is started automatically as a dependency of xlogin@vdr.service and starts the X-server. xlogin@vdr.service also starts a systemd user session using user@666.service. In order to use the keyboard layout configured during installation for the X-Server we are using a script write-x11-keyboard-config, which reads the keyboard configuration from /etc/default/keyboard when starting x@.service and writes the file /etc/X11/xorg.conf.d/00-keyboard.conf (because systemd for Ubuntu (and Debian) has been patched not to automatically create the configuration file /etc/X11/xorg.conf.d/00-keyboard.conf according to the localectl settings).

To prevent stopping the X-server when vdr is running, additional dependencies are set up - see the drop-in rules created in /etc/systemd/system/vdr.service.d/.

Basic Services like the session DBus, pulseaudio etc. are started for the default.target of the session. The openbox autostart script is used to update the systemd session with the needed session variables. It creates a file ~/.session-env which is used as an environment file by vdr.service (so the vdr knows the DISPLAY and can access pulseaudio).

The autostart script then enables all services to be pulled in by yavdr-desktop.target. As the last step yavdr-desktop.target is startet, which results in staring yavdr-frontend.service and additional Units for a second display (openbox and a browser for osd2eb, if available).

In order to achive a clean shutdown of the session, x@t7.service is set as a dependency of the systemd unit instance user@666.service and all processes within the session must be shutdown properly when stopping xlogin@vdr.service. If systemd units are used within the user session, they must stop their process(es) successfully (you should set them up to accept expected exit codes appropriately). The window manager openbox is started using the ~/.xinitrc and stopped using the systemd unit exit-wm.service (which runs on stopping the jobs pulled in by the default.target of the session).

7.11.2 default variables

preferred_outputs:
  - HDMI
  - DP
  - DVI
  - VGA
  - TV
preferred_resolutions:
  - 7680x4320
  - 3840x2160
  - 1920x1080
  - 1280x720
  - 720x576
preferred_refreshrates:
  - 50
  - 60

7.11.3 tasks

  1. main.yml
    ---
    # file: roles/yavdr-xorg/tasks/main.yml
    - import_tasks: setup-xorg.yml
      tags: [install,update]
    - import_tasks: detect-xorg.yml
      tags: [xorg.detect,install]
    
  2. setup-xorg.yml
    ---
    # file: roles/yavdr-xorg/tasks/setup-xorg.yml
    - name: create directories for systemd drop-in files
      file:
        dest: '{{ item }}'
        state: directory
      with_items:
        - "/etc/systemd/system/x@.service.d/"
        - "/etc/systemd/system/vdr.service.d/"
        - "/etc/systemd/system/user@{{ vdr.uid }}.service.d/"
        - "/etc/X11/xorg.conf.d/"
    
    - name: systemd-drop-in | add dependency to X-server for vdr.service
      template:
        src: templates/vdr-xorg.conf
        dest: /etc/systemd/system/vdr.service.d/vdr-xorg.conf
    
    - name: systemd-drop-in | start x@.service after plymouth.service
      template:
        src: templates/x@service.d/plymouth.conf.j2
        dest: /etc/systemd/system/x@.service.d/plymouth.conf
    
    - name: systemd-drop-in | start x@.service before xlogin@.service
      template:
        src: templates/x@service.d/xlogin.conf.j2
        dest: /etc/systemd/system/x@.service.d/xlogin.conf
    
    - name: systemd-drop-in | load environment file for vdr.service
      template:
        src: templates/systemd/vdr-environ.j2
        dest: /etc/systemd/system/vdr.service.d/load-environ.conf
    
    - name: systemd-drop-in | user@{{ vdr.uid }}.service depends on x@vt7.service
      template:
        src: templates/user@666.service.d/x-dependency.conf.j2
        dest: '/etc/systemd/system/user@{{ vdr.uid }}.service.d/x-dependency.conf'
    
    - name: vdr-config | start softhddevice detached and set audio to pulseaudio
      lineinfile:
        path: /etc/vdr/conf.avail/softhddevice.conf
        line: '{{ item }}'
      with_items:
          - '-D'
          - '-w alsa-driver-broken'
          # - '-a pulse' # do we need this with our existing asound.conf?
    
    - name: vdr-config | use vaapi for softhddevice if we got an intel IGP
      lineinfile:
        path: /etc/vdr/conf.avail/softhddevice.conf
        line: '-v va-api'
        state: '{{ "present" if (not nvidia_detected and intel_detected) else "absent" }}'
    
    - name: add a login shell for the user vdr
      user:
          name: '{{ vdr.user }}'
          shell: '/bin/bash'
          state: present
          uid: '{{ vdr.uid }}'
          groups: '{{ vdr.group }}'
          append: yes
    
    - name: apt | install packages for xorg
      apt:
        name:
          - xlogin
          - yavdr-xorg
          - xorg
          - xserver-xorg-input-all
          - xserver-xorg-video-all
          - openbox
          - python3-dbus2vdr
          - python3-yavdrfrontend
          - read-edid
          - tmux
          - vdr-plugin-desktop
          - vdr-plugin-osd2web
          - feh
          #- yavdr-xorg
        state: present
    
    - name: apt | install packages for Intel IGP
      apt:
        name:
          - xserver-xorg-video-intel
          - i965-va-driver-shaders
        state: present
      when: intel_detected and not nvidia_detected
    
    - name: Install yaVDR Media (e.g. Backgrounds)
      apt:
        name: yavdr-media
    
    - name: command | write keyboard configuration to /etc/X11/xorg.conf.d/00-keyboard.conf
      command: write-x11-keyboard-config
      args:
        creates: /etc/X11/xorg.conf.d/00-keyboard.conf
      # NOTE: write-x11-keyboard-config is in the xlogin package
      # Ubuntu's systemd is patched, so it does not create the file automatically
    
    - name: add snippet to ignore eventlircd devices in xorg
      template:
        src: templates/xorg-ignore-eventlircd.conf.j2
        dest: /etc/X11/xorg.conf.d/40-ignore-eventlircd.conf
    
    - name: apt | install desktop programs
      apt:
        name:
          - xterm
          - firefox
          - kiosk-browser
        state: present
    
  3. detect-xorg.yml
    ---
    # file: roles/yavdr-xorg/tasks/detect-xorg.yml
    
    - name: "expand template for x-verbose@.service"
      template:
        src: "templates/systemd/system/x-verbose@.service.j2"
        dest: "/etc/systemd/system/x-verbose@.service"
    
    - name: "expand template for xorg-verbose.conf"
      template:
        src: "templates/xorg-verbose.conf.j2"
        dest: "/etc/X11/xorg-verbose.conf"
    
    - name: "remove intel snippet"
      file:
        path: /etc/X11/xorg.conf.d/20-intel.conf
        state: absent
    
    - name: Stop VDR
      systemd:
        name: vdr.service
        state: stopped
        enabled: yes
      notify: ['Start VDR']
    
    - name: Stop yavdr-xorg
      systemd:
        name: 'yavdr-xorg'
        state: stopped
        enabled: yes
      notify: ['Start yavdr-xorg']
    
    - name: Stop xlogin
      systemd:
        name: 'xlogin@{{ vdr.user }}.service'
        state: stopped
      notify: ['Start xlogin']
    
    - name: Stop x
      systemd:
        name: x@vt7.service
        state: stopped
    
    - name: check if a nvidia driver has been installed
      set_fact:
        nvidia_driver_installed: '{{ "nvidia" in lookup("file", "/etc/yavdr/autoinstalled") }}'
    
    - name: unload nouveau driver, replace with nvidia
      block:
        - name: unbind vconsole
          shell: echo 0 > /sys/class/vtconsole/vtcon1/bind
    
        - name: "wait a little, so noveau isn't used anymore"
          wait_for:
            timeout: 1
    
        # unloading the drivers using the modprobe module does not work for some strange reason...
        - name: unload kms drivers
          command: rmmod '{{ item }}'
          loop:
            - nouveau
            - ttm
            - drm_kms_helper
            - drm
          ignore_errors: yes
    
        - name: load nvidia driver
          modprobe:
            name: "nvidia"
            state: present
    
        - name: "wait a little before starting nvidia-persistenced"
          wait_for:
            timeout: 1
    
        - name: start nvidia-persistenced
          systemd:
            name: "nvidia-persistenced"
            state: started
            enabled: true
            masked: false
            daemon_reload: true
      when:
        - nvidia_detected
        - nvidia_driver_installed
        - '"nouveau" in modules'
    
    - name: "start x-verbose@.service"
      systemd:
        name: "x-verbose@vt7.service"
        state: started
        enabled: false
        masked: false
        daemon_reload: true
    
    - name: "wait a little, so X has some time to start up"
      wait_for:
        timeout: 3
    
    - name: "detect xorg configuration"
      xrandr_facts:
        preferred_refreshrates: '{{ preferred_refreshrates }}'
        preferred_resolutions: '{{ preferred_resolutions }}'
        preferred_outputs: '{{ preferred_outputs }}'
    
    - debug:
        var: xorg.primary
    
    - debug:
        var: xorg.secondary
      when: xorg.secondary is defined
    
    - debug:
        var: drm
      when: drm is defined
    
    - name: "stop x-verbose@vt7.service"
      systemd:
        name: "x-verbose@vt7.service"
        state: stopped
        enabled: false
        masked: true
    
    #- name: "wait a little bit, so X has some time to shut down (needed?)"
    #  wait_for:
    #    timeout: 3
    
    - name: save results
      block:
        - name: ensure facts.d directory exists
          file:
            state: directory
            path: /etc/ansible/facts.d
    
        - name: write xorg variable as local fact
          copy:
            content: '{{ {"xorg": xorg} | to_nice_json }}'
            dest: /etc/ansible/facts.d/xorg.fact
    
        - name: write xrandr variable as local fact
          copy:
            content: '{{ {"xrandr": xrandr} | to_nice_json }}'
            dest: /etc/ansible/facts.d/xrandr.fact
    
        - name: write drm variable as local fact
          copy:
            content: '{{ {"drm": drm} | to_nice_json }}'
            dest: /etc/ansible/facts.d/drm.fact
      when:
        - xrandr is defined
        - xorg is defined
        - drm is defined
    
    - name: update xorg, xrandr and drm variables with values from local facts if needed
      set_fact:
        xorg: '{{ ansible_local.xorg.xorg }}'
        xrandr: '{{ ansible_local.xrandr.xrandr }}'
        drm: '{{ ansible_local.drm.drm }}'
      when:
        - xrandr is undefined
        - xorg is undefined
        - drm is undefined
    
    # TODO: expand template for xorg.conf (or snippets)
    #       with respect for the available graphics card driver
    #       nvidia, noveau, intel, radeon
    
    - name: nvidia related config
      block:
      - name: create xorg.conf (for nvidia driver)
        template:
            src: templates/xorg.conf.j2
            dest: /etc/X11/xorg.conf
            backup: yes
      when:
        - nvidia_detected
    
    - name: intel xorg config snippet
      template:
        src: templates/20-intel.conf.j2
        dest: /etc/X11/xorg.conf.d/20-intel.conf
      when:
        - intel_detected and not nvidia_detected
    
    - name: server flags config snippet to disable screen blanking
      template:
        src: templates/10-serverflags.conf.j2
        dest: /etc/X11/xorg.conf.d/10-serverflags.conf
    
  4. intel.yml

    KMS drivers (like for intel (i915) and amd (radeon)) require additional configuration besides a customized xorg.conf - for a „static“ output configuration (which works if the TV or AV receiver is not turned on) we need to force loading the display(s) EDID early during the boot process.

    This task therefore performs the following actions after the xrandr detection has been executed:

    • create an initramfs-hook to copy the EDID(s) into the initramfs
    • get the connector names and match them to the ones determined by xrandr (this is done by xrandr_facts.py)
    • add kernel boot arguments to set EDID and mode (refreshrate and resolution) for all outputs
    • recreate and update initramfs and grub config

    Please note that rescanning the connected displays works only after removing the forced loading of EDID(s) during boot (call clean-edids) and a reboot.

    - name: "create initramfs hook to copy EDID files"
      copy:
        src: files/include-edid-data.sh
        dest: '/etc/initramfs-tools/hooks/include-edid-data'
        mode: 0755
        force: yes
    
    - name: "create /lib/firmware/edid"
      file:
        state: directory
        dest: /lib/firmware/edid
    # TODO:
    # - write EDID files to /lib/firmware/edid/
    # - complete template for grub.d
    name: "add kernel boot parameters for static display configuration"
    template:
      src: templates/grub.d/intel.j2
      dest: /etc/grub.d/99_intel
      mode: 0755
      notify: ['Update Initramfs', 'Update GRUB']
    
    

7.11.4 templates

  1. grub
    {{ ansible_managed | comment }}
    {% set output_flag = 'D' if ("HDMI" in xorg.primary.connector or "DVI" in xorg.primary.connector or "DP" in xorg.primary.connector) else 'e' %} 
    GRUB_CMDLINE_LINUX+=" video={{ drm.primary.drm_connector }}:{{ xorg.primary.mode|replace('_', '@') }}{{ output_flag }} drm.edid_firmware={{ drm.primary.drm_connector }}:edid/{{ drm.primary.edid }} {% for ignored_c in drm.ignored_outputs %}video={{ ignored_c }}:d{% endfor %}"
    
  2. xorg
    1. x-verbose@.service
      # file: roles/yavdr-xorg/templates/systemd/system/x-verbose@.service.j2
      [Unit]
      Description=X with verbose logging on %I
      Wants=graphical.target
      Before=graphical.target
      
      [Service]
      Type=forking
      ExecStart=/usr/bin/x-daemon -logverbose 6 -noreset %I -config /etc/X11/xorg-verbose.conf
      
    2. Drop-in snippet for x@.service
      [Unit]
      After=plymouth.service
      
      [Unit]
      Before=xlogin@{{ vdr.user }}.service
      
      [Unit]
      Wants=x@vt7.service
      After=x@vt7.service
      
      [Service]
      TimeoutStopSec=20
      KillMode=mixed
      
    3. Drop-in snippet for vdr.service
      {{ ansible_managed | comment }}
      [Unit]
      After=x@vt7.service
      Wants=x@vt7.service
      #BindsTo=x@vt7.service
      
    4. xorg.conf snippets
      {{ ansible_managed | comment }}
      
      Section "InputClass"
          Identifier "exclude eventlircd devices"
          MatchTag   "eventlircd"
          Option     "Ignore"    "True"
      EndSection
      
    5. intel graphics xorg.conf snippet
      {{ ansible_managed | comment }}
      {% set primary_output = xorg.primary.connector|replace("-", "") %}
      {% if xorg.secondary is defined %}
        {% set secondary_output = xorg.secondary.connector|replace("-", "") %}
      {% endif %}
      
      Section "Device"
        Identifier  "Device0"
        Driver      "intel"
        Option      "TearFree" "true"
      {% if xorg.secondary is defined %}
        Option      "ZaphodHeads" "{{ primary_output }}"
        Screen      0  
      {% endif %}
      EndSection
      
      {% if xorg.secondary is defined %}
      Section "Device"
        Identifier  "Device1"
        Driver      "intel"
        Option      "TearFree" "true"
        Option      "ZaphodHeads" "{{ secondary_output }}"
        Screen      1  
      EndSection
      
      Section "ServerLayout"
          Identifier     "Layout0"
          Screen      0  "Screen0" 0 0
          Screen      1  "Screen1" RightOf "Screen0"
      EndSection
      {% endif %}
      
      Section "Screen"
        Identifier  "Screen0"
        Device      "Device0"
        Monitor     "{{ primary_output }}"
        DefaultDepth  24
        SubSection "Display"
          Depth       24
      {% if xorg.primary.mode %}
          Modes     "{{ xorg.primary.mode }}"
      {% else %}
          Modes     "1920x1080_50" "1920x1080_60" "1920x1080_24"
      {% endif %}
        EndSubSection
      EndSection
      
      {% if xorg.secondary is defined %}
      Section "Screen"
        Identifier  "Screen1"
        Device      "Device1"
        Monitor     "{{ secondary_output }}"
        DefaultDepth  24
        SubSection "Display"
          Depth       24
      {% if xorg.secondary.mode %}
          Modes     "{{ xorg.secondary.mode }}"
      {% else %}
          Modes     "1920x1080_50" "1920x1080_60" "1920x1080_24"
      {% endif %}
        EndSubSection
      EndSection
      
      {% endif %}
      Section "Monitor"
        Identifier  "{{ primary_output }}"
      {% if xorg.primary.modelines %}
      {% for modeline in xorg.primary.modelines %}
        {{ modeline }}
      {% endfor %}
      {% else %}
        Modeline    "1920x1080_24"     74.230 1920 2560 2604 2752 1080 1084 1089 1125 +hsync +vsync
        Modeline    "1920x1080_50"    148.500 1920 2448 2492 2640 1080 1084 1089 1125 +hsync +vsync
        Modeline    "1920x1080_60"    148.500 1920 2008 2056 2200 1080 1084 1089 1125 +hsync +vsync
      {% endif %}
      EndSection
      
      {% if xorg.secondary is defined %}
      Section "Monitor"
        Identifier  "{{ secondary_output }}"
      {% if xorg.secondary.modelines %}
      {% for modeline in xorg.secondary.modelines %}
        {{ modeline }}
      {% endfor %}
      {% else %}
        Modeline    "1920x1080_24"     74.230 1920 2560 2604 2752 1080 1084 1089 1125 +hsync +vsync
        Modeline    "1920x1080_50"    148.500 1920 2448 2492 2640 1080 1084 1089 1125 +hsync +vsync
        Modeline    "1920x1080_60"    148.500 1920 2008 2056 2200 1080 1084 1089 1125 +hsync +vsync
      {% endif %}
      EndSection
      
      {% endif %}
      {% for connector, data in xrandr["Screen 0:"].items() | list %}
      {% if not data.is_connected or connector != xorg.primary.connector or (xorg.secondary is defined and connector != xorg.secondary.connector) %}
      Section "Monitor"
        Identifier "{{ connector|replace("-","") }}"
        Option "Ignore" "true"
      EndSection
      {% endif %}
      
      {% endfor %}
      
    6. xorg.conf
      {{ ansible_managed | comment }}
      
      Section "Device"
              Identifier "nvidia"
              Driver  "nvidia"
              Option  "DynamicTwinView"     "true"
              Option  "NoFlip"              "false"
      EndSection
      
      Section "Screen"
              Identifier      "screen"
              Device          "nvidia"
      EndSection
      
      {{ ansible_managed | comment }}
      
      Section "ServerLayout"
          Identifier     "Layout0"
          Screen         0  "Screen0" 0 0
      {% if xorg.secondary is defined %}
          Screen         1  "Screen1" RightOf "Screen0"
      {% endif %}
          #InputDevice    "Keyboard0" "CoreKeyboard"
          #InputDevice    "Mouse0" "CorePointer"
          Option         "Xinerama" "0"
      EndSection
      
      Section "InputClass"
          Identifier "exclude eventlircd devices"
          MatchTag   "eventlircd"
          Option     "Ignore"    "True"
      EndSection
      
      Section "Monitor"
          Identifier     "Monitor0"
          VendorName     "{{ xorg.primary.vendor }}"
          ModelName      "{{ xorg.primary.model }}"
          Option         "DPMS"
          Option         "ExactModeTimingsDVI" "True"
      EndSection
      
      Section "Device"
          Identifier     "Device0"
          Driver         "nvidia"
          VendorName     "NVIDIA Corporation"
          Option         "NoLogo"              "true"
      {% if xorg.primary.bus_id is defined %}
          BoardName      "{{ xorg.primary.gpu_name }}"
          BusID          "{{ xorg.primary.bus_id }}"
      {% else %}
          BoardName      "Unknown"
      {% endif %}
          Screen          0
          Option         "ConnectedMonitor" "{{ xorg.primary.connector }}{% if xorg.secondary is defined %}, {{ xorg.secondary.connector }}{% endif %}"
          Option         "CustomEDID" "{{ xorg.primary.connector }}:/etc/X11/edid.{{ xorg.primary.connector }}.bin{% if xorg.secondary is defined %};{{ xorg.secondary.connector }}:/etc/X11/edid.{{ xorg.secondary.connector }}.bin{% endif %}"
      EndSection
      
      Section "Screen"
          Identifier     "Screen0"
          Device         "Device0"
          Monitor        "Monitor0"
          DefaultDepth    24
          Option         "nvidiaXineramaInfoOrder" "{{ xorg.primary.connector }}"
          Option         "UseDisplayDevice" "{{ xorg.primary.connector }}"
          Option         "metamodes" "{{ xorg.primary.connector }}: {{ xorg.primary.mode }} +0+0 {ForceCompositionPipeline=Off, ForceFullCompositionPipeline=Off}"
          Option         "AllowIndirectGLXProtocol" "off"
          Option         "TripleBuffer" "on"
          Option         "SLI" "Off"
          Option         "MultiGPU" "Off"
          Option         "BaseMosaic" "off"
          SubSection     "Display"
              Depth       24
          EndSubSection
      EndSection
      
      {% if xorg.secondary is defined %}
      Section "Device"
          Identifier     "Device1"
          Driver         "nvidia"
          VendorName     "NVIDIA Corporation"
          Option  "NoLogo"              "true"
      {% if xorg.secondary.bus_id is defined %}
          BoardName      "{{ xorg.primary.gpu_name }}"
          BusID          "{{ xorg.primary.bus_id }}"
      {% else %}
          BoardName      "Unknown"
      {% endif %}
          Screen          1
      EndSection
      
      Section "Monitor"
          Identifier     "Monitor1"
          VendorName     "{{ xorg.secondary.vendor }}"
          ModelName      "{{ xorg.secondary.model }}"
      EndSection
      
      Section "Screen"
          Identifier     "Screen1"
          Device         "Device1"
          Monitor        "Monitor1"
          DefaultDepth    24
          Option         "nvidiaXineramaInfoOrder" "{{ xorg.secondary.connector }}"
          #Option         "ConnectedMonitor" "{{ xorg.secondary.connector }}"
          Option         "UseDisplayDevice" "{{ xorg.secondary.connector }}"
          #Option         "CustomEDID" "{{ xorg.secondary.connector }}:/etc/X11/edid.{{ xorg.secondary.connector }}.bin"
          Option         "metamodes" "{{ xorg.secondary.connector }}: {{ xorg.secondary.mode }} +0+0 {ForceCompositionPipeline=Off, ForceFullCompositionPipeline=Off}"
          Option         "AllowIndirectGLXProtocol" "off"
          Option         "TripleBuffer" "on"
          Option         "SLI" "Off"
          Option         "MultiGPU" "Off"
          Option         "BaseMosaic" "off"
          SubSection     "Display"
              Depth       24
          EndSubSection
      EndSection
      {% endif %}
      
      Section "Extensions"
          Option         "Composite" "Disable"
      EndSection
      
      {{ ansible_managed | comment }}
      
      Section "ServerLayout"
          Identifier     "Layout0"
          Screen      0  "Screen0"
          {% if xorg.best_tv_mode.secondary is defined %}
          Screen      1  "Screen1" RightOf "Screen0"
          {% endif %}
          InputDevice    "Keyboard0" "CoreKeyboard"
          InputDevice    "Mouse0" "CorePointer"
      EndSection
      
      Section "InputDevice"
          # generated from default
          Identifier     "Mouse0"
          Driver         "mouse"
          Option         "Protocol" "auto"
          Option         "Device" "/dev/psaux"
          Option         "Emulate3Buttons" "no"
          Option         "ZAxisMapping" "4 5"
      EndSection
      
      # ignore devices with eventlircd tag 
      # ENV{ID_INPUT.tags}+="eventlircd"
      # must be set by an udev rule
      Section "InputClass"
          Identifier "ignore eventlircd devices"
          MatchTag "eventlircd"
          Option "Ignore" "True"
      EndSection
      
      
      
      Section "InputDevice"
          # generated from default
          Identifier     "Keyboard0"
          Driver         "kbd"
      EndSection
      
      
      Section "Monitor"
          Identifier     "Monitor0"
          VendorName     "Unknown"
          ModelName      "Unknown"
      {# TODO: VGA2SCART support (if needed)
      {% if system.x11.display.0.default == "VGA2Scart_4_3" or system.x11.display.0.default == "VGA2Scart_16_9" %}
          HorizSync       14-17
          VertRefresh     49-61
          {% if system.x11.display.0.default == "VGA2Scart_4_3" %}
          Modeline "VGA2Scart_4_3" 13.875 720 744 808 888 576 580 585 625 -HSync -Vsync interlace
          {% elif system.x11.display.0.default == "VGA2Scart_16_9" %}
          Modeline "VGA2Scart_16_9" 19 1024 1032 1120 1216 576 581 586 625 -Hsync -Vsync interlace
          {% endif %}
      {% endif %}
      #}
          Option         "DPMS"
          Option         "ExactModeTimingsDVI" "True"
      EndSection
      
      
      {% if xrandr.best_tv_mode.secondary is defined %}
      Section "Monitor"
          Identifier     "Monitor1"
          VendorName     "Unknown"
          ModelName      "Unknown"
      {#
      {% if system.x11.display.1.default in ("VGA2Scart_4_3",  "VGA2Scart_16_9") %}
          HorizSync       14-17
          VertRefresh     49-61
          {% if system.x11.display.1.default == "VGA2Scart_4_3" %}
          Modeline "VGA2Scart_4_3" 13.875 720 744 808 888 576 580 585 625 -HSync -Vsync interlace
          {% elif system.x11.display.1.default == "VGA2Scart_16_9" %}
          Modeline "VGA2Scart_16_9" 19 1024 1032 1120 1216 576 581 586 625 -Hsync -Vsync interlace
          {% endif %}
          Option         "DPMS"
          Option         "ExactModeTimingsDVI" "True"
      {% endif %}
      #}
      EndSection
      {% endif %}
      
      Section "Device"
          Identifier     "Device0"
      {% if system.hardware.nvidia.detected %}
          Driver         "nvidia"
          VendorName     "NVIDIA Corporation"
      {% endif %}
          Screen          0
          Option         "DPI" "100x100"
      {% if system.hardware.nvidia.busid is defined and system.hardware.busid %}
          BusID          "PCI: {{ system.hardware.nvidia.busid }}"
      {% endif %}
          Option         "NoLogo" "True"
          Option         "UseEvents" "True"
          Option         "TripleBuffer" "False"
          Option         "AddARGBGLXVisuals" "True"
          Option         "TwinView" "0"
          Option         "DynamicTwinView" "0"
          Option         "OnDemandVBlankinterrupts" "on"
          Option         "FlatPanelProperties" "Scaling = Native"
      EndSection
      
      {% if xrandr.best_tv_mode.secondary is defined %}
      Section "Device"
          Identifier     "Device1"
          {% if system.hardware.nvidia.detected %}
          Driver         "nvidia"
          VendorName     "NVIDIA Corporation"
          {% endif %}
          Screen          1
          {% if system.hardware.nvidia.busid is defined and system.hardware.nvidia.busid %}
          BusID          "PCI: {{ system.hardware.nvidia.busid }}"
          {% endif %}
          Option         "NoLogo" "True"
          Option         "UseEvents" "True"
          Option         "TripleBuffer" "False"
          Option         "AddARGBGLXVisuals" "True"
          Option         "TwinView" "0"
          Option         "DynamicTwinView" "0"
      EndSection
      {% endif %}
      
      
      Section "Screen"
          Identifier     "Screen0"
          Device         "Device0"
          Monitor        "Monitor0"
          DefaultDepth    24
          SubSection     "Display"
              Depth       24
      {% if xrandr.best_tv_mode.primary is defined %}
              Modes      "{{ xrandr.best_tv_mode.primary.mode }}"{#{% for mode in xrandr %}{% if mode != system.x11.display.0.default %} "{{ mode }}"{% endif %}{% endfor %}#} nvidia-auto-select
      
      {% elif system.hardware.nvidia.detected == 1 %}
              Modes      "nvidia-auto-select"
      {% endif %}
          EndSubSection
      {% if system.x11.display.0.default or system.x11.default %}
          {% if system.x11.display.0.device is definded and system.x11.display.0.device %}
          Option         "ConnectedMonitor" {{ system.x11.display.0.device }}
          {% else %}
          Option         "ConnectedMonitor" {{ system.x11.default }}
          {% endif %}
          #     Option         "ConnectedMonitor" "<?cs if:(?system.x11.display.0.device) ?><?cs call:fix_display_name(system.x11.display.0.device) ?><?cs else ?><?cs var:system.x11.default ?><?cs /if ?><?cs if:(?system.x11.dualhead.enabled && system.x11.dualhead.enabled == 1) ?>, <?cs call:fix_display_name(system.x11.display.1.device) ?><?cs /if ?>"
          #Option         "ConnectedMonitor"
          "<?cs if:(?system.x11.display.0.device) ?>
                <?cs call:fix_display_name(system.x11.display.0.device) ?>
           <?cs else ?>
                <?cs var:system.x11.default ?>
           <?cs /if ?>
           <?cs if:(?system.x11.dualhead.enabled && system.x11.dualhead.enabled == 1) ?>, <?cs call:fix_display_name(system.x11.display.1.device) ?><?cs /if ?>"
      #     Option         "UseDisplayDevice" "<?cs if:(?system.x11.display.0.device) ?><?cs call:fix_display_name(system.x11.display.0.device) ?><?cs else ?><?cs var:system.x11.default ?><?cs /if ?>"
      # <?cs /if ?>
      # <?cs if:(?system.hardware.nvidia.0.edid && system.hardware.nvidia.0.edid == "1") ?>
      #     Option         "CustomEDID" "<?cs call:fix_display_name(system.x11.display.0.device) ?>:/etc/X11/edid.0.yavdr"
      # <?cs /if ?>
      # <?cs if:(system.hardware.nvidia.detected == 1 && ?system.x11.display.0.device) ?>
      #     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.0.device) ?>: <?cs var:system.x11.display.0.default ?> { ViewPortIn=<?cs var:system.x11.display.0.viewport.in.x ?>x<?cs var:system.x11.display.0.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.0.viewport.out.x ?>x<?cs var:system.x11.display.0.viewport.out.y ?>+<?cs var:system.x11.display.0.viewport.out.plusx ?>+<?cs var:system.x11.display.0.viewport.out.plusy ?> }"
      # <?cs each:mode = system.x11.display.0.mode ?><?cs if:(mode != system.x11.display.0.default) ?>
      #     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.0.device) ?>: <?cs var:mode ?> { ViewPortIn=<?cs var:system.x11.display.0.viewport.in.x ?>x<?cs var:system.x11.display.0.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.0.viewport.out.x ?>x<?cs var:system.x11.display.0.viewport.out.y ?>+<?cs var:system.x11.display.0.viewport.out.plusx ?>+<?cs var:system.x11.display.0.viewport.out.plusy ?> }"<?cs /if ?><?cs /each ?> 
      {% endif %}
      EndSection
      
      {% if system.x11.dualhead.enabled == "1" %}
      Section "Screen"
      
           Identifier     "Screen1"
           Device         "Device1"
           Monitor        "Monitor1"
           DefaultDepth    24
           SubSection     "Display"
               Depth       24
      {% if system.x11.display.0.default is defined and system.x11.display.0.default %}
               Modes      "{{ system.x11.display.1.default }}"{% for mode in system.x11.display.1.mode %}{% if mode != system.x11.display.1.default %} "{{ mode }}"{% endif %}{% endfor %}
      
      {% elif system.hardware.nvidia.detected == "1" %}
               Modes      "nvidia-auto-select"
      {% endif %}
            EndSubSection
      
      # <?cs if:(?system.x11.display.1.default && system.x11.display.1.default != "" && system.x11.display.1.default != "disabled") ?>
      #     Option         "UseDisplayDevice" "<?cs call:fix_display_name(system.x11.display.1.device) ?>"
      # <?cs /if ?>
      # <?cs if:(?system.hardware.nvidia.1.edid && system.hardware.nvidia.1.edid == "1") ?>
      #     Option         "CustomEDID" "<?cs call:fix_display_name(system.x11.display.1.device) ?>:/etc/X11/edid.1.yavdr"
      # <?cs /if ?>
      # <?cs if:(system.hardware.nvidia.detected == 1 && ?system.x11.display.1.device) ?>
      #     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.1.device) ?>: <?cs var:system.x11.display.1.default ?> { ViewPortIn=<?cs var:system.x11.display.1.viewport.in.x ?>x<?cs var:system.x11.display.1.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.1.viewport.out.x ?>x<?cs var:system.x11.display.1.viewport.out.y ?>+<?cs var:system.x11.display.1.viewport.out.plusx ?>+<?cs var:system.x11.display.1.viewport.out.plusy ?> }"
      # <?cs each:mode = system.x11.display.1.mode ?><?cs if:(mode != system.x11.display.1.default) ?>
      #     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.1.device) ?>: <?cs var:mode ?> { ViewPortIn=<?cs var:system.x11.display.1.viewport.in.x ?>x<?cs var:system.x11.display.1.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.1.viewport.out.x ?>x<?cs var:system.x11.display.1.viewport.out.y ?>+<?cs var:system.x11.display.1.viewport.out.plusx ?>+<?cs var:system.x11.display.1.viewport.out.plusy ?> }"<?cs /if ?><?cs /each ?>
      # <?cs /if ?>
      EndSection
      {% endif %}
      
    7. snippet to disable screen blanking
      {{ ansible_managed | comment }}
      Section "ServerFlags"
          Option         "NoPM" "true"
          Option         "blank time" "0"
          Option         "standby time" "0"
          Option         "suspend time" "0"
          Option         "off time" "0"
      EndSection
      
  3. vdr

    Set environment variables for system locale and user session on startup

    [Service]
    EnvironmentFile=-/etc/default/locale
    Environment=XDG_RUNTIME_DIR=/run/user/{{ vdr.uid }}/
    EnvironmentFile=-/var/lib/vdr/.session-env
    

7.11.5 files

  1. initramfs EDID hook
    #!/bin/bash
    {{ ansible_managed | comment }}
    # This hook copies EDID files with the naming scheme "edid.${OUTPUT}.bin" to the initramfs.
    
    [ "$1" = "prereqs" ] && { echo "udev"; exit 0; }
    
    # load hook helper functions
    . /usr/share/initramfs-tools/hook-functions
    
    rm /lib/firmware/edid/edid.*.bin
    find "/etc/X11/" -name "edid.*.bin" -type f -exec cp -t "/lib/firmware/edid/" {} +
    mkdir -p "${DESTDIR}/lib/firmware/edid"
    find "/etc/X11/" -name "edid.*.bin" -type f -exec cp -t "${DESTDIR}/lib/firmware/edid/" {} +
    manual_add_modules i915 radeon
    exit 0
    

7.12 yavdr-desktop

7.12.1 Tasks

  1. main.yml
    ---
    # file: roles/yavdr-desktop/tasks/main.yml
    
    - name: create folders for user configuration files
      file:
        state: directory
        dest: '{{ item }}'
        mode: '0775'
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
      with_items:
        - '{{ vdr.home }}/.config/systemd/user'
        - '{{ vdr.home }}/.config/openbox'
        - '{{ vdr.home }}/.config/pulse'
        - '{{ vdr.home }}/bin'
    
    - name: expand template for .xinitrc for vdr user
      template:
          src: 'templates/.xinitrc.j2'
          dest: '{{ vdr.home }}/.xinitrc'
          mode: 0755
          owner: '{{ vdr.user }}'
          group: '{{ vdr.group }}'
    
    - name: "expand template for vdr's .Xresources"
      template:
          src: 'templates/.Xresources.j2'
          dest: '{{ vdr.home }}/.Xresources'
          mode: 0644
          owner: '{{ vdr.user }}'
          group: '{{ vdr.group }}'
    
    - name: expand template for openbox autostart
      template:
          src: 'templates/openbox/autostart.j2'
          dest: '{{ vdr.home }}/.config/openbox/autostart'
          mode: 0755
          owner: '{{ vdr.user }}'
          group: '{{ vdr.group }}'
    
    - name: expand rc.xml for openbox
      template:
          src: 'templates/openbox/rc.xml.j2'
          dest: '{{ vdr.home }}/.config/openbox/rc.xml'
          mode: 0644
          owner: '{{ vdr.user }}'
          group: '{{ vdr.group }}'
    
    - name: expand rc2.xml for openbox on second display
      template:
          src: 'templates/openbox/rc2.xml.j2'
          dest: '{{ vdr.home }}/.config/openbox/rc2.xml'
          mode: 0644
          owner: '{{ vdr.user }}'
          group: '{{ vdr.group }}'
    
    - name: create yavdr-desktop.target for the user session
      template:
          src: 'templates/systemd/user/yavdr-desktop.target.j2'
          dest: '{{ vdr.home }}/.config/systemd/user/yavdr-desktop.target'
          mode: 0644
          owner: '{{ vdr.user }}'
          group: '{{ vdr.group }}'
    
    - name: disable pulseaudio autospawning
      lineinfile:
        path: '{{ vdr.home }}/.config/pulse/client.conf'
        line: 'autospawn = yes'
        create: yes
        state: present
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
    
    - name: create tmux.service for the session
      template:
        src: 'templates/systemd/user/tmux.service.j2'
        dest: '{{ vdr.home }}/.config/systemd/user/tmux.service'
        mode: 0644
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
    
    - name: create wm-exit.service for the session
      template:
        src: templates/systemd/user/wm-exit.service.j2
        dest: '{{ vdr.home }}/.config/systemd/user/wm-exit.service'
        mode: 0644
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
    
    - name: create detect-second-display.service for the session
      template:
        src: templates/systemd/user/detect-second-display.service.j2
        dest: '{{ vdr.home }}/.config/systemd/user/detect-second-display.service'
        mode: 0644
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
    
    - name: create detect-second-diplay script
      template:
        src: templates/bin/detect-second-display.j2
        dest: '{{ vdr.home }}/bin/detect-second-display'
        mode: 0755
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
    
    - name: create switch-displays script
      template:
        src: templates/bin/switch-displays.j2
        dest: '{{ vdr.home }}/bin/switch-displays'
        mode: 0755
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
    
    - name: create openbox-second.service for the session
      template:
        src: templates/systemd/user/openbox-second.service.j2
        dest: '{{ vdr.home }}/.config/systemd/user/openbox-second.service'
        mode: 0644
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
    
    - name: create osd2web.service for the session
      template:
        src: templates/systemd/user/osd2web.service.j2
        dest: '{{ vdr.home }}/.config/systemd/user/osd2web.service'
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
        mode: 0644
    
    - name: create irexec configuration for the session
      block:
        - template:
            src: templates/systemd/user/irexec.service.j2
            dest: '{{ vdr.home }}/.config/systemd/user/irexec.service'
            mode: 0644
            owner: '{{ vdr.user }}'
            group: '{{ vdr.group }}'
        - template:
            src: templates/.lircrc.j2
            dest: '{{ vdr.home }}/.lircrc'
            mode: 0644
            owner: '{{ vdr.user }}'
            group: '{{ vdr.group }}'
            force: no
    
    - name: link /usr/bin/start-desktop to /var/lib/vdr/plugins/desktop/starter
      file:
        src: /usr/bin/start-desktop
        dest: /var/lib/vdr/plugins/desktop/starter
        state: link
      
    - name: enable and start yavdr-xorg for the vdr user
      systemd:
        daemon_reload: yes
        name: 'yavdr-xorg'
        enabled: yes
        state: started
    

7.12.2 Templates

  1. xorg configuration
    1. .Xresources
      xterm*background: Black
      xterm*foreground: grey
      XTerm*locale: true
      XTerm*metaSendsEscape: true
      XTerm*eightBitInput: false
      XTerm*backarrowKey: false
      XTerm*ttyModes: erase ^?
      Xterm*saveLines: 4096
      XTerm.vt100.metaSendsEscape: true
      XTerm.vt100.geometry: 80x32
      XTerm.vt100.renderFont: true
      xterm*faceName: xft:DejaVu Sans Mono
      xterm*faceSize: 14
      xterm*renderFont: true
      XTerm.vt100.faceName: xft:DejaVu Sans Mono:size=12:antialias=false
      XTerm.vt100.font: 7x13
      
    2. .xinitrc
      #!/bin/bash
      {{ ansible_managed | comment }}
      exec openbox-session
      
  2. openbox
    1. autostart
      # forward environment variables to an environment file and the systemd user session
      env | grep "DISPLAY\|DBUS_SESSION_BUS_ADDRESS\|XDG_RUNTIME_DIR" > ~/.session-env
      systemctl --user import-environment DISPLAY XAUTHORITY XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS
      
      feh --bg-center "/usr/share/yavdr/images/yavdr_logo.png"
      
      enabled_services=(
          "tmux.service" "detect-second-display.service"
          "openbox-second.service" "osd2web.service"
          "yavdr-frontend.service" "pulseaudio.socket"
          "pulseaudio.service" "wm-exit.service" "irexec.service")
      disabled_services=()
      
      # enable configured services for the user session
      for service in "${enabled_services[@]}"; do
        systemctl --user enable "$service"
      done
      
      # disable unwanted services for the user session
      for service in "${disabled_services[@]}"; do
        systemctl --user disable "$service"
      done
      
      if which dbus-update-activation-environment >/dev/null 2>&1; then
              dbus-update-activation-environment DISPLAY XAUTHORITY XDG_RUNTIME_DIR
      fi
      
      # start systemd units for the yavdr user session
      systemctl --user isolate yavdr-desktop.target
      
    2. rc.xml
      <?xml version="1.0" encoding="UTF-8"?>
      <openbox_config xmlns="http://openbox.org/3.4/rc" xmlns:xi="http://www.w3.org/2001/XInclude">
      <!-- Do not edit this file, it will be overwritten on install.
              Copy the file to $HOME/.config/openbox/ instead. -->
        <resistance>
          <strength>10</strength>
          <screen_edge_strength>20</screen_edge_strength>
        </resistance>
        <focus>
          <focusNew>yes</focusNew>
          <!-- always try to focus new windows when they appear. other rules do
             apply -->
          <followMouse>no</followMouse>
          <!-- move focus to a window when you move the mouse into it -->
          <focusLast>yes</focusLast>
          <!-- focus the last used window when changing desktops, instead of the one
             under the mouse pointer. when followMouse is enabled -->
          <underMouse>no</underMouse>
          <!-- move focus under the mouse, even when the mouse is not moving -->
          <focusDelay>200</focusDelay>
          <!-- when followMouse is enabled, the mouse must be inside the window for
             this many milliseconds (1000 = 1 sec) before moving focus to it -->
          <raiseOnFocus>no</raiseOnFocus>
          <!-- when followMouse is enabled, and a window is given focus by moving the
             mouse into it, also raise the window -->
        </focus>
        <placement>
          <policy>Smart</policy>
          <!-- 'Smart' or 'UnderMouse' -->
          <center>yes</center>
          <!-- whether to place windows in the center of the free area found or
             the top left corner -->
          <monitor>Active</monitor>
          <!-- with Smart placement on a multi-monitor system, try to place new windows
             on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
             the active window is, 'Primary' - only on the primary monitor -->
          <primaryMonitor>Active</primaryMonitor>
          <!-- The monitor where Openbox should place popup dialogs such as the
             focus cycling popup, or the desktop switch popup.  It can be an index
             from 1, specifying a particular monitor.  Or it can be one of the
             following: 'Mouse' - where the mouse is, or
                        'Active' - where the active window is -->
        </placement>
        <theme>
          <name>Onyx</name>
          <titleLayout>NLIMC</titleLayout>
          <!--
            available characters are NDSLIMC, each can occur at most once.
            N: window icon
            L: window label (AKA title).
            I: iconify
            M: maximize
            C: close
            S: shade (roll up/down)
            D: omnipresent (on all desktops).
        -->
          <keepBorder>no</keepBorder>
          <animateIconify>yes</animateIconify>
          <font place="ActiveWindow">
            <name>sans</name>
            <size>8</size>
            <!-- font size in points -->
            <weight>bold</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
          <font place="InactiveWindow">
            <name>sans</name>
            <size>8</size>
            <!-- font size in points -->
            <weight>bold</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
          <font place="MenuHeader">
            <name>sans</name>
            <size>9</size>
            <!-- font size in points -->
            <weight>normal</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
          <font place="MenuItem">
            <name>sans</name>
            <size>9</size>
            <!-- font size in points -->
            <weight>normal</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
          <font place="ActiveOnScreenDisplay">
            <name>sans</name>
            <size>9</size>
            <!-- font size in points -->
            <weight>bold</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
          <font place="InactiveOnScreenDisplay">
            <name>sans</name>
            <size>9</size>
            <!-- font size in points -->
            <weight>bold</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
        </theme>
        <desktops>
          <!-- this stuff is only used at startup, pagers allow you to change them
             during a session
      
             these are default values to use when other ones are not already set
             by other applications, or saved in your session
      
             use obconf if you want to change these without having to log out
             and back in -->
          <number>2</number>
          <firstdesk>1</firstdesk>
          <names>
            <!-- set names up here if you want to, like this:
          <name>desktop 1</name>
          <name>desktop 2</name>
          -->
          </names>
          <popupTime>875</popupTime>
          <!-- The number of milliseconds to show the popup for when switching
             desktops.  Set this to 0 to disable the popup. -->
        </desktops>
        <resize>
          <drawContents>yes</drawContents>
          <popupShow>Nonpixel</popupShow>
          <!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
          <popupPosition>Center</popupPosition>
          <!-- 'Center', 'Top', or 'Fixed' -->
          <popupFixedPosition>
            <!-- these are used if popupPosition is set to 'Fixed' -->
            <x>10</x>
            <!-- positive number for distance from left edge, negative number for
               distance from right edge, or 'Center' -->
            <y>10</y>
            <!-- positive number for distance from top edge, negative number for
               distance from bottom edge, or 'Center' -->
          </popupFixedPosition>
        </resize>
        <!-- You can reserve a portion of your screen where windows will not cover when
           they are maximized, or when they are initially placed.
           Many programs reserve space automatically, but you can use this in other
           cases. -->
        <margins>
          <top>0</top>
          <bottom>0</bottom>
          <left>0</left>
          <right>0</right>
        </margins>
        <dock>
          <position>TopLeft</position>
          <!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
          <floatingX>0</floatingX>
          <floatingY>0</floatingY>
          <noStrut>no</noStrut>
          <stacking>Above</stacking>
          <!-- 'Above', 'Normal', or 'Below' -->
          <direction>Vertical</direction>
          <!-- 'Vertical' or 'Horizontal' -->
          <autoHide>no</autoHide>
          <hideDelay>300</hideDelay>
          <!-- in milliseconds (1000 = 1 second) -->
          <showDelay>300</showDelay>
          <!-- in milliseconds (1000 = 1 second) -->
          <moveButton>Middle</moveButton>
          <!-- 'Left', 'Middle', 'Right' -->
        </dock>
        <keyboard>
          <chainQuitKey>C-g</chainQuitKey>
          <!-- Keybindings for yavdr-frontend -->
          <keybind key="W-a">
            <action name="Execute">
            <command>frontend-dbus-send start</command>
            </action>
          </keybind>
          <keybind key="W-s">
            <action name="Execute">
            <command>frontend-dbus-send stop</command>
            </action>
          </keybind>
          <keybind key="W-x">
            <action name="Execute">
            <command>frontend-dbus-send switchbetween kodi vdr</command>
            </action>
          </keybind>
          <keybind key="XF86HomePage">
            <action name="Execute">
            <command>frontend-dbus-send toggle</command>
            </action>
          </keybind>
          <!-- Keybindings for desktop switching -->
          <keybind key="C-A-Left">
            <action name="GoToDesktop">
              <to>left</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="C-A-Right">
            <action name="GoToDesktop">
              <to>right</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="C-A-Up">
            <action name="GoToDesktop">
              <to>up</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="C-A-Down">
            <action name="GoToDesktop">
              <to>down</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="S-A-Left">
            <action name="SendToDesktop">
              <to>left</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="S-A-Right">
            <action name="SendToDesktop">
              <to>right</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="S-A-Up">
            <action name="SendToDesktop">
              <to>up</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="S-A-Down">
            <action name="SendToDesktop">
              <to>down</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="W-F1">
            <action name="GoToDesktop">
              <to>1</to>
            </action>
          </keybind>
          <keybind key="W-F2">
            <action name="GoToDesktop">
              <to>2</to>
            </action>
          </keybind>
          <keybind key="W-F3">
            <action name="GoToDesktop">
              <to>3</to>
            </action>
          </keybind>
          <keybind key="W-F4">
            <action name="GoToDesktop">
              <to>4</to>
            </action>
          </keybind>
          <keybind key="W-d">
            <action name="ToggleShowDesktop"/>
          </keybind>
          <!-- Keybindings for windows -->
          <keybind key="A-F4">
            <action name="Close"/>
          </keybind>
          <keybind key="A-Escape">
            <action name="Lower"/>
            <action name="FocusToBottom"/>
            <action name="Unfocus"/>
          </keybind>
          <keybind key="A-space">
            <action name="ShowMenu">
              <menu>client-menu</menu>
            </action>
          </keybind>
          <!-- Take a screenshot of the current window with scrot when Alt+Print are pressed -->
          <keybind key="A-Print">
            <action name="Execute">
              <command>scrot -s</command>
            </action>
          </keybind>
          <!-- Keybindings for window switching -->
          <keybind key="A-Tab">
            <action name="NextWindow">
              <finalactions>
                <action name="Focus"/>
                <action name="Raise"/>
                <action name="Unshade"/>
              </finalactions>
            </action>
          </keybind>
          <keybind key="A-S-Tab">
            <action name="PreviousWindow">
              <finalactions>
                <action name="Focus"/>
                <action name="Raise"/>
                <action name="Unshade"/>
              </finalactions>
            </action>
          </keybind>
          <keybind key="C-A-Tab">
            <action name="NextWindow">
              <panels>yes</panels>
              <desktop>yes</desktop>
              <finalactions>
                <action name="Focus"/>
                <action name="Raise"/>
                <action name="Unshade"/>
              </finalactions>
            </action>
          </keybind>
          <!-- Keybindings for window switching with the arrow keys -->
          <keybind key="W-S-Right">
            <action name="DirectionalCycleWindows">
              <direction>right</direction>
            </action>
          </keybind>
          <keybind key="W-S-Left">
            <action name="DirectionalCycleWindows">
              <direction>left</direction>
            </action>
          </keybind>
          <keybind key="W-S-Up">
            <action name="DirectionalCycleWindows">
              <direction>up</direction>
            </action>
          </keybind>
          <keybind key="W-S-Down">
            <action name="DirectionalCycleWindows">
              <direction>down</direction>
            </action>
          </keybind>
        </keyboard>
        <mouse>
          <dragThreshold>1</dragThreshold>
          <!-- number of pixels the mouse must move before a drag begins -->
          <doubleClickTime>500</doubleClickTime>
          <!-- in milliseconds (1000 = 1 second) -->
          <screenEdgeWarpTime>400</screenEdgeWarpTime>
          <!-- Time before changing desktops when the pointer touches the edge of the
             screen while moving a window, in milliseconds (1000 = 1 second).
             Set this to 0 to disable warping -->
          <screenEdgeWarpMouse>false</screenEdgeWarpMouse>
          <!-- Set this to TRUE to move the mouse pointer across the desktop when
             switching due to hitting the edge of the screen -->
          <context name="Frame">
            <mousebind button="A-Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="A-Left" action="Click">
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="A-Left" action="Drag">
              <action name="Move"/>
            </mousebind>
            <mousebind button="A-Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="A-Right" action="Drag">
              <action name="Resize"/>
            </mousebind>
            <mousebind button="A-Middle" action="Press">
              <action name="Lower"/>
              <action name="FocusToBottom"/>
              <action name="Unfocus"/>
            </mousebind>
            <mousebind button="A-Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="A-Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="C-A-Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="C-A-Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="A-S-Up" action="Click">
              <action name="SendToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="A-S-Down" action="Click">
              <action name="SendToDesktop">
                <to>next</to>
              </action>
            </mousebind>
          </context>
          <context name="Titlebar">
            <mousebind button="Left" action="Drag">
              <action name="Move"/>
            </mousebind>
            <mousebind button="Left" action="DoubleClick">
              <action name="ToggleMaximize"/>
            </mousebind>
            <mousebind button="Up" action="Click">
              <action name="if">
                <shaded>no</shaded>
                <then>
                  <action name="Shade"/>
                  <action name="FocusToBottom"/>
                  <action name="Unfocus"/>
                  <action name="Lower"/>
                </then>
              </action>
            </mousebind>
            <mousebind button="Down" action="Click">
              <action name="if">
                <shaded>yes</shaded>
                <then>
                  <action name="Unshade"/>
                  <action name="Raise"/>
                </then>
              </action>
            </mousebind>
          </context>
          <context name="Titlebar Top Right Bottom Left TLCorner TRCorner BRCorner BLCorner">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Middle" action="Press">
              <action name="Lower"/>
              <action name="FocusToBottom"/>
              <action name="Unfocus"/>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="ShowMenu">
                <menu>client-menu</menu>
              </action>
            </mousebind>
          </context>
          <context name="Top">
            <mousebind button="Left" action="Drag">
              <action name="Resize">
                <edge>top</edge>
              </action>
            </mousebind>
          </context>
          <context name="Left">
            <mousebind button="Left" action="Drag">
              <action name="Resize">
                <edge>left</edge>
              </action>
            </mousebind>
          </context>
          <context name="Right">
            <mousebind button="Left" action="Drag">
              <action name="Resize">
                <edge>right</edge>
              </action>
            </mousebind>
          </context>
          <context name="Bottom">
            <mousebind button="Left" action="Drag">
              <action name="Resize">
                <edge>bottom</edge>
              </action>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="ShowMenu">
                <menu>client-menu</menu>
              </action>
            </mousebind>
          </context>
          <context name="TRCorner BRCorner TLCorner BLCorner">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Left" action="Drag">
              <action name="Resize"/>
            </mousebind>
          </context>
          <context name="Client">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="Middle" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
          </context>
          <context name="Icon">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
              <action name="ShowMenu">
                <menu>client-menu</menu>
              </action>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="ShowMenu">
                <menu>client-menu</menu>
              </action>
            </mousebind>
          </context>
          <context name="AllDesktops">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Left" action="Click">
              <action name="ToggleOmnipresent"/>
            </mousebind>
          </context>
          <context name="Shade">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="Left" action="Click">
              <action name="ToggleShade"/>
            </mousebind>
          </context>
          <context name="Iconify">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="Left" action="Click">
              <action name="Iconify"/>
            </mousebind>
          </context>
          <context name="Maximize">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Middle" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Left" action="Click">
              <action name="ToggleMaximize"/>
            </mousebind>
            <mousebind button="Middle" action="Click">
              <action name="ToggleMaximize">
                <direction>vertical</direction>
              </action>
            </mousebind>
            <mousebind button="Right" action="Click">
              <action name="ToggleMaximize">
                <direction>horizontal</direction>
              </action>
            </mousebind>
          </context>
          <context name="Close">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Left" action="Click">
              <action name="Close"/>
            </mousebind>
          </context>
          <context name="Desktop">
            <mousebind button="Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="A-Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="A-Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="C-A-Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="C-A-Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
          </context>
          <context name="Root">
            <!-- Menus -->
            <mousebind button="Middle" action="Press">
              <action name="ShowMenu">
                <menu>client-list-combined-menu</menu>
              </action>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="ShowMenu">
                <menu>root-menu</menu>
              </action>
            </mousebind>
          </context>
          <context name="MoveResize">
            <mousebind button="Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="A-Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="A-Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
          </context>
        </mouse>
        <menu>
          <!-- You can specify more than one menu file in here and they are all loaded,
             just don't make menu ids clash or, well, it'll be kind of pointless -->
          <!-- default menu file (or custom one in $HOME/.config/openbox/) -->
          <!-- system menu files on Debian systems -->
          <file>/var/lib/openbox/debian-menu.xml</file>
          <file>menu.xml</file>
          <hideDelay>200</hideDelay>
          <!-- if a press-release lasts longer than this setting (in milliseconds), the
             menu is hidden again -->
          <middle>no</middle>
          <!-- center submenus vertically about the parent entry -->
          <submenuShowDelay>100</submenuShowDelay>
          <!-- time to delay before showing a submenu after hovering over the parent
             entry.
             if this is a negative value, then the delay is infinite and the
             submenu will not be shown until it is clicked on -->
          <submenuHideDelay>400</submenuHideDelay>
          <!-- time to delay before hiding a submenu when selecting another
             entry in parent menu
             if this is a negative value, then the delay is infinite and the
             submenu will not be hidden until a different submenu is opened -->
          <showIcons>yes</showIcons>
          <!-- controls if icons appear in the client-list-(combined-)menu -->
          <manageDesktops>yes</manageDesktops>
          <!-- show the manage desktops section in the client-list-(combined-)menu -->
        </menu>
          <!--
        # this is an example with comments through out. use these to make your
        # own rules, but without the comments of course.
        # you may use one or more of the name/class/role/title/type rules to specify
        # windows to match
      
        <application name="the window's _OB_APP_NAME property (see obxprop)"
                    class="the window's _OB_APP_CLASS property (see obxprop)"
                groupname="the window's _OB_APP_GROUP_NAME property (see obxprop)"
               groupclass="the window's _OB_APP_GROUP_CLASS property (see obxprop)"
                     role="the window's _OB_APP_ROLE property (see obxprop)"
                    title="the window's _OB_APP_TITLE property (see obxprop)"
                     type="the window's _OB_APP_TYPE property (see obxprob)..
                            (if unspecified, then it is 'dialog' for child windows)">
        # you may set only one of name/class/role/title/type, or you may use more
        # than one together to restrict your matches.
      
        # the name, class, role, and title use simple wildcard matching such as those
        # used by a shell. you can use * to match any characters and ? to match
        # any single character.
      
        # the type is one of: normal, dialog, splash, utility, menu, toolbar, dock,
        #    or desktop
      
        # when multiple rules match a window, they will all be applied, in the
        # order that they appear in this list
      
      
          # each rule element can be left out or set to 'default' to specify to not 
          # change that attribute of the window
      
          <decor>yes</decor>
          # enable or disable window decorations
      
          <shade>no</shade>
          # make the window shaded when it appears, or not
      
          <position force="no">
            # the position is only used if both an x and y coordinate are provided
            # (and not set to 'default')
            # when force is "yes", then the window will be placed here even if it
            # says you want it placed elsewhere.  this is to override buggy
            # applications who refuse to behave
            <x>center</x>
            # a number like 50, or 'center' to center on screen. use a negative number
            # to start from the right (or bottom for <y>), ie -50 is 50 pixels from
            # the right edge (or bottom). use 'default' to specify using value
            # provided by the application, or chosen by openbox, instead.
            <y>200</y>
            <monitor>1</monitor>
            # specifies the monitor in a xinerama setup.
            # 1 is the first head, or 'mouse' for wherever the mouse is
          </position>
      
          <size>
            # the size to make the window.
            <width>20</width>
            # a number like 20, or 'default' to use the size given by the application.
            # you can use fractions such as 1/2 or percentages such as 75% in which
            # case the value is relative to the size of the monitor that the window
            # appears on.
            <height>30%</height>
          </size>
      
          <focus>yes</focus>
          # if the window should try be given focus when it appears. if this is set
          # to yes it doesn't guarantee the window will be given focus. some
          # restrictions may apply, but Openbox will try to
      
          <desktop>1</desktop>
          # 1 is the first desktop, 'all' for all desktops
      
          <layer>normal</layer>
          # 'above', 'normal', or 'below'
      
          <iconic>no</iconic>
          # make the window iconified when it appears, or not
      
          <skip_pager>no</skip_pager>
          # asks to not be shown in pagers
      
          <skip_taskbar>no</skip_taskbar>
          # asks to not be shown in taskbars. window cycling actions will also
          # skip past such windows
      
          <fullscreen>yes</fullscreen>
          # make the window in fullscreen mode when it appears
      
          <maximized>true</maximized>
          # 'Horizontal', 'Vertical' or boolean (yes/no)
        </application>
      
        # end of the example
      -->
        <applications>
          <application title="softhddevice">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
          <application title="softhdcuvid">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
          <application title="vaapidevice">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
          <application class="VDR">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
          <application title="browser">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
          <application title="osd2Web">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
        </applications>
      </openbox_config>
      
      
    3. rc2.xml
      <?xml version="1.0" encoding="UTF-8"?>
      <openbox_config xmlns="http://openbox.org/3.4/rc" xmlns:xi="http://www.w3.org/2001/XInclude">
      <!-- Do not edit this file, it will be overwritten on install.
              Copy the file to $HOME/.config/openbox/ instead. -->
        <resistance>
          <strength>10</strength>
          <screen_edge_strength>20</screen_edge_strength>
        </resistance>
        <focus>
          <focusNew>no</focusNew>
          <!-- always try to focus new windows when they appear. other rules do
             apply -->
          <followMouse>no</followMouse>
          <!-- move focus to a window when you move the mouse into it -->
          <focusLast>yes</focusLast>
          <!-- focus the last used window when changing desktops, instead of the one
             under the mouse pointer. when followMouse is enabled -->
          <underMouse>no</underMouse>
          <!-- move focus under the mouse, even when the mouse is not moving -->
          <focusDelay>200</focusDelay>
          <!-- when followMouse is enabled, the mouse must be inside the window for
             this many milliseconds (1000 = 1 sec) before moving focus to it -->
          <raiseOnFocus>no</raiseOnFocus>
          <!-- when followMouse is enabled, and a window is given focus by moving the
             mouse into it, also raise the window -->
        </focus>
        <placement>
          <policy>Smart</policy>
          <!-- 'Smart' or 'UnderMouse' -->
          <center>yes</center>
          <!-- whether to place windows in the center of the free area found or
             the top left corner -->
          <monitor>Active</monitor>
          <!-- with Smart placement on a multi-monitor system, try to place new windows
             on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
             the active window is, 'Primary' - only on the primary monitor -->
          <primaryMonitor>Active</primaryMonitor>
          <!-- The monitor where Openbox should place popup dialogs such as the
             focus cycling popup, or the desktop switch popup.  It can be an index
             from 1, specifying a particular monitor.  Or it can be one of the
             following: 'Mouse' - where the mouse is, or
                        'Active' - where the active window is -->
        </placement>
        <theme>
          <name>Onyx</name>
          <titleLayout>NLIMC</titleLayout>
          <!--
            available characters are NDSLIMC, each can occur at most once.
            N: window icon
            L: window label (AKA title).
            I: iconify
            M: maximize
            C: close
            S: shade (roll up/down)
            D: omnipresent (on all desktops).
        -->
          <keepBorder>no</keepBorder>
          <animateIconify>yes</animateIconify>
          <font place="ActiveWindow">
            <name>sans</name>
            <size>8</size>
            <!-- font size in points -->
            <weight>bold</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
          <font place="InactiveWindow">
            <name>sans</name>
            <size>8</size>
            <!-- font size in points -->
            <weight>bold</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
          <font place="MenuHeader">
            <name>sans</name>
            <size>9</size>
            <!-- font size in points -->
            <weight>normal</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
          <font place="MenuItem">
            <name>sans</name>
            <size>9</size>
            <!-- font size in points -->
            <weight>normal</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
          <font place="ActiveOnScreenDisplay">
            <name>sans</name>
            <size>9</size>
            <!-- font size in points -->
            <weight>bold</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
          <font place="InactiveOnScreenDisplay">
            <name>sans</name>
            <size>9</size>
            <!-- font size in points -->
            <weight>bold</weight>
            <!-- 'bold' or 'normal' -->
            <slant>normal</slant>
            <!-- 'italic' or 'normal' -->
          </font>
        </theme>
        <desktops>
          <!-- this stuff is only used at startup, pagers allow you to change them
             during a session
      
             these are default values to use when other ones are not already set
             by other applications, or saved in your session
      
             use obconf if you want to change these without having to log out
             and back in -->
          <number>2</number>
          <firstdesk>1</firstdesk>
          <names>
            <!-- set names up here if you want to, like this:
          <name>desktop 1</name>
          <name>desktop 2</name>
          -->
          </names>
          <popupTime>875</popupTime>
          <!-- The number of milliseconds to show the popup for when switching
             desktops.  Set this to 0 to disable the popup. -->
        </desktops>
        <resize>
          <drawContents>yes</drawContents>
          <popupShow>Nonpixel</popupShow>
          <!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
          <popupPosition>Center</popupPosition>
          <!-- 'Center', 'Top', or 'Fixed' -->
          <popupFixedPosition>
            <!-- these are used if popupPosition is set to 'Fixed' -->
            <x>10</x>
            <!-- positive number for distance from left edge, negative number for
               distance from right edge, or 'Center' -->
            <y>10</y>
            <!-- positive number for distance from top edge, negative number for
               distance from bottom edge, or 'Center' -->
          </popupFixedPosition>
        </resize>
        <!-- You can reserve a portion of your screen where windows will not cover when
           they are maximized, or when they are initially placed.
           Many programs reserve space automatically, but you can use this in other
           cases. -->
        <margins>
          <top>0</top>
          <bottom>0</bottom>
          <left>0</left>
          <right>0</right>
        </margins>
        <dock>
          <position>TopLeft</position>
          <!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
          <floatingX>0</floatingX>
          <floatingY>0</floatingY>
          <noStrut>no</noStrut>
          <stacking>Above</stacking>
          <!-- 'Above', 'Normal', or 'Below' -->
          <direction>Vertical</direction>
          <!-- 'Vertical' or 'Horizontal' -->
          <autoHide>no</autoHide>
          <hideDelay>300</hideDelay>
          <!-- in milliseconds (1000 = 1 second) -->
          <showDelay>300</showDelay>
          <!-- in milliseconds (1000 = 1 second) -->
          <moveButton>Middle</moveButton>
          <!-- 'Left', 'Middle', 'Right' -->
        </dock>
        <keyboard>
          <chainQuitKey>C-g</chainQuitKey>
          <!-- Keybindings for desktop switching -->
          <keybind key="C-A-Left">
            <action name="GoToDesktop">
              <to>left</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="C-A-Right">
            <action name="GoToDesktop">
              <to>right</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="C-A-Up">
            <action name="GoToDesktop">
              <to>up</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="C-A-Down">
            <action name="GoToDesktop">
              <to>down</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="S-A-Left">
            <action name="SendToDesktop">
              <to>left</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="S-A-Right">
            <action name="SendToDesktop">
              <to>right</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="S-A-Up">
            <action name="SendToDesktop">
              <to>up</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="S-A-Down">
            <action name="SendToDesktop">
              <to>down</to>
              <wrap>no</wrap>
            </action>
          </keybind>
          <keybind key="W-F1">
            <action name="GoToDesktop">
              <to>1</to>
            </action>
          </keybind>
          <keybind key="W-F2">
            <action name="GoToDesktop">
              <to>2</to>
            </action>
          </keybind>
          <keybind key="W-F3">
            <action name="GoToDesktop">
              <to>3</to>
            </action>
          </keybind>
          <keybind key="W-F4">
            <action name="GoToDesktop">
              <to>4</to>
            </action>
          </keybind>
          <keybind key="W-d">
            <action name="ToggleShowDesktop"/>
          </keybind>
          <!-- Keybindings for windows -->
          <keybind key="A-F4">
            <action name="Close"/>
          </keybind>
          <keybind key="A-Escape">
            <action name="Lower"/>
            <action name="FocusToBottom"/>
            <action name="Unfocus"/>
          </keybind>
          <keybind key="A-space">
            <action name="ShowMenu">
              <menu>client-menu</menu>
            </action>
          </keybind>
          <!-- Take a screenshot of the current window with scrot when Alt+Print are pressed -->
          <keybind key="A-Print">
            <action name="Execute">
              <command>scrot -s</command>
            </action>
          </keybind>
          <!-- Keybindings for window switching -->
          <keybind key="A-Tab">
            <action name="NextWindow">
              <finalactions>
                <action name="Focus"/>
                <action name="Raise"/>
                <action name="Unshade"/>
              </finalactions>
            </action>
          </keybind>
          <keybind key="A-S-Tab">
            <action name="PreviousWindow">
              <finalactions>
                <action name="Focus"/>
                <action name="Raise"/>
                <action name="Unshade"/>
              </finalactions>
            </action>
          </keybind>
          <keybind key="C-A-Tab">
            <action name="NextWindow">
              <panels>yes</panels>
              <desktop>yes</desktop>
              <finalactions>
                <action name="Focus"/>
                <action name="Raise"/>
                <action name="Unshade"/>
              </finalactions>
            </action>
          </keybind>
          <!-- Keybindings for window switching with the arrow keys -->
          <keybind key="W-S-Right">
            <action name="DirectionalCycleWindows">
              <direction>right</direction>
            </action>
          </keybind>
          <keybind key="W-S-Left">
            <action name="DirectionalCycleWindows">
              <direction>left</direction>
            </action>
          </keybind>
          <keybind key="W-S-Up">
            <action name="DirectionalCycleWindows">
              <direction>up</direction>
            </action>
          </keybind>
          <keybind key="W-S-Down">
            <action name="DirectionalCycleWindows">
              <direction>down</direction>
            </action>
          </keybind>
        </keyboard>
        <mouse>
          <dragThreshold>1</dragThreshold>
          <!-- number of pixels the mouse must move before a drag begins -->
          <doubleClickTime>500</doubleClickTime>
          <!-- in milliseconds (1000 = 1 second) -->
          <screenEdgeWarpTime>400</screenEdgeWarpTime>
          <!-- Time before changing desktops when the pointer touches the edge of the
             screen while moving a window, in milliseconds (1000 = 1 second).
             Set this to 0 to disable warping -->
          <screenEdgeWarpMouse>false</screenEdgeWarpMouse>
          <!-- Set this to TRUE to move the mouse pointer across the desktop when
             switching due to hitting the edge of the screen -->
          <context name="Frame">
            <mousebind button="A-Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="A-Left" action="Click">
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="A-Left" action="Drag">
              <action name="Move"/>
            </mousebind>
            <mousebind button="A-Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="A-Right" action="Drag">
              <action name="Resize"/>
            </mousebind>
            <mousebind button="A-Middle" action="Press">
              <action name="Lower"/>
              <action name="FocusToBottom"/>
              <action name="Unfocus"/>
            </mousebind>
            <mousebind button="A-Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="A-Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="C-A-Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="C-A-Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="A-S-Up" action="Click">
              <action name="SendToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="A-S-Down" action="Click">
              <action name="SendToDesktop">
                <to>next</to>
              </action>
            </mousebind>
          </context>
          <context name="Titlebar">
            <mousebind button="Left" action="Drag">
              <action name="Move"/>
            </mousebind>
            <mousebind button="Left" action="DoubleClick">
              <action name="ToggleMaximize"/>
            </mousebind>
            <mousebind button="Up" action="Click">
              <action name="if">
                <shaded>no</shaded>
                <then>
                  <action name="Shade"/>
                  <action name="FocusToBottom"/>
                  <action name="Unfocus"/>
                  <action name="Lower"/>
                </then>
              </action>
            </mousebind>
            <mousebind button="Down" action="Click">
              <action name="if">
                <shaded>yes</shaded>
                <then>
                  <action name="Unshade"/>
                  <action name="Raise"/>
                </then>
              </action>
            </mousebind>
          </context>
          <context name="Titlebar Top Right Bottom Left TLCorner TRCorner BRCorner BLCorner">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Middle" action="Press">
              <action name="Lower"/>
              <action name="FocusToBottom"/>
              <action name="Unfocus"/>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="ShowMenu">
                <menu>client-menu</menu>
              </action>
            </mousebind>
          </context>
          <context name="Top">
            <mousebind button="Left" action="Drag">
              <action name="Resize">
                <edge>top</edge>
              </action>
            </mousebind>
          </context>
          <context name="Left">
            <mousebind button="Left" action="Drag">
              <action name="Resize">
                <edge>left</edge>
              </action>
            </mousebind>
          </context>
          <context name="Right">
            <mousebind button="Left" action="Drag">
              <action name="Resize">
                <edge>right</edge>
              </action>
            </mousebind>
          </context>
          <context name="Bottom">
            <mousebind button="Left" action="Drag">
              <action name="Resize">
                <edge>bottom</edge>
              </action>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="ShowMenu">
                <menu>client-menu</menu>
              </action>
            </mousebind>
          </context>
          <context name="TRCorner BRCorner TLCorner BLCorner">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Left" action="Drag">
              <action name="Resize"/>
            </mousebind>
          </context>
          <context name="Client">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="Middle" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
          </context>
          <context name="Icon">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
              <action name="ShowMenu">
                <menu>client-menu</menu>
              </action>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="ShowMenu">
                <menu>client-menu</menu>
              </action>
            </mousebind>
          </context>
          <context name="AllDesktops">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Left" action="Click">
              <action name="ToggleOmnipresent"/>
            </mousebind>
          </context>
          <context name="Shade">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="Left" action="Click">
              <action name="ToggleShade"/>
            </mousebind>
          </context>
          <context name="Iconify">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="Left" action="Click">
              <action name="Iconify"/>
            </mousebind>
          </context>
          <context name="Maximize">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Middle" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Left" action="Click">
              <action name="ToggleMaximize"/>
            </mousebind>
            <mousebind button="Middle" action="Click">
              <action name="ToggleMaximize">
                <direction>vertical</direction>
              </action>
            </mousebind>
            <mousebind button="Right" action="Click">
              <action name="ToggleMaximize">
                <direction>horizontal</direction>
              </action>
            </mousebind>
          </context>
          <context name="Close">
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
              <action name="Unshade"/>
            </mousebind>
            <mousebind button="Left" action="Click">
              <action name="Close"/>
            </mousebind>
          </context>
          <context name="Desktop">
            <mousebind button="Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="A-Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="A-Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="C-A-Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="C-A-Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="Left" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="Focus"/>
              <action name="Raise"/>
            </mousebind>
          </context>
          <context name="Root">
            <!-- Menus -->
            <mousebind button="Middle" action="Press">
              <action name="ShowMenu">
                <menu>client-list-combined-menu</menu>
              </action>
            </mousebind>
            <mousebind button="Right" action="Press">
              <action name="ShowMenu">
                <menu>root-menu</menu>
              </action>
            </mousebind>
          </context>
          <context name="MoveResize">
            <mousebind button="Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
            <mousebind button="A-Up" action="Click">
              <action name="GoToDesktop">
                <to>previous</to>
              </action>
            </mousebind>
            <mousebind button="A-Down" action="Click">
              <action name="GoToDesktop">
                <to>next</to>
              </action>
            </mousebind>
          </context>
        </mouse>
        <menu>
          <!-- You can specify more than one menu file in here and they are all loaded,
             just don't make menu ids clash or, well, it'll be kind of pointless -->
          <!-- default menu file (or custom one in $HOME/.config/openbox/) -->
          <!-- system menu files on Debian systems -->
          <file>/var/lib/openbox/debian-menu.xml</file>
          <file>menu.xml</file>
          <hideDelay>200</hideDelay>
          <!-- if a press-release lasts longer than this setting (in milliseconds), the
             menu is hidden again -->
          <middle>no</middle>
          <!-- center submenus vertically about the parent entry -->
          <submenuShowDelay>100</submenuShowDelay>
          <!-- time to delay before showing a submenu after hovering over the parent
             entry.
             if this is a negative value, then the delay is infinite and the
             submenu will not be shown until it is clicked on -->
          <submenuHideDelay>400</submenuHideDelay>
          <!-- time to delay before hiding a submenu when selecting another
             entry in parent menu
             if this is a negative value, then the delay is infinite and the
             submenu will not be hidden until a different submenu is opened -->
          <showIcons>yes</showIcons>
          <!-- controls if icons appear in the client-list-(combined-)menu -->
          <manageDesktops>yes</manageDesktops>
          <!-- show the manage desktops section in the client-list-(combined-)menu -->
        </menu>
          <!--
        # this is an example with comments through out. use these to make your
        # own rules, but without the comments of course.
        # you may use one or more of the name/class/role/title/type rules to specify
        # windows to match
      
        <application name="the window's _OB_APP_NAME property (see obxprop)"
                    class="the window's _OB_APP_CLASS property (see obxprop)"
                groupname="the window's _OB_APP_GROUP_NAME property (see obxprop)"
               groupclass="the window's _OB_APP_GROUP_CLASS property (see obxprop)"
                     role="the window's _OB_APP_ROLE property (see obxprop)"
                    title="the window's _OB_APP_TITLE property (see obxprop)"
                     type="the window's _OB_APP_TYPE property (see obxprob)..
                            (if unspecified, then it is 'dialog' for child windows)">
        # you may set only one of name/class/role/title/type, or you may use more
        # than one together to restrict your matches.
      
        # the name, class, role, and title use simple wildcard matching such as those
        # used by a shell. you can use * to match any characters and ? to match
        # any single character.
      
        # the type is one of: normal, dialog, splash, utility, menu, toolbar, dock,
        #    or desktop
      
        # when multiple rules match a window, they will all be applied, in the
        # order that they appear in this list
      
      
          # each rule element can be left out or set to 'default' to specify to not 
          # change that attribute of the window
      
          <decor>yes</decor>
          # enable or disable window decorations
      
          <shade>no</shade>
          # make the window shaded when it appears, or not
      
          <position force="no">
            # the position is only used if both an x and y coordinate are provided
            # (and not set to 'default')
            # when force is "yes", then the window will be placed here even if it
            # says you want it placed elsewhere.  this is to override buggy
            # applications who refuse to behave
            <x>center</x>
            # a number like 50, or 'center' to center on screen. use a negative number
            # to start from the right (or bottom for <y>), ie -50 is 50 pixels from
            # the right edge (or bottom). use 'default' to specify using value
            # provided by the application, or chosen by openbox, instead.
            <y>200</y>
            <monitor>1</monitor>
            # specifies the monitor in a xinerama setup.
            # 1 is the first head, or 'mouse' for wherever the mouse is
          </position>
      
          <size>
            # the size to make the window.
            <width>20</width>
            # a number like 20, or 'default' to use the size given by the application.
            # you can use fractions such as 1/2 or percentages such as 75% in which
            # case the value is relative to the size of the monitor that the window
            # appears on.
            <height>30%</height>
          </size>
      
          <focus>yes</focus>
          # if the window should try be given focus when it appears. if this is set
          # to yes it doesn't guarantee the window will be given focus. some
          # restrictions may apply, but Openbox will try to
      
          <desktop>1</desktop>
          # 1 is the first desktop, 'all' for all desktops
      
          <layer>normal</layer>
          # 'above', 'normal', or 'below'
      
          <iconic>no</iconic>
          # make the window iconified when it appears, or not
      
          <skip_pager>no</skip_pager>
          # asks to not be shown in pagers
      
          <skip_taskbar>no</skip_taskbar>
          # asks to not be shown in taskbars. window cycling actions will also
          # skip past such windows
      
          <fullscreen>yes</fullscreen>
          # make the window in fullscreen mode when it appears
      
          <maximized>true</maximized>
          # 'Horizontal', 'Vertical' or boolean (yes/no)
        </application>
      
        # end of the example
      -->
        <applications>
          <application title="softhddevice">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
          <application title="softhdcuvid">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
          <application title="vaapidevice">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
          <application class="VDR">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
          <application title="browser">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
          <application title="osd2Web">
            <decor>no</decor>
            <maximized>yes</maximized>
            <!--<skip_pager>yes</skip_pager>-->
            <skip_taskbar>no</skip_taskbar>
          </application>
        </applications>
      </openbox_config>
      
      
  3. Systemd User Session
    1. yavdr-desktop.target
      [Unit]
      Description=yaVDR Desktop
      Requires=default.target
      After=default.target pulseaudio.service
      Wants=dbus.service pulseaudio.service
      AllowIsolate=yes
      
    2. detect-second-display
      #!/bin/bash
      source <(systemctl --user show-environment)
      SECOND_DISPLAY="${DISPLAY%\.[0-9]*}.1"
      if xrandr -d "$SECOND_DISPLAY" -q | grep -q "connected"; then
          [ "$DISPLAY" != "$SECOND_DISPLAY" ] && d="DISPLAY=$SECOND_DISPLAY" || d="DISPLAY=$DISPLAY"
          echo "$d" > ~/.second_display;
      else
          rm -f ~/.second_display
      fi
      
      [Unit]
      Description=Detect second DISPLAY using xrandr
      
      [Service]
      Type=oneshot
      RemainAfterExit=yes
      ExecStart=%h/bin/detect-second-display
      
      [Install]
      WantedBy=yavdr-desktop.target
      
    3. switch-displays

      This script exchanges the primary and secondary screen. It needs access to the systemd user session.

      #!/bin/bash
      source <(systemctl --user show-environment)
      [[ "$DISPLAY" =~ \.1$ ]] && DISPLAY="${DISPLAY%.1}.0" || DISPLAY="${DISPLAY%.0}.1"
      
      frontend-dbus-send stop
      systemctl --user stop osd2web
      frontend-dbus-send setDisplay "$DISPLAY"
      systemctl --user start osd2web
      frontend-dbus-send start
      
    4. openbox-second.service
      [Unit]
      Description=Start openbox on the second DISPLAY if it exists
      After=detect-second-display.service
      Wants=detect-second-display.service
      Before=yavdr-frontend.service
      ConditionFileNotEmpty=%h/.second_display
      
      [Service]
      EnvironmentFile=%h/.second_display
      ExecStart=/usr/bin/openbox --config-file %h/.config/openbox/rc2.xml
      ExecStartPost=/usr/bin/xset -dpms s off -display $DISPLAY
      ExecStartPost=/bin/bash %h/.fehbg
      
      [Install]
      WantedBy=yavdr-desktop.target
      
    5. osd2web.service
      [Unit]
      Description=Start a kiosk browser on the second DISPLAY if it exists
      After=detect-second-display.service openbox-second.service
      Wants=detect-second-display.service openbox-second.service
      ConditionFileNotEmpty=%h/.second_display
      
      [Service]
      EnvironmentFile=%h/.second_display
      Environment=url="http://localhost:4444/skins/horchiTft/index.html?theme=anthraize&onlyView=1"
      Environment=browser="kiosk-browser"
      EnvironmentFile=-%h/.config/osd2web/config
      ExecStart=/usr/bin/on_vdr -o -c '${browser} "${url}"'
      KillSignal=SIGINT
      
      [Install]
      WantedBy=yavdr-desktop.target
      
    6. tmux.service
      [Unit]
      Description=Start tmux in detached session
      
      [Service]
      EnvironmentFile=-%h/.session-env
      Type=forking
      ExecStart=/usr/bin/tmux new-session -s use-session -d
      ExecStop=/usr/bin/tmux kill-session -t %u
      Restart=always
      
      [Install]
      WantedBy=default.target
      
    7. wm-exit.service
      [Unit]
      Description=exit window manager gracefully
      
      [Service]
      ExecStart=/bin/true
      ExecStop=/usr/bin/openbox --exit
      RemainAfterExit=True
      
      [Install]
      WantedBy=default.target
      
  4. irexec

    irexec.service starts irexec for the user session

    [Unit]
    Description=LIRC command handler
    
    [Service]
    Type=simple
    ExecStart=/usr/bin/irexec %h/.lircrc
    
    [Install]
    WantedBy=yavdr-desktop.target
    

    ~/.lircrc contains the irexec configuration

    begin
        prog = irexec
        button = KEY_HOME
        config = frontend-dbus-send switchbetween kodi vdr
    end
    

7.13 samba-install

7.13.1 tasks

---
# file: roles/samba-install/tasks/main.yml

- name: install samba server
  apt:
    name:
      - samba
      - samba-common
      - samba-common-bin
      - tdb-tools
    state: present
    install_recommends: no

7.14 samba-config

7.14.1 tasks

---
# file: roles/samba-config/tasks/main.yml

# TODO:
# - name: divert original smbd.conf

- name: touch smb.conf.custom
  file:
    state: touch
    dest: '/etc/samba/smb.conf.custom'
  notify: [ 'Restart Samba' ]
    
- name: expand template for smb.conf
  template:
    src: 'templates/smb.conf.j2'
    dest: '/etc/samba/smb.conf'
    #validate: 'testparm -s %s'
  notify: [ 'Restart Samba' ]

7.14.2 templates

  1. smb.conf
    1. global settings
      {{ ansible_managed | comment }}
      
      #======================= Global Settings =======================
      
      [global]
      
      ## Browsing/Identification ###
      
      # Change this to the workgroup/NT-domain name your Samba server will part of
         workgroup = {{ samba.workgroup }}
      
      # server string is the equivalent of the NT Description field
         server string = %h server (Samba, Ubuntu)
      
      # This will prevent nmbd to search for NetBIOS names through DNS.
         dns proxy = no
      
      #### Debugging/Accounting ####
      
      # This tells Samba to use a separate log file for each machine
      # that connects
         log file = /var/log/samba/log.%m
      
      # Cap the size of the individual log files (in KiB).
         max log size = 1000
      
      # We want Samba to log a minimum amount of information to syslog. Everything
      # should go to /var/log/samba/log.{smbd,nmbd} instead. If you want to log
      # through syslog you should set the following parameter to something higher.
         syslog = 0
      
      # Do something sensible when Samba crashes: mail the admin a backtrace
         panic action = /usr/share/samba/panic-action %d
      
      
      ####### Authentication #######
      
      # "security = user" is always a good idea. This will require a Unix account
      # in this server for every user accessing the server. See
      # /usr/share/doc/samba-doc/htmldocs/Samba3-HOWTO/ServerType.html
      # in the samba-doc package for details.
      #   security = user
      
      # You may wish to use password encryption.  See the section on
      # 'encrypt passwords' in the smb.conf(5) manpage before enabling.
         encrypt passwords = true
      
      # If you are using encrypted passwords, Samba will need to know what
      # password database type you are using.  
         passdb backend = tdbsam
      
         obey pam restrictions = yes
      
      # This boolean parameter controls whether Samba attempts to sync the Unix
      # password with the SMB password when the encrypted SMB password in the
      # passdb is changed.
         unix password sync = yes
      
      # For Unix password sync to work on a Debian GNU/Linux system, the following
      # parameters must be set (thanks to Ian Kahan <<kahan@informatik.tu-muenchen.de> for
      # sending the correct chat script for the passwd program in Debian Sarge).
         passwd program = /usr/bin/passwd %u
         passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
      
      # This boolean controls whether PAM will be used for password changes
      # when requested by an SMB client instead of the program listed in
      # 'passwd program'. The default is 'no'.
         pam password change = yes
      
      # This option controls how unsuccessful authentication attempts are mapped 
      # to anonymous connections
         map to guest = bad password
         guest account = nobody
      
      {% if samba.windows_compatible %}
      # disable unix extensions and enable following symlinks
         unix extensions = no
         follow symlinks= yes
         wide links= yes
      {% endif %}
      
    2. media directories
      {% for name, path in media_dirs.items() | list %}
      [{{ name }}]
         path = {{ path }}
         comment = {{ name }} on %h
         guest ok = yes
         writeable = yes
         browseable = yes
         create mode = 0664
         directory mode = 0775
         force user = {{ vdr.user }}
         force group = {{ vdr.group }}
         follow symlinks = yes
         wide links = yes
      
      {% endfor %}
      
    3. include custom samba exports
      include = /etc/samba/smb.conf.custom
      

7.15 STARTED autoinstall-drivers

It would be nice to be able to detect if it is suitable to install those drivers:

7.15.1 TODO sundtek for Sundtek devices (local or network connection)

For now we have the role install-sundtek, in the future we could do a little better:

We should be able to detect local devices by USB-ID:

Vendor-IDs:

  • eb1a:5[1b2] (alte Generation)
  • 2659:* (neuere Sticks)
  1. planned packages
    • yavdr-hardware-sundtek (basic configuration files and scripts, sundtek-avahi-mounter)
    • python3-sundtek (Python C-extension for sundtek API)
    • yavdr-backend-sundtek (WFE)

7.15.2 TODO drivers for TT S2-6400 FF

https://github.com/s-moch/linux-saa716x - kann man den relevanten Teil als dkms-Paket bauen?

7.16 autoinstall-ubuntu-drivers

The tool ubuntu-drivers is used to install the matching driver version for nvidia graphics cards, virtualbox guest additions and Intel and AMD microcode updates.

7.16.1 tasks

---
# file roles/autoinstall-ubuntu-drivers/tasks/main.yml

- name: autoinstall ubuntu-drivers-common
  block:
    - name: apt | install ubuntu-drivers-common
      apt:
        name: ubuntu-drivers-common
        state: present

    - name: use ubuntu-drivers to install additional drivers automatically
      command: ubuntu-drivers --package-list /etc/yavdr/autoinstalled autoinstall
  when: (ansible_virtualization_type != "virtualbox" and ansible_virtualization_role != "guest")
        # ubuntu-drivers-common tries to autoinstall
        # conflicting packages for virtualbox in Ubuntu 16.04 and 18.04 :(
        # also alternates between virtualbox-guest-dkms and virtualbox-guest-dkms-hwe on successive runs

7.17 autoinstall-virtualbox-guest

This role installs the guest additions for virtualbox guests on Ubuntu 18.04

---
# file roles/autoinstall-virtualbox-guest/tasks/main.yml

- name: collect facts about installed packages
  package_facts:
    manager: "apt"

- name: install virtualbox guest additions
  block:
    - name: install packages
      apt:
        state: present
        name:
          - dkms
          - '{{ "virtualbox-guest-x11-hwe" if "xserver-xorg-hwe-18.04" in ansible_facts.packages else "virtualbox-guest-x11" }}'
          - '{{ "virtualbox-guest-dkms-hwe" if "linux-generic-hwe-18.04" in ansible_facts.packages else "virtualbox-guest-dkms" }}'

    # TODO: set xineliboutput as frontend
  when:
    - ansible_virtualization_type == "virtualbox"
    - ansible_virtualization_role == "guest"
    - ansible_distribution == "Ubuntu"

- name: set xineliboutput as frontend
  block:
    - apt:
        name:
          - vdr-plugin-xineliboutput
          - xineliboutput-sxfe
        state: present
    - command: '{{ item }}'
      with_items:
        - vdrctl disable softhddevice
        - vdrctl disable vaapidevice
        - vdrctl disable xine
        - vdrctl disable pvr350
        - vdrctl enable xineliboutput
      ignore_errors: yes
  when:
    - ansible_virtualization_type in ["virtualbox", "VirtualPC", "VMware"]
    - ansible_virtualization_role == "guest"

7.18 autoinstall-atric-usb

7.18.1 dependencies

---
dependencies:
    - { role: collect-facts }
    - { role: yavdr-remote }

7.18.2 tasks

---
# file roles/autoinstall-atric-usb/tasks/main.yml

- name: install and configure Atric IR-WakeupUSB
  block:
    - name: apt | install lirc-drv-irman
      apt:
        name: lirc-drv-irman
        state: present
      tags:
        - packages

    - name: write lirc configuration
      template:
        src: templates/lirc_options.conf.j2
        dest: /etc/lirc/lirc_options.conf
      tags:
        - config
        - systemd

    - name: enable lircd, eventlircd, lircd2uinput
      systemd:
        name: '{{ item }}'
        enabled: yes
        state: started
        masked: no
        daemon_reload: yes
      with_items:
        - eventlircd.service
        - lircd2uinput.service
        - lircd.service
      tags:
        - systemd

  when: '"04d8:f844" in usb'

7.18.3 templates

{{ ansible_managed | comment }}

[lircd]
nodaemon        = False
driver          = irman
device          = /dev/irman
output          = /var/run/lirc/lircd0
pidfile         = /var/run/lirc/lircd0.pid
plugindir       = /usr/lib/x86_64-linux-gnu/lirc/plugins
permission      = 666
allow-simulate  = No
repeat-max      = 600
#loglevel       = 6
#release        = true
#release_suffix = _EVUP
#logfile        = ...
#driver-options = ...

[lircmd]
uinput          = False
nodaemon        = False

7.19 autoinstall-yausbir

7.19.1 dependencies

---
dependencies:
    - { role: yavdr-remote }

7.19.2 tasks

---
# file roles/autoinstall-atric-usb/tasks/main.yml

- name: install and configure Atric IR-WakeupUSB
  block:
    - name: apt | install lirc-drv-yausbir
      apt:
        name: lirc-drv-yausbir
        state: present
      tags:
        - packages

    - name: write lirc configuration
      template:
        src: templates/lirc_options.conf.j2
        dest: /etc/lirc/lirc_options.conf
      tags:
        - config
        - systemd

    - name: enable lircd, eventlircd, lircd2uinput
      systemd:
        name: '{{ item }}'
        enabled: yes
        state: started
        masked: no
        daemon_reload: yes
      with_items:
        - eventlircd.service
        - lircd2uinput.service
        - lircd.service
      tags:
        - systemd

  when: '"10c4:876c" in usb'

7.19.3 templates

{{ ansible_managed | comment }}

[lircd]
nodaemon        = False
driver          = ya_usbir
output          = /var/run/lirc/lircd0
pidfile         = /var/run/lirc/lircd0.pid
plugindir       = /usr/lib/x86_64-linux-gnu/lirc/plugins
permission      = 666
allow-simulate  = No
repeat-max      = 600
#loglevel       = 6
#release        = true
#release_suffix = _EVUP
#logfile        = ...
#driver-options = ...

[lircmd]
uinput          = False
nodaemon        = False

7.20 autoinstall-satip

If a Sat>IP Server responds to a discovery request, the package vdr-plugin-satip is installed.

7.20.1 tasks

---
# file roles/autoinstall-satip/tasks/main.yml

- name: "detect SAT>IP Server(s) on the network"
  action: satip_facts

- debug:
    var: satip_devices
    verbosity: 1

- name: apt | install vdr-plugin-satip if a Sat>IP server has been detected
  apt:
    name: vdr-plugin-satip
    state: present
  when: satip_devices
  notify: [ 'Restart VDR' ]

7.21 autoinstall-targavfd

7.21.1 tasks

---
# file roles/autoinstall-targavfd/tasks/main.yml

- name: apt | install vdr-plugin-targavfd if connected
  apt:
    name: vdr-plugin-targavfd
  when: '"19c2:6a11" in usb'
  notify: [ 'Restart VDR' ]

7.22 autoinstall-imonlcd

7.22.1 tasks

---
# file roles/autoinstall-imonlcd/tasks/main.yml

- name: apt | install vdr-plugin-imonlcd if connected
  apt:
    name: vdr-plugin-imonlcd
  when: '"15c2:0038" in usb or "15c2:ffdc" in usb'
  notify: [ 'Restart VDR' ]

7.23 autoinstall-libcecdaemon

7.23.1 tasks

---
# file roles/autoinstall-libcec-daemon/tasks/main.yml

- name: apt | install libcec-daemon if connected
  apt:
    name: libcec-daemon
  when: 
    - "2548:1002" in usb

7.24 autoinstall-pvr350

7.24.1 tasks

---
# file roles/autoinstall-pvr350/tasks/main.yml

- name: apt | install vdr-plugin-pvr350 if connected
  apt:
    name: vdr-plugin-pvr350
  when: '"0070:4000" in pci'
  notify: [ 'Restart VDR' ]

7.25 autoinstall-hauppauge-pvr

---
# file roles/autoinstall-hauppauge-pvr/tasks/main.yml

- name: apt | install vdr-plugin-pvrinput if a haupauge pvr card is found
  apt:
    name: vdr-plugin-pvrinput
  when: '"0070:4000" in pci or "4444:0016" in pci'
  notify: [ 'Restart VDR' ]

7.26 autoinstall-firmware

7.26.1 dependencies

---
dependencies:
  - { role: collect-facts }

7.26.2 tasks

---

# This role provides easyily downloadable firmware files

- name: Firmware dvb-demod-si2168-b40-01.fw for Hauppauge WinTV HD Solo/Duo, PCTV 292e
  get_url:
    url: https://github.com/OpenELEC/dvb-firmware/raw/master/firmware/dvb-demod-si2168-b40-01.fw
    checksum: sha256:8507536630d75a316d0719d6b95c04b90c36baa5b457ad457c9bacadafcef134
    dest: /lib/firmware/dvb-demod-si2168-b40-01.fw
  when: '"2040:0264" in usb or "2013:025f" in usb'

- name: Firmware dvb-fe-xc5000-1.6.114.fw for Hauppauge WinTV-HVR-930C
  get_url:
    url: https://github.com/OpenELEC/dvb-firmware/raw/master/firmware/dvb-fe-xc5000-1.6.114.fw
    checksum: sha256:7104bda8df301fe1bd4c09de1708aeb6d0d8e1f9d55505449fecfad82639235f
    dest: /lib/firmware/dvb-fe-xc5000-1.6.114.fw
  when: '"2040:1605" in usb'

- name: Firmware ngenge_18.fw for ngene cards
  get_url:
    url: http://l4m-daten.de/downloads/firmware/dvb-s2/linux/all/ngene_18.fw
    checksum: sha256:213d98ec2cd575eba15d82ee79fed7098e670de43792f8aa773a95cfb7c32060
    dest: /lib/firmware/ngene_18.fw
  when: '"ngene" in modules'
  notify: reboot required

- name: Firmware drxk_a3.mc for drxk
  get_url:
     url: https://github.com/OpenELEC/dvb-firmware/raw/master/firmware/drxk_a3.mc
     checksum: sha256:f8956ad6f92a4ce90a6ab94ed23e2f9a27e9317e936fd3e0119778dd28e7e294
     dest: /lib/firmware/drxk_a3.mc
  when: '"ngene" in modules or "drxk" in modules'
  notify: reboot required

7.27 autoinstall-dvbsky-firmware

7.27.1 dependencies

---
dependencies:
  - { role: collect-facts }

7.27.2 defaults

---
# file roles/autoinstall-dvbsky-firmware
dvbsky_firmware_files: []

7.27.3 tasks

---
# file roles/autoinstall-dvbsky-firmware/tasks/main.yml

- name: determine needed firmware files by device ids
  block:
    - name: add firmware files for DVBSky if a card has been detected
      set_fact:
        dvbsky_firmware_files: ['dvb-demod-m88ds3103.fw', 'dvb-demod-m88rs6000.fw', 'dvb-tuner-si2158-a20-01.fw', 'dvb-demod-si2168-a20-01.fw', 'dvb-demod-si2168-a30-01.fw', 'dvb-demod-si2168-b40-01.fw', 'dvb-fe-ds300x.fw', 'dvb-fe-ds3103.fw', 'dvb-fe-rs6000.fw']
      when:
        - '"1ade:3038" in pci'
  when:
    - not dvbsky_firmware_files

- name: Download firmware for DVBSky cards and copy required files to /lib/firmware
  block:
    # download and extract firmware
    - unarchive:
        src: http://www.dvbsky.net/download/linux/dvbsky-firmware.tar.gz
        dest: /tmp/
        remote_src: yes

    # copy firmware file
    - copy:
        src: "/tmp/dvbsky-firmware/{{ item }}"
        dest: /lib/firmware/
        owner: root
        group: root
        mode: 0644
      with_items: '{{ dvbsky_firmware_files }}'
      notify: ['Trigger Udev']

    # Remove temporary files
    - file:
        path: /tmp/dvbsky-firmware
        state: absent
  when:
    - dvbsky_firmware_files is defined
    - dvbsky_firmware_files
  tags:
    - install
    - autodetect
    - dvbsky

7.28 TODO autoinstall-dvbhddevice

How to get a driver for the TT-6400 FF card? Needed firmware files are in the yavdr-firmware package.

7.28.1 dependencies

---
dependencies:
    - { role: collect-facts }

7.28.2 tasks

---
# file roles/autoinstall-dvbhddevice/tasks/main.yml

- name: apt | install vdr-plugin-dvbhddevice if connected
  apt:
    name:
      - vdr-plugin-dvbhddevice
      - yavdr-firmware
  when: '"13c2:300a" in pci or "13c2:300b" in pci'
  notify: [ 'Restart VDR' ]

7.29 TODO autoinstall-dvbsddevice

7.29.1 dependencies

---
dependencies:
  - { role: collect-facts }

7.29.2 tasks

---
# file roles/autoinstall-dvbsddevice/tasks/main.yml

# TODO: install firmware

- name: apt | install vdr-plugin-dvbsddevice if module is loaded
  apt:
    name:
      - vdr-plugin-dvbsddevice
      - yavdr-firmware
  when: '"dvb_ttpci" in modules'
  notify: [ 'Restart VDR' ]

7.30 autoinstall-hardware-irmp

7.30.1 dependencies

---
dependencies:
  - { role: collect-facts }

7.30.2 tasks

---
# file roles/autoinstall-hardware-irmp/tasks/main.yml

- name: apt | install yavdr-hardware-irmp if connected
  apt:
    name: yavdr-hardware-irmp
    state: present
  when: '"1209:4444" in usb or "16c0:27d9" in usb'

7.31 Serial IR Receivers

This role preconfigures the system for serial „homebrew“ receivers. Newer kernel versions provide serial_ir which acts as a rc-core driver, so we don’t need lircd - eventlircd can use the device directly.

As configured in the role yavdr-remote (/etc/rc_maps.cfg) a keytable for a RC-6 MCE remote is loaded by default. You can choose a different keymap which may also use another ir-protocol.

7.31.1 defaults

--- 
# file: roles/serial-ir/defaults/main.yml

serial_ir_device: ttyS0

7.31.2 tasks

---
# file: roles/serial-ir/tasks/main.yml

- name: apt | install setserial
  apt:
    name: setserial

- name: load serial_ir on startup
  template:
    src: roles/serial-ir/templates/modules-load.d/serial_ir.conf.j2
    dest: /etc/modules-load.d/serial_ir.conf

- name: serial_ir module options
  template:
    src: roles/serial-ir/templates/modprobe.d/serial_ir.conf.j2
    dest: /etc/modprobe.d/serial_ir.conf

- name: setserial options
  template:
    src: roles/serial-ir/templates/serial.conf.j2
    dest: /etc/serial.conf

7.31.3 templates

  1. load serial_ir on startup
    {{ ansible_managed | comment }}
    
    serial_ir
    
  2. serial_ir module options
    {{ ansible_managed | comment }}
    
    {% if serial_ir_device == "ttyS0" %}
    #COM1 equivalent, /dev/ttyS0
    options serial_ir irq=4 io=0x3f8
    {% elif serial_ir_device == "ttyS1" %}
    #COM2 equivalent, /dev/ttyS1
    options serial_ir irq=3 io=0x2f8
    {% endif %}
    {% if serial_ir_device in ("ttyS0", "ttyS1") %}
    install serial_ir setserial /dev/{{serial_ir_device}} uart none; /sbin/modprobe --ignore-install serial_ir
    {% endif %}
    
  3. setserial options
    {{ ansible_managed | comment }}
    
    {% if serial_ir_device == "ttyS0" %}
    #COM1 equivalent, /dev/ttyS0
    /dev/ttyS0 uart none
    {% elif serial_ir_device == "ttyS1" %}
    #COM2 equivalent, /dev/ttyS1
    /dev/ttyS1 uart none
    {% endif %}
    

7.32 kodi

7.32.1 tasks

  1. Install KODI
    • main.yml
      - import_tasks: install-kodi.yml
        tags:
          - install
          - update
          - kodi:install
      - import_tasks: configure-kodi.yml
        tags:
          - install
          - update
          - kodi:configure
      
    • install-kodi.yml
      ---
      
      - name: apt | install kodi packages
        apt:
          name:
            - kodi
            - kodi-pvr-vdr-vnsi
            - python3-lxml
          state: present
          install_recommends: no
      
      - name: apt | install kodi-send (team-xbmc PPA)
        apt:
          name: "kodi-eventclients-xbmc-send" # team-xbmc uses "kodi-eventclients-xbmc-send for some reason
          state: present
          install_recommends: no
        register: team_xbmc_ppa
        ignore_errors: True
      
      - name: apt | install kodi-send (Ubuntu package)
        apt:
          name: "kodi-eventclients-kodi-send" # fallback to ubuntu package
          state: present
          install_recommends: no
        when: team_xbmc_ppa.failed
      
    • configure-kodi.yml
      - name: create kodi.service for the user session
        template:
            src: 'templates/kodi.service.j2'
            dest: '{{ vdr.home }}/.config/systemd/user/kodi.service'
            mode: 0644
            owner: '{{ vdr.user }}'
            group: '{{ vdr.group }}'
      
      - name: create kodi user directory
        file:
            dest: '{{ vdr.home }}/{{ item }}'
            state: directory
            owner: '{{ vdr.user }}'
            group: '{{ vdr.group }}'
            mode: "0775"
        with_items:
            - .kodi
            - .kodi/userdata
            - .kodi/userdata/keymaps
      
      - name: copy Lircmap.xml if it does not exist yet
        copy:
            dest: '{{ vdr.home }}/.kodi/userdata/Lircmap.xml'
            src: 'files/userdata/Lircmap.xml'
            owner: '{{ vdr.user }}'
            group: '{{ vdr.group }}'    
            mode: "0664"
            force: no
      
      - name: copy remote.xml if it does not exist yet
        copy:
            dest: '{{ vdr.home }}/.kodi/userdata/keymaps/remote.xml'
            src: 'files/userdata/keymaps/remote.xml'
            owner: '{{ vdr.user }}'
            group: '{{ vdr.group }}'    
            mode: "0664"
            force: no
      
      - name: expand template for set-kodi-display
        template:
          src: 'templates/set-kodi-display.j2'
          dest: '/usr/bin/set-kodi-display'
          mode: "0755"
      

7.32.2 templates

  1. kodi.service

    This systemd unit for the user session starts (and stops) kodi.

    [Unit]
    Description=Start kodi in user session
    
    [Service]
    Type=simple
    PassEnvironment=DISPLAY XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS
    ExecStartPre=-/usr/bin/set-kodi-display
    ExecStart=/usr/bin/kodi -l /run/lirc/lircd
    ExecStop=/bin/bash -c "/usr/bin/kodi-send --action=QUIT; while ps -p $MAINPID -o comm=; do sleep .25; done"
    TimeoutStopSec=10
    SuccessExitStatus=0 127 SIGKILL
    Restart=on-failure
    
  2. 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.

    #!/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!).
    
    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
    from lxml import etree as ET
    
    
    GUISETTINGS = '/var/lib/vdr/.kodi/userdata/guisettings.xml'
    CACHE_DIR = '/var/lib/vdr/.kodi/.display_cache'
    VIDEOSCREEN_TEMPLATE = """<settings version="2">
        <setting id="videoscreen.monitor">{}</setting>
    </settings>"""
    
    
    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
        """
        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 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:
            xml_tree.write(template_path)
        return xml_tree
    
    
    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('<settings version="2"></settings>')
        xml_tree = ET.ElementTree(base_tree)
        backup_root = xml_tree.getroot()
    
        # 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(f"written backup for {old_output} to {xml_path}", file=sys.stderr)
    
        # 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()
        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)
    

7.32.3 files

Those configuration files provide a preconfiguration for kodi which overrides the system wide configuration

  1. Lircmap.xml

    This file allows to map keys sent by remotes via eventlircd (which uses the name devinput) to kodi key names.

    <?xml version="1.0" encoding="UTF-8"?>
    <lircmap>
            <remote device="linux-input-layer">
            <altname>cx23885_remote</altname>
            <altname>devinput</altname>
                    <up>KEY_UP</up>
                    <down>KEY_DOWN</down>
                    <menu>KEY_MENU</menu>
                    <select>KEY_OK</select>
                    <back>KEY_ESC</back>
                    <left>KEY_LEFT</left>
                    <right>KEY_RIGHT</right>
                    <red>KEY_RED</red>
                    <green>KEY_GREEN</green>
                    <yellow>KEY_YELLOW</yellow>
                    <blue>KEY_BLUE</blue>
                    <zero>KEY_0</zero>
                    <one>KEY_1</one>
                    <two>KEY_2</two>
                    <three>KEY_3</three>
                    <four>KEY_4</four>
                    <five>KEY_5</five>
                    <six>KEY_6</six>
                    <seven>KEY_7</seven>
                    <eight>KEY_8</eight>
                    <nine>KEY_9</nine>
                    <info>KEY_INFO</info>
                    <play>KEY_PLAY</play>
                    <play>KEY_PLAYPAUSE</play>
                    <pause>KEY_PAUSE</pause>
                    <stop>KEY_STOP</stop>
                    <record>KEY_RECORD</record>
                    <forward>KEY_FASTFORWARD</forward>
                    <reverse>KEY_REWIND</reverse>
                    <skipplus>KEY_NEXT</skipplus>
                    <skipminus>KEY_BACK</skipminus>
                    <power>KEY_POWER2</power>
                    <channelplus>KEY_CHANNELUP</channelplus>
                    <channelminus>KEY_CHANNELDOWN</channelminus>
                    <title>KEY_PROG3</title>
                    <volumeplus>KEY_VOLUMEUP</volumeplus>
                    <volumeminus>KEY_VOLUMEDOWN</volumeminus>
                    <teletext>KEY_TEXT</teletext>
                    <mute>KEY_MUTE</mute>
                    <start>KEY_SELECT</start>
                    <subtitle>KEY_SUBTITLE</subtitle>
                    <audio>KEY_MODE</audio>
                    <mute>KEY_MUTE</mute>
                    <myvideo>KEY_VIDEO</myvideo>
                    <mymusic>KEY_AUDIO</mymusic>
                    <mypictures>KEY_IMAGES</mypictures>
                    <recordedtv>KEY_PVR</recordedtv>
                    <guide>KEY_EPG</guide>
                    <display>KEY_SCREEN</display>
                    <enter>KEY_PROG4</enter>
                    <playlist>KEY_ZOOM</playlist>
                    <livetv>KEY_CHANNEL</livetv>
                    <mytv>KEY_TV</mytv>
                    <liveradio>KEY_FN</liveradio>
            </remote>
    </lircmap>
    
  2. keymaps

    This file maps the keys defined in Lircmap.xml to actions within kodi.

    1. remote.xml
      <?xml version="1.0" encoding="UTF-8"?>
      <!-- This file contains the mapping of keys (gamepad, remote, and keyboard) to actions within XBMC -->
      <!-- The <global> section is a fall through - they will only be used if the button is not          -->
      <!-- used in the current window's section.  Note that there is only handling                       -->
      <!-- for a single action per button at this stage.                                                 -->
      <!-- For joystick/gamepad configuration under linux/win32, see below as it differs from xbox       -->
      <!-- gamepads.                                                                                     -->
      
      <!-- The format is:                      -->
      <!--    <device>                         -->
      <!--      <button>action</button>        -->
      <!--    </device>                        -->
      
      <!-- To map keys from other remotes using the RCA protocol, you may add <universalremote> blocks -->
      <!-- In this case, the tags used are <obc#> where # is the original button code (OBC) of the key -->
      <!-- You set it up by adding a <universalremote> block to the window or <global> section:       -->
      <!--    <universalremote>             -->
      <!--       <obc45>Stop</obc45>         -->
      <!--    </universalremote>            -->
      <!-- To find out the OBC's of your remote, try enabling the <displayremotecodes> tag in AdvancedSettings.xml -->
      
      <!-- Note that the action can be a built-in function.                 -->
      <!--  eg <B>XBMC.ActivateWindow(MyMusic)</B>                         -->
      <!-- would automatically go to My Music on the press of the B button. -->
      
      <!-- Joysticks / Gamepads:                                                                    -->
      <!--   See the sample PS3 controller configuration below for the format.                      -->
      <!--                                                                                          -->
      <!--  Joystick Name:                                                                          -->
      <!--   Do 'cat /proc/bus/input/devices' or see your xbmc log file  to find the names of       -->
      <!--   detected joysticks. The name used in the configuration should match the detected name. -->
      <!--                                                                                          -->
      <!--  Button Ids:                                                                             -->
      <!--   'id' is the button ID used by SDL. Joystick button ids of connected joysticks appear   -->
      <!--   in xbmc.log when they are pressed. Use your log to map custom buttons to actions.      -->
      <!--                                                                                          -->
      <!--  Axis Ids / Analog Controls                                                              -->
      <!--   Coming soon.                                                                           -->
      <keymap>
        <global>
          <universalremote>
             <obc150>XBMC.ejecttray()</obc150>
             <obc151>Playlist</obc151>
          </universalremote>
          <remote>
            <play>PlayPause</play>
            <pause>Pause</pause>
            <stop>Stop</stop>
            <forward>FastForward</forward>
            <reverse>Rewind</reverse>
            <left>Left</left>
            <right>Right</right>
            <up>Up</up>
            <down>Down</down>
            <select>Select</select>
            <pageplus>PageUp</pageplus>
            <pageminus>PageDown</pageminus>
            <back>ParentDir</back>
            <menu>PreviousMenu</menu>
            <title>ContextMenu</title>
            <info>Info</info>
            <skipplus>SkipNext</skipplus>
            <skipminus>SkipPrevious</skipminus>
            <display>FullScreen</display>
            <start>PreviousMenu</start>
            <record>Screenshot</record>
            <volumeplus>VolumeUp</volumeplus>
            <volumeminus>VolumeDown</volumeminus>
            <mute>Mute</mute>
            <power>XBMC.Quit()</power>
            <myvideo>XBMC.ActivateWindow(MyVideos)</myvideo>
            <mymusic>XBMC.ActivateWindow(MyMusic)</mymusic>
            <mypictures>XBMC.ActivateWindow(MyPictures)</mypictures>
            <!--- <mytv>XBMC.ActivateWindow(Home)</mytv> -->
            <recordedtv>XBMC.ActivateWindowAndFocus(MyPVR, 34,0, 13,0)</recordedtv>
            <!-- PVR: EPG -->
            <guide>XBMC.ActivateWindowAndFocus(MyPVR, 31,0, 10,0)</guide>
            <!-- PVR: Channellist -->
            <livetv>XBMC.ActivateWindowAndFocus(MyPVR, 32,0, 11,0)</livetv>
            <!-- PVR: Radio -->
            <liveradio>XBMC.ActivateWindowAndFocus(MyPVR, 33,0, 12,0)</liveradio>
            <red>XBMC.ActivateWindow(Home)</red>
            <yellow>XBMC.ActivateWindow(MyVideos)</yellow>
            <green>XBMC.ActivateWindow(MyMusic)</green>
            <!-- <blue>XBMC.ActivateWindow(MyPictures)</blue> -->
            <zero>Number0</zero>
            <one>Number1</one>
            <two>JumpSMS2</two>
            <three>JumpSMS3</three>
            <four>JumpSMS4</four>
            <five>JumpSMS5</five>
            <six>JumpSMS6</six>
            <seven>JumpSMS7</seven>
            <eight>JumpSMS8</eight>
            <nine>JumpSMS9</nine>
            <audio>AudioNextLanguage</audio>
            <subtitle>ShowSubtitles</subtitle>
            <enter>FullScreen</enter>
            <blue>ContextMenu</blue>
            <playlist>XBMC.ActivateWindow(MyMusicPlaylist)</playlist>
          </remote>
        </global>
        <Home>
          <remote>
            <menu>XBMC.Skin.ToggleSetting(HomeViewToggle)</menu>
            <info>XBMC.ActivateWindow(SystemInfo)</info>
            <clear>XBMC.ActivateWindow(Weather)</clear>
            <blue>XBMC.ActivateWindow(Settings)</blue>
            <red>FullScreen</red>
            <back>FullScreen</back>
          </remote>
        </Home>
        <MyFiles>
          <remote>
            <clear>Delete</clear>
          </remote>
        </MyFiles>
        <MyMusicPlaylist>
          <remote>
            <back>Playlist</back>      <!-- Close playlist -->
            <clear>Delete</clear>
          </remote>
        </MyMusicPlaylist>
        <MyMusicPlaylistEditor>
          <remote>
            <zero>Queue</zero>
          </remote>
        </MyMusicPlaylistEditor>
        <MyMusicFiles>
          <remote>
            <zero>Queue</zero>
            <star>Queue</star>
            <start>ContextMenu</start>
          </remote>
        </MyMusicFiles>
        <MyMusicLibrary>
          <remote>
            <zero>Queue</zero>
            <star>Queue</star>
          </remote>
        </MyMusicLibrary>
        <FullscreenVideo>
          <remote>
            <back>FullScreen</back>
            <zero>Number0</zero>
            <one>Number1</one>
            <two>Number2</two>
            <three>Number3</three>
            <four>Number4</four>
            <five>Number5</five>
            <six>Number6</six>
            <seven>Number7</seven>
            <eight>Number8</eight>
            <nine>Number9</nine>
            <left>Rewind</left>
            <right>FastForward</right>
            <up>Play</up>
            <down>Pause</down>
            <yellow>StepForward</yellow>
            <green>StepBack</green>
            <blue>CodecInfo</blue>
            <menu>OSD</menu>
            <start>OSD</start>
            <info>XBMC.ActivateWindow(PVROSDChannels)</info>
            <title>XBMC.ActivateWindow(PVROSDChannels)</title>
            <display>AspectRatio</display>
            <select>Info</select>
            <teletext>XBMC.ActivateWindow(Teletext)</teletext>
            <channelplus>SkipNext</channelplus>
            <channelminus>SkipPrevious</channelminus>
            <record>XBMC.PlayerControl(Record)</record>
          </remote>
        </FullscreenVideo>
        <FullscreenInfo>
          <remote>
            <back>FullScreen</back>
            <left>Rewind</left>
            <right>FastForward</right>
            <up>Play</up>
            <down>Pause</down>
            <yellow>StepForward</yellow>
            <green>StepBack</green>
            <display>AspectRatio</display>
            <info>CodecInfo</info>
            <select>Close</select>
            <menu>OSD</menu>
          </remote>
        </FullscreenInfo>
        <PlayerControls>
          <remote>
            <back>Close</back>
            <menu>Close</menu>
          </remote>
        </PlayerControls>
        <Visualisation>
          <universalremote>
             <obc151>XBMC.ActivateWindow(MyMusicPlaylist)</obc151>
          </universalremote>
          <remote>
            <red>FullScreen</red>
            <yellow>AnalogSeekForward</yellow>
            <green>AnalogSeekBack</green>
            <blue>CodecInfo</blue>
            <left>Rewind</left>
            <right>FastForward</right>
            <pageplus>SkipNext</pageplus>
            <pageminus>SkipPrevious</pageminus>
            <up>Play</up>
            <down>Pause</down>
            <info>XBMC.ActivateWindow(PVROSDChannels)</info>
            <back>XBMC.ActivateWindow(MyMusicPlaylist)</back>
            <select>Info</select>
            <menu>XBMC.ActivateWindow(MusicOSD)</menu>
            <start>XBMC.ActivateWindow(MusicOSD)</start>
          </remote>
        </Visualisation>
        <MusicOSD>
          <remote>
            <back>Close</back>
            <menu>Close</menu>
            <start>Close</start>
            <info>CodecInfo</info>
          </remote>
        </MusicOSD>
        <VisualisationSettings>
          <remote>
            <back>Close</back>
            <menu>Close</menu>
          </remote>
        </VisualisationSettings>
        <VisualisationPresetList>
          <remote>
            <back>Close</back>
            <menu>Close</menu>
          </remote>
        </VisualisationPresetList>
        <SlideShow>
          <remote>
            <zero>ZoomNormal</zero>
            <one>ZoomLevel1</one>
            <two>ZoomLevel2</two>
            <three>ZoomLevel3</three>
            <four>ZoomLevel4</four>
            <five>ZoomLevel5</five>
            <six>ZoomLevel6</six>
            <seven>ZoomLevel7</seven>
            <eight>ZoomLevel8</eight>
            <nine>ZoomLevel9</nine>
            <info>CodecInfo</info>
            <pageplus>ZoomIn</pageplus>
            <pageminus>ZoomOut</pageminus>
            <select>Rotate</select>
            <up>Pause</up>
            <down>Play</down>
            <back>PreviousMenu</back>
          </remote>
        </SlideShow>
        <ScreenCalibration>
          <remote>
            <select>NextCalibration</select>
            <enter>NextCalibration</enter>
            <zero>ResetCalibration</zero>
            <display>NextResolution</display>
            <xbox>NextResolution</xbox>
          </remote>
        </ScreenCalibration>
        <GUICalibration>
          <remote>
            <select>NextCalibration</select>
            <enter>NextCalibration</enter>
            <zero>ResetCalibration</zero>
          </remote>
        </GUICalibration>
        <SelectDialog>
          <remote>
            <back>Close</back>
          </remote>
        </SelectDialog>
        <VideoOSD>
          <remote>
            <back>PreviousMenu</back>
            <menu>Close</menu>
            <start>Close</start>
          </remote>
        </VideoOSD>
        <VideoMenu>
          <remote>
            <menu>OSD</menu>
            <back>PreviousMenu</back>
            <info>Info</info>
            <title>CodecInfo</title>
            <zero>Number0</zero>
            <one>Number1</one>
            <two>Number2</two>
            <three>Number3</three>
            <four>Number4</four>
            <five>Number5</five>
            <six>Number6</six>
            <seven>Number7</seven>
            <eight>Number8</eight>
            <nine>Number9</nine>
          </remote>
        </VideoMenu>
        <OSDVideoSettings>
          <remote>
            <back>Close</back>
            <menu>Close</menu>
            <start>Close</start>
          </remote>
        </OSDVideoSettings>
        <OSDAudioSettings>
          <remote>
            <back>Close</back>
            <menu>Close</menu>
            <start>Close</start>
          </remote>
        </OSDAudioSettings>
        <VideoBookmarks>
          <remote>
            <back>Close</back>
            <menu>Close</menu>
            <start>Close</start>
            <zero>Delete</zero>
          </remote>
        </VideoBookmarks>
        <MyVideoLibrary>
          <remote>
            <zero>Queue</zero>
            <clear>Delete</clear>
            <green>ToggleWatched</green>
            <yellow>Queue</yellow>
          </remote>
        </MyVideoLibrary>
        <MyVideoFiles>
          <remote>
            <zero>Queue</zero>
            <star>Queue</star>
            <blue>ContextMenu</blue>
            <green>ToggleWatched</green>
            <yellow>Queue</yellow>
          </remote>
        </MyVideoFiles>
        <MyPictures>
          <remote>
            <zero>Queue</zero>
            <star>Queue</star>
            <start>ContextMenu</start>
          </remote>
        </MyPictures>
        <MyVideoPlaylist>
          <remote>
            <back>Playlist</back>      <!-- Close playlist -->
            <clear>Delete</clear>
            <zero>Delete</zero>
          </remote>
        </MyVideoPlaylist>
        <VirtualKeyboard>
          <remote>
            <back>BackSpace</back>
            <star>Shift</star>
            <hash>Symbols</hash>
            <zero>Number0</zero>
            <one>Number1</one>
            <two>Number2</two>
            <three>Number3</three>
            <four>Number4</four>
            <five>Number5</five>
            <six>Number6</six>
            <seven>Number7</seven>
            <eight>Number8</eight>
            <nine>Number9</nine>
            <pageminus>CursorLeft</pageminus>
            <pageplus>CursorRight</pageplus>
          </remote>
        </VirtualKeyboard>
        <ContextMenu>
          <remote>
            <blue>Close</blue>
            <back>Close</back>
          </remote>
        </ContextMenu>
        <FileStackingDialog>
          <remote>
            <back>Close</back>
          </remote>
        </FileStackingDialog>
        <Scripts>
          <remote>
            <info>XBMC.ActivateWindow(ScriptsDebugInfo)</info>
          </remote>
        </Scripts>
        <ScriptsDebugInfo>
          <remote>
            <info>Info</info>      <!-- clears debug python info -->
          </remote>
        </ScriptsDebugInfo>
        <NumericInput>
          <remote>
            <zero>Number0</zero>
            <one>Number1</one>
            <two>Number2</two>
            <three>Number3</three>
            <four>Number4</four>
            <five>Number5</five>
            <six>Number6</six>
            <seven>Number7</seven>
            <eight>Number8</eight>
            <nine>Number9</nine>
            <back>BackSpace</back>
          </remote>
        </NumericInput>
        <Weather>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </Weather>
        <Settings>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </Settings>
        <MyPicturesSettings>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </MyPicturesSettings>
        <MyProgramsSettings>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </MyProgramsSettings>
        <MyWeatherSettings>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </MyWeatherSettings>
        <MyMusicSettings>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </MyMusicSettings>
        <SystemSettings>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </SystemSettings>
        <MyVideosSettings>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </MyVideosSettings>
        <NetworkSettings>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </NetworkSettings>
        <AppearanceSettings>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </AppearanceSettings>
        <Profiles>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </Profiles>
        <systeminfo>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </systeminfo>
        <shutdownmenu>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </shutdownmenu>
        <submenu>
          <remote>
            <back>PreviousMenu</back>
          </remote>
        </submenu>
        <MusicInformation>
          <remote>
            <back>Close</back>
          </remote>
        </MusicInformation>
        <MovieInformation>
          <remote>
            <back>Close</back>
          </remote>
        </MovieInformation>
        <LockSettings>
          <remote>
            <menu>Close</menu>
            <back>PreviousMenu</back>
          </remote>
        </LockSettings>
        <ProfileSettings>
          <remote>
            <menu>Close</menu>
            <back>PreviousMenu</back>
          </remote>
        </ProfileSettings>
        <PictureInfo>
          <remote>
            <skipplus>NextPicture</skipplus>
            <skipminus>PreviousPicture</skipminus>
            <info>Close</info>
            <back>Close</back>
          </remote>
        </PictureInfo>
        <Teletext>
          <remote>
            <zero>number0</zero>
            <one>number1</one>
            <two>number2</two>
            <three>number3</three>
            <four>number4</four>
            <five>number5</five>
            <six>number6</six>
            <seven>number7</seven>
            <eight>number8</eight>
            <nine>number9</nine>
            <red>Red</red>
            <green>Green</green>
            <yellow>Yellow</yellow>
            <blue>Blue</blue>
            <info>Info</info>
            <back>Close</back>
            <menu>Close</menu>
            <start>Close</start>
            <teletext>Close</teletext>
          </remote>
        </Teletext>
        <Favourites>
          <remote>
            <back>Close</back>
          </remote>
        </Favourites>
      </keymap>
      

7.33 dvd

7.33.1 tasks

  • install libdvd-pkg, allow programs to eject optical media
    ---
    # file: roles/dvd/tasks/main.yml
    
    #- name: preconfigure libdvd-pkg
    #  shell: |
    #    echo 'libdvd-pkg libdvd-pkg/post-invoke_hook-install boolean true' | debconf-set-selections
    #    echo 'libdvd-pkg libdvd-pkg/build boolean true' | debconf-set-selections
    
    - name: apt | install libdvd-pkg
      apt:
        name: libdvd-pkg
        state: present
        install_recommends: no
    
    - name: reconfigure libdvd-pkg
      dpkg_reconfigure:
        pkg: libdvd-pkg
        answers:
         libdvd-pkg/post-invoke_hook-install: boolean true
         libdvd-pkg/build: boolean true
    
    
    - name: change udev rule to allow KODI to eject optical disks
      shell: sed 's/--lock-media //' /lib/udev/rules.d/60-cdrom_id.rules > /etc/udev/rules.d/60-cdrom_id.rules
      args:
        creates: /etc/udev/rules.d/60-cdrom_id.rules
        warn: false
    

7.34 vdr-epg-daemon

This role installs the vdr-epg-daemon

7.34.1 defaults

---
# file: roles/epgd/defaults/main.yml

epgd_dbhost: localhost
epgd_dbport: 3306
epgd_dbname: epg2vdr
epgd_dbuser: epg2vdr
epgd_dbpass: epg
epgd_epgview: eventsview.sql
epgd_epgviewweb: eventsviewplain.sql
epgd_thetvdbview: thetvdbview.sql
epgd_checkinitial: 1
epgd_daysinadvance: 8
epgd_daystoupdate: 4
epgd_updatetime: 12
epgd_updatethreshold: 500
epgd_xmlstoretofs: 0
epgd_getepgimages: 1
# EpgImageSize
# 0 = 174x130
# 1 = 329x245
# 2 = 525x400
epgd_epgimagesize: 2
epgd_maximagesperevent: 1
epgd_seriesenabled: 1
epgd_seriesport: 2006
epgd_seriesstoretofs: 0
epgd_seriesurl: www.eplists.de
epgd_scrapepg: 1
epgd_scrapRecordings: 1
#epgd_scrapmoviedbapikey: ""
epgd_cachepath: /var/cache/epgd/
epgd_epghttpd_port: 9999
#epgd_httpproxy: 127.0.0.1:8000
epgd_httpproxy_username: ""
epgd_httpproxy_password: ""
epgd_LogLevel: 1
epgd_epgdata_url: http://www.epgdata.com
epgd_epgdata_pin: insert-your-pin-here
epgd_epgdata_timeout: 180

7.34.2 tasks

---
# file: roles/epgd/tasks/main.yml

#Installing mariadb, epg-daemon and epg2vdr.
#bind-address is set to 0.0.0.0 --> 99-epgd.cnf.
#The root SQL password is only stored (NONE), because the epgd-tool
#is expecting it. But it is not really used,
#because the connection is established via a socket without a password.
#Normal users must use sudo if they want to access mysql as root.
#TODO:
    # - Create channelmap if channels.conf is present.
    #   (the script already exists at mld.)
    # - because skindesigner-logos delivers only svg-logos, logoSuffix svg should be the preset --> vdr-epg-daemon / lib / parameters.c
    # - customize epgd-tool for mariadb


- name: MariaDB and Plugin | install
  apt:
    name: ['mariadb-server', 'mariadb-plugin-epglv', 'python-pymysql']
    state: present
    
- name: copy file | epgd mariadb config
  template:
    src: templates/etc_mysql_mariadb.conf.d_99-epgd.cnf.j2
    dest: /etc/mysql/mariadb.conf.d/99-epgd.cnf
    owner: root
    group: root

- name: create /root/.ssh if it doesn't exist yes
  file:
    path: /root/.ssh
    state: directory
    owner: root
    group: root
    mode: 0700
    
- name: copy file | mysqlpasswd
  template:
    src: templates/root_.ssh_mysqlpasswd.j2
    dest: /root/.ssh/mysqlpasswd
    owner: root
    group: root
    mode: 0600
    backup: yes

- name: Restart mariadb
  systemd:
    name: mariadb.service
    state: restarted
    enabled: yes
    masked: no
    
- name: Create a new database for epgd
  mysql_db:
    name: "{{ epgd_dbname }}"
    encoding: utf8
    state: present
    login_unix_socket: /var/run/mysqld/mysqld.sock
    
- name: Create User for epgd database  
  mysql_user:
    name: "{{ epgd_dbuser }}"
    password: "{{ epgd_dbpass }}"
    host: '%'
    priv: '{{ epgd_dbname }}.*:ALL'
    state: present
    login_unix_socket: /var/run/mysqld/mysqld.sock

- name: Create User for epgd database  
  mysql_user:
    name: "{{ epgd_dbuser }}"
    password: "{{ epgd_dbpass }}"
    host: 'localhost'
    priv: '{{ epgd_dbname }}.*:ALL'
    state: present
    login_unix_socket: /var/run/mysqld/mysqld.sock

- name: create epgd config folder
  file:
   path: /etc/epgd
   state: directory

- name: epgd config
  template:
    src: templates/etc_epgd_epgd.conf.j2
    dest: /etc/epgd/epgd.conf

- name: epgd | install
  apt:
    name: ['vdr-epg-daemon', 'vdr-plugin-epg2vdr', 'skindesigner-logos']
    state: present

#- name: epgd | start
  #systemd:
    #name: epgd_service
    #state: started
    #enabled: yes
    #masked: no

7.34.3 templates

  1. Template for /etc/mysql/mariadb.conf.d/99-epgd.cnf

    to allow Database access from all hosts on network file: roles/epgd/templates/etc_mysql_mariadb.conf.d_99-epgd.cnf.j2

    {{ ansible_managed | comment }}
    [mysqld]
    bind-address            = 0.0.0.0
    
  2. template for /root/.ssh/mysqlpasswd so epgd-tool is happy

    file: roles/epgd/templates/root_.ssh_mysqlpasswd.j2

    {{ ansible_managed | comment }}
    PASSWORD=NONE 
    
  3. template for /etc/epgd/epgd.conf

    file: roles/epgd/templates/etc_epgd_epgd.conf.j2

    {{ ansible_managed | comment }}
    # ------------------------------------
    # Configuration of EPG Daemon
    # ------------------------------------
    
    DbHost = {{ epgd_dbhost }}
    DbPort = {{ epgd_dbport }}
    DbName = {{ epgd_dbname }}
    DbUser = {{ epgd_dbuser }}
    DbPass = {{ epgd_dbpass }}
    
    EpgView = {{ epgd_epgview }}
    EpgViewWeb = {{ epgd_epgviewweb }}
    TheTvDBView = {{ epgd_thetvdbview }}
    
    CheckInitial = {{ epgd_checkinitial }}
    DaysInAdvance = {{ epgd_daysinadvance }}
    DaysToUpdate = {{ epgd_daystoupdate }}
    UpdateTime = {{ epgd_updatetime }}
    UpdateThreshold = {{ epgd_updatethreshold }}
    
    XmlStoreToFs = {{ epgd_xmlstoretofs }}
    
    GetEPGImages = {{ epgd_getepgimages }}
    # EpgImageSize
    # 0 = 174x130
    # 1 = 329x245
    # 2 = 525x400
    EpgImageSize = {{ epgd_epgimagesize }}
    MaxImagesPerEvent = {{ epgd_maximagesperevent }}
    
    SeriesEnabled = {{ epgd_seriesenabled }}
    SeriesPort = {{ epgd_seriesport }}
    SeriesStoreToFs = {{ epgd_seriesstoretofs }}
    SeriesUrl = {{ epgd_seriesurl }}
    
    ScrapEpg = {{ epgd_scrapepg }}
    ScrapRecordings = {{ epgd_scrapRecordings }}
    {% if epgd_scrapmoviedbapikey is defined %}
    ScapMovieDbApiKey = {{ epgd_scrapmoviedbapikey }}
    {% else %}
    #ScrapMovieDbApiKey =
    {% endif %}
    
    {% if epgd_netdevice is defined %}
    NetDevice = {{ epgd_netdevice }}
    {% else %}
    # NetDevice = eth0
    {% endif %}
    
    CachePath = {{ epgd_cachepath }}
    HttpPort = {{ epgd_epghttpd_port }}
    
    {% if epgd_httpproxy is defined %}
    HttpProxy = {{ epgd_httpproxy }}
    UserName = {{ epgd_httpproxy_username }}
    Password = {{ epgd_httpproxy_password }}
    {% else %}
    # HttpProxy = 127.0.0.1:8000
    # UserName =
    # Password =
    {% endif %}
    
    LogLevel = {{ epgd_LogLevel }}
    
    # ---------------
    # epgdata plugin
    # ---------------
    
    epgdata.url = {{ epgd_epgdata_url }}
    epgdata.pin = {{ epgd_epgdata_pin }} 
    
    # Download timeout in seconds (default 180)
    epgdata.timeout = {{ epgd_epgdata_timeout }}
    

7.35 install-sundtek

7.35.1 dependencies

---
dependencies:
  - { role: collect-facts }

7.35.2 defaults

--- 
# file: roles/install-sundtek/defaults/main.yml

sundtek:
  install: auto
  wait_for_devices: true
  wait_for_network: false

7.35.3 tasks

---
# file roles/install-sundtek/tasks/main.yml

# TODO: include LD_PRELOAD and other dependencies in yavdr-hardware-sundtek package
#- name: apt | install yavdr-hardware-sundtek
#  apt:
#    name: yavdr-hardware-sundtek

- name: expand template for custom sundtek.service
  template:
    src: templates/systemd/sundtek.service.j2
    dest: /etc/systemd/systemd/sundtek.service
  when: sundtek.install

- name: apt | install sundtek dvb driver
  apt:
    name: dvb-driver-sundtek
    state: present
  when: sundtek.install
# TODO: state should depend on sundtek.install:
# '{{ "present" if sundtek.install else "absent" }}'

- name: apt | install vdr-plugins for dynamic sundtek configuration else uninstall
  apt:
    name:
      - vdr-plugin-dynamite
      - vdr-plugin-sundtek
    state: '{{ "absent" if sundtek.wait_for_devices else "present" }}'
  when: sundtek.install


- name: systemd | start sundtek.service
  systemd:
    name: sundtek.service
    state: started
    enabled: yes
    masked: no
    daemon-reload: yes
  when: sundtek.install

# TODO: Scan for local and remote sundtek sticks and create
#       /etc/sundtek.conf - needs yavdr-hardware-sundtek
#- name: detect local and remote sundtek devices
#  command: scansundtek
#  register: sundtek_scan
# 
# - name: set variable sundtek_scan
#   set_fact:
#     sundtek_devices: "{{ sundtek_scan.stdout | from_json }}"
#
#- name: create /etc/sundtek.conf
#  template:
#    src: templates/sundtek.conf.j2
#    dest: /etc/sundtek.conf
#  notify: Restart sundtek.service

- name: set LD_PRELOAD for sundtek driver to VDR's environment
  template:
    src: templates/systemd/vdr.service.d/sundtek.conf.j2
    dest: /etc/systemd/system/vdr.service.d/sundtek_LD_PRELOAD.conf
  notify: ['Restart VDR']

# TODO: enable sundtek-avahi-mounter (needs to be ported to python3 and 
#       work indenpendently of yavdrhw.db files)
#- name: systemd | start sundtek-avahi-mounter.service
#  systemd:
#    name: sundtek-avahi-mounter.service
#    state: started
#    masked: no
#    enabled: yes
#    daemon-reload: yes

7.35.4 templates

  1. LD_PRELOAD for vdr.service
    {{ ansible_managed | comment }}
    
    {% if sundtek.install %}
    [Service]
    Environment=LD_PRELOAD=/opt/lib/libmediaclient.so
    {% endif %}
    
  2. /etc/systemd/system/sundtek.service
    {{ ansible_managed | comment }}
    
    [Unit]
    Description=Sundtek mediasrv
    {% if sundtek.wait_for_network %}
    After=network-online.target
    {% endif %}
    Before=vdr.service
    [Service]
    Type=forking
    ExecStart=/opt/bin/mediasrv -d --pluginpath=/opt/bin {% if sundtek.wait_for_devices %}--wait-for-devices{% endif%}
    ExecStop=/opt/bin/mediaclient --shutdown
    Restart=on-failure
    [Install]
    WantedBy=multi-user.target
    
  3. /etc/sundtek.conf
    {{ ansible_managed | comment }}
    # ----- GLOBAL SECTION -----
    #Set loglevel for logging to /var/log/mediasrv.log
    #autoupdate=[on|off]                    #enable automatic driver update, default off
    autoupdate=off
    #loglevel=[off|min|full]                #default: off
                                            #max .. little bit more
    loglevel=off
    #pluginpath=/path/to/drivers            #set path to drivers
    #dvb_api_version=x.y                    #default API version will be parsed from
                                            #/usr/include/linux/dvb/version.h, if no such file is
                                            #available the latest internally supported API ::version will be 
                                            #used (which will be quite new)
    #audio_skip_bytes=N                     #when changing analogTV channels, 
                                            #skip N bytes to suppress audio noise
    #bulk_notification[on|off]              #default off, bulk_notification will only trigger device_attach once
                                            #after the the first device scan when the driver is started up
                                            #after this first scan, the notification will be triggered
                                            #normally again for each device.
                                            #
                                            #for example when 2 devices are attached to the system only one
                                            #notification will be triggered initially, any at a later time
                                            #attached device will generate another device_attach notification
                                            #by default each device will call the script which is set up with
                                            #device_attach
    #use_tvvideo=[on|off]                   #use /dev/tvvideoN instead of /dev/video
                                            #this works around a new Adobe Flash bug in 2012, where flash crashes
                                            #if files (/dev/videoN) other than /dev/video0 are available
     
    #use_hwpidfilter=[on|off]               #For DVB-C, DVB-T, DVB-S/S2
                                            #enable PID filter, please be careful with that, there are only 15 HW Pid filters available
                                            #when more than 15 pids are set up the pid filter will be disabled automatically
    
    #dmhwpidfilter=[on|off]                 #default on, in some cases off might be useful to disable hw pid filter for settopboxes
                                            #if you get a black image when capturing a TV channel and watching another channel on the
                                            #same transponder - try this option and reboot
    
    #usb_transaction_delay=[0-20]           #throttle the USB setup transactions, this can solve problems with weak USB stacks for
                                            #example for embedded boards, unit is milliseconds
    
    #usb_timeout=[0 - N]                    #USB timeout for each transaction in milliseconds, 0 default infinite
    #voltage_tone_off=[1 or 0]              #0 .. default, normal behaviour
                                            #1 .. force DVB-S/S2 to not use any Voltage or Tone (ignore any user parameters)
                                            
    #ir_disabled=[1 or 0]                   #0 .. enable remote control polling, the driver will permanently check for remote control keys
                                            #1 .. disable remote control polling, might fix bugs with weak USB stacks on embedded boards
    
    #
    #Enable listening on network
    #enablenetwork=[on|off]                 #default: off
    
    #Lowest adapter number to start with, e.g. /dev/dvb/adapter5/frontend0
    #first_adapter=5
    
    #Call attach script when new device appears
    #device_attach=[PATH_TO_SCRIPT] [PARAMETER|DEVID]       #"DEVID" will automatically be replaced with the device ID
    device_attach=/usr/bin/sundtek_attach
    
    #Call detach script when device disappears
    #device_detach=[PATH_TO_SCRIPT] [PARAMETER|DEVID]       #"DEVID" will automatically be replaced with the device ID
    device_detach=/usr/bin/sundtek_detach
    
    
    {% if sundtek_devices is definded %}
    {% for device in sundtek_devices.local %}
    {% if device.serial is defined %}
    [{{ device.serial }}]
    
    #Infrared protocol to use
    #ir_protocol=[RC5|NEC|RC6]                      #default: NEC
    {% if device.ir_protocol|default("") %}
    ir_protocol={{ device.ir_protocol }}
    {% endif %}
    
    #Keymap to use, e.g. "/lib/udev/rc_keymaps/vp702x"
    #rcmap=[PATH_TO_KEYMAP]                 #default: keymap which comes with the device
    {% if device.rcmap|default("") %}
    rcmap={{ device.rcmap }}
    {% endif %}
    
    #Choose initial DVB mode for hybrid DVB-T/DVB-C devices only
    #initial_dvb_mode=[DVBC|DVBT]
    {% if device.initial_dvb_mode|default("") %}
    initial_dvb_mode={{ device.dvb_mode }}
    {% endif %}
    
    #Call attach script when new device appears
    #device_attach=[PATH_TO_SCRIPT] [PARAMETER|DEVID]       #"DEVID" will automatically be replaced with the device ID
    device_attach=/usr/bin/sundtek_attach
    
    #Call detach script when device disappears
    #device_detach=[PATH_TO_SCRIPT] [PARAMETER|DEVID]       #"DEVID" will automatically be replaced with the device ID
    device_detach=/usr/bin/sundtek_detach
    
    {% if device.capabilites.analog_tv %}
    disable_analogtv=1                                      #disable initialization of analogTV Frontend
    {% endif %}
    
    {% endif %}
    {% endfor %}
    {% endif %}
    

7.36 template-test

---

- name: show vars
  debug: 
    var: '{{ system }}'

- name: test templates
  template:
    src: templates/test.j2
    dest: /tmp/test.txt
{{ ansible_managed | comment }}

Section "ServerLayout"
    Identifier     "Layout0"
    Screen      0  "Screen0"
    {% if system.x11.dualhead.enabled %}
    Screen      1  "Screen1" RightOf "Screen0"
    {% endif %}
    InputDevice    "Keyboard0" "CoreKeyboard"
    InputDevice    "Mouse0" "CorePointer"
EndSection

Section "InputDevice"
    # generated from default
    Identifier     "Mouse0"
    Driver         "mouse"
    Option         "Protocol" "auto"
    Option         "Device" "/dev/psaux"
    Option         "Emulate3Buttons" "no"
    Option         "ZAxisMapping" "4 5"
EndSection

# ignore devices with eventlircd tag 
# ENV{ID_INPUT.tags}+="eventlircd"
# must be set by an udev rule
Section "InputClass"
    Identifier "ignore eventlircd devices"
    MatchTag "eventlircd"
    Option "Ignore" "True"
EndSection



Section "InputDevice"
    # generated from default
    Identifier     "Keyboard0"
    Driver         "kbd"
EndSection


Section "Monitor"
    Identifier     "Monitor0"
    VendorName     "Unknown"
    ModelName      "Unknown"
{% if system.x11.display.0.default == "VGA2Scart_4_3" or system.x11.display.0.default == "VGA2Scart_16_9" %}
    HorizSync       14-17
    VertRefresh     49-61
    {% if system.x11.display.0.default == "VGA2Scart_4_3" %}
    Modeline "VGA2Scart_4_3" 13.875 720 744 808 888 576 580 585 625 -HSync -Vsync interlace
    {% elif system.x11.display.0.default == "VGA2Scart_16_9" %}
    Modeline "VGA2Scart_16_9" 19 1024 1032 1120 1216 576 581 586 625 -Hsync -Vsync interlace
    {% endif %}
{% endif %}
    Option         "DPMS"
    Option         "ExactModeTimingsDVI" "True"
EndSection


{% if system.x11.dualhead.enabled == "1" %}
Section "Monitor"
    Identifier     "Monitor1"
    VendorName     "Unknown"
    ModelName      "Unknown"
{% if system.x11.display.1.default in ("VGA2Scart_4_3",  "VGA2Scart_16_9") %}
    HorizSync       14-17
    VertRefresh     49-61
    {% if system.x11.display.1.default == "VGA2Scart_4_3" %}
    Modeline "VGA2Scart_4_3" 13.875 720 744 808 888 576 580 585 625 -HSync -Vsync interlace
    {% elif system.x11.display.1.default == "VGA2Scart_16_9" %}
    Modeline "VGA2Scart_16_9" 19 1024 1032 1120 1216 576 581 586 625 -Hsync -Vsync interlace
    {% endif %}
    Option         "DPMS"
    Option         "ExactModeTimingsDVI" "True"
{% endif %}
EndSection
{% endif %}

Section "Device"
    Identifier     "Device0"
{% if system.hardware.nvidia.detected %}
    Driver         "nvidia"
    VendorName     "NVIDIA Corporation"
{% endif %}
    Screen          0
    Option         "DPI" "100x100"
{% if system.hardware.nvidia.busid %}
    BusID          "PCI: {{ system.hardware.nvidia.busid }}"
{% endif %}
    Option         "NoLogo" "True"
    Option         "UseEvents" "True"
    Option         "TripleBuffer" "False"
    Option         "AddARGBGLXVisuals" "True"
    Option         "TwinView" "0"
    Option         "DynamicTwinView" "0"
    Option         "OnDemandVBlankinterrupts" "on"
    Option         "FlatPanelProperties" "Scaling = Native"
EndSection

{% if system.x11.dualhead.enabled == "1" %}
Section "Device"
    Identifier     "Device1"
    {% if system.hardware.nvidia.detected %}
    Driver         "nvidia"
    VendorName     "NVIDIA Corporation"
    {% endif %}
    Screen          1
    {% if system.hardware.nvidia.busid %}
    BusID          "PCI: {{ system.hardware.nvidia.busid }}"
    {% endif %}
    Option         "NoLogo" "True"
    Option         "UseEvents" "True"
    Option         "TripleBuffer" "False"
    Option         "AddARGBGLXVisuals" "True"
    Option         "TwinView" "0"
    Option         "DynamicTwinView" "0"
EndSection
{% endif %}


Section "Screen"
    Identifier     "Screen0"
    Device         "Device0"
    Monitor        "Monitor0"
    DefaultDepth    24
    SubSection     "Display"
        Depth       24
{% if system.x11.display.0.default is defined and system.x11.display.0.default %}
        Modes      "{{ system.x11.display.0.default }}"{% for mode in system.x11.display.0.mode %}{% if mode != system.x11.display.0.default %} "{{ mode }}"{% endif %}{% endfor %}

{% elif system.hardware.nvidia.detected == 1 %}
        Modes      "nvidia-auto-select"
{% endif %}
    EndSubSection
{% if system.x11.display.0.default or system.x11.default %}
    {% if system.x11.display.0.device is defined and system.x11.display.0.device %}
    Option         "ConnectedMonitor" {{ system.x11.display.0.device }}
    {% else %}
    Option         "ConnectedMonitor" {{ system.x11.default }}
    {% endif %}
    #     Option         "ConnectedMonitor" "<?cs if:(?system.x11.display.0.device) ?><?cs call:fix_display_name(system.x11.display.0.device) ?><?cs else ?><?cs var:system.x11.default ?><?cs /if ?><?cs if:(?system.x11.dualhead.enabled && system.x11.dualhead.enabled == 1) ?>, <?cs call:fix_display_name(system.x11.display.1.device) ?><?cs /if ?>"
    #Option         "ConnectedMonitor"
    "<?cs if:(?system.x11.display.0.device) ?>
          <?cs call:fix_display_name(system.x11.display.0.device) ?>
     <?cs else ?>
          <?cs var:system.x11.default ?>
     <?cs /if ?>
     <?cs if:(?system.x11.dualhead.enabled && system.x11.dualhead.enabled == 1) ?>, <?cs call:fix_display_name(system.x11.display.1.device) ?><?cs /if ?>"
#     Option         "UseDisplayDevice" "<?cs if:(?system.x11.display.0.device) ?><?cs call:fix_display_name(system.x11.display.0.device) ?><?cs else ?><?cs var:system.x11.default ?><?cs /if ?>"
# <?cs /if ?>
# <?cs if:(?system.hardware.nvidia.0.edid && system.hardware.nvidia.0.edid == "1") ?>
#     Option         "CustomEDID" "<?cs call:fix_display_name(system.x11.display.0.device) ?>:/etc/X11/edid.0.yavdr"
# <?cs /if ?>
# <?cs if:(system.hardware.nvidia.detected == 1 && ?system.x11.display.0.device) ?>
#     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.0.device) ?>: <?cs var:system.x11.display.0.default ?> { ViewPortIn=<?cs var:system.x11.display.0.viewport.in.x ?>x<?cs var:system.x11.display.0.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.0.viewport.out.x ?>x<?cs var:system.x11.display.0.viewport.out.y ?>+<?cs var:system.x11.display.0.viewport.out.plusx ?>+<?cs var:system.x11.display.0.viewport.out.plusy ?> }"
# <?cs each:mode = system.x11.display.0.mode ?><?cs if:(mode != system.x11.display.0.default) ?>
#     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.0.device) ?>: <?cs var:mode ?> { ViewPortIn=<?cs var:system.x11.display.0.viewport.in.x ?>x<?cs var:system.x11.display.0.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.0.viewport.out.x ?>x<?cs var:system.x11.display.0.viewport.out.y ?>+<?cs var:system.x11.display.0.viewport.out.plusx ?>+<?cs var:system.x11.display.0.viewport.out.plusy ?> }"<?cs /if ?><?cs /each ?> 
{% endif %}
EndSection

{% if system.x11.dualhead.enabled == "1" %}
Section "Screen"

     Identifier     "Screen1"
     Device         "Device1"
     Monitor        "Monitor1"
     DefaultDepth    24
     SubSection     "Display"
         Depth       24
{% if system.x11.display.0.default is defined and system.x11.display.0.default %}
         Modes      "{{ system.x11.display.1.default }}"{% for mode in system.x11.display.1.mode %}{% if mode != system.x11.display.1.default %} "{{ mode }}"{% endif %}{% endfor %}

{% elif system.hardware.nvidia.detected == "1" %}
         Modes      "nvidia-auto-select"
{% endif %}
      EndSubSection

# <?cs if:(?system.x11.display.1.default && system.x11.display.1.default != "" && system.x11.display.1.default != "disabled") ?>
#     Option         "UseDisplayDevice" "<?cs call:fix_display_name(system.x11.display.1.device) ?>"
# <?cs /if ?>
# <?cs if:(?system.hardware.nvidia.1.edid && system.hardware.nvidia.1.edid == "1") ?>
#     Option         "CustomEDID" "<?cs call:fix_display_name(system.x11.display.1.device) ?>:/etc/X11/edid.1.yavdr"
# <?cs /if ?>
# <?cs if:(system.hardware.nvidia.detected == 1 && ?system.x11.display.1.device) ?>
#     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.1.device) ?>: <?cs var:system.x11.display.1.default ?> { ViewPortIn=<?cs var:system.x11.display.1.viewport.in.x ?>x<?cs var:system.x11.display.1.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.1.viewport.out.x ?>x<?cs var:system.x11.display.1.viewport.out.y ?>+<?cs var:system.x11.display.1.viewport.out.plusx ?>+<?cs var:system.x11.display.1.viewport.out.plusy ?> }"
# <?cs each:mode = system.x11.display.1.mode ?><?cs if:(mode != system.x11.display.1.default) ?>
#     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.1.device) ?>: <?cs var:mode ?> { ViewPortIn=<?cs var:system.x11.display.1.viewport.in.x ?>x<?cs var:system.x11.display.1.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.1.viewport.out.x ?>x<?cs var:system.x11.display.1.viewport.out.y ?>+<?cs var:system.x11.display.1.viewport.out.plusx ?>+<?cs var:system.x11.display.1.viewport.out.plusy ?> }"<?cs /if ?><?cs /each ?>
# <?cs /if ?>
EndSection
{% endif %}

{% if not system.x11.use_compositing %}
Section "Extensions"
# if not open-gl OSD is needed (e.g. for vdr-sxfe):
    Option         "Composite" "Disable"
EndSection
{% endif %}
foo:
  - bar
  - baz
  - spam

system:
  hardware:
    nvidia:
      detected: "1"
      busid: "000:2304:234"
  x11:
    dualhead:
      enabled: "0"
    display:
      0:
        mode:
          - "1920x1080_50"
        default: "nvidia-auto"

      1:
        mode:
          - "1280x720_60"

7.37 wakeup

7.37.1 defaults

---
# file roles/wakeup/defaults/main.yml
wakeup_method: "acpiwakeup"

7.37.2 tasks

---
# file roles/wakeup/tasks/main.yml

- name: install and configure vdr-addon-acpiwakeup
  apt:
    name: vdr-addon-acpiwakeup
    state: present
  when: wakeup_method == "acpiwakeup"
      
- name: expand template for /etc/vdr/vdr-addon-acpiwakeup.conf
  template:
    src: templates/vdr-addon-acpiwakeup.conf.j2
    dest: /etc/vdr/vdr-addon-acpiwakeup.conf

7.37.3 templates

{{ ansible_managed | comment }}

# Activate/deactivate ACPIWakeup with yes/no:
ACPI_ENABLED="{% if wakeup_method == 'acpiwakeup' %}yes{% else %}no{% endif %}"

# How many minutes should the machine wake up before the timer starts:
ACPI_START_AHEAD=5

# If you want your VDR machine to wakeup in regular intervals (i.e. for
# updating EPG data), specify the days of the week and the wakeup time.
#
# Days of the week for regular wakeup (not set=Disabled, 1=Monday...7=Sunday)
# ACPI_REGULAR_DAYS="1 2 3 4 5 6 7"

# Wakeup time
#ACPI_REGULAR_TIME=01:00  # HH:MM

7.38 grub-config

7.38.1 default variables

system:
  shutdown: poweroff
grub:
  timeout: 0
  boot_options: quiet nosplash

7.38.2 tasks

---

- name: custom grub configuration for timeout and reboot halt
  template:
    src: templates/50_custom.j2
    dest: /etc/grub.d/50_custom
    mode: '0775'
  notify: [ 'Update GRUB' ]

# TODO: add special case if plymouth is used
- name: let the system boot quietly
  lineinfile:
      dest: /etc/default/grub
      state: present
      regexp: '^(GRUB_CMDLINE_LINUX_DEFAULT=")'
      line: '\1{{ grub.boot_options}}"'
      backrefs: yes
  notify: [ 'Update GRUB' ]

7.38.3 templates

#!/bin/sh
{{ ansible_managed | comment }}

exec tail -n +3 $0

{% if system.shutdown is defined and system.shutdown == 'reboot' %}
menuentry "PowerOff" {
    halt
}
{% endif %}

if [ "${recordfail}" = 1 ]; then
    set timeout={{ 3 if grub.timeout < 3 else grub.timeout }}
else
    set timeout={{ grub.timeout if grub.timeout is defined else 0 }}
fi

7.38.4 handlers

---

- name: Update Initramfs
  command: "update-initramfs -u"
  failed_when: ('error' in initramfs_register_update.stderr)
  register: initramfs_register_update

- name: Update GRUB
  command: update-grub
  failed_when: ('error' in grub_register_update.stderr)
  register: grub_register_update

  # TODO: Do we need to use grub-set-default?
  # https://github.com/yavdr/yavdr-utils/blob/master/events/actions/update-grub

8 Modules

This section contains custom modules for the yaVDR Playbooks. They are used to collect facts about the system and configure applications and daemons.

8.1 hardware_facts.py

# This Module collects the vendor- and device ids for USB- and PCI(e)-devices and currently loaded kernel modules.
DOCUMENTATION = '''
  ---
  module: hardware_facts
  short_description: collects facts for kernel modules, usb and pci devices
  description:
       - This Module collects the vendor- and device ids for USB- and PCI(e)-devices and
         currently loaded kernel modules.
  options:
      usb:
          required: False
          default: True
          description:
            - return a list of vendor- and device ids for usb devices in '04x:04x' notation

    pci:
        required: False
        default: True
        description:
          - return a list of vendor- and device ids for pci devices in '04x:04x' notation

    modules:
        required: False
        default: True
        description:
          - return a list of currently loaded kernel modules

    gpus:
        required: False
        default: True
        description:
          - return a list of devices of the pci gpu class (0x030000)

    acpi_power_modes:
        required: False
        default: True
        description:
          - return a list of supported acpi power saving modes
notes:
   - requres python-pyusb and python-kmodpy
requirements: [ ]
author: "Alexander Grothe <seahawk1986@gmx.de>"
'''

EXAMPLES = '''
- name: get information about usb and pci hardware and loaded kernel modules
  hardware_facts:
    usb: True
    pci: True
    modules: True
    acpi_power_modes: True
- debug:
    var: usb
- debug:
    var: pci
- debug:
    var: modules
- debug:
    var: gpus
'''

import glob
import json
import os
import sys
import usb.core
from collections import namedtuple
from itertools import chain

import kmodpy
from ansible.module_utils.basic import *


PCIDevice = namedtuple("PCIDevice", 'idVendor idProduct idClass pciPath')

vendor_dict = {
    0x10de: 'nvidia',
    0x8086: 'intel',
    0x1002: 'amd',
    0x80ee: 'virtualbox',
    }

def get_pci_devices():
    for device in chain(glob.glob('/sys/devices/pci*/*:*:*/'), glob.glob('/sys/devices/pci*/*:*:*/*:*:*/')):
        try:
            with open(os.path.join(device, 'device')) as d:
                product_id = int(d.read().strip(), 16)
            with open(os.path.join(device, 'vendor')) as d:
                vendor_id = int(d.read().strip(), 16)
            with open(os.path.join(device, 'class')) as d:
                class_id = int(d.read().strip(), 16)
            yield PCIDevice(idVendor=vendor_id, idProduct=product_id, idClass=class_id, pciPath=device)
        except IOError:
            pass

def format_device_list(iterator):
    return ["{:04x}:{:04x}".format(d.idVendor, d.idProduct) for d in iterator]

def format_gpu_device_list(iterator):
    def get_entries(iterator):
        for d in iterator:
            if d.idClass == 0x030000:
                yield {"VendorName": vendor_dict.get(d.idVendor, "unknown"),
                       "VendorID": d.idVendor, "ProductID": d.idProduct}
    return [entry for entry in get_entries(iterator)]

def list_acpi_power_modes():
    acpi_power_modes = []
    try:
        with open('/sys/power/state') as f:
            acpi_power_modes = [l for l in f.readline().split()]
    except IOError:
        pass
    return acpi_power_modes

arg_specs = {
    'usb': dict(default=True, type='bool', required=False),
    'pci': dict(default=True, type='bool', required=False),
    'modules': dict(default=True, type='bool', required=False),
    'gpus': dict(default=True, type='bool', required=False),
    'acpi_power_modes': dict(default=True, type='bool', required=False),
    }


def main():
    module = AnsibleModule(argument_spec=arg_specs, supports_check_mode=True,)
    collect_usb = module.params['usb']
    collect_pci = module.params['pci']
    collect_modules = module.params['modules']
    collect_gpus = module.params['gpus']
    collect_acpi_power_modes = module.params['acpi_power_modes']

    usb_devices = []
    pci_devices = []
    modules = []
    gpus = []
    nvidia_detected = False
    intel_detected = False
    amd_detected = False
    virtualbox_detected = False
    acpi_power_modes = []

    if collect_usb:
        usb_devices = format_device_list(usb.core.find(find_all=True))

    if collect_pci:
        pci_devices = format_device_list(get_pci_devices())

    if collect_modules:
        k = kmodpy.Kmod()
        modules = [m[0] for m in k.loaded()]

    if collect_gpus:
        gpus = format_gpu_device_list(get_pci_devices())
        nvidia_detected = any((True for gpu in gpus if gpu['VendorName'] == 'nvidia'))
        intel_detected = any((True for gpu in gpus if gpu['VendorName'] == 'intel'))
        amd_detected = any((True for gpu in gpus if gpu['VendorName'] == 'amd'))
        virtualbox_detected = any((True for gpu in gpus if gpu['VendorName'] == 'virtualbox'))

    if collect_acpi_power_modes:
        acpi_power_modes = list_acpi_power_modes()

    data = {'usb': usb_devices, 'pci': pci_devices, 'modules': modules, 'gpus': gpus,
            'acpi_power_modes': acpi_power_modes, 'nvidia_detected': nvidia_detected,
            'intel_detected': intel_detected, 'amd_detected': amd_detected,
            'virtualbox_detected': virtualbox_detected}
    module.exit_json(changed=False, ansible_facts=data, msg=data)


if __name__ == '__main__':  
    main()

8.2 pci_facts.py

# This module parses the output of lspci for detailed information about available (sub) devices.
DOCUMENTATION = '''
  ---
  module: pci_facts
  short_description: parses lspci output for detailed (sub) devices data
  description:
      - This module parses the output of lspci for detailed information about available (sub) devices.
      - returns a list with a dict for each device

notes:
    - requires lspci (package pciutils)

'''

EXAMPLES = '''
- name: get detailled pci device infos
  pci_facts:

- debug:
    var: pci_devices
'''


import argparse
import shlex
import subprocess
from collections import namedtuple

from ansible.module_utils.basic import *

def convert2hex(arg):
    arg = arg.strip('"')
    if arg:
        return int(arg, 16)
    else:
        return None

def parse_lspci_data():
    parser = argparse.ArgumentParser()
    parser.add_argument('-r', '--revision', help='revision', type=convert2hex)
    parser.add_argument('-p', '--progif', help='proginf', type=convert2hex)
    parser.add_argument('slot')
    parser.add_argument('device_class', type=convert2hex)
    parser.add_argument('vendor_id', type=convert2hex)
    parser.add_argument('device_id', type=convert2hex)
    parser.add_argument('sub_vendor_id', type=convert2hex)
    parser.add_argument('sub_device_id', type=convert2hex)
    parser.add_argument('other', nargs='*', default=[])

    devices = []
    for line in subprocess.check_output(['lspci', '-nm'], universal_newlines=True).splitlines():
        args = parser.parse_args(args=shlex.split(line))
        devices.append(vars(args))
    return devices


def main():
    arg_specs = {}
    module = AnsibleModule(argument_spec=arg_specs, supports_check_mode=True,)
    try:
        pci_devices = parse_lspci_data()
    except:
        module.fail_json(msg="Something fatal happened")
    data = {'pci_devices': pci_devices}
    module.exit_json(changed=False, ansible_facts=data, msg=data)

if __name__ == '__main__':
    main()

8.3 satip_facts.py

DOCUMENTATION = '''
---
module: hardware_facts
short_description: "check if at least one SAT>IP server responds on the network"
description:
     - This script sends a multicast message and awaits responses by Sat>IP servers.
       Returns a list of detected SAT>IP servers with their name and capabilites.
'''
EXAMPLES = '''
- name: "detect SAT>IP Server on the network"
  action: satip_facts

- debug:
    var: satip_devices
'''

import json
import socket
import sys
import time
import xml.etree.ElementTree as ET
import requests
from contextlib import contextmanager
from ansible.module_utils.basic import *

SSDP_BIND = "0.0.0.0"
SSDP_ADDR = "239.255.255.250"
SSDP_PORT = 1900
# SSDP_MX = max delay for server response
# a value of 2s is recommended by the SAT>IP specification 1.2.2
SSDP_MX = 2
SSDP_ST = "urn:ses-com:device:SatIPServer:1"

ssdpRequest = "\r\n".join((
    "M-SEARCH * HTTP/1.1",
    "HOST: %s:%d" % (SSDP_ADDR, SSDP_PORT),
    "MAN: \"ssdp:discover\"",
    "MX: %d" % (SSDP_MX),
    "ST: %s" % (SSDP_ST),
    "\r\n"))

@contextmanager
def socket_manager(*args, **kwargs):
    """provide a context manager for socket"""
    sock = socket.socket(*args, **kwargs)
    sock.setblocking(False)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    try:
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
    except socket.error:
        pass
    sock.settimeout(SSDP_MX + 0.5)
    sock.bind((SSDP_BIND, SSDP_PORT))
    try:
        yield sock
    finally:
        sock.close()


def parse_satip_xml(data):
    """ Parse SAT>IP XML data.
    Args:
        data (str): XML input data..
    Returns:
        dict: Parsed SAT>IP device name and frontend information.
    """
    result = {'name': '', 'frontends': {}}
    if data:
        root = ET.fromstring(data)
        name = root.find('.//*/{urn:schemas-upnp-org:device-1-0}friendlyName')
        result['name'] = name.text
        satipcap = root.find('.//*/{urn:ses-com:satip}X_SATIPCAP')
        if satipcap is None:
            raise ValueError("Invalid SAT>IP device description")
        caps = {}
        for system in satipcap.text.split(","):
            cap = system.split("-")
            if cap:
                count = int(cap[1])
                if cap[0] in caps:
                    count = count + caps[cap[0]]
                caps[cap[0]] = count
        result['frontends'] = caps
    return result


def main():
    description_urls = []
    device_list = []
    module = AnsibleModule(argument_spec={}, supports_check_mode=True,)
    with socket_manager(socket.AF_INET, socket.SOCK_DGRAM) as sock:
        # according to Sat>IP Specification 1.2.2, p. 20
        # a client should send three requests within 100 ms with a ttl of 2

        for _ in range(3):
            sock.sendto(ssdpRequest, (SSDP_ADDR, SSDP_PORT))
            time.sleep(0.03)
        try:
            response = sock.recv(1024)
            if response and "SERVER:" in response:
                for line in response.splitlines():
                    if "LOCATION" in line:
                        url = line.split()[-1].strip()
                        if url not in description_urls:
                            description_urls.append(url)
                            info = requests.get(url, timeout=2)
                            device_list.append(parse_satip_xml(info.text))
            else:
                raise ValueError('No satip server detected')
        except (socket.timeout, ValueError, ET.ParseError):
            pass

    module.exit_json(changed=False, ansible_facts={'satip_devices': device_list})

if __name__ == '__main__':
    main()

8.4 xrandr_facts.py

  • [ ] support multiple screens (-d :0.0 .. :0.n)
#!/usr/bin/env python2
from __future__ import print_function
import ast
import binascii
import csv
import os
import re
import subprocess
from collections import namedtuple
from glob import glob

from ansible.module_utils.basic import AnsibleModule

DOCUMENTATION = '''
---
module: xrandr_facts
short_description: "gather facts about connected monitors and available modelines"
description:
     - This module needs a running x-server on a given display in
       order to successfully call xrandr. Returns the dictionary
       "xrandr", wich contains all screens with output states,
       connected displays, EDID info and their modes and a
       recommendation for the best fitting tv mode, the dictionary
       "xorg" with a recommendation for the primary and secondary
       output and a "drm" dictionary whose "primary" key associates
       the primary device name of the drm subsystem with the one from
       the xrandr output by comparing the edid data and a list of
       "ignored_devices". Note that the proprietary nvidia driver
       doesn't support KMS/drm, so in this case the dictionary is
       always empty.
options:
    display:
        required: False
        default: ":0"
        description:
          - the DISPLAY variable to use when calling xrandr
    preferred_outputs:
        required: False
        default: ["HDMI", "DP", "eDP", "DVI", "VGA", "TV", "Virtual"]
        description:
          - ranking of the preferred display connectors
    preferred_refreshrates:
        required: False
        default: ["50", "60", "75", "30", "25"]
        description:
          - ranking of the preferred display refreshrate
    preferred_resolutions:
        required: False
        default: ["7680x4320", "3840x2160", "1920x1080", "1280x720", "720x576"]
        description:
           - ranking of the preferred display resolutions
    write_edids:
        required: False
        default: True
        description:
           - write edid data to /etc/X11/edid.{connector}.bin
           - the dictionary "drm" can only be filled with data if write_edids is enabled
'''
EXAMPLES = '''
- name: "collect facts for connected displays"
  action: xserver_facts
    display: ":0"

- debug:
    var: xrandr

- debug:
    var: xorg

- debug:
    var: drm
'''

ARG_SPECS = {
    'display': dict(default=":0", type='str', required=False),
    'preferred_outputs': dict(
        default=["HDMI", "DP", "eDP", "DVI", "VGA", "TV", "Virtual"], type='list', required=False),
    'preferred_refreshrates': dict(
        default=[50, 60, 75, 30, 25], type='list', required=False),
    'preferred_resolutions': dict(
        default=[
            "7680x4320", "3840x2160", "1920x1080", "1280x720", "720x576"],
        type='list', required=False),
    'write_edids': dict(default=True, type='bool', required=False),
}

SCREEN_REGEX = re.compile(r"^(?P<screen>Screen\s\d+:)(?:.*)")
CONNECTOR_REGEX = re.compile(
    r"^(?P<connector>.*-?\d+)\s(?P<connection_state>connected|disconnected)\s(?P<primary>primary)?")
MODE_REGEX = re.compile(r"^\s+(?P<resolution>\d{3,}x\d{3,}).*")

Mode = namedtuple('Mode', ['connection', 'resolution', 'refreshrate'])


def check_for_screen(line):
    """check line for screen information"""
    match = re.match(SCREEN_REGEX, line)
    if match:
        return match.groupdict()['screen']


def check_for_connection(line):
    """check line for connection name and state"""
    match = re.match(CONNECTOR_REGEX, line)
    connector = None
    is_connected = False
    if match:
        match = match.groupdict()
        connector = match['connector']
        is_connected = True if match['connection_state'] == 'connected' else False
    return connector, is_connected


def get_indentation(line):
    """return the number of leading whitespace characters"""
    return len(line) - len(line.lstrip())


def sort_mode(mode):
    """rate modes by several criteria"""
    connection_score = 0
    rrate_score = 0
    resolution_score = 0
    preferred_rrates = module.params['preferred_refreshrates']
    # [50, 60]
    preferred_resolutions = module.params['preferred_resolutions']
    # ["7680x4320", "3840x2160", "1920x1080", "1280x720", "720x576"]
    preferred_outputs = module.params['preferred_outputs']
    # ["HDMI", "DP", "DVI", "VGA"]
    if mode.refreshrate in preferred_rrates:
        rrate_score = len(preferred_rrates) - preferred_rrates.index(mode.refreshrate)
    if mode.resolution in preferred_resolutions:
        resolution_score = len(preferred_resolutions) - preferred_resolutions.index(mode.resolution)
    x_resolution, y_resolution = (int(n) for n in mode.resolution.split('x'))
    connection = mode.connection.split('-')[0]
    if connection in preferred_outputs:
        connection_score = len(preferred_outputs) - preferred_outputs.index(connection)
    return (rrate_score, resolution_score, x_resolution, y_resolution, connection_score)


def parse_xrandr_verbose(iterator):
    """parse the output of xrandr --verbose using an iterator delivering single lines"""
    xorg = {}
    is_connected = False
    for line in iterator:
        if line.startswith('Screen'):
            screen = check_for_screen(line)
            xorg[screen] = {}
        elif 'connected' in line:
            connector, is_connected = check_for_connection(line)
            xorg[screen][connector] = {
                'is_connected': is_connected,
                'EDID': '',
                'modes': {},
                'preferred': '',
                'current': '',
                'auto': '',
            }
        elif is_connected and 'EDID:' in line:
            edid_str = ""
            outer_indentation = get_indentation(line)
            while True:
                line = next(iterator)
                if get_indentation(line) > outer_indentation:
                    edid_str += line.strip()
                else:
                    break
            xorg[screen][connector]['EDID'] = edid_str
        elif is_connected and "MHz" in line and "Interlace" not in line:
            match = re.match(MODE_REGEX, line)
            if match:
                match = match.groupdict()
                preferred = bool("+preferred" in line)
                current = bool("*current" in line)

                while True:
                    line = next(iterator)
                    if line.strip().startswith('v:'):
                        refresh_rate = ast.literal_eval(line.split()[-1][:-2])
                        rrate = int(round(refresh_rate))
                        if xorg[screen][connector]['modes'].get(match['resolution']) is None:
                            xorg[screen][connector]['modes'][match['resolution']] = []
                        if rrate not in xorg[screen][connector]['modes'][match['resolution']]:
                            xorg[screen][connector]['modes'][match['resolution']].append(rrate)
                        if preferred:
                            xorg[screen][connector]['preferred'] = "{}_{}".format(
                                match['resolution'], rrate)
                        if current:
                            xorg[screen][connector]['current'] = "{}_{}".format(
                                match['resolution'], rrate)
                        break
    return xorg


def parse_edid_data(edid):
    vendor = "Unknown"
    model = "Unknown"
    modelines = []
    try:
        data = subprocess.check_output("parse-edid < {}".format(edid),
                                       shell=True, universal_newlines=True)
    except subprocess.CalledProcessError:
        pass
    else:
        for line in data.splitlines():
            line = line.strip()
            if "VendorName" in line:
                vendor = line.split('"')[1]
            if "ModelName" in line:
                model = line.split('"')[1]
            if "Modeline" in line:
                print(line)
                _, _, line = line.split('"', 2)
                if not line:
                    print("no timing information")
                    continue
                try:
                    FF, H1, H2, H3, H4, V1, V2, V3, V4, FLAGS = line.split(None, 9)
                except ValueError:
                    print("invalid timing information")
                    continue
                print(FF, H1, H2, H3, H4, V1, V2, V3, V4, FLAGS)
                refresh = round(float(FF) * 1E6 / (float(H4) * float(V4)))
                interlaced = "interlaced" in FLAGS
                if interlaced:
                    refresh /= 2
                refresh = int(refresh)
                modeline_name = '"{}x{}_{}{}"'.format(H1, V1, refresh, "i" if interlaced else '')
                modelines.append(" ".join(("Modeline", modeline_name,
                                          FF, H1, H2, H3, H4, V1, V2, V3, V4, FLAGS)))
    return vendor, model, modelines


def collect_nvidia_data():
    BusID_RE = re.compile((
        r'(?P<domain>[0-9a-fA-F]+)'
        r':'
        r'(?P<bus>[0-9a-fA-F]+)'
        r':'
        r'(?P<device>[0-9a-fA-F]+)'
        r'\.'
        r'(?P<function>[0-9a-fA-F]+)'
    ))
    try:
        data = subprocess.check_output(["nvidia-smi", "--query-gpu=name,pci.bus_id",
                                        "--format=csv", "-i0"], universal_newlines=True)
    except subprocess.CalledProcessError:
        pass
    except OSError:
        # nvidia-smi is not installed
        pass
    else:
        for row in csv.DictReader(data.splitlines(), delimiter=',', skipinitialspace=True):
            name = row['name']
            bus_id = row['pci.bus_id']
            # pci.bus_id structure as reported by nvidia-smi: "domain:bus:device.function", in hex.
            match = BusID_RE.search(bus_id)
            if match:
                domain, bus, device, function = (int(n, 16) for n in match.groups())
                bus_id = "PCI:{:d}@{:d}:{:d}:{:d}".format(bus, domain, device, function)
                return name, bus_id
    raise ValueError


def find_drm_connectors(connections):
    """
    returns a dict with the following schema (secondary may be empty):
    {
        'primary': {
            'edid': 'edid.HDMI-1.bin',
            'drm_connector': 'HDMI-A-1',
            'xrandr_connector': 'HDMI-1',
        },
        'secondary': {
            'edid': 'edid.eDP-1.bin',
            'drm_connector': 'eDP-1',
            'xrandr_connector': 'eDP-1',
        }
        'ignored_outputs': ['HDMI-A-2', 'DP-1']
    }
    """
    STATUS_GLOB = '/sys/class/drm/card0*/status'
    CONNECTOR_RE = re.compile('card0-(?P<connector>[^/]+)/status')

    def read_edid_bytes(edid_file):
        edid_bytes = b''
        try:
            with open(edid_file, 'rb') as f:
                edid_bytes = f.read()
        except IOError:
            pass
        return edid_bytes

    xrandr_edid_bytes = read_edid_bytes(connections.get('primary', {}).get('edid', ""))
    secondary_xrandr_edid_bytes = read_edid_bytes(connections.get('secondary', {}).get('edid', ''))

    drm = {'primary': {}, 'secondary': {}, 'ignored_outputs': []}
    for status_p in glob(STATUS_GLOB):
        match = re.search(CONNECTOR_RE, status_p)
        if match:
            drm_connector = match.group('connector')
        else:
            continue

        try:
            with open(status_p) as f:
                connected = f.read().strip() == 'connected'
        except IOError:
            continue

        if connected:
            edid = read_edid_bytes(os.path.join(
                os.path.dirname(status_p), 'edid'))
            if edid:
                if edid == xrandr_edid_bytes:
                    drm['primary'] = {
                        'edid': os.path.basename(connections['primary'].get('edid', "")),
                        'drm_connector': drm_connector,
                        'xrandr_connector': connections['primary'].get('connector',''),
                    }
                    continue
                if secondary_xrandr_edid_bytes:
                    drm['secondary'] = {
                        'edid': os.path.basename(connections.get('secondary').get('edid', '')),
                        'drm_connector': drm_connector,
                        'xrandr_connector': connections['secondary'].get('connector',''),
                    }
                    continue
        drm['ignored_outputs'].append(drm_connector)
    return drm


def output_data(data, write_edids=True):
    result = {}
    drm = {}
    if data:
        modes = []
        for _, screen_data in data.items():
            for connector, connection_data in screen_data.items():
                if connection_data.get('EDID') and write_edids:
                    with open('/etc/X11/edid.{}.bin'.format(connector), 'wb') as edid:
                        edid.write(binascii.a2b_hex(connection_data['EDID']))
                for resolution, refreshrates in connection_data['modes'].items():
                    for refreshrate in refreshrates:
                        modes.append(Mode(connector, resolution, refreshrate))
        if modes:
            try:
                gpu_name, bus_id = collect_nvidia_data()
            except ValueError:
                gpu_name = None
                bus_id = None

            def create_entry(my_dict, name, connector, resolution, refreshrate, vendor, model, modelines):
                my_dict[name] = {
                    'connector': connector,
                    'resolution': resolution,
                    'refreshrate': refreshrate,
                    'edid': '/etc/X11/edid.{}.bin'.format(connector),
                    'mode': "{}_{}".format(resolution, refreshrate),
                    'vendor': vendor,
                    'model': model,
                    'modelines': modelines,
                }
                if gpu_name and bus_id:
                    result[name]['gpu_name'] = gpu_name
                    result[name]['bus_id'] = bus_id

            connector_0, resolution_0, refreshrate_0 = max(modes, key=sort_mode)[:3]
            connector_0_edid = '/etc/X11/edid.{}.bin'.format(connector_0)
            vendor_0, model_0, modelines_0 = parse_edid_data(connector_0_edid)
            create_entry(result, 'primary', connector_0, resolution_0,
                         refreshrate_0, vendor_0, model_0, modelines_0)

            # check if additional monitors exist
            other_modes = [mode for mode in modes if mode[0] != connector_0]
            if other_modes:
                connector_1, resolution_1, refreshrate_1 = max(other_modes, key=sort_mode)[:3]
                connector_1_edid = '/etc/X11/edid.{}.bin'.format(connector_1)
                vendor_1, model_1, modelines_1 = parse_edid_data(connector_1_edid)
                create_entry(result, 'secondary', connector_1, resolution_1,
                             refreshrate_1, vendor_1, model_1, modelines_1)

            if write_edids:
                drm = find_drm_connectors(result)


    module.exit_json(changed=True if write_edids else False,
                     ansible_facts={'xrandr': data, 'xorg': result, 'drm': drm})


if __name__ == '__main__':
    module = AnsibleModule(argument_spec=ARG_SPECS, supports_check_mode=False,)
    try:
        d = subprocess.check_output(['xrandr', '-d', module.params['display'], '--verbose'],
                                    universal_newlines=True).splitlines()
    except subprocess.CalledProcessError:
        xorg_data = {}
    else:
        xorg_data = parse_xrandr_verbose(iter(d))
    output_data(xorg_data, module.params['write_edids'])

9 Handlers

- name: Reconfigure unattended upgrades with dpkg
  command: '/usr/sbin/dpkg-reconfigure --frontend noninteractive unattended-upgrades'

- name: Trigger Udev
  command: 'udevadm trigger '
  
- name: Restart Samba
  systemd:
    name: smbd.service
    state: restarted
    enabled: yes
    #masked: no
  register: samba_reload

- name: Restart NFS Kernel Server
  systemd:
    name: nfs-server.service
    state: restarted
    enabled: yes
    #masked: no
  register: nfs_reload

- name: Restart sundtek.service
  systemd:
    name: sundtek.service
    state: restarted
    enabled: yes
    masked: no

- name: Restart VDR
  systemd:
    name: vdr.service
    state: restarted
    enabled: yes
  register: vdr_restart

- name: Stop VDR
  systemd:
    name: vdr.service
    state: stopped
    enabled: yes
  register: vdr_stop

- name: Start VDR
  systemd:
    name: vdr.service
    state: started
    enabled: yes
  register: vdr_start

- name: Start yavdr-xorg
  systemd:
    daemon_reload: yes
    name: 'yavdr-xorg'
    enabled: yes
    state: started
  register: yavdr_xorg_start


- name: Stop xlogin
  systemd:
    name: xlogin@vdr.service
    state: stopped
    enabled: yes
  register: xlogin_stop

- name: Start xlogin
  systemd:
    daemon_reload: yes
    name: 'xlogin@{{ vdr.user }}'
    enabled: yes
    state: started
  register: xlogin_start

- name: Stop x
  systemd:
    name: x@vt7.service
    state: stopped
  register: x_stop

- name: reboot required
  debug:
    msg: PLEASE REBOOT YOUR SYSTEM.

Autor: Alexander Grothe <seahawk1986@gmx.de>

Created: 2019-05-06 Mo 18:26

Validate